mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-01-19 20:25:01 +00:00
115 lines
3.1 KiB
TypeScript
115 lines
3.1 KiB
TypeScript
import { useGLTF, useTexture } from "@react-three/drei";
|
|
import { BASE_URL, interiorTextureToUrl, interiorToUrl } from "@/src/loaders";
|
|
import {
|
|
ConsoleObject,
|
|
getPosition,
|
|
getProperty,
|
|
getRotation,
|
|
getScale,
|
|
} from "@/src/mission";
|
|
import { memo, Suspense, useEffect, useMemo } from "react";
|
|
import { Material, Mesh, MeshBasicMaterial } from "three";
|
|
import { setupColor } from "@/src/textureUtils";
|
|
|
|
const FALLBACK_URL = `${BASE_URL}/black.png`;
|
|
|
|
/**
|
|
* Load a .gltf file that was converted from a .dif, used for "interior" models.
|
|
*/
|
|
function useInterior(interiorFile: string) {
|
|
const url = interiorToUrl(interiorFile);
|
|
return useGLTF(url);
|
|
}
|
|
|
|
function InteriorTexture({ material }: { material: Material }) {
|
|
let url = FALLBACK_URL;
|
|
try {
|
|
url = interiorTextureToUrl(material.name);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
|
|
const texture = useTexture(url, (texture) => setupColor(texture));
|
|
|
|
useEffect(() => {
|
|
const asBasicMaterial = material as MeshBasicMaterial;
|
|
asBasicMaterial.map = texture;
|
|
asBasicMaterial.needsUpdate = true;
|
|
}, [material, texture]);
|
|
|
|
return <primitive object={material} attach="material" />;
|
|
}
|
|
|
|
function InteriorMesh({ node }: { node: Mesh }) {
|
|
return (
|
|
<mesh 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="yellow" wireframe />
|
|
}
|
|
>
|
|
{Array.isArray(node.material) ? (
|
|
node.material.map((mat, index) => (
|
|
<InteriorTexture key={index} material={mat} />
|
|
))
|
|
) : (
|
|
<InteriorTexture material={node.material} />
|
|
)}
|
|
</Suspense>
|
|
) : null}
|
|
</mesh>
|
|
);
|
|
}
|
|
|
|
export const InteriorModel = memo(
|
|
({ interiorFile }: { interiorFile: string }) => {
|
|
const { nodes } = useInterior(interiorFile);
|
|
|
|
return (
|
|
<>
|
|
{Object.entries(nodes)
|
|
.filter(
|
|
([name, node]: [string, any]) =>
|
|
!node.material || !node.material.name.match(/\.\d+$/)
|
|
)
|
|
.map(([name, node]: [string, any]) => (
|
|
<InteriorMesh key={name} node={node} />
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
);
|
|
|
|
function InteriorPlaceholder() {
|
|
return (
|
|
<mesh>
|
|
<boxGeometry args={[10, 10, 10]} />
|
|
<meshStandardMaterial color="orange" wireframe />
|
|
</mesh>
|
|
);
|
|
}
|
|
|
|
export const InteriorInstance = memo(
|
|
({ object }: { object: ConsoleObject }) => {
|
|
const interiorFile = getProperty(object, "interiorFile").value;
|
|
const [z, y, x] = useMemo(() => getPosition(object), [object]);
|
|
const [scaleX, scaleY, scaleZ] = useMemo(() => getScale(object), [object]);
|
|
const q = useMemo(() => getRotation(object, true), [object]);
|
|
|
|
return (
|
|
<group
|
|
quaternion={q}
|
|
position={[x - 1024, y, z - 1024]}
|
|
scale={[-scaleX, scaleY, -scaleZ]}
|
|
>
|
|
<Suspense fallback={<InteriorPlaceholder />}>
|
|
<InteriorModel interiorFile={interiorFile} />
|
|
</Suspense>
|
|
</group>
|
|
);
|
|
}
|
|
);
|