feat: add vec3 to stem
This commit is contained in:
parent
815152d23c
commit
32426ac045
137
Cargo.lock
generated
137
Cargo.lock
generated
@ -65,26 +65,6 @@ dependencies = [
|
||||
"wasm-bindgen-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gl_matrix"
|
||||
version = "0.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df64d0245c589931a0b5a385a63e7db2aeff209bdd471df0417e0f230a4c33ae"
|
||||
dependencies = [
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.27.0"
|
||||
@ -106,12 +86,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
@ -141,6 +115,48 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "max-plantarium-sum"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"macros",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"utils",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "max-plantarium-triangle"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"macros",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"utils",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "max-plantarium-vec3"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"macros",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"utils",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
@ -152,6 +168,7 @@ name = "output"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"glam",
|
||||
"macros",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
@ -162,12 +179,6 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.80"
|
||||
@ -186,47 +197,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "random"
|
||||
version = "0.1.0"
|
||||
@ -308,20 +278,6 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sum"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"macros",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"utils",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@ -369,7 +325,6 @@ name = "utils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"gl_matrix",
|
||||
"glam",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -377,12 +332,6 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.92"
|
||||
|
@ -6,13 +6,12 @@
|
||||
export let node: Node;
|
||||
export let input: NodeInput;
|
||||
export let id: string;
|
||||
export let label: string | undefined;
|
||||
|
||||
const graph = getGraphManager();
|
||||
|
||||
let value = node?.props?.[id] ?? input.value;
|
||||
|
||||
let elementId = Math.random().toString(36).substring(7);
|
||||
export let elementId: string = `input-${Math.random().toString(36).substring(7)}`;
|
||||
|
||||
$: if (node?.props?.[id] !== value) {
|
||||
node.props = { ...node.props, [id]: value };
|
||||
@ -21,5 +20,4 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<label for="input-{elementId}">{label || id}</label>
|
||||
<Input id="input-{elementId}" {input} bind:value />
|
||||
|
@ -1,174 +1,196 @@
|
||||
<script lang="ts">
|
||||
import type { NodeInput as NodeInputType, Socket, Node as NodeType } from '@nodes/types';
|
||||
import { getContext } from 'svelte';
|
||||
import { createNodePath } from '../helpers/index.js';
|
||||
import { possibleSocketIds } from '../graph/stores.js';
|
||||
import { getGraphManager } from '../graph/context.js';
|
||||
import NodeInput from './NodeInput.svelte';
|
||||
import type {
|
||||
NodeInput as NodeInputType,
|
||||
Socket,
|
||||
Node as NodeType,
|
||||
} from "@nodes/types";
|
||||
import { getContext } from "svelte";
|
||||
import { createNodePath } from "../helpers/index.js";
|
||||
import { possibleSocketIds } from "../graph/stores.js";
|
||||
import { getGraphManager } from "../graph/context.js";
|
||||
import NodeInput from "./NodeInput.svelte";
|
||||
|
||||
export let node: NodeType;
|
||||
export let input: NodeInputType;
|
||||
export let id: string;
|
||||
export let isLast = false;
|
||||
export let node: NodeType;
|
||||
export let input: NodeInputType;
|
||||
export let id: string;
|
||||
export let isLast = false;
|
||||
|
||||
const socketId = `${node.id}-${id}`;
|
||||
const socketId = `${node.id}-${id}`;
|
||||
|
||||
const graph = getGraphManager();
|
||||
const graphId = graph.id;
|
||||
const inputSockets = graph.inputSockets;
|
||||
const graph = getGraphManager();
|
||||
const graphId = graph.id;
|
||||
const inputSockets = graph.inputSockets;
|
||||
|
||||
const setDownSocket = getContext<(socket: Socket) => void>('setDownSocket');
|
||||
const getSocketPosition =
|
||||
getContext<(node: NodeType, index: string) => [number, number]>('getSocketPosition');
|
||||
const elementId = `input-${Math.random().toString(36).substring(7)}`;
|
||||
|
||||
function handleMouseDown(ev: MouseEvent) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setDownSocket({
|
||||
node,
|
||||
index: id,
|
||||
position: getSocketPosition(node, id)
|
||||
});
|
||||
}
|
||||
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
|
||||
const getSocketPosition =
|
||||
getContext<(node: NodeType, index: string) => [number, number]>(
|
||||
"getSocketPosition",
|
||||
);
|
||||
|
||||
const leftBump = node.tmp?.type?.inputs?.[id].internal !== true;
|
||||
const cornerBottom = isLast ? 5 : 0;
|
||||
const aspectRatio = 0.5;
|
||||
function handleMouseDown(ev: MouseEvent) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setDownSocket({
|
||||
node,
|
||||
index: id,
|
||||
position: getSocketPosition(node, id),
|
||||
});
|
||||
}
|
||||
|
||||
const path = createNodePath({
|
||||
depth: 4,
|
||||
height: 12,
|
||||
y: 51,
|
||||
cornerBottom,
|
||||
leftBump,
|
||||
aspectRatio
|
||||
});
|
||||
const pathDisabled = createNodePath({
|
||||
depth: 0,
|
||||
height: 15,
|
||||
y: 50,
|
||||
cornerBottom,
|
||||
leftBump,
|
||||
aspectRatio
|
||||
});
|
||||
const pathHover = createNodePath({
|
||||
depth: 6,
|
||||
height: 18,
|
||||
y: 50.5,
|
||||
cornerBottom,
|
||||
leftBump,
|
||||
aspectRatio
|
||||
});
|
||||
const leftBump = node.tmp?.type?.inputs?.[id].internal !== true;
|
||||
const cornerBottom = isLast ? 5 : 0;
|
||||
const aspectRatio = 0.5;
|
||||
|
||||
const path = createNodePath({
|
||||
depth: 4,
|
||||
height: 12,
|
||||
y: 51,
|
||||
cornerBottom,
|
||||
leftBump,
|
||||
aspectRatio,
|
||||
});
|
||||
const pathDisabled = createNodePath({
|
||||
depth: 0,
|
||||
height: 15,
|
||||
y: 50,
|
||||
cornerBottom,
|
||||
leftBump,
|
||||
aspectRatio,
|
||||
});
|
||||
const pathHover = createNodePath({
|
||||
depth: 6,
|
||||
height: 18,
|
||||
y: 50.5,
|
||||
cornerBottom,
|
||||
leftBump,
|
||||
aspectRatio,
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="wrapper" class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)}>
|
||||
{#key id && graphId}
|
||||
{#if node?.tmp?.type?.inputs?.[id]?.external !== true}
|
||||
<div class="content" class:disabled={$inputSockets.has(socketId)}>
|
||||
<NodeInput {node} {input} {id} label={input.label} />
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
class="wrapper"
|
||||
class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)}
|
||||
>
|
||||
{#key id && graphId}
|
||||
<div class="content" class:disabled={$inputSockets.has(socketId)}>
|
||||
<label for={elementId}>{input.label || id}</label>
|
||||
{#if node?.tmp?.type?.inputs?.[id]?.external !== true}
|
||||
<NodeInput {elementId} {node} {input} {id} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
|
||||
<div class="large target" on:mousedown={handleMouseDown} role="button" tabindex="0" />
|
||||
<div class="small target" on:mousedown={handleMouseDown} role="button" tabindex="0" />
|
||||
{/if}
|
||||
{/key}
|
||||
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
|
||||
<div
|
||||
class="large target"
|
||||
on:mousedown={handleMouseDown}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
class="small target"
|
||||
on:mousedown={handleMouseDown}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100"
|
||||
width="100"
|
||||
height="100"
|
||||
preserveAspectRatio="none"
|
||||
style={`
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100"
|
||||
width="100"
|
||||
height="100"
|
||||
preserveAspectRatio="none"
|
||||
style={`
|
||||
--path: path("${path}");
|
||||
--hover-path: path("${pathHover}");
|
||||
--hover-path-disabled: path("${pathDisabled}");
|
||||
`}
|
||||
>
|
||||
<path vector-effect="non-scaling-stroke"></path>
|
||||
</svg>
|
||||
>
|
||||
<path vector-effect="non-scaling-stroke"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
transform: translateY(-0.5px);
|
||||
}
|
||||
.wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
transform: translateY(-0.5px);
|
||||
}
|
||||
|
||||
.target {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
/* background: red; */
|
||||
/* opacity: 0.1; */
|
||||
}
|
||||
.target {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
/* background: red; */
|
||||
/* opacity: 0.1; */
|
||||
}
|
||||
|
||||
.small.target {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.small.target {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.large.target {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
cursor: unset;
|
||||
pointer-events: none;
|
||||
}
|
||||
.large.target {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
cursor: unset;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:global(.hovering-sockets) .large.target {
|
||||
pointer-events: all;
|
||||
}
|
||||
:global(.hovering-sockets) .large.target {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: space-around;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: space-around;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:global(.zoom-small) .content {
|
||||
display: none;
|
||||
}
|
||||
:global(.zoom-small) .content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
svg {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
svg path {
|
||||
transition:
|
||||
d 0.3s ease,
|
||||
fill 0.3s ease;
|
||||
fill: var(--background-color);
|
||||
stroke: var(--stroke);
|
||||
stroke-width: var(--stroke-width);
|
||||
d: var(--path);
|
||||
}
|
||||
svg path {
|
||||
transition:
|
||||
d 0.3s ease,
|
||||
fill 0.3s ease;
|
||||
fill: var(--background-color);
|
||||
stroke: var(--stroke);
|
||||
stroke-width: var(--stroke-width);
|
||||
d: var(--path);
|
||||
}
|
||||
|
||||
:global(.hovering-sockets) .large:hover ~ svg path {
|
||||
d: var(--hover-path);
|
||||
}
|
||||
:global(.hovering-sockets) .large:hover ~ svg path {
|
||||
d: var(--hover-path);
|
||||
}
|
||||
|
||||
.content.disabled {
|
||||
opacity: 0.2;
|
||||
pointer-events: none;
|
||||
}
|
||||
.content.disabled {
|
||||
opacity: 0.2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.disabled svg path {
|
||||
d: var(--hover-path-disabled) !important;
|
||||
}
|
||||
.disabled svg path {
|
||||
d: var(--hover-path-disabled) !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -73,6 +73,7 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
||||
nodeIds.push("max/plantarium/random");
|
||||
nodeIds.push("max/plantarium/float");
|
||||
nodeIds.push("max/plantarium/triangle");
|
||||
nodeIds.push("max/plantarium/vec3");
|
||||
nodeIds.push("max/plantarium/output");
|
||||
nodeIds.push("max/plantarium/array");
|
||||
nodeIds.push("max/plantarium/sum");
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { Graph, NodeRegistry, NodeType, RuntimeExecutor } from "@nodes/types";
|
||||
import { fastHash, concat_encoded, encodeFloat } from "@nodes/utils"
|
||||
import { fastHash, concat_encoded, encodeFloat, encode } from "@nodes/utils"
|
||||
|
||||
export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||
|
||||
@ -176,15 +176,22 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||
return encodeFloat(value as number);
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return encode(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
console.log(transformed_inputs);
|
||||
|
||||
const a2 = performance.now();
|
||||
|
||||
// console.log(`${a2 - a1}ms TRANSFORMED_INPUTS`);
|
||||
|
||||
const _inputs = concat_encoded(transformed_inputs);
|
||||
const a3 = performance.now();
|
||||
console.log(`executing ${node_type.id || node.id}`, _inputs);
|
||||
results[node.id] = node_type.execute(_inputs) as number;
|
||||
const duration = performance.now() - a3;
|
||||
if (duration > 5) {
|
||||
|
6
nodes/max/plantarium/.template/.gitignore
vendored
Normal file
6
nodes/max/plantarium/.template/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
bin/
|
||||
pkg/
|
||||
wasm-pack.log
|
28
nodes/max/plantarium/.template/Cargo.toml
Normal file
28
nodes/max/plantarium/.template/Cargo.toml
Normal file
@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "triangle"
|
||||
version = "0.1.0"
|
||||
authors = ["Max Richter <jim-x@web.de>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2.84"
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
# code size when deploying.
|
||||
utils = { version = "0.1.0", path = "../../../../packages/utils" }
|
||||
macros = { version = "0.1.0", path = "../../../../packages/macros" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde-wasm-bindgen = "0.4"
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
web-sys = { version = "0.3.69", features = ["console"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.34"
|
6
nodes/max/plantarium/.template/package.json
Normal file
6
nodes/max/plantarium/.template/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "wasm-pack build --release --out-name index --no-default-features",
|
||||
"dev": "cargo watch -s 'pnpm build'"
|
||||
}
|
||||
}
|
4
nodes/max/plantarium/.template/src/input.json
Normal file
4
nodes/max/plantarium/.template/src/input.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"outputs": [],
|
||||
"inputs": {}
|
||||
}
|
12
nodes/max/plantarium/.template/src/lib.rs
Normal file
12
nodes/max/plantarium/.template/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use macros::include_definition_file;
|
||||
use utils::{decode_float, encode_float, wrap_arg};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::console;
|
||||
|
||||
include_definition_file!("src/input.json");
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[wasm_bindgen]
|
||||
pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
vec![]
|
||||
}
|
13
nodes/max/plantarium/.template/tests/web.rs
Normal file
13
nodes/max/plantarium/.template/tests/web.rs
Normal file
@ -0,0 +1,13 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate wasm_bindgen_test;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass() {
|
||||
assert_eq!(1 + 1, 2);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use macros::include_definition_file;
|
||||
use utils::{evaluate_args, get_args};
|
||||
use utils::{evaluate_arg, get_args};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::console;
|
||||
|
||||
@ -11,9 +11,8 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
|
||||
let args = get_args(input);
|
||||
|
||||
let value_encoded = evaluate_args(args[0]);
|
||||
// let value = decode_float(value_encoded[0], value_encoded[1]);
|
||||
let length = (args[1][0]) as usize;
|
||||
let value_encoded = evaluate_arg(args[0]);
|
||||
let length = evaluate_arg(args[1]) as usize;
|
||||
|
||||
console::log_1(&format!("WASM(array): input: {:?} -> {:?}", args, value_encoded).into());
|
||||
|
||||
@ -22,7 +21,7 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
res.push(0);
|
||||
res.push(length as i32 + 4);
|
||||
for _ in 0..length {
|
||||
res.push(value_encoded[0]);
|
||||
res.push(value_encoded);
|
||||
}
|
||||
res.push(1);
|
||||
res.push(1);
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::geometry::calculate_normals;
|
||||
use macros::include_definition_file;
|
||||
use utils::{
|
||||
decode_float, encode_float, evaluate_args, geometry, get_args, set_panic_hook, wrap_arg,
|
||||
encode_float, evaluate_float, geometry::calculate_normals, get_args, set_panic_hook, wrap_arg,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::console;
|
||||
@ -18,12 +17,10 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
|
||||
console::log_1(&format!("WASM(cube): input: {:?} -> {:?}", input, args ).into());
|
||||
|
||||
let arg1 = evaluate_args(args[0]);
|
||||
let size = evaluate_float(args[0]);
|
||||
|
||||
let decoded = decode_float(arg1[0]);
|
||||
|
||||
let p = encode_float(decoded);
|
||||
let n = encode_float(-decoded);
|
||||
let p = encode_float(size);
|
||||
let n = encode_float(-size);
|
||||
|
||||
|
||||
// [[1,3, x, y, z, x, y,z,x,y,z]];
|
||||
|
@ -25,6 +25,7 @@ macros = { version = "0.1.0", path = "../../../../packages/macros" }
|
||||
serde-wasm-bindgen = "0.4"
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
web-sys = { version = "0.3.69", features = ["console"] }
|
||||
glam = "0.27.0"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.34"
|
||||
|
@ -1,31 +1,36 @@
|
||||
use glam::{Mat4, Vec3};
|
||||
use macros::include_definition_file;
|
||||
use utils::{concat_args, geometry::extrude_path, get_args};
|
||||
use utils::{
|
||||
concat_args,
|
||||
geometry::{extrude_path, transform_geometry},
|
||||
get_args,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
include_definition_file!("src/inputs.json");
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[wasm_bindgen]
|
||||
pub fn execute(input: Vec<i32>) -> Vec<i32> {
|
||||
utils::set_panic_hook();
|
||||
|
||||
let args = get_args(input.as_slice());
|
||||
|
||||
let mut output:Vec<Vec<i32>> = Vec::new();
|
||||
let mut output: Vec<Vec<i32>> = Vec::new();
|
||||
for arg in args {
|
||||
|
||||
if arg.len() < 3 { continue; }
|
||||
if arg.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if arg[2] == 0 {
|
||||
let _arg = &arg[3..];
|
||||
let geometry = extrude_path(_arg, 16);
|
||||
let mut geometry = extrude_path(_arg, 4);
|
||||
let matrix = Mat4::from_translation(Vec3::new(0.0, 0.0, 0.0));
|
||||
geometry = transform_geometry(geometry, matrix);
|
||||
output.push(geometry);
|
||||
}else if arg[2] == 1 {
|
||||
} else if arg[2] == 1 {
|
||||
output.push(arg.to_vec());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
concat_args(output)
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,15 @@
|
||||
"plant"
|
||||
],
|
||||
"inputs": {
|
||||
"origin": {
|
||||
"type": "vec3",
|
||||
"value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"external": true
|
||||
},
|
||||
"length": {
|
||||
"type": "float",
|
||||
"value": 2
|
||||
|
@ -1,5 +1,5 @@
|
||||
use macros::include_definition_file;
|
||||
use utils::{decode_float, evaluate_args, get_args, set_panic_hook, wrap_arg};
|
||||
use utils::{evaluate_float, evaluate_vec3, get_args, log, set_panic_hook, wrap_arg};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
include_definition_file!("src/input.json");
|
||||
@ -10,9 +10,13 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
|
||||
let args = get_args(input);
|
||||
|
||||
let length = decode_float(evaluate_args(args[0])[0]);
|
||||
let thickness = decode_float(evaluate_args(args[1])[0]);
|
||||
let resolution = 512; //evaluate_args(args[2]);
|
||||
log!("Args: {:?}", args);
|
||||
|
||||
let origin = evaluate_vec3(args[0]);
|
||||
log!("Origin: {:?}", origin);
|
||||
let length = evaluate_float(args[1]);
|
||||
let thickness = evaluate_float(args[2]);
|
||||
let resolution = 16;
|
||||
|
||||
let mut path: Vec<i32> = vec![0; resolution * 4 + 1];
|
||||
path.resize(resolution * 4 + 1, 0);
|
||||
@ -32,9 +36,9 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
|
||||
for i in 0..resolution {
|
||||
let a = i as f32 / resolution as f32;
|
||||
path_p[i * 4] = (a * 8.0).sin() * 0.2;
|
||||
path_p[i * 4 + 1] = a * length;
|
||||
path_p[i * 4 + 2] = 0.0;
|
||||
path_p[i * 4] = origin[0] + (a * 8.0).sin() * 0.2;
|
||||
path_p[i * 4 + 1] = origin[1] + a * length;
|
||||
path_p[i * 4 + 2] = origin[2] + 0.0;
|
||||
path_p[i * 4 + 3] = thickness * (1.0 - a);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "sum"
|
||||
name = "max-plantarium-sum"
|
||||
version = "0.1.0"
|
||||
authors = ["Max Richter <jim-x@web.de>"]
|
||||
edition = "2018"
|
||||
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "triangle"
|
||||
name = "max-plantarium-triangle"
|
||||
version = "0.1.0"
|
||||
authors = ["Max Richter <jim-x@web.de>"]
|
||||
edition = "2018"
|
||||
|
6
nodes/max/plantarium/vec3/.gitignore
vendored
Normal file
6
nodes/max/plantarium/vec3/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
bin/
|
||||
pkg/
|
||||
wasm-pack.log
|
28
nodes/max/plantarium/vec3/Cargo.toml
Normal file
28
nodes/max/plantarium/vec3/Cargo.toml
Normal file
@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "max-plantarium-vec3"
|
||||
version = "0.1.0"
|
||||
authors = ["Max Richter <jim-x@web.de>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2.84"
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
# code size when deploying.
|
||||
utils = { version = "0.1.0", path = "../../../../packages/utils" }
|
||||
macros = { version = "0.1.0", path = "../../../../packages/macros" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde-wasm-bindgen = "0.4"
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
web-sys = { version = "0.3.69", features = ["console"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.34"
|
6
nodes/max/plantarium/vec3/package.json
Normal file
6
nodes/max/plantarium/vec3/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "wasm-pack build --release --out-name index --no-default-features",
|
||||
"dev": "cargo watch -s 'pnpm build'"
|
||||
}
|
||||
}
|
16
nodes/max/plantarium/vec3/src/input.json
Normal file
16
nodes/max/plantarium/vec3/src/input.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"outputs": [
|
||||
"vec3"
|
||||
],
|
||||
"inputs": {
|
||||
"0": {
|
||||
"type": "float"
|
||||
},
|
||||
"1": {
|
||||
"type": "float"
|
||||
},
|
||||
"2": {
|
||||
"type": "float"
|
||||
}
|
||||
}
|
||||
}
|
20
nodes/max/plantarium/vec3/src/lib.rs
Normal file
20
nodes/max/plantarium/vec3/src/lib.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use macros::include_definition_file;
|
||||
use utils::log;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
include_definition_file!("src/input.json");
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn execute(args: &[i32]) -> Vec<i32> {
|
||||
let mut result = Vec::with_capacity(args.len() + 2);
|
||||
result.push(0); // encoding the [ bracket
|
||||
result.push(args[1]);
|
||||
|
||||
result.extend_from_slice(&args[2..]);
|
||||
|
||||
result.push(1);
|
||||
result.push(1); // closing bracket
|
||||
|
||||
log!("WASM(vec3): res {:?}", result);
|
||||
result
|
||||
}
|
13
nodes/max/plantarium/vec3/tests/web.rs
Normal file
13
nodes/max/plantarium/vec3/tests/web.rs
Normal file
@ -0,0 +1,13 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate wasm_bindgen_test;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass() {
|
||||
assert_eq!(1 + 1, 2);
|
||||
}
|
@ -12,5 +12,4 @@ web-sys = { version = "0.3.69", features = ["console"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
gl_matrix = "0.0.2"
|
||||
glam = "0.27.0"
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::create_empty_geometry;
|
||||
use super::{create_geometry_data, wrap_geometry_data};
|
||||
use glam::{Mat4, Quat, Vec3};
|
||||
|
||||
fn create_circle(res: usize) -> Vec<f32> {
|
||||
@ -19,22 +19,19 @@ pub fn extrude_path(input_path: &[i32], res_x: usize) -> Vec<i32> {
|
||||
|
||||
let circle = create_circle(res_x);
|
||||
|
||||
let mut geometry = create_empty_geometry(vertices_amount, face_amount);
|
||||
let mut geometry_data = create_geometry_data(vertices_amount, face_amount);
|
||||
|
||||
let geometry = wrap_geometry_data(&mut geometry_data);
|
||||
|
||||
let normals = geometry.normals;
|
||||
let positions = geometry.positions;
|
||||
let indices = geometry.faces;
|
||||
|
||||
let (_header,rest) = geometry.split_at_mut(5);
|
||||
let (indices, rest) = rest.split_at_mut(face_amount*3);
|
||||
let (_positions, _normals) = rest.split_at_mut(vertices_amount*3);
|
||||
let positions: &mut [f32];
|
||||
let normals: &mut [f32];
|
||||
let path: &[f32];
|
||||
unsafe {
|
||||
path = std::slice::from_raw_parts(input_path.as_ptr() as *const f32, input_path.len());
|
||||
positions = std::slice::from_raw_parts_mut(_positions.as_mut_ptr() as *mut f32, _positions.len());
|
||||
normals = std::slice::from_raw_parts_mut(_normals.as_mut_ptr() as *mut f32, _normals.len());
|
||||
}
|
||||
|
||||
normals[0] = 0.0;
|
||||
|
||||
for i in 0..point_amount {
|
||||
|
||||
let index_offset = i * res_x * 6;
|
||||
@ -109,5 +106,5 @@ pub fn extrude_path(input_path: &[i32], res_x: usize) -> Vec<i32> {
|
||||
}
|
||||
}
|
||||
|
||||
geometry
|
||||
geometry_data
|
||||
}
|
||||
|
74
packages/utils/src/geometry/geometry.rs
Normal file
74
packages/utils/src/geometry/geometry.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use crate::log;
|
||||
|
||||
pub struct GeometryData<'a> {
|
||||
pub positions: &'a mut [f32], // View into `data`
|
||||
pub normals: &'a mut [f32], // View into `data`
|
||||
pub faces: &'a mut [i32], // View into `data`
|
||||
}
|
||||
|
||||
pub fn create_geometry_data(vertex_amount: usize, face_amount: usize) -> Vec<i32> {
|
||||
let amount = 3 // definition (type, vertex_amount, face_amount)
|
||||
+ 4 // opening and closing brackets
|
||||
+ vertex_amount * 3 // positions
|
||||
+ vertex_amount * 3 // normals
|
||||
+ face_amount * 3; // faces
|
||||
|
||||
let mut geo = vec![0; amount];
|
||||
|
||||
geo[0] = 0; // opening bracket
|
||||
geo[1] = amount as i32 - 2; // opening bracket
|
||||
geo[2] = 1; // type: geometry
|
||||
geo[3] = vertex_amount as i32;
|
||||
geo[4] = face_amount as i32;
|
||||
geo[amount - 2] = 1; // closing bracket
|
||||
geo[amount - 1] = 1; // closing bracket
|
||||
|
||||
geo
|
||||
}
|
||||
|
||||
pub fn wrap_geometry_data(geometry: &mut [i32]) -> GeometryData {
|
||||
// Basic validity checks
|
||||
assert!(
|
||||
geometry.len() > 5,
|
||||
"Geometry vector does not contain enough data for a header."
|
||||
);
|
||||
|
||||
// Split at after header
|
||||
let (header, rest) = geometry.split_at_mut(5);
|
||||
|
||||
let vertices_amount = header[3] as usize;
|
||||
let face_amount = header[4] as usize;
|
||||
let total_floats = vertices_amount * 3 * 2;
|
||||
|
||||
let (faces, rest) = rest.split_at_mut(face_amount * 3);
|
||||
let (positions_slice, rest) = rest.split_at_mut(vertices_amount * 3);
|
||||
let (normals_slice, _) = rest.split_at_mut(vertices_amount * 3);
|
||||
|
||||
log!(
|
||||
"Vertices: {}, normals: {}, Total floats: {}",
|
||||
positions_slice.len(),
|
||||
normals_slice.len(),
|
||||
total_floats
|
||||
);
|
||||
|
||||
assert!(
|
||||
positions_slice.len() + normals_slice.len() == total_floats,
|
||||
"Slices do not match the expected sizes."
|
||||
);
|
||||
|
||||
let positions: &mut [f32] = unsafe {
|
||||
std::slice::from_raw_parts_mut(
|
||||
positions_slice.as_mut_ptr() as *mut f32,
|
||||
positions_slice.len(),
|
||||
)
|
||||
};
|
||||
let normals: &mut [f32] = unsafe {
|
||||
std::slice::from_raw_parts_mut(normals_slice.as_mut_ptr() as *mut f32, normals_slice.len())
|
||||
};
|
||||
|
||||
GeometryData {
|
||||
positions,
|
||||
normals,
|
||||
faces,
|
||||
}
|
||||
}
|
@ -1,33 +1,9 @@
|
||||
mod calculate_normals;
|
||||
mod extrude_path;
|
||||
mod geometry;
|
||||
mod transform;
|
||||
|
||||
pub use calculate_normals::*;
|
||||
pub use extrude_path::*;
|
||||
|
||||
use crate::log;
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn create_empty_geometry(vertex_amount: usize, face_amount: usize) -> Vec<i32> {
|
||||
log!(
|
||||
"create_empty_geometry: vertex_amount: {}, face_amount: {}",
|
||||
vertex_amount,
|
||||
face_amount
|
||||
);
|
||||
|
||||
let amount =
|
||||
3 // definition (type, vertex_amount, face_amount)
|
||||
+ 4 // opening and closing brackets
|
||||
+ vertex_amount * 3 // positions
|
||||
+ vertex_amount * 3 // normals
|
||||
+ face_amount * 3; // faces
|
||||
|
||||
let mut vec: Vec<i32> = vec![0; amount];
|
||||
vec[0] = 0; // opening bracket
|
||||
vec[1] = amount as i32 - 2; // opening bracket
|
||||
vec[2] = 1; // type: geometry
|
||||
vec[3] = vertex_amount as i32;
|
||||
vec[4] = face_amount as i32;
|
||||
vec[amount - 2] = 1; // closing bracket
|
||||
vec[amount - 1] = 1; // closing bracket
|
||||
vec
|
||||
}
|
||||
pub use geometry::*;
|
||||
pub use transform::*;
|
||||
|
25
packages/utils/src/geometry/transform.rs
Normal file
25
packages/utils/src/geometry/transform.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use glam::{Mat4, Vec3};
|
||||
|
||||
pub fn transform_geometry(mut geometry: Vec<i32>, matrix: Mat4) -> Vec<i32> {
|
||||
let (_header, rest) = geometry.split_at_mut(5);
|
||||
|
||||
let vertices_amount = _header[3] as usize;
|
||||
let face_amount = _header[4] as usize;
|
||||
|
||||
let (_indices, rest) = rest.split_at_mut(face_amount * 3);
|
||||
let (_positions, _normals) = rest.split_at_mut(vertices_amount * 3);
|
||||
let positions: &mut [f32];
|
||||
unsafe {
|
||||
positions =
|
||||
std::slice::from_raw_parts_mut(_positions.as_mut_ptr() as *mut f32, _positions.len());
|
||||
}
|
||||
|
||||
for i in 0..vertices_amount {
|
||||
let pos = Mat4::transform_point3(&matrix, Vec3::from_slice(&positions[i * 3..i * 3 + 3]));
|
||||
positions[i * 3] = pos.x;
|
||||
positions[i * 3 + 1] = pos.y;
|
||||
positions[i * 3 + 2] = pos.z;
|
||||
}
|
||||
|
||||
geometry
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
use crate::decode_float;
|
||||
|
||||
pub fn get_args(args: &[i32]) -> Vec<&[i32]> {
|
||||
let mut idx: usize = 0;
|
||||
let mut depth = -1;
|
||||
@ -105,13 +107,29 @@ pub fn evaluate_node(input_args: &[i32]) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate_args(input_args: &[i32]) -> Vec<i32> {
|
||||
if input_args.len() == 1 {
|
||||
return input_args.to_vec();
|
||||
}
|
||||
pub fn evaluate_vec3(input_args: &[i32]) -> Vec<f32> {
|
||||
let args = get_args(input_args);
|
||||
|
||||
if input_args.len() == 4 && input_args[0] == 0 && input_args[1] == 3 {
|
||||
return vec![input_args[2], input_args[3]];
|
||||
assert!(
|
||||
args.len() == 3,
|
||||
"Failed to evaluate Vec3 - Expected 3 arguments, got {}",
|
||||
args.len()
|
||||
);
|
||||
|
||||
let x = evaluate_float(args[0]);
|
||||
let y = evaluate_float(args[1]);
|
||||
let z = evaluate_float(args[2]);
|
||||
|
||||
vec![x, y, z]
|
||||
}
|
||||
|
||||
pub fn evaluate_float(arg: &[i32]) -> f32 {
|
||||
decode_float(evaluate_arg(arg))
|
||||
}
|
||||
|
||||
pub fn evaluate_arg(input_args: &[i32]) -> i32 {
|
||||
if input_args.len() == 1 {
|
||||
return input_args.to_vec()[0];
|
||||
}
|
||||
|
||||
let args = get_args(input_args);
|
||||
@ -125,22 +143,20 @@ pub fn evaluate_args(input_args: &[i32]) -> Vec<i32> {
|
||||
resolved.push(arg[2]);
|
||||
resolved.push(arg[3]);
|
||||
} else {
|
||||
resolved.push(evaluate_args(arg)[0]);
|
||||
resolved.push(evaluate_arg(arg));
|
||||
}
|
||||
}
|
||||
|
||||
if resolved.len() > 1 {
|
||||
let res = evaluate_node(&resolved);
|
||||
vec![res]
|
||||
evaluate_node(&resolved)
|
||||
} else {
|
||||
resolved
|
||||
resolved[0]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::encoding::decode_float;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@ -152,10 +168,9 @@ mod tests {
|
||||
// and another math node that adds 2 to that result
|
||||
// the numbers are f32 floats encoded as two i32's
|
||||
|
||||
let result = evaluate_args(&input);
|
||||
let decoded = decode_float(result[0]);
|
||||
let result = evaluate_float(&input);
|
||||
|
||||
assert_eq!(decoded, 6.0);
|
||||
assert_eq!(result, 6.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
Loading…
Reference in New Issue
Block a user