Reputation: 198
I'm trying to recreate some old Unity QuadSphere code with a @react-three/fiber
version, and I've written a quick test that creates a custom class, QuadGeometry
extending from THREE.BufferGeometry
in which I generate my vertices and indices. The starting configuration looks something like:
6-7-8
|\ /|
3 4 5
|/ \|
0-1-2
where 0 through 8 are groups of 3 numbers creating my vertices array which is then added as an attribute using setAttribute('position', new Float32BufferAttribute(vertices, 3))
and my indices are [0,4,2,2,4,8,8,4,6,6,4,0]
which are specified using setIndex(indices)
. this all works perfectly well and displays correctly. the problem occurs when I go to update the indices so that my quad has activated sides such that it would look like:
6-7-8
|\|/|
3-4-5
|/|\|
0-1-2
the above change only modifies the indices to be like [0,4,1,1,4,2...6,4,3,3,4,0]
(more indices for more triangles) and I set these by again calling setIndex(indices)
, but my mesh isn't updating to reflect the change in faces (indices). I've setup my mesh
using the following so that it will update the indices every 5 seconds:
export function QuadMesh(props: QuadMeshProps) {
const meshRef = useRef<Mesh>();
const [state, setState] = useState<QuadState>('unified');
const quad = useMemo<QuadGeometry>(() => {
console.info('creating new QuadGeometry!', {props});
return new QuadGeometry({
centre: {x: props.position[0] ?? 0, y: props.position[1] ?? 0, z: props.position[2] ?? 0},
radius: props.radius ?? 1
});
}, [props]);
let nextChangeAt: number;
const changeFrequency = 5; // 5 seconds
useFrame(({ clock }) => {
const time = clock.getElapsedTime(); // in seconds
if (nextChangeAt == null) {
nextChangeAt = time + changeFrequency;
}
if (time >= nextChangeAt) {
nextChangeAt = time + changeFrequency;
const geometry = meshRef.current.geometry as QuadGeometry;
setState(modifyQuadGeometry(geometry, state));
}
});
return (
<mesh ref={meshRef} dispose={null} geometry={quad} castShadow receiveShadow>
<meshBasicMaterial attach="material" wireframe={true} />
</mesh>
);
}
function modifyQuadGeometry(geometry: QuadGeometry, state: QuadState): QuadState {
let outState: QuadState;
/* update to indices happens here on geometry obj */
const { position } = geometry.attributes;
position.needsUpdate = true;
geometry.computeVertexNormals();
return outState;
}
so how should I go about triggering an update of the faces / triangles in my mesh?
NOTE: if I modify the indices before I generate the mesh then it renders with the expected additional faces, but I specifically want to modify it at runtime as this is meant to be used to generate a level of detail effect (eventually).
You can see the code at: https://github.com/bicarbon8/QuadSphere/blob/in-javascript/src/components/shapes.tsx
and a demo at: https://bicarbon8.github.io/QuadSphere/
Upvotes: 0
Views: 710
Reputation: 198
ok, so I consider this a bit of a hack, but for the sake of anyone who is trying to do something similar, I managed to get something similar to what I want by forcing the mesh
to fully re-load and render by adding a key
property based on the QuadGeometry
id and active sides and then also creating a recursive function to render all child QuadGeometry
objects in their own mesh and not rendering a mesh of my QuadGeometry
if it has any children. the updated code looks like this
export function QuadMesh(props: QuadMeshProps) {
const [level, setLevel] = useState<number>(0);
const [offset, setOffset] = useState<number>(1);
const registry = useMemo<QuadRegistry>(() => {
console.info('creating new QuadRegistry!');
return new QuadRegistry();
}, [props]);
const quad = useMemo<QuadGeometry>(() => {
console.info('creating new QuadGeometry!', {props});
return new QuadGeometry({
centre: {x: props.position[0] ?? 0, y: props.position[1] ?? 0, z: props.position[2] ?? 0},
radius: props.radius ?? 1,
registry: registry
});
}, [props]);
return MeshBufferGeom({quad});
}
function MeshBufferGeom(props: {quad: QuadGeometry}) {
const meshes = new Array<MeshProps>();
if (!props.quad.hasChildren()) {
const positions = new Float32Array(props.quad.vertices);
const indices = new Uint16Array(props.quad.indices);
meshes.push(
<mesh key={`${props.quad.id}-${props.quad.activeSides.join('-')}`} castShadow receiveShadow>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
array={positions}
count={positions.length / 3}
itemSize={3} />
<bufferAttribute
attach="index"
array={indices}
count={indices.length}
itemSize={1} />
</bufferGeometry>
<meshBasicMaterial attach="material" wireframe={true} />
</mesh>
);
} else {
meshes.push(...[
props.quad.bottomleftChild,
props.quad.bottomrightChild,
props.quad.topleftChild,
props.quad.toprightChild
].map(c => MeshBufferGeom({quad: c})));
}
return (
<>
{...meshes}
</>
);
}
where MeshBufferGeom
is the recursive function that returns either a single or multiple <mesh>...</mesh>
elements (if multiple it's all of the child and children's children, etc. mesh objects). I'm still hoping someone can tell me a better way to update the faces on an existing mesh
so I can reduce the number of throw-away objects I'm creating and disposing of each time the QuadGeometry
activates a side, but for now this will work
Upvotes: 0