fix texture lookup - try .jpg and .bmp too

This commit is contained in:
Brian Beck 2025-12-03 05:57:16 -08:00
parent 5d0a8a3fab
commit 5f48c1c2d2
12 changed files with 95 additions and 46 deletions

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

View file

@ -2,7 +2,7 @@
2:I[9766,[],""]
3:I[8924,[],""]
4:I[1959,[],"ClientPageRoot"]
5:I[8283,["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","331","static/chunks/331-37e8e553d8a20c21.js","974","static/chunks/app/page-f60b39f483c86849.js"],"default"]
5:I[8283,["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","331","static/chunks/331-37e8e553d8a20c21.js","974","static/chunks/app/page-562a5e5ed29fb045.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/e7a9161e212d890f.css","style"]
0:{"P":null,"b":"1kiuXQr5AbGwVAE5IklXM","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/e7a9161e212d890f.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":"JA8GPtPjSEu8NmS5GBeMX","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/e7a9161e212d890f.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"}]]

View file

@ -1,10 +1,6 @@
import { memo, Suspense, useMemo, useRef, useEffect } from "react";
import { useGLTF, useTexture } from "@react-three/drei";
import {
FALLBACK_TEXTURE_URL,
shapeTextureToUrl,
shapeToUrl,
} from "../loaders";
import { FALLBACK_TEXTURE_URL, textureToUrl, shapeToUrl } from "../loaders";
import { filterGeometryByVertexGroups, getHullBoneIndices } from "../meshUtils";
import {
createAlphaAsRoughnessMaterial,
@ -73,7 +69,7 @@ function StaticTexture({ material, shapeName }) {
}
return resourcePath
? // Use custom `resource_path` added by forked io_dts3d Blender add-on
shapeTextureToUrl(resourcePath)
textureToUrl(resourcePath)
: FALLBACK_TEXTURE_URL;
}, [material, resourcePath, shapeName]);

View file

