UNPKG

rabbit-hole

Version:

A volumetric terrain engine for three.js.

609 lines (433 loc) 23.7 kB
/** * rabbit-hole v0.0.0 build Mar 23 2016 * https://github.com/vanruesc/rabbit-hole * Copyright 2016 Raoul van Rüschen, Zlib */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three')) : typeof define === 'function' && define.amd ? define(['exports', 'three'], factory) : (factory((global.RABBITHOLE = global.RABBITHOLE || {}),global.THREE)); }(this, function (exports,THREE) { 'use strict'; THREE = 'default' in THREE ? THREE['default'] : THREE; let shader = { fragment: "#ifdef USE_FOG\r\n\r\n\t#define LOG2 1.442695\r\n\t#define whiteCompliment(a) (1.0 - saturate(a))\r\n\r\n\tuniform vec3 fogColor;\r\n\r\n\t#ifdef FOG_EXP2\r\n\r\n\t\tuniform float fogDensity;\r\n\r\n\t#else\r\n\r\n\t\tuniform float fogNear;\r\n\t\tuniform float fogFar;\r\n\r\n\t#endif\r\n\r\n#endif\r\n\r\n#ifdef USE_LOGDEPTHBUF\r\n\r\n\tuniform float logDepthBufFC;\r\n\r\n\t#ifdef USE_LOGDEPTHBUF_EXT\r\n\r\n\t\tvarying float vFragDepth;\r\n\r\n\t#endif\r\n\r\n#endif\r\n\r\nuniform sampler2D tHeight;\r\n\r\nuniform float worldSize;\r\n\r\nvarying vec3 vNormal;\r\nvarying vec3 vPosition;\r\nvarying float vMorphFactor;\r\nvarying vec2 vUvs[3];\r\n\r\nfloat getHeight() {\r\n\r\n\t// Sample multiple times to get more detail.\r\n\tfloat h = worldSize * texture2D(tHeight, vUvs[0]).r;\r\n\th += 64.0 * texture2D(tHeight, vUvs[1]).r;\r\n\th += 4.0 * texture2D(tHeight, vUvs[2]).r;\r\n\r\n\t// Square the height, leads to more rocky looking terrain.\r\n\treturn h * h / 2000.0;\r\n\r\n}\r\n\r\nvec3 getNormal() {\r\n\r\n\t/* Differentiate the position vector (this will give us two vectors perpendicular to the surface)\r\n\t * Before differentiating, add the displacement based on the height from the height map. By doing this\r\n\t * calculation here, rather than in the vertex shader, we get a per-fragment calculated normal, rather\r\n\t * than a per-vertex normal. This improves the look of distant low-vertex terrain.\r\n\t */\r\n\r\n\tfloat height = getHeight();\r\n\tvec3 p = vec3(vPosition.xy, height);\r\n\tvec3 dPositiondx = dFdx(p);\r\n\tvec3 dPositiondy = dFdy(p);\r\n\r\n\t// The normal is the cross product of the differentials.\r\n\treturn normalize(cross(dPositiondx, dPositiondy));\r\n\r\n}\r\n\r\nvoid main() {\r\n\r\n\t// Base color\r\n\tvec3 light = vec3(80.0, 150.0, 50.0);\r\n\t//vec3 color = colorForScale();\r\n\tvec3 color = vec3(0.27, 0.27, 0.17);\r\n\t//color = vec3(vMorphFactor);\r\n\r\n\tvec3 normal = getNormal();\r\n\r\n\t// Incident light.\r\n\tfloat incidence = dot(normalize(light - vPosition), normal);\r\n\tincidence = clamp(incidence, 0.0, 1.0);\r\n\tincidence = pow(incidence, 0.02);\r\n\tcolor = mix(vec3(0, 0, 0), color, incidence);\r\n\r\n\t// Mix in specular light.\r\n\tvec3 halfVector = normalize(normalize(cameraPosition - vPosition) + normalize(light - vPosition));\r\n\tfloat specular = dot(normal, halfVector);\r\n\tspecular = max(0.0, specular);\r\n\tspecular = pow(specular, 25.0);\r\n\tcolor = mix(color, vec3(0, 1.0, 1.0), 0.5 * specular);\r\n\r\n\t// Add more specular light for fun.\r\n\tvec3 light2 = vec3(420.0, 510.0, 30.0);\r\n\thalfVector = normalize(normalize(cameraPosition - vPosition) + normalize(light2 - vPosition));\r\n\tspecular = dot(normal, halfVector);\r\n\tspecular = max(0.0, specular);\r\n\tspecular = pow(specular, 3.0);\r\n\tcolor = mix(color, vec3(1.0, 0.3, 0), 0.5 * specular);\r\n\r\n\tvec3 light3 = vec3(0.0, 0.0, 1000.0);\r\n\thalfVector = normalize(normalize(cameraPosition - vPosition) + normalize(light3 - vPosition));\r\n\tspecular = dot(normal, halfVector);\r\n\tspecular = max(0.0, specular);\r\n\tspecular = pow(specular, 130.0);\r\n\tcolor = mix(color, vec3(1.0, 0.5, 0), specular);\r\n\r\n\t// Add height fog.\r\n\tfloat heightFogFactor = clamp(1.0 - vPosition.z / 25.0, 0.0, 1.0);\r\n\theightFogFactor = pow(heightFogFactor, 5.4);\r\n\tcolor = mix(color, vec3(1.0, 0.9, 0.8), heightFogFactor);\r\n\r\n\t#if defined(USE_LOGDEPTHBUF) && defined(USE_LOGDEPTHBUF_EXT)\r\n\r\n\t\tgl_FragDepthEXT = log2(vFragDepth) * logDepthBufFC * 0.5;\r\n\r\n\t#endif\r\n\r\n\t#ifdef USE_FOG\r\n\r\n\t\t#ifdef USE_LOGDEPTHBUF_EXT\r\n\r\n\t\t\tfloat depth = gl_FragDepthEXT / gl_FragCoord.w;\r\n\r\n\t\t#else\r\n\r\n\t\t\tfloat depth = gl_FragCoord.z / gl_FragCoord.w;\r\n\r\n\t\t#endif\r\n\r\n\t\t#ifdef FOG_EXP2\r\n\r\n\t\t\tfloat fogFactor = whiteCompliment(exp2(-fogDensity * fogDensity * depth * depth * LOG2));\r\n\r\n\t\t#else\r\n\r\n\t\t\tfloat fogFactor = smoothstep(fogNear, fogFar, depth);\r\n\r\n\t\t#endif\r\n\r\n\t\tcolor = mix(color, fogColor, fogFactor);\r\n\r\n\t#endif\r\n\r\n\tgl_FragColor = vec4(color, 1.0);\r\n\r\n}\r\n", vertex: "#define EPSILON 1e-6\r\n\r\n#ifdef USE_LOGDEPTHBUF\r\n\r\n\tuniform float logDepthBufFC;\r\n\r\n\t#ifdef USE_LOGDEPTHBUF_EXT\r\n\r\n\t\tvarying float vFragDepth;\r\n\r\n\t#endif\r\n\r\n#endif\r\n\r\nuniform sampler2D tHeight;\r\n\r\nuniform vec3 globalOffset;\r\nuniform vec2 tileOffset;\r\n\r\nuniform float worldSize;\r\nuniform float scale;\r\nuniform int edgeMorph;\r\n\r\nvarying vec3 vNormal;\r\nvarying vec3 vPosition;\r\nvarying float vMorphFactor;\r\nvarying vec2 vUvs[3];\r\n\r\nconst int EGDE_MORPH_TOP = 1;\r\nconst int EGDE_MORPH_LEFT = 2;\r\nconst int EGDE_MORPH_BOTTOM = 4;\r\nconst int EGDE_MORPH_RIGHT = 8;\r\n\r\nconst float MORPH_REGION = 0.3;\r\nconst float MORPH_REGION_INV = 0.7;\r\n\r\nfloat getHeight(vec3 p) {\r\n\r\n\tfloat lod = 0.0;//log2(scale) - 6.0;\r\n\tvec2 st = p.xy / worldSize;\r\n\r\n\t// Sample multiple times to get more detail.\r\n\tfloat h = worldSize * texture2DLod(tHeight, st, lod).r;\r\n\t// todo: Adjust to worldsize.\r\n\th += 64.0 * texture2DLod(tHeight, 16.0 * st, lod).r;\r\n\th += 4.0 * texture2DLod(tHeight, 256.0 * st, lod).r;\r\n\r\n\t// Square the height, leads to more rocky looking terrain.\r\n\treturn h * h / 2000.0;\r\n\t//return h / 10.0;\r\n\r\n}\r\n\r\nvec3 getNormal(float h) {\r\n\r\n\t// Build 2 vectors that are perpendicular to the surface normal.\r\n\t//float delta = 1024.0 / 4.0;\r\n\tfloat delta = (vMorphFactor + 1.0) * scale / RESOLUTION;\r\n\tvec3 dA = delta * normalize(cross(normal.yzx, normal));\r\n\tvec3 dB = delta * normalize(cross(dA, normal));\r\n\tvec3 p = vPosition;\r\n\tvec3 pA = vPosition + dA;\r\n\tvec3 pB = vPosition + dB;\r\n\r\n\t// Get the height at those points.\r\n\tfloat hA = getHeight(pA);\r\n\tfloat hB = getHeight(pB);\r\n\r\n\t// Update the points with the new height and calculate the normal.\r\n\tp += normal * h;\r\n\tpA += normal * hA;\r\n\tpB += normal * hB;\r\n\r\n\treturn normalize(cross(pB - p, pA - p));\r\n\r\n}\r\n\r\n/**\r\n * Poor man's bitwise &.\r\n */\r\n\r\nbool edgePresent(int edge) {\r\n\r\n\tint e = edgeMorph / edge;\r\n\r\n\treturn (2 * (e / 2) != e);\r\n\r\n}\r\n\r\n/**\r\n * At the edges of tiles morph the vertices if they are joining onto a higher layer.\r\n */\r\n\r\nfloat calculateMorph(vec3 p) {\r\n\r\n\tfloat morphFactor = 0.0;\r\n\r\n\tif(edgePresent(EGDE_MORPH_TOP) && p.y >= MORPH_REGION_INV) {\r\n\r\n\t\tfloat m = 1.0 - clamp((1.0 - p.y) / MORPH_REGION, 0.0, 1.0);\r\n\t\tmorphFactor = max(m, morphFactor);\r\n\r\n\t}\r\n\r\n\tif(edgePresent(EGDE_MORPH_LEFT) && p.x <= MORPH_REGION) {\r\n\r\n\t\tfloat m = 1.0 - clamp(p.x / MORPH_REGION, 0.0, 1.0);\r\n\t\tmorphFactor = max(m, morphFactor);\r\n\r\n\t}\r\n\r\n\tif(edgePresent(EGDE_MORPH_BOTTOM) && p.y <= MORPH_REGION) {\r\n\r\n\t\tfloat m = 1.0 - clamp(p.y / MORPH_REGION, 0.0, 1.0);\r\n\t\tmorphFactor = max(m, morphFactor);\r\n\r\n\t}\r\n\r\n\tif(edgePresent(EGDE_MORPH_RIGHT) && p.x >= MORPH_REGION_INV) {\r\n\r\n\t\tfloat m = 1.0 - clamp((1.0 - p.x) / MORPH_REGION, 0.0, 1.0);\r\n\t\tmorphFactor = max(m, morphFactor);\r\n\r\n\t}\r\n\r\n\treturn morphFactor;\r\n\r\n}\r\n\r\nvoid main() {\r\n\r\n\t/* Morph factor indicates the proximity to the next level.\r\n\t *\r\n\t * 0.0 = this level.\r\n\t * 1.0 = next level.\r\n\t */\r\n\r\n\tvMorphFactor = calculateMorph(position);\r\n\r\n\t// Move into correct place.\r\n\tvPosition = scale * position + vec3(tileOffset, 0.0) + globalOffset;\r\n\r\n\t// Snap to grid.\r\n\tfloat grid = scale / RESOLUTION;\r\n\tvPosition = floor(vPosition / grid) * grid;\r\n\r\n\t// Morph between zoom layers.\r\n\tif(vMorphFactor > 0.0) {\r\n\r\n\t\t// Get position that we would have if we were on higher level grid.\r\n\t\tgrid *= 2.0;\r\n\t\tvec3 position2 = floor(vPosition / grid) * grid;\r\n\r\n\t\t// Linearly interpolate the two, depending on morph factor.\r\n\t\tvPosition = mix(vPosition, position2, vMorphFactor);\r\n\r\n\t}\r\n\r\n\t// Get height and calculate normal.\r\n\tfloat height = getHeight(vPosition);\r\n\tvPosition += normal * height;\r\n\t//vNormal = getNormal(height);\r\n\r\n\t// Allow pre-fetching of texels.\r\n\tvUvs[0] = vPosition.xy / worldSize;\r\n\tvUvs[1] = 16.0 * vUvs[0];\r\n\tvUvs[2] = 256.0 * vUvs[0];\r\n\r\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1.0);\r\n\r\n\t#ifdef USE_LOGDEPTHBUF\r\n\r\n\t\tgl_Position.z = log2(max(EPSILON, gl_Position.w + 1.0)) * logDepthBufFC;\r\n\r\n\t\t#ifdef USE_LOGDEPTHBUF_EXT\r\n\r\n\t\t\tvFragDepth = 1.0 + gl_Position.w;\r\n\r\n\t\t#else\r\n\r\n\t\t\tgl_Position.z = (gl_Position.z - 1.0) * gl_Position.w;\r\n\r\n\t\t#endif\r\n\r\n\t#endif\r\n\r\n}\r\n" }; /** * A heightmap shader material for LOD terrain rendering. * * @class HeightFieldMaterial * @constructor * @extends ShaderMaterial */ class HeightFieldMaterial extends THREE.ShaderMaterial { constructor(resolution) { super({ defines: { RESOLUTION: (resolution !== undefined) ? resolution.toFixed(1) : "128.0" }, uniforms: { tDiffuse: {type: "t", value: null}, tHeight: {type: "t", value: null}, globalOffset: {type: "v3", value: null}, tileOffset: {type: "v2", value: new THREE.Vector2()}, worldSize: {type: "f", value: 1024.0}, scale: {type: "f", value: 1.0}, edgeMorph: {type: "i", value: 1} }, fragmentShader: shader.fragment, vertexShader: shader.vertex, extensions: { derivatives: true } }); } } /** * Tiles that sit next to a tile of a greater scale need to have their edges morphed to avoid * edges. Mark which edges need morphing using flags. These flags are then read by the vertex * shader which performs the actual morph * * @property EDGE * @type Object * @private * @static * @final */ const EDGE = { NONE: 0, TOP: 1, LEFT: 2, BOTTOM: 4, RIGHT: 8 }; /** * A heightmap-based terrain. * * @class HeightField * @constructor * @extends Object3D */ class HeightField extends THREE.Object3D { constructor(heightmap, worldSize, levels, resolution) { super(); this.worldSize = (worldSize !== undefined) ? worldSize : 1024.0; this.levels = (levels !== undefined) ? levels : 6; this.resolution = (resolution !== undefined) ? resolution : 128; // Offset is used to re-center the terrain, this way we get the greates detail // nearest to the camera. In the future, should calculate required detail level per tile. this.offset = new THREE.Vector3(); this.heightmap = heightmap; // Create geometry that we'll use for each tile, just a standard plane. this.geometry = new THREE.PlaneBufferGeometry(1, 1, this.resolution, this.resolution); // Place origin at bottom left corner, rather than center. let m = new THREE.Matrix4(); m.makeTranslation(0.5, 0.5, 0); this.geometry.applyMatrix(m); this.createLayers(); } /** * Creates the LOD layers, a collection of tiles. * * @method createLayers * @private */ createLayers() { let initialScale = this.worldSize / Math.pow(2, this.levels); /* * The center layer. * * +---+---+ * | O | O | * +---+---+ * | O | O | * +---+---+ * */ this.createTile(-initialScale, -initialScale, initialScale, EDGE.NONE); this.createTile(-initialScale, 0, initialScale, EDGE.NONE); this.createTile(0, 0, initialScale, EDGE.NONE); this.createTile(0, -initialScale, initialScale, EDGE.NONE); /* * Quad tree of tiles, with smallest quads in the center. * * Each added layer consists of the following tiles (A), with * the tiles in the middle being created in previous layers. * * +---+---+---+---+ * | A | A | A | A | * +---+---+---+---+ * | A | | | A | * +---+---+---+---+ * | A | | | A | * +---+---+---+---+ * | A | A | A | A | * +---+---+---+---+ * */ let scale; for(scale = initialScale; scale < this.worldSize; scale *= 2) { this.createTile(-2 * scale, -2 * scale, scale, EDGE.BOTTOM | EDGE.LEFT); this.createTile(-2 * scale, -scale, scale, EDGE.LEFT); this.createTile(-2 * scale, 0, scale, EDGE.LEFT); this.createTile(-2 * scale, scale, scale, EDGE.TOP | EDGE.LEFT); this.createTile(-scale, -2 * scale, scale, EDGE.BOTTOM); // The tile missing here is in the previous layer. this.createTile(-scale, scale, scale, EDGE.TOP); this.createTile(0, -2 * scale, scale, EDGE.BOTTOM); // The tile missing here is in the previous layer. this.createTile(0, scale, scale, EDGE.TOP); this.createTile(scale, -2 * scale, scale, EDGE.BOTTOM | EDGE.RIGHT); this.createTile(scale, -scale, scale, EDGE.RIGHT); this.createTile(scale, 0, scale, EDGE.RIGHT); this.createTile(scale, scale, scale, EDGE.TOP | EDGE.RIGHT); } } /** * Creates a tile with a specific offset and resolution. * * @method createTile * @private * @param * @param * @param * @param */ createTile(x, y, scale, edgeMorph) { let material = new HeightFieldMaterial(this.resolution); material.uniforms.tHeight.value = this.heightmap; material.uniforms.globalOffset.value = this.offset; material.uniforms.tileOffset.value.set(x, y); material.uniforms.worldSize.value = this.worldSize; material.uniforms.scale.value = scale; material.uniforms.edgeMorph.value = edgeMorph; this.add(new THREE.Mesh(this.geometry, material)); } } /** * Precomputed cube edges. * * Used for computing the centroid of each boundary cell. * * @property CUBE_EDGES * @type Int32Array * @private * @static * @final */ const CUBE_EDGES = (function() { let i, j, k, l; let edges = new Int32Array(24); for(i = 0; i < 8; ++i) { for(j = 1; j <= 4; j <<= 1) { l = i ^ j; if(i <= l) { edges[k++] = i; edges[k++] = l; } } } return edges; }()); /** * Precomputed edge intersection table. * * This is a 2 ^ (cube configuration) -> 2 ^ (edge configuration) map. * There is one entry for each possible cube configuration, and the * output is a 12-bit vector enumerating all edges crossing the 0-level. * * @property EDGE_TABLE * @type Int32Array * @private * @static * @final */ const EDGE_TABLE = (function() { let i, j, k, l, m; let table = new Int32Array(256); for(i = 0, k = 0; i < 256; ++i) { for(j = 0; j < 24; j += 2) { l = !!(i & (1 << CUBE_EDGES[j])); m = !!(i & (1 << CUBE_EDGES[j + 1])); k |= (l !== m) ? (1 << (j >> 1)) : 0; } table[i] = k; } return table; }()); /** * Surface net algorithm for isosurface extraction. * * Original code by Mikola Lysenko. * Based on: S.F. Gibson, "Constrained Elastic Surface Nets". (1998) MERL Tech Report. * * @class SurfaceNet * @constructor * @extends BufferGeometry * @param {Float32Array} dimensions - The dimensions of the isosurface geometry. * @param {Function} potential - The potential function that describes each point inside the 3D bounds of the isosurface. * @param {Array} [bounds] - The bounds of the isosurface geometry. */ class SurfaceNet extends THREE.BufferGeometry { constructor(dimensions, potential, bounds) { super(); /** * The dimensions of the isosurface geometry. * * @property bounds * @type Float32Array * @private */ this._dimensions = (dimensions !== undefined) ? dimensions : new Float32Array(3); /** * The potential function that describes each point * inside the 3D bounds of the isosurface. * * @property potential * @type Function * @private */ this._potential = (potential !== undefined) ? potential : function(x, y, z) { return 0; }; /** * The bounds of the isosurface geometry. * * @property bounds * @type Array * @private */ this._bounds = (bounds !== undefined) ? bounds : [[0, 0, 0], this.dimensions]; this.update(); } /** * The dimensions of the isosurface geometry. * * @property bounds * @type Float32Array */ get dimensions() { return this._dimensions; } set dimensions(x) { this._dimensions = x; this.update(); } /** * The potential function that describes each point * inside the 3D bounds of the isosurface. * * @property potential * @type Function */ get potential() { return this._potential; } set potential(x) { this._potential = x; this.update(); } /** * The bounds of the isosurface geometry. * * @property bounds * @type Array * @default [[0, 0, 0], dimensions] */ get bounds() { return this._bounds; } set bounds(x) { this._bounds = x; this.update(); } /** * Constructs a surface net from the current data. * * @method update * @private */ update() { let x = new Uint32Array(3); let R = new Float32Array([1, (this.dimensions[0] + 1), (this.dimensions[0] + 1) * (this.dimensions[1] + 1)]); let grid = new Float32Array(8); let maxVertexCount = R[2] * 2; if(maxVertexCount > 65536) { throw new Error("The specified dimensions exceed the maximum possible number of vertices (65536)."); } let indices = new Uint16Array(maxVertexCount * 6); let vertexIndices = new Uint16Array(maxVertexCount); let vertices = new Float32Array(vertexIndices.length * 3); let vertexCounter = 0; let indexCounter = 0; let m; let scale = new Float32Array(3); let shift = new Float32Array(3); let i, j, k, bufferNo, n; let mask, g, p; let v = new Float32Array(3); let edgeMask, edgeCount; let e0, e1, g0, g1, t, a, b; let s, iu, iv, du, dv; for(i = 0; i < 3; ++i) { scale[i] = (this.bounds[1][i] - this.bounds[0][i]) / this.dimensions[i]; shift[i] = this.bounds[0][i]; } // March over the voxel grid. for(x[2] = 0, n = 0, bufferNo = 1; x[2] < (this.dimensions[2] - 1); ++x[2], n += this.dimensions[0], bufferNo ^= 1, R[2] =- R[2]) { m = 1 + (this.dimensions[0] + 1) * (1 + bufferNo * (this.dimensions[1] + 1)); // The contents of the vertexIndices will be the indices of the vertices on the previous x/y slice of the volume. for(x[1] = 0; x[1] < this.dimensions[1] - 1; ++x[1], ++n, m += 2) { for(x[0] = 0, mask = 0, g = 0; x[0] < this.dimensions[0] - 1; ++x[0], ++n, ++m) { /* Read in 8 field values around this vertex and store them in an array. * Also calculate 8-bit mask, like in marching cubes, so we can speed up sign checks later. */ for(k = 0; k < 2; ++k) { for(j = 0; j < 2; ++j) { for(i = 0; i < 2; ++i, ++g) { p = this.potential( scale[0] * (x[0] + i) + shift[0], scale[1] * (x[1] + j) + shift[1], scale[2] * (x[2] + k) + shift[2] ); grid[g] = p; mask |= (p < 0) ? (1 << g) : 0; } } } // Continue if the cell doesn't intersect the boundary. if(mask !== 0 && mask !== 0xff) { // Sum up edge intersections. edgeMask = EDGE_TABLE[mask]; v[0] = v[1] = v[2] = 0.0; edgeCount = 0; // For every edge of the cube. for(i = 0; i < 12; ++i) { // Use edge mask to check if it is crossed. if(edgeMask & (1 << i)) { // If it did, increment number of edge crossings. ++edgeCount; // Now find the point of intersection. // Unpack vertices. e0 = CUBE_EDGES[i << 1]; e1 = CUBE_EDGES[(i << 1) + 1]; // Unpack grid values. g0 = grid[e0]; g1 = grid[e1]; // Compute point of intersection. t = g0 - g1; // Threshold check. if(Math.abs(t) > 1e-6) { t = g0 / t; // Interpolate vertices and add up intersections (this can be done without multiplying). for(j = 0, k = 1; j < 3; ++j, k <<= 1) { a = e0 & k; b = e1 & k; if(a !== b) { v[j] += a ? 1.0 - t : t; } else { v[j] += a ? 1.0 : 0; } } } } } // Average the edge intersections and add them to coordinate. s = 1.0 / edgeCount; for(i = 0; i < 3; ++i) { v[i] = scale[i] * (x[i] + s * v[i]) + shift[i]; } // Add vertex to vertices, store pointer to vertex in vertexIndices. vertexIndices[m] = vertexCounter / 3; vertices[vertexCounter++] = v[0]; vertices[vertexCounter++] = v[1]; vertices[vertexCounter++] = v[2]; // Add faces together by looping over 3 basis components. for(i = 0; i < 3; ++i) { // The first three entries of the edgeMask count the crossings along the edge. if(edgeMask & (1 << i)) { // i = axes we are pointing along. iu, iv = orthogonal axes. iu = (i + 1) % 3; iv = (i + 2) % 3; // If we are on a boundary, skip. if(x[iu] !== 0 && x[iv] !== 0) { // Otherwise, look up adjacent edges in vertexIndices. du = R[iu]; dv = R[iv]; // Remember to flip orientation depending on the sign of the corner. if(mask & 1) { indices[indexCounter++] = vertexIndices[m]; indices[indexCounter++] = vertexIndices[m - du]; indices[indexCounter++] = vertexIndices[m - dv]; indices[indexCounter++] = vertexIndices[m - dv]; indices[indexCounter++] = vertexIndices[m - du]; indices[indexCounter++] = vertexIndices[m - du - dv]; } else { indices[indexCounter++] = vertexIndices[m]; indices[indexCounter++] = vertexIndices[m - dv]; indices[indexCounter++] = vertexIndices[m - du]; indices[indexCounter++] = vertexIndices[m - du]; indices[indexCounter++] = vertexIndices[m - dv]; indices[indexCounter++] = vertexIndices[m - du - dv]; } } } } } } } } if(indices.length !== indexCounter) { indices = indices.slice(0, indexCounter); } if(vertices.length !== vertexCounter) { vertices = vertices.slice(0, vertexCounter); } this.setIndex(new THREE.BufferAttribute(indices, 1)); this.addAttribute("position", new THREE.BufferAttribute(vertices, 3)); //this.addAttribute("uv", new THREE.BufferAttribute(uvs, 2)); } } exports.HeightField = HeightField; exports.HeightFieldMaterial = HeightFieldMaterial; exports.SurfaceNet = SurfaceNet; }));