bicarbon8
bicarbon8

Reputation: 198

modifying triangles and vertices on react-three-fiber mesh after render

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

Answers (1)

bicarbon8
bicarbon8

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

Related Questions