@ -2,7 +2,7 @@ import { memo, Suspense, useMemo } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { Mesh } from "three";
import { useGLTF, useTexture } from "@react-three/drei";
import { interiorTextureToUrl, interiorToUrl } from "../loaders";
import { textureToUrl, interiorToUrl } from "../loaders";
import type { TorqueObject } from "../torqueScript";
import { getPosition, getProperty, getRotation, getScale } from "../mission";
import { setupColor } from "../textureUtils";
@ -18,7 +18,7 @@ function useInterior(interiorFile: string) {
}
function InteriorTexture({ materialName }: { materialName: string }) {
const url = interiorTextureToUrl(materialName);
const url = textureToUrl(materialName);
const texture = useTexture(url, (texture) => setupColor(texture));
return <meshStandardMaterial map={texture} side={2} />;

View file

@ -5,10 +5,10 @@ import { Color, ShaderMaterial, BackSide, Euler } from "three";
import type { TorqueObject } from "../torqueScript";
import { getFloat, getProperty } from "../mission";
import { useSettings } from "./SettingsProvider";
import { BASE_URL, getUrlForPath, loadDetailMapList } from "../loaders";
import { BASE_URL, loadDetailMapList, textureToUrl } from "../loaders";
import { useThree } from "@react-three/fiber";
const FALLBACK_URL = `${BASE_URL}/black.png`;
const FALLBACK_TEXTURE_URL = `${BASE_URL}/black.png`;
/**
* Load a .dml file, used to list the textures for different faces of a skybox.
@ -35,20 +35,20 @@ export function SkyBox({
() =>
detailMapList
? [
getUrlForPath(detailMapList[1], FALLBACK_URL), // +x
getUrlForPath(detailMapList[3], FALLBACK_URL), // -x
getUrlForPath(detailMapList[4], FALLBACK_URL), // +y
getUrlForPath(detailMapList[5], FALLBACK_URL), // -y
getUrlForPath(detailMapList[0], FALLBACK_URL), // +z
getUrlForPath(detailMapList[2], FALLBACK_URL), // -z
textureToUrl(detailMapList[1]), // +x
textureToUrl(detailMapList[3]), // -x
textureToUrl(detailMapList[4]), // +y
textureToUrl(detailMapList[5]), // -y
textureToUrl(detailMapList[0]), // +z
textureToUrl(detailMapList[2]), // -z
]
: [
FALLBACK_URL,
FALLBACK_URL,
FALLBACK_URL,
FALLBACK_URL,
FALLBACK_URL,
FALLBACK_URL,
FALLBACK_TEXTURE_URL,
FALLBACK_TEXTURE_URL,
FALLBACK_TEXTURE_URL,
FALLBACK_TEXTURE_URL,
FALLBACK_TEXTURE_URL,
FALLBACK_TEXTURE_URL,
],
[detailMapList],
);
@ -146,6 +146,22 @@ export function Sky({ object }: { object: TorqueObject }) {
// Skybox textures.
const materialList = getProperty(object, "materialList");
const skySolidColor = useMemo(() => {
const colorString = getProperty(object, "SkySolidColor");
if (colorString) {
// `colorString` might specify an alpha value, but three.js doesn't
// support opacity on fog or scene backgrounds, so ignore it.
// Note: This is a space-separated string, so we split and parse each component.
const [r, g, b] = colorString
.split(" ")
.map((s: string) => parseFloat(s));
return [
new Color().setRGB(r, g, b),
new Color().setRGB(r, g, b).convertSRGBToLinear(),
];
}
}, [object]);
// Fog parameters.
// TODO: There can be multiple fog volumes/layers. Render simple fog for now.
const fogDistance = getFloat(object, "fogDistance");
@ -166,8 +182,10 @@ export function Sky({ object }: { object: TorqueObject }) {
}
}, [object]);
const backgroundColor = fogColor ? (
<color attach="background" args={[fogColor[0]]} />
const skyColor = skySolidColor || fogColor;
const backgroundColor = skyColor ? (
<color attach="background" args={[skyColor[0]]} />
) : null;
return (

View file

@ -3,9 +3,9 @@ import {
getActualResourceKey,
getMissionInfo,
getSourceAndPath,
getStandardTextureResourceKey,
} from "./manifest";
import { parseMissionScript } from "./mission";
import { normalizePath } from "./stringUtils";
import { parseTerrainBuffer } from "./terrain";
export const BASE_URL = "/t2-mapper";
@ -46,25 +46,20 @@ export function shapeToUrl(name: string) {
export function terrainTextureToUrl(name: string) {
name = name.replace(/^terrain\./, "");
return getUrlForPath(`textures/terrain/${name}.png`, FALLBACK_TEXTURE_URL);
}
export function interiorTextureToUrl(name: string) {
// name = name.replace(/\.\d+$/, "");
return getUrlForPath(`textures/${name}.png`, FALLBACK_TEXTURE_URL);
const resourceKey = getStandardTextureResourceKey(`textures/terrain/${name}`);
return getUrlForPath(resourceKey, FALLBACK_TEXTURE_URL);
}
export function textureFrameToUrl(fileName: string) {
return getUrlForPath(`textures/skins/${fileName}`, FALLBACK_TEXTURE_URL);
}
export function shapeTextureToUrl(name: string) {
// name = name.replace(/\.\d+$/, "");
return getUrlForPath(`textures/${name}.png`, FALLBACK_TEXTURE_URL);
const resourceKey = getStandardTextureResourceKey(
`textures/skins/${fileName}`,
);
return getUrlForPath(resourceKey, FALLBACK_TEXTURE_URL);
}
export function textureToUrl(name: string) {
return getUrlForPath(`textures/${name}.png`, FALLBACK_TEXTURE_URL);
const resourceKey = getStandardTextureResourceKey(`textures/${name}`);
return getUrlForPath(resourceKey, FALLBACK_TEXTURE_URL);
}
export function audioToUrl(fileName: string) {
@ -76,8 +71,14 @@ export async function loadDetailMapList(name: string) {
const res = await fetch(url);
const text = await res.text();
return text
.split(/(?:\r\n|\n|\r)/)
.map((line) => `textures/${line.trim().replace(/\.png$/i, "")}.png`);
.split(/(?:\r\n|\r|\n)/)
.map((line) => {
line = line.trim();
if (!line.startsWith(";")) {
return line;
}
})
.filter(Boolean);
}
export async function loadMission(name: string) {

View file

@ -88,6 +88,40 @@ export function getResourceList(): string[] {
return Object.keys(manifest.resources);
}
/**
* Standard texture file extension loading order:
*
* 1. "" (no extension - exact filename match)
* 2. .jpg
* 3. .png
* 4. .gif
* 5. .bmp
*/
const standardTextureExt = ["", ".jpg", ".png", ".gif", ".bmp"];
export function getStandardTextureResourceKey(resourcePath: string) {
const baseResourceKey = getResourceKey(resourcePath);
for (const ext of standardTextureExt) {
const resourceKey = `${baseResourceKey}${ext}`;
if (manifest.resources[resourceKey]) {
return resourceKey;
}
}
return baseResourceKey;
}
/**
* Paletted texture file extension loading order:
*
* 1. "" (no extension - exact filename match)
* 2. .bm8
* 3. .bmp
* 4. .jpg
* 5. .png
* 6. .gif
*/
// Not used for now!
const palettedTextureExt = ["", ".bm8", ".bmp", ".jpg", ".png", ".gif"];
export function getLocalFilePath(resourcePath: string): string {
const resourceKey = getResourceKey(resourcePath);
const [sourcePath, actualPath] = getSourceAndPath(resourceKey);