set up static shape components

This commit is contained in:
Brian Beck 2025-11-14 19:05:24 -08:00
parent beade00727
commit d925585a34
7 changed files with 243 additions and 3 deletions

13
package-lock.json generated
View file

@ -16,6 +16,7 @@
"next": "^15.5.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-error-boundary": "^6.0.0",
"three": "^0.180.0",
"unzipper": "^0.12.3"
},
@ -2220,6 +2221,18 @@
"react": "^19.1.1"
}
},
"node_modules/react-error-boundary": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.0.0.tgz",
"integrity": "sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.12.5"
},
"peerDependencies": {
"react": ">=16.13.1"
}
},
"node_modules/react-reconciler": {
"version": "0.31.0",
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz",

View file

@ -23,6 +23,7 @@
"next": "^15.5.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-error-boundary": "^6.0.0",
"three": "^0.180.0",
"unzipper": "^0.12.3"
},

79
src/components/Item.tsx Normal file
View file

@ -0,0 +1,79 @@
import { Suspense, useMemo } from "react";
import { ErrorBoundary } from "react-error-boundary";
import {
ConsoleObject,
getPosition,
getProperty,
getRotation,
getScale,
} from "../mission";
import { shapeToUrl } from "../loaders";
import { useGLTF } from "@react-three/drei";
const dataBlockToShapeName = {
RepairPack: "pack_upgrade_repair.dts",
};
/**
* Load a .glb file that was converted from a .dts, used for static shapes.
*/
function useStaticShape(shapeName: string) {
const url = shapeToUrl(shapeName);
return useGLTF(url);
}
function ShapeModel({ shapeName }: { shapeName: string }) {
const { nodes } = useStaticShape(shapeName);
return (
<>
{Object.entries(nodes)
.filter(
([name, node]: [string, any]) =>
!node.material || !node.material.name.match(/\.\d+$/)
)
.map(([name, node]: [string, any]) => (
<mesh geometry={node.geometry} castShadow receiveShadow>
<meshStandardMaterial color="cyan" wireframe />
</mesh>
))}
</>
);
}
function ShapePlaceholder({ color }: { color: string }) {
return (
<mesh>
<boxGeometry args={[10, 10, 10]} />
<meshStandardMaterial color={color} wireframe />
</mesh>
);
}
export function Item({ object }: { object: ConsoleObject }) {
const dataBlock = getProperty(object, "dataBlock").value;
const [z, y, x] = useMemo(() => getPosition(object), [object]);
const [scaleX, scaleY, scaleZ] = useMemo(() => getScale(object), [object]);
const q = useMemo(() => getRotation(object, true), [object]);
const shapeName = dataBlockToShapeName[dataBlock];
return (
<group
quaternion={q}
position={[x - 1024, y, z - 1024]}
scale={[-scaleX, scaleY, -scaleZ]}
>
{shapeName ? (
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
<Suspense fallback={<ShapePlaceholder color="yellow" />}>
<ShapeModel shapeName={shapeName} />
</Suspense>
</ErrorBoundary>
) : (
<ShapePlaceholder color="orange" />
)}
</group>
);
}

View file

@ -0,0 +1,80 @@
import { Suspense, useMemo } from "react";
import { ErrorBoundary } from "react-error-boundary";
import {
ConsoleObject,
getPosition,
getProperty,
getRotation,
getScale,
} from "../mission";
import { shapeToUrl } from "../loaders";
import { useGLTF } from "@react-three/drei";
const dataBlockToShapeName = {
StationInventory: "station_inv_human.dts",
SensorLargePulse: "sensor_pulse_large.dts",
};
/**
* Load a .glb file that was converted from a .dts, used for static shapes.
*/
function useStaticShape(shapeName: string) {
const url = shapeToUrl(shapeName);
return useGLTF(url);
}
function ShapeModel({ shapeName }: { shapeName: string }) {
const { nodes } = useStaticShape(shapeName);
return (
<>
{Object.entries(nodes)
.filter(
([name, node]: [string, any]) =>
!node.material || !node.material.name.match(/\.\d+$/)
)
.map(([name, node]: [string, any]) => (
<mesh geometry={node.geometry} castShadow receiveShadow>
<meshStandardMaterial color="cyan" wireframe />
</mesh>
))}
</>
);
}
function ShapePlaceholder({ color }: { color: string }) {
return (
<mesh>
<boxGeometry args={[10, 10, 10]} />
<meshStandardMaterial color={color} wireframe />
</mesh>
);
}
export function StaticShape({ object }: { object: ConsoleObject }) {
const dataBlock = getProperty(object, "dataBlock").value;
const [z, y, x] = useMemo(() => getPosition(object), [object]);
const [scaleX, scaleY, scaleZ] = useMemo(() => getScale(object), [object]);
const q = useMemo(() => getRotation(object, true), [object]);
const shapeName = dataBlockToShapeName[dataBlock];
return (
<group
quaternion={q}
position={[x - 1024, y, z - 1024]}
scale={[-scaleX, scaleY, -scaleZ]}
>
{shapeName ? (
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
<Suspense fallback={<ShapePlaceholder color="yellow" />}>
<ShapeModel shapeName={shapeName} />
</Suspense>
</ErrorBoundary>
) : (
<ShapePlaceholder color="orange" />
)}
</group>
);
}

View file

@ -0,0 +1,56 @@
import { Suspense, useMemo } from "react";
import { ErrorBoundary } from "react-error-boundary";
import {
ConsoleObject,
getPosition,
getProperty,
getRotation,
getScale,
} from "../mission";
import { shapeToUrl } from "../loaders";
import { useGLTF } from "@react-three/drei";
/**
* Load a .glb file that was converted from a .dts, used for static shapes.
*/
function useStaticShape(shapeName: string) {
const url = shapeToUrl(shapeName);
return useGLTF(url);
}
function ShapeModel({ shapeName }: { shapeName: string }) {
const { scene } = useStaticShape(shapeName);
return <primitive object={scene} />;
}
function ShapePlaceholder({ color }: { color: string }) {
return (
<mesh>
<boxGeometry args={[10, 10, 10]} />
<meshStandardMaterial color={color} wireframe />
</mesh>
);
}
export function TSStatic({ object }: { object: ConsoleObject }) {
const shapeName = getProperty(object, "shapeName").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]}
>
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
<Suspense fallback={<ShapePlaceholder color="yellow" />}>
<ShapeModel shapeName={shapeName} />
</Suspense>
</ErrorBoundary>
</group>
);
}

View file

@ -5,14 +5,20 @@ import { SimGroup } from "./SimGroup";
import { InteriorInstance } from "./InteriorInstance";
import { Sky } from "./Sky";
import { Sun } from "./Sun";
import { TSStatic } from "./TSStatic";
import { StaticShape } from "./StaticShape";
import { Item } from "./Item";
const componentMap = {
SimGroup,
TerrainBlock,
WaterBlock,
InteriorInstance,
Item,
SimGroup,
Sky,
StaticShape,
Sun,
TerrainBlock,
TSStatic,
WaterBlock,
};
export function renderObject(object: ConsoleObject, key: string | number) {

View file

@ -29,6 +29,11 @@ export function interiorToUrl(name: string) {
return difUrl.replace(/\.dif$/i, ".gltf");
}
export function shapeToUrl(name: string) {
const difUrl = getUrlForPath(`shapes/${name}`);
return difUrl.replace(/\.dts$/i, ".glb");
}
export function terrainTextureToUrl(name: string) {
name = name.replace(/^terrain\./, "");
return getUrlForPath(`textures/terrain/${name}.png`, `${BASE_URL}/black.png`);