Files
nodarium/app/src/lib/result-viewer/geometryPool.ts
Max Richter d64877666b
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m47s
fix: 120 type errors
2025-11-24 00:10:38 +01:00

271 lines
7.2 KiB
TypeScript

import { fastHashArrayBuffer } from "@nodes/utils";
import {
BufferAttribute,
BufferGeometry,
Float32BufferAttribute,
Group,
InstancedMesh,
Material,
Matrix4,
Mesh,
} from "three";
function fastArrayHash(arr: Int32Array) {
const sampleDistance = Math.max(Math.floor(arr.length / 100), 1);
const sampleCount = Math.floor(arr.length / sampleDistance);
let hash = new Int32Array(sampleCount);
for (let i = 0; i < sampleCount; i++) {
const index = i * sampleDistance;
hash[i] = arr[index];
}
return fastHashArrayBuffer(hash);
}
export function createGeometryPool(parentScene: Group, material: Material) {
const scene = new Group();
parentScene.add(scene);
let meshes: Mesh[] = [];
let totalVertices = 0;
let totalFaces = 0;
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++];
totalVertices += vertexCount;
const faceCount = data[index++];
totalFaces += faceCount;
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[]) {
totalVertices = 0;
totalFaces = 0;
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);
}
}
return { totalVertices, totalFaces };
},
};
}
export function createInstancedGeometryPool(
parentScene: Group,
material: Material,
) {
const scene = new Group();
parentScene.add(scene);
const instances: InstancedMesh[] = [];
let totalVertices = 0;
let totalFaces = 0;
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++];
index++;
const vertexCount = data[index++];
const faceCount = data[index++];
const instanceCount = data[index++];
// const stemDepth = data[index++];
index++;
totalVertices += vertexCount * instanceCount;
totalFaces += faceCount * instanceCount;
// 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[]) {
totalVertices = 0;
totalFaces = 0;
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);
}
}
return { totalVertices, totalFaces };
},
};
}