From d925585a34038f81f9d4d7c679ef785e4fb2923a Mon Sep 17 00:00:00 2001 From: Brian Beck Date: Fri, 14 Nov 2025 19:05:24 -0800 Subject: [PATCH] set up static shape components --- package-lock.json | 13 ++++++ package.json | 1 + src/components/Item.tsx | 79 ++++++++++++++++++++++++++++++++ src/components/StaticShape.tsx | 80 +++++++++++++++++++++++++++++++++ src/components/TSStatic.tsx | 56 +++++++++++++++++++++++ src/components/renderObject.tsx | 12 +++-- src/loaders.ts | 5 +++ 7 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 src/components/Item.tsx create mode 100644 src/components/StaticShape.tsx create mode 100644 src/components/TSStatic.tsx diff --git a/package-lock.json b/package-lock.json index f6e8d0bf..03a2a729 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 8afa6744..a483e240 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/components/Item.tsx b/src/components/Item.tsx new file mode 100644 index 00000000..1b26287e --- /dev/null +++ b/src/components/Item.tsx @@ -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]) => ( + + + + ))} + + ); +} + +function ShapePlaceholder({ color }: { color: string }) { + return ( + + + + + ); +} + +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 ( + + {shapeName ? ( + }> + }> + + + + ) : ( + + )} + + ); +} diff --git a/src/components/StaticShape.tsx b/src/components/StaticShape.tsx new file mode 100644 index 00000000..01895097 --- /dev/null +++ b/src/components/StaticShape.tsx @@ -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]) => ( + + + + ))} + + ); +} + +function ShapePlaceholder({ color }: { color: string }) { + return ( + + + + + ); +} + +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 ( + + {shapeName ? ( + }> + }> + + + + ) : ( + + )} + + ); +} diff --git a/src/components/TSStatic.tsx b/src/components/TSStatic.tsx new file mode 100644 index 00000000..f85b1f74 --- /dev/null +++ b/src/components/TSStatic.tsx @@ -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 ; +} + +function ShapePlaceholder({ color }: { color: string }) { + return ( + + + + + ); +} + +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 ( + + }> + }> + + + + + ); +} diff --git a/src/components/renderObject.tsx b/src/components/renderObject.tsx index 6a57e422..13de1dc1 100644 --- a/src/components/renderObject.tsx +++ b/src/components/renderObject.tsx @@ -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) { diff --git a/src/loaders.ts b/src/loaders.ts index eb299f10..8114788c 100644 --- a/src/loaders.ts +++ b/src/loaders.ts @@ -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`);