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:
@ -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 };
|
||||
}
|
||||
|
Reference in New Issue
Block a user