mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-07-10 05:54:52 +00:00
fix terrain positioning, layer blending, lighting when sun points up, add xPack2
This commit is contained in:
parent
aeda3ca8d5
commit
7f75ed84da
2238 changed files with 159619 additions and 1107 deletions
|
|
@ -11,7 +11,7 @@ import {
|
|||
Texture,
|
||||
BufferGeometry,
|
||||
} from "three";
|
||||
import { setupColor, setupAlphaTestedTexture } from "../textureUtils";
|
||||
import { setupTexture } from "../textureUtils";
|
||||
import { useDebug } from "./SettingsProvider";
|
||||
import { useShapeInfo, isOrganicShape } from "./ShapeInfoProvider";
|
||||
import { FloatingLabel } from "./FloatingLabel";
|
||||
|
|
@ -217,10 +217,10 @@ const StaticTexture = memo(function StaticTexture({
|
|||
const texture = useTexture(url, (texture) => {
|
||||
// Organic/alpha-tested textures need special handling to avoid mipmap artifacts
|
||||
if (isOrganic || isTranslucent) {
|
||||
return setupAlphaTestedTexture(texture);
|
||||
return setupTexture(texture, { disableMipmaps: true });
|
||||
}
|
||||
// Standard color texture setup for diffuse-only materials
|
||||
return setupColor(texture);
|
||||
return setupTexture(texture);
|
||||
});
|
||||
|
||||
const customMaterial = useMemo(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { useGLTF, useTexture } from "@react-three/drei";
|
|||
import { textureToUrl, interiorToUrl } from "../loaders";
|
||||
import type { TorqueObject } from "../torqueScript";
|
||||
import { getPosition, getProperty, getRotation, getScale } from "../mission";
|
||||
import { setupColor } from "../textureUtils";
|
||||
import { setupTexture } from "../textureUtils";
|
||||
import { FloatingLabel } from "./FloatingLabel";
|
||||
import { useDebug } from "./SettingsProvider";
|
||||
import { injectCustomFog } from "../fogShader";
|
||||
|
|
@ -40,7 +40,7 @@ function InteriorTexture({
|
|||
const debugContext = useDebug();
|
||||
const debugMode = debugContext?.debugMode ?? false;
|
||||
const url = textureToUrl(materialName);
|
||||
const texture = useTexture(url, (texture) => setupColor(texture));
|
||||
const texture = useTexture(url, (texture) => setupTexture(texture));
|
||||
|
||||
// Check for self-illuminating flag in material userData
|
||||
// Note: The io_dif Blender add-on needs to be updated to export material flags
|
||||
|
|
@ -95,7 +95,6 @@ function InteriorTexture({
|
|||
key={materialKey}
|
||||
map={texture}
|
||||
toneMapped={false}
|
||||
// @ts-expect-error - defines exists on Material but R3F types don't expose it
|
||||
defines={defines}
|
||||
onBeforeCompile={onBeforeCompile}
|
||||
/>
|
||||
|
|
@ -114,9 +113,8 @@ function InteriorTexture({
|
|||
ref={lambertMaterialRef}
|
||||
key={materialKey}
|
||||
map={texture}
|
||||
lightMap={lightMap ?? undefined}
|
||||
lightMap={lightMap}
|
||||
toneMapped={false}
|
||||
// @ts-expect-error - defines exists on Material but R3F types don't expose it
|
||||
defines={defines}
|
||||
onBeforeCompile={onBeforeCompile}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -39,8 +39,10 @@ const sourceGroupNames: Record<string, string> = {
|
|||
"z_mappacks/CTF/TWL2-MapPack.vl2": "TWL2",
|
||||
"z_mappacks/CTF/TWL2-MapPackEDIT.vl2": "TWL2",
|
||||
"z_mappacks/TWL_T2arenaOfficialMaps.vl2": "Arena",
|
||||
"z_mappacks/xPack2.vl2": "xPack2",
|
||||
"z_mappacks/z_DMP2-V0.6.vl2": "DMP2 (Discord Map Pack)",
|
||||
"z_mappacks/zDMP-4.7.3DX.vl2": "DMP (Discord Map Pack)",
|
||||
"z_mappacks/zDMP-4.7.3DX-ServerOnly.vl2": "DMP (Discord Map Pack)",
|
||||
};
|
||||
|
||||
const dirGroupNames: Record<string, string> = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Color, Vector3 } from "three";
|
||||
import type { TorqueObject } from "../torqueScript";
|
||||
import { getProperty } from "../mission";
|
||||
import { updateGlobalSunUniforms } from "../globalSunUniforms";
|
||||
|
||||
export function Sun({ object }: { object: TorqueObject }) {
|
||||
// Parse sun direction - points FROM sun TO scene
|
||||
|
|
@ -43,6 +44,16 @@ export function Sun({ object }: { object: TorqueObject }) {
|
|||
return new Color(r, g, b);
|
||||
}, [object]);
|
||||
|
||||
// Torque lighting check (terrLighting.cc): if light direction points up,
|
||||
// terrain surfaces with upward normals receive only ambient light.
|
||||
// direction.y < 0 means light pointing down (toward ground)
|
||||
const sunLightPointsDown = direction.y < 0;
|
||||
|
||||
// Update global uniform so terrain shader knows the light direction
|
||||
useEffect(() => {
|
||||
updateGlobalSunUniforms(sunLightPointsDown);
|
||||
}, [sunLightPointsDown]);
|
||||
|
||||
// Base lighting intensities - neutral baseline, each object type applies its own multipliers
|
||||
// See lightingConfig.ts for per-object-type adjustments
|
||||
const directionalIntensity = 1.0;
|
||||
|
|
@ -69,6 +80,7 @@ export function Sun({ object }: { object: TorqueObject }) {
|
|||
shadow-camera-far={12000}
|
||||
shadow-bias={-0.00001}
|
||||
shadow-normalBias={0.4}
|
||||
shadow-radius={2}
|
||||
/>
|
||||
{/* Ambient fill light - prevents pure black shadows */}
|
||||
<ambientLight color={ambient} intensity={ambientIntensity} />
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
Vector3,
|
||||
} from "three";
|
||||
import type { TorqueObject } from "../torqueScript";
|
||||
import { getFloat, getInt, getPosition, getProperty } from "../mission";
|
||||
import { getFloat, getInt, getProperty } from "../mission";
|
||||
import { loadTerrain } from "../loaders";
|
||||
import { uint16ToFloat32 } from "../arrayUtils";
|
||||
import { setupMask } from "../textureUtils";
|
||||
|
|
@ -543,10 +543,13 @@ export const TerrainBlock = memo(function TerrainBlock({
|
|||
const visibleDistance = useVisibleDistance();
|
||||
const camera = useThree((state) => state.camera);
|
||||
|
||||
// Torque ignores the mission's terrain position and always uses a fixed formula:
|
||||
// setPosition(Point3F(-squareSize * (BlockSize >> 1), -squareSize * (BlockSize >> 1), 0));
|
||||
// where BlockSize = 256. See tribes2-engine/terrain/terrData.cc:679
|
||||
const basePosition = useMemo(() => {
|
||||
const [x, , z] = getPosition(object);
|
||||
return { x, z };
|
||||
}, [object]);
|
||||
const offset = -squareSize * (TERRAIN_SIZE / 2);
|
||||
return { x: offset, z: offset };
|
||||
}, [squareSize]);
|
||||
|
||||
const emptySquares = useMemo(() => {
|
||||
const value = getProperty(object, "emptySquares");
|
||||
|
|
|
|||
|
|
@ -11,14 +11,12 @@ import {
|
|||
terrainTextureToUrl,
|
||||
textureToUrl,
|
||||
} from "../loaders";
|
||||
import { setupColor } from "../textureUtils";
|
||||
import { setupTexture } from "../textureUtils";
|
||||
import { updateTerrainTextureShader } from "../terrainMaterial";
|
||||
import { useDebug } from "./SettingsProvider";
|
||||
import { injectCustomFog } from "../fogShader";
|
||||
import { globalFogUniforms } from "../globalFogUniforms";
|
||||
|
||||
const DEFAULT_SQUARE_SIZE = 8;
|
||||
|
||||
// Texture tiling factors for each terrain layer
|
||||
const TILING: Record<number, number> = {
|
||||
0: 32,
|
||||
|
|
@ -64,7 +62,7 @@ function BlendedTerrainTextures({
|
|||
const baseTextures = useTexture(
|
||||
textureNames.map((name) => terrainTextureToUrl(name)),
|
||||
(textures) => {
|
||||
textures.forEach((tex) => setupColor(tex));
|
||||
textures.forEach((tex) => setupTexture(tex));
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -76,7 +74,7 @@ function BlendedTerrainTextures({
|
|||
const detailTexture = useTexture(
|
||||
detailTextureUrl ?? FALLBACK_TEXTURE_URL,
|
||||
(tex) => {
|
||||
setupColor(tex);
|
||||
setupTexture(tex);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -134,7 +132,6 @@ function BlendedTerrainTextures({
|
|||
map={displacementMap}
|
||||
depthWrite
|
||||
side={FrontSide}
|
||||
// @ts-expect-error - defines exists on Material but R3F types don't expose it
|
||||
defines={{ DEBUG_MODE: debugMode ? 1 : 0 }}
|
||||
onBeforeCompile={onBeforeCompile}
|
||||
/>
|
||||
|
|
@ -190,9 +187,10 @@ export const TerrainTile = memo(function TerrainTile({
|
|||
visible = true,
|
||||
}: TerrainTileProps) {
|
||||
const position = useMemo(() => {
|
||||
// Terrain geometry is centered at origin, but Tribes 2 terrain origin is at
|
||||
// corner. The engine always uses the default square size (8) for positioning.
|
||||
const geometryOffset = (DEFAULT_SQUARE_SIZE * 256) / 2;
|
||||
// Terrain geometry is centered at origin. Torque's terrain position formula
|
||||
// is -squareSize * 128, which equals -blockSize / 2. Since our geometry is
|
||||
// already centered, basePosition + geometryOffset cancels to 0 for single tiles.
|
||||
const geometryOffset = blockSize / 2;
|
||||
return [
|
||||
basePosition.x + tileX * blockSize + geometryOffset,
|
||||
0,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
getRotation,
|
||||
getScale,
|
||||
} from "../mission";
|
||||
import { setupColor } from "../textureUtils";
|
||||
import { setupTexture } from "../textureUtils";
|
||||
import { createWaterMaterial } from "../waterMaterial";
|
||||
import { useDebug, useSettings } from "./SettingsProvider";
|
||||
import { usePositionTracker } from "./usePositionTracker";
|
||||
|
|
@ -63,7 +63,7 @@ export function WaterMaterial({
|
|||
attach?: string;
|
||||
}) {
|
||||
const url = textureToUrl(surfaceTexture);
|
||||
const texture = useTexture(url, (texture) => setupColor(texture));
|
||||
const texture = useTexture(url, (texture) => setupTexture(texture));
|
||||
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
|
|
@ -309,7 +309,7 @@ const WaterReps = memo(function WaterReps({
|
|||
(textures) => {
|
||||
const texArray = Array.isArray(textures) ? textures : [textures];
|
||||
texArray.forEach((tex) => {
|
||||
setupColor(tex);
|
||||
setupTexture(tex);
|
||||
tex.colorSpace = NoColorSpace;
|
||||
tex.wrapS = RepeatWrapping;
|
||||
tex.wrapT = RepeatWrapping;
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ interface IflAtlas {
|
|||
const atlasCache = new Map<string, IflAtlas>();
|
||||
|
||||
function createAtlas(textures: Texture[]): IflAtlas {
|
||||
const frameWidth = textures[0].image.width;
|
||||
const frameHeight = textures[0].image.height;
|
||||
const firstImage = textures[0].image as HTMLImageElement;
|
||||
const frameWidth = firstImage.width;
|
||||
const frameHeight = firstImage.height;
|
||||
const frameCount = textures.length;
|
||||
|
||||
// Arrange frames in a roughly square grid.
|
||||
|
|
@ -45,7 +46,7 @@ function createAtlas(textures: Texture[]): IflAtlas {
|
|||
textures.forEach((tex, i) => {
|
||||
const col = i % columns;
|
||||
const row = Math.floor(i / columns);
|
||||
ctx.drawImage(tex.image, col * frameWidth, row * frameHeight);
|
||||
ctx.drawImage(tex.image as CanvasImageSource, col * frameWidth, row * frameHeight);
|
||||
});
|
||||
|
||||
const texture = new CanvasTexture(canvas);
|
||||
|
|
|
|||
18
src/globalSunUniforms.ts
Normal file
18
src/globalSunUniforms.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Global sun shader uniforms shared across all materials.
|
||||
*
|
||||
* Used to communicate sun light direction to shaders that need to adjust
|
||||
* their lighting calculations (e.g., terrain ignores light from below).
|
||||
*/
|
||||
|
||||
export const globalSunUniforms = {
|
||||
/** True if sun light points downward (negative Y). False if pointing up. */
|
||||
sunLightPointsDown: { value: true },
|
||||
};
|
||||
|
||||
/**
|
||||
* Update sun light direction state. Called by Sun component when direction changes.
|
||||
*/
|
||||
export function updateGlobalSunUniforms(sunLightPointsDown: boolean): void {
|
||||
globalSunUniforms.sunLightPointsDown.value = sunLightPointsDown;
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
DoubleSide,
|
||||
MeshStandardMaterial,
|
||||
Texture,
|
||||
RepeatWrapping,
|
||||
|
|
@ -53,7 +54,7 @@ const alphaAsRoughnessShaderModifier = (shader: any) => {
|
|||
#include <roughness_fragment>
|
||||
// Override roughness with map alpha channel if map exists
|
||||
#ifdef USE_MAP
|
||||
roughnessFactor = texture2D(map, vMapUv).a * 1;
|
||||
roughnessFactor = texture2D(map, vMapUv).a;
|
||||
#endif
|
||||
`,
|
||||
);
|
||||
|
|
@ -81,7 +82,7 @@ export function setupAlphaAsRoughnessTexture(texture: Texture) {
|
|||
*/
|
||||
export function createAlphaAsRoughnessMaterial() {
|
||||
const material = new MeshStandardMaterial({
|
||||
side: 2, // DoubleSide
|
||||
side: DoubleSide,
|
||||
metalness: 0.0,
|
||||
roughness: 1.0,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
* 4. Final output = lightmap × texture, all in gamma space
|
||||
*/
|
||||
|
||||
import { globalSunUniforms } from "./globalSunUniforms";
|
||||
|
||||
// Terrain and texture dimensions (must match TerrainBlock.tsx constants)
|
||||
const TERRAIN_SIZE = 256; // Terrain grid size in squares
|
||||
const LIGHTMAP_SIZE = 512; // Lightmap texture size (2 pixels per terrain square)
|
||||
|
|
@ -68,16 +70,17 @@ export function updateTerrainTextureShader({
|
|||
detailTexture?: any;
|
||||
lightmap?: any;
|
||||
}) {
|
||||
// Add global sun uniform (shared reference - value updates automatically)
|
||||
shader.uniforms.sunLightPointsDown = globalSunUniforms.sunLightPointsDown;
|
||||
const layerCount = baseTextures.length;
|
||||
|
||||
baseTextures.forEach((tex, i) => {
|
||||
shader.uniforms[`albedo${i}`] = { value: tex };
|
||||
});
|
||||
|
||||
// Pass all alpha textures including mask0 for additive blending
|
||||
alphaTextures.forEach((tex, i) => {
|
||||
if (i > 0) {
|
||||
shader.uniforms[`mask${i}`] = { value: tex };
|
||||
}
|
||||
shader.uniforms[`mask${i}`] = { value: tex };
|
||||
});
|
||||
|
||||
// Add visibility mask uniform if we have empty squares
|
||||
|
|
@ -92,7 +95,6 @@ export function updateTerrainTextureShader({
|
|||
};
|
||||
});
|
||||
|
||||
|
||||
// Add lightmap uniform for smooth per-pixel terrain lighting
|
||||
if (lightmap) {
|
||||
shader.uniforms.terrainLightmap = { value: lightmap };
|
||||
|
|
@ -126,6 +128,7 @@ uniform sampler2D albedo2;
|
|||
uniform sampler2D albedo3;
|
||||
uniform sampler2D albedo4;
|
||||
uniform sampler2D albedo5;
|
||||
uniform sampler2D mask0;
|
||||
uniform sampler2D mask1;
|
||||
uniform sampler2D mask2;
|
||||
uniform sampler2D mask3;
|
||||
|
|
@ -139,6 +142,7 @@ uniform float tiling4;
|
|||
uniform float tiling5;
|
||||
${visibilityMask ? "uniform sampler2D visibilityMask;" : ""}
|
||||
${lightmap ? "uniform sampler2D terrainLightmap;" : ""}
|
||||
uniform bool sunLightPointsDown;
|
||||
${
|
||||
detailTexture
|
||||
? `uniform sampler2D detailTexture;
|
||||
|
|
@ -202,25 +206,29 @@ float terrainShadowFactor = 1.0;
|
|||
: ""
|
||||
}
|
||||
|
||||
// Sample linear masks (use R channel)
|
||||
// Sample alpha masks for all layers (use R channel)
|
||||
// Add +0.5 texel offset: Torque samples alpha at grid corners (integer indices),
|
||||
// but GPU linear filtering samples at texel centers. This offset aligns them.
|
||||
vec2 alphaUv = baseUv + vec2(0.5 / ${TERRAIN_SIZE}.0);
|
||||
float a1 = texture2D(mask1, alphaUv).r;
|
||||
${layerCount > 1 ? `float a2 = texture2D(mask2, alphaUv).r;` : ""}
|
||||
${layerCount > 2 ? `float a3 = texture2D(mask3, alphaUv).r;` : ""}
|
||||
${layerCount > 3 ? `float a4 = texture2D(mask4, alphaUv).r;` : ""}
|
||||
${layerCount > 4 ? `float a5 = texture2D(mask5, alphaUv).r;` : ""}
|
||||
float a0 = texture2D(mask0, alphaUv).r;
|
||||
${layerCount > 1 ? `float a1 = texture2D(mask1, alphaUv).r;` : ""}
|
||||
${layerCount > 2 ? `float a2 = texture2D(mask2, alphaUv).r;` : ""}
|
||||
${layerCount > 3 ? `float a3 = texture2D(mask3, alphaUv).r;` : ""}
|
||||
${layerCount > 4 ? `float a4 = texture2D(mask4, alphaUv).r;` : ""}
|
||||
${layerCount > 5 ? `float a5 = texture2D(mask5, alphaUv).r;` : ""}
|
||||
|
||||
// Bottom-up compositing: each mask tells how much the higher layer replaces lower
|
||||
${layerCount > 1 ? `vec3 blended = mix(c0, c1, clamp(a1, 0.0, 1.0));` : ""}
|
||||
${layerCount > 2 ? `blended = mix(blended, c2, clamp(a2, 0.0, 1.0));` : ""}
|
||||
${layerCount > 3 ? `blended = mix(blended, c3, clamp(a3, 0.0, 1.0));` : ""}
|
||||
${layerCount > 4 ? `blended = mix(blended, c4, clamp(a4, 0.0, 1.0));` : ""}
|
||||
${layerCount > 5 ? `blended = mix(blended, c5, clamp(a5, 0.0, 1.0));` : ""}
|
||||
// Torque-style additive weighted blending (blender.cc):
|
||||
// result = tex0 * alpha0 + tex1 * alpha1 + tex2 * alpha2 + ...
|
||||
// Each layer's alpha map defines its contribution weight.
|
||||
vec3 blended = c0 * a0;
|
||||
${layerCount > 1 ? `blended += c1 * a1;` : ""}
|
||||
${layerCount > 2 ? `blended += c2 * a2;` : ""}
|
||||
${layerCount > 3 ? `blended += c3 * a3;` : ""}
|
||||
${layerCount > 4 ? `blended += c4 * a4;` : ""}
|
||||
${layerCount > 5 ? `blended += c5 * a5;` : ""}
|
||||
|
||||
// Assign to diffuseColor before lighting
|
||||
vec3 textureColor = ${layerCount > 1 ? "blended" : "c0"};
|
||||
vec3 textureColor = blended;
|
||||
|
||||
${
|
||||
detailTexture
|
||||
|
|
@ -256,6 +264,12 @@ float terrainShadowFactor = 1.0;
|
|||
// Override RE_Direct to extract shadow factor for Torque-style gamma-space lighting
|
||||
#undef RE_Direct
|
||||
void RE_Direct_TerrainShadow( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {
|
||||
// Torque lighting (terrLighting.cc): if light points up, terrain gets only ambient
|
||||
// This prevents shadow acne from light hitting terrain backfaces
|
||||
if (!sunLightPointsDown) {
|
||||
terrainShadowFactor = 0.0;
|
||||
return;
|
||||
}
|
||||
// directLight.color = sunColor * shadowFactor (shadow already applied by Three.js)
|
||||
// Extract shadow factor by comparing to original sun color
|
||||
#if ( NUM_DIR_LIGHTS > 0 )
|
||||
|
|
|
|||
|
|
@ -55,29 +55,6 @@ export function setupTexture<T extends Texture>(
|
|||
return tex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup a color texture with standard settings.
|
||||
* @deprecated Use setupTexture() instead
|
||||
*/
|
||||
export function setupColor<T extends Texture>(
|
||||
tex: T,
|
||||
repeat: [number, number] = [1, 1],
|
||||
): T {
|
||||
return setupTexture(tex, { repeat });
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup for alpha-tested textures (vegetation, etc).
|
||||
* Disables mipmaps to prevent checkerboard artifacts from alpha averaging.
|
||||
* @deprecated Use setupTexture(tex, { disableMipmaps: true }) instead
|
||||
*/
|
||||
export function setupAlphaTestedTexture<T extends Texture>(
|
||||
tex: T,
|
||||
repeat: [number, number] = [1, 1],
|
||||
): T {
|
||||
return setupTexture(tex, { repeat, disableMipmaps: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup a mask texture (single channel, linear color space).
|
||||
* Used for terrain blend masks and similar data textures.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue