fix terrain positioning, layer blending, lighting when sun points up, add xPack2

This commit is contained in:
Brian Beck 2025-12-12 14:16:21 -08:00
parent aeda3ca8d5
commit 7f75ed84da
2238 changed files with 159619 additions and 1107 deletions

View file

@ -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(

View file

@ -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}
/>

View file

@ -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> = {

View file

@ -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} />

View file

@ -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");

View file

@ -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,

View file

@ -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;

View file

@ -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);