mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-03-03 12:30:35 +00:00
remove as many transforms as possible, render Z-up axes
This commit is contained in:
parent
b2404a90af
commit
60a46e708b
424 changed files with 383 additions and 256882 deletions
|
|
@ -50,7 +50,7 @@ export function AudioEmitter({ object }: { object: ConsoleObject }) {
|
|||
);
|
||||
const is3D = parseInt(getProperty(object, "is3D")?.value ?? "0");
|
||||
|
||||
const [z, y, x] = getPosition(object);
|
||||
const [x, y, z] = getPosition(object);
|
||||
const { scene, camera } = useThree();
|
||||
const { audioLoader, audioListener } = useAudio();
|
||||
const { audioEnabled } = useSettings();
|
||||
|
|
@ -60,7 +60,7 @@ export function AudioEmitter({ object }: { object: ConsoleObject }) {
|
|||
const loopGapIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const isLoadedRef = useRef(false);
|
||||
const isInRangeRef = useRef(false);
|
||||
const emitterPosRef = useRef(new Vector3(x - 1024, y, z - 1024));
|
||||
const emitterPosRef = useRef(new Vector3(x, y, z));
|
||||
|
||||
// Create sound object on mount
|
||||
useEffect(() => {
|
||||
|
|
@ -208,10 +208,9 @@ export function AudioEmitter({ object }: { object: ConsoleObject }) {
|
|||
<meshBasicMaterial
|
||||
color="#00ff00"
|
||||
wireframe
|
||||
opacity={0.2}
|
||||
opacity={0.05}
|
||||
transparent
|
||||
toneMapped={false}
|
||||
fog={false}
|
||||
/>
|
||||
<FloatingLabel color="#00ff00" position={[0, minDistance + 1, 0]}>
|
||||
{fileName}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,46 @@
|
|||
import { Stats } from "@react-three/drei";
|
||||
import { Stats, Html } from "@react-three/drei";
|
||||
import { useSettings } from "./SettingsProvider";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { AxesHelper } from "three";
|
||||
|
||||
export function DebugElements() {
|
||||
const { debugMode } = useSettings();
|
||||
const axesRef = useRef<AxesHelper>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const axes = axesRef.current;
|
||||
if (!axes) {
|
||||
return;
|
||||
}
|
||||
axes.setColors("rgb(153, 255, 0)", "rgb(0, 153, 255)", "rgb(255, 153, 0)");
|
||||
});
|
||||
|
||||
return debugMode ? (
|
||||
<>
|
||||
<Stats className="StatsPanel" />
|
||||
<axesHelper ref={axesRef} args={[70]} renderOrder={999}>
|
||||
<lineBasicMaterial
|
||||
depthTest={false}
|
||||
depthWrite={false}
|
||||
fog={false}
|
||||
vertexColors
|
||||
/>
|
||||
</axesHelper>
|
||||
<Html position={[80, 0, 0]} center>
|
||||
<span className="AxisLabel" data-axis="y">
|
||||
Y
|
||||
</span>
|
||||
</Html>
|
||||
<Html position={[0, 80, 0]} center>
|
||||
<span className="AxisLabel" data-axis="z">
|
||||
Z
|
||||
</span>
|
||||
</Html>
|
||||
<Html position={[0, 0, 80]} center>
|
||||
<span className="AxisLabel" data-axis="x">
|
||||
X
|
||||
</span>
|
||||
</Html>
|
||||
</>
|
||||
) : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export function ShapeModel() {
|
|||
}, [nodes, hullBoneIndices]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<group rotation={[0, Math.PI / 2, 0]}>
|
||||
{processedNodes.map(({ node, geometry }) => (
|
||||
<mesh key={node.id} geometry={geometry} castShadow receiveShadow>
|
||||
{node.material ? (
|
||||
|
|
@ -134,6 +134,6 @@ export function ShapeModel() {
|
|||
</mesh>
|
||||
))}
|
||||
{debugMode ? <FloatingLabel>{shapeName}</FloatingLabel> : null}
|
||||
</>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ export const InteriorModel = memo(
|
|||
const { nodes } = useInterior(interiorFile);
|
||||
|
||||
return (
|
||||
<>
|
||||
<group rotation={[0, -Math.PI / 2, 0]}>
|
||||
{Object.entries(nodes)
|
||||
.filter(
|
||||
([name, node]: [string, any]) =>
|
||||
|
|
@ -66,7 +66,7 @@ export const InteriorModel = memo(
|
|||
.map(([name, node]: [string, any]) => (
|
||||
<InteriorMesh key={name} node={node} />
|
||||
))}
|
||||
</>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
@ -83,16 +83,12 @@ function InteriorPlaceholder() {
|
|||
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]);
|
||||
const position = useMemo(() => getPosition(object), [object]);
|
||||
const scale = useMemo(() => getScale(object), [object]);
|
||||
const q = useMemo(() => getRotation(object), [object]);
|
||||
|
||||
return (
|
||||
<group
|
||||
quaternion={q}
|
||||
position={[x - 1024, y, z - 1024]}
|
||||
scale={[-scaleX, scaleY, -scaleZ]}
|
||||
>
|
||||
<group position={position} quaternion={q} scale={scale}>
|
||||
<Suspense fallback={<InteriorPlaceholder />}>
|
||||
<InteriorModel interiorFile={interiorFile} />
|
||||
</Suspense>
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@ function getDataBlockShape(dataBlock: string) {
|
|||
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 position = useMemo(() => getPosition(object), [object]);
|
||||
const scale = useMemo(() => getScale(object), [object]);
|
||||
const q = useMemo(() => getRotation(object), [object]);
|
||||
|
||||
const shapeName = getDataBlockShape(dataBlock);
|
||||
|
||||
|
|
@ -69,11 +69,7 @@ export function Item({ object }: { object: ConsoleObject }) {
|
|||
|
||||
return (
|
||||
<ShapeInfoProvider shapeName={shapeName} type="Item">
|
||||
<group
|
||||
quaternion={q}
|
||||
position={[x - 1024, y, z - 1024]}
|
||||
scale={[scaleX, scaleY, scaleZ]}
|
||||
>
|
||||
<group position={position} quaternion={q} scale={scale}>
|
||||
{shapeName ? (
|
||||
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
|
||||
<Suspense fallback={<ShapePlaceholder color="pink" />}>
|
||||
|
|
|
|||
|
|
@ -16,5 +16,5 @@ export function Mission({ name }: { name: string }) {
|
|||
return null;
|
||||
}
|
||||
|
||||
return <>{mission.objects.map((object, i) => renderObject(object, i))}</>;
|
||||
return mission.objects.map((object, i) => renderObject(object, i));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,5 @@ import { useSettings } from "./SettingsProvider";
|
|||
export function ObserverCamera() {
|
||||
const { fov } = useSettings();
|
||||
|
||||
return (
|
||||
<PerspectiveCamera makeDefault position={[-512, 256, -512]} fov={fov} />
|
||||
);
|
||||
return <PerspectiveCamera makeDefault position={[0, 256, 0]} fov={fov} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ enum Controls {
|
|||
down = "down",
|
||||
}
|
||||
|
||||
const BASE_SPEED = 80; // units per second
|
||||
const BASE_SPEED = 80;
|
||||
const MIN_SPEED_ADJUSTMENT = 0.05;
|
||||
const MAX_SPEED_ADJUSTMENT = 0.5;
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ function CameraMovement() {
|
|||
};
|
||||
}, [gl]);
|
||||
|
||||
useFrame((_, delta) => {
|
||||
useFrame((state, delta) => {
|
||||
const { forward, backward, left, right, up, down } = getKeys();
|
||||
|
||||
if (!forward && !backward && !left && !right && !up && !down) {
|
||||
|
|
|
|||
|
|
@ -47,9 +47,9 @@ function getDataBlockShape(dataBlock: string) {
|
|||
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 position = useMemo(() => getPosition(object), [object]);
|
||||
const q = useMemo(() => getRotation(object), [object]);
|
||||
const scale = useMemo(() => getScale(object), [object]);
|
||||
|
||||
const shapeName = getDataBlockShape(dataBlock);
|
||||
|
||||
|
|
@ -59,11 +59,7 @@ export function StaticShape({ object }: { object: ConsoleObject }) {
|
|||
|
||||
return (
|
||||
<ShapeInfoProvider shapeName={shapeName} type="StaticShape">
|
||||
<group
|
||||
quaternion={q}
|
||||
position={[x - 1024, y, z - 1024]}
|
||||
scale={[scaleX, scaleY, scaleZ]}
|
||||
>
|
||||
<group position={position} quaternion={q} scale={scale}>
|
||||
{shapeName ? (
|
||||
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
|
||||
<Suspense fallback={<ShapePlaceholder color="yellow" />}>
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ import { ShapeInfoProvider } from "./ShapeInfoProvider";
|
|||
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]);
|
||||
const position = useMemo(() => getPosition(object), [object]);
|
||||
const q = useMemo(() => getRotation(object), [object]);
|
||||
const scale = useMemo(() => getScale(object), [object]);
|
||||
|
||||
if (!shapeName) {
|
||||
console.error("<TSStatic> missing shapeName for object", object);
|
||||
|
|
@ -23,11 +23,7 @@ export function TSStatic({ object }: { object: ConsoleObject }) {
|
|||
|
||||
return (
|
||||
<ShapeInfoProvider shapeName={shapeName} type="TSStatic">
|
||||
<group
|
||||
quaternion={q}
|
||||
position={[x - 1024, y, z - 1024]}
|
||||
scale={[scaleX, scaleY, scaleZ]}
|
||||
>
|
||||
<group position={position} quaternion={q} scale={scale}>
|
||||
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
|
||||
<Suspense fallback={<ShapePlaceholder color="yellow" />}>
|
||||
<ShapeModel />
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import {
|
|||
ClampToEdgeWrapping,
|
||||
UnsignedByteType,
|
||||
PlaneGeometry,
|
||||
DoubleSide,
|
||||
FrontSide,
|
||||
} from "three";
|
||||
import { useTexture } from "@react-three/drei";
|
||||
import { uint16ToFloat32 } from "../arrayUtils";
|
||||
|
|
@ -25,6 +27,9 @@ import {
|
|||
setupMask,
|
||||
updateTerrainTextureShader,
|
||||
} from "../textureUtils";
|
||||
import { useSettings } from "./SettingsProvider";
|
||||
|
||||
const DEFAULT_SQUARE_SIZE = 8;
|
||||
|
||||
/**
|
||||
* Load a .ter file, used for terrain heightmap and texture info.
|
||||
|
|
@ -47,6 +52,8 @@ function BlendedTerrainTextures({
|
|||
textureNames: string[];
|
||||
alphaMaps: Uint8Array[];
|
||||
}) {
|
||||
const { debugMode } = useSettings();
|
||||
|
||||
const baseTextures = useTexture(
|
||||
textureNames.map((name) => terrainTextureToUrl(name)),
|
||||
(textures) => {
|
||||
|
|
@ -79,19 +86,22 @@ function BlendedTerrainTextures({
|
|||
alphaTextures,
|
||||
visibilityMask,
|
||||
tiling,
|
||||
debugMode,
|
||||
});
|
||||
},
|
||||
[baseTextures, alphaTextures, visibilityMask, tiling]
|
||||
[baseTextures, alphaTextures, visibilityMask, tiling, debugMode]
|
||||
);
|
||||
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
// For testing tiling values; forces recompile.
|
||||
key={JSON.stringify(tiling)}
|
||||
key={`${JSON.stringify(tiling)}-${debugMode}`}
|
||||
displacementMap={displacementMap}
|
||||
map={displacementMap}
|
||||
displacementScale={2048}
|
||||
depthWrite
|
||||
// In debug mode, render both sides so we can see wireframe from below
|
||||
side={debugMode ? DoubleSide : FrontSide}
|
||||
onBeforeCompile={onBeforeCompile}
|
||||
/>
|
||||
);
|
||||
|
|
@ -199,7 +209,9 @@ export function TerrainBlock({ object }: { object: ConsoleObject }) {
|
|||
object,
|
||||
"squareSize"
|
||||
)?.value;
|
||||
return squareSizeString ? parseInt(squareSizeString, 10) : 8;
|
||||
return squareSizeString
|
||||
? parseInt(squareSizeString, 10)
|
||||
: DEFAULT_SQUARE_SIZE;
|
||||
}, [object]);
|
||||
|
||||
const emptySquares: number[] = useMemo(() => {
|
||||
|
|
@ -213,37 +225,44 @@ export function TerrainBlock({ object }: { object: ConsoleObject }) {
|
|||
: [];
|
||||
}, [object]);
|
||||
|
||||
const position = useMemo(() => getPosition(object), [object]);
|
||||
const scale = useMemo(() => getScale(object), [object]);
|
||||
const position = useMemo(() => {
|
||||
// Terrain position.z is ignored in Torque - heightmap values are absolute
|
||||
const [x, y, z] = getPosition(object);
|
||||
return [x, 0, z] as [number, number, number];
|
||||
}, [object]);
|
||||
const q = useMemo(() => getRotation(object), [object]);
|
||||
const scale = useMemo(() => getScale(object), [object]);
|
||||
|
||||
const planeGeometry = useMemo(() => {
|
||||
const size = squareSize * 256;
|
||||
const geometry = new PlaneGeometry(size, size, 256, 256);
|
||||
// PlaneGeometry starts in XY plane. Rotate to XZ plane for Y-up world.
|
||||
geometry.rotateX(-Math.PI / 2);
|
||||
// Also need to rotate to swap X and Z.
|
||||
geometry.rotateY(-Math.PI / 2);
|
||||
// Shift origin from center to corner so position offset works correctly.
|
||||
// Tribes 2 terrain origin is at the corner, Three.js PlaneGeometry is centered.
|
||||
// But, T2 does this before the `squareSize` scales it up or down, so it's
|
||||
// essentially a fixed offset.
|
||||
const defaultSize = DEFAULT_SQUARE_SIZE * 256;
|
||||
geometry.translate(defaultSize / 2, 0, defaultSize / 2);
|
||||
return geometry;
|
||||
}, [squareSize]);
|
||||
|
||||
const { data: terrain } = useTerrain(terrainFile);
|
||||
|
||||
return (
|
||||
<mesh
|
||||
quaternion={q}
|
||||
position={[position[0], 0, position[2]]} // Y up is unused for terrain
|
||||
scale={scale}
|
||||
geometry={planeGeometry}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
{terrain ? (
|
||||
<TerrainMaterial
|
||||
heightMap={terrain.heightMap}
|
||||
emptySquares={emptySquares}
|
||||
textureNames={terrain.textureNames}
|
||||
alphaMaps={terrain.alphaMaps}
|
||||
/>
|
||||
) : null}
|
||||
</mesh>
|
||||
<group position={position} quaternion={q} scale={scale}>
|
||||
<mesh geometry={planeGeometry} receiveShadow castShadow>
|
||||
{terrain ? (
|
||||
<TerrainMaterial
|
||||
heightMap={terrain.heightMap}
|
||||
emptySquares={emptySquares}
|
||||
textureNames={terrain.textureNames}
|
||||
alphaMaps={terrain.alphaMaps}
|
||||
/>
|
||||
) : null}
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ export function Turret({ object }: { object: ConsoleObject }) {
|
|||
const dataBlock = getProperty(object, "dataBlock").value;
|
||||
const initialBarrel = getProperty(object, "initialBarrel").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 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);
|
||||
|
|
@ -56,11 +56,7 @@ export function Turret({ object }: { object: ConsoleObject }) {
|
|||
|
||||
return (
|
||||
<ShapeInfoProvider shapeName={shapeName} type="Turret">
|
||||
<group
|
||||
quaternion={q}
|
||||
position={[x - 1024, y, z - 1024]}
|
||||
scale={[-scaleX, scaleY, scaleZ]}
|
||||
>
|
||||
<group position={position} quaternion={q} scale={scale}>
|
||||
{shapeName ? (
|
||||
<ErrorBoundary fallback={<ShapePlaceholder color="red" />}>
|
||||
<Suspense fallback={<ShapePlaceholder color="yellow" />}>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Suspense, useMemo } from "react";
|
||||
import { Suspense, useEffect, useMemo } from "react";
|
||||
import { useTexture } from "@react-three/drei";
|
||||
import { BoxGeometry, DoubleSide } from "three";
|
||||
import { textureToUrl } from "../loaders";
|
||||
import {
|
||||
ConsoleObject,
|
||||
|
|
@ -10,34 +11,92 @@ import {
|
|||
} from "../mission";
|
||||
import { setupColor } from "../textureUtils";
|
||||
|
||||
export function WaterMaterial({ surfaceTexture }: { surfaceTexture: string }) {
|
||||
export function WaterMaterial({
|
||||
surfaceTexture,
|
||||
attach,
|
||||
}: {
|
||||
surfaceTexture: string;
|
||||
attach?: string;
|
||||
}) {
|
||||
const url = textureToUrl(surfaceTexture);
|
||||
const texture = useTexture(url, (texture) => setupColor(texture, [8, 8]));
|
||||
const texture = useTexture(url, (texture) => setupColor(texture));
|
||||
|
||||
return <meshStandardMaterial map={texture} transparent opacity={0.8} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
attach={attach}
|
||||
map={texture}
|
||||
transparent
|
||||
opacity={0.8}
|
||||
side={DoubleSide}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function WaterBlock({ object }: { object: ConsoleObject }) {
|
||||
const [z, y, x] = useMemo(() => getPosition(object), [object]);
|
||||
const [scaleZ, scaleY, scaleX] = useMemo(() => getScale(object), [object]);
|
||||
const q = useMemo(() => getRotation(object, true), [object]);
|
||||
const position = useMemo(() => getPosition(object), [object]);
|
||||
const q = useMemo(() => getRotation(object), [object]);
|
||||
const [scaleX, scaleY, scaleZ] = useMemo(() => getScale(object), [object]);
|
||||
|
||||
const surfaceTexture =
|
||||
getProperty(object, "surfaceTexture")?.value ?? "liquidTiles/BlueWater";
|
||||
|
||||
const geometry = useMemo(() => {
|
||||
const geom = new BoxGeometry(scaleX, scaleY, scaleZ);
|
||||
|
||||
geom.translate(scaleX / 2, scaleY / 2, scaleZ / 2);
|
||||
|
||||
const uvAttr = geom.getAttribute("uv");
|
||||
const uv = uvAttr.array as Float32Array;
|
||||
const faceRepeats: [number, number][] = [
|
||||
// +x, -x (depth x height)
|
||||
[scaleX / 32, scaleY / 32],
|
||||
[scaleX / 32, scaleY / 32],
|
||||
// +y, -y (width x depth)
|
||||
[scaleZ / 32, scaleX / 32],
|
||||
[scaleZ / 32, scaleX / 32],
|
||||
// +z, -z (width x height)
|
||||
[scaleZ / 32, scaleY / 32],
|
||||
[scaleZ / 32, scaleY / 32],
|
||||
];
|
||||
|
||||
for (let face = 0; face < 6; face++) {
|
||||
const [uRepeat, vRepeat] = faceRepeats[face];
|
||||
const offset = face * 4 * 2; // 4 verts per face, 2 components per vert
|
||||
for (let i = 0; i < 4; i++) {
|
||||
uv[offset + i * 2] *= uRepeat;
|
||||
uv[offset + i * 2 + 1] *= vRepeat;
|
||||
}
|
||||
}
|
||||
uvAttr.needsUpdate = true;
|
||||
return geom;
|
||||
}, [scaleX, scaleY, scaleZ]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
geometry.dispose();
|
||||
};
|
||||
}, [geometry]);
|
||||
|
||||
return (
|
||||
<mesh
|
||||
position={[x - 1024 + scaleX / 2, y + scaleY / 2, z - 1024 + scaleZ / 2]}
|
||||
quaternion={q}
|
||||
>
|
||||
<boxGeometry args={[scaleZ, scaleY, scaleX]} />
|
||||
<mesh position={position} quaternion={q} geometry={geometry}>
|
||||
<meshStandardMaterial attach="material-0" transparent opacity={0} />
|
||||
<meshStandardMaterial attach="material-1" transparent opacity={0} />
|
||||
<Suspense
|
||||
fallback={
|
||||
<meshStandardMaterial color="blue" transparent opacity={0.3} />
|
||||
<meshStandardMaterial
|
||||
attach="material-2"
|
||||
color="blue"
|
||||
transparent
|
||||
opacity={0.3}
|
||||
side={DoubleSide}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<WaterMaterial surfaceTexture={surfaceTexture} />
|
||||
<WaterMaterial attach="material-2" surfaceTexture={surfaceTexture} />
|
||||
</Suspense>
|
||||
<meshStandardMaterial attach="material-3" transparent opacity={0} />
|
||||
<meshStandardMaterial attach="material-4" transparent opacity={0} />
|
||||
<meshStandardMaterial attach="material-5" transparent opacity={0} />
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export function getUrlForPath(resourcePath: string, fallbackUrl?: string) {
|
|||
|
||||
export function interiorToUrl(name: string) {
|
||||
const difUrl = getUrlForPath(`interiors/${name}`);
|
||||
return difUrl.replace(/\.dif$/i, ".gltf");
|
||||
return difUrl.replace(/\.dif$/i, ".glb");
|
||||
}
|
||||
|
||||
export function shapeToUrl(name: string) {
|
||||
|
|
|
|||
|
|
@ -212,38 +212,25 @@ export function getProperty(obj: ConsoleObject, name: string) {
|
|||
|
||||
export function getPosition(obj: ConsoleObject): [number, number, number] {
|
||||
const position = getProperty(obj, "position")?.value ?? "0 0 0";
|
||||
const [x, z, y] = position.split(" ").map((s) => parseFloat(s));
|
||||
return [x || 0, y || 0, z || 0];
|
||||
const [x, y, z] = position.split(" ").map((s) => parseFloat(s));
|
||||
// Convert Torque3D coordinates to Three.js: XYZ -> YZX
|
||||
return [y || 0, z || 0, x || 0];
|
||||
}
|
||||
|
||||
export function getScale(obj: ConsoleObject): [number, number, number] {
|
||||
const scale = getProperty(obj, "scale")?.value ?? "1 1 1";
|
||||
const [scaleX, scaleZ, scaleY] = scale.split(" ").map((s) => parseFloat(s));
|
||||
return [scaleX, scaleY, scaleZ];
|
||||
const [sx, sy, sz] = scale.split(" ").map((s) => parseFloat(s));
|
||||
// Convert Torque3D coordinates to Three.js: XYZ -> YZX
|
||||
return [sy || 0, sz || 0, sx || 0];
|
||||
}
|
||||
|
||||
export function getRotation(obj: ConsoleObject, isInterior = false) {
|
||||
export function getRotation(obj: ConsoleObject): Quaternion {
|
||||
const rotation = getProperty(obj, "rotation")?.value ?? "1 0 0 0";
|
||||
const [ax, az, ay, angle] = rotation.split(" ").map((s) => parseFloat(s));
|
||||
|
||||
if (isInterior) {
|
||||
// For interiors: Apply coordinate system transformation
|
||||
// 1. Convert rotation axis from source coords (ax, az, ay) to Three.js coords
|
||||
// 2. Apply -90 Y rotation to align coordinate systems
|
||||
const sourceRotation = new Quaternion().setFromAxisAngle(
|
||||
new Vector3(az, ay, ax),
|
||||
-angle * (Math.PI / 180)
|
||||
);
|
||||
const coordSystemFix = new Quaternion().setFromAxisAngle(
|
||||
new Vector3(0, 1, 0),
|
||||
Math.PI / 2
|
||||
);
|
||||
return sourceRotation.multiply(coordSystemFix);
|
||||
} else {
|
||||
// For other objects (terrain, etc)
|
||||
return new Quaternion().setFromAxisAngle(
|
||||
new Vector3(ax, ay, -az),
|
||||
angle * (Math.PI / 180)
|
||||
);
|
||||
}
|
||||
const [ax, ay, az, angleDegrees] = rotation
|
||||
.split(" ")
|
||||
.map((s) => parseFloat(s));
|
||||
// Convert Torque3D coordinates to Three.js: XYZ -> YZX
|
||||
const axis = new Vector3(ay, az, ax).normalize();
|
||||
const angleRadians = -angleDegrees * (Math.PI / 180);
|
||||
return new Quaternion().setFromAxisAngle(axis, angleRadians);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
const SIZE = 256;
|
||||
const SCALE = 8;
|
||||
|
||||
export function parseTerrainBuffer(arrayBuffer: ArrayBufferLike) {
|
||||
const dataView = new DataView(arrayBuffer);
|
||||
|
|
|
|||
|
|
@ -53,6 +53,14 @@ export function updateTerrainTextureShader({
|
|||
alphaTextures,
|
||||
visibilityMask,
|
||||
tiling,
|
||||
debugMode = false,
|
||||
}: {
|
||||
shader: any;
|
||||
baseTextures: any[];
|
||||
alphaTextures: any[];
|
||||
visibilityMask: any;
|
||||
tiling: Record<number, number>;
|
||||
debugMode?: boolean;
|
||||
}) {
|
||||
const layerCount = baseTextures.length;
|
||||
|
||||
|
|
@ -78,6 +86,9 @@ export function updateTerrainTextureShader({
|
|||
};
|
||||
});
|
||||
|
||||
// Add debug mode uniform
|
||||
shader.uniforms.debugMode = { value: debugMode ? 1.0 : 0.0 };
|
||||
|
||||
// Declare our uniforms at the top of the fragment shader
|
||||
shader.fragmentShader =
|
||||
`
|
||||
|
|
@ -98,7 +109,17 @@ uniform float tiling2;
|
|||
uniform float tiling3;
|
||||
uniform float tiling4;
|
||||
uniform float tiling5;
|
||||
uniform float debugMode;
|
||||
${visibilityMask ? "uniform sampler2D visibilityMask;" : ""}
|
||||
|
||||
// Wireframe edge detection for debug mode
|
||||
float getWireframe(vec2 uv, float gridSize, float lineWidth) {
|
||||
vec2 gridUv = uv * gridSize;
|
||||
vec2 grid = abs(fract(gridUv - 0.5) - 0.5);
|
||||
vec2 deriv = fwidth(gridUv);
|
||||
vec2 edge = smoothstep(vec2(0.0), deriv * lineWidth, grid);
|
||||
return 1.0 - min(edge.x, edge.y);
|
||||
}
|
||||
` + shader.fragmentShader;
|
||||
|
||||
if (visibilityMask) {
|
||||
|
|
@ -164,7 +185,27 @@ ${visibilityMask ? "uniform sampler2D visibilityMask;" : ""}
|
|||
${layerCount > 5 ? `blended = mix(blended, c5, clamp(a5, 0.0, 1.0));` : ""}
|
||||
|
||||
// Assign to diffuseColor before lighting
|
||||
diffuseColor.rgb = ${layerCount > 1 ? "blended" : "c0"};
|
||||
vec3 textureColor = ${layerCount > 1 ? "blended" : "c0"};
|
||||
|
||||
// Debug mode wireframe handling
|
||||
if (debugMode > 0.5) {
|
||||
// 256 grid cells across the terrain (matches terrain resolution)
|
||||
float wireframe = getWireframe(baseUv, 256.0, 1.0);
|
||||
vec3 wireColor = vec3(0.0, 0.8, 0.4); // Green wireframe
|
||||
|
||||
if (gl_FrontFacing) {
|
||||
// Front face: show textures with barely visible wireframe overlay
|
||||
diffuseColor.rgb = mix(textureColor, wireColor, wireframe * 0.05);
|
||||
} else {
|
||||
// Back face: show only wireframe, discard non-wireframe pixels
|
||||
if (wireframe < 0.1) {
|
||||
discard;
|
||||
}
|
||||
diffuseColor.rgb = mix(vec3(0.0), wireColor, 0.25);
|
||||
}
|
||||
} else {
|
||||
diffuseColor.rgb = textureColor;
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue