Files
webeditor3d/src/geometry/model-instance-collider-debug-mesh.ts

166 lines
4.7 KiB
TypeScript
Raw Normal View History

import {
BoxGeometry,
BufferGeometry,
Float32BufferAttribute,
Group,
Mesh,
MeshBasicMaterial,
Vector3,
type Material
} from "three";
import { ConvexGeometry } from "three/examples/jsm/geometries/ConvexGeometry.js";
import type {
GeneratedModelBoxCollider,
GeneratedModelCollider,
GeneratedModelCompoundCollider,
GeneratedModelHeightfieldCollider,
GeneratedModelTriMeshCollider
} from "./model-instance-collider-generation";
const DEBUG_COLLIDER_COLORS = {
simple: 0x87d2ff,
terrain: 0x7be7b4,
static: 0xffc66d,
dynamic: 0xff8b7a
} as const;
function createWireframeMaterial(color: number): MeshBasicMaterial {
return new MeshBasicMaterial({
color,
wireframe: true,
transparent: true,
opacity: 0.85,
depthWrite: false,
toneMapped: false
});
}
function markDebugMesh(mesh: Mesh) {
mesh.userData.shadowIgnored = true;
mesh.userData.nonPickable = true;
mesh.renderOrder = 3_500;
}
function createBoxColliderDebugMesh(collider: GeneratedModelBoxCollider): Mesh {
const mesh = new Mesh(new BoxGeometry(collider.size.x, collider.size.y, collider.size.z), createWireframeMaterial(DEBUG_COLLIDER_COLORS.simple));
mesh.position.set(collider.center.x, collider.center.y, collider.center.z);
markDebugMesh(mesh);
return mesh;
}
function createTriMeshColliderDebugMesh(collider: GeneratedModelTriMeshCollider): Mesh {
const geometry = new BufferGeometry();
geometry.setAttribute("position", new Float32BufferAttribute(collider.vertices, 3));
geometry.setIndex(Array.from(collider.indices));
const mesh = new Mesh(geometry, createWireframeMaterial(DEBUG_COLLIDER_COLORS.static));
markDebugMesh(mesh);
return mesh;
}
function createHeightfieldColliderDebugMesh(collider: GeneratedModelHeightfieldCollider): Mesh {
const vertices: number[] = [];
const indices: number[] = [];
const width = collider.maxX - collider.minX;
const depth = collider.maxZ - collider.minZ;
for (let zIndex = 0; zIndex < collider.cols; zIndex += 1) {
const zLerp = collider.cols === 1 ? 0 : zIndex / (collider.cols - 1);
const z = collider.minZ + depth * zLerp;
for (let xIndex = 0; xIndex < collider.rows; xIndex += 1) {
const xLerp = collider.rows === 1 ? 0 : xIndex / (collider.rows - 1);
const x = collider.minX + width * xLerp;
const y = collider.heights[xIndex + zIndex * collider.rows];
vertices.push(x, y, z);
}
}
for (let zIndex = 0; zIndex < collider.cols - 1; zIndex += 1) {
for (let xIndex = 0; xIndex < collider.rows - 1; xIndex += 1) {
const topLeft = xIndex + zIndex * collider.rows;
const topRight = topLeft + 1;
const bottomLeft = topLeft + collider.rows;
const bottomRight = bottomLeft + 1;
indices.push(topLeft, bottomLeft, bottomRight, topLeft, bottomRight, topRight);
}
}
const geometry = new BufferGeometry();
geometry.setAttribute("position", new Float32BufferAttribute(vertices, 3));
geometry.setIndex(indices);
const mesh = new Mesh(geometry, createWireframeMaterial(DEBUG_COLLIDER_COLORS.terrain));
markDebugMesh(mesh);
return mesh;
}
function createCompoundColliderDebugGroup(collider: GeneratedModelCompoundCollider): Group {
const group = new Group();
for (const piece of collider.pieces) {
const points: Vector3[] = [];
for (let index = 0; index < piece.points.length; index += 3) {
points.push(new Vector3(piece.points[index], piece.points[index + 1], piece.points[index + 2]));
}
const mesh = new Mesh(new ConvexGeometry(points), createWireframeMaterial(DEBUG_COLLIDER_COLORS.dynamic));
markDebugMesh(mesh);
group.add(mesh);
}
return group;
}
export function createModelColliderDebugGroup(collider: GeneratedModelCollider): Group {
const group = new Group();
switch (collider.kind) {
case "box":
group.add(createBoxColliderDebugMesh(collider));
break;
case "trimesh":
group.add(createTriMeshColliderDebugMesh(collider));
break;
case "heightfield":
group.add(createHeightfieldColliderDebugMesh(collider));
break;
case "compound":
group.add(createCompoundColliderDebugGroup(collider));
break;
}
group.userData.nonPickable = true;
return group;
}
function disposeMaterial(material: Material | Material[]) {
if (Array.isArray(material)) {
for (const item of material) {
item.dispose();
}
return;
}
material.dispose();
}
export function disposeModelColliderDebugGroup(group: Group) {
group.traverse((object) => {
const maybeMesh = object as Mesh & { isMesh?: boolean };
if (maybeMesh.isMesh !== true) {
return;
}
maybeMesh.geometry.dispose();
disposeMaterial(maybeMesh.material);
});
}