mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-01-19 20:25:01 +00:00
press 1-9 to select cameras, only show error placeholders in debug mode
This commit is contained in:
parent
825b21acfd
commit
3bb3f7afbd
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -2,7 +2,7 @@
|
|||
2:I[9766,[],""]
|
||||
3:I[8924,[],""]
|
||||
4:I[1959,[],"ClientPageRoot"]
|
||||
5:I[8519,["367","static/chunks/b536a0f1-05ee2c75df4a3b9d.js","831","static/chunks/bd904a5c-3aea2adebde6f067.js","664","static/chunks/a3cd4a83-5c5b758da206345b.js","794","static/chunks/f6211eb1-4f3105d2434536dc.js","413","static/chunks/1329d575-16915d95397758f8.js","369","static/chunks/369-34d0e374bac367b6.js","974","static/chunks/app/page-c14a97db6896095b.js"],"default"]
|
||||
5:I[8519,["367","static/chunks/b536a0f1-05ee2c75df4a3b9d.js","831","static/chunks/bd904a5c-3aea2adebde6f067.js","664","static/chunks/a3cd4a83-5c5b758da206345b.js","794","static/chunks/f6211eb1-4f3105d2434536dc.js","413","static/chunks/1329d575-16915d95397758f8.js","369","static/chunks/369-34d0e374bac367b6.js","974","static/chunks/app/page-14947d32ca3dbe94.js"],"default"]
|
||||
8:I[4431,[],"OutletBoundary"]
|
||||
a:I[5278,[],"AsyncMetadataOutlet"]
|
||||
c:I[4431,[],"ViewportBoundary"]
|
||||
|
|
@ -10,7 +10,7 @@ e:I[4431,[],"MetadataBoundary"]
|
|||
f:"$Sreact.suspense"
|
||||
11:I[7150,[],""]
|
||||
:HL["/t2-mapper/_next/static/css/71910d47103c2b82.css","style"]
|
||||
0:{"P":null,"b":"JRzZ4JcBdWRKU37DH9xVb","p":"/t2-mapper","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/css/71910d47103c2b82.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","searchParams":{},"params":{},"promises":["$@6","$@7"]}],null,["$","$L8",null,{"children":["$L9",["$","$La",null,{"promise":"$@b"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lc",null,{"children":"$Ld"}],null],["$","$Le",null,{"children":["$","div",null,{"hidden":true,"children":["$","$f",null,{"fallback":null,"children":"$L10"}]}]}]]}],false]],"m":"$undefined","G":["$11",[]],"s":false,"S":true}
|
||||
0:{"P":null,"b":"9vBBTV2JEIUmXNr6BPGfH","p":"/t2-mapper","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/css/71910d47103c2b82.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","searchParams":{},"params":{},"promises":["$@6","$@7"]}],null,["$","$L8",null,{"children":["$L9",["$","$La",null,{"promise":"$@b"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lc",null,{"children":"$Ld"}],null],["$","$Le",null,{"children":["$","div",null,{"hidden":true,"children":["$","$f",null,{"fallback":null,"children":"$L10"}]}]}]]}],false]],"m":"$undefined","G":["$11",[]],"s":false,"S":true}
|
||||
6:{}
|
||||
7:"$0:f:0:1:2:children:1:props:children:0:props:params"
|
||||
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ interface CamerasContextValue {
|
|||
registerCamera: (camera: any) => void;
|
||||
unregisterCamera: (camera: any) => void;
|
||||
nextCamera: () => void;
|
||||
setCameraIndex: (index: number) => void;
|
||||
cameraCount: number;
|
||||
}
|
||||
|
||||
const CamerasContext = createContext<CamerasContextValue | null>(null);
|
||||
|
|
@ -52,15 +54,25 @@ export function CamerasProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
}, []);
|
||||
|
||||
const cameraCount = Object.keys(cameraMap).length;
|
||||
|
||||
const nextCamera = useCallback(() => {
|
||||
setCameraIndex((prev) => {
|
||||
const cameraCount = Object.keys(cameraMap).length;
|
||||
if (cameraCount === 0) {
|
||||
return 0;
|
||||
}
|
||||
return (prev + 1) % cameraCount;
|
||||
});
|
||||
}, [cameraMap]);
|
||||
}, [cameraCount]);
|
||||
|
||||
const setCamera = useCallback(
|
||||
(index: number) => {
|
||||
if (index >= 0 && index < cameraCount) {
|
||||
setCameraIndex(index);
|
||||
}
|
||||
},
|
||||
[cameraCount]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const cameraCount = Object.keys(cameraMap).length;
|
||||
|
|
@ -82,8 +94,10 @@ export function CamerasProvider({ children }: { children: ReactNode }) {
|
|||
registerCamera,
|
||||
unregisterCamera,
|
||||
nextCamera,
|
||||
setCameraIndex: setCamera,
|
||||
cameraCount,
|
||||
}),
|
||||
[registerCamera, unregisterCamera, nextCamera]
|
||||
[registerCamera, unregisterCamera, nextCamera, setCamera, cameraCount]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -67,6 +67,11 @@ export function ShapePlaceholder({ color }: { color: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function DebugPlaceholder({ color }: { color: string }) {
|
||||
const { debugMode } = useDebug();
|
||||
return debugMode ? <ShapePlaceholder color={color} /> : null;
|
||||
}
|
||||
|
||||
export type StaticShapeType = "StaticShape" | "TSStatic" | "Item" | "Turret";
|
||||
|
||||
export const ShapeModel = memo(function ShapeModel() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { memo, Suspense, useMemo } from "react";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import { Mesh } from "three";
|
||||
import { useGLTF, useTexture } from "@react-three/drei";
|
||||
import { BASE_URL, interiorTextureToUrl, interiorToUrl } from "../loaders";
|
||||
|
|
@ -75,15 +76,20 @@ export const InteriorModel = memo(
|
|||
}
|
||||
);
|
||||
|
||||
function InteriorPlaceholder() {
|
||||
function InteriorPlaceholder({ color }: { color: string }) {
|
||||
return (
|
||||
<mesh>
|
||||
<boxGeometry args={[10, 10, 10]} />
|
||||
<meshStandardMaterial color="orange" wireframe />
|
||||
<meshStandardMaterial color={color} wireframe />
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
|
||||
function DebugInteriorPlaceholder() {
|
||||
const { debugMode } = useDebug();
|
||||
return debugMode ? <InteriorPlaceholder color="red" /> : null;
|
||||
}
|
||||
|
||||
export const InteriorInstance = memo(function InteriorInstance({
|
||||
object,
|
||||
}: {
|
||||
|
|
@ -96,9 +102,11 @@ export const InteriorInstance = memo(function InteriorInstance({
|
|||
|
||||
return (
|
||||
<group position={position} quaternion={q} scale={scale}>
|
||||
<Suspense fallback={<InteriorPlaceholder />}>
|
||||
<InteriorModel interiorFile={interiorFile} />
|
||||
</Suspense>
|
||||
<ErrorBoundary fallback={<DebugInteriorPlaceholder />}>
|
||||
<Suspense fallback={<InteriorPlaceholder color="orange" />}>
|
||||
<InteriorModel interiorFile={interiorFile} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</group>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
getRotation,
|
||||
getScale,
|
||||
} from "../mission";
|
||||
import { ShapeModel, ShapePlaceholder } from "./GenericShape";
|
||||
import { DebugPlaceholder, ShapeModel, ShapePlaceholder } from "./GenericShape";
|
||||
import { ShapeInfoProvider } from "./ShapeInfoProvider";
|
||||
import { useSimGroup } from "./SimGroup";
|
||||
import { FloatingLabel } from "./FloatingLabel";
|
||||
|
|
@ -84,7 +84,7 @@ export function Item({ object }: { object: ConsoleObject }) {
|
|||
<ShapeInfoProvider shapeName={shapeName} type="Item">
|
||||
<group position={position} quaternion={q} scale={scale}>
|
||||
{shapeName ? (
|
||||
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
|
||||
<ErrorBoundary fallback={<DebugPlaceholder color="red" />}>
|
||||
<Suspense fallback={<ShapePlaceholder color="pink" />}>
|
||||
<ShapeModel />
|
||||
{label ? (
|
||||
|
|
@ -93,7 +93,7 @@ export function Item({ object }: { object: ConsoleObject }) {
|
|||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
) : (
|
||||
<ShapePlaceholder color="orange" />
|
||||
<DebugPlaceholder color="orange" />
|
||||
)}
|
||||
</group>
|
||||
</ShapeInfoProvider>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@ enum Controls {
|
|||
right = "right",
|
||||
up = "up",
|
||||
down = "down",
|
||||
camera1 = "camera1",
|
||||
camera2 = "camera2",
|
||||
camera3 = "camera3",
|
||||
camera4 = "camera4",
|
||||
camera5 = "camera5",
|
||||
camera6 = "camera6",
|
||||
camera7 = "camera7",
|
||||
camera8 = "camera8",
|
||||
camera9 = "camera9",
|
||||
}
|
||||
|
||||
const BASE_SPEED = 80;
|
||||
|
|
@ -23,7 +32,7 @@ function CameraMovement() {
|
|||
const { speedMultiplier, setSpeedMultiplier } = useControls();
|
||||
const [subscribe, getKeys] = useKeyboardControls<Controls>();
|
||||
const { camera, gl } = useThree();
|
||||
const { nextCamera } = useCameras();
|
||||
const { nextCamera, setCameraIndex, cameraCount } = useCameras();
|
||||
const controlsRef = useRef<PointerLockControls | null>(null);
|
||||
|
||||
// Scratch vectors to avoid allocations each frame
|
||||
|
|
@ -53,6 +62,30 @@ function CameraMovement() {
|
|||
};
|
||||
}, [camera, gl, nextCamera]);
|
||||
|
||||
// Handle number keys 1-9 for camera selection
|
||||
useEffect(() => {
|
||||
const cameraControls = [
|
||||
Controls.camera1,
|
||||
Controls.camera2,
|
||||
Controls.camera3,
|
||||
Controls.camera4,
|
||||
Controls.camera5,
|
||||
Controls.camera6,
|
||||
Controls.camera7,
|
||||
Controls.camera8,
|
||||
Controls.camera9,
|
||||
];
|
||||
|
||||
return subscribe((state) => {
|
||||
for (let i = 0; i < cameraControls.length; i++) {
|
||||
if (state[cameraControls[i]] && i < cameraCount) {
|
||||
setCameraIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [subscribe, setCameraIndex, cameraCount]);
|
||||
|
||||
// Handle mousewheel for speed adjustment
|
||||
useEffect(() => {
|
||||
const handleWheel = (e: WheelEvent) => {
|
||||
|
|
@ -135,6 +168,15 @@ const KEYBOARD_CONTROLS = [
|
|||
{ name: Controls.right, keys: ["KeyD"] },
|
||||
{ name: Controls.up, keys: ["Space"] },
|
||||
{ name: Controls.down, keys: ["ShiftLeft", "ShiftRight"] },
|
||||
{ name: Controls.camera1, keys: ["Digit1"] },
|
||||
{ name: Controls.camera2, keys: ["Digit2"] },
|
||||
{ name: Controls.camera3, keys: ["Digit3"] },
|
||||
{ name: Controls.camera4, keys: ["Digit4"] },
|
||||
{ name: Controls.camera5, keys: ["Digit5"] },
|
||||
{ name: Controls.camera6, keys: ["Digit6"] },
|
||||
{ name: Controls.camera7, keys: ["Digit7"] },
|
||||
{ name: Controls.camera8, keys: ["Digit8"] },
|
||||
{ name: Controls.camera9, keys: ["Digit9"] },
|
||||
];
|
||||
|
||||
export function ObserverControls() {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
getRotation,
|
||||
getScale,
|
||||
} from "../mission";
|
||||
import { ShapeModel, ShapePlaceholder } from "./GenericShape";
|
||||
import { DebugPlaceholder, ShapeModel, ShapePlaceholder } from "./GenericShape";
|
||||
import { ShapeInfoProvider } from "./ShapeInfoProvider";
|
||||
|
||||
const dataBlockToShapeName = {
|
||||
|
|
@ -61,13 +61,13 @@ export function StaticShape({ object }: { object: ConsoleObject }) {
|
|||
<ShapeInfoProvider shapeName={shapeName} type="StaticShape">
|
||||
<group position={position} quaternion={q} scale={scale}>
|
||||
{shapeName ? (
|
||||
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
|
||||
<ErrorBoundary fallback={<DebugPlaceholder color="red" />}>
|
||||
<Suspense fallback={<ShapePlaceholder color="yellow" />}>
|
||||
<ShapeModel />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
) : (
|
||||
<ShapePlaceholder color="orange" />
|
||||
<DebugPlaceholder color="orange" />
|
||||
)}
|
||||
</group>
|
||||
</ShapeInfoProvider>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
getRotation,
|
||||
getScale,
|
||||
} from "../mission";
|
||||
import { ShapeModel, ShapePlaceholder } from "./GenericShape";
|
||||
import { DebugPlaceholder, ShapeModel, ShapePlaceholder } from "./GenericShape";
|
||||
import { ShapeInfoProvider } from "./ShapeInfoProvider";
|
||||
|
||||
export function TSStatic({ object }: { object: ConsoleObject }) {
|
||||
|
|
@ -24,7 +24,7 @@ export function TSStatic({ object }: { object: ConsoleObject }) {
|
|||
return (
|
||||
<ShapeInfoProvider shapeName={shapeName} type="TSStatic">
|
||||
<group position={position} quaternion={q} scale={scale}>
|
||||
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
|
||||
<ErrorBoundary fallback={<DebugPlaceholder color="red" />}>
|
||||
<Suspense fallback={<ShapePlaceholder color="yellow" />}>
|
||||
<ShapeModel />
|
||||
</Suspense>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
getRotation,
|
||||
getScale,
|
||||
} from "../mission";
|
||||
import { ShapeModel, ShapePlaceholder } from "./GenericShape";
|
||||
import { DebugPlaceholder, ShapeModel, ShapePlaceholder } from "./GenericShape";
|
||||
import { ShapeInfoProvider } from "./ShapeInfoProvider";
|
||||
|
||||
const dataBlockToShapeName = {
|
||||
|
|
@ -36,14 +36,16 @@ function getDataBlockShape(dataBlock: string) {
|
|||
|
||||
export function Turret({ object }: { object: ConsoleObject }) {
|
||||
const dataBlock = getProperty(object, "dataBlock").value;
|
||||
const initialBarrel = getProperty(object, "initialBarrel").value;
|
||||
const initialBarrel = getProperty(object, "initialBarrel")?.value;
|
||||
|
||||
const position = useMemo(() => getPosition(object), [object]);
|
||||
const q = useMemo(() => getRotation(object), [object]);
|
||||
const scale = useMemo(() => getScale(object), [object]);
|
||||
|
||||
const shapeName = getDataBlockShape(dataBlock);
|
||||
const barrelShapeName = getDataBlockShape(initialBarrel);
|
||||
const barrelShapeName = initialBarrel
|
||||
? getDataBlockShape(initialBarrel)
|
||||
: undefined;
|
||||
|
||||
if (!shapeName) {
|
||||
console.error(`<Turret> missing shape for dataBlock: ${dataBlock}`);
|
||||
|
|
@ -58,24 +60,24 @@ export function Turret({ object }: { object: ConsoleObject }) {
|
|||
<ShapeInfoProvider shapeName={shapeName} type="Turret">
|
||||
<group position={position} quaternion={q} scale={scale}>
|
||||
{shapeName ? (
|
||||
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
|
||||
<ErrorBoundary fallback={<DebugPlaceholder color="red" />}>
|
||||
<Suspense fallback={<ShapePlaceholder color="yellow" />}>
|
||||
<ShapeModel />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
) : (
|
||||
<ShapePlaceholder color="orange" />
|
||||
<DebugPlaceholder color="orange" />
|
||||
)}
|
||||
<ShapeInfoProvider shapeName={barrelShapeName} type="Turret">
|
||||
<group position={[0, 1.5, 0]}>
|
||||
{barrelShapeName ? (
|
||||
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
|
||||
<ErrorBoundary fallback={<DebugPlaceholder color="red" />}>
|
||||
<Suspense fallback={<ShapePlaceholder color="yellow" />}>
|
||||
<ShapeModel />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
) : (
|
||||
<ShapePlaceholder color="orange" />
|
||||
<DebugPlaceholder color="orange" />
|
||||
)}
|
||||
</group>
|
||||
</ShapeInfoProvider>
|
||||
|
|
|
|||
Loading…
Reference in a new issue