greedy meshing
This commit is contained in:
@@ -1,35 +1,23 @@
|
||||
import type { ObjectType, RuntimeGameObjectInstance, RuntimeObjectInstance } from "../types";
|
||||
import type { VoxelGroup } from "../types/runtime/voxelGroup";
|
||||
import { useEffect, useImperativeHandle, useLayoutEffect, useRef, type Ref, memo } from "react";
|
||||
import { Euler, Matrix4, Quaternion, type Group, type InstancedMesh } from "three";
|
||||
import { useEffect, useImperativeHandle, useRef, type Ref, memo, useMemo } from "react";
|
||||
import { Euler, Quaternion, type Group } from "three";
|
||||
import type { ThreeEvent } from "@react-three/fiber";
|
||||
import { ConvexHullCollider, RapierRigidBody } from "@react-three/rapier";
|
||||
import { SyncRigidBody } from "./SyncRigidBody";
|
||||
import { reaction, runInAction } from "mobx";
|
||||
import { v3toRapier } from "../utils";
|
||||
import { state } from "../state";
|
||||
|
||||
const _m4 = new Matrix4();
|
||||
import { greedyMesh } from "../utils/graphics/greedyMesh";
|
||||
|
||||
const VoxelGroupMesh = memo(function VoxelGroupMesh({ vg }: { vg: VoxelGroup }) {
|
||||
const ref = useRef<InstancedMesh>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const mesh = ref.current;
|
||||
if (!mesh)
|
||||
return;
|
||||
vg.positions.forEach(([x, y, z], i) => {
|
||||
mesh.setMatrixAt(i, _m4.makeTranslation(x, y, z));
|
||||
});
|
||||
mesh.instanceMatrix.needsUpdate = true;
|
||||
mesh.computeBoundingSphere();
|
||||
}, [vg]);
|
||||
const geo = useMemo(() => greedyMesh(vg.positions), [vg]);
|
||||
useEffect(() => () => { geo.dispose(); }, [geo]);
|
||||
|
||||
return (
|
||||
<instancedMesh ref={ref} args={[undefined, undefined, vg.positions.length]}>
|
||||
<boxGeometry args={[1, 1, 1]} />
|
||||
<mesh geometry={geo}>
|
||||
<meshStandardMaterial color={vg.color} opacity={vg.opacity} transparent={vg.opacity < 1} />
|
||||
</instancedMesh>
|
||||
</mesh>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { BufferGeometry, Float32BufferAttribute } from 'three';
|
||||
import type { V3 } from '../../types';
|
||||
|
||||
// tangent axes for each main axis
|
||||
const U_OF = [1, 0, 0] as const;
|
||||
const V_OF = [2, 2, 1] as const;
|
||||
|
||||
/**
|
||||
* Greedy meshing: merges face-adjacent same-material voxels into minimal quads.
|
||||
* A 50×1×50 flat layer produces 6 quads (12 triangles), not 50×50×6×2.
|
||||
*
|
||||
* Only face-adjacency counts — diagonal neighbours do not merge.
|
||||
*/
|
||||
export function greedyMesh(positions: V3[]): BufferGeometry {
|
||||
if (!positions.length)
|
||||
return new BufferGeometry();
|
||||
|
||||
const occ = new Set<string>();
|
||||
let x0 = Infinity, y0 = Infinity, z0 = Infinity;
|
||||
let x1 = -Infinity, y1 = -Infinity, z1 = -Infinity;
|
||||
|
||||
for (const [x, y, z] of positions) {
|
||||
occ.add(`${x},${y},${z}`);
|
||||
if (x < x0)
|
||||
x0 = x;
|
||||
if (x > x1)
|
||||
x1 = x;
|
||||
if (y < y0)
|
||||
y0 = y;
|
||||
if (y > y1)
|
||||
y1 = y;
|
||||
if (z < z0)
|
||||
z0 = z;
|
||||
if (z > z1)
|
||||
z1 = z;
|
||||
}
|
||||
|
||||
const has = (x: number, y: number, z: number) => occ.has(`${x},${y},${z}`);
|
||||
const lo = [x0, y0, z0];
|
||||
const hi = [x1, y1, z1];
|
||||
|
||||
const verts: number[] = [];
|
||||
const inds: number[] = [];
|
||||
|
||||
for (let axis = 0; axis < 3; axis++) {
|
||||
const u = U_OF[axis];
|
||||
const v = V_OF[axis];
|
||||
const dLen = hi[axis] - lo[axis] + 1;
|
||||
const uLen = hi[u] - lo[u] + 1;
|
||||
const vLen = hi[v] - lo[v] + 1;
|
||||
const mask = new Uint8Array(uLen * vLen);
|
||||
const done = new Uint8Array(uLen * vLen);
|
||||
|
||||
for (let dir = -1; dir <= 1; dir += 2) {
|
||||
// For corner order (u0,v0)(u1,v0)(u1,v1)(u0,v1),
|
||||
// cross product = +e_axis when axis≠Y, −e_Y when axis=Y.
|
||||
// flip reverses winding to point outward for the given dir.
|
||||
const flip = axis === 1 ? dir > 0 : dir < 0;
|
||||
|
||||
for (let di = 0; di < dLen; di++) {
|
||||
const D = lo[axis] + di;
|
||||
|
||||
mask.fill(0);
|
||||
done.fill(0);
|
||||
|
||||
for (let vi = 0; vi < vLen; vi++) {
|
||||
for (let ui = 0; ui < uLen; ui++) {
|
||||
const p: [number, number, number] = [0, 0, 0];
|
||||
p[axis] = D;
|
||||
p[u] = lo[u] + ui;
|
||||
p[v] = lo[v] + vi;
|
||||
if (!has(p[0], p[1], p[2]))
|
||||
continue;
|
||||
const np: [number, number, number] = [p[0], p[1], p[2]];
|
||||
np[axis] += dir;
|
||||
if (!has(np[0], np[1], np[2]))
|
||||
mask[ui + vi * uLen] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (let vi = 0; vi < vLen; vi++) {
|
||||
for (let ui = 0; ui < uLen; ui++) {
|
||||
if (!mask[ui + vi * uLen] || done[ui + vi * uLen])
|
||||
continue;
|
||||
|
||||
let wu = 1;
|
||||
while (ui + wu < uLen && mask[ui + wu + vi * uLen] && !done[ui + wu + vi * uLen])
|
||||
wu++;
|
||||
|
||||
let wv = 1;
|
||||
expand: while (vi + wv < vLen) {
|
||||
for (let k = 0; k < wu; k++)
|
||||
if (!mask[ui + k + (vi + wv) * uLen] || done[ui + k + (vi + wv) * uLen])
|
||||
break expand;
|
||||
wv++;
|
||||
}
|
||||
|
||||
for (let dv = 0; dv < wv; dv++)
|
||||
for (let du = 0; du < wu; du++)
|
||||
done[ui + du + (vi + dv) * uLen] = 1;
|
||||
|
||||
const dp = D + (dir > 0 ? 0.5 : -0.5);
|
||||
const uA = lo[u] + ui - 0.5;
|
||||
const uB = uA + wu;
|
||||
const vA = lo[v] + vi - 0.5;
|
||||
const vB = vA + wv;
|
||||
|
||||
const base = verts.length / 3;
|
||||
for (const [cu, cv] of [[uA, vA], [uB, vA], [uB, vB], [uA, vB]] as const) {
|
||||
const pt: [number, number, number] = [0, 0, 0];
|
||||
pt[axis] = dp;
|
||||
pt[u] = cu;
|
||||
pt[v] = cv;
|
||||
verts.push(pt[0], pt[1], pt[2]);
|
||||
}
|
||||
|
||||
if (flip) {
|
||||
inds.push(base, base + 2, base + 1, base, base + 3, base + 2);
|
||||
} else {
|
||||
inds.push(base, base + 1, base + 2, base, base + 2, base + 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const geo = new BufferGeometry();
|
||||
geo.setAttribute('position', new Float32BufferAttribute(verts, 3));
|
||||
geo.setIndex(inds);
|
||||
geo.computeVertexNormals();
|
||||
return geo;
|
||||
}
|
||||
Reference in New Issue
Block a user