feat: instance node
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m44s
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m44s
This commit is contained in:
parent
a01a409b97
commit
10a12ad41c
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -157,6 +157,21 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nodarium_instance"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"console_error_panic_hook",
|
||||||
|
"glam",
|
||||||
|
"nodarium_macros",
|
||||||
|
"nodarium_utils",
|
||||||
|
"serde",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-test",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nodarium_macros"
|
name = "nodarium_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
Vector3,
|
Vector3,
|
||||||
type Vector3Tuple,
|
type Vector3Tuple,
|
||||||
Box3,
|
Box3,
|
||||||
|
Mesh,
|
||||||
|
MeshMatcapMaterial,
|
||||||
|
MeshBasicMaterial,
|
||||||
} from "three";
|
} from "three";
|
||||||
import { AppSettings } from "../settings/app-settings";
|
import { AppSettings } from "../settings/app-settings";
|
||||||
import Camera from "./Camera.svelte";
|
import Camera from "./Camera.svelte";
|
||||||
@ -51,11 +54,21 @@
|
|||||||
export let centerCamera: boolean = true;
|
export let centerCamera: boolean = true;
|
||||||
let center = new Vector3(0, 4, 0);
|
let center = new Vector3(0, 4, 0);
|
||||||
|
|
||||||
|
function isMesh(child: Mesh | any): child is Mesh {
|
||||||
|
return child.isObject3D && "material" in child;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMatCapMaterial(material: any): material is MeshBasicMaterial {
|
||||||
|
return material.isMaterial && "matcap" in material;
|
||||||
|
}
|
||||||
|
|
||||||
$: if ($AppSettings && scene) {
|
$: if ($AppSettings && scene) {
|
||||||
scene.children.forEach((child) => {
|
scene.traverse(function (child) {
|
||||||
|
if (isMesh(child) && isMatCapMaterial(child.material)) {
|
||||||
child.material.wireframe = $AppSettings.wireframe;
|
child.material.wireframe = $AppSettings.wireframe;
|
||||||
threlte.invalidate();
|
}
|
||||||
});
|
});
|
||||||
|
threlte.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPosition(geo: BufferGeometry, i: number) {
|
function getPosition(geo: BufferGeometry, i: number) {
|
||||||
|
252
app/src/lib/result-viewer/geometryPool.ts
Normal file
252
app/src/lib/result-viewer/geometryPool.ts
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
import { fastHashArrayBuffer } from "@nodes/utils";
|
||||||
|
import { BufferAttribute, BufferGeometry, Float32BufferAttribute, Group, InstancedMesh, Material, Matrix4, Mesh } from "three"
|
||||||
|
|
||||||
|
|
||||||
|
function fastArrayHash(arr: ArrayBuffer) {
|
||||||
|
let ints = new Uint8Array(arr);
|
||||||
|
|
||||||
|
const sampleDistance = Math.max(Math.floor(ints.length / 100), 1);
|
||||||
|
const sampleCount = Math.floor(ints.length / sampleDistance);
|
||||||
|
|
||||||
|
let hash = new Uint8Array(sampleCount);
|
||||||
|
|
||||||
|
for (let i = 0; i < sampleCount; i++) {
|
||||||
|
const index = i * sampleDistance;
|
||||||
|
hash[i] = ints[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return fastHashArrayBuffer(hash.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createGeometryPool(parentScene: Group, material: Material) {
|
||||||
|
const scene = new Group();
|
||||||
|
parentScene.add(scene);
|
||||||
|
|
||||||
|
let meshes: Mesh[] = [];
|
||||||
|
|
||||||
|
function updateSingleGeometry(data: Int32Array, existingMesh: Mesh | null = null) {
|
||||||
|
|
||||||
|
let hash = fastArrayHash(data);
|
||||||
|
|
||||||
|
let geometry = existingMesh ? existingMesh.geometry : new BufferGeometry();
|
||||||
|
|
||||||
|
// Extract data from the encoded array
|
||||||
|
// const geometryType = encodedData[index++];
|
||||||
|
let index = 1;
|
||||||
|
const vertexCount = data[index++];
|
||||||
|
const faceCount = data[index++];
|
||||||
|
|
||||||
|
if (geometry.userData?.hash === hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indices
|
||||||
|
const indicesEnd = index + faceCount * 3;
|
||||||
|
const indices = data.subarray(index, indicesEnd);
|
||||||
|
index = indicesEnd;
|
||||||
|
|
||||||
|
// Vertices
|
||||||
|
const vertices = new Float32Array(
|
||||||
|
data.buffer,
|
||||||
|
index * 4,
|
||||||
|
vertexCount * 3,
|
||||||
|
);
|
||||||
|
index = index + vertexCount * 3;
|
||||||
|
|
||||||
|
let posAttribute = geometry.getAttribute(
|
||||||
|
"position",
|
||||||
|
) as BufferAttribute | null;
|
||||||
|
|
||||||
|
if (posAttribute && posAttribute.count === vertexCount) {
|
||||||
|
posAttribute.set(vertices, 0);
|
||||||
|
posAttribute.needsUpdate = true;
|
||||||
|
} else {
|
||||||
|
geometry.setAttribute(
|
||||||
|
"position",
|
||||||
|
new Float32BufferAttribute(vertices, 3),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const normals = new Float32Array(
|
||||||
|
data.buffer,
|
||||||
|
index * 4,
|
||||||
|
vertexCount * 3,
|
||||||
|
);
|
||||||
|
index = index + vertexCount * 3;
|
||||||
|
|
||||||
|
if (
|
||||||
|
geometry.userData?.faceCount !== faceCount ||
|
||||||
|
geometry.userData?.vertexCount !== vertexCount
|
||||||
|
) {
|
||||||
|
// Add data to geometry
|
||||||
|
geometry.setIndex([...indices]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalsAttribute = geometry.getAttribute(
|
||||||
|
"normal",
|
||||||
|
) as BufferAttribute | null;
|
||||||
|
if (normalsAttribute && normalsAttribute.count === vertexCount) {
|
||||||
|
normalsAttribute.set(normals, 0);
|
||||||
|
normalsAttribute.needsUpdate = true;
|
||||||
|
} else {
|
||||||
|
geometry.setAttribute("normal", new Float32BufferAttribute(normals, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry.userData = {
|
||||||
|
vertexCount,
|
||||||
|
faceCount,
|
||||||
|
hash,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!existingMesh) {
|
||||||
|
const mesh = new Mesh(geometry, material);
|
||||||
|
scene.add(mesh);
|
||||||
|
meshes.push(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
update(
|
||||||
|
newData: Int32Array[],
|
||||||
|
) {
|
||||||
|
for (let i = 0; i < Math.max(newData.length, meshes.length); i++) {
|
||||||
|
const existingMesh = meshes[i];
|
||||||
|
let input = newData[i];
|
||||||
|
if (input) {
|
||||||
|
updateSingleGeometry(input, existingMesh || null);
|
||||||
|
} else if (existingMesh) {
|
||||||
|
existingMesh.visible = false;
|
||||||
|
scene.remove(existingMesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createInstancedGeometryPool(parentScene: Group, material: Material) {
|
||||||
|
const scene = new Group();
|
||||||
|
parentScene.add(scene);
|
||||||
|
|
||||||
|
const instances: InstancedMesh[] = [];
|
||||||
|
|
||||||
|
function updateSingleInstance(data: Int32Array, existingInstance: InstancedMesh | null = null) {
|
||||||
|
|
||||||
|
let hash = fastArrayHash(data);
|
||||||
|
|
||||||
|
let geometry = existingInstance ? existingInstance.geometry : new BufferGeometry();
|
||||||
|
|
||||||
|
// Extract data from the encoded array
|
||||||
|
let index = 0;
|
||||||
|
const geometryType = data[index++];
|
||||||
|
const vertexCount = data[index++];
|
||||||
|
const faceCount = data[index++];
|
||||||
|
const instanceCount = data[index++];
|
||||||
|
const stemDepth = data[index++];
|
||||||
|
|
||||||
|
// Indices
|
||||||
|
const indicesEnd = index + faceCount * 3;
|
||||||
|
const indices = data.subarray(index, indicesEnd);
|
||||||
|
index = indicesEnd;
|
||||||
|
if (
|
||||||
|
geometry.userData?.faceCount !== faceCount ||
|
||||||
|
geometry.userData?.vertexCount !== vertexCount
|
||||||
|
) {
|
||||||
|
// Add data to geometry
|
||||||
|
geometry.setIndex([...indices]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertices
|
||||||
|
const vertices = new Float32Array(
|
||||||
|
data.buffer,
|
||||||
|
index * 4,
|
||||||
|
vertexCount * 3,
|
||||||
|
);
|
||||||
|
index = index + vertexCount * 3;
|
||||||
|
let posAttribute = geometry.getAttribute(
|
||||||
|
"position",
|
||||||
|
) as BufferAttribute | null;
|
||||||
|
if (posAttribute && posAttribute.count === vertexCount) {
|
||||||
|
posAttribute.set(vertices, 0);
|
||||||
|
posAttribute.needsUpdate = true;
|
||||||
|
} else {
|
||||||
|
geometry.setAttribute(
|
||||||
|
"position",
|
||||||
|
new Float32BufferAttribute(vertices, 3),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const normals = new Float32Array(
|
||||||
|
data.buffer,
|
||||||
|
index * 4,
|
||||||
|
vertexCount * 3,
|
||||||
|
);
|
||||||
|
index = index + vertexCount * 3;
|
||||||
|
const normalsAttribute = geometry.getAttribute(
|
||||||
|
"normal",
|
||||||
|
) as BufferAttribute | null;
|
||||||
|
if (normalsAttribute && normalsAttribute.count === vertexCount) {
|
||||||
|
normalsAttribute.set(normals, 0);
|
||||||
|
normalsAttribute.needsUpdate = true;
|
||||||
|
} else {
|
||||||
|
geometry.setAttribute("normal", new Float32BufferAttribute(normals, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingInstance && instanceCount > existingInstance.geometry.userData.count) {
|
||||||
|
console.log("recreating instance")
|
||||||
|
scene.remove(existingInstance);
|
||||||
|
instances.splice(instances.indexOf(existingInstance), 1);
|
||||||
|
existingInstance = new InstancedMesh(geometry, material, instanceCount);
|
||||||
|
scene.add(existingInstance)
|
||||||
|
instances.push(existingInstance)
|
||||||
|
} else if (!existingInstance) {
|
||||||
|
console.log("creating instance")
|
||||||
|
existingInstance = new InstancedMesh(geometry, material, instanceCount);
|
||||||
|
scene.add(existingInstance)
|
||||||
|
instances.push(existingInstance)
|
||||||
|
} else {
|
||||||
|
console.log("updating instance")
|
||||||
|
existingInstance.count = instanceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the matrices
|
||||||
|
|
||||||
|
const matrices = new Float32Array(
|
||||||
|
data.buffer,
|
||||||
|
index * 4,
|
||||||
|
instanceCount * 16);
|
||||||
|
|
||||||
|
for (let i = 0; i < instanceCount; i++) {
|
||||||
|
const matrix = new Matrix4().fromArray(matrices.subarray(i * 16, i * 16 + 16));
|
||||||
|
existingInstance.setMatrixAt(i, matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry.userData = {
|
||||||
|
vertexCount,
|
||||||
|
faceCount,
|
||||||
|
count: Math.max(instanceCount, existingInstance.geometry.userData.count || 0),
|
||||||
|
hash,
|
||||||
|
};
|
||||||
|
|
||||||
|
existingInstance.instanceMatrix.needsUpdate = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
update(
|
||||||
|
newData: Int32Array[],
|
||||||
|
) {
|
||||||
|
for (let i = 0; i < Math.max(newData.length, instances.length); i++) {
|
||||||
|
const existingMesh = instances[i];
|
||||||
|
let input = newData[i];
|
||||||
|
if (input) {
|
||||||
|
updateSingleInstance(input, existingMesh || null);
|
||||||
|
} else if (existingMesh) {
|
||||||
|
existingMesh.visible = false;
|
||||||
|
scene.remove(existingMesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,5 @@
|
|||||||
import { fastHashArrayBuffer } from "@nodes/utils";
|
import { MeshMatcapMaterial, TextureLoader, type Group } from "three";
|
||||||
import { BufferAttribute, BufferGeometry, Float32BufferAttribute, Mesh, MeshMatcapMaterial, TextureLoader, type Group } from "three";
|
import { createGeometryPool, createInstancedGeometryPool } from "./geometryPool";
|
||||||
|
|
||||||
function fastArrayHash(arr: ArrayBuffer) {
|
|
||||||
let ints = new Uint8Array(arr);
|
|
||||||
|
|
||||||
const sampleDistance = Math.max(Math.floor(ints.length / 100), 1);
|
|
||||||
const sampleCount = Math.floor(ints.length / sampleDistance);
|
|
||||||
|
|
||||||
let hash = new Uint8Array(sampleCount);
|
|
||||||
|
|
||||||
for (let i = 0; i < sampleCount; i++) {
|
|
||||||
const index = i * sampleDistance;
|
|
||||||
hash[i] = ints[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return fastHashArrayBuffer(hash.buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
const loader = new TextureLoader();
|
const loader = new TextureLoader();
|
||||||
const matcap = loader.load('/matcap_green.jpg');
|
const matcap = loader.load('/matcap_green.jpg');
|
||||||
@ -25,122 +9,19 @@ const material = new MeshMatcapMaterial({
|
|||||||
matcap
|
matcap
|
||||||
});
|
});
|
||||||
|
|
||||||
function createGeometryFromEncodedData(
|
let geometryPool: ReturnType<typeof createGeometryPool>;
|
||||||
encodedData: Int32Array,
|
let instancePool: ReturnType<typeof createInstancedGeometryPool>;
|
||||||
geometry = new BufferGeometry(),
|
|
||||||
): BufferGeometry {
|
|
||||||
// Extract data from the encoded array
|
|
||||||
let index = 1;
|
|
||||||
// const geometryType = encodedData[index++];
|
|
||||||
const vertexCount = encodedData[index++];
|
|
||||||
const faceCount = encodedData[index++];
|
|
||||||
|
|
||||||
let hash = fastArrayHash(encodedData);
|
|
||||||
|
|
||||||
if (geometry.userData?.hash === hash) {
|
|
||||||
return geometry;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indices
|
|
||||||
const indicesEnd = index + faceCount * 3;
|
|
||||||
const indices = encodedData.subarray(index, indicesEnd);
|
|
||||||
index = indicesEnd;
|
|
||||||
|
|
||||||
// Vertices
|
|
||||||
const vertices = new Float32Array(
|
|
||||||
encodedData.buffer,
|
|
||||||
index * 4,
|
|
||||||
vertexCount * 3,
|
|
||||||
);
|
|
||||||
index = index + vertexCount * 3;
|
|
||||||
|
|
||||||
let posAttribute = geometry.getAttribute(
|
|
||||||
"position",
|
|
||||||
) as BufferAttribute | null;
|
|
||||||
|
|
||||||
if (posAttribute && posAttribute.count === vertexCount) {
|
|
||||||
posAttribute.set(vertices, 0);
|
|
||||||
posAttribute.needsUpdate = true;
|
|
||||||
} else {
|
|
||||||
geometry.setAttribute(
|
|
||||||
"position",
|
|
||||||
new Float32BufferAttribute(vertices, 3),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const normals = new Float32Array(
|
|
||||||
encodedData.buffer,
|
|
||||||
index * 4,
|
|
||||||
vertexCount * 3,
|
|
||||||
);
|
|
||||||
index = index + vertexCount * 3;
|
|
||||||
|
|
||||||
if (
|
|
||||||
geometry.userData?.faceCount !== faceCount ||
|
|
||||||
geometry.userData?.vertexCount !== vertexCount
|
|
||||||
) {
|
|
||||||
// Add data to geometry
|
|
||||||
geometry.setIndex([...indices]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalsAttribute = geometry.getAttribute(
|
|
||||||
"normal",
|
|
||||||
) as BufferAttribute | null;
|
|
||||||
if (normalsAttribute && normalsAttribute.count === vertexCount) {
|
|
||||||
normalsAttribute.set(normals, 0);
|
|
||||||
normalsAttribute.needsUpdate = true;
|
|
||||||
} else {
|
|
||||||
geometry.setAttribute("normal", new Float32BufferAttribute(normals, 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
geometry.userData = {
|
|
||||||
vertexCount,
|
|
||||||
faceCount,
|
|
||||||
hash,
|
|
||||||
};
|
|
||||||
|
|
||||||
return geometry;
|
|
||||||
}
|
|
||||||
|
|
||||||
let meshes: Mesh[] = [];
|
|
||||||
|
|
||||||
|
|
||||||
export function updateGeometries(inputs: Int32Array[], group: Group) {
|
export function updateGeometries(inputs: Int32Array[], group: Group) {
|
||||||
|
|
||||||
|
geometryPool = geometryPool || createGeometryPool(group, material);
|
||||||
|
instancePool = instancePool || createInstancedGeometryPool(group, material);
|
||||||
|
|
||||||
let totalVertices = 0;
|
let totalVertices = 0;
|
||||||
let totalFaces = 0;
|
let totalFaces = 0;
|
||||||
let newGeometries = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < Math.max(meshes.length, inputs.length); i++) {
|
geometryPool.update(inputs.filter(i => i[0] === 1));
|
||||||
let existingMesh = meshes[i];
|
instancePool.update(inputs.filter(i => i[0] === 2));
|
||||||
let input = inputs[i];
|
|
||||||
|
|
||||||
if (input) {
|
|
||||||
if (input[0] !== 1) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
totalVertices += input[1];
|
|
||||||
totalFaces += input[2];
|
|
||||||
} else {
|
|
||||||
if (existingMesh) {
|
|
||||||
existingMesh.visible = false;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingMesh) {
|
|
||||||
createGeometryFromEncodedData(input, existingMesh.geometry);
|
|
||||||
} else {
|
|
||||||
let geo = createGeometryFromEncodedData(input);
|
|
||||||
const mesh = new Mesh(geo, material);
|
|
||||||
meshes[i] = mesh;
|
|
||||||
newGeometries.push(mesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < newGeometries.length; i++) {
|
|
||||||
group.add(newGeometries[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { totalFaces, totalVertices };
|
return { totalFaces, totalVertices };
|
||||||
}
|
}
|
||||||
|
25
docs/PLANTARIUM.md
Normal file
25
docs/PLANTARIUM.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Plantarium Documentation
|
||||||
|
|
||||||
|
## Encoding Scheme
|
||||||
|
|
||||||
|
Plantarium encodes different types of data differently. Right now we have three types of data:
|
||||||
|
|
||||||
|
### Path
|
||||||
|
[0, stem_depth, ... x,y,z,thickness, x,y,z,thickness...]
|
||||||
|
|
||||||
|
### Geometry
|
||||||
|
[1, vertex_amount, face_amount, ... faces in index_a, index_b, index_c ..., ... vertices in x,y,z,x,y,z ..., ... normals in x,y,z,x,y,z ...]
|
||||||
|
|
||||||
|
### Instances
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
vertex_amount,
|
||||||
|
face_amount,
|
||||||
|
instance_amount,
|
||||||
|
stem_depth,
|
||||||
|
...faces...,
|
||||||
|
...vertices...,
|
||||||
|
...normals...,
|
||||||
|
...4x4 matrices for instances...
|
||||||
|
]
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
use nodarium_macros::include_definition_file;
|
use nodarium_macros::include_definition_file;
|
||||||
use nodarium_utils::{
|
use nodarium_utils::{
|
||||||
encode_float, evaluate_float, geometry::calculate_normals, log, set_panic_hook, split_args,
|
concat_args, encode_float, evaluate_float, geometry::calculate_normals, log, set_panic_hook,
|
||||||
wrap_arg,
|
split_args, wrap_arg,
|
||||||
};
|
};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
|
|
||||||
let res = wrap_arg(&cube_geometry);
|
let res = wrap_arg(&cube_geometry);
|
||||||
|
|
||||||
log!("WASM(cube): output: {:?}", res);
|
log!("WASM(box): output: {:?}", res);
|
||||||
|
|
||||||
res
|
res
|
||||||
|
|
||||||
|
6
nodes/max/plantarium/instance/.gitignore
vendored
Normal file
6
nodes/max/plantarium/instance/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
|
bin/
|
||||||
|
pkg/
|
||||||
|
wasm-pack.log
|
29
nodes/max/plantarium/instance/Cargo.toml
Normal file
29
nodes/max/plantarium/instance/Cargo.toml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[package]
|
||||||
|
name = "nodarium_instance"
|
||||||
|
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.
|
||||||
|
nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" }
|
||||||
|
nodarium_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"] }
|
||||||
|
glam = "0.27.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wasm-bindgen-test = "0.3.34"
|
6
nodes/max/plantarium/instance/package.json
Normal file
6
nodes/max/plantarium/instance/package.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "wasm-pack build --release --out-name index --no-default-features",
|
||||||
|
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
|
||||||
|
}
|
||||||
|
}
|
32
nodes/max/plantarium/instance/src/input.json
Normal file
32
nodes/max/plantarium/instance/src/input.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"id": "max/plantarium/instance",
|
||||||
|
"outputs": [
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"inputs": {
|
||||||
|
"plant": {
|
||||||
|
"type": "path"
|
||||||
|
},
|
||||||
|
"geometry": {
|
||||||
|
"type": "geometry",
|
||||||
|
"value": [0,0,0]
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lowestInstance": {
|
||||||
|
"type": "float",
|
||||||
|
"min":0,
|
||||||
|
"max":1,
|
||||||
|
"value": 0.5,
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
"highestInstance": {
|
||||||
|
"type": "float",
|
||||||
|
"min":0,
|
||||||
|
"max":1,
|
||||||
|
"value":1,
|
||||||
|
"hidden": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
119
nodes/max/plantarium/instance/src/lib.rs
Normal file
119
nodes/max/plantarium/instance/src/lib.rs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
use glam::{Mat4, Quat, Vec3};
|
||||||
|
use nodarium_macros::include_definition_file;
|
||||||
|
use nodarium_utils::{
|
||||||
|
concat_args, encode_float, evaluate_float, evaluate_int,
|
||||||
|
geometry::{
|
||||||
|
calculate_normals, create_instance_data, wrap_geometry_data, wrap_instance_data, wrap_path,
|
||||||
|
},
|
||||||
|
log, set_panic_hook, split_args, wrap_arg,
|
||||||
|
};
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
include_definition_file!("src/input.json");
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn create_geo() -> Vec<i32> {
|
||||||
|
let size = 5.0;
|
||||||
|
|
||||||
|
let p = encode_float(size);
|
||||||
|
let n = encode_float(-size);
|
||||||
|
|
||||||
|
// [[1,3, x, y, z, x, y,z,x,y,z]];
|
||||||
|
wrap_arg(calculate_normals(&mut [
|
||||||
|
1, // 1: geometry
|
||||||
|
8, // 8 vertices
|
||||||
|
12, // 12 faces
|
||||||
|
/*
|
||||||
|
Indeces:
|
||||||
|
5----6
|
||||||
|
| 4--+-7
|
||||||
|
0-|--1 |
|
||||||
|
3----2
|
||||||
|
|
||||||
|
*/
|
||||||
|
// this are the indeces for the face
|
||||||
|
0, 1, 2,
|
||||||
|
0, 2, 3,
|
||||||
|
0, 3, 4,
|
||||||
|
4, 5, 0,
|
||||||
|
6, 1, 0,
|
||||||
|
5, 6, 0,
|
||||||
|
7, 2, 1,
|
||||||
|
6, 7, 1,
|
||||||
|
2, 7, 3,
|
||||||
|
3, 7, 4,
|
||||||
|
7, 6, 4,
|
||||||
|
4, 6, 5, // Bottom plate
|
||||||
|
p, n, n,
|
||||||
|
p, n, p,
|
||||||
|
n, n, p,
|
||||||
|
n, n, n, // Top Plate
|
||||||
|
n, p, n,
|
||||||
|
p, p, n,
|
||||||
|
p, p, p,
|
||||||
|
n, p, p, // this is the normal for every single vert 1065353216 == 1.0f encoded is i32
|
||||||
|
0, 0, 0,
|
||||||
|
0, 0, 0,
|
||||||
|
0, 0, 0,
|
||||||
|
0, 0, 0,
|
||||||
|
0, 0, 0,
|
||||||
|
0, 0, 0,
|
||||||
|
0, 0, 0,
|
||||||
|
0, 0, 0,
|
||||||
|
]))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||||
|
set_panic_hook();
|
||||||
|
|
||||||
|
let args = split_args(input);
|
||||||
|
let mut inputs = split_args(args[0]);
|
||||||
|
log!("WASM(instance): inputs: {:?}", inputs);
|
||||||
|
|
||||||
|
let mut geo_data = args[1].to_vec();
|
||||||
|
let geo = wrap_geometry_data(&mut geo_data);
|
||||||
|
|
||||||
|
let mut transforms: Vec<Mat4> = Vec::new();
|
||||||
|
|
||||||
|
for path_data in inputs.iter() {
|
||||||
|
let amount = evaluate_int(args[2]);
|
||||||
|
|
||||||
|
let lowest_instance = evaluate_float(args[3]);
|
||||||
|
let highest_instance = evaluate_float(args[4]);
|
||||||
|
|
||||||
|
let path = wrap_path(path_data);
|
||||||
|
|
||||||
|
for i in 0..amount {
|
||||||
|
let alpha =
|
||||||
|
lowest_instance + (i as f32 / amount as f32) * (highest_instance - lowest_instance);
|
||||||
|
|
||||||
|
let point = path.get_point_at(alpha);
|
||||||
|
let direction = path.get_direction_at(alpha);
|
||||||
|
|
||||||
|
let transform = Mat4::from_scale_rotation_translation(
|
||||||
|
Vec3::new(0.1, 0.1, 0.1),
|
||||||
|
Quat::from_xyzw(direction[0], direction[1], direction[2], 1.0).normalize(),
|
||||||
|
Vec3::from_slice(&point),
|
||||||
|
);
|
||||||
|
transforms.push(transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut instance_data = create_instance_data(
|
||||||
|
geo.positions.len() / 3,
|
||||||
|
geo.faces.len() / 3,
|
||||||
|
transforms.len(),
|
||||||
|
);
|
||||||
|
let mut instances = wrap_instance_data(&mut instance_data);
|
||||||
|
instances.set_geometry(geo);
|
||||||
|
(0..transforms.len()).for_each(|i| {
|
||||||
|
instances.set_transformation_matrix(i, &transforms[i].to_cols_array());
|
||||||
|
});
|
||||||
|
|
||||||
|
log!("WASM(instance): geo: {:?}", instance_data);
|
||||||
|
inputs.push(&instance_data);
|
||||||
|
|
||||||
|
concat_args(inputs)
|
||||||
|
}
|
13
nodes/max/plantarium/instance/tests/web.rs
Normal file
13
nodes/max/plantarium/instance/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,6 +12,8 @@ include_definition_file!("src/inputs.json");
|
|||||||
pub fn execute(input: &[i32]) -> Vec<i32> {
|
pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||||
set_panic_hook();
|
set_panic_hook();
|
||||||
|
|
||||||
|
log!("WASM(output): input: {:?}", input);
|
||||||
|
|
||||||
let args = split_args(input);
|
let args = split_args(input);
|
||||||
|
|
||||||
log!("WASM(output) args: {:?}", args);
|
log!("WASM(output) args: {:?}", args);
|
||||||
@ -29,16 +31,15 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
log!("arg_type: {}, \n {:?}", arg_type, arg,);
|
log!("arg_type: {}, \n {:?}", arg_type, arg,);
|
||||||
|
|
||||||
if arg_type == 0 {
|
if arg_type == 0 {
|
||||||
// this is path
|
// if this is path we need to extrude it
|
||||||
let vec = arg.to_vec();
|
output.push(arg.to_vec());
|
||||||
output.push(vec.clone());
|
|
||||||
let path_data = wrap_path(arg);
|
let path_data = wrap_path(arg);
|
||||||
let geometry = extrude_path(path_data, resolution);
|
let geometry = extrude_path(path_data, resolution);
|
||||||
output.push(geometry);
|
output.push(geometry);
|
||||||
} else if arg_type == 1 {
|
continue;
|
||||||
// this is geometry
|
|
||||||
output.push(arg.to_vec());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output.push(arg.to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
concat_args(output.iter().map(|v| v.as_slice()).collect())
|
concat_args(output.iter().map(|v| v.as_slice()).collect())
|
||||||
|
@ -184,7 +184,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
const inputNode = node.tmp?.inputNodes?.[key];
|
const inputNode = node.tmp?.inputNodes?.[key];
|
||||||
if (inputNode) {
|
if (inputNode) {
|
||||||
if (results[inputNode.id] === undefined) {
|
if (results[inputNode.id] === undefined) {
|
||||||
throw new Error("Input node has no result");
|
throw new Error(`Node ${node.type} is missing input from node ${inputNode.type}`);
|
||||||
}
|
}
|
||||||
return results[inputNode.id];
|
return results[inputNode.id];
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{decode_float, encode_float};
|
use crate::{decode_float, encode_float};
|
||||||
use glam::Vec3;
|
use glam::Vec3;
|
||||||
|
|
||||||
pub fn calculate_normals(geometry: &mut [i32]) {
|
pub fn calculate_normals(geometry: &mut [i32]) -> &mut [i32] {
|
||||||
let vertex_count = geometry[1] as usize;
|
let vertex_count = geometry[1] as usize;
|
||||||
let face_count = geometry[2] as usize;
|
let face_count = geometry[2] as usize;
|
||||||
let index_start = 3;
|
let index_start = 3;
|
||||||
@ -47,4 +47,5 @@ pub fn calculate_normals(geometry: &mut [i32]) {
|
|||||||
geometry[normals_start + idx + 2] = encode_float(normal.z);
|
geometry[normals_start + idx + 2] = encode_float(normal.z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
geometry
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,30 @@ pub struct GeometryData<'a> {
|
|||||||
pub faces: &'a mut [i32], // View into `data`
|
pub faces: &'a mut [i32], // View into `data`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GeometryData<'_> {
|
||||||
|
pub fn set_position(&mut self, index: usize, x: f32, y: f32, z: f32) {
|
||||||
|
assert!(index < self.positions.len() / 3, "Index out of bounds.");
|
||||||
|
let i = index * 3;
|
||||||
|
self.positions[i] = x;
|
||||||
|
self.positions[i + 1] = y;
|
||||||
|
self.positions[i + 2] = z;
|
||||||
|
}
|
||||||
|
pub fn set_normal(&mut self, index: usize, x: f32, y: f32, z: f32) {
|
||||||
|
assert!(index < self.normals.len() / 3, "Index out of bounds.");
|
||||||
|
let i = index * 3;
|
||||||
|
self.normals[i] = x;
|
||||||
|
self.normals[i + 1] = y;
|
||||||
|
self.normals[i + 2] = z;
|
||||||
|
}
|
||||||
|
pub fn set_face(&mut self, index: usize, a: i32, b: i32, c: i32) {
|
||||||
|
assert!(index < self.faces.len() / 3, "Index out of bounds.");
|
||||||
|
let i = index * 3;
|
||||||
|
self.faces[i] = a;
|
||||||
|
self.faces[i + 1] = b;
|
||||||
|
self.faces[i + 2] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_geometry_data(vertex_amount: usize, face_amount: usize) -> Vec<i32> {
|
pub fn create_geometry_data(vertex_amount: usize, face_amount: usize) -> Vec<i32> {
|
||||||
let amount = GEOMETRY_HEADER_SIZE // definition (type, vertex_amount, face_amount)
|
let amount = GEOMETRY_HEADER_SIZE // definition (type, vertex_amount, face_amount)
|
||||||
+ 4 // opening and closing brackets
|
+ 4 // opening and closing brackets
|
||||||
|
118
packages/utils/src/geometry/instance_data.rs
Normal file
118
packages/utils/src/geometry/instance_data.rs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
use super::GeometryData;
|
||||||
|
|
||||||
|
pub struct InstanceData<'a> {
|
||||||
|
pub instances: &'a mut [f32], // View into `data`
|
||||||
|
pub instance_amount: usize,
|
||||||
|
pub positions: &'a mut [f32], // View into `data`
|
||||||
|
pub normals: &'a mut [f32], // View into `data`
|
||||||
|
pub faces: &'a mut [i32], // View into `data`
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstanceData<'_> {
|
||||||
|
pub fn set_geometry(&mut self, geometry: GeometryData) {
|
||||||
|
assert_eq!(self.positions.len(), geometry.positions.len());
|
||||||
|
|
||||||
|
for i in 0..self.positions.len() {
|
||||||
|
self.positions[i] = geometry.positions[i];
|
||||||
|
}
|
||||||
|
for i in 0..self.normals.len() {
|
||||||
|
self.normals[i] = geometry.normals[i];
|
||||||
|
}
|
||||||
|
for i in 0..self.faces.len() {
|
||||||
|
self.faces[i] = geometry.faces[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_transformation_matrix(&mut self, index: usize, matrix: &[f32; 16]) {
|
||||||
|
assert_eq!(matrix.len(), 16, "Matrix length must be 16.");
|
||||||
|
assert!(
|
||||||
|
index * 16 + 16 <= self.instances.len(),
|
||||||
|
"Matrix does not fit. index: {} len: {}",
|
||||||
|
index,
|
||||||
|
self.instances.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
(0..16).for_each(|i| {
|
||||||
|
self.instances[index * 16 + i] = matrix[i];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static INSTANCE_HEADER_SIZE: usize = 5;
|
||||||
|
// 0: type instance = 2;
|
||||||
|
// 1: vertex amount
|
||||||
|
// 2: face amount
|
||||||
|
// 3: stem_depth
|
||||||
|
// 4: instance amount
|
||||||
|
|
||||||
|
pub fn create_instance_data(
|
||||||
|
vertex_amount: usize,
|
||||||
|
face_amount: usize,
|
||||||
|
instance_amount: usize,
|
||||||
|
) -> Vec<i32> {
|
||||||
|
let amount = INSTANCE_HEADER_SIZE // definition (type, vertex_amount, face_amount, instance_amount)
|
||||||
|
+ 4 // opening and closing brackets
|
||||||
|
+ instance_amount * 16 // instances stored as 4x4 matrices
|
||||||
|
+ 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 - 3; // opening bracket
|
||||||
|
geo[2] = 2; // type: instance
|
||||||
|
geo[3] = vertex_amount as i32;
|
||||||
|
geo[4] = face_amount as i32;
|
||||||
|
geo[5] = instance_amount as i32;
|
||||||
|
geo[6] = 1; // stem_depth
|
||||||
|
|
||||||
|
geo[amount - 2] = 1; // closing bracket
|
||||||
|
geo[amount - 1] = 1; // closing bracket
|
||||||
|
|
||||||
|
geo
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrap_instance_data(instances: &mut [i32]) -> InstanceData {
|
||||||
|
assert!(
|
||||||
|
instances.len() > INSTANCE_HEADER_SIZE,
|
||||||
|
"Instance vector does not contain enough data for a header."
|
||||||
|
);
|
||||||
|
|
||||||
|
let (header, rest) = instances.split_at_mut(2 + INSTANCE_HEADER_SIZE);
|
||||||
|
|
||||||
|
let vertices_amount = header[3] as usize;
|
||||||
|
let face_amount = header[4] as usize;
|
||||||
|
let instance_amount = header[5] as usize;
|
||||||
|
|
||||||
|
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) = rest.split_at_mut(vertices_amount * 3);
|
||||||
|
let (instances_slice, _) = rest.split_at_mut(instance_amount * 16);
|
||||||
|
|
||||||
|
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())
|
||||||
|
};
|
||||||
|
|
||||||
|
let instances: &mut [f32] = unsafe {
|
||||||
|
std::slice::from_raw_parts_mut(
|
||||||
|
instances_slice.as_mut_ptr() as *mut f32,
|
||||||
|
instances_slice.len(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
InstanceData {
|
||||||
|
instances,
|
||||||
|
instance_amount,
|
||||||
|
positions,
|
||||||
|
normals,
|
||||||
|
faces,
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
mod calculate_normals;
|
mod calculate_normals;
|
||||||
mod extrude_path;
|
mod extrude_path;
|
||||||
mod geometry_data;
|
mod geometry_data;
|
||||||
|
mod instance_data;
|
||||||
mod math;
|
mod math;
|
||||||
mod path_data;
|
mod path_data;
|
||||||
mod transform;
|
mod transform;
|
||||||
@ -8,6 +9,7 @@ mod transform;
|
|||||||
pub use calculate_normals::*;
|
pub use calculate_normals::*;
|
||||||
pub use extrude_path::*;
|
pub use extrude_path::*;
|
||||||
pub use geometry_data::*;
|
pub use geometry_data::*;
|
||||||
|
pub use instance_data::*;
|
||||||
pub use math::*;
|
pub use math::*;
|
||||||
pub use path_data::*;
|
pub use path_data::*;
|
||||||
pub use transform::*;
|
pub use transform::*;
|
||||||
|
@ -31,6 +31,7 @@ impl PathDataMut<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct PathData<'a> {
|
pub struct PathData<'a> {
|
||||||
pub depth: i32,
|
pub depth: i32,
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
@ -55,6 +56,15 @@ impl PathData<'_> {
|
|||||||
}
|
}
|
||||||
l
|
l
|
||||||
}
|
}
|
||||||
|
pub fn get_point_at(&self, alpha: f32) -> [f32; 4] {
|
||||||
|
get_point_at_path(self.points, alpha)
|
||||||
|
}
|
||||||
|
pub fn get_direction_at(&self, alpha: f32) -> [f32; 3] {
|
||||||
|
get_direction_at_path(self.points, alpha)
|
||||||
|
}
|
||||||
|
pub fn interpolate_along(&self, alpha: f32) -> (Vec4, Vec3, Vec3) {
|
||||||
|
interpolate_along_path(self.points, alpha)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_multiple_paths(amount: usize, point_amount: usize, depth: i32) -> Vec<i32> {
|
pub fn create_multiple_paths(amount: usize, point_amount: usize, depth: i32) -> Vec<i32> {
|
||||||
|
@ -118,11 +118,12 @@ pub fn concat_args(mut data: Vec<&[i32]>) -> Vec<i32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn wrap_arg(arg: &[i32]) -> Vec<i32> {
|
pub fn wrap_arg(arg: &[i32]) -> Vec<i32> {
|
||||||
let mut out_args = Vec::with_capacity(arg.len() + 8);
|
let mut out_args = Vec::with_capacity(arg.len() + 4);
|
||||||
out_args.extend_from_slice(&[0, 1, 0, arg.len() as i32 + 1]);
|
out_args.push(0);
|
||||||
|
out_args.push(arg.len() as i32 + 1);
|
||||||
out_args.extend_from_slice(arg);
|
out_args.extend_from_slice(arg);
|
||||||
out_args.extend_from_slice(&[1, 1]);
|
out_args.push(1);
|
||||||
out_args.extend_from_slice(&[1, 1]);
|
out_args.push(1);
|
||||||
out_args
|
out_args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user