feat: instance node
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m44s

This commit is contained in:
max_richter 2024-05-06 01:10:23 +02:00
parent a01a409b97
commit 10a12ad41c
20 changed files with 695 additions and 147 deletions

15
Cargo.lock generated
View File

@ -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"

View File

@ -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) {

View 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);
}
}
}
}
}

View File

@ -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
View 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...
]

View File

@ -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

View File

@ -0,0 +1,6 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log

View 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"

View 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'"
}
}

View 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
}
}
}

View 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)
}

View 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);
}

View File

@ -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())

View File

@ -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];
} }

View File

@ -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
} }

View File

@ -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

View 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,
}
}

View File

@ -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::*;

View File

@ -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> {

View File

@ -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
} }