terrain: extend map with reduced-resolution outer height skirt
This commit is contained in:
1 parent
4441619b8f
commit
c8fa1b3260
2 files changed
+273
-6
No files matched your search
@@ -235,6 +235,14 @@ window.CONFIG = {
|
|||||||
enabled: true, // false = flat plane (no DEM, no terrain shadows)
|
enabled: true, // false = flat plane (no DEM, no terrain shadows)
|
||||||
exaggeration: 8, // vertical scale (1 = real, nearly invisible here)
|
exaggeration: 8, // vertical scale (1 = real, nearly invisible here)
|
||||||
edgeFadeTiles: 4, // soft rim at the mosaic border, in tiles (~13 km)
|
edgeFadeTiles: 4, // soft rim at the mosaic border, in tiles (~13 km)
|
||||||
|
edgeSkirtKm: 800, // coarse outer terrain pulled past the horizon
|
||||||
|
edgeSkirtRings: 24, // coarse rings in the outer skirt
|
||||||
|
edgeSkirtNoise: 0.28, // fallback height variation if outer DEM is off
|
||||||
|
outerDem: {
|
||||||
|
enabled: true,
|
||||||
|
zoom: 7, // reduced-resolution terrarium zoom for the skirt
|
||||||
|
maxMeters: 2500 // height normalization for skirt colors
|
||||||
|
},
|
||||||
carLights: true, // moving vehicle lights on the real road network
|
carLights: true, // moving vehicle lights on the real road network
|
||||||
cityLights: true, // settlement lights from OSM place nodes
|
cityLights: true, // settlement lights from OSM place nodes
|
||||||
wireframe: false // true = draw the mesh edges (orientation debug)
|
wireframe: false // true = draw the mesh edges (orientation debug)
|
||||||
|
|||||||
Vendored
+265
-6
@@ -528,6 +528,7 @@ var TERRAIN_VERT_SRC = [
|
|||||||
'attribute vec3 aPos;',
|
'attribute vec3 aPos;',
|
||||||
'attribute vec3 aNormal;',
|
'attribute vec3 aNormal;',
|
||||||
'attribute vec2 aUV;',
|
'attribute vec2 aUV;',
|
||||||
|
'attribute vec3 aColor;',
|
||||||
'uniform mat4 uView;',
|
'uniform mat4 uView;',
|
||||||
'uniform mat4 uProj;',
|
'uniform mat4 uProj;',
|
||||||
'uniform vec3 uCamPos;',
|
'uniform vec3 uCamPos;',
|
||||||
@@ -538,6 +539,7 @@ var TERRAIN_VERT_SRC = [
|
|||||||
'varying float vY;',
|
'varying float vY;',
|
||||||
'varying vec3 vViewPos;',
|
'varying vec3 vViewPos;',
|
||||||
'varying vec3 vWorld;',
|
'varying vec3 vWorld;',
|
||||||
|
'varying vec3 vColor;',
|
||||||
'void main() {',
|
'void main() {',
|
||||||
'vec3 pos = aPos;',
|
'vec3 pos = aPos;',
|
||||||
'vec3 n = aNormal;',
|
'vec3 n = aNormal;',
|
||||||
@@ -551,6 +553,7 @@ var TERRAIN_VERT_SRC = [
|
|||||||
'}',
|
'}',
|
||||||
'vNormal = n;',
|
'vNormal = n;',
|
||||||
'vUV = aUV;',
|
'vUV = aUV;',
|
||||||
|
'vColor = aColor;',
|
||||||
'vY = pos.y;',
|
'vY = pos.y;',
|
||||||
'vWorld = pos;',
|
'vWorld = pos;',
|
||||||
'vec4 vp = uView * vec4(pos, 1.0);',
|
'vec4 vp = uView * vec4(pos, 1.0);',
|
||||||
@@ -677,6 +680,7 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
'varying float vY;',
|
'varying float vY;',
|
||||||
'varying vec3 vViewPos;',
|
'varying vec3 vViewPos;',
|
||||||
'varying vec3 vWorld;',
|
'varying vec3 vWorld;',
|
||||||
|
'varying vec3 vColor;',
|
||||||
CLOUD_SAMPLER_GLSL,
|
CLOUD_SAMPLER_GLSL,
|
||||||
'void main() {',
|
'void main() {',
|
||||||
'vec2 gUv = vUV;',
|
'vec2 gUv = vUV;',
|
||||||
@@ -688,6 +692,9 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
'float edgeX = min(gUv.x, 1.0 - gUv.x) * uGroundSize.x;',
|
'float edgeX = min(gUv.x, 1.0 - gUv.x) * uGroundSize.x;',
|
||||||
'float edgeZ = min(gUv.y, 1.0 - gUv.y) * uGroundSize.y;',
|
'float edgeZ = min(gUv.y, 1.0 - gUv.y) * uGroundSize.y;',
|
||||||
'float edgeFade = smoothstep(0.0, uEdgeFadeTiles, min(edgeX, edgeZ));',
|
'float edgeFade = smoothstep(0.0, uEdgeFadeTiles, min(edgeX, edgeZ));',
|
||||||
|
'float outside = step(0.0, -gUv.x) + step(1.0001, gUv.x) +',
|
||||||
|
'step(0.0, -gUv.y) + step(1.0001, gUv.y);',
|
||||||
|
'if (outside > 0.0) edgeFade = 1.0;',
|
||||||
'vec3 col = texture2D(uGround, gUv).rgb;',
|
'vec3 col = texture2D(uGround, gUv).rgb;',
|
||||||
'vec3 n = normalize(vNormal);',
|
'vec3 n = normalize(vNormal);',
|
||||||
// House lights are not painted into the texture any more (the VIIRS
|
// House lights are not painted into the texture any more (the VIIRS
|
||||||
@@ -698,7 +705,8 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
// Direct beam (extinction-coloured by the JS air-mass model) +
|
// Direct beam (extinction-coloured by the JS air-mass model) +
|
||||||
// hemispherical skylight + moonlight. No hand-picked day/night
|
// hemispherical skylight + moonlight. No hand-picked day/night
|
||||||
// ramp: the sun altitude drives colour and strength continuously.
|
// ramp: the sun altitude drives colour and strength continuously.
|
||||||
'vec3 alb = texture2D(uGround, gUv).rgb * uAlbedoTint;',
|
'float outsideMask = clamp(outside, 0.0, 1.0);',
|
||||||
|
'vec3 alb = mix(texture2D(uGround, gUv).rgb, vColor, outsideMask) * uAlbedoTint;',
|
||||||
'float ndl = max(0.0, dot(n, uSunDir));',
|
'float ndl = max(0.0, dot(n, uSunDir));',
|
||||||
// Cloud shadow: march from the surface point towards the sun
|
// Cloud shadow: march from the surface point towards the sun
|
||||||
// through the cloud layer and accumulate optical depth. Same
|
// through the cloud layer and accumulate optical depth. Same
|
||||||
@@ -881,6 +889,7 @@ function init(canvas) {
|
|||||||
var terrainApos = terrainProg ? gl.getAttribLocation(terrainProg, 'aPos') : -1;
|
var terrainApos = terrainProg ? gl.getAttribLocation(terrainProg, 'aPos') : -1;
|
||||||
var terrainAnormal = terrainProg ? gl.getAttribLocation(terrainProg, 'aNormal') : -1;
|
var terrainAnormal = terrainProg ? gl.getAttribLocation(terrainProg, 'aNormal') : -1;
|
||||||
var terrainAuv = terrainProg ? gl.getAttribLocation(terrainProg, 'aUV') : -1;
|
var terrainAuv = terrainProg ? gl.getAttribLocation(terrainProg, 'aUV') : -1;
|
||||||
|
var terrainAcolor = terrainProg ? gl.getAttribLocation(terrainProg, 'aColor') : -1;
|
||||||
var terrainUview = terrainProg ? gl.getUniformLocation(terrainProg, 'uView') : null;
|
var terrainUview = terrainProg ? gl.getUniformLocation(terrainProg, 'uView') : null;
|
||||||
var terrainUproj = terrainProg ? gl.getUniformLocation(terrainProg, 'uProj') : null;
|
var terrainUproj = terrainProg ? gl.getUniformLocation(terrainProg, 'uProj') : null;
|
||||||
var terrainUground = terrainProg ? gl.getUniformLocation(terrainProg, 'uGround') : null;
|
var terrainUground = terrainProg ? gl.getUniformLocation(terrainProg, 'uGround') : null;
|
||||||
@@ -962,6 +971,7 @@ function init(canvas) {
|
|||||||
terrain: {
|
terrain: {
|
||||||
prog: terrainProg,
|
prog: terrainProg,
|
||||||
aPos: terrainApos, aNormal: terrainAnormal, aUV: terrainAuv,
|
aPos: terrainApos, aNormal: terrainAnormal, aUV: terrainAuv,
|
||||||
|
aColor: terrainAcolor,
|
||||||
uView: terrainUview, uProj: terrainUproj,
|
uView: terrainUview, uProj: terrainUproj,
|
||||||
uGround: terrainUground, uSunDir: terrainUsundir,
|
uGround: terrainUground, uSunDir: terrainUsundir,
|
||||||
uGroundCenter: terrainUgroundcenter, uGroundSize: terrainUgroundsize,
|
uGroundCenter: terrainUgroundcenter, uGroundSize: terrainUgroundsize,
|
||||||
@@ -1112,6 +1122,20 @@ var TERRAIN_ON = TERRAIN_CFG.enabled === false ? 0.0 : 1.0;
|
|||||||
var EDGE_FADE_TILES = (typeof TERRAIN_CFG.edgeFadeTiles === 'number')
|
var EDGE_FADE_TILES = (typeof TERRAIN_CFG.edgeFadeTiles === 'number')
|
||||||
? Math.max(0.5, TERRAIN_CFG.edgeFadeTiles) : 4.0;
|
? Math.max(0.5, TERRAIN_CFG.edgeFadeTiles) : 4.0;
|
||||||
var EDGE_SINK_TILES = 0.06;
|
var EDGE_SINK_TILES = 0.06;
|
||||||
|
var OUTER_DEM_CFG = TERRAIN_CFG.outerDem || {};
|
||||||
|
var OUTER_DEM_ON = OUTER_DEM_CFG.enabled !== false && TERRAIN_ON > 0.5;
|
||||||
|
var OUTER_DEM_Z = Math.max(4, Math.min(11,
|
||||||
|
(typeof OUTER_DEM_CFG.zoom === 'number') ? OUTER_DEM_CFG.zoom : 7));
|
||||||
|
var OUTER_TILES_PER = 1 << (11 - OUTER_DEM_Z);
|
||||||
|
var OUTER_PX_PER_Z11 = 256 / OUTER_TILES_PER;
|
||||||
|
var outerElevCanvas = null, outerElevCtx = null, outerElevData = null;
|
||||||
|
var outerElevReady = false;
|
||||||
|
var outerHalfX = 0, outerHalfY = 0, outerW = 0, outerH = 0;
|
||||||
|
function terrainSkirtUnits() {
|
||||||
|
var km = (typeof TERRAIN_CFG.edgeSkirtKm === 'number')
|
||||||
|
? Math.max(0, TERRAIN_CFG.edgeSkirtKm) : 800;
|
||||||
|
return km / KM_PER_TILE;
|
||||||
|
}
|
||||||
var TERRAIN_EXAG = (typeof TERRAIN_CFG.exaggeration === 'number' &&
|
var TERRAIN_EXAG = (typeof TERRAIN_CFG.exaggeration === 'number' &&
|
||||||
TERRAIN_CFG.exaggeration > 0) ? TERRAIN_CFG.exaggeration : 8;
|
TERRAIN_CFG.exaggeration > 0) ? TERRAIN_CFG.exaggeration : 8;
|
||||||
// metres -> world units, including the vertical exaggeration
|
// metres -> world units, including the vertical exaggeration
|
||||||
@@ -1797,6 +1821,119 @@ function fetchElevationGrid() {
|
|||||||
next(0);
|
next(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadOuterElevTile(zx, zy) {
|
||||||
|
var id = 'elev/' + OUTER_DEM_Z + '/' + zx + '/' + zy;
|
||||||
|
function place(img) {
|
||||||
|
var cx = (OUTER_TILES_PER * zx - GROUND_BASE_X + outerHalfX) * OUTER_PX_PER_Z11;
|
||||||
|
var cy = (OUTER_TILES_PER * zy - GROUND_BASE_Y + outerHalfY) * OUTER_PX_PER_Z11;
|
||||||
|
outerElevCtx.drawImage(img, cx, cy);
|
||||||
|
}
|
||||||
|
return openTileDB().then(function (db) {
|
||||||
|
return tileDbGet(db, id).then(function (rec) {
|
||||||
|
if (rec && rec.blob) {
|
||||||
|
return decodeToBitmap(rec.blob).then(place, function () {
|
||||||
|
return fetchFresh(db);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return fetchFresh(db);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
function fetchFresh(db) {
|
||||||
|
return fetch(ELEV_TILE_URL + OUTER_DEM_Z + '/' + zx + '/' + zy + '.png')
|
||||||
|
.then(function (r) {
|
||||||
|
if (!r.ok) throw new Error('outer elev http ' + r.status);
|
||||||
|
return r.blob();
|
||||||
|
})
|
||||||
|
.then(function (blob) {
|
||||||
|
if (db) tileDbPut(db, { id: id, blob: blob, savedAt: Date.now() });
|
||||||
|
return decodeToBitmap(blob);
|
||||||
|
})
|
||||||
|
.then(place)
|
||||||
|
.catch(function (e) {
|
||||||
|
console.warn('[clouds3d] outer elev tile failed z' + OUTER_DEM_Z + '/' + zx + '/' + zy + ': ' + (e && e.message || e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchOuterElevationGrid() {
|
||||||
|
if (!outerElevCanvas || !outerElevCtx) return;
|
||||||
|
var jobs = [];
|
||||||
|
var zx0 = Math.floor((GROUND_BASE_X - outerHalfX) / OUTER_TILES_PER);
|
||||||
|
var zx1 = Math.floor((GROUND_BASE_X + outerHalfX) / OUTER_TILES_PER);
|
||||||
|
var zy0 = Math.floor((GROUND_BASE_Y - outerHalfY) / OUTER_TILES_PER);
|
||||||
|
var zy1 = Math.floor((GROUND_BASE_Y + outerHalfY) / OUTER_TILES_PER);
|
||||||
|
for (var zx = zx0; zx <= zx1; zx++)
|
||||||
|
for (var zy = zy0; zy <= zy1; zy++)
|
||||||
|
jobs.push([zx, zy]);
|
||||||
|
function next(i) {
|
||||||
|
if (i >= jobs.length) {
|
||||||
|
try {
|
||||||
|
outerElevData = outerElevCtx.getImageData(0, 0, outerW, outerH).data;
|
||||||
|
outerElevReady = true;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[clouds3d] outer DEM getImageData failed: ' + (e && e.message || e));
|
||||||
|
outerElevReady = false;
|
||||||
|
}
|
||||||
|
if (outerElevReady && terrainVbo && elevGl) {
|
||||||
|
terrainVbo = null;
|
||||||
|
buildTerrainMesh(elevGl, clouds3dState);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var n = Math.min(i + GROUND_QUEUE_CONC, jobs.length);
|
||||||
|
var batch = [];
|
||||||
|
for (var k = i; k < n; k++) batch.push(loadOuterElevTile(jobs[k][0], jobs[k][1]));
|
||||||
|
Promise.all(batch).then(function () {
|
||||||
|
setTimeout(function () { next(n); }, GROUND_QUEUE_YIELD_MS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildOuterElevationSystem() {
|
||||||
|
if (!OUTER_DEM_ON || outerElevCanvas) return;
|
||||||
|
var skirt = terrainSkirtUnits();
|
||||||
|
outerHalfX = groundTilesX() / 2 + skirt;
|
||||||
|
outerHalfY = groundTilesY() / 2 + skirt;
|
||||||
|
outerW = Math.max(16, Math.ceil(outerHalfX * 2 * OUTER_PX_PER_Z11));
|
||||||
|
outerH = Math.max(16, Math.ceil(outerHalfY * 2 * OUTER_PX_PER_Z11));
|
||||||
|
outerElevCanvas = document.createElement('canvas');
|
||||||
|
outerElevCanvas.width = outerW;
|
||||||
|
outerElevCanvas.height = outerH;
|
||||||
|
outerElevCtx = outerElevCanvas.getContext('2d');
|
||||||
|
outerElevCtx.fillStyle = 'rgb(128,0,0)';
|
||||||
|
outerElevCtx.fillRect(0, 0, outerW, outerH);
|
||||||
|
fetchOuterElevationGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
function outerHeightAt(x, z) {
|
||||||
|
if (!outerElevReady || !outerElevData) return null;
|
||||||
|
var px = Math.round((x + outerHalfX) * OUTER_PX_PER_Z11);
|
||||||
|
var py = Math.round((outerHalfY - z) * OUTER_PX_PER_Z11);
|
||||||
|
if (px < 0 || py < 0 || px >= outerW || py >= outerH) return null;
|
||||||
|
var idx = (py * outerW + px) * 4;
|
||||||
|
var m = outerElevData[idx] * 256 + outerElevData[idx + 1] + outerElevData[idx + 2] / 256 - 32768;
|
||||||
|
return m * ELEV_SCALE_UNITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function skirtColorForHeight(hUnits) {
|
||||||
|
var maxM = (typeof OUTER_DEM_CFG.maxMeters === 'number')
|
||||||
|
? Math.max(100, OUTER_DEM_CFG.maxMeters) : 2500;
|
||||||
|
var m = hUnits / ELEV_SCALE_UNITS;
|
||||||
|
var t = Math.max(0, Math.min(1, m / maxM));
|
||||||
|
var stops = [
|
||||||
|
[0.36, 0.42, 0.30],
|
||||||
|
[0.55, 0.52, 0.47],
|
||||||
|
[0.78, 0.75, 0.68],
|
||||||
|
[0.92, 0.90, 0.84]
|
||||||
|
];
|
||||||
|
var q = t * (stops.length - 1);
|
||||||
|
var i = Math.max(0, Math.min(stops.length - 2, Math.floor(q)));
|
||||||
|
var f = q - i;
|
||||||
|
var a = stops[i], b = stops[i + 1];
|
||||||
|
return [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f, a[2] + (b[2] - a[2]) * f];
|
||||||
|
}
|
||||||
|
|
||||||
function buildElevationSystem(glCtx) {
|
function buildElevationSystem(glCtx) {
|
||||||
if (elevCanvas) return;
|
if (elevCanvas) return;
|
||||||
elevGl = glCtx;
|
elevGl = glCtx;
|
||||||
@@ -1809,7 +1946,10 @@ function buildElevationSystem(glCtx) {
|
|||||||
elevCtx.fillRect(0, 0, elevCanvas.width, elevCanvas.height);
|
elevCtx.fillRect(0, 0, elevCanvas.width, elevCanvas.height);
|
||||||
elevTexture = elevGl.createTexture();
|
elevTexture = elevGl.createTexture();
|
||||||
uploadElevTexture();
|
uploadElevTexture();
|
||||||
if (TERRAIN_ON > 0.5) fetchElevationGrid();
|
if (TERRAIN_ON > 0.5) {
|
||||||
|
fetchElevationGrid();
|
||||||
|
buildOuterElevationSystem();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 3D math helpers (used by the terrain mesh + view-proj matrices) ----
|
// ---- 3D math helpers (used by the terrain mesh + view-proj matrices) ----
|
||||||
@@ -1988,10 +2128,17 @@ function buildTerrainMesh(gl, state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Build vertex list: 2 triangles per cell, 6 vertices each.
|
// Build vertex list: 2 triangles per cell, 6 vertices each.
|
||||||
// Attributes: position (3), normal (3), uv (2) = 8 floats per vertex.
|
// Attributes: position (3), normal (3), uv (2), color (3) = 11 floats.
|
||||||
var cols = gw - 1, rows = gh - 1;
|
var cols = gw - 1, rows = gh - 1;
|
||||||
var vCount = cols * rows * 6;
|
var SKIRT_UNITS = terrainSkirtUnits();
|
||||||
var verts = new Float32Array(vCount * 8);
|
var SKIRT_RINGS = Math.max(1, (typeof TERRAIN_CFG.edgeSkirtRings === 'number')
|
||||||
|
? TERRAIN_CFG.edgeSkirtRings : 24);
|
||||||
|
var SKIRT_NOISE = (typeof TERRAIN_CFG.edgeSkirtNoise === 'number')
|
||||||
|
? Math.max(0, Math.min(1, TERRAIN_CFG.edgeSkirtNoise)) : 0.28;
|
||||||
|
var skirtSegs = 2 * cols + 2 * rows;
|
||||||
|
var skirtVerts = SKIRT_UNITS > 0 ? skirtSegs * SKIRT_RINGS * 6 : 0;
|
||||||
|
var vCount = cols * rows * 6 + skirtVerts;
|
||||||
|
var verts = new Float32Array(vCount * 11);
|
||||||
var vi = 0;
|
var vi = 0;
|
||||||
// Per-vertex normals from central differences of the smoothed
|
// Per-vertex normals from central differences of the smoothed
|
||||||
// heightfield (Gouraud shading - no flat facets on the slopes).
|
// heightfield (Gouraud shading - no flat facets on the slopes).
|
||||||
@@ -2017,6 +2164,9 @@ function buildTerrainMesh(gl, state) {
|
|||||||
var _x = i * s - halfX, _z = halfY - j * s;
|
var _x = i * s - halfX, _z = halfY - j * s;
|
||||||
verts[vi++] = 0.5 - _x / tilesX;
|
verts[vi++] = 0.5 - _x / tilesX;
|
||||||
verts[vi++] = _z / tilesY + 0.5;
|
verts[vi++] = _z / tilesY + 0.5;
|
||||||
|
// Detailed terrain uses the basemap texture; the color attribute is
|
||||||
|
// only consumed by the outer skirt.
|
||||||
|
verts[vi++] = 1; verts[vi++] = 1; verts[vi++] = 1;
|
||||||
}
|
}
|
||||||
for (var j = 0; j < rows; j++) {
|
for (var j = 0; j < rows; j++) {
|
||||||
for (var i = 0; i < cols; i++) {
|
for (var i = 0; i < cols; i++) {
|
||||||
@@ -2027,6 +2177,110 @@ function buildTerrainMesh(gl, state) {
|
|||||||
setVertex(i0, j0); setVertex(i1, j1); setVertex(i1, j0);
|
setVertex(i0, j0); setVertex(i1, j1); setVertex(i1, j0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Outer skirt -------------------------------------------------
|
||||||
|
// Coarse outer terrain from reduced-resolution heightmap tiles. The
|
||||||
|
// skirt has no basemap texture; its colour comes from vertex heights.
|
||||||
|
function pushRaw(x, y, z, nx, ny, nz, u, v, cr, cg, cb) {
|
||||||
|
verts[vi++] = x; verts[vi++] = y; verts[vi++] = z;
|
||||||
|
verts[vi++] = nx; verts[vi++] = ny; verts[vi++] = nz;
|
||||||
|
verts[vi++] = u; verts[vi++] = v;
|
||||||
|
verts[vi++] = cr; verts[vi++] = cg; verts[vi++] = cb;
|
||||||
|
}
|
||||||
|
function uvFromXZ(x, z) {
|
||||||
|
return [0.5 - x / tilesX, z / tilesY + 0.5];
|
||||||
|
}
|
||||||
|
function skirtHeight(base, x, z, t) {
|
||||||
|
var real = outerHeightAt(x, z);
|
||||||
|
if (real === null) {
|
||||||
|
if (SKIRT_NOISE <= 0) return base;
|
||||||
|
var amp = SKIRT_NOISE * (0.15 + 0.85 * t);
|
||||||
|
var n = Math.sin(x * 0.11 + t * 2.1) * Math.cos(z * 0.09 - t * 1.7)
|
||||||
|
+ 0.5 * Math.sin((x * 0.7 + z * 0.5) * 0.13 + t * 3.3);
|
||||||
|
return base + n * amp;
|
||||||
|
}
|
||||||
|
if (t <= 0) return base;
|
||||||
|
var blend = Math.min(1, t * 2.0);
|
||||||
|
return base + (real - base) * blend;
|
||||||
|
}
|
||||||
|
function quadNormal(x0,y0,z0, x1,y1,z1, x2,y2,z2) {
|
||||||
|
var ax = x1 - x0, ay = y1 - y0, az = z1 - z0;
|
||||||
|
var bx = x2 - x0, by = y2 - y0, bz = z2 - z0;
|
||||||
|
var nx = ay * bz - az * by;
|
||||||
|
var ny = az * bx - ax * bz;
|
||||||
|
var nz = ax * by - ay * bx;
|
||||||
|
var nl = Math.hypot(nx, ny, nz);
|
||||||
|
if (!(nl > 1e-6)) return [0, 1, 0];
|
||||||
|
nx /= nl; ny /= nl; nz /= nl;
|
||||||
|
if (ny < 0) { nx = -nx; ny = -ny; nz = -nz; }
|
||||||
|
return [nx, ny, nz];
|
||||||
|
}
|
||||||
|
function ringT(k, rings) {
|
||||||
|
var u = k / rings;
|
||||||
|
return Math.pow(u, 1.35);
|
||||||
|
}
|
||||||
|
function outwardFor(i, j) {
|
||||||
|
var ox = 0, oz = 0;
|
||||||
|
if (j === 0) oz = 1;
|
||||||
|
else if (j === gh - 1) oz = -1;
|
||||||
|
if (i === 0) ox = -1;
|
||||||
|
else if (i === gw - 1) ox = 1;
|
||||||
|
if (ox !== 0 && oz !== 0) {
|
||||||
|
var inv = 1 / Math.SQRT2;
|
||||||
|
ox *= inv; oz *= inv;
|
||||||
|
}
|
||||||
|
return [ox, oz];
|
||||||
|
}
|
||||||
|
function boundaryLoop() {
|
||||||
|
var loop = [];
|
||||||
|
function add(i, j) {
|
||||||
|
var o = outwardFor(i, j);
|
||||||
|
loop.push({
|
||||||
|
x: i * s - halfX,
|
||||||
|
z: halfY - j * s,
|
||||||
|
y: hAt(i, j),
|
||||||
|
ox: o[0], oz: o[1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (var i = 0; i < gw; i++) add(i, 0);
|
||||||
|
for (var j = 1; j < gh; j++) add(gw - 1, j);
|
||||||
|
for (var i = gw - 2; i >= 0; i--) add(i, gh - 1);
|
||||||
|
for (var j = gh - 2; j >= 1; j--) add(0, j);
|
||||||
|
return loop;
|
||||||
|
}
|
||||||
|
function pushSkirtLoop() {
|
||||||
|
if (SKIRT_UNITS <= 0) return;
|
||||||
|
var loop = boundaryLoop();
|
||||||
|
for (var seg = 0; seg < loop.length; seg++) {
|
||||||
|
var a = loop[seg], b = loop[(seg + 1) % loop.length];
|
||||||
|
for (var k = 0; k < SKIRT_RINGS; k++) {
|
||||||
|
var t0 = ringT(k, SKIRT_RINGS);
|
||||||
|
var t1 = ringT(k + 1, SKIRT_RINGS);
|
||||||
|
var d0 = SKIRT_UNITS * t0, d1 = SKIRT_UNITS * t1;
|
||||||
|
var x00 = a.x + a.ox * d0, z00 = a.z + a.oz * d0;
|
||||||
|
var x01 = b.x + b.ox * d0, z01 = b.z + b.oz * d0;
|
||||||
|
var x10 = a.x + a.ox * d1, z10 = a.z + a.oz * d1;
|
||||||
|
var x11 = b.x + b.ox * d1, z11 = b.z + b.oz * d1;
|
||||||
|
var y00 = skirtHeight(a.y, x00, z00, t0);
|
||||||
|
var y01 = skirtHeight(b.y, x01, z01, t0);
|
||||||
|
var y10 = skirtHeight(a.y, x10, z10, t1);
|
||||||
|
var y11 = skirtHeight(b.y, x11, z11, t1);
|
||||||
|
var uv00 = uvFromXZ(x00, z00), uv01 = uvFromXZ(x01, z01);
|
||||||
|
var uv10 = uvFromXZ(x10, z10), uv11 = uvFromXZ(x11, z11);
|
||||||
|
var nrm = quadNormal(x00,y00,z00, x01,y01,z01, x10,y10,z10);
|
||||||
|
var nx = nrm[0], ny = nrm[1], nz = nrm[2];
|
||||||
|
var c00 = skirtColorForHeight(y00), c01 = skirtColorForHeight(y01);
|
||||||
|
var c10 = skirtColorForHeight(y10), c11 = skirtColorForHeight(y11);
|
||||||
|
pushRaw(x00, y00, z00, nx, ny, nz, uv00[0], uv00[1], c00[0], c00[1], c00[2]);
|
||||||
|
pushRaw(x01, y01, z01, nx, ny, nz, uv01[0], uv01[1], c01[0], c01[1], c01[2]);
|
||||||
|
pushRaw(x11, y11, z11, nx, ny, nz, uv11[0], uv11[1], c11[0], c11[1], c11[2]);
|
||||||
|
pushRaw(x00, y00, z00, nx, ny, nz, uv00[0], uv00[1], c00[0], c00[1], c00[2]);
|
||||||
|
pushRaw(x11, y11, z11, nx, ny, nz, uv11[0], uv11[1], c11[0], c11[1], c11[2]);
|
||||||
|
pushRaw(x10, y10, z10, nx, ny, nz, uv10[0], uv10[1], c10[0], c10[1], c10[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pushSkirtLoop();
|
||||||
var buf = gl.createBuffer();
|
var buf = gl.createBuffer();
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);
|
||||||
@@ -5517,7 +5771,7 @@ window.clouds3dRender = function(target) {
|
|||||||
gl.uniform1f(terr.uEdgeFadeTiles, EDGE_FADE_TILES);
|
gl.uniform1f(terr.uEdgeFadeTiles, EDGE_FADE_TILES);
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, terr.vbo);
|
gl.bindBuffer(gl.ARRAY_BUFFER, terr.vbo);
|
||||||
var STRIDE = 8 * 4; // 8 floats per vertex
|
var STRIDE = 11 * 4; // 11 floats per vertex
|
||||||
if (terr.aPos >= 0) {
|
if (terr.aPos >= 0) {
|
||||||
gl.enableVertexAttribArray(terr.aPos);
|
gl.enableVertexAttribArray(terr.aPos);
|
||||||
gl.vertexAttribPointer(terr.aPos, 3, gl.FLOAT, false, STRIDE, 0);
|
gl.vertexAttribPointer(terr.aPos, 3, gl.FLOAT, false, STRIDE, 0);
|
||||||
@@ -5530,6 +5784,10 @@ window.clouds3dRender = function(target) {
|
|||||||
gl.enableVertexAttribArray(terr.aUV);
|
gl.enableVertexAttribArray(terr.aUV);
|
||||||
gl.vertexAttribPointer(terr.aUV, 2, gl.FLOAT, false, STRIDE, 24);
|
gl.vertexAttribPointer(terr.aUV, 2, gl.FLOAT, false, STRIDE, 24);
|
||||||
}
|
}
|
||||||
|
if (terr.aColor >= 0) {
|
||||||
|
gl.enableVertexAttribArray(terr.aColor);
|
||||||
|
gl.vertexAttribPointer(terr.aColor, 3, gl.FLOAT, false, STRIDE, 32);
|
||||||
|
}
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, terr.vertCount);
|
gl.drawArrays(gl.TRIANGLES, 0, terr.vertCount);
|
||||||
|
|
||||||
// Wireframe overlay: draw the mesh edges as bright lines on top
|
// Wireframe overlay: draw the mesh edges as bright lines on top
|
||||||
@@ -5545,6 +5803,7 @@ window.clouds3dRender = function(target) {
|
|||||||
gl.enableVertexAttribArray(terr.aPos);
|
gl.enableVertexAttribArray(terr.aPos);
|
||||||
gl.disableVertexAttribArray(terr.aNormal);
|
gl.disableVertexAttribArray(terr.aNormal);
|
||||||
gl.disableVertexAttribArray(terr.aUV);
|
gl.disableVertexAttribArray(terr.aUV);
|
||||||
|
if (terr.aColor >= 0) gl.disableVertexAttribArray(terr.aColor);
|
||||||
gl.vertexAttribPointer(terr.aPos, 3, gl.FLOAT, false, 0, 0);
|
gl.vertexAttribPointer(terr.aPos, 3, gl.FLOAT, false, 0, 0);
|
||||||
gl.depthMask(false);
|
gl.depthMask(false);
|
||||||
gl.drawArrays(gl.LINES, 0, terr.wireVertCount);
|
gl.drawArrays(gl.LINES, 0, terr.wireVertCount);
|
||||||
|
|||||||
Reference in new issue
Block a user