mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-06 04:43:43 +00:00
feat(dashboard): terrain lit by the zone's lights, blending with the scenery
The textured terrain in the world and property 3D views now takes the lighting the scenery uses (the scene's sun and ambient light, blending as the focus moves between scenes), as TiledDetailDiffuse_4_PS does: the blended textures times the diffuse map times two times the clamped vertex light, times the blend alpha. Until a manifest brings the lighting, the terrain keeps its own light. Checked against the client (RAWReadColorandLightMaps 0x0102e3e0 and the texture map reader at 0x0103aaf0): the game's diffuse map and blend map are DDS copies of the color and texture maps the views use (same pixels on the Avant Gardens chunks compared), so the dark patches and the steep drops at the plateau's edges are in the terrain file itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -304,6 +304,9 @@ void main() {
|
||||
const TERRAIN_FRAGMENT = `
|
||||
uniform sampler2D texture1, texture2, texture3, texture4, blendMap, colorMap, sceneMap, scenePalette;
|
||||
uniform vec3 lightDirection;
|
||||
// The zone's lights as the game's TerrainDiffuse.fx gets them (scenery.js gameLights), when gameLightOn
|
||||
uniform vec3 gameLightColor, gameAmbient, gameLightVec;
|
||||
uniform float gameLightOn;
|
||||
uniform int look;
|
||||
uniform float sceneSize;
|
||||
varying vec2 vUv;
|
||||
@@ -327,6 +330,12 @@ void main() {
|
||||
color = mix(color, texture2D(texture2, tiled), blend.r);
|
||||
color = mix(color, texture2D(texture3, tiled), blend.g);
|
||||
color = mix(color, texture2D(texture4, tiled), blend.b);
|
||||
if (gameLightOn > 0.5) {
|
||||
// TiledDetailDiffuse_4_PS: textures * diffuse map * 2 * (sun * N.L + ambient, clamped as a vertex color), * blend alpha
|
||||
vec3 lit = clamp(gameLightColor * max(0.0, dot(normalize(vNormal), gameLightVec)) + gameAmbient, 0.0, 1.0);
|
||||
gl_FragColor = vec4(min(color.rgb * tint * lit * blend.a, 1.0), 1.0);
|
||||
return;
|
||||
}
|
||||
gl_FragColor = vec4(min(color.rgb * tint, 1.0) * light * blend.a, 1.0);
|
||||
}`;
|
||||
|
||||
@@ -345,7 +354,7 @@ function sceneTexture(base64, size) {
|
||||
* Returns the group, a function giving the ground height at (x, z), and setLook(look, layers): switch to one of
|
||||
* TERRAIN_LOOKS; the scene look needs the zone's terrain layers (/api/world3d/:zone/terrain_layers).
|
||||
*/
|
||||
export function buildTerrainChunks(data, loadTexture, lightDirection) {
|
||||
export function buildTerrainChunks(data, loadTexture, lightDirection, gameLights = null) {
|
||||
const group = new THREE.Group();
|
||||
const grids = [];
|
||||
const materials = [];
|
||||
@@ -380,7 +389,12 @@ export function buildTerrainChunks(data, loadTexture, lightDirection) {
|
||||
texture3: { value: loadTexture(chunk.textures[2]) }, texture4: { value: loadTexture(chunk.textures[3]) },
|
||||
blendMap: { value: bgraTexture(chunk.blend, chunk.blendSize) }, colorMap: { value: bgraTexture(chunk.color, chunk.colorSize) },
|
||||
sceneMap: { value: null }, scenePalette: { value: null }, sceneSize: { value: 1 },
|
||||
lightDirection: { value: lightDirection }, look: { value: 0 }
|
||||
lightDirection: { value: lightDirection }, look: { value: 0 },
|
||||
// Shared with the scenery, so the terrain follows its lighting (and its blends between scenes)
|
||||
gameLightColor: gameLights ? gameLights.gameLightColor : { value: new THREE.Vector3() },
|
||||
gameAmbient: gameLights ? gameLights.gameAmbient : { value: new THREE.Vector3() },
|
||||
gameLightVec: gameLights ? gameLights.gameLightVec : { value: new THREE.Vector3(0, 1, 0) },
|
||||
gameLightOn: gameLights ? gameLights.gameLightOn : { value: 0 }
|
||||
}
|
||||
});
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
@@ -772,7 +786,7 @@ export function createViewer(container, { onProgress, onSelect, onTick, brickUrl
|
||||
* The zone's whole terrain as the game draws it (null removes it): data from /api/properties/:id/terrain_chunks,
|
||||
* textures from textureUrl(id).
|
||||
*/
|
||||
setTerrainChunks(data, textureUrl) {
|
||||
setTerrainChunks(data, textureUrl, gameLights = null) {
|
||||
removeTerrain();
|
||||
if (data && data.chunks && data.chunks.length) {
|
||||
const loader = new THREE.TextureLoader();
|
||||
@@ -788,7 +802,7 @@ export function createViewer(container, { onProgress, onSelect, onTick, brickUrl
|
||||
return textures.get(id);
|
||||
};
|
||||
const sunDirection = sun.position.clone().sub(sun.target.position).normalize();
|
||||
const built = buildTerrainChunks(data, loadTexture, sunDirection);
|
||||
const built = buildTerrainChunks(data, loadTexture, sunDirection, gameLights);
|
||||
terrain = built.group;
|
||||
groundHeight = built.heightAt;
|
||||
scene.add(terrain);
|
||||
|
||||
@@ -184,8 +184,12 @@ export function createScenery({ scene, camera, renderer, urls, focus, onProgress
|
||||
}
|
||||
|
||||
// The zone's lights as the game's shaders get them (manifest.lighting), shared by every lit material
|
||||
const gameLights = { gameLightColor: { value: new THREE.Vector3(1, 1, 1) }, gameAmbient: { value: new THREE.Vector3() }, gameLightVec: { value: new THREE.Vector3(0, 1, 0) } };
|
||||
const gameLights = {
|
||||
gameLightColor: { value: new THREE.Vector3(1, 1, 1) }, gameAmbient: { value: new THREE.Vector3() }, gameLightVec: { value: new THREE.Vector3(0, 1, 0) },
|
||||
gameLightOn: { value: 0 } // 1 once a manifest brought the zone's lighting (the terrain uses its own light until then)
|
||||
};
|
||||
function setGameLights(lighting) {
|
||||
gameLights.gameLightOn.value = lighting ? 1 : 0;
|
||||
if (!lighting) return;
|
||||
gameLights.gameLightColor.value.fromArray(lighting.light);
|
||||
gameLights.gameAmbient.value.fromArray(lighting.ambient);
|
||||
@@ -569,6 +573,8 @@ export function createScenery({ scene, camera, renderer, urls, focus, onProgress
|
||||
sceneState() {
|
||||
return { mode: sceneMode, scene: focusScene, shown: shownScenes, scenes: manifest && manifest.scenes ? manifest.scenes : [] };
|
||||
},
|
||||
/** The zone's lights as uniforms (updated in place, blends too), for the terrain to be lit like the scenery. */
|
||||
gameLights() { return gameLights; },
|
||||
/** How many objects the loaded manifest places. */
|
||||
count() { return manifest ? manifest.objects.asset.length : 0; },
|
||||
stats() {
|
||||
|
||||
@@ -417,7 +417,7 @@ function showTerrain() {
|
||||
}
|
||||
return textures.get(id);
|
||||
};
|
||||
const built = buildTerrainChunks(terrainData, loadTexture, sun.position.clone().normalize());
|
||||
const built = buildTerrainChunks(terrainData, loadTexture, sun.position.clone().normalize(), scenery.gameLights());
|
||||
// A flat plane far from every object is a placeholder the game never shows: leave it out, and don't put heat
|
||||
// cells or players on it
|
||||
const terrainBox = new THREE.Box3().setFromObject(built.group);
|
||||
|
||||
@@ -357,7 +357,7 @@ async function showTerrain() {
|
||||
chunkTerrain = t && t.chunks ? t : null;
|
||||
}
|
||||
if (chunkTerrain) {
|
||||
viewer.setTerrainChunks(chunkTerrain, (id) => assetApi + '/terrain_textures/' + id);
|
||||
viewer.setTerrainChunks(chunkTerrain, (id) => assetApi + '/terrain_textures/' + id, scenery.gameLights());
|
||||
return;
|
||||
}
|
||||
if (terrainData === undefined) {
|
||||
|
||||
Reference in New Issue
Block a user