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
Vendored
+265
-6
@@ -528,6 +528,7 @@ var TERRAIN_VERT_SRC = [
|
||||
'attribute vec3 aPos;',
|
||||
'attribute vec3 aNormal;',
|
||||
'attribute vec2 aUV;',
|
||||
'attribute vec3 aColor;',
|
||||
'uniform mat4 uView;',
|
||||
'uniform mat4 uProj;',
|
||||
'uniform vec3 uCamPos;',
|
||||
@@ -538,6 +539,7 @@ var TERRAIN_VERT_SRC = [
|
||||
'varying float vY;',
|
||||
'varying vec3 vViewPos;',
|
||||
'varying vec3 vWorld;',
|
||||
'varying vec3 vColor;',
|
||||
'void main() {',
|
||||
'vec3 pos = aPos;',
|
||||
'vec3 n = aNormal;',
|
||||
@@ -551,6 +553,7 @@ var TERRAIN_VERT_SRC = [
|
||||
'}',
|
||||
'vNormal = n;',
|
||||
'vUV = aUV;',
|
||||
'vColor = aColor;',
|
||||
'vY = pos.y;',
|
||||
'vWorld = pos;',
|
||||
'vec4 vp = uView * vec4(pos, 1.0);',
|
||||
@@ -677,6 +680,7 @@ var TERRAIN_FRAG_SRC = [
|
||||
'varying float vY;',
|
||||
'varying vec3 vViewPos;',
|
||||
'varying vec3 vWorld;',
|
||||
'varying vec3 vColor;',
|
||||
CLOUD_SAMPLER_GLSL,
|
||||
'void main() {',
|
||||
'vec2 gUv = vUV;',
|
||||
@@ -688,6 +692,9 @@ var TERRAIN_FRAG_SRC = [
|
||||
'float edgeX = min(gUv.x, 1.0 - gUv.x) * uGroundSize.x;',
|
||||
'float edgeZ = min(gUv.y, 1.0 - gUv.y) * uGroundSize.y;',
|
||||
'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 n = normalize(vNormal);',
|
||||
// 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) +
|
||||
// hemispherical skylight + moonlight. No hand-picked day/night
|
||||
// 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));',
|
||||
// Cloud shadow: march from the surface point towards the sun
|
||||
// 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 terrainAnormal = terrainProg ? gl.getAttribLocation(terrainProg, 'aNormal') : -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 terrainUproj = terrainProg ? gl.getUniformLocation(terrainProg, 'uProj') : null;
|
||||
var terrainUground = terrainProg ? gl.getUniformLocation(terrainProg, 'uGround') : null;
|
||||
@@ -962,6 +971,7 @@ function init(canvas) {
|
||||
terrain: {
|
||||
prog: terrainProg,
|
||||
aPos: terrainApos, aNormal: terrainAnormal, aUV: terrainAuv,
|
||||
aColor: terrainAcolor,
|
||||
uView: terrainUview, uProj: terrainUproj,
|
||||
uGround: terrainUground, uSunDir: terrainUsundir,
|
||||
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')
|
||||
? Math.max(0.5, TERRAIN_CFG.edgeFadeTiles) : 4.0;
|
||||
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' &&
|
||||
TERRAIN_CFG.exaggeration > 0) ? TERRAIN_CFG.exaggeration : 8;
|
||||
// metres -> world units, including the vertical exaggeration
|
||||
@@ -1797,6 +1821,119 @@ function fetchElevationGrid() {
|
||||
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) {
|
||||
if (elevCanvas) return;
|
||||
elevGl = glCtx;
|
||||
@@ -1809,7 +1946,10 @@ function buildElevationSystem(glCtx) {
|
||||
elevCtx.fillRect(0, 0, elevCanvas.width, elevCanvas.height);
|
||||
elevTexture = elevGl.createTexture();
|
||||
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) ----
|
||||
@@ -1988,10 +2128,17 @@ function buildTerrainMesh(gl, state) {
|
||||
}
|
||||
}
|
||||
// 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 vCount = cols * rows * 6;
|
||||
var verts = new Float32Array(vCount * 8);
|
||||
var SKIRT_UNITS = terrainSkirtUnits();
|
||||
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;
|
||||
// Per-vertex normals from central differences of the smoothed
|
||||
// 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;
|
||||
verts[vi++] = 0.5 - _x / tilesX;
|
||||
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 i = 0; i < cols; i++) {
|
||||
@@ -2027,6 +2177,110 @@ function buildTerrainMesh(gl, state) {
|
||||
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();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
||||
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.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) {
|
||||
gl.enableVertexAttribArray(terr.aPos);
|
||||
gl.vertexAttribPointer(terr.aPos, 3, gl.FLOAT, false, STRIDE, 0);
|
||||
@@ -5530,6 +5784,10 @@ window.clouds3dRender = function(target) {
|
||||
gl.enableVertexAttribArray(terr.aUV);
|
||||
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);
|
||||
|
||||
// Wireframe overlay: draw the mesh edges as bright lines on top
|
||||
@@ -5545,6 +5803,7 @@ window.clouds3dRender = function(target) {
|
||||
gl.enableVertexAttribArray(terr.aPos);
|
||||
gl.disableVertexAttribArray(terr.aNormal);
|
||||
gl.disableVertexAttribArray(terr.aUV);
|
||||
if (terr.aColor >= 0) gl.disableVertexAttribArray(terr.aColor);
|
||||
gl.vertexAttribPointer(terr.aPos, 3, gl.FLOAT, false, 0, 0);
|
||||
gl.depthMask(false);
|
||||
gl.drawArrays(gl.LINES, 0, terr.wireVertCount);
|
||||
|
||||
Reference in new issue
Block a user