Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| /** | |
| * THREE.Terrain.js 1.6.0-20180415 | |
| * | |
| * @author Isaac Sukin (http://www.isaacsukin.com/) | |
| * @license MIT | |
| */ | |
| /** | |
| * Simplex and Perlin noise. | |
| * | |
| * Copied with small edits from https://github.com/josephg/noisejs which is | |
| * public domain. Originally by Stefan Gustavson (stegu@itn.liu.se) with | |
| * optimizations by Peter Eastman (peastman@drizzle.stanford.edu) and converted | |
| * to JavaScript by Joseph Gentle. | |
| */ | |
| (function(global) { | |
| var module = global.noise = {}; | |
| function Grad(x, y, z) { | |
| this.x = x; | |
| this.y = y; | |
| this.z = z; | |
| } | |
| Grad.prototype.dot2 = function(x, y) { | |
| return this.x*x + this.y*y; | |
| }; | |
| Grad.prototype.dot3 = function(x, y, z) { | |
| return this.x*x + this.y*y + this.z*z; | |
| }; | |
| var grad3 = [ | |
| new Grad(1,1,0),new Grad(-1,1,0),new Grad(1,-1,0),new Grad(-1,-1,0), | |
| new Grad(1,0,1),new Grad(-1,0,1),new Grad(1,0,-1),new Grad(-1,0,-1), | |
| new Grad(0,1,1),new Grad(0,-1,1),new Grad(0,1,-1),new Grad(0,-1,-1), | |
| ]; | |
| var p = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103, | |
| 30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94, | |
| 252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171, | |
| 168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122, | |
| 60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161, | |
| 1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159, | |
| 86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147, | |
| 118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183, | |
| 170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129, | |
| 22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228, | |
| 251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239, | |
| 107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4, | |
| 150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215, | |
| 61,156,180]; | |
| // To avoid the need for index wrapping, double the permutation table length | |
| var perm = new Array(512), | |
| gradP = new Array(512); | |
| // This isn't a very good seeding function, but it works okay. It supports | |
| // 2^16 different seed values. Write your own if you need more seeds. | |
| module.seed = function(seed) { | |
| if (seed > 0 && seed < 1) { | |
| // Scale the seed out | |
| seed *= 65536; | |
| } | |
| seed = Math.floor(seed); | |
| if (seed < 256) { | |
| seed |= seed << 8; | |
| } | |
| for (var i = 0; i < 256; i++) { | |
| var v; | |
| if (i & 1) { | |
| v = p[i] ^ (seed & 255); | |
| } | |
| else { | |
| v = p[i] ^ ((seed>>8) & 255); | |
| } | |
| perm[i] = perm[i + 256] = v; | |
| gradP[i] = gradP[i + 256] = grad3[v % 12]; | |
| } | |
| }; | |
| module.seed(Math.random()); | |
| // Skewing and unskewing factors for 2 and 3 dimensions | |
| var F2 = 0.5*(Math.sqrt(3)-1), | |
| G2 = (3-Math.sqrt(3))/6, | |
| F3 = 1/3, | |
| G3 = 1/6; | |
| // 2D simplex noise | |
| module.simplex = function(xin, yin) { | |
| var n0, n1, n2; // Noise contributions from the three corners | |
| // Skew the input space to determine which simplex cell we're in | |
| var s = (xin+yin)*F2; // Hairy factor for 2D | |
| var i = Math.floor(xin+s); | |
| var j = Math.floor(yin+s); | |
| var t = (i+j)*G2; | |
| var x0 = xin-i+t; // The x,y distances from the cell origin, unskewed | |
| var y0 = yin-j+t; | |
| // For the 2D case, the simplex shape is an equilateral triangle. | |
| // Determine which simplex we are in. | |
| var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords | |
| if (x0 > y0) { // Lower triangle, XY order: (0,0)->(1,0)->(1,1) | |
| i1 = 1; j1 = 0; | |
| } | |
| else { // Upper triangle, YX order: (0,0)->(0,1)->(1,1) | |
| i1 = 0; j1 = 1; | |
| } | |
| // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and | |
| // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where | |
| // c = (3-sqrt(3))/6 | |
| var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords | |
| var y1 = y0 - j1 + G2; | |
| var x2 = x0 - 1 + 2 * G2; // Offsets for last corner in (x,y) unskewed coords | |
| var y2 = y0 - 1 + 2 * G2; | |
| // Work out the hashed gradient indices of the three simplex corners | |
| i &= 255; | |
| j &= 255; | |
| var gi0 = gradP[i+perm[j]]; | |
| var gi1 = gradP[i+i1+perm[j+j1]]; | |
| var gi2 = gradP[i+1+perm[j+1]]; | |
| // Calculate the contribution from the three corners | |
| var t0 = 0.5 - x0*x0-y0*y0; | |
| if (t0 < 0) { | |
| n0 = 0; | |
| } | |
| else { | |
| t0 *= t0; | |
| n0 = t0 * t0 * gi0.dot2(x0, y0); // (x,y) of grad3 used for 2D gradient | |
| } | |
| var t1 = 0.5 - x1*x1-y1*y1; | |
| if (t1 < 0) { | |
| n1 = 0; | |
| } | |
| else { | |
| t1 *= t1; | |
| n1 = t1 * t1 * gi1.dot2(x1, y1); | |
| } | |
| var t2 = 0.5 - x2*x2-y2*y2; | |
| if (t2 < 0) { | |
| n2 = 0; | |
| } | |
| else { | |
| t2 *= t2; | |
| n2 = t2 * t2 * gi2.dot2(x2, y2); | |
| } | |
| // Add contributions from each corner to get the final noise value. | |
| // The result is scaled to return values in the interval [-1,1]. | |
| return 70 * (n0 + n1 + n2); | |
| }; | |
| // ##### Perlin noise stuff | |
| function fade(t) { | |
| return t*t*t*(t*(t*6-15)+10); | |
| } | |
| function lerp(a, b, t) { | |
| return (1-t)*a + t*b; | |
| } | |
| // 2D Perlin Noise | |
| module.perlin = function(x, y) { | |
| // Find unit grid cell containing point | |
| var X = Math.floor(x), | |
| Y = Math.floor(y); | |
| // Get relative xy coordinates of point within that cell | |
| x = x - X; | |
| y = y - Y; | |
| // Wrap the integer cells at 255 (smaller integer period can be introduced here) | |
| X = X & 255; | |
| Y = Y & 255; | |
| // Calculate noise contributions from each of the four corners | |
| var n00 = gradP[X+perm[Y]].dot2(x, y); | |
| var n01 = gradP[X+perm[Y+1]].dot2(x, y-1); | |
| var n10 = gradP[X+1+perm[Y]].dot2(x-1, y); | |
| var n11 = gradP[X+1+perm[Y+1]].dot2(x-1, y-1); | |
| // Compute the fade curve value for x | |
| var u = fade(x); | |
| // Interpolate the four results | |
| return lerp( | |
| lerp(n00, n10, u), | |
| lerp(n01, n11, u), | |
| fade(y) | |
| ); | |
| }; | |
| })(this); | |
| /** | |
| * A terrain object for use with the Three.js library. | |
| * | |
| * Usage: `var terrainScene = THREE.Terrain();` | |
| * | |
| * @param {Object} [options] | |
| * An optional map of settings that control how the terrain is constructed | |
| * and displayed. Options include: | |
| * | |
| * - `after`: A function to run after other transformations on the terrain | |
| * produce the highest-detail heightmap, but before optimizations and | |
| * visual properties are applied. Takes two parameters, which are the same | |
| * as those for {@link THREE.Terrain.DiamondSquare}: an array of | |
| * `THREE.Vector3` objects representing the vertices of the terrain, and a | |
| * map of options with the same available properties as the `options` | |
| * parameter for the `THREE.Terrain` function. | |
| * - `easing`: A function that affects the distribution of slopes by | |
| * interpolating the height of each vertex along a curve. Valid values | |
| * include `THREE.Terrain.Linear` (the default), `THREE.Terrain.EaseIn`, | |
| * `THREE.Terrain.EaseOut`, `THREE.Terrain.EaseInOut`, | |
| * `THREE.Terrain.InEaseOut`, and any custom function that accepts a float | |
| * between 0 and 1 and returns a float between 0 and 1. | |
| * - `frequency`: For terrain generation methods that support it (Perlin, | |
| * Simplex, and Worley) the octave of randomness. This basically controls | |
| * how big features of the terrain will be (higher frequencies result in | |
| * smaller features). Often running multiple generation functions with | |
| * different frequencies and heights results in nice detail, as | |
| * the PerlinLayers and SimplexLayers methods demonstrate. (The counterpart | |
| * to frequency, amplitude, is represented by the difference between the | |
| * `maxHeight` and `minHeight` parameters.) Defaults to 2.5. | |
| * - `heightmap`: Either a canvas or pre-loaded image (from the same domain | |
| * as the webpage or served with a CORS-friendly header) representing | |
| * terrain height data (lighter pixels are higher); or a function used to | |
| * generate random height data for the terrain. Valid random functions are | |
| * specified in `generators.js` (or custom functions with the same | |
| * signature). Ideally heightmap images have the same number of pixels as | |
| * the terrain has vertices, as determined by the `xSegments` and | |
| * `ySegments` options, but this is not required. If the heightmap is a | |
| * different size, vertex height values will be interpolated.) Defaults to | |
| * `THREE.Terrain.DiamondSquare`. | |
| * - `material`: a THREE.Material instance used to display the terrain. | |
| * Defaults to `new THREE.MeshBasicMaterial({color: 0xee6633})`. | |
| * - `maxHeight`: the highest point, in Three.js units, that a peak should | |
| * reach. Defaults to 100. Setting to `undefined`, `null`, or `Infinity` | |
| * removes the cap, but this is generally not recommended because many | |
| * generators and filters require a vertical range. Instead, consider | |
| * setting the `stretch` option to `false`. | |
| * - `minHeight`: the lowest point, in Three.js units, that a valley should | |
| * reach. Defaults to -100. Setting to `undefined`, `null`, or `-Infinity` | |
| * removes the cap, but this is generally not recommended because many | |
| * generators and filters require a vertical range. Instead, consider | |
| * setting the `stretch` option to `false`. | |
| * - `steps`: If this is a number above 1, the terrain will be paritioned | |
| * into that many flat "steps," resulting in a blocky appearance. Defaults | |
| * to 1. | |
| * - `stretch`: Determines whether to stretch the heightmap across the | |
| * maximum and minimum height range if the height range produced by the | |
| * `heightmap` property is smaller. Defaults to true. | |
| * - `turbulent`: Whether to perform a turbulence transformation. Defaults to | |
| * false. | |
| * - `useBufferGeometry`: a Boolean indicating whether to use | |
| * THREE.BufferGeometry instead of THREE.Geometry for the Terrain plane. | |
| * Defaults to `false`. | |
| * - `xSegments`: The number of segments (rows) to divide the terrain plane | |
| * into. (This basically determines how detailed the terrain is.) Defaults | |
| * to 63. | |
| * - `xSize`: The width of the terrain in Three.js units. Defaults to 1024. | |
| * Rendering might be slightly faster if this is a multiple of | |
| * `options.xSegments + 1`. | |
| * - `ySegments`: The number of segments (columns) to divide the terrain | |
| * plane into. (This basically determines how detailed the terrain is.) | |
| * Defaults to 63. | |
| * - `ySize`: The length of the terrain in Three.js units. Defaults to 1024. | |
| * Rendering might be slightly faster if this is a multiple of | |
| * `options.ySegments + 1`. | |
| */ | |
| THREE.Terrain = function(options) { | |
| var defaultOptions = { | |
| after: null, | |
| easing: THREE.Terrain.Linear, | |
| heightmap: THREE.Terrain.DiamondSquare, | |
| material: null, | |
| maxHeight: 100, | |
| minHeight: -100, | |
| optimization: THREE.Terrain.NONE, | |
| frequency: 2.5, | |
| steps: 1, | |
| stretch: true, | |
| turbulent: false, | |
| useBufferGeometry: false, | |
| xSegments: 63, | |
| xSize: 1024, | |
| ySegments: 63, | |
| ySize: 1024, | |
| _mesh: null, // internal only | |
| }; | |
| options = options || {}; | |
| for (var opt in defaultOptions) { | |
| if (defaultOptions.hasOwnProperty(opt)) { | |
| options[opt] = typeof options[opt] === 'undefined' ? defaultOptions[opt] : options[opt]; | |
| } | |
| } | |
| options.material = options.material || new THREE.MeshBasicMaterial({ color: 0xee6633 }); | |
| // Encapsulating the terrain in a parent object allows us the flexibility | |
| // to more easily have multiple meshes for optimization purposes. | |
| var scene = new THREE.Object3D(); | |
| // Planes are initialized on the XY plane, so rotate the plane to make it lie flat. | |
| scene.rotation.x = -0.5 * Math.PI; | |
| // Create the terrain mesh. | |
| // To save memory, it is possible to re-use a pre-existing mesh. | |
| var mesh = options._mesh; | |
| if (mesh && mesh.geometry.type === 'PlaneGeometry' && | |
| mesh.geometry.parameters.widthSegments === options.xSegments && | |
| mesh.geometry.parameters.heightSegments === options.ySegments) { | |
| mesh.material = options.material; | |
| mesh.scale.x = options.xSize / mesh.geometry.parameters.width; | |
| mesh.scale.y = options.ySize / mesh.geometry.parameters.height; | |
| for (var i = 0, l = mesh.geometry.vertices.length; i < l; i++) { | |
| mesh.geometry.vertices[i].z = 0; | |
| } | |
| } | |
| else { | |
| mesh = new THREE.Mesh( | |
| new THREE.PlaneGeometry(options.xSize, options.ySize, options.xSegments, options.ySegments), | |
| options.material | |
| ); | |
| } | |
| delete options._mesh; // Remove the reference for GC | |
| // Assign elevation data to the terrain plane from a heightmap or function. | |
| if (options.heightmap instanceof HTMLCanvasElement || options.heightmap instanceof Image) { | |
| THREE.Terrain.fromHeightmap(mesh.geometry.vertices, options); | |
| } | |
| else if (typeof options.heightmap === 'function') { | |
| options.heightmap(mesh.geometry.vertices, options); | |
| } | |
| else { | |
| console.warn('An invalid value was passed for `options.heightmap`: ' + options.heightmap); | |
| } | |
| THREE.Terrain.Normalize(mesh, options); | |
| if (options.useBufferGeometry) { | |
| mesh.geometry = (new THREE.BufferGeometry()).fromGeometry(mesh.geometry); | |
| } | |
| // lod.addLevel(mesh, options.unit * 10 * Math.pow(2, lodLevel)); | |
| scene.add(mesh); | |
| return scene; | |
| }; | |
| /** | |
| * Normalize the terrain after applying a heightmap or filter. | |
| * | |
| * This applies turbulence, steps, and height clamping; calls the `after` | |
| * callback; updates normals and the bounding sphere; and marks vertices as | |
| * dirty. | |
| * | |
| * @param {THREE.Mesh} mesh | |
| * The terrain mesh. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid options are the same as for {@link THREE.Terrain}(). | |
| */ | |
| THREE.Terrain.Normalize = function(mesh, options) { | |
| var v = mesh.geometry.vertices; | |
| if (options.turbulent) { | |
| THREE.Terrain.Turbulence(v, options); | |
| } | |
| if (options.steps > 1) { | |
| THREE.Terrain.Step(v, options.steps); | |
| THREE.Terrain.Smooth(v, options); | |
| } | |
| // Keep the terrain within the allotted height range if necessary, and do easing. | |
| THREE.Terrain.Clamp(v, options); | |
| // Call the "after" callback | |
| if (typeof options.after === 'function') { | |
| options.after(v, options); | |
| } | |
| // Mark the geometry as having changed and needing updates. | |
| mesh.geometry.verticesNeedUpdate = true; | |
| mesh.geometry.normalsNeedUpdate = true; | |
| mesh.geometry.computeBoundingSphere(); | |
| mesh.geometry.computeFaceNormals(); | |
| mesh.geometry.computeVertexNormals(); | |
| }; | |
| /** | |
| * Optimization types. | |
| * | |
| * Note that none of these are implemented right now. They should be done as | |
| * shaders so that they execute on the GPU, and the resulting scene would need | |
| * to be updated every frame to adjust to the camera's position. | |
| * | |
| * Further reading: | |
| * - http://vterrain.org/LOD/Papers/ | |
| * - http://vterrain.org/LOD/Implementations/ | |
| * | |
| * GEOMIPMAP: The terrain plane should be split into sections, each with their | |
| * own LODs, for screen-space occlusion and detail reduction. Intermediate | |
| * vertices on higher-detail neighboring sections should be interpolated | |
| * between neighbor edge vertices in order to match with the edge of the | |
| * lower-detail section. The number of sections should be around sqrt(segments) | |
| * along each axis. It's unclear how to make materials stretch across segments. | |
| * Possible example (I haven't looked too much into it) at | |
| * https://github.com/felixpalmer/lod-terrain/tree/master/js/shaders | |
| * | |
| * GEOCLIPMAP: The terrain should be composed of multiple donut-shaped sections | |
| * at decreasing resolution as the radius gets bigger. When the player moves, | |
| * the sections should morph so that the detail "follows" the player around. | |
| * There is an implementation of geoclipmapping at | |
| * https://github.com/CodeArtemis/TriggerRally/blob/unified/server/public/scripts/client/terrain.coffee | |
| * and a tutorial on morph targets at | |
| * http://nikdudnik.com/making-3d-gfx-for-the-cinema-on-low-budget-and-three-js/ | |
| * | |
| * POLYGONREDUCTION: Combine areas that are relatively coplanar into larger | |
| * polygons as described at http://www.shamusyoung.com/twentysidedtale/?p=142. | |
| * This method can be combined with the others if done very carefully, or it | |
| * can be adjusted to be more aggressive at greater distance from the camera | |
| * (similar to combining with geomipmapping). | |
| * | |
| * If these do get implemented, here is the option description to add to the | |
| * `THREE.Terrain` docblock: | |
| * | |
| * - `optimization`: the type of optimization to apply to the terrain. If | |
| * an optimization is applied, the number of segments along each axis that | |
| * the terrain should be divided into at the most detailed level should | |
| * equal (n * 2^(LODs-1))^2 - 1, for arbitrary n, where LODs is the number | |
| * of levels of detail desired. Valid values include: | |
| * | |
| * - `THREE.Terrain.NONE`: Don't apply any optimizations. This is the | |
| * default. | |
| * - `THREE.Terrain.GEOMIPMAP`: Divide the terrain into evenly-sized | |
| * sections with multiple levels of detail. For each section, | |
| * display a level of detail dependent on how close the camera is. | |
| * - `THREE.Terrain.GEOCLIPMAP`: Divide the terrain into donut-shaped | |
| * sections, where detail decreases as the radius increases. The | |
| * rings then morph to "follow" the camera around so that the camera | |
| * is always at the center, surrounded by the most detail. | |
| */ | |
| THREE.Terrain.NONE = 0; | |
| THREE.Terrain.GEOMIPMAP = 1; | |
| THREE.Terrain.GEOCLIPMAP = 2; | |
| THREE.Terrain.POLYGONREDUCTION = 3; | |
| /** | |
| * Get a 2D array of heightmap values from a 1D array of plane vertices. | |
| * | |
| * @param {THREE.Vector3[]} vertices | |
| * A 1D array containing the vertices of the plane geometry representing the | |
| * terrain, where the z-value of the vertices represent the terrain's | |
| * heightmap. | |
| * @param {Object} options | |
| * A map of settings defining properties of the terrain. The only properties | |
| * that matter here are `xSegments` and `ySegments`, which represent how many | |
| * vertices wide and deep the terrain plane is, respectively (and therefore | |
| * also the dimensions of the returned array). | |
| * | |
| * @return {Number[][]} | |
| * A 2D array representing the terrain's heightmap. | |
| */ | |
| THREE.Terrain.toArray2D = function(vertices, options) { | |
| var tgt = new Array(options.xSegments), | |
| xl = options.xSegments + 1, | |
| yl = options.ySegments + 1, | |
| i, j; | |
| for (i = 0; i < xl; i++) { | |
| tgt[i] = new Float64Array(options.ySegments); | |
| for (j = 0; j < yl; j++) { | |
| tgt[i][j] = vertices[j * xl + i].z; | |
| } | |
| } | |
| return tgt; | |
| }; | |
| /** | |
| * Set the height of plane vertices from a 2D array of heightmap values. | |
| * | |
| * @param {THREE.Vector3[]} vertices | |
| * A 1D array containing the vertices of the plane geometry representing the | |
| * terrain, where the z-value of the vertices represent the terrain's | |
| * heightmap. | |
| * @param {Number[][]} src | |
| * A 2D array representing a heightmap to apply to the terrain. | |
| */ | |
| THREE.Terrain.fromArray2D = function(vertices, src) { | |
| for (var i = 0, xl = src.length; i < xl; i++) { | |
| for (var j = 0, yl = src[i].length; j < yl; j++) { | |
| vertices[j * xl + i].z = src[i][j]; | |
| } | |
| } | |
| }; | |
| /** | |
| * Get a 1D array of heightmap values from a 1D array of plane vertices. | |
| * | |
| * @param {THREE.Vector3[]} vertices | |
| * A 1D array containing the vertices of the plane geometry representing the | |
| * terrain, where the z-value of the vertices represent the terrain's | |
| * heightmap. | |
| * @param {Object} options | |
| * A map of settings defining properties of the terrain. The only properties | |
| * that matter here are `xSegments` and `ySegments`, which represent how many | |
| * vertices wide and deep the terrain plane is, respectively (and therefore | |
| * also the dimensions of the returned array). | |
| * | |
| * @return {Number[]} | |
| * A 1D array representing the terrain's heightmap. | |
| */ | |
| THREE.Terrain.toArray1D = function(vertices) { | |
| var tgt = new Float64Array(vertices.length); | |
| for (var i = 0, l = tgt.length; i < l; i++) { | |
| tgt[i] = vertices[i].z; | |
| } | |
| return tgt; | |
| }; | |
| /** | |
| * Set the height of plane vertices from a 1D array of heightmap values. | |
| * | |
| * @param {THREE.Vector3[]} vertices | |
| * A 1D array containing the vertices of the plane geometry representing the | |
| * terrain, where the z-value of the vertices represent the terrain's | |
| * heightmap. | |
| * @param {Number[]} src | |
| * A 1D array representing a heightmap to apply to the terrain. | |
| */ | |
| THREE.Terrain.fromArray1D = function(vertices, src) { | |
| for (var i = 0, l = Math.min(vertices.length, src.length); i < l; i++) { | |
| vertices[i].z = src[i]; | |
| } | |
| }; | |
| /** | |
| * Generate a 1D array containing random heightmap data. | |
| * | |
| * This is like {@link THREE.Terrain.toHeightmap} except that instead of | |
| * generating the Three.js mesh and material information you can just get the | |
| * height data. | |
| * | |
| * @param {Function} method | |
| * The method to use to generate the heightmap data. Works with function that | |
| * would be an acceptable value for the `heightmap` option for the | |
| * {@link THREE.Terrain} function. | |
| * @param {Number} options | |
| * The same as the options parameter for the {@link THREE.Terrain} function. | |
| */ | |
| THREE.Terrain.heightmapArray = function(method, options) { | |
| var arr = new Array((options.xSegments+1) * (options.ySegments+1)), | |
| l = arr.length, | |
| i; | |
| // The heightmap functions provided by this script operate on THREE.Vector3 | |
| // objects by changing the z field, so we need to make that available. | |
| // Unfortunately that means creating a bunch of objects we're just going to | |
| // throw away, but a conscious decision was made here to optimize for the | |
| // vector case. | |
| for (i = 0; i < l; i++) { | |
| arr[i] = {z: 0}; | |
| } | |
| options.minHeight = options.minHeight || 0; | |
| options.maxHeight = typeof options.maxHeight === 'undefined' ? 1 : options.maxHeight; | |
| options.stretch = options.stretch || false; | |
| method(arr, options); | |
| THREE.Terrain.Clamp(arr, options); | |
| for (i = 0; i < l; i++) { | |
| arr[i] = arr[i].z; | |
| } | |
| return arr; | |
| }; | |
| /** | |
| * Randomness interpolation functions. | |
| */ | |
| THREE.Terrain.Linear = function(x) { | |
| return x; | |
| }; | |
| // x = [0, 1], x^2 | |
| THREE.Terrain.EaseIn = function(x) { | |
| return x*x; | |
| }; | |
| // x = [0, 1], -x(x-2) | |
| THREE.Terrain.EaseOut = function(x) { | |
| return -x * (x - 2); | |
| }; | |
| // x = [0, 1], x^2(3-2x) | |
| // Nearly identical alternatives: 0.5+0.5*cos(x*pi-pi), x^a/(x^a+(1-x)^a) (where a=1.6 seems nice) | |
| // For comparison: http://www.wolframalpha.com/input/?i=x^1.6%2F%28x^1.6%2B%281-x%29^1.6%29%2C+x^2%283-2x%29%2C+0.5%2B0.5*cos%28x*pi-pi%29+from+0+to+1 | |
| THREE.Terrain.EaseInOut = function(x) { | |
| return x*x*(3-2*x); | |
| }; | |
| // x = [0, 1], 0.5*(2x-1)^3+0.5 | |
| THREE.Terrain.InEaseOut = function(x) { | |
| var y = 2*x-1; | |
| return 0.5 * y*y*y + 0.5; | |
| }; | |
| // x = [0, 1], x^1.55 | |
| THREE.Terrain.EaseInWeak = function(x) { | |
| return Math.pow(x, 1.55); | |
| }; | |
| // x = [0, 1], x^7 | |
| THREE.Terrain.EaseInStrong = function(x) { | |
| return x*x*x*x*x*x*x; | |
| }; | |
| /** | |
| * Convert an image-based heightmap into vertex-based height data. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}(). | |
| */ | |
| THREE.Terrain.fromHeightmap = function(g, options) { | |
| var canvas = document.createElement('canvas'), | |
| context = canvas.getContext('2d'), | |
| rows = options.ySegments + 1, | |
| cols = options.xSegments + 1, | |
| spread = options.maxHeight - options.minHeight; | |
| canvas.width = cols; | |
| canvas.height = rows; | |
| context.drawImage(options.heightmap, 0, 0, canvas.width, canvas.height); | |
| var data = context.getImageData(0, 0, canvas.width, canvas.height).data; | |
| for (var row = 0; row < rows; row++) { | |
| for (var col = 0; col < cols; col++) { | |
| var i = row * cols + col, | |
| idx = i * 4; | |
| g[i].z = (data[idx] + data[idx+1] + data[idx+2]) / 765 * spread + options.minHeight; | |
| } | |
| } | |
| }; | |
| /** | |
| * Convert a terrain plane into an image-based heightmap. | |
| * | |
| * Parameters are the same as for {@link THREE.Terrain.fromHeightmap} except | |
| * that if `options.heightmap` is a canvas element then the image will be | |
| * painted onto that canvas; otherwise a new canvas will be created. | |
| * | |
| * NOTE: this method performs an operation on an array of vertices, which | |
| * aren't available when using `BufferGeometry`. So, if you want to use this | |
| * method, make sure to set the `useBufferGeometry` option to `false` when | |
| * generating your terrain. | |
| * | |
| * @return {HTMLCanvasElement} | |
| * A canvas with the relevant heightmap painted on it. | |
| */ | |
| THREE.Terrain.toHeightmap = function(g, options) { | |
| var hasMax = typeof options.maxHeight === 'undefined', | |
| hasMin = typeof options.minHeight === 'undefined', | |
| max = hasMax ? options.maxHeight : -Infinity, | |
| min = hasMin ? options.minHeight : Infinity; | |
| if (!hasMax || !hasMin) { | |
| var max2 = max, | |
| min2 = min; | |
| for (var k = 0, l = g.length; k < l; k++) { | |
| if (g[k].z > max2) max2 = g[k].z; | |
| if (g[k].z < min2) min2 = g[k].z; | |
| } | |
| if (!hasMax) max = max2; | |
| if (!hasMin) min = min2; | |
| } | |
| var canvas = options.heightmap instanceof HTMLCanvasElement ? options.heightmap : document.createElement('canvas'), | |
| context = canvas.getContext('2d'), | |
| rows = options.ySegments + 1, | |
| cols = options.xSegments + 1, | |
| spread = options.maxHeight - options.minHeight; | |
| canvas.width = cols; | |
| canvas.height = rows; | |
| var d = context.createImageData(canvas.width, canvas.height), | |
| data = d.data; | |
| for (var row = 0; row < rows; row++) { | |
| for (var col = 0; col < cols; col++) { | |
| var i = row * cols + col, | |
| idx = i * 4; | |
| data[idx] = data[idx+1] = data[idx+2] = Math.round(((g[i].z - options.minHeight) / spread) * 255); | |
| data[idx+3] = 255; | |
| } | |
| } | |
| context.putImageData(d, 0, 0); | |
| return canvas; | |
| }; | |
| /** | |
| * Rescale the heightmap of a terrain to keep it within the maximum range. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}() but only `maxHeight`, `minHeight`, and `easing` | |
| * are used. | |
| */ | |
| THREE.Terrain.Clamp = function(g, options) { | |
| var min = Infinity, | |
| max = -Infinity, | |
| l = g.length, | |
| i; | |
| options.easing = options.easing || THREE.Terrain.Linear; | |
| for (i = 0; i < l; i++) { | |
| if (g[i].z < min) min = g[i].z; | |
| if (g[i].z > max) max = g[i].z; | |
| } | |
| var actualRange = max - min, | |
| optMax = typeof options.maxHeight !== 'number' ? max : options.maxHeight, | |
| optMin = typeof options.minHeight !== 'number' ? min : options.minHeight, | |
| targetMax = options.stretch ? optMax : (max < optMax ? max : optMax), | |
| targetMin = options.stretch ? optMin : (min > optMin ? min : optMin), | |
| range = targetMax - targetMin; | |
| if (targetMax < targetMin) { | |
| targetMax = optMax; | |
| range = targetMax - targetMin; | |
| } | |
| for (i = 0; i < l; i++) { | |
| g[i].z = options.easing((g[i].z - min) / actualRange) * range + optMin; | |
| } | |
| }; | |
| /** | |
| * Move the edges of the terrain up or down based on distance from the edge. | |
| * | |
| * Useful to make islands or enclosing walls/cliffs. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}(). | |
| * @param {Boolean} direction | |
| * `true` if the edges should be turned up; `false` if they should be turned | |
| * down. | |
| * @param {Number} distance | |
| * The distance from the edge at which the edges should begin to be affected | |
| * by this operation. | |
| * @param {Number/Function} [e=THREE.Terrain.EaseInOut] | |
| * A function that determines how quickly the terrain will transition between | |
| * its current height and the edge shape as distance to the edge decreases. | |
| * It does this by interpolating the height of each vertex along a curve. | |
| * Valid values include `THREE.Terrain.Linear`, `THREE.Terrain.EaseIn`, | |
| * `THREE.Terrain.EaseOut`, `THREE.Terrain.EaseInOut`, | |
| * `THREE.Terrain.InEaseOut`, and any custom function that accepts a float | |
| * between 0 and 1 and returns a float between 0 and 1. | |
| * @param {Object} [edges={top: true, bottom: true, left: true, right: true}] | |
| * Determines which edges should be affected by this function. Defaults to | |
| * all edges. If passed, should be an object with `top`, `bottom`, `left`, | |
| * and `right` Boolean properties specifying which edges to affect. | |
| */ | |
| THREE.Terrain.Edges = function(g, options, direction, distance, easing, edges) { | |
| var numXSegments = Math.floor(distance / (options.xSize / options.xSegments)) || 1, | |
| numYSegments = Math.floor(distance / (options.ySize / options.ySegments)) || 1, | |
| peak = direction ? options.maxHeight : options.minHeight, | |
| max = direction ? Math.max : Math.min, | |
| xl = options.xSegments + 1, | |
| yl = options.ySegments + 1, | |
| i, j, multiplier, k1, k2; | |
| easing = easing || THREE.Terrain.EaseInOut; | |
| if (typeof edges !== 'object') { | |
| edges = {top: true, bottom: true, left: true, right: true}; | |
| } | |
| for (i = 0; i < xl; i++) { | |
| for (j = 0; j < numYSegments; j++) { | |
| multiplier = easing(1 - j / numYSegments); | |
| k1 = j*xl + i; | |
| k2 = (options.ySegments-j)*xl + i; | |
| if (edges.top) { | |
| g[k1].z = max(g[k1].z, (peak - g[k1].z) * multiplier + g[k1].z); | |
| } | |
| if (edges.bottom) { | |
| g[k2].z = max(g[k2].z, (peak - g[k2].z) * multiplier + g[k2].z); | |
| } | |
| } | |
| } | |
| for (i = 0; i < yl; i++) { | |
| for (j = 0; j < numXSegments; j++) { | |
| multiplier = easing(1 - j / numXSegments); | |
| k1 = i*xl+j; | |
| k2 = (options.ySegments-i)*xl + (options.xSegments-j); | |
| if (edges.left) { | |
| g[k1].z = max(g[k1].z, (peak - g[k1].z) * multiplier + g[k1].z); | |
| } | |
| if (edges.right) { | |
| g[k2].z = max(g[k2].z, (peak - g[k2].z) * multiplier + g[k2].z); | |
| } | |
| } | |
| } | |
| THREE.Terrain.Clamp(g, { | |
| maxHeight: options.maxHeight, | |
| minHeight: options.minHeight, | |
| stretch: true, | |
| }); | |
| }; | |
| /** | |
| * Move the edges of the terrain up or down based on distance from the center. | |
| * | |
| * Useful to make islands or enclosing walls/cliffs. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}(). | |
| * @param {Boolean} direction | |
| * `true` if the edges should be turned up; `false` if they should be turned | |
| * down. | |
| * @param {Number} distance | |
| * The distance from the center at which the edges should begin to be | |
| * affected by this operation. | |
| * @param {Number/Function} [e=THREE.Terrain.EaseInOut] | |
| * A function that determines how quickly the terrain will transition between | |
| * its current height and the edge shape as distance to the edge decreases. | |
| * It does this by interpolating the height of each vertex along a curve. | |
| * Valid values include `THREE.Terrain.Linear`, `THREE.Terrain.EaseIn`, | |
| * `THREE.Terrain.EaseOut`, `THREE.Terrain.EaseInOut`, | |
| * `THREE.Terrain.InEaseOut`, and any custom function that accepts a float | |
| * between 0 and 1 and returns a float between 0 and 1. | |
| */ | |
| THREE.Terrain.RadialEdges = function(g, options, direction, distance, easing) { | |
| var peak = direction ? options.maxHeight : options.minHeight, | |
| max = direction ? Math.max : Math.min, | |
| xl = (options.xSegments + 1), | |
| yl = (options.ySegments + 1), | |
| xl2 = xl * 0.5, | |
| yl2 = yl * 0.5, | |
| xSegmentSize = options.xSize / options.xSegments, | |
| ySegmentSize = options.ySize / options.ySegments, | |
| edgeRadius = Math.min(options.xSize, options.ySize) * 0.5 - distance, | |
| i, j, multiplier, k, vertexDistance; | |
| for (i = 0; i < xl; i++) { | |
| for (j = 0; j < yl2; j++) { | |
| k = j*xl + i; | |
| vertexDistance = Math.min(edgeRadius, Math.sqrt((xl2-i)*xSegmentSize*(xl2-i)*xSegmentSize + (yl2-j)*ySegmentSize*(yl2-j)*ySegmentSize) - distance); | |
| if (vertexDistance < 0) continue; | |
| multiplier = easing(vertexDistance / edgeRadius); | |
| g[k].z = max(g[k].z, (peak - g[k].z) * multiplier + g[k].z); | |
| // Use symmetry to reduce the number of iterations. | |
| k = (options.ySegments-j)*xl + i; | |
| g[k].z = max(g[k].z, (peak - g[k].z) * multiplier + g[k].z); | |
| } | |
| } | |
| }; | |
| /** | |
| * Smooth the terrain by setting each point to the mean of its neighborhood. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}(). | |
| * @param {Number} [weight=0] | |
| * How much to weight the original vertex height against the average of its | |
| * neighbors. | |
| */ | |
| THREE.Terrain.Smooth = function(g, options, weight) { | |
| var heightmap = new Float64Array(g.length); | |
| for (var i = 0, xl = options.xSegments + 1, yl = options.ySegments + 1; i < xl; i++) { | |
| for (var j = 0; j < yl; j++) { | |
| var sum = 0, | |
| c = 0; | |
| for (var n = -1; n <= 1; n++) { | |
| for (var m = -1; m <= 1; m++) { | |
| var key = (j+n)*xl + i + m; | |
| if (typeof g[key] !== 'undefined' && i+m >= 0 && j+n >= 0 && i+m < xl && j+n < yl) { | |
| sum += g[key].z; | |
| c++; | |
| } | |
| } | |
| } | |
| heightmap[j*xl + i] = sum / c; | |
| } | |
| } | |
| weight = weight || 0; | |
| var w = 1 / (1 + weight); | |
| for (var k = 0, l = g.length; k < l; k++) { | |
| g[k].z = (heightmap[k] + g[k].z * weight) * w; | |
| } | |
| }; | |
| /** | |
| * Smooth the terrain by setting each point to the median of its neighborhood. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.SmoothMedian = function(g, options) { | |
| var heightmap = new Float64Array(g.length), | |
| neighborValues = [], | |
| neighborKeys = [], | |
| sortByValue = function(a, b) { | |
| return neighborValues[a] - neighborValues[b]; | |
| }; | |
| for (var i = 0, xl = options.xSegments + 1, yl = options.ySegments + 1; i < xl; i++) { | |
| for (var j = 0; j < yl; j++) { | |
| neighborValues.length = 0; | |
| neighborKeys.length = 0; | |
| for (var n = -1; n <= 1; n++) { | |
| for (var m = -1; m <= 1; m++) { | |
| var key = (j+n)*xl + i + m; | |
| if (typeof g[key] !== 'undefined' && i+m >= 0 && j+n >= 0 && i+m < xl && j+n < yl) { | |
| neighborValues.push(g[key].z); | |
| neighborKeys.push(key); | |
| } | |
| } | |
| } | |
| neighborKeys.sort(sortByValue); | |
| var halfKey = Math.floor(neighborKeys.length*0.5), | |
| median; | |
| if (neighborKeys.length % 2 === 1) { | |
| median = g[neighborKeys[halfKey]].z; | |
| } | |
| else { | |
| median = (g[neighborKeys[halfKey-1]].z + g[neighborKeys[halfKey]].z) * 0.5; | |
| } | |
| heightmap[j*xl + i] = median; | |
| } | |
| } | |
| for (var k = 0, l = g.length; k < l; k++) { | |
| g[k].z = heightmap[k]; | |
| } | |
| }; | |
| /** | |
| * Smooth the terrain by clamping each point within its neighbors' extremes. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}(). | |
| * @param {Number} [multiplier=1] | |
| * By default, this filter clamps each point within the highest and lowest | |
| * value of its neighbors. This parameter is a multiplier for the range | |
| * outside of which the point will be clamped. Higher values mean that the | |
| * point can be farther outside the range of its neighbors. | |
| */ | |
| THREE.Terrain.SmoothConservative = function(g, options, multiplier) { | |
| var heightmap = new Float64Array(g.length); | |
| for (var i = 0, xl = options.xSegments + 1, yl = options.ySegments + 1; i < xl; i++) { | |
| for (var j = 0; j < yl; j++) { | |
| var max = -Infinity, | |
| min = Infinity; | |
| for (var n = -1; n <= 1; n++) { | |
| for (var m = -1; m <= 1; m++) { | |
| var key = (j+n)*xl + i + m; | |
| if (typeof g[key] !== 'undefined' && n && m && i+m >= 0 && j+n >= 0 && i+m < xl && j+n < yl) { | |
| if (g[key].z < min) min = g[key].z; | |
| if (g[key].z > max) max = g[key].z; | |
| } | |
| } | |
| } | |
| var kk = j*xl + i; | |
| if (typeof multiplier === 'number') { | |
| var halfdiff = (max - min) * 0.5, | |
| middle = min + halfdiff; | |
| max = middle + halfdiff * multiplier; | |
| min = middle - halfdiff * multiplier; | |
| } | |
| heightmap[kk] = g[kk].z > max ? max : (g[kk].z < min ? min : g[kk].z); | |
| } | |
| } | |
| for (var k = 0, l = g.length; k < l; k++) { | |
| g[k].z = heightmap[k]; | |
| } | |
| }; | |
| /** | |
| * Partition a terrain into flat steps. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Number} [levels] | |
| * The number of steps to divide the terrain into. Defaults to | |
| * (g.length/2)^(1/4). | |
| */ | |
| THREE.Terrain.Step = function(g, levels) { | |
| // Calculate the max, min, and avg values for each bucket | |
| var i = 0, | |
| j = 0, | |
| l = g.length, | |
| inc = Math.floor(l / levels), | |
| heights = new Array(l), | |
| buckets = new Array(levels); | |
| if (typeof levels === 'undefined') { | |
| levels = Math.floor(Math.pow(l*0.5, 0.25)); | |
| } | |
| for (i = 0; i < l; i++) { | |
| heights[i] = g[i].z; | |
| } | |
| heights.sort(function(a, b) { return a - b; }); | |
| for (i = 0; i < levels; i++) { | |
| // Bucket by population (bucket size) not range size | |
| var subset = heights.slice(i*inc, (i+1)*inc), | |
| sum = 0, | |
| bl = subset.length; | |
| for (j = 0; j < bl; j++) { | |
| sum += subset[j]; | |
| } | |
| buckets[i] = { | |
| min: subset[0], | |
| max: subset[subset.length-1], | |
| avg: sum / bl, | |
| }; | |
| } | |
| // Set the height of each vertex to the average height of its bucket | |
| for (i = 0; i < l; i++) { | |
| var startHeight = g[i].z; | |
| for (j = 0; j < levels; j++) { | |
| if (startHeight >= buckets[j].min && startHeight <= buckets[j].max) { | |
| g[i].z = buckets[j].avg; | |
| break; | |
| } | |
| } | |
| } | |
| }; | |
| /** | |
| * Transform to turbulent noise. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.Turbulence = function(g, options) { | |
| var range = options.maxHeight - options.minHeight; | |
| for (var i = 0, l = g.length; i < l; i++) { | |
| g[i].z = options.minHeight + Math.abs((g[i].z - options.minHeight) * 2 - range); | |
| } | |
| }; | |
| /** | |
| * A utility for generating heightmap functions by additive composition. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} [options] | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}(). | |
| * @param {Object[]} passes | |
| * Determines which heightmap functions to compose to create a new one. | |
| * Consists of an array of objects with the following properties: | |
| * - `method`: Contains something that will be passed around as an | |
| * `options.heightmap` (a heightmap-generating function or a heightmap image) | |
| * - `amplitude`: A multiplier for the heightmap of the pass. Applied before | |
| * the result of the pass is added to the result of previous passes. | |
| * - `frequency`: For terrain generation methods that support it (Perlin, | |
| * Simplex, and Worley) the octave of randomness. This basically controls | |
| * how big features of the terrain will be (higher frequencies result in | |
| * smaller features). Often running multiple generation functions with | |
| * different frequencies and amplitudes results in nice detail. | |
| */ | |
| THREE.Terrain.MultiPass = function(g, options, passes) { | |
| var clonedOptions = {}; | |
| for (var opt in options) { | |
| if (options.hasOwnProperty(opt)) { | |
| clonedOptions[opt] = options[opt]; | |
| } | |
| } | |
| var range = options.maxHeight - options.minHeight; | |
| for (var i = 0, l = passes.length; i < l; i++) { | |
| var amp = typeof passes[i].amplitude === 'undefined' ? 1 : passes[i].amplitude, | |
| move = 0.5 * (range - range * amp); | |
| clonedOptions.maxHeight = options.maxHeight - move; | |
| clonedOptions.minHeight = options.minHeight + move; | |
| clonedOptions.frequency = typeof passes[i].frequency === 'undefined' ? options.frequency : passes[i].frequency; | |
| passes[i].method(g, clonedOptions); | |
| } | |
| }; | |
| /** | |
| * Generate random terrain using a curve. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}(). | |
| * @param {Function} curve | |
| * A function that takes an x- and y-coordinate and returns a z-coordinate. | |
| * For example, `function(x, y) { return Math.sin(x*y*Math.PI*100); }` | |
| * generates sine noise, and `function() { return Math.random(); }` sets the | |
| * vertex elevations entirely randomly. The function's parameters (the x- and | |
| * y-coordinates) are given as percentages of a phase (i.e. how far across | |
| * the terrain in the relevant direction they are). | |
| */ | |
| THREE.Terrain.Curve = function(g, options, curve) { | |
| var range = (options.maxHeight - options.minHeight) * 0.5, | |
| scalar = options.frequency / (Math.min(options.xSegments, options.ySegments) + 1); | |
| for (var i = 0, xl = options.xSegments + 1, yl = options.ySegments + 1; i < xl; i++) { | |
| for (var j = 0; j < yl; j++) { | |
| g[j * xl + i].z += curve(i * scalar, j * scalar) * range; | |
| } | |
| } | |
| }; | |
| /** | |
| * Generate random terrain using the Cosine waves. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.Cosine = function(g, options) { | |
| var amplitude = (options.maxHeight - options.minHeight) * 0.5, | |
| frequencyScalar = options.frequency * Math.PI / (Math.min(options.xSegments, options.ySegments) + 1), | |
| phase = Math.random() * Math.PI * 2; | |
| for (var i = 0, xl = options.xSegments + 1; i < xl; i++) { | |
| for (var j = 0, yl = options.ySegments + 1; j < yl; j++) { | |
| g[j * xl + i].z += amplitude * (Math.cos(i * frequencyScalar + phase) + Math.cos(j * frequencyScalar + phase)); | |
| } | |
| } | |
| }; | |
| /** | |
| * Generate random terrain using layers of Cosine waves. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.CosineLayers = function(g, options) { | |
| THREE.Terrain.MultiPass(g, options, [ | |
| { method: THREE.Terrain.Cosine, frequency: 2.5 }, | |
| { method: THREE.Terrain.Cosine, amplitude: 0.1, frequency: 12 }, | |
| { method: THREE.Terrain.Cosine, amplitude: 0.05, frequency: 15 }, | |
| { method: THREE.Terrain.Cosine, amplitude: 0.025, frequency: 20 }, | |
| ]); | |
| }; | |
| /** | |
| * Generate random terrain using the Diamond-Square method. | |
| * | |
| * Based on https://github.com/srchea/Terrain-Generation/blob/master/js/classes/TerrainGeneration.js | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}(). | |
| */ | |
| THREE.Terrain.DiamondSquare = function(g, options) { | |
| // Set the segment length to the smallest power of 2 that is greater than | |
| // the number of vertices in either dimension of the plane | |
| var segments = THREE.Math.nextPowerOfTwo(Math.max(options.xSegments, options.ySegments) + 1); | |
| // Initialize heightmap | |
| var size = segments + 1, | |
| heightmap = [], | |
| smoothing = (options.maxHeight - options.minHeight), | |
| i, | |
| j, | |
| xl = options.xSegments + 1, | |
| yl = options.ySegments + 1; | |
| for (i = 0; i <= segments; i++) { | |
| heightmap[i] = new Float64Array(segments+1); | |
| } | |
| // Generate heightmap | |
| for (var l = segments; l >= 2; l /= 2) { | |
| var half = Math.round(l*0.5), | |
| whole = Math.round(l), | |
| x, | |
| y, | |
| avg, | |
| d, | |
| e; | |
| smoothing /= 2; | |
| // square | |
| for (x = 0; x < segments; x += whole) { | |
| for (y = 0; y < segments; y += whole) { | |
| d = Math.random() * smoothing * 2 - smoothing; | |
| avg = heightmap[x][y] + // top left | |
| heightmap[x+whole][y] + // top right | |
| heightmap[x][y+whole] + // bottom left | |
| heightmap[x+whole][y+whole]; // bottom right | |
| avg *= 0.25; | |
| heightmap[x+half][y+half] = avg + d; | |
| } | |
| } | |
| // diamond | |
| for (x = 0; x < segments; x += half) { | |
| for (y = (x+half) % l; y < segments; y += l) { | |
| d = Math.random() * smoothing * 2 - smoothing; | |
| avg = heightmap[(x-half+size)%size][y] + // middle left | |
| heightmap[(x+half)%size][y] + // middle right | |
| heightmap[x][(y+half)%size] + // middle top | |
| heightmap[x][(y-half+size)%size]; // middle bottom | |
| avg *= 0.25; | |
| avg += d; | |
| heightmap[x][y] = avg; | |
| // top and right edges | |
| if (x === 0) heightmap[segments][y] = avg; | |
| if (y === 0) heightmap[x][segments] = avg; | |
| } | |
| } | |
| } | |
| // Apply heightmap | |
| for (i = 0; i < xl; i++) { | |
| for (j = 0; j < yl; j++) { | |
| g[j * xl + i].z += heightmap[i][j]; | |
| } | |
| } | |
| // THREE.Terrain.SmoothConservative(g, options); | |
| }; | |
| /** | |
| * Generate random terrain using the Fault method. | |
| * | |
| * Based on http://www.lighthouse3d.com/opengl/terrain/index.php3?fault | |
| * Repeatedly draw random lines that cross the terrain. Raise the terrain on | |
| * one side of the line and lower it on the other. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.Fault = function(g, options) { | |
| var d = Math.sqrt(options.xSegments*options.xSegments + options.ySegments*options.ySegments), | |
| iterations = d * options.frequency, | |
| range = (options.maxHeight - options.minHeight) * 0.5, | |
| displacement = range / iterations, | |
| smoothDistance = Math.min(options.xSize / options.xSegments, options.ySize / options.ySegments) * options.frequency; | |
| for (var k = 0; k < iterations; k++) { | |
| var v = Math.random(), | |
| a = Math.sin(v * Math.PI * 2), | |
| b = Math.cos(v * Math.PI * 2), | |
| c = Math.random() * d - d*0.5; | |
| for (var i = 0, xl = options.xSegments + 1; i < xl; i++) { | |
| for (var j = 0, yl = options.ySegments + 1; j < yl; j++) { | |
| var distance = a*i + b*j - c; | |
| if (distance > smoothDistance) { | |
| g[j * xl + i].z += displacement; | |
| } | |
| else if (distance < -smoothDistance) { | |
| g[j * xl + i].z -= displacement; | |
| } | |
| else { | |
| g[j * xl + i].z += Math.cos(distance / smoothDistance * Math.PI * 2) * displacement; | |
| } | |
| } | |
| } | |
| } | |
| // THREE.Terrain.Smooth(g, options); | |
| }; | |
| /** | |
| * Generate random terrain using the Hill method. | |
| * | |
| * The basic approach is to repeatedly pick random points on or near the | |
| * terrain and raise a small hill around those points. Those small hills | |
| * eventually accumulate into large hills with distinct features. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}(). | |
| * @param {Function} [feature=THREE.Terrain.Influences.Hill] | |
| * A function describing the feature to raise at the randomly chosen points. | |
| * Typically this is a hill shape so that the accumulated features result in | |
| * something resembling mountains, but it could be any function that accepts | |
| * one parameter representing the distance from the feature's origin | |
| * expressed as a number between -1 and 1 inclusive. Optionally it can accept | |
| * a second and third parameter, which are the x- and y- distances from the | |
| * feature's origin, respectively. It should return a number between -1 and 1 | |
| * representing the height of the feature at the given coordinate. | |
| * `THREE.Terrain.Influences` contains some useful functions for this | |
| * purpose. | |
| * @param {Function} [shape] | |
| * A function that takes an object with `x` and `y` properties consisting of | |
| * uniform random variables from 0 to 1, and returns a number from 0 to 1, | |
| * typically by transforming it over a distribution. The result affects where | |
| * small hills are raised thereby affecting the overall shape of the terrain. | |
| */ | |
| THREE.Terrain.Hill = function(g, options, feature, shape) { | |
| var frequency = options.frequency * 2, | |
| numFeatures = frequency * frequency * 10, | |
| heightRange = options.maxHeight - options.minHeight, | |
| minHeight = heightRange / (frequency * frequency), | |
| maxHeight = heightRange / frequency, | |
| smallerSideLength = Math.min(options.xSize, options.ySize), | |
| minRadius = smallerSideLength / (frequency * frequency), | |
| maxRadius = smallerSideLength / frequency; | |
| feature = feature || THREE.Terrain.Influences.Hill; | |
| var coords = { x: 0, y: 0 }; | |
| for (var i = 0; i < numFeatures; i++) { | |
| var radius = Math.random() * (maxRadius - minRadius) + minRadius, | |
| height = Math.random() * (maxHeight - minHeight) + minHeight; | |
| var min = 0 - radius, | |
| maxX = options.xSize + radius, | |
| maxY = options.ySize + radius; | |
| coords.x = Math.random(); | |
| coords.y = Math.random(); | |
| if (typeof shape === 'function') shape(coords); | |
| THREE.Terrain.Influence( | |
| g, options, | |
| feature, | |
| coords.x, coords.y, | |
| radius, height, | |
| THREE.AdditiveBlending, | |
| THREE.Terrain.EaseInStrong | |
| ); | |
| } | |
| }; | |
| /** | |
| * Generate random terrain using the Hill method, centered on the terrain. | |
| * | |
| * The only difference between this and the Hill method is that the locations | |
| * of the points to place small hills are not uniformly randomly distributed | |
| * but instead are more likely to occur close to the center of the terrain. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}(). | |
| * @param {Function} [feature=THREE.Terrain.Influences.Hill] | |
| * A function describing the feature. The function should accept one | |
| * parameter representing the distance from the feature's origin expressed as | |
| * a number between -1 and 1 inclusive. Optionally it can accept a second and | |
| * third parameter, which are the x- and y- distances from the feature's | |
| * origin, respectively. It should return a number between -1 and 1 | |
| * representing the height of the feature at the given coordinate. | |
| * `THREE.Terrain.Influences` contains some useful functions for this | |
| * purpose. | |
| */ | |
| THREE.Terrain.HillIsland = (function() { | |
| var island = function(coords) { | |
| var theta = Math.random() * Math.PI * 2; | |
| coords.x = 0.5 + Math.cos(theta) * coords.x * 0.4; | |
| coords.y = 0.5 + Math.sin(theta) * coords.y * 0.4; | |
| }; | |
| return function(g, options, feature) { | |
| THREE.Terrain.Hill(g, options, feature, island); | |
| }; | |
| })(); | |
| (function() { | |
| /** | |
| * Deposit a particle at a vertex. | |
| */ | |
| function deposit(g, i, j, xl, displacement) { | |
| var currentKey = j * xl + i; | |
| // Pick a random neighbor. | |
| for (var k = 0; k < 3; k++) { | |
| var r = Math.floor(Math.random() * 8); | |
| switch (r) { | |
| case 0: i++; break; | |
| case 1: i--; break; | |
| case 2: j++; break; | |
| case 3: j--; break; | |
| case 4: i++; j++; break; | |
| case 5: i++; j--; break; | |
| case 6: i--; j++; break; | |
| case 7: i--; j--; break; | |
| } | |
| var neighborKey = j * xl + i; | |
| // If the neighbor is lower, move the particle to that neighbor and re-evaluate. | |
| if (typeof g[neighborKey] !== 'undefined') { | |
| if (g[neighborKey].z < g[currentKey].z) { | |
| deposit(g, i, j, xl, displacement); | |
| return; | |
| } | |
| } | |
| // Deposit some particles on the edge. | |
| else if (Math.random() < 0.2) { | |
| g[currentKey].z += displacement; | |
| return; | |
| } | |
| } | |
| g[currentKey].z += displacement; | |
| } | |
| /** | |
| * Generate random terrain using the Particle Deposition method. | |
| * | |
| * Based on http://www.lighthouse3d.com/opengl/terrain/index.php?particle | |
| * Repeatedly deposit particles on terrain vertices. Pick a random neighbor | |
| * of that vertex. If the neighbor is lower, roll the particle to the | |
| * neighbor. When the particle stops, displace the vertex upwards. | |
| * | |
| * The shape of the outcome is highly dependent on options.frequency | |
| * because that affects how many particles will be dropped. Values around | |
| * 0.25 generally result in archipelagos whereas the default of 2.5 | |
| * generally results in one large mountainous island. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.Particles = function(g, options) { | |
| var iterations = Math.sqrt(options.xSegments*options.xSegments + options.ySegments*options.ySegments) * options.frequency * 300, | |
| xl = options.xSegments + 1, | |
| displacement = (options.maxHeight - options.minHeight) / iterations * 1000, | |
| i = Math.floor(Math.random() * options.xSegments), | |
| j = Math.floor(Math.random() * options.ySegments), | |
| xDeviation = Math.random() * 0.2 - 0.1, | |
| yDeviation = Math.random() * 0.2 - 0.1; | |
| for (var k = 0; k < iterations; k++) { | |
| deposit(g, i, j, xl, displacement); | |
| var d = Math.random() * Math.PI * 2; | |
| if (k % 1000 === 0) { | |
| xDeviation = Math.random() * 0.2 - 0.1; | |
| yDeviation = Math.random() * 0.2 - 0.1; | |
| } | |
| if (k % 100 === 0) { | |
| i = Math.floor(options.xSegments*(0.5+xDeviation) + Math.cos(d) * Math.random() * options.xSegments*(0.5-Math.abs(xDeviation))); | |
| j = Math.floor(options.ySegments*(0.5+yDeviation) + Math.sin(d) * Math.random() * options.ySegments*(0.5-Math.abs(yDeviation))); | |
| } | |
| } | |
| // THREE.Terrain.Smooth(g, options, 3); | |
| }; | |
| })(); | |
| /** | |
| * Generate random terrain using the Perlin Noise method. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.Perlin = function(g, options) { | |
| noise.seed(Math.random()); | |
| var range = (options.maxHeight - options.minHeight) * 0.5, | |
| divisor = (Math.min(options.xSegments, options.ySegments) + 1) / options.frequency; | |
| for (var i = 0, xl = options.xSegments + 1; i < xl; i++) { | |
| for (var j = 0, yl = options.ySegments + 1; j < yl; j++) { | |
| g[j * xl + i].z += noise.perlin(i / divisor, j / divisor) * range; | |
| } | |
| } | |
| }; | |
| /** | |
| * Generate random terrain using the Perlin and Diamond-Square methods composed. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.PerlinDiamond = function(g, options) { | |
| THREE.Terrain.MultiPass(g, options, [ | |
| { method: THREE.Terrain.Perlin }, | |
| { method: THREE.Terrain.DiamondSquare, amplitude: 0.75 }, | |
| { method: function(g, o) { return THREE.Terrain.SmoothMedian(g, o); } }, | |
| ]); | |
| }; | |
| /** | |
| * Generate random terrain using layers of Perlin noise. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.PerlinLayers = function(g, options) { | |
| THREE.Terrain.MultiPass(g, options, [ | |
| { method: THREE.Terrain.Perlin, frequency: 1.25 }, | |
| { method: THREE.Terrain.Perlin, amplitude: 0.05, frequency: 2.5 }, | |
| { method: THREE.Terrain.Perlin, amplitude: 0.35, frequency: 5 }, | |
| { method: THREE.Terrain.Perlin, amplitude: 0.15, frequency: 10 }, | |
| ]); | |
| }; | |
| /** | |
| * Generate random terrain using the Simplex Noise method. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| * | |
| * See https://github.com/mrdoob/three.js/blob/master/examples/webgl_terrain_dynamic.html | |
| * for an interesting comparison where the generation happens in GLSL. | |
| */ | |
| THREE.Terrain.Simplex = function(g, options) { | |
| noise.seed(Math.random()); | |
| var range = (options.maxHeight - options.minHeight) * 0.5, | |
| divisor = (Math.min(options.xSegments, options.ySegments) + 1) * 2 / options.frequency; | |
| for (var i = 0, xl = options.xSegments + 1; i < xl; i++) { | |
| for (var j = 0, yl = options.ySegments + 1; j < yl; j++) { | |
| g[j * xl + i].z += noise.simplex(i / divisor, j / divisor) * range; | |
| } | |
| } | |
| }; | |
| /** | |
| * Generate random terrain using layers of Simplex noise. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.SimplexLayers = function(g, options) { | |
| THREE.Terrain.MultiPass(g, options, [ | |
| { method: THREE.Terrain.Simplex, frequency: 1.25 }, | |
| { method: THREE.Terrain.Simplex, amplitude: 0.5, frequency: 2.5 }, | |
| { method: THREE.Terrain.Simplex, amplitude: 0.25, frequency: 5 }, | |
| { method: THREE.Terrain.Simplex, amplitude: 0.125, frequency: 10 }, | |
| { method: THREE.Terrain.Simplex, amplitude: 0.0625, frequency: 20 }, | |
| ]); | |
| }; | |
| (function() { | |
| /** | |
| * Generate a heightmap using white noise. | |
| * | |
| * @param {THREE.Vector3[]} g The terrain vertices. | |
| * @param {Object} options Settings | |
| * @param {Number} scale The resolution of the resulting heightmap. | |
| * @param {Number} segments The width of the target heightmap. | |
| * @param {Number} range The altitude of the noise. | |
| * @param {Number[]} data The target heightmap. | |
| */ | |
| function WhiteNoise(g, options, scale, segments, range, data) { | |
| if (scale > segments) return; | |
| var i = 0, | |
| j = 0, | |
| xl = segments, | |
| yl = segments, | |
| inc = Math.floor(segments / scale), | |
| lastX = -inc, | |
| lastY = -inc; | |
| // Walk over the target. For a target of size W and a resolution of N, | |
| // set every W/N points (in both directions). | |
| for (i = 0; i <= xl; i += inc) { | |
| for (j = 0; j <= yl; j += inc) { | |
| var k = j * xl + i; | |
| data[k] = Math.random() * range; | |
| if (lastX < 0 && lastY < 0) continue; | |
| // jscs:disable disallowSpacesInsideBrackets | |
| /* c b * | |
| * l t */ | |
| var t = data[k], | |
| l = data[ j * xl + (i-inc)] || t, // left | |
| b = data[(j-inc) * xl + i ] || t, // bottom | |
| c = data[(j-inc) * xl + (i-inc)] || t; // corner | |
| // jscs:enable disallowSpacesInsideBrackets | |
| // Interpolate between adjacent points to set the height of | |
| // higher-resolution target data. | |
| for (var x = lastX; x < i; x++) { | |
| for (var y = lastY; y < j; y++) { | |
| if (x === lastX && y === lastY) continue; | |
| var z = y * xl + x; | |
| if (z < 0) continue; | |
| var px = ((x-lastX) / inc), | |
| py = ((y-lastY) / inc), | |
| r1 = px * b + (1-px) * c, | |
| r2 = px * t + (1-px) * l; | |
| data[z] = py * r2 + (1-py) * r1; | |
| } | |
| } | |
| lastY = j; | |
| } | |
| lastX = i; | |
| lastY = -inc; | |
| } | |
| // Assign the temporary data back to the actual terrain heightmap. | |
| for (i = 0, xl = options.xSegments + 1; i < xl; i++) { | |
| for (j = 0, yl = options.ySegments + 1; j < yl; j++) { | |
| // http://stackoverflow.com/q/23708306/843621 | |
| var kg = j * xl + i, | |
| kd = j * segments + i; | |
| g[kg].z += data[kd]; | |
| } | |
| } | |
| } | |
| /** | |
| * Generate random terrain using value noise. | |
| * | |
| * The basic approach of value noise is to generate white noise at a | |
| * smaller octave than the target and then interpolate to get a higher- | |
| * resolution result. This is then repeated at different resolutions. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.Value = function(g, options) { | |
| // Set the segment length to the smallest power of 2 that is greater | |
| // than the number of vertices in either dimension of the plane | |
| var segments = THREE.Math.nextPowerOfTwo(Math.max(options.xSegments, options.ySegments) + 1); | |
| // Store the array of white noise outside of the WhiteNoise function to | |
| // avoid allocating a bunch of unnecessary arrays; we can just | |
| // overwrite old data each time WhiteNoise() is called. | |
| var data = new Float64Array((segments+1)*(segments+1)); | |
| // Layer white noise at different resolutions. | |
| var range = options.maxHeight - options.minHeight; | |
| for (var i = 2; i < 7; i++) { | |
| WhiteNoise(g, options, Math.pow(2, i), segments, range * Math.pow(2, 2.4-i*1.2), data); | |
| } | |
| // White noise creates some weird artifacts; fix them. | |
| // THREE.Terrain.Smooth(g, options, 1); | |
| THREE.Terrain.Clamp(g, { | |
| maxHeight: options.maxHeight, | |
| minHeight: options.minHeight, | |
| stretch: true, | |
| }); | |
| }; | |
| })(); | |
| /** | |
| * Generate random terrain using Weierstrass functions. | |
| * | |
| * Weierstrass functions are known for being continuous but not differentiable | |
| * anywhere. This produces some nice shapes that look terrain-like, but can | |
| * look repetitive from above. | |
| * | |
| * Parameters are the same as those for {@link THREE.Terrain.DiamondSquare}. | |
| */ | |
| THREE.Terrain.Weierstrass = function(g, options) { | |
| var range = (options.maxHeight - options.minHeight) * 0.5, | |
| dir1 = Math.random() < 0.5 ? 1 : -1, | |
| dir2 = Math.random() < 0.5 ? 1 : -1, | |
| r11 = 0.5 + Math.random() * 1.0, | |
| r12 = 0.5 + Math.random() * 1.0, | |
| r13 = 0.025 + Math.random() * 0.10, | |
| r14 = -1.0 + Math.random() * 2.0, | |
| r21 = 0.5 + Math.random() * 1.0, | |
| r22 = 0.5 + Math.random() * 1.0, | |
| r23 = 0.025 + Math.random() * 0.10, | |
| r24 = -1.0 + Math.random() * 2.0; | |
| for (var i = 0, xl = options.xSegments + 1; i < xl; i++) { | |
| for (var j = 0, yl = options.ySegments + 1; j < yl; j++) { | |
| var sum = 0; | |
| for (var k = 0; k < 20; k++) { | |
| var x = Math.pow(1+r11, -k) * Math.sin(Math.pow(1+r12, k) * (i + 0.25*Math.cos(j) + r14*j) * r13); | |
| var y = Math.pow(1+r21, -k) * Math.sin(Math.pow(1+r22, k) * (j + 0.25*Math.cos(i) + r24*i) * r23); | |
| sum -= Math.exp(dir1*x*x + dir2*y*y); | |
| } | |
| g[j * xl + i].z += sum * range; | |
| } | |
| } | |
| THREE.Terrain.Clamp(g, options); | |
| }; | |
| /** | |
| * Generate a material that blends together textures based on vertex height. | |
| * | |
| * Inspired by http://www.chandlerprall.com/2011/06/blending-webgl-textures/ | |
| * | |
| * Usage: | |
| * | |
| * // Assuming the textures are already loaded | |
| * var material = THREE.Terrain.generateBlendedMaterial([ | |
| * {texture: THREE.ImageUtils.loadTexture('img1.jpg')}, | |
| * {texture: THREE.ImageUtils.loadTexture('img2.jpg'), levels: [-80, -35, 20, 50]}, | |
| * {texture: THREE.ImageUtils.loadTexture('img3.jpg'), levels: [20, 50, 60, 85]}, | |
| * {texture: THREE.ImageUtils.loadTexture('img4.jpg'), glsl: '1.0 - smoothstep(65.0 + smoothstep(-256.0, 256.0, vPosition.x) * 10.0, 80.0, vPosition.z)'}, | |
| * ]); | |
| * | |
| * This material tries to behave exactly like a MeshLambertMaterial other than | |
| * the fact that it blends multiple texture maps together, although | |
| * ShaderMaterials are treated slightly differently by Three.js so YMMV. Note | |
| * that this means the texture will appear black unless there are lights | |
| * shining on it. | |
| * | |
| * @param {Object[]} textures | |
| * An array of objects specifying textures to blend together and how to blend | |
| * them. Each object should have a `texture` property containing a | |
| * `THREE.Texture` instance. There must be at least one texture and the first | |
| * texture does not need any other properties because it will serve as the | |
| * base, showing up wherever another texture isn't blended in. Other textures | |
| * must have either a `levels` property containing an array of four numbers | |
| * or a `glsl` property containing a single GLSL expression evaluating to a | |
| * float between 0.0 and 1.0. For the `levels` property, the four numbers | |
| * are, in order: the height at which the texture will start blending in, the | |
| * height at which it will be fully blended in, the height at which it will | |
| * start blending out, and the height at which it will be fully blended out. | |
| * The `vec3 vPosition` variable is available to `glsl` expressions; it | |
| * contains the coordinates in Three-space of the texel currently being | |
| * rendered. | |
| */ | |
| THREE.Terrain.generateBlendedMaterial = function(textures) { | |
| // Convert numbers to strings of floats so GLSL doesn't barf on "1" instead of "1.0" | |
| function glslifyNumber(n) { | |
| return n === (n|0) ? n+'.0' : n+''; | |
| } | |
| var uniforms = THREE.UniformsUtils.merge([THREE.ShaderLib.lambert.uniforms]), | |
| declare = '', | |
| assign = '', | |
| t0Repeat = textures[0].texture.repeat, | |
| t0Offset = textures[0].texture.offset; | |
| for (var i = 0, l = textures.length; i < l; i++) { | |
| // Uniforms | |
| textures[i].texture.wrapS = textures[i].wrapT = THREE.RepeatWrapping; | |
| textures[i].texture.needsUpdate = true; | |
| uniforms['texture_' + i] = { | |
| type: 't', | |
| value: textures[i].texture, | |
| }; | |
| // Shader fragments | |
| // Declare each texture, then mix them together. | |
| declare += 'uniform sampler2D texture_' + i + ';\n'; | |
| if (i !== 0) { | |
| var v = textures[i].levels, // Vertex heights at which to blend textures in and out | |
| p = textures[i].glsl, // Or specify a GLSL expression that evaluates to a float between 0.0 and 1.0 indicating how opaque the texture should be at this texel | |
| useLevels = typeof v !== 'undefined', // Use levels if they exist; otherwise, use the GLSL expression | |
| tiRepeat = textures[i].texture.repeat, | |
| tiOffset = textures[i].texture.offset; | |
| if (useLevels) { | |
| // Must fade in; can't start and stop at the same point. | |
| // So, if levels are too close, move one of them slightly. | |
| if (v[1] - v[0] < 1) v[0] -= 1; | |
| if (v[3] - v[2] < 1) v[3] += 1; | |
| for (var j = 0; j < v.length; j++) { | |
| v[j] = glslifyNumber(v[j]); | |
| } | |
| } | |
| // The transparency of the new texture when it is layered on top of the existing color at this texel is | |
| // (how far between the start-blending-in and fully-blended-in levels the current vertex is) + | |
| // (how far between the start-blending-out and fully-blended-out levels the current vertex is) | |
| // So the opacity is 1.0 minus that. | |
| var blendAmount = !useLevels ? p : | |
| '1.0 - smoothstep(' + v[0] + ', ' + v[1] + ', vPosition.z) + smoothstep(' + v[2] + ', ' + v[3] + ', vPosition.z)'; | |
| assign += ' color = mix( ' + | |
| 'texture2D( texture_' + i + ', MyvUv * vec2( ' + glslifyNumber(tiRepeat.x) + ', ' + glslifyNumber(tiRepeat.y) + ' ) + vec2( ' + glslifyNumber(tiOffset.x) + ', ' + glslifyNumber(tiOffset.y) + ' ) ), ' + | |
| 'color, ' + | |
| 'max(min(' + blendAmount + ', 1.0), 0.0)' + | |
| ');\n'; | |
| } | |
| } | |
| var params = { | |
| // I don't know which of these properties have any effect | |
| fog: true, | |
| lights: true, | |
| // shading: THREE.SmoothShading, | |
| // blending: THREE.NormalBlending, | |
| // depthTest: <bool>, | |
| // depthWrite: <bool>, | |
| // wireframe: false, | |
| // wireframeLinewidth: 1, | |
| // vertexColors: THREE.NoColors, | |
| // skinning: <bool>, | |
| // morphTargets: <bool>, | |
| // morphNormals: <bool>, | |
| // opacity: 1.0, | |
| // transparent: <bool>, | |
| // side: THREE.FrontSide, | |
| uniforms: uniforms, | |
| vertexShader: THREE.ShaderLib.lambert.vertexShader.replace( | |
| 'void main() {', | |
| 'varying vec2 MyvUv;\nvarying vec3 vPosition;\nvarying vec3 myNormal; void main() {\nMyvUv = uv;\nvPosition = position;\nmyNormal = normal;' | |
| ), | |
| // This is mostly copied from THREE.ShaderLib.lambert.fragmentShader | |
| fragmentShader: [ | |
| 'uniform vec3 diffuse;', | |
| 'uniform vec3 emissive;', | |
| 'uniform float opacity;', | |
| 'varying vec3 vLightFront;', | |
| '#ifdef DOUBLE_SIDED', | |
| ' varying vec3 vLightBack;', | |
| '#endif', | |
| THREE.ShaderChunk.common, | |
| THREE.ShaderChunk.packing, | |
| THREE.ShaderChunk.dithering_pars_fragment, | |
| THREE.ShaderChunk.color_pars_fragment, | |
| THREE.ShaderChunk.uv_pars_fragment, | |
| THREE.ShaderChunk.uv2_pars_fragment, | |
| THREE.ShaderChunk.map_pars_fragment, | |
| THREE.ShaderChunk.alphamap_pars_fragment, | |
| THREE.ShaderChunk.aomap_pars_fragment, | |
| THREE.ShaderChunk.lightmap_pars_fragment, | |
| THREE.ShaderChunk.emissivemap_pars_fragment, | |
| THREE.ShaderChunk.envmap_pars_fragment, | |
| THREE.ShaderChunk.bsdfs, | |
| THREE.ShaderChunk.lights_pars_begin, | |
| THREE.ShaderChunk.lights_pars_maps, | |
| THREE.ShaderChunk.fog_pars_fragment, | |
| THREE.ShaderChunk.shadowmap_pars_fragment, | |
| THREE.ShaderChunk.shadowmask_pars_fragment, | |
| THREE.ShaderChunk.specularmap_pars_fragment, | |
| THREE.ShaderChunk.logdepthbuf_pars_fragment, | |
| THREE.ShaderChunk.clipping_planes_pars_fragment, | |
| declare, | |
| 'varying vec2 MyvUv;', | |
| 'varying vec3 vPosition;', | |
| 'varying vec3 myNormal;', | |
| 'void main() {', | |
| THREE.ShaderChunk.clipping_planes_fragment, | |
| 'ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );', | |
| 'vec3 totalEmissiveRadiance = emissive;', | |
| // TODO: The second vector here is the object's "up" vector. Ideally we'd just pass it in directly. | |
| 'float slope = acos(max(min(dot(myNormal, vec3(0.0, 0.0, 1.0)), 1.0), -1.0));', | |
| ' vec4 diffuseColor = vec4( diffuse, opacity );', | |
| ' vec4 color = texture2D( texture_0, MyvUv * vec2( ' + glslifyNumber(t0Repeat.x) + ', ' + glslifyNumber(t0Repeat.y) + ' ) + vec2( ' + glslifyNumber(t0Offset.x) + ', ' + glslifyNumber(t0Offset.y) + ' ) ); // base', | |
| assign, | |
| ' diffuseColor = color;', | |
| // ' gl_FragColor = color;', | |
| THREE.ShaderChunk.logdepthbuf_fragment, | |
| THREE.ShaderChunk.map_fragment, | |
| THREE.ShaderChunk.color_fragment, | |
| THREE.ShaderChunk.alphamap_fragment, | |
| THREE.ShaderChunk.alphatest_fragment, | |
| THREE.ShaderChunk.specularmap_fragment, | |
| THREE.ShaderChunk.emissivemap_fragment, | |
| // accumulation | |
| ' reflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor );', | |
| THREE.ShaderChunk.lightmap_fragment, | |
| ' reflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );', | |
| ' #ifdef DOUBLE_SIDED', | |
| ' reflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;', | |
| ' #else', | |
| ' reflectedLight.directDiffuse = vLightFront;', | |
| ' #endif', | |
| ' reflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();', | |
| // modulation | |
| THREE.ShaderChunk.aomap_fragment, | |
| ' vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;', | |
| THREE.ShaderChunk.normal_flip, | |
| THREE.ShaderChunk.envmap_fragment, | |
| ' gl_FragColor = vec4( outgoingLight, diffuseColor.a );', // This will probably change in future three.js releases | |
| THREE.ShaderChunk.tonemapping_fragment, | |
| THREE.ShaderChunk.encodings_fragment, | |
| THREE.ShaderChunk.fog_fragment, | |
| THREE.ShaderChunk.premultiplied_alpha_fragment, | |
| THREE.ShaderChunk.dithering_fragment, | |
| '}' | |
| ].join('\n'), | |
| }; | |
| return new THREE.ShaderMaterial(params); | |
| }; | |
| /** | |
| * Scatter a mesh across the terrain. | |
| * | |
| * @param {THREE.Geometry} geometry | |
| * The terrain's geometry (or the highest-resolution version of it). | |
| * @param {Object} options | |
| * A map of settings that controls how the meshes are scattered, with the | |
| * following properties: | |
| * - `mesh`: A `THREE.Mesh` instance to scatter across the terrain. | |
| * - `spread`: A number or a function that affects where meshes are placed. | |
| * If it is a number, it represents the percent of faces of the terrain | |
| * onto which a mesh should be placed. If it is a function, it takes a | |
| * vertex from the terrain and the key of a related face and returns a | |
| * boolean indicating whether to place a mesh on that face or not. An | |
| * example could be `function(v, k) { return v.z > 0 && !(k % 4); }`. | |
| * Defaults to 0.025. | |
| * - `smoothSpread`: If the `spread` option is a number, this affects how | |
| * much placement is "eased in." Specifically, if the `randomness` function | |
| * returns a value for a face that is within `smoothSpread` percentiles | |
| * above `spread`, then the probability that a mesh is placed there is | |
| * interpolated between zero and `spread`. This creates a "thinning" effect | |
| * near the edges of clumps, if the randomness function creates clumps. | |
| * - `scene`: A `THREE.Object3D` instance to which the scattered meshes will | |
| * be added. This is expected to be either a return value of a call to | |
| * `THREE.Terrain()` or added to that return value; otherwise the position | |
| * and rotation of the meshes will be wrong. | |
| * - `sizeVariance`: The percent by which instances of the mesh can be scaled | |
| * up or down when placed on the terrain. | |
| * - `randomness`: If `options.spread` is a number, then this property is a | |
| * function that determines where meshes are placed. Specifically, it | |
| * returns an array of numbers, where each number is the probability that | |
| * a mesh is NOT placed on the corresponding face. Valid values include | |
| * `Math.random` and the return value of a call to | |
| * `THREE.Terrain.ScatterHelper`. | |
| * - `maxSlope`: The angle in radians between the normal of a face of the | |
| * terrain and the "up" vector above which no mesh will be placed on the | |
| * related face. Defaults to ~0.63, which is 36 degrees. | |
| * - `maxTilt`: The maximum angle in radians a mesh can be tilted away from | |
| * the "up" vector (towards the normal vector of the face of the terrain). | |
| * Defaults to Infinity (meshes will point towards the normal). | |
| * - `w`: The number of horizontal segments of the terrain. | |
| * - `h`: The number of vertical segments of the terrain. | |
| * | |
| * @return {THREE.Object3D} | |
| * An Object3D containing the scattered meshes. This is the value of the | |
| * `options.scene` parameter if passed. This is expected to be either a | |
| * return value of a call to `THREE.Terrain()` or added to that return value; | |
| * otherwise the position and rotation of the meshes will be wrong. | |
| */ | |
| THREE.Terrain.ScatterMeshes = function(geometry, options) { | |
| if (!options.mesh) { | |
| console.error('options.mesh is required for THREE.Terrain.ScatterMeshes but was not passed'); | |
| return; | |
| } | |
| if (geometry instanceof THREE.BufferGeometry) { | |
| console.warn('The terrain mesh is using BufferGeometry but THREE.Terrain.ScatterMeshes can only work with Geometry.'); | |
| return; | |
| } | |
| if (!options.scene) { | |
| options.scene = new THREE.Object3D(); | |
| } | |
| var defaultOptions = { | |
| spread: 0.025, | |
| smoothSpread: 0, | |
| sizeVariance: 0.1, | |
| randomness: Math.random, | |
| maxSlope: 0.6283185307179586, // 36deg or 36 / 180 * Math.PI, about the angle of repose of earth | |
| maxTilt: Infinity, | |
| w: 0, | |
| h: 0, | |
| }; | |
| for (var opt in defaultOptions) { | |
| if (defaultOptions.hasOwnProperty(opt)) { | |
| options[opt] = typeof options[opt] === 'undefined' ? defaultOptions[opt] : options[opt]; | |
| } | |
| } | |
| var spreadIsNumber = typeof options.spread === 'number', | |
| randomHeightmap, | |
| randomness, | |
| spreadRange = 1 / options.smoothSpread, | |
| doubleSizeVariance = options.sizeVariance * 2, | |
| v = geometry.vertices, | |
| meshes = [], | |
| up = options.mesh.up.clone().applyAxisAngle(new THREE.Vector3(1, 0, 0), 0.5*Math.PI); | |
| if (spreadIsNumber) { | |
| randomHeightmap = options.randomness(); | |
| randomness = typeof randomHeightmap === 'number' ? Math.random : function(k) { return randomHeightmap[k]; }; | |
| } | |
| // geometry.computeFaceNormals(); | |
| for (var i = 0, w = options.w*2; i < w; i++) { | |
| for (var j = 0, h = options.h; j < h; j++) { | |
| var key = j*w + i, | |
| f = geometry.faces[key], | |
| place = false; | |
| if (spreadIsNumber) { | |
| var rv = randomness(key); | |
| if (rv < options.spread) { | |
| place = true; | |
| } | |
| else if (rv < options.spread + options.smoothSpread) { | |
| // Interpolate rv between spread and spread + smoothSpread, | |
| // then multiply that "easing" value by the probability | |
| // that a mesh would get placed on a given face. | |
| place = THREE.Terrain.EaseInOut((rv - options.spread) * spreadRange) * options.spread > Math.random(); | |
| } | |
| } | |
| else { | |
| place = options.spread(v[f.a], key, f, i, j); | |
| } | |
| if (place) { | |
| // Don't place a mesh if the angle is too steep. | |
| if (f.normal.angleTo(up) > options.maxSlope) { | |
| continue; | |
| } | |
| var mesh = options.mesh.clone(); | |
| // mesh.geometry.computeBoundingBox(); | |
| mesh.position.copy(v[f.a]).add(v[f.b]).add(v[f.c]).divideScalar(3); | |
| // mesh.translateZ((mesh.geometry.boundingBox.max.z - mesh.geometry.boundingBox.min.z) * 0.5); | |
| if (options.maxTilt > 0) { | |
| var normal = mesh.position.clone().add(f.normal); | |
| mesh.lookAt(normal); | |
| var tiltAngle = f.normal.angleTo(up); | |
| if (tiltAngle > options.maxTilt) { | |
| var ratio = options.maxTilt / tiltAngle; | |
| mesh.rotation.x *= ratio; | |
| mesh.rotation.y *= ratio; | |
| mesh.rotation.z *= ratio; | |
| } | |
| } | |
| mesh.rotation.x += 90 / 180 * Math.PI; | |
| mesh.rotateY(Math.random() * 2 * Math.PI); | |
| if (options.sizeVariance) { | |
| var variance = Math.random() * doubleSizeVariance - options.sizeVariance; | |
| mesh.scale.x = mesh.scale.z = 1 + variance; | |
| mesh.scale.y += variance; | |
| } | |
| meshes.push(mesh); | |
| } | |
| } | |
| } | |
| // Merge geometries. | |
| var k, l; | |
| if (options.mesh.geometry instanceof THREE.Geometry) { | |
| var g = new THREE.Geometry(); | |
| for (k = 0, l = meshes.length; k < l; k++) { | |
| var m = meshes[k]; | |
| m.updateMatrix(); | |
| g.merge(m.geometry, m.matrix); | |
| } | |
| /* | |
| if (!(options.mesh.material instanceof THREE.MeshFaceMaterial)) { | |
| g = THREE.BufferGeometryUtils.fromGeometry(g); | |
| } | |
| */ | |
| options.scene.add(new THREE.Mesh(g, options.mesh.material)); | |
| } | |
| // There's no BufferGeometry merge method implemented yet. | |
| else { | |
| for (k = 0, l = meshes.length; k < l; k++) { | |
| options.scene.add(meshes[k]); | |
| } | |
| } | |
| return options.scene; | |
| }; | |
| /** | |
| * Generate a function that returns a heightmap to pass to ScatterMeshes. | |
| * | |
| * Specifically, this function generates a heightmap and then uses that | |
| * heightmap as a map of probabilities of where meshes will be placed. | |
| * | |
| * @param {Function} method | |
| * A random terrain generation function (i.e. a valid value for the | |
| * `options.heightmap` parameter of the `THREE.Terrain` function). | |
| * @param {Object} options | |
| * A map of settings that control how the resulting noise should be generated | |
| * (with the same parameters as the `options` parameter to the | |
| * `THREE.Terrain` function). `options.minHeight` must equal `0` and | |
| * `options.maxHeight` must equal `1` if they are specified. | |
| * @param {Number} skip | |
| * The number of sequential faces to skip between faces that are candidates | |
| * for placing a mesh. This avoid clumping meshes too closely together. | |
| * Defaults to 1. | |
| * @param {Number} threshold | |
| * The probability that, if a mesh can be placed on a non-skipped face due to | |
| * the shape of the heightmap, a mesh actually will be placed there. Helps | |
| * thin out placement and make it less regular. Defaults to 0.25. | |
| * | |
| * @return {Function} | |
| * Returns a function that can be passed as the value of the | |
| * `options.randomness` parameter to the {@link THREE.Terrain.ScatterMeshes} | |
| * function. | |
| */ | |
| THREE.Terrain.ScatterHelper = function(method, options, skip, threshold) { | |
| skip = skip || 1; | |
| threshold = threshold || 0.25; | |
| options.frequency = options.frequency || 2.5; | |
| var clonedOptions = {}; | |
| for (var opt in options) { | |
| if (options.hasOwnProperty(opt)) { | |
| clonedOptions[opt] = options[opt]; | |
| } | |
| } | |
| clonedOptions.xSegments *= 2; | |
| clonedOptions.stretch = true; | |
| clonedOptions.maxHeight = 1; | |
| clonedOptions.minHeight = 0; | |
| var heightmap = THREE.Terrain.heightmapArray(method, clonedOptions); | |
| for (var i = 0, l = heightmap.length; i < l; i++) { | |
| if (i % skip || Math.random() > threshold) { | |
| heightmap[i] = 1; // 0 = place, 1 = don't place | |
| } | |
| } | |
| return function() { | |
| return heightmap; | |
| }; | |
| }; | |
| // Allows placing geometrically-described features on a terrain. | |
| // If you want these features to look a little less regular, | |
| // just apply them before a procedural pass. | |
| // If you want more complex influence, you can just composite heightmaps. | |
| /** | |
| * Equations describing geographic features. | |
| */ | |
| THREE.Terrain.Influences = { | |
| Mesa: function(x) { | |
| return 1.25 * Math.min(0.8, Math.exp(-(x*x))); | |
| }, | |
| Hole: function(x) { | |
| return -THREE.Terrain.Influences.Mesa(x); | |
| }, | |
| Hill: function(x) { | |
| // Same curve as EaseInOut, but mirrored and translated. | |
| return x < 0 ? (x+1)*(x+1)*(3-2*(x+1)) : 1-x*x*(3-2*x); | |
| }, | |
| Valley: function(x) { | |
| return -THREE.Terrain.Influences.Hill(x); | |
| }, | |
| Dome: function(x) { | |
| // Parabola | |
| return -(x+1)*(x-1); | |
| }, | |
| // Not meaningful in Additive or Subtractive mode | |
| Flat: function(x) { | |
| return 0; | |
| }, | |
| Volcano: function(x) { | |
| return 0.94 - 0.32 * (Math.abs(2 * x) + Math.cos(2 * Math.PI * Math.abs(x) + 0.4)); | |
| }, | |
| }; | |
| /** | |
| * Place a geographic feature on the terrain. | |
| * | |
| * @param {THREE.Vector3[]} g | |
| * The vertex array for plane geometry to modify with heightmap data. This | |
| * method sets the `z` property of each vertex. | |
| * @param {Object} options | |
| * A map of settings that control how the terrain is constructed and | |
| * displayed. Valid values are the same as those for the `options` parameter | |
| * of {@link THREE.Terrain}(). | |
| * @param {Function} f | |
| * A function describing the feature. The function should accept one | |
| * parameter representing the distance from the feature's origin expressed as | |
| * a number between -1 and 1 inclusive. Optionally it can accept a second and | |
| * third parameter, which are the x- and y- distances from the feature's | |
| * origin, respectively. It should return a number between -1 and 1 | |
| * representing the height of the feature at the given coordinate. | |
| * `THREE.Terrain.Influences` contains some useful functions for this | |
| * purpose. | |
| * @param {Number} [x=0.5] | |
| * How far across the terrain the feature should be placed on the X-axis, in | |
| * PERCENT (as a decimal) of the size of the terrain on that axis. | |
| * @param {Number} [y=0.5] | |
| * How far across the terrain the feature should be placed on the Y-axis, in | |
| * PERCENT (as a decimal) of the size of the terrain on that axis. | |
| * @param {Number} [r=64] | |
| * The radius of the feature. | |
| * @param {Number} [h=64] | |
| * The height of the feature. | |
| * @param {String} [t=THREE.NormalBlending] | |
| * Determines how to layer the feature on top of the existing terrain. Valid | |
| * values include `THREE.AdditiveBlending`, `THREE.SubtractiveBlending`, | |
| * `THREE.MultiplyBlending`, `THREE.NoBlending`, `THREE.NormalBlending`, and | |
| * any function that takes the terrain's current height, the feature's | |
| * displacement at a vertex, and the vertex's distance from the feature | |
| * origin, and returns the new height for that vertex. (If a custom function | |
| * is passed, it can take optional fourth and fifth parameters, which are the | |
| * x- and y-distances from the feature's origin, respectively.) | |
| * @param {Number/Function} [e=THREE.Terrain.EaseIn] | |
| * A function that determines the "falloff" of the feature, i.e. how quickly | |
| * the terrain will get close to its height before the feature was applied as | |
| * the distance increases from the feature's location. It does this by | |
| * interpolating the height of each vertex along a curve. Valid values | |
| * include `THREE.Terrain.Linear`, `THREE.Terrain.EaseIn`, | |
| * `THREE.Terrain.EaseOut`, `THREE.Terrain.EaseInOut`, | |
| * `THREE.Terrain.InEaseOut`, and any custom function that accepts a float | |
| * between 0 and 1 representing the distance to the feature origin and | |
| * returns a float between 0 and 1 with the adjusted distance. (Custom | |
| * functions can also accept optional second and third parameters, which are | |
| * the x- and y-distances to the feature origin, respectively.) | |
| */ | |
| THREE.Terrain.Influence = function(g, options, f, x, y, r, h, t, e) { | |
| f = f || THREE.Terrain.Influences.Hill; // feature shape | |
| x = typeof x === 'undefined' ? 0.5 : x; // x-location % | |
| y = typeof y === 'undefined' ? 0.5 : y; // y-location % | |
| r = typeof r === 'undefined' ? 64 : r; // radius | |
| h = typeof h === 'undefined' ? 64 : h; // height | |
| t = typeof t === 'undefined' ? THREE.NormalBlending : t; // blending | |
| e = e || THREE.Terrain.EaseIn; // falloff | |
| // Find the vertex location of the feature origin | |
| var xl = options.xSegments + 1, // # x-vertices | |
| yl = options.ySegments + 1, // # y-vertices | |
| vx = xl * x, // vertex x-location | |
| vy = yl * y, // vertex y-location | |
| xw = options.xSize / options.xSegments, // width of x-segments | |
| yw = options.ySize / options.ySegments, // width of y-segments | |
| rx = r / xw, // radius of the feature in vertices on the x-axis | |
| ry = r / yw, // radius of the feature in vertices on the y-axis | |
| r1 = 1 / r, // for speed | |
| xs = Math.ceil(vx - rx), // starting x-vertex index | |
| xe = Math.floor(vx + rx), // ending x-vertex index | |
| ys = Math.ceil(vy - ry), // starting y-vertex index | |
| ye = Math.floor(vy + ry); // ending y-vertex index | |
| // Walk over the vertices within radius of origin | |
| for (var i = xs; i < xe; i++) { | |
| for (var j = ys; j < ye; j++) { | |
| var k = j * xl + i, | |
| // distance to the feature origin | |
| fdx = (i - vx) * xw, | |
| fdy = (j - vy) * yw, | |
| fd = Math.sqrt(fdx*fdx + fdy*fdy), | |
| fdr = fd * r1, | |
| fdxr = fdx * r1, | |
| fdyr = fdy * r1, | |
| // Get the displacement according to f, multiply it by h, | |
| // interpolate using e, then blend according to t. | |
| d = f(fdr, fdxr, fdyr) * h * (1 - e(fdr, fdxr, fdyr)); | |
| if (fd > r || typeof g[k] == 'undefined') continue; | |
| if (t === THREE.AdditiveBlending) g[k].z += d; // jscs:ignore requireSpaceAfterKeywords | |
| else if (t === THREE.SubtractiveBlending) g[k].z -= d; | |
| else if (t === THREE.MultiplyBlending) g[k].z *= d; | |
| else if (t === THREE.NoBlending) g[k].z = d; | |
| else if (t === THREE.NormalBlending) g[k].z = e(fdr, fdxr, fdyr) * g[k].z + d; | |
| else if (typeof t === 'function') g[k].z = t(g[k].z, d, fdr, fdxr, fdyr); | |
| } | |
| } | |
| }; |