Hide hulls.

This commit is contained in:
bmathews 2025-11-14 23:43:31 -08:00
parent 1c6d7effac
commit c09fb315d5
2 changed files with 139 additions and 27 deletions

View file

@ -2,6 +2,8 @@ import { Suspense } from "react";
import { useGLTF, useTexture } from "@react-three/drei";
import { BASE_URL, shapeTextureToUrl, shapeToUrl } from "../loaders";
import { setupColor } from "../textureUtils";
import { filterGeometryByVertexGroups, getHullBoneIndices } from "../meshUtils";
import { MeshStandardMaterial } from "three";
const FALLBACK_URL = `${BASE_URL}/black.png`;
@ -13,45 +15,74 @@ export function useStaticShape(shapeName: string) {
return useGLTF(url);
}
export function ShapeTexture({ materialName }: { materialName: string }) {
// console.log({ materialName });
const url = shapeTextureToUrl(materialName, FALLBACK_URL);
export function ShapeTexture({
material,
}: {
material?: MeshStandardMaterial;
}) {
const url = shapeTextureToUrl(material.name, FALLBACK_URL);
const texture = useTexture(url, (texture) => setupColor(texture));
return <meshStandardMaterial map={texture} side={2} />;
material.map = texture;
material.side = 2;
material.transparent = true;
return <primitive object={material} attach="material" />;
}
export function ShapeModel({ shapeName }: { shapeName: string }) {
const { nodes } = useStaticShape(shapeName);
let hullBoneIndices = new Set<number>();
const skeletonsFound = Object.values(nodes).filter(
(node: any) => node.skeleton
);
if (skeletonsFound.length > 0) {
const skeleton = (skeletonsFound[0] as any).skeleton;
hullBoneIndices = getHullBoneIndices(skeleton);
}
return (
<>
{Object.entries(nodes)
.filter(
([name, node]: [string, any]) =>
!node.material || !node.material.name.match(/\.\d+$/)
node.material &&
node.material.name !== "Unassigned" &&
!node.name.match(/^Hulk/i)
)
.map(([name, node]: [string, any]) => (
<mesh key={node.id} geometry={node.geometry} castShadow receiveShadow>
{node.material ? (
<Suspense
fallback={
// Allow the mesh to render while the texture is still loading;
// show a wireframe placeholder.
<meshStandardMaterial color="gray" wireframe />
}
>
{Array.isArray(node.material) ? (
node.material.map((mat, index) => (
<ShapeTexture key={index} materialName={mat.name} />
))
) : (
<ShapeTexture materialName={node.material.name} />
)}
</Suspense>
) : null}
</mesh>
))}
.map(([name, node]: [string, any]) => {
const geometry = filterGeometryByVertexGroups(
node.geometry,
hullBoneIndices
);
return (
<mesh key={node.id} geometry={geometry} castShadow receiveShadow>
{node.material ? (
<Suspense
fallback={
// Allow the mesh to render while the texture is still loading;
// show a wireframe placeholder.
<meshStandardMaterial color="gray" wireframe />
}
>
{Array.isArray(node.material) ? (
node.material.map((mat, index) => (
<ShapeTexture
key={index}
material={mat as MeshStandardMaterial}
/>
))
) : (
<ShapeTexture
material={node.material as MeshStandardMaterial}
/>
)}
</Suspense>
) : null}
</mesh>
);
})}
</>
);
}

81
src/meshUtils.ts Normal file
View file

@ -0,0 +1,81 @@
/**
* Extract hull bone indices from a skeleton
* @param skeleton - The Three.js skeleton to scan
* @returns Set of bone indices for bones matching the hull pattern (starts with "Hulk")
*/
export function getHullBoneIndices(skeleton: any): Set<number> {
const hullBoneIndices = new Set<number>();
skeleton.bones.forEach((bone: any, index: number) => {
if (bone.name.match(/^Hulk/i)) {
hullBoneIndices.add(index);
}
});
return hullBoneIndices;
}
/**
* Filter geometry by removing faces influenced by hull bones
* @param geometry - The Three.js geometry to filter
* @param hullBoneIndices - Set of bone indices that represent hull (collision) geometry
* @returns Filtered geometry with hull-influenced faces removed
*/
export function filterGeometryByVertexGroups(
geometry: any,
hullBoneIndices: Set<number>
): any {
// If no hull bones or no skinning data, return original geometry
if (hullBoneIndices.size === 0 || !geometry.attributes.skinIndex) {
return geometry;
}
const skinIndex = geometry.attributes.skinIndex;
const skinWeight = geometry.attributes.skinWeight;
const index = geometry.index;
// Track which vertices are influenced by hull bones
const vertexHasHullInfluence = new Array(skinIndex.count).fill(false);
// Check each vertex's bone influences
for (let i = 0; i < skinIndex.count; i++) {
for (let j = 0; j < 4; j++) {
const boneIndex = skinIndex.array[i * 4 + j];
const weight = skinWeight.array[i * 4 + j];
// If this vertex has significant weight to a hull bone, mark it
if (weight > 0.01 && hullBoneIndices.has(boneIndex)) {
vertexHasHullInfluence[i] = true;
break;
}
}
}
// Build new index array excluding faces that use hull-influenced vertices
if (index) {
const newIndices: number[] = [];
const indexArray = index.array;
for (let i = 0; i < indexArray.length; i += 3) {
const i0 = indexArray[i];
const i1 = indexArray[i + 1];
const i2 = indexArray[i + 2];
// Only keep face if all vertices don't have hull influence
if (
!vertexHasHullInfluence[i0] &&
!vertexHasHullInfluence[i1] &&
!vertexHasHullInfluence[i2]
) {
newIndices.push(i0, i1, i2);
}
}
// Create new geometry with filtered indices
const filteredGeometry = geometry.clone();
filteredGeometry.setIndex(newIndices);
return filteredGeometry;
}
return geometry;
}