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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "nodarium_macros"
|
||||
version = "0.1.0"
|
||||
|
@ -7,6 +7,9 @@
|
||||
Vector3,
|
||||
type Vector3Tuple,
|
||||
Box3,
|
||||
Mesh,
|
||||
MeshMatcapMaterial,
|
||||
MeshBasicMaterial,
|
||||
} from "three";
|
||||
import { AppSettings } from "../settings/app-settings";
|
||||
import Camera from "./Camera.svelte";
|
||||
@ -51,11 +54,21 @@
|
||||
export let centerCamera: boolean = true;
|
||||
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) {
|
||||
scene.children.forEach((child) => {
|
||||
child.material.wireframe = $AppSettings.wireframe;
|
||||
threlte.invalidate();
|
||||
scene.traverse(function (child) {
|
||||
if (isMesh(child) && isMatCapMaterial(child.material)) {
|
||||
child.material.wireframe = $AppSettings.wireframe;
|
||||
}
|
||||
});
|
||||
threlte.invalidate();
|
||||
}
|
||||
|
||||
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 { BufferAttribute, BufferGeometry, Float32BufferAttribute, Mesh, MeshMatcapMaterial, TextureLoader, type Group } 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);
|
||||
}
|
||||
import { MeshMatcapMaterial, TextureLoader, type Group } from "three";
|
||||
import { createGeometryPool, createInstancedGeometryPool } from "./geometryPool";
|
||||
|
||||
const loader = new TextureLoader();
|
||||
const matcap = loader.load('/matcap_green.jpg');
|
||||
@ -25,122 +9,19 @@ const material = new MeshMatcapMaterial({
|
||||
matcap
|
||||
});
|
||||
|
||||
function createGeometryFromEncodedData(
|
||||
encodedData: Int32Array,
|
||||
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[] = [];
|
||||
|
||||
let geometryPool: ReturnType<typeof createGeometryPool>;
|
||||
let instancePool: ReturnType<typeof createInstancedGeometryPool>;
|
||||
|
||||
export function updateGeometries(inputs: Int32Array[], group: Group) {
|
||||
|
||||
geometryPool = geometryPool || createGeometryPool(group, material);
|
||||
instancePool = instancePool || createInstancedGeometryPool(group, material);
|
||||
|
||||
let totalVertices = 0;
|
||||
let totalFaces = 0;
|
||||
let newGeometries = [];
|
||||
|
||||
for (let i = 0; i < Math.max(meshes.length, inputs.length); i++) {
|
||||
let existingMesh = meshes[i];
|
||||
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]);
|
||||
}
|
||||
geometryPool.update(inputs.filter(i => i[0] === 1));
|
||||
instancePool.update(inputs.filter(i => i[0] === 2));
|
||||
|
||||
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_utils::{
|
||||
encode_float, evaluate_float, geometry::calculate_normals, log, set_panic_hook, split_args,
|
||||
wrap_arg,
|
||||
concat_args, encode_float, evaluate_float, geometry::calculate_normals, log, set_panic_hook,
|
||||
split_args, wrap_arg,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
@ -81,7 +81,7 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
|
||||
let res = wrap_arg(&cube_geometry);
|
||||
|
||||
log!("WASM(cube): output: {:?}", res);
|
||||
log!("WASM(box): output: {:?}", 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> {
|
||||
set_panic_hook();
|
||||
|
||||
log!("WASM(output): input: {:?}", input);
|
||||
|
||||
let args = split_args(input);
|
||||
|
||||
log!("WASM(output) args: {:?}", args);
|
||||
@ -29,16 +31,15 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
log!("arg_type: {}, \n {:?}", arg_type, arg,);
|
||||
|
||||
if arg_type == 0 {
|
||||
// this is path
|
||||
let vec = arg.to_vec();
|
||||
output.push(vec.clone());
|
||||
// if this is path we need to extrude it
|
||||
output.push(arg.to_vec());
|
||||
let path_data = wrap_path(arg);
|
||||
let geometry = extrude_path(path_data, resolution);
|
||||
output.push(geometry);
|
||||
} else if arg_type == 1 {
|
||||
// this is geometry
|
||||
output.push(arg.to_vec());
|
||||
continue;
|
||||
}
|
||||
|
||||
output.push(arg.to_vec());
|
||||
}
|
||||
|
||||
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];
|
||||
if (inputNode) {
|
||||
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];
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{decode_float, encode_float};
|
||||
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 face_count = geometry[2] as usize;
|
||||
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
|
||||
}
|
||||
|
@ -11,6 +11,30 @@ pub struct GeometryData<'a> {
|
||||
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> {
|
||||
let amount = GEOMETRY_HEADER_SIZE // definition (type, vertex_amount, face_amount)
|
||||
+ 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 extrude_path;
|
||||
mod geometry_data;
|
||||
mod instance_data;
|
||||
mod math;
|
||||
mod path_data;
|
||||
mod transform;
|
||||
@ -8,6 +9,7 @@ mod transform;
|
||||
pub use calculate_normals::*;
|
||||
pub use extrude_path::*;
|
||||
pub use geometry_data::*;
|
||||
pub use instance_data::*;
|
||||
pub use math::*;
|
||||
pub use path_data::*;
|
||||
pub use transform::*;
|
||||
|
@ -31,6 +31,7 @@ impl PathDataMut<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PathData<'a> {
|
||||
pub depth: i32,
|
||||
pub length: usize,
|
||||
@ -55,6 +56,15 @@ impl PathData<'_> {
|
||||
}
|
||||
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> {
|
||||
|
@ -118,11 +118,12 @@ pub fn concat_args(mut data: Vec<&[i32]>) -> Vec<i32> {
|
||||
}
|
||||
|
||||
pub fn wrap_arg(arg: &[i32]) -> Vec<i32> {
|
||||
let mut out_args = Vec::with_capacity(arg.len() + 8);
|
||||
out_args.extend_from_slice(&[0, 1, 0, arg.len() as i32 + 1]);
|
||||
let mut out_args = Vec::with_capacity(arg.len() + 4);
|
||||
out_args.push(0);
|
||||
out_args.push(arg.len() as i32 + 1);
|
||||
out_args.extend_from_slice(arg);
|
||||
out_args.extend_from_slice(&[1, 1]);
|
||||
out_args.extend_from_slice(&[1, 1]);
|
||||
out_args.push(1);
|
||||
out_args.push(1);
|
||||
out_args
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user