mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-01-19 12:14:47 +00:00
terrain support for detailTexture
This commit is contained in:
parent
0bcb2ff9f4
commit
035812724d
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[39756,["/t2-mapper/_next/static/chunks/060f9a97930f3d04.js"],"default"]
|
||||
3:I[37457,["/t2-mapper/_next/static/chunks/060f9a97930f3d04.js"],"default"]
|
||||
4:I[47257,["/t2-mapper/_next/static/chunks/060f9a97930f3d04.js"],"ClientPageRoot"]
|
||||
5:I[31713,["/t2-mapper/_next/static/chunks/98d29aec52be59c3.js","/t2-mapper/_next/static/chunks/32ef0c8650712240.js","/t2-mapper/_next/static/chunks/b70f08013a69708a.js","/t2-mapper/_next/static/chunks/aaf7d6869584978f.js"],"default"]
|
||||
5:I[31713,["/t2-mapper/_next/static/chunks/98d29aec52be59c3.js","/t2-mapper/_next/static/chunks/32ef0c8650712240.js","/t2-mapper/_next/static/chunks/b70f08013a69708a.js","/t2-mapper/_next/static/chunks/186f602c78093cf5.js"],"default"]
|
||||
8:I[97367,["/t2-mapper/_next/static/chunks/060f9a97930f3d04.js"],"OutletBoundary"]
|
||||
a:I[11533,["/t2-mapper/_next/static/chunks/060f9a97930f3d04.js"],"AsyncMetadataOutlet"]
|
||||
c:I[97367,["/t2-mapper/_next/static/chunks/060f9a97930f3d04.js"],"ViewportBoundary"]
|
||||
|
|
@ -10,7 +10,7 @@ e:I[97367,["/t2-mapper/_next/static/chunks/060f9a97930f3d04.js"],"MetadataBounda
|
|||
f:"$Sreact.suspense"
|
||||
11:I[68027,[],"default"]
|
||||
:HL["/t2-mapper/_next/static/chunks/15b04c9d2ba2c4cf.css","style"]
|
||||
0:{"P":null,"b":"70hpG-x4f3KKfn_e3s67S","p":"/t2-mapper","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/15b04c9d2ba2c4cf.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"]}],[["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/98d29aec52be59c3.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/32ef0c8650712240.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/b70f08013a69708a.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/aaf7d6869584978f.js","async":true,"nonce":"$undefined"}]],["$","$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",[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/15b04c9d2ba2c4cf.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"s":false,"S":true}
|
||||
0:{"P":null,"b":"t8mk0uRHwYhFYlZ_2J51e","p":"/t2-mapper","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/15b04c9d2ba2c4cf.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"]}],[["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/98d29aec52be59c3.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/32ef0c8650712240.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/b70f08013a69708a.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/186f602c78093cf5.js","async":true,"nonce":"$undefined"}]],["$","$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",[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/15b04c9d2ba2c4cf.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"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"}]]
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ export const TerrainBlock = memo(function TerrainBlock({
|
|||
}) {
|
||||
const terrainFile = getProperty(object, "terrainFile");
|
||||
const squareSize = getInt(object, "squareSize") ?? DEFAULT_SQUARE_SIZE;
|
||||
const detailTexture = getProperty(object, "detailTexture");
|
||||
const blockSize = squareSize * 256;
|
||||
const visibleDistance = useVisibleDistance();
|
||||
const camera = useThree((state) => state.camera);
|
||||
|
|
@ -234,6 +235,7 @@ export const TerrainBlock = memo(function TerrainBlock({
|
|||
displacementMap={sharedDisplacementMap}
|
||||
visibilityMask={primaryVisibilityMask}
|
||||
alphaTextures={sharedAlphaTextures}
|
||||
detailTextureName={detailTexture}
|
||||
/>
|
||||
{/* Pooled tiles - stable keys, always mounted */}
|
||||
{poolIndices.map((poolIndex) => {
|
||||
|
|
@ -250,6 +252,7 @@ export const TerrainBlock = memo(function TerrainBlock({
|
|||
displacementMap={sharedDisplacementMap}
|
||||
visibilityMask={pooledVisibilityMask}
|
||||
alphaTextures={sharedAlphaTextures}
|
||||
detailTextureName={detailTexture}
|
||||
visible={assignment !== null}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { memo, Suspense, useCallback, useMemo } from "react";
|
||||
import { DataTexture, DoubleSide, FrontSide, type PlaneGeometry } from "three";
|
||||
import { useTexture } from "@react-three/drei";
|
||||
import { terrainTextureToUrl } from "../loaders";
|
||||
import {
|
||||
FALLBACK_TEXTURE_URL,
|
||||
terrainTextureToUrl,
|
||||
textureToUrl,
|
||||
} from "../loaders";
|
||||
import { setupColor } from "../textureUtils";
|
||||
import { updateTerrainTextureShader } from "../terrainMaterial";
|
||||
import { useDebug } from "./SettingsProvider";
|
||||
|
|
@ -28,6 +32,7 @@ interface TerrainTileProps {
|
|||
displacementMap: DataTexture;
|
||||
visibilityMask: DataTexture;
|
||||
alphaTextures: DataTexture[];
|
||||
detailTextureName?: string;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -36,11 +41,13 @@ function BlendedTerrainTextures({
|
|||
visibilityMask,
|
||||
textureNames,
|
||||
alphaTextures,
|
||||
detailTextureName,
|
||||
}: {
|
||||
displacementMap: DataTexture;
|
||||
visibilityMask: DataTexture;
|
||||
textureNames: string[];
|
||||
alphaTextures: DataTexture[];
|
||||
detailTextureName?: string;
|
||||
}) {
|
||||
const { debugMode } = useDebug();
|
||||
|
||||
|
|
@ -51,6 +58,18 @@ function BlendedTerrainTextures({
|
|||
},
|
||||
);
|
||||
|
||||
// Load detail texture if specified
|
||||
const detailTextureUrl = detailTextureName
|
||||
? textureToUrl(detailTextureName)
|
||||
: null;
|
||||
|
||||
const detailTexture = useTexture(
|
||||
detailTextureUrl ?? FALLBACK_TEXTURE_URL,
|
||||
(tex) => {
|
||||
setupColor(tex);
|
||||
},
|
||||
);
|
||||
|
||||
const onBeforeCompile = useCallback(
|
||||
(shader) => {
|
||||
updateTerrainTextureShader({
|
||||
|
|
@ -60,14 +79,27 @@ function BlendedTerrainTextures({
|
|||
visibilityMask,
|
||||
tiling: TILING,
|
||||
debugMode,
|
||||
detailTexture: detailTextureUrl ? detailTexture : null,
|
||||
});
|
||||
},
|
||||
[baseTextures, alphaTextures, visibilityMask, debugMode],
|
||||
[
|
||||
baseTextures,
|
||||
alphaTextures,
|
||||
visibilityMask,
|
||||
debugMode,
|
||||
detailTexture,
|
||||
detailTextureUrl,
|
||||
],
|
||||
);
|
||||
|
||||
// Key must include factors that change shader code structure (not just uniforms)
|
||||
// - debugMode: affects fragment shader branching
|
||||
// - detailTextureUrl: affects vertex shader (adds varying) and fragment shader
|
||||
const materialKey = `${debugMode ? "debug" : "normal"}-${detailTextureUrl ? "detail" : "nodetail"}`;
|
||||
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
key={debugMode ? "debug" : "normal"}
|
||||
key={materialKey}
|
||||
displacementMap={displacementMap}
|
||||
map={displacementMap}
|
||||
displacementScale={2048}
|
||||
|
|
@ -83,11 +115,13 @@ function TerrainMaterial({
|
|||
visibilityMask,
|
||||
textureNames,
|
||||
alphaTextures,
|
||||
detailTextureName,
|
||||
}: {
|
||||
displacementMap: DataTexture;
|
||||
visibilityMask: DataTexture;
|
||||
textureNames: string[];
|
||||
alphaTextures: DataTexture[];
|
||||
detailTextureName?: string;
|
||||
}) {
|
||||
return (
|
||||
<Suspense
|
||||
|
|
@ -105,6 +139,7 @@ function TerrainMaterial({
|
|||
visibilityMask={visibilityMask}
|
||||
textureNames={textureNames}
|
||||
alphaTextures={alphaTextures}
|
||||
detailTextureName={detailTextureName}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
|
|
@ -120,6 +155,7 @@ export const TerrainTile = memo(function TerrainTile({
|
|||
displacementMap,
|
||||
visibilityMask,
|
||||
alphaTextures,
|
||||
detailTextureName,
|
||||
visible = true,
|
||||
}: TerrainTileProps) {
|
||||
const position = useMemo(() => {
|
||||
|
|
@ -146,6 +182,7 @@ export const TerrainTile = memo(function TerrainTile({
|
|||
visibilityMask={visibilityMask}
|
||||
textureNames={textureNames}
|
||||
alphaTextures={alphaTextures}
|
||||
detailTextureName={detailTextureName}
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@
|
|||
* Handles multi-layer texture blending for Tribes 2 terrain rendering.
|
||||
*/
|
||||
|
||||
// Detail texture tiling factor.
|
||||
// Torque uses world-space generation: U = worldX * (62.0 / textureWidth)
|
||||
// For 256px texture across 2048 world units, this gives ~496 repeats mathematically.
|
||||
// However, this appears visually excessive. Using a moderate multiplier relative
|
||||
// to base texture tiling (32x) - detail should be finer but not overwhelming.
|
||||
const DETAIL_TILING = 64.0;
|
||||
|
||||
// Distance at which detail texture fully fades out (in world units)
|
||||
// Torque: zeroDetailDistance = (squareSize * worldToScreenScale) / 64 - squareSize/2
|
||||
// For squareSize=8 and typical worldToScreenScale (~800), this gives ~96 units.
|
||||
// Using 150 for a slightly more gradual fade.
|
||||
const DETAIL_FADE_DISTANCE = 150.0;
|
||||
|
||||
export function updateTerrainTextureShader({
|
||||
shader,
|
||||
baseTextures,
|
||||
|
|
@ -10,6 +23,7 @@ export function updateTerrainTextureShader({
|
|||
visibilityMask,
|
||||
tiling,
|
||||
debugMode = false,
|
||||
detailTexture = null,
|
||||
}: {
|
||||
shader: any;
|
||||
baseTextures: any[];
|
||||
|
|
@ -17,6 +31,7 @@ export function updateTerrainTextureShader({
|
|||
visibilityMask: any;
|
||||
tiling: Record<number, number>;
|
||||
debugMode?: boolean;
|
||||
detailTexture?: any;
|
||||
}) {
|
||||
const layerCount = baseTextures.length;
|
||||
|
||||
|
|
@ -45,6 +60,25 @@ export function updateTerrainTextureShader({
|
|||
// Add debug mode uniform
|
||||
shader.uniforms.debugMode = { value: debugMode ? 1.0 : 0.0 };
|
||||
|
||||
// Add detail texture uniforms
|
||||
if (detailTexture) {
|
||||
shader.uniforms.detailTexture = { value: detailTexture };
|
||||
shader.uniforms.detailTiling = { value: DETAIL_TILING };
|
||||
shader.uniforms.detailFadeDistance = { value: DETAIL_FADE_DISTANCE };
|
||||
|
||||
// Add vertex shader code to pass world position to fragment shader
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
"#include <common>",
|
||||
`#include <common>
|
||||
varying vec3 vTerrainWorldPos;`,
|
||||
);
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
"#include <worldpos_vertex>",
|
||||
`#include <worldpos_vertex>
|
||||
vTerrainWorldPos = (modelMatrix * vec4(transformed, 1.0)).xyz;`,
|
||||
);
|
||||
}
|
||||
|
||||
// Declare our uniforms at the top of the fragment shader
|
||||
shader.fragmentShader =
|
||||
`
|
||||
|
|
@ -67,6 +101,10 @@ uniform float tiling4;
|
|||
uniform float tiling5;
|
||||
uniform float debugMode;
|
||||
${visibilityMask ? "uniform sampler2D visibilityMask;" : ""}
|
||||
${detailTexture ? `uniform sampler2D detailTexture;
|
||||
uniform float detailTiling;
|
||||
uniform float detailFadeDistance;
|
||||
varying vec3 vTerrainWorldPos;` : ""}
|
||||
|
||||
// Wireframe edge detection for debug mode
|
||||
float getWireframe(vec2 uv, float gridSize, float lineWidth) {
|
||||
|
|
@ -143,6 +181,24 @@ float getWireframe(vec2 uv, float gridSize, float lineWidth) {
|
|||
// Assign to diffuseColor before lighting
|
||||
vec3 textureColor = ${layerCount > 1 ? "blended" : "c0"};
|
||||
|
||||
${
|
||||
detailTexture
|
||||
? `// Detail texture blending (Torque-style multiplicative blend)
|
||||
// Sample detail texture at high frequency tiling
|
||||
vec3 detailColor = texture2D(detailTexture, baseUv * detailTiling).rgb;
|
||||
|
||||
// Calculate distance-based fade factor using world positions
|
||||
// Torque: distFactor = (zeroDetailDistance - distance) / zeroDetailDistance
|
||||
float distToCamera = distance(vTerrainWorldPos, cameraPosition);
|
||||
float detailFade = clamp(1.0 - distToCamera / detailFadeDistance, 0.0, 1.0);
|
||||
|
||||
// Torque blending: dst * lerp(1.0, detailTexel, fadeFactor)
|
||||
// Detail textures are authored with bright values (~0.8 mean), not 0.5 gray
|
||||
// Direct multiplication adds subtle darkening for surface detail
|
||||
textureColor *= mix(vec3(1.0), detailColor, detailFade);`
|
||||
: ""
|
||||
}
|
||||
|
||||
// Debug mode wireframe handling
|
||||
if (debugMode > 0.5) {
|
||||
// 256 grid cells across the terrain (matches terrain resolution)
|
||||
|
|
|
|||
Loading…
Reference in a new issue