diff --git a/public/adl/terrain/index.vwf.yaml b/public/adl/terrain/index.vwf.yaml new file mode 100644 index 000000000..45bff2deb --- /dev/null +++ b/public/adl/terrain/index.vwf.yaml @@ -0,0 +1,75 @@ +# Copyright 2012 United States Government, as represented by the Secretary of Defense, Under +# Secretary of Defense (Personnel & Readiness). +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. + +--- +extends: http://vwf.example.com/scene.vwf +properties: + navmode: none + rotationSpeed: 10 + translationSpeed: .001 +methods: + initializeCamera: +children: + duck: + extends: http://vwf.example.com/terrain.vwf + # # 2: Uncomment to apply the spin-on-click behavior + # # ... located in public/types/spin-on-click.vwf.yaml + properties: + DisplayName: "terrain1" + terrainType: "heightmapTerrainAlgorithm" + type: "Terrain" + url: "./terrain/11743-hm.jpg" + Extents: 2048 + minTileSize: 16 + tileRes: 16 + maxTileSize: 8192 + terrainParams: + DisplayName: "terrain1" + addNoise: true + bUrl: "./terrain/snow.jpg" + baseUrl: "./terrain/dirt.JPG" + cubic: true + gUrl: "./terrain/ground.jpg" + mixUrl: "./terrain/mixmap.png" + rUrl: "./terrain/cliff.jpg" + terrainType: "heightmapTerrainAlgorithm" + texture: "checker.jpg" + type: "Terrain" + url: "./terrain/11743-hm.jpg" + worldLength: 2000 + worldWidth: 2000 + diffuseUrl: "./terrain/deathvallydiffuse.jpeg" + heightScale: 1 + point2: + extends: http://vwf.example.com/light.vwf + properties: + lightType: "directional" + enable: true + distance: 2000 + intensity: 2 + color: [ 155, 155, 128 ] + translation: [ 400, 400, 190 ] +scripts: +- | + this.initialize = function() + { + this.camera.translation = [0,0,150] + this.camera.translationSpeed = .3; + this.ambientColor = [75,75,75]; + this.translationSpeed = .001; + this.camera.far = 5000; + this.camera.near = 1; + this.camera.FOV = 70; + this.backgroundColor = [200,200,255] + } + diff --git a/public/adl/terrain/terrain/11743-hm.jpg b/public/adl/terrain/terrain/11743-hm.jpg new file mode 100644 index 000000000..1793a3ae6 Binary files /dev/null and b/public/adl/terrain/terrain/11743-hm.jpg differ diff --git a/public/adl/terrain/terrain/3091-normal.jpg b/public/adl/terrain/terrain/3091-normal.jpg new file mode 100644 index 000000000..6b11e6c8c Binary files /dev/null and b/public/adl/terrain/terrain/3091-normal.jpg differ diff --git a/public/adl/terrain/terrain/4979-normal.jpg b/public/adl/terrain/terrain/4979-normal.jpg new file mode 100644 index 000000000..2b1ed7179 Binary files /dev/null and b/public/adl/terrain/terrain/4979-normal.jpg differ diff --git a/public/adl/terrain/terrain/Cliff.jpg b/public/adl/terrain/terrain/Cliff.jpg new file mode 100644 index 000000000..a5fececc5 Binary files /dev/null and b/public/adl/terrain/terrain/Cliff.jpg differ diff --git a/public/adl/terrain/terrain/bestnoise.png b/public/adl/terrain/terrain/bestnoise.png new file mode 100644 index 000000000..11679cf2c Binary files /dev/null and b/public/adl/terrain/terrain/bestnoise.png differ diff --git a/public/adl/terrain/terrain/deathvallydiffuse.jpeg b/public/adl/terrain/terrain/deathvallydiffuse.jpeg new file mode 100644 index 000000000..3a16fef3a Binary files /dev/null and b/public/adl/terrain/terrain/deathvallydiffuse.jpeg differ diff --git a/public/adl/terrain/terrain/dirt.JPG b/public/adl/terrain/terrain/dirt.JPG new file mode 100644 index 000000000..020889000 Binary files /dev/null and b/public/adl/terrain/terrain/dirt.JPG differ diff --git a/public/adl/terrain/terrain/grassnorm.jpg b/public/adl/terrain/terrain/grassnorm.jpg new file mode 100644 index 000000000..2a4a5f08d Binary files /dev/null and b/public/adl/terrain/terrain/grassnorm.jpg differ diff --git a/public/adl/terrain/terrain/ground.jpg b/public/adl/terrain/terrain/ground.jpg new file mode 100644 index 000000000..c5f354d73 Binary files /dev/null and b/public/adl/terrain/terrain/ground.jpg differ diff --git a/public/adl/terrain/terrain/mixmap.png b/public/adl/terrain/terrain/mixmap.png new file mode 100644 index 000000000..1a333dc16 Binary files /dev/null and b/public/adl/terrain/terrain/mixmap.png differ diff --git a/public/adl/terrain/terrain/snow 1.png b/public/adl/terrain/terrain/snow 1.png new file mode 100644 index 000000000..1c4d5fb06 Binary files /dev/null and b/public/adl/terrain/terrain/snow 1.png differ diff --git a/public/adl/terrain/terrain/snow.jpg b/public/adl/terrain/terrain/snow.jpg new file mode 100644 index 000000000..e9512ae74 Binary files /dev/null and b/public/adl/terrain/terrain/snow.jpg differ diff --git a/public/adl/terrain/terrain/stone 1.png b/public/adl/terrain/terrain/stone 1.png new file mode 100644 index 000000000..eb2ffedd0 Binary files /dev/null and b/public/adl/terrain/terrain/stone 1.png differ diff --git a/public/adl/terrain/textures/waternormal.jpg b/public/adl/terrain/textures/waternormal.jpg new file mode 100644 index 000000000..09426bd09 Binary files /dev/null and b/public/adl/terrain/textures/waternormal.jpg differ diff --git a/support/client/lib/vwf/model/threejs.js b/support/client/lib/vwf/model/threejs.js index 3f27ba58b..1b5e76241 100644 --- a/support/client/lib/vwf/model/threejs.js +++ b/support/client/lib/vwf/model/threejs.js @@ -234,6 +234,21 @@ define( [ "module", "vwf/model", "vwf/utility", "vwf/utility/color", "jquery" ], node.threeObject = new THREE.MeshPhongMaterial(); SetMaterial( parentNode.threeObject, node.threeObject, childName ); } + } else if ( protos && isTerrainDefinition.call( this, protos ) ) { + + node = this.state.nodes[childID] = { + name: childName, + threeObject: null, + ID: childID, + parentID: nodeID, + type: childExtendsID, + sourceType: childType, + }; + + if(!node.threeObject) + { + CreateTerrain.call(this,nodeID,childID,childName); + } } else if ( protos && isParticleDefinition.call( this, protos ) ) { node = this.state.nodes[childID] = { @@ -405,6 +420,11 @@ define( [ "module", "vwf/model", "vwf/utility", "vwf/utility/color", "jquery" ], if ( myNode && !( myNode.threeObject instanceof THREE.Material ) ) { generateNodeMaterial.call( this, childID, myNode );//Potential node, need to do node things! } + + if ( myNode && myNode.terrain) { + + myNode.terrain.initializingNode(); + } }, // -- deletingNode ------------------------------------------------------------------------- @@ -415,6 +435,11 @@ define( [ "module", "vwf/model", "vwf/utility", "vwf/utility/color", "jquery" ], this.logger.infox( "deletingNode", nodeID ); } + if ( myNode && myNode.terrain) { + debugger; + myNode.terrain.deletingNode(); + } + if(nodeID) { var childNode = this.state.nodes[nodeID]; @@ -552,10 +577,16 @@ define( [ "module", "vwf/model", "vwf/utility", "vwf/utility/color", "jquery" ], //There is not three object for this node, so there is nothing this driver can do. return if(!threeObject) return value; - + if ( propertyValue !== undefined ) { self = this; + if ( node && node.terrain) { + + node.terrain.settingProperty(propertyName, propertyValue ); + } + + if ( threeObject instanceof THREE.Object3D ) { // Function to make the object continuously look at a position or node @@ -1492,6 +1523,11 @@ define( [ "module", "vwf/model", "vwf/utility", "vwf/utility/color", "jquery" ], //There is not three object for this node, so there is nothing this driver can do. return if(!threeObject) return value; + if ( node && node.terrain) { + debugger; + return node.terrain.gettingProperty(propertyName); + } + if(threeObject instanceof THREE.Object3D) { if(propertyName == 'transform' && node.transform) @@ -2011,8 +2047,16 @@ define( [ "module", "vwf/model", "vwf/utility", "vwf/utility/color", "jquery" ], return foundSystem; } - - + function isTerrainDefinition( prototypes ) { + var foundTerrain = false; + if ( prototypes ) { + for ( var i = 0; i < prototypes.length && !foundTerrain; i++ ) { + foundTerrain = ( prototypes[i] == "http-vwf-example-com-terrain-vwf" ); + } + } + + return foundTerrain; + } function isNodeDefinition( prototypes ) { var foundNode = false; if ( prototypes ) { @@ -3060,6 +3104,30 @@ define( [ "module", "vwf/model", "vwf/utility", "vwf/utility/color", "jquery" ], return vwfColor; } + function loadScript (url) + { + + var xhr = $.ajax(url,{async:false}); + return eval(xhr.responseText); + + } + + function CreateTerrain(nodeID, childID, childName ) + { + var child = this.state.nodes[childID]; + if ( child ) + { + var factory = loadScript( "vwf/model/threejs/terrain/terrain.js"); + var terrain = new factory(childID, null, childName); + child.terrain = terrain; + child.threeObject = terrain.getRoot(); + child.threeObject.vwfID = childID; + } + + child.threeObject.name = childName; + child.name = childName; + addThreeChild.call( this, nodeID, childID ); + } function CreateParticleSystem(nodeID, childID, childName ) { diff --git a/support/client/lib/vwf/model/threejs/terrain/NoiseTerrainAlgorithm.js b/support/client/lib/vwf/model/threejs/terrain/NoiseTerrainAlgorithm.js new file mode 100644 index 000000000..073135f68 --- /dev/null +++ b/support/client/lib/vwf/model/threejs/terrain/NoiseTerrainAlgorithm.js @@ -0,0 +1,127 @@ +function NoiseTerrainAlgorithm() +{ + + + this.init = function(data) + { + + console.log(data); + this.importScript('terrain/simplexNoise.js'); + this.importScript('terrain/Rc4Random.js'); + } + //This can generate data on the main thread, and it will be passed to the coppies in the thread pool + this.poolInit = function() + { + return 65; + } + //This is the settings data, set both main and pool side + this.setAlgorithmData = function(seed) + { + this.SimplexNoise = new SimplexNoise((new Rc4Random(seed +"")).random); + } + this.setAlgorithmDataPool = function(seed) + { + this.SimplexNoise = new SimplexNoise((new Rc4Random(seed +"")).random); + } + this.getAlgorithmDataPool = function(seed) + { + return this.seed; + } + this.forceTileRebuildCallback = function() + { + return true; + } + this.getEditorData = function() + { + + } + this.getMaterialUniforms = function(mesh,matrix) + { + var uniforms_default = { + grassSampler: { type: "t", value: _SceneManager.getTexture( "terrain/grass.jpg",true ) }, + cliffSampler: { type: "t", value: _SceneManager.getTexture( "terrain/cliff.jpg",true ) }, + dirtSampler: { type: "t", value: _SceneManager.getTexture( "terrain/dirt.jpg",true ) }, + snowSampler: { type: "t", value: _SceneManager.getTexture( "terrain/snow.jpg",true ) } + }; + + uniforms_default.grassSampler.value.wrapS = uniforms_default.grassSampler.value.wrapT = THREE.RepeatWrapping; + uniforms_default.cliffSampler.value.wrapS = uniforms_default.cliffSampler.value.wrapT = THREE.RepeatWrapping; + uniforms_default.dirtSampler.value.wrapS = uniforms_default.dirtSampler.value.wrapT = THREE.RepeatWrapping; + uniforms_default.snowSampler.value.wrapS = uniforms_default.snowSampler.value.wrapT = THREE.RepeatWrapping; + return uniforms_default; + } + this.getDiffuseFragmentShader = function(mesh,matrix) + { + return ( + "uniform sampler2D grassSampler;\n"+ + "uniform sampler2D cliffSampler;\n"+ + "uniform sampler2D dirtSampler;\n"+ + "uniform sampler2D snowSampler;\n"+ + "uniform sampler2D noiseSampler;\n"+ + "vec4 getMix(vec3 norm)" + + "{"+ + "float side = min(1.0,pow(1.0-abs(dot(norm,(viewMatrix * vec4(0.0,0.0,1.0,0.0)).xyz)),3.0) * 10.0);\n"+ + "float bottom = 1.0-smoothstep(-20.0,60.0,npos.z);\n"+ + "float top = clamp(0.0,1.0,(smoothstep(100.0,140.0,npos.z)));\n"+ + "float middle = clamp(0.0,1.0,(1.0 - bottom - top));\n"+ + "bottom = clamp(0.0,1.0,mix(bottom,0.0,npos.z/100.0));\n"+ + "vec4 mixvec = normalize(vec4(bottom,middle,side* 4.0,top)) ;\n"+ + "return mixvec;\n"+ + "}"+ + "vec4 getTexture(vec3 coords, vec3 norm, vec2 uv)" + + "{"+ + //"coords /= 100.0;\n"+ + "vec4 noiseMain = texture2D(noiseSampler,(npos.xy/10.0)/2.0);\n"+ + "vec4 mixvec = getMix(norm + (noiseMain.rgb - .5)/10.0) ;\n"+ + "vec2 c0 = (coords.xy/10.0)/2.0 ;\n"+ + "vec2 c1 = (coords.xy/10.0)/2.0 ;\n"+ + "c1.y /= .5;\n"+ + "vec2 c2 = (coords.xy/10.0)/2.0 ;\n"+ + "vec2 c3 = (coords.xy/30.0)/2.0 ;\n"+ + "vec2 c0a = (coords.xy/20.0)/2.0 ;\n"+ + "vec2 c1a = (coords.xy/100.0)/2.0 ;\n"+ + "vec2 c2a = (coords.xy/100.0)/2.0 ;\n"+ + "vec2 c3a = (coords.xy/300.0)/2.0 ;\n"+ + "vec4 grass =.5*texture2D(grassSampler,c0) + .5*texture2D(grassSampler,c0a);\n"+ + "vec4 cliff =.5*texture2D(cliffSampler,c1) + .5*texture2D(cliffSampler,c1a);\n"+ + "vec4 dirt = .5*texture2D(dirtSampler,c2) + .5*texture2D(dirtSampler,c2a);\n"+ + "vec4 snow = .5*texture2D(snowSampler,c3) + .5*texture2D(snowSampler,c3a);\n"+ + "vec4 noise = texture2D(noiseSampler,c0);\n"+ + + "vec4 grass1 = mix(grass,cliff/4.0,noise.r*noise.r*noise.r);"+ + "snow = mix(snow,dirt/4.0,noise.g*noise.r*noise.b);"+ + + "return mixvec.r * grass1 + mixvec.g * grass1 + (mixvec.b) * cliff/2.0 + mixvec.a * snow;\n"+ + "}") + } + this.displace= function(vert) + { + var z = 0; + z = this.SimplexNoise.noise2D((vert.x)/10000,(vert.y)/1000) * 15; + z = z*z; + z += this.SimplexNoise.noise2D((vert.x)/100000,(vert.y)/100000) * 450; + z += this.SimplexNoise.noise2D((vert.x)/10000,(vert.y)/100000) * 250; + z += this.SimplexNoise.noise2D((vert.x)/1000,(vert.y)/100) * 25; + z += this.SimplexNoise.noise2D((vert.x)/1000,(vert.y)/5000) * 50; + z += this.SimplexNoise.noise2D((vert.x)/500,(vert.y)/50) * 10; + z += this.SimplexNoise.noise2D((vert.x)/100,(vert.y)/100) * 5.0; + z += this.SimplexNoise.noise2D((vert.x)/20,(vert.y)/20) * 1.5; + z += this.SimplexNoise.noise2D((vert.x)/5,(vert.y)/5) * .25; + + var canynon = this.SimplexNoise.noise2D((vert.x)/2000,(vert.y)/10000) * -50; + if(canynon < -30) + { + canynon += 30; + canynon *= canynon; + } + else + canynon = 0; + z-= canynon; + if(z < 0) + { + z/=5; + + } + return z + 30; + } +} \ No newline at end of file diff --git a/support/client/lib/vwf/model/threejs/terrain/Rc4Random.js b/support/client/lib/vwf/model/threejs/terrain/Rc4Random.js new file mode 100644 index 000000000..75e2869bb --- /dev/null +++ b/support/client/lib/vwf/model/threejs/terrain/Rc4Random.js @@ -0,0 +1,45 @@ +function Rc4Random(seed) + { + var keySchedule = []; + var keySchedule_i = 0; + var keySchedule_j = 0; + + function init(seed) { + for (var i = 0; i < 256; i++) + keySchedule[i] = i; + + var j = 0; + for (var i = 0; i < 256; i++) + { + j = (j + keySchedule[i] + seed.charCodeAt(i % seed.length)) % 256; + + var t = keySchedule[i]; + keySchedule[i] = keySchedule[j]; + keySchedule[j] = t; + } + } + init(seed); + + function getRandomByte() { + keySchedule_i = (keySchedule_i + 1) % 256; + keySchedule_j = (keySchedule_j + keySchedule[keySchedule_i]) % 256; + + var t = keySchedule[keySchedule_i]; + keySchedule[keySchedule_i] = keySchedule[keySchedule_j]; + keySchedule[keySchedule_j] = t; + + return keySchedule[(keySchedule[keySchedule_i] + keySchedule[keySchedule_j]) % 256]; + } + + this.getRandomNumber = function() { + var number = 0; + var multiplier = 1; + for (var i = 0; i < 8; i++) { + number += getRandomByte() * multiplier; + multiplier *= 256; + } + return number / 18446744073709551616; + }.bind(this); + this.Random = this.getRandomNumber; + this.random = this.getRandomNumber; + } \ No newline at end of file diff --git a/support/client/lib/vwf/model/threejs/terrain/heightmapTerrainAlgorithm.js b/support/client/lib/vwf/model/threejs/terrain/heightmapTerrainAlgorithm.js new file mode 100644 index 000000000..6ed8c1da7 --- /dev/null +++ b/support/client/lib/vwf/model/threejs/terrain/heightmapTerrainAlgorithm.js @@ -0,0 +1,750 @@ +function heightmapTerrainAlgorithm() +{ + + this.dataHeight = 0; + this.dataWidth = 0; + this.worldLength = 13500; + this.worldWidth = 9500; + this.addNoise = false; + this.cubic = false; + this.gamma = false; + this.min = 0; + + //this init is called from each thread, and gets data from the poolInit function. + this.init = function(data) + { + this.data = data.data; + + console.log('data received'); + + this.dataHeight = data.dataHeight || 0; + this.dataWidth = data.dataWidth || 0; + + this.worldLength = data.worldLength || 13500; + this.worldWidth = data.worldWidth || 9500; + this.addNoise = data.addNoise || false; + this.cubic = data.cubic || false; + this.gamma = data.gamma || false; + this.min = data.min || 0; + console.log('from thread: min is ' + this.min); + //this.type = 'bt'; + this.heightScale = data.heightScale || 1; + this.importScript('terrain/simplexNoise.js'); + this.importScript('terrain/Rc4Random.js'); + this.SimplexNoise = new SimplexNoise((new Rc4Random(1 +"")).random); + } + //This can generate data on the main thread, and it will be passed to the coppies in the thread pool + this.poolInit = function(cb,params) + { + + this.type = 'bt'; + if(!params) params = {}; + this.addNoise = params.addNoise || false; + this.cubic = params.cubic || false; + this.gamma = params.gamma || false; + this.heightScale = params.heightScale || 1; + this.url = (params && params.url) || 'terrain/River.bt'; + + if(this.url && this.url.lastIndexOf('.') > -1) + { + var type = this.url.substr(this.url.lastIndexOf('.')+1); + if(type.toLowerCase() == 'bt') + this.type = 'bt' + else + this.type = 'img'; + } + this.diffuseUrl = (params && params.diffuseUrl) || 'terrain/River.jpg'; + this.mixUrl = (params && params.mixUrl) || 'terrain/River.jpg'; + this.rUrl = (params && params.rUrl) || 'terrain/River.jpg'; + this.gUrl = (params && params.gUrl) || 'terrain/River.jpg'; + this.bUrl = (params && params.bUrl) || 'terrain/River.jpg'; + this.baseUrl = (params && params.baseUrl) || 'terrain/River.jpg'; + + this.rUrlNorm = (params && params.rUrlNorm) || 'terrain/River.jpg'; + this.gUrlNorm = (params && params.gUrlNorm) || 'terrain/River.jpg'; + this.bUrlNorm = (params && params.bUrlNorm) || 'terrain/River.jpg'; + this.baseUrlNorm = (params && params.baseUrlNorm) || 'terrain/River.jpg'; + + if(this.type == 'img') + { + canvas = document.createElement('canvas'); + + this.worldLength = params && parseFloat(params.worldLength) || 13500; + this.worldWidth = params && parseFloat(params.worldWidth) || 9500; + var img = new Image(); + img.src = this.url; + + img.onload = function() + { + + this.worldLength = params && parseFloat(params.worldLength) || 13500; + this.worldWidth = params && parseFloat(params.worldWidth) || 9500; + this.dataHeight = img.naturalHeight; + this.dataWidth = img.naturalWidth; + this.heightScale = params.heightScale || 1; + canvas.height = this.dataHeight; + canvas.width = this.dataWidth; + var context = canvas.getContext('2d'); + context.drawImage(img, 0, 0); + var data = context.getImageData(0, 0, this.dataHeight, this.dataWidth).data; + + var array = new Uint8Array(this.dataHeight*this.dataWidth); + for(var i =0; i < this.dataHeight*this.dataWidth * 4; i+=4) + array[Math.floor(i/4)] = Math.pow(data[i]/255.0,1.0) * 255; + var data = new Uint8Array(this.dataHeight*this.dataWidth); + for(var i = 0; i < this.dataWidth; i++) + { + for(var j = 0; j < this.dataHeight; j++) + { + var c = i * this.dataWidth + j; + var c2 = j * this.dataHeight + i; + data[c] = array[c2]; + } + } + + cb({heightScale:this.heightScale,worldLength:this.worldLength,worldWidth:this.worldWidth,dataHeight:this.dataHeight,dataWidth:this.dataWidth,min:0,data:data,addNoise:params.addNoise,cubic:params.cubic,gamma:params.gamma}); + } + } + if(this.type == 'bt') + { + this.worldLength = params && parseFloat(params.worldLength) || 13500; + this.worldWidth = params && parseFloat(params.worldWidth) || 9500; + + //check if it was preloaded + if(_assetLoader.getTerrain(this.url)) + { + var terraindata = _assetLoader.getTerrain(this.url); + terraindata.worldLength = this.worldLength; + terraindata.worldWidth = this.worldWidth; + terraindata.addNoise=this.addNoise + terraindata.cubic=this.cubic; + terraindata.gamma=this.gamma; + terraindata.heightScale = this.heightScale; + window.setTimeout(function(){ + cb(terraindata); + },0); + + } + else + { + var buff; + var self2 = this; + var xhr = new XMLHttpRequest(); + xhr.responseType = 'arraybuffer'; + xhr.onload = function(e) { + if (xhr.status === 200) { + buff = xhr.response; + cb(self2.parseBT(buff)); + } else + { + cb(null); + } + }; + xhr.open('GET', this.url); + xhr.send(); + } + } + + //signal the pool that we need an async startup + return false; + } + this.parseBT = function(arraybuf) + { + + var DV = new DataView(arraybuf); + this.dataWidth = DV.getInt32(10,true); + this.dataHeight = DV.getInt32(14,true); + var dataSize = DV.getInt16(18,true); + var isfloat = DV.getInt16(20,true); + var scale = DV.getFloat32(62,true); + var data; + if(isfloat == 1) + { + data = new Float32Array(this.dataWidth*this.dataHeight); + } + else + { + data = new Int16Array(this.dataWidth*this.dataHeight); + } + var min = Infinity; + for(var i =0; i < this.dataWidth*this.dataHeight; i++) + { + if(isfloat == 1) + { + data[i] = DV.getFloat32(256 + 4 * i,true); + }else + { + data[i] = DV.getInt16(256 + 2 * i,true); + } + if(data[i] < min) + min = data[i]; + } + this.min = min; + this.data = data; + return {heightScale:this.heightScale,worldLength:this.worldLength,worldWidth:this.worldWidth,dataHeight:this.dataHeight,dataWidth:this.dataWidth,min:min,data:data,addNoise:this.addNoise,cubic:this.cubic,gamma:this.gamma} + } + //This is the settings data, set both main and pool side + this.getEditorData = function(data) + { + return { + + h_aaalable:{ + + displayname: 'Heightmap Algorithm', + type: 'sectionTitle' + }, + h_heightmapSrc:{ + displayname : 'HeightMap (Data Source) URL', + property:'url', + type:'map' + }, + h_worldLength:{ + displayname : 'Data Source Length (m)', + property:'worldLength', + type:'prompt' + }, + h_worldWidth:{ + displayname : 'Data Source Width (m)', + property:'worldWidth', + type:'prompt' + }, + h_addNoise:{ + displayname : 'Add additional noise', + property:'addNoise', + type:'check' + }, + h_cubic:{ + displayname : 'Use Bicubic interpolation', + property:'cubic', + type:'check' + }, + h_gamma:{ + displayname : 'Use gamma correction', + property:'gamma', + type:'check' + }, + h_heightScale:{ + displayname : 'Height Scale', + property:'heightScale', + type:'prompt' + }, + zh_:{ + displayname : 'Material', + type:'sectionTitle' + }, + zzh_diffuseSrc:{ + displayname : 'Texture URL', + property:'diffuseUrl', + type:'map' + }, + zzh_mixSrc:{ + displayname : 'Mix Map URL', + property:'mixUrl', + type:'map' + }, + zh_rSrc:{ + displayname : 'Red Channel Texture', + property:'rUrl', + type:'map' + }, + zh_gSrc:{ + displayname : 'Green Channel Texture', + property:'gUrl', + type:'map' + }, + zh_bSrc:{ + displayname : 'Blue Channel Texture', + property:'bUrl', + type:'map' + }, + + zh_baseSrc:{ + displayname : 'Black Channel Texture', + property:'baseUrl', + type:'map' + }, + + zh_rSrcNorm:{ + displayname : 'Red Channel NormalMap', + property:'rUrlNorm', + type:'map' + }, + zh_gSrcNorm:{ + displayname : 'Green Channel NormalMap', + property:'gUrlNorm', + type:'map' + }, + zh_bSrcNorm:{ + displayname : 'Blue Channel NormalMap', + property:'bUrlNorm', + type:'map' + }, + + zh_baseSrcNorm:{ + displayname : 'Black Channel NormalMap', + property:'baseUrlNorm', + type:'map' + } + }; + } + //This is the settings data, set both main and pool side + this.setAlgorithmData = function(data) + { + + } + //this sets the values on the pool side. Keep these cached here, so the engine can query them without an async call + //updatelist is the existing tiles. Return tiles in an array that will need an update after the property set. This will + //allow the engine to only schedule tile updates that are necessary. + this.setAlgorithmDataPool = function(data,updateList) + { + if(!data) return []; + var needRebuild = false; + if(data.url && data.url != this.url) + { + this.url = data.url; + needRebuild = true; + } + if(data.worldLength && data.worldLength != this.worldLength) + { + this.worldLength = parseFloat(data.worldLength); + needRebuild = true; + } + if(data.worldWidth && data.worldWidth != this.worldWidth) + { + + this.worldWidth = parseFloat(data.worldWidth); + needRebuild = true; + } + if(data.cubic != this.cubic) + { + this.cubic = data.cubic; + needRebuild = true; + } + if(data.gamma != this.gamma) + { + this.gamma = data.gamma; + needRebuild = true; + } + if(data.addNoise != this.addNoise) + { + this.addNoise = data.addNoise; + needRebuild = true; + } + if(data.heightScale != this.heightScale) + { + + this.heightScale = data.heightScale; + needRebuild = true; + } + if(data.diffuseUrl != this.diffuseUrl) + { + this.diffuseUrl = data.diffuseUrl; + this.materialRebuildCB(); + } + if(data.rUrl != this.rUrl) + { + this.rUrl = data.rUrl; + this.materialRebuildCB(); + } + if(data.gUrl != this.gUrl) + { + this.gUrl = data.gUrl; + this.materialRebuildCB(); + } + if(data.bUrl != this.bUrl) + { + this.bUrl = data.bUrl; + this.materialRebuildCB(); + } + if(data.baseUrl != this.baseUrl) + { + this.baseUrl = data.baseUrl; + this.materialRebuildCB(); + } + if(data.rUrlNorm != this.rUrlNorm) + { + this.rUrlNorm = data.rUrlNorm; + this.materialRebuildCB(); + } + if(data.gUrlNorm != this.gUrlNorm) + { + this.gUrlNorm = data.gUrlNorm; + this.materialRebuildCB(); + } + if(data.bUrlNorm != this.bUrlNorm) + { + this.bUrlNorm = data.bUrlNorm; + this.materialRebuildCB(); + } + if(data.baseUrlNorm != this.baseUrlNorm) + { + this.baseUrlNorm = data.baseUrlNorm; + this.materialRebuildCB(); + } + if(data.mixUrl != this.mixUrl) + { + + this.mixUrl = data.mixUrl; + this.materialRebuildCB(); + } + if(needRebuild) return updateList; + return []; + } + + //the engine will read the data values here + this.getAlgorithmDataPool = function(seed) + { + return { + url:this.url, + diffuseUrl:this.diffuseUrl, + mixUrl:this.mixUrl, + baseUrl:this.baseUrl, + rUrl:this.rUrl, + gUrl:this.gUrl, + bUrl:this.bUrl, + baseUrlNorm:this.baseUrlNorm, + rUrlNorm:this.rUrlNorm, + gUrlNorm:this.gUrlNorm, + bUrlNorm:this.bUrlNorm, + worldWidth:this.worldWidth, + worldLength:this.worldLength, + cubic:this.cubic || false, + gamma:this.gamma || false, + addNoise:this.addNoise || false, + heightScale:this.heightScale || 1 + }; + } + this.textureCache = {}, + this.getTexture = function(url) + { + if(!this.textureCache[url]) + { + this.textureCache[url] = THREE.ImageUtils.loadTexture(url,THREE.UVMapping); + this.textureCache[url].wrapS = THREE.RepeatWrapping; + this.textureCache[url].wrapT = THREE.RepeatWrapping; + + } + return this.textureCache[url]; + + } + //This will allow you to setup shader variables that will be merged into the the terrain shader + this.getMaterialUniforms = function(mesh,matrix) + { + + var uniforms_default = { + diffuseSampler: { type: "t", value: this.getTexture( this.diffuseUrl) }, + baseSampler: { type: "t", value:this.getTexture( this.baseUrl || "terrain/ground.jpg" ) }, + gSampler: { type: "t", value: this.getTexture( this.gUrl ||"terrain/cliff.jpg" ) }, + rSampler: { type: "t", value: this.getTexture( this.rUrl ||"terrain/cliff.jpg" ) }, + bSampler: { type: "t", value: this.getTexture( this.bUrl ||"terrain/ground.jpg" ) }, + + baseNormalMap: { type: "t", value: this.getTexture( this.baseUrlNorm ||"terrain/3091-normal.jpg" ) }, + gNormalMap: { type: "t", value: this.getTexture( this.gUrlNorm ||"terrain/grassnorm.jpg" ) }, + rNormalMap: { type: "t", value: this.getTexture( this.rUrlNorm ||"terrain/4979-normal.jpg" ) }, + bNormalMap: { type: "t", value: this.getTexture( this.bUrlNorm ||"textures/waternormal.jpg" ) }, + + mixMap: { type: "t", value: this.getTexture( this.mixUrl || "terrain/rivermix.png" ) }, + + }; + + return uniforms_default; + } + + this.getNormalFragmentShader = function() + { + + + + // http://www.thetenthplanet.de/archives/1180 + + return ""+ + + "uniform sampler2D baseNormalMap;\n"+ + "uniform sampler2D rNormalMap;\n"+ + "uniform sampler2D gNormalMap;\n"+ + "uniform sampler2D bNormalMap;\n"+ + + "mat3 cotangent_frame(vec3 N, vec3 p, vec2 uv)\n"+ + "{\n"+ + " // get edge vectors of the pixel triangle\n"+ + " vec3 dp1 = dFdx( p );\n"+ + " vec3 dp2 = dFdy( p );\n"+ + " vec2 duv1 = dFdx( uv );\n"+ + " vec2 duv2 = dFdy( uv );\n"+ + + " // solve the linear system\n"+ + " vec3 dp2perp = cross( dp2, N );\n"+ + " vec3 dp1perp = cross( N, dp1 );\n"+ + " vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;\n"+ + " vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;\n"+ + + " // construct a scale-invariant frame \n"+ + " float invmax = inversesqrt( max( dot(T,T), dot(B,B) ) );\n"+ + " return mat3( T * invmax, B * invmax, N );\n"+ + "}\n"+ + + "vec3 perturb_normal( mat3 TBN, vec2 texcoord, sampler2D sampler )\n"+ + "{\n"+ + " // assume N, the interpolated vertex normal and \n"+ + " // V, the view vector (vertex to eye)\n"+ + " vec3 map = texture2D(sampler, texcoord ).xyz;\n"+ + " map = map * 2.0 - 1.0;\n"+ + " return normalize(TBN * map);\n"+ + "}\n"+ + "vec3 triPlanerNorm(mat3 TBN0, mat3 TBN1, mat3 TBN2, vec3 coords, vec3 norm, sampler2D sampler)" + + "{"+ + "vec3 dirt0 = perturb_normal(TBN0, coords.yz, sampler);\n"+ + "vec3 dirt1 = perturb_normal(TBN1, coords.zx, sampler);\n"+ + "vec3 dirt2 = perturb_normal(TBN2, -coords.xy, sampler);\n"+ + "return blend_weights.x * dirt0 + blend_weights.y * dirt1 + blend_weights.z * dirt2;\n"+ + "}"+ + "vec3 blendNorm(vec3 texture1,vec3 texture2, float a1)\n"+ + "{\n"+ + " return mix(texture1,texture2,1.0-a1);\n"+ + "}\n"+ + "vec3 getNormal(vec3 coords, vec3 viewNorm, vec2 uv,vec3 wN) {\n"+ + + //"return viewNorm;\n"+ + + + " vec3 med = viewNorm;\n"+ + " vec3 near = viewNorm;\n"+ + " vec3 V = -((viewMatrix * vec4(coords,1.0)).xyz);\n"+ + " mat3 TBN0 = cotangent_frame(viewNorm, V, coordsScaleA.yz);\n"+ + " mat3 TBN1 = cotangent_frame(viewNorm, V, coordsScaleA.zx);\n"+ + " mat3 TBN2 = cotangent_frame(viewNorm, V, coordsScaleA.xy);\n"+ + + "float faramt = interp(800.0,1000.0,distance(cameraPosition , coords));\n"+ + "float nearamt = interp(25.0,35.0,distance(cameraPosition , coords));\n"+ + + // "if(modelMatrix[0][0] < 3.0 ){\n" +//it's faster to branch on a uniform value than a computed model. This limits to tiles less than a certain size, which must be a given distance + "if(faramt < 1.0){\n"+ + " vec3 basemap = triPlanerNorm(TBN0,TBN1,TBN2,coordsScaleA,wN,baseNormalMap);\n"+ + " vec3 rmap = triPlanerNorm(TBN0,TBN1,TBN2,coordsScaleA,wN,rNormalMap);\n"+ + " vec3 gmap = triPlanerNorm(TBN0,TBN1,TBN2,coordsScaleA,wN,gNormalMap);\n"+ + " vec3 bmap = triPlanerNorm(TBN0,TBN1,TBN2,coordsScaleA,wN,bNormalMap);\n"+ + " med = blendNorm(bmap,blendNorm(gmap,blendNorm(rmap,basemap,mixVal.r),mixVal.g),mixVal.b);\n"+ + "}\n"+ + // "}\n"+ + + // "if(modelMatrix[0][0] < 0.2 ){\n"+//it's faster to branch on a uniform value than a computed model. This limits to tiles less than a certain size, which must be a given distance + "if(nearamt < 1.0 ){\n"+ + " vec3 basenear = triPlanerNorm(TBN0,TBN1,TBN2,coordsScaleB,wN,baseNormalMap) * coordA/coordB;\n"+ + " vec3 rnear = triPlanerNorm(TBN0,TBN1,TBN2,coordsScaleB,wN,rNormalMap) * coordA/coordB;\n"+ + " vec3 gnear = triPlanerNorm(TBN0,TBN1,TBN2,coordsScaleB,wN,gNormalMap) * coordA/coordB;\n"+ + " vec3 bnear = triPlanerNorm(TBN0,TBN1,TBN2,coordsScaleB,wN,bNormalMap) * coordA/coordB;\n"+ + " near = blendNorm(bnear,blendNorm(gnear,blendNorm(rnear,basenear,mixVal.r),mixVal.g),mixVal.b);\n"+ + "}\n"+ + // "}\n"+ + + + + + + "vec3 dist = mix(med,viewNorm,faramt);\n"+ + "return normalize(near*(1.0-nearamt) +dist);\n"+ + + "}\n"; + } + //This funciton allows you to compute the diffuse surface color however you like. + //must implement vec4 getTexture(vec3 coords, vec3 norm) or return null which will give you the default white + this.getDiffuseFragmentShader = function(mesh,matrix) + { + + return ( + + "uniform sampler2D diffuseSampler;\n"+ + "uniform sampler2D baseSampler;\n"+ + "uniform sampler2D rSampler;\n"+ + "uniform sampler2D gSampler;\n"+ + "uniform sampler2D bSampler;\n"+ + "uniform sampler2D mixMap;\n"+ + "uniform mat4 modelMatrix;\n"+ + "vec4 mixVal=vec4(0.0,0.0,0.0,0.0);\n"+ + "vec3 blend_weights = vec3(0.0,0.0,0.0);\n"+ + + "vec4 triPlanerMap(vec3 coords, vec3 norm, sampler2D sampler)" + + "{"+ + "vec4 dirt0 = texture2D(sampler,((coords.yz )));\n"+ + "vec4 dirt1 = texture2D(sampler,((coords.zx )));\n"+ + "vec4 dirt2 = texture2D(sampler,((coords.xy )));\n"+ + "vec3 blend_weights = abs( norm.xyz ); // Tighten up the blending zone: \n"+ + + "return blend_weights.x * dirt0 + blend_weights.y * dirt1 + blend_weights.z * dirt2;\n"+ + "}"+ + "vec4 blend(vec4 texture1, vec4 texture2, float a1)\n"+ + "{\n"+ + " float a2 = 1.0-a1;\n"+ + " float depth = 0.2;\n"+ + " float ma = max(length(texture1) + a1,length( texture2) + a2) - depth;\n"+ + + " float b1 = max(length(texture1) + a1 - ma, 0.0);\n"+ + " float b2 = max(length(texture2) + a2 - ma, 0.0);\n"+ + //"return mix(texture1,texture2,a1);\n"+ + " return vec4((texture1.rgb * b1 + texture2.rgb * b2) / (b1 + b2),max(texture1.a,texture2.a));\n"+ + "}\n"+ + "float interp(float min, float max, float val)"+ + "{"+ + "return clamp((val - min)/(max-min),0.0,1.0);"+ + "}"+ + "vec4 getTexture(vec3 coords, vec3 norm, vec2 uv)" + + "{"+ + + (this.type == 'bt'? + "mixVal = texture2D(mixMap,((coords.yx * vec2(1.0,1.0) + vec2("+((this.worldWidth)/2).toFixed(5)+","+((this.worldLength)/2).toFixed(5)+"))/vec2("+((this.worldWidth)).toFixed(5)+","+((this.worldLength)).toFixed(5)+")));\n" + : + "mixVal = texture2D(mixMap,((coords.yx * vec2(1.0,-1.0) + vec2("+((this.worldWidth)/2).toFixed(5)+","+((this.worldLength)/2).toFixed(5)+"))/vec2("+((this.worldWidth)).toFixed(5)+","+((this.worldLength)).toFixed(5)+")));\n" + )+ + + + "mixVal /= (mixVal.x + mixVal.y+mixVal.z);\n"+ + "blend_weights = abs( wN.xyz ); // Tighten up the blending zone: \n"+ + "blend_weights = (blend_weights - 0.2679); \n"+ + "blend_weights = max(blend_weights, 0.0); // Force weights to sum to 1.0 (very important!) \n"+ + "blend_weights /= (blend_weights.x + blend_weights.y + blend_weights.z ); \n"+ + + (this.type == 'bt'? + "vec4 diffuse = texture2D(diffuseSampler,((coords.yx * vec2(1.0,1.0) + vec2("+((this.worldWidth)/2).toFixed(5)+","+((this.worldLength)/2).toFixed(5)+"))/vec2("+((this.worldWidth)).toFixed(5)+","+((this.worldLength)).toFixed(5)+")));\n" + : + "vec4 diffuse = texture2D(diffuseSampler,((coords.yx * vec2(1.0,-1.0) + vec2("+((this.worldWidth)/2).toFixed(5)+","+((this.worldLength)/2).toFixed(5)+"))/vec2("+((this.worldWidth)).toFixed(5)+","+((this.worldLength)).toFixed(5)+")));\n" + )+ + + "vec4 med = diffuse;\n"+ + + + "float faramt = interp(800.0,1000.0,distance(cameraPosition , coords));\n"+ + "float nearamt = interp(25.0,35.0,distance(cameraPosition , coords));\n"+ + + + // "if(modelMatrix[0][0] < 3.0 ){\n"+//it's faster to branch on a uniform value than a computed model. This limits to tiles less than a certain size, which must be a given distance + "if(faramt < 1.0 ){\n"+ + "vec4 basemap = triPlanerMap(coordsScaleA,wN,baseSampler);\n"+ + "vec4 rmap = triPlanerMap(coordsScaleA,wN,rSampler);\n"+ + "vec4 gmap = triPlanerMap(coordsScaleA,wN,gSampler);\n"+ + "vec4 bmap = triPlanerMap(coordsScaleA,wN,bSampler);\n"+ + "med = blend(bmap,blend(gmap,blend(rmap,basemap,mixVal.r),mixVal.g),mixVal.b);\n"+ + "}\n"+ + // "}\n"+ + "vec4 near = med;\n"+ + + // "if(modelMatrix[0][0] < 0.2 ){\n"+//it's faster to branch on a uniform value than a computed model. This limits to tiles less than a certain size, which must be a given distance + "if(nearamt < 1.0){\n"+ + + "vec4 basenear = triPlanerMap(coordsScaleB,wN,baseSampler);\n"+ + "vec4 rnear = triPlanerMap(coordsScaleB,wN,rSampler);\n"+ + "vec4 gnear = triPlanerMap(coordsScaleB,wN,gSampler);\n"+ + "vec4 bnear = triPlanerMap(coordsScaleB,wN,bSampler);\n"+ + "near = blend(bnear,blend(gnear,blend(rnear,basenear,mixVal.r),mixVal.g),mixVal.b);\n"+ + // "}\n"+ + "}\n"+ + + + + + //"vec4 near = blend(dirt,vec4(0.0,0.0,0.0,0.0),mixVal.r);\n"+ + + + "vec4 medAndFar = mix(med,diffuse,faramt);\n"+ + "return mix(near,medAndFar,nearamt);\n"+ + "}") + } + + //This is the displacement function, which is called in paralell by the thread pool + this.displace= function(vert,matrix,res) + { + var z = 0; + + if(this.addNoise) + { + z = this.SimplexNoise.noise2D((vert.x)/100,(vert.y)/100) * 4.5; + z += this.SimplexNoise.noise2D((vert.x)/300,(vert.y)/300) * 4.5; + z += this.SimplexNoise.noise2D((vert.x)/10,(vert.y)/10) * 0.5; + } + //this is gamma correction + var h = this.type == 'img' && this.gamma?2.2:1.0; + if(this.cubic) + return this.sampleBiCubic((vert.x+ (this.worldLength/2)) / this.worldLength ,(vert.y + (this.worldWidth/2)) / this.worldWidth,matrix,res ) * h * this.heightScale + z|| 0; + else + return this.sampleBiLinear((vert.x+ (this.worldLength/2)) / this.worldLength ,(vert.y + (this.worldWidth/2)) / this.worldWidth,matrix,res ) * h * this.heightScale + z|| 0; + } + this.at = function(x,y) + { + x = Math.floor(x); + y = Math.floor(y); + if(!this.data) return 0; + if( x >= this.dataHeight || x < 0) return 0; + if( y >= this.dataWidth || y < 0) return 0; + var i = y * this.dataWidth + x; + return this.data[i] - this.min; + } + this.sampleBiLinear = function(u,v) + { + //u = u - Math.floor(u); + //v = v - Math.floor(v); + u = u * this.dataWidth - .5; + v = v * this.dataHeight - .5; + var x = Math.floor(u); + var y = Math.floor(v); + var u_ratio = u -x; + var v_ratio = v - y; + var u_opposite = 1 - u_ratio; + var v_opposite = 1 - v_ratio; + var result = (this.at(x,y) * u_opposite + this.at(x+1,y) * u_ratio) * v_opposite + + (this.at(x,y+1) * u_opposite + this.at(x+1,y+1) * u_ratio) * v_ratio; + return result; + } + this.cubicInterpolate = function(p, x) + { + return p[1] + 0.5 * x*(p[2] - p[0] + x*(2.0*p[0] - 5.0*p[1] + 4.0*p[2] - p[3] + x*(3.0*(p[1] - p[2]) + p[3] - p[0]))); + } + this.bicubicInterpolate = function(p, x, y) + { + var arr = []; + arr[0] = this.cubicInterpolate(p[0], y); + arr[1] = this.cubicInterpolate(p[1], y); + arr[2] = this.cubicInterpolate(p[2], y); + arr[3] = this.cubicInterpolate(p[3], y); + return this.cubicInterpolate(arr, x); + } + this.mipAt = function(x,xo,y,yo,mip) + { + return this.at(x*mip + xo * mip,y*mip+yo*mip); + } + this.sampleBiCubic = function(u,v,matrix,tileres) + { + + var res = 1; + if((this.worldWidth/this.dataWidth) < 2) + res = Math.min(1,(this.worldWidth/this.dataWidth)/50); + var mip = 1/res; + var dh = this.dataHeight * res; + var dw = this.dataWidth * res; + + + var y = Math.floor(u * dh); + var x = Math.floor(v * dw); + + u = (u * dh) - Math.floor(u * dh); + v = (v * dw) - Math.floor(v * dw); + var p = []; + var t = x; + x = y; + y = t; + t = u; + u = v; + v = t; + + + // p[0] = [this.at(x-1 ,y-1 ),this.at(x-0,y-1 ),this.at(x+1 ,y-1 ),this.at(x+2 ,y-1 )]; + // p[1] = [this.at(x-1 ,y-0 ),this.at(x-0,y-0 ),this.at(x+1 ,y-0 ),this.at(x+2 ,y-0 )]; + // p[2] = [this.at(x-1 ,y+1 ),this.at(x-0,y+1 ),this.at(x+1 ,y+1 ),this.at(x+2 ,y+1 )]; + // p[3] = [this.at(x-1 ,y+2 ),this.at(x-0,y+2 ),this.at(x+1 ,y+2 ),this.at(x+2 ,y+2 )]; + + + p[0] = [this.mipAt(x,-1 ,y,-1,mip ),this.mipAt(x,0,y,-1,mip ),this.mipAt(x,1 ,y,-1,mip ),this.mipAt(x,2 ,y,-1,mip )]; + p[1] = [this.mipAt(x,-1 ,y,-0,mip ),this.mipAt(x,0,y,0,mip ),this.mipAt(x,1 ,y,0,mip ),this.mipAt(x,2 ,y,0,mip )]; + p[2] = [this.mipAt(x,-1 ,y,1,mip ),this.mipAt(x,0,y,1,mip ),this.mipAt(x,1 ,y,1,mip ),this.mipAt(x,2 ,y,1,mip )]; + p[3] = [this.mipAt(x,-1 ,y,2,mip ),this.mipAt(x,0,y,2,mip ),this.mipAt(x,1 ,y,2,mip ),this.mipAt(x,2 ,y,2,mip )]; + return this.bicubicInterpolate(p,u,v); + } +} \ No newline at end of file diff --git a/support/client/lib/vwf/model/threejs/terrain/simplexNoise.js b/support/client/lib/vwf/model/threejs/terrain/simplexNoise.js new file mode 100644 index 000000000..f4a4d5fb6 --- /dev/null +++ b/support/client/lib/vwf/model/threejs/terrain/simplexNoise.js @@ -0,0 +1,358 @@ + var F2 = 0.5 * (Math.sqrt(3.0) - 1.0), + G2 = (3.0 - Math.sqrt(3.0)) / 6.0, + F3 = 1.0 / 3.0, + G3 = 1.0 / 6.0, + F4 = (Math.sqrt(5.0) - 1.0) / 4.0, + G4 = (5.0 - Math.sqrt(5.0)) / 20.0; + + function SimplexNoise(random) { + if (!random) random = Math.random; + this.p = new Uint8Array(256); + this.perm = new Uint8Array(512); + this.permMod12 = new Uint8Array(512); + for (var i = 0; i < 256; i++) { + this.p[i] = random() * 256; + } + for (i = 0; i < 512; i++) { + this.perm[i] = this.p[i & 255]; + this.permMod12[i] = this.perm[i] % 12; + } + + } + + + SimplexNoise.prototype = { + grad3: new Float32Array([1, 1, 0, + - 1, 1, 0, + 1, - 1, 0, + + - 1, - 1, 0, + 1, 0, 1, + - 1, 0, 1, + + 1, 0, - 1, + - 1, 0, - 1, + 0, 1, 1, + + 0, - 1, 1, + 0, 1, - 1, + 0, - 1, - 1]), + grad4: new Float32Array([0, 1, 1, 1, 0, 1, 1, - 1, 0, 1, - 1, 1, 0, 1, - 1, - 1, + 0, - 1, 1, 1, 0, - 1, 1, - 1, 0, - 1, - 1, 1, 0, - 1, - 1, - 1, + 1, 0, 1, 1, 1, 0, 1, - 1, 1, 0, - 1, 1, 1, 0, - 1, - 1, + - 1, 0, 1, 1, - 1, 0, 1, - 1, - 1, 0, - 1, 1, - 1, 0, - 1, - 1, + 1, 1, 0, 1, 1, 1, 0, - 1, 1, - 1, 0, 1, 1, - 1, 0, - 1, + - 1, 1, 0, 1, - 1, 1, 0, - 1, - 1, - 1, 0, 1, - 1, - 1, 0, - 1, + 1, 1, 1, 0, 1, 1, - 1, 0, 1, - 1, 1, 0, 1, - 1, - 1, 0, + - 1, 1, 1, 0, - 1, 1, - 1, 0, - 1, - 1, 1, 0, - 1, - 1, - 1, 0]), + noise2D: function (xin, yin) { + var permMod12 = this.permMod12, + perm = this.perm, + grad3 = this.grad3; + var n0=0, n1=0, n2=0; // 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 = i - t; // Unskew the cell origin back to (x,y) space + var Y0 = j - t; + var x0 = xin - X0; // The x,y distances from the cell origin + var y0 = yin - Y0; + // 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) { + i1 = 1; + j1 = 0; + } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { + i1 = 0; + j1 = 1; + } // upper triangle, YX order: (0,0)->(0,1)->(1,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.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords + var y2 = y0 - 1.0 + 2.0 * G2; + // Work out the hashed gradient indices of the three simplex corners + var ii = i & 255; + var jj = j & 255; + // Calculate the contribution from the three corners + var t0 = 0.5 - x0 * x0 - y0 * y0; + if (t0 >= 0) { + var gi0 = permMod12[ii + perm[jj]] * 3; + t0 *= t0; + n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0); // (x,y) of grad3 used for 2D gradient + } + var t1 = 0.5 - x1 * x1 - y1 * y1; + if (t1 >= 0) { + var gi1 = permMod12[ii + i1 + perm[jj + j1]] * 3; + t1 *= t1; + n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1); + } + var t2 = 0.5 - x2 * x2 - y2 * y2; + if (t2 >= 0) { + var gi2 = permMod12[ii + 1 + perm[jj + 1]] * 3; + t2 *= t2; + n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * 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.0 * (n0 + n1 + n2); + }, + // 3D simplex noise + noise3D: function (xin, yin, zin) { + var permMod12 = this.permMod12, + perm = this.perm, + grad3 = this.grad3; + var n0, n1, n2, n3; // Noise contributions from the four corners + // Skew the input space to determine which simplex cell we're in + var s = (xin + yin + zin) * F3; // Very nice and simple skew factor for 3D + var i = Math.floor(xin + s); + var j = Math.floor(yin + s); + var k = Math.floor(zin + s); + var t = (i + j + k) * G3; + var X0 = i - t; // Unskew the cell origin back to (x,y,z) space + var Y0 = j - t; + var Z0 = k - t; + var x0 = xin - X0; // The x,y,z distances from the cell origin + var y0 = yin - Y0; + var z0 = zin - Z0; + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + // Determine which simplex we are in. + var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords + var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } // X Y Z order + else if (x0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 0; + k2 = 1; + } // X Z Y order + else { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 1; + j2 = 0; + k2 = 1; + } // Z X Y order + } + else { // x0 y0) rankx++; + else ranky++; + if (x0 > z0) rankx++; + else rankz++; + if (x0 > w0) rankx++; + else rankw++; + if (y0 > z0) ranky++; + else rankz++; + if (y0 > w0) ranky++; + else rankw++; + if (z0 > w0) rankz++; + else rankw++; + var i1, j1, k1, l1; // The integer offsets for the second simplex corner + var i2, j2, k2, l2; // The integer offsets for the third simplex corner + var i3, j3, k3, l3; // The integer offsets for the fourth simplex corner + // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. + // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0; + j1 = ranky >= 3 ? 1 : 0; + k1 = rankz >= 3 ? 1 : 0; + l1 = rankw >= 3 ? 1 : 0; + // Rank 2 denotes the second largest coordinate. + i2 = rankx >= 2 ? 1 : 0; + j2 = ranky >= 2 ? 1 : 0; + k2 = rankz >= 2 ? 1 : 0; + l2 = rankw >= 2 ? 1 : 0; + // Rank 1 denotes the second smallest coordinate. + i3 = rankx >= 1 ? 1 : 0; + j3 = ranky >= 1 ? 1 : 0; + k3 = rankz >= 1 ? 1 : 0; + l3 = rankw >= 1 ? 1 : 0; + // The fifth corner has all coordinate offsets = 1, so no need to compute that. + var x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords + var y1 = y0 - j1 + G4; + var z1 = z0 - k1 + G4; + var w1 = w0 - l1 + G4; + var x2 = x0 - i2 + 2.0 * G4; // Offsets for third corner in (x,y,z,w) coords + var y2 = y0 - j2 + 2.0 * G4; + var z2 = z0 - k2 + 2.0 * G4; + var w2 = w0 - l2 + 2.0 * G4; + var x3 = x0 - i3 + 3.0 * G4; // Offsets for fourth corner in (x,y,z,w) coords + var y3 = y0 - j3 + 3.0 * G4; + var z3 = z0 - k3 + 3.0 * G4; + var w3 = w0 - l3 + 3.0 * G4; + var x4 = x0 - 1.0 + 4.0 * G4; // Offsets for last corner in (x,y,z,w) coords + var y4 = y0 - 1.0 + 4.0 * G4; + var z4 = z0 - 1.0 + 4.0 * G4; + var w4 = w0 - 1.0 + 4.0 * G4; + // Work out the hashed gradient indices of the five simplex corners + var ii = i & 255; + var jj = j & 255; + var kk = k & 255; + var ll = l & 255; + // Calculate the contribution from the five corners + var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; + if (t0 < 0) n0 = 0.0; + else { + var gi0 = (perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32) * 4; + t0 *= t0; + n0 = t0 * t0 * (grad4[gi0] * x0 + grad4[gi0 + 1] * y0 + grad4[gi0 + 2] * z0 + grad4[gi0 + 3] * w0); + } + var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; + if (t1 < 0) n1 = 0.0; + else { + var gi1 = (perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32) * 4; + t1 *= t1; + n1 = t1 * t1 * (grad4[gi1] * x1 + grad4[gi1 + 1] * y1 + grad4[gi1 + 2] * z1 + grad4[gi1 + 3] * w1); + } + var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; + if (t2 < 0) n2 = 0.0; + else { + var gi2 = (perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32) * 4; + t2 *= t2; + n2 = t2 * t2 * (grad4[gi2] * x2 + grad4[gi2 + 1] * y2 + grad4[gi2 + 2] * z2 + grad4[gi2 + 3] * w2); + } + var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; + if (t3 < 0) n3 = 0.0; + else { + var gi3 = (perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32) * 4; + t3 *= t3; + n3 = t3 * t3 * (grad4[gi3] * x3 + grad4[gi3 + 1] * y3 + grad4[gi3 + 2] * z3 + grad4[gi3 + 3] * w3); + } + var t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; + if (t4 < 0) n4 = 0.0; + else { + var gi4 = (perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32) * 4; + t4 *= t4; + n4 = t4 * t4 * (grad4[gi4] * x4 + grad4[gi4 + 1] * y4 + grad4[gi4 + 2] * z4 + grad4[gi4 + 3] * w4); + } + // Sum up and scale the result to cover the range [-1,1] + return 27.0 * (n0 + n1 + n2 + n3 + n4); + } + + + }; \ No newline at end of file diff --git a/support/client/lib/vwf/model/threejs/terrain/terrain.js b/support/client/lib/vwf/model/threejs/terrain/terrain.js new file mode 100644 index 000000000..dbe83a989 --- /dev/null +++ b/support/client/lib/vwf/model/threejs/terrain/terrain.js @@ -0,0 +1,902 @@ +(function(){ + function terrain(childID, childSource, childName) + { + window._dTerrain = this; + var self = this; + var totalmintilesize = 16; + var tileres = 16; + var minTileSize = totalmintilesize; + var maxTileSize = 2048; + var worldExtents = 2048; + var updateEvery = 30; + + + function loadScript (url) + { + + var xhr = $.ajax(url,{async:false}); + return eval(xhr.responseText); + + } + + loadScript( "vwf/model/threejs/terrain/terrainTileCache.js"); + loadScript( "vwf/model/threejs/terrain/terrainQuadtree.js"); + + + + + this.init = false; + this.initializingNode = function() + { + window.requestAnimationFrame(this.update); + + Math.sign = function(e){ return e<0?-1:1}; + + + quadtreesetSelf(this); + quadtreesetRes(tileres); + self.TileCache = new TileCache(); + + + + this.terrainGenerator = loadScript("vwf/model/threejs/terrain/terrainGenerator.js"); + + /* + + this.decorationManager = loadScript( "vwf/model/threejs/terrainDecorationManager.js"); + + this.decorationManager.setGenerator(this.terrainGenerator); + this.decorationManager.setRoot(this.getRoot()); + this.decorationManager.init(new THREE.Vector3(0,0,0)); + this.decorationManager.update(new THREE.Vector3(0,0,0)); + */ + + self.TileCache.terrainGenerator = this.terrainGenerator; + + if(!this.terrainType) + this.terrainType = 'NoiseTerrainAlgorithm'; + + + this.terrainGenerator.init(this.terrainType,this.terrainParams); + + + + + this.quadtree = new QuadtreeNode([-worldExtents,-worldExtents],[worldExtents,worldExtents],this.getRoot(),0,-1,minTileSize,maxTileSize); + + + this.quadtree.update([[1,1]],[]); + + + //this.quadtree.updateMesh(); + window.terrain = self; + this.counter = 0; + + this.getRoot().FrustrumCast = function(frustrum,opts){return {};}; + this.getRoot().CPUPick = function(o,d,opts){ + + + if(d[0] == 0 && d[1] == 0 && d[2] == -1) + { + + var point = new THREE.Vector3(o[0],o[1],o[2]); + var z = self.terrainGenerator.sample(point) + 1; + // if(z > o[2]) return []; + point.z = z; + + return[ + { + distance:Math.abs(o[2] - point.z), + face:null, + normal:[0,0,1], + object:self.getRoot(), + point:[point.x,point.y,point.z], + priority:1 + + } + ] + + }else + { + + + + var hits = self.quadtree.CPUPick(o,d,opts); + // for(var i = 0; i < hits.length; i++) + // { + + // var point = new THREE.Vector3(hits[i].point[0],hits[i].point[1],hits[i].point[2]); + // var z = self.terrainGenerator.sample(point); + // point[2] = z || point.z; + // } + return hits; + + } + + } + this.getRoot().SphereCast = function(o,d,opts){ + return null; + } + + + this.init = true; + + } + this.setAlgorithmData = function(data) + { + + this.terrainParams = data; + + vwf.setProperty(this.ID,'terrainParams',data); + if(!this.init) return; + this.cancelUpdates(); + this.quadtree.walk(function(n){ + + if(n.mesh) + self.needRebuild.push(n); + + }); + var rebuildlist = this.terrainGenerator.setAlgorithmData(data,self.needRebuild); + + if(rebuildlist && self.needRebuild && rebuildlist.length == self.needRebuild.length) + { + self.setTerrainAlgorithm(this.terrainType,this.terrainParams); + } + + self.needRebuild = rebuildlist || self.needRebuild; + self.rebuild(true); + + } + this.getAlgorithmData = function() + { + if(!this.init) return; + return this.terrainGenerator.getAlgorithmData(); + } + this.setMeshParams = function(min,max,size,res) + { + totalmintilesize = min; + minTileSize = totalmintilesize; + tileres = res; + maxTileSize = max; + worldExtents = size; + if(!this.init) return; + this.cancelUpdates(); + this.quadtree.walk(function(n) + { + if(n.mesh) + self.TileCache.returnMesh(n.mesh); + }); + quadtreesetRes(tileres); + this.quadtree = new QuadtreeNode([-worldExtents,-worldExtents],[worldExtents,worldExtents],this.getRoot(),0,-1,minTileSize,maxTileSize); + this.terrainGenerator.reInit(this.terrainType,this.terrainParams); + this.TileCache.clear(); + } + this.setTerrainAlgorithm = function(algo,params) + { + try{ + loadScript('vwf/model/threejs/terrain/' + algo + '.js'); + this.terrainType = algo; + this.terrainParams = params; + if(!this.init) return; + this.cancelUpdates(); + this.terrainGenerator.reInit(this.terrainType,this.terrainParams); + this.TileCache.clear(); + + this.quadtree.walk(function(n) + { + if(n.mesh) + self.TileCache.returnMesh(n.mesh); + }); + quadtreesetRes(tileres); + this.quadtree = new QuadtreeNode([-worldExtents,-worldExtents],[worldExtents,worldExtents],this.getRoot(),0,-1,minTileSize,maxTileSize); + }catch(e) + { + console.log('terrain algorithm not found'); + } + } + this.deletingNode = function() + { + + this.cancelUpdates(); + this.terrainGenerator.deinit(); + var children = this.getRoot().children; + for(var i =0 ; i < children.length; i++) + { + children[i].parent.remove(children[i]); + } + delete window._dTerrain; + } + this.cancelUpdates =function() + { + self.needRebuild = []; + self.terrainGenerator.cancel(); + + this.quadtree.walk(function(n) + { + if(n.waitingForRebuild) + debugger; + if(n.fadelist) + { + n.fadelist.forEach(function(e) + { + if(e.parent) + { + e.parent.remove(e); + self.TileCache.returnMesh(e); + e.quadnode = null; + } + window.cancelAnimationFrame(e.fadeHandle); + }); + n.fadelist = null; + if(n.mesh) + n.mesh.visible = true; + } + + }); + + this.quadtree.walk(function(n) + { + if(n.setForDesplit) + delete n.setForDesplit; + if(n.mesh && n.mesh.visible == false) + { + if(n.mesh.parent) + { + n.mesh.parent.remove(n.mesh); + self.TileCache.returnMesh(n.mesh); + } + n.mesh.quadnode = null; + n.mesh.visible = true; + n.mesh = null; + } + if(n.backupmesh) + { + n.mesh = n.backupmesh; + n.mesh.quadnode = n; + delete n.backupmesh; + } + delete n.oldmesh; + delete n.waiting_for_rebuild; + + }); + this.quadtree.walk(function(n) + { + + if(n.isSplit() && n.mesh) + { + var list = []; + n.children[0].destroy(list); + n.children[1].destroy(list); + n.children[2].destroy(list); + n.children[3].destroy(list); + n.children = []; + list.forEach(function(e) + { + e.quadnode = null; + if(e.parent) + { + e.parent.remove(e); + self.TileCache.returnMesh(e); + } + }); + } + + + }); + this.quadtree.walk(function(n) + { + + if(n.mesh) + { + n.mesh.material.uniforms.blendPercent.value = 1; + } + + + }); + } + this.removelist = []; + this.containingList = []; + self.needRebuild = []; + this.enabled = true; + this.BuildTerrainInner= function(mesh,normlen,cb) + { + this.terrainGenerator.generateTerrain(mesh,normlen,cb); + } + this.rebuildAll = function() + { + this.cancelUpdates(); + this.quadtree.walk(function(n){ + + if(n.mesh) + self.needRebuild.push(n); + + }); + self.rebuild(true); + + } + this.update = function() + { + window.requestAnimationFrame(this.update); + this.counter ++; + if(this.counter >= updateEvery && this.enabled) + { + + this.counter = 0; + var now = performance.now(); + var campos = new THREE.Vector3(); + campos.setFromMatrixPosition(vwf.models[1].model.state.nodes['http-vwf-example-com-camera-vwf-camera'].threeObject.matrixWorld); + //this.decorationManager.update(campos); + var x = campos.x; + var y = campos.y; + + var hit = this.getRoot().CPUPick([x,y,10000],[0,0,-1],{}); + var height = 0; + if(hit && hit[0]) + height = hit[0].point[2]; + var minRes = Math.pow(2,Math.floor(Math.log(Math.max(1.0,campos.z - height))/Math.LN2)-1); + minTileSize = Math.max(minRes,totalmintilesize); + var maxRes = Math.pow(2,Math.floor(Math.log(campos.z)/Math.LN2)+4); + if((this.containingList.indexOf(this.quadtree.containing([x,y])) == -1 || this.currentMinRes != minTileSize)) + { + + + + + this.currentMinRes = minTileSize; + //maxTileSize = Math.max(maxRes,2048); + this.quadtree.updateMinMax(minTileSize,maxTileSize); + //cant resize the max side on the fly -- tiles in update have already made choice + + if (self.needRebuild.length > 0 || this.terrainGenerator.countBusyWorkers() > 0) + { + this.cancelUpdates(); + //wait for next loop + return; + } + + + + this.quadtree.update([[x,y]],this.removelist); + + + + var cont = this.quadtree.containing([x,y]); + if(!cont || !cont.parent) return; + this.containing = cont.parent; + + + + + while(this.containing.NN() && this.containing.NN().depth != this.containing.depth) + this.containing.NN().split(this.removelist); + while(this.containing.SN() && this.containing.SN().depth != this.containing.depth) + this.containing.SN().split(this.removelist); + while(this.containing.EN() && this.containing.EN().depth != this.containing.depth) + this.containing.EN().split(this.removelist); + while(this.containing.WN() && this.containing.WN().depth != this.containing.depth) + this.containing.WN().split(this.removelist); + + + while(this.containing.NEN() && this.containing.NEN().depth != this.containing.depth) + this.containing.NEN().split(this.removelist); + while(this.containing.SEN() && this.containing.SEN().depth != this.containing.depth) + this.containing.SEN().split(this.removelist); + while(this.containing.SWN() && this.containing.SWN().depth != this.containing.depth) + this.containing.SWN().split(this.removelist); + while(this.containing.NWN() && this.containing.NWN().depth != this.containing.depth) + this.containing.NWN().split(this.removelist); + + if(this.containing.NN()) + this.containing.NN().split(this.removelist); + if(this.containing.EN()) + this.containing.EN().split(this.removelist); + if(this.containing.WN()) + this.containing.WN().split(this.removelist); + if(this.containing.SN()) + this.containing.SN().split(this.removelist); + + if(this.containing.NEN()) + this.containing.NEN().split(this.removelist); + if(this.containing.SEN()) + this.containing.SEN().split(this.removelist); + if(this.containing.NWN()) + this.containing.NWN().split(this.removelist); + if(this.containing.SWN()) + this.containing.SWN().split(this.removelist); + + var lowergrid = [this.containing, + this.containing.NN(), + this.containing.EN(), + this.containing.SN(), + this.containing.WN(), + this.containing.NEN(), + this.containing.NWN(), + this.containing.SEN(), + this.containing.SWN()] + + + for(var i = 0; i < lowergrid.length ; i++) + { + if(lowergrid[i]) + for(var j =0; j < lowergrid[i].children.length; j++) + { + + lowergrid[i].children[j].isMip = true; + } + + } + + var lowergridinner = [this.containing.NW(),this.containing.NE(),this.containing.SE(),this.containing.SW()]; + if(this.containing.NN()) + { + lowergridinner.push(this.containing.NN().SE()); + lowergridinner.push(this.containing.NN().SW()); + } + if(this.containing.EN()) + { + lowergridinner.push(this.containing.EN().NW()); + lowergridinner.push(this.containing.EN().SW()); + } + if(this.containing.SN()) + { + lowergridinner.push(this.containing.SN().NE()); + lowergridinner.push(this.containing.SN().NW()); + } + if(this.containing.WN()) + { + lowergridinner.push(this.containing.WN().SE()); + lowergridinner.push(this.containing.WN().NE()); + } + + + + + if(this.containing.NEN()) + lowergridinner.push(this.containing.NEN().SW()); + if(this.containing.NWN()) + lowergridinner.push(this.containing.NWN().SE()); + if(this.containing.SEN()) + lowergridinner.push(this.containing.SEN().NW()); + if(this.containing.SWN()) + lowergridinner.push(this.containing.SWN().NE()); + + + + + + this.containingList = lowergridinner; + this.quadtree.balance(this.removelist); + this.quadtree.balance(this.removelist); + //this.quadtree.cleanup(this.removelist); + var nodes = this.quadtree.getBottom(); + + //immediately remove old nodes that are now too big + this.quadtree.walk(function(n) + { + if(n.max[0]-n.min[0] > maxTileSize && n.setForDesplit) + { + + var list = []; + n.cleanup(list); + list.forEach(function(e) + { + if(e.parent) + { + e.parent.remove(e); + self.TileCache.returnMesh(e); + + } + e.quadnode = null; + }); + n.children = []; + delete n.setForDesplit; + } + }); + var newleaves = this.quadtree.getLeaves(); + + for(var i = 0; i < newleaves.length; i++) + { + + if(!newleaves[i].mesh) + { + if(newleaves[i].max[0] - newleaves[i].min[0] < maxTileSize) + self.needRebuild.push(newleaves[i]); + } + else if(newleaves[i].sideNeeded() != newleaves[i].side) + { + self.needRebuild.push(newleaves[i]); + } + + } + + self.needRebuild.sort(function(a,b) + { + return (a.max[0] - a.min[0]) - (b.max[0] - b.min[0]); + + }); + + + //walk the parents of the nodes whose meshs are removing, and + //note how may children that nodes has to rebuild + var splitting = []; + this.quadtree.walk(function(n) + { + if(n.backupmesh && n.isSplit()) + { + splitting.push(n); + + var count = 0; + n.getLeaves().forEach(function(e) + { + if(!e.mesh) + count++; + + }); + n.waiting_for_rebuild = count; + } + + }); + + + self.rebuild(); + + var time = performance.now() - now; + console.log(time); + } + + } + }.bind(this); + + self.rebuildCallback = function(tile,force) + { + //if forcing update, don't bother with any of this drama, the mesh was rebuilt, but has no children or parents taht need updating + if(!force) + { + //now that I've drawn my tile, I can remove my children. + var list = [] + tile.cleanup(list) + tile.debug(0,0,0); + + if(list.length > 0) + tile.mesh.visible = false; + var o = tile.mesh; + list.forEach(function(e) + { + tile.fadelist = list; + if(e.parent) + { + e.material.uniforms.debugColor.value.r = 0; + e.material.uniforms.debugColor.value.g = 0; + e.material.uniforms.debugColor.value.b = 1; + e.quadnode.mesh = null; + e.material.uniforms.blendPercent.value = 1; + var fade = function() + { + e.material.uniforms.blendPercent.value -= .1 * window.deltaTime/30; + if(e.material.uniforms.blendPercent.value > 0) + { + e.fadeHandle = window.requestAnimationFrame(fade); + }else + { + if(e.parent) + { + e.parent.remove(e); + e.quadnode = null; + self.TileCache.returnMesh(e); + } + o.visible = true + o.material.uniforms.blendPercent.value = 1; + + tile.fadelist = null; + } + }; + e.fadeHandle = window.requestAnimationFrame(fade); + + } + }); + + var e = tile.mesh; + e.material.uniforms.blendPercent.value = 1; + + var p = tile.parent; + //look up for the node I'm replaceing + while(p && !p.waiting_for_rebuild) + p = p.parent; + if(p) + { + if(p.waiting_for_rebuild > 1) + { + + p.waiting_for_rebuild--; + tile.mesh.visible = false; + window.cancelAnimationFrame(tile.fade); + p.debug(1,.5,.5); + } + else if(p.waiting_for_rebuild == 1) + { + + if(p.backupmesh && p.backupmesh.parent) + { + + var e = p.backupmesh; + + e.quadnode = null; + if(e.parent) + { + e.parent.remove(e); + self.TileCache.returnMesh(e); + } + + + + p.backupmesh = null; + } + + delete p.waiting_for_rebuild; + p.walk(function(l) + { + //this really should be true now! + if(l.mesh) + { + l.debug(0,1,1); + l.mesh.visible = true; + var o = l.mesh; + o.material.uniforms.blendPercent.value = 0; + var fade = function() + { + + o.material.uniforms.blendPercent.value += .1 * window.deltaTime/30; + if(o.material.uniforms.blendPercent.value < 1) + { + window.requestAnimationFrame(fade); + }else + { + l.debug(0,0,0); + o.material.uniforms.blendPercent.value = 1; + } + + }; + window.requestAnimationFrame(fade); + } + }); + } + } + } + + self.rebuild(force); + + + }.bind(self) + + self.rebuild = function(force) + { + + while(self.terrainGenerator.countFreeWorkers() > 0 && self.needRebuild.length > 0) + { + + var tile1 = self.needRebuild.shift(); + tile1.debug(0,1,0); + tile1.updateMesh(self.rebuildCallback,force); + } + + }.bind(self); + + + + + + + + + + + + + + + + + + + + + + + + + this.callingMethod = function(methodName,args) + { + if(methodName == 'setPoint') + { + if(args.length == 6) + { + var cp = this.controlPoints[args[0]]; + cp.x = args[1]; + cp.y = args[2]; + cp.z = args[3]; + cp.dist = args[4]; + cp.falloff = args[5]; + } + else if(args.length == 2) + { + this.controlPoints[args[0]] = args[1]; + } + + return true; + } + if(methodName == 'getPoint') + { + return this.controlPoints[args[0]]; + } + if(methodName == 'getPointCount') + { + return this.controlPoints.length; + } + + } + this.settingProperty = function(propertyName,propertyValue) + { + + if( this.terrainGenerator) + { + + var algoprops = this.terrainGenerator.getAlgorithmData(); + if(algoprops && algoprops.hasOwnProperty(propertyName)) + { + algoprops[propertyName] = propertyValue; + self.setAlgorithmData(algoprops); + } + } + + if(propertyName == 'controlPoints') + { + this.controlPoints = propertyValue; + } + if(propertyName == 'terrainType') + { + self.setTerrainAlgorithm(propertyValue,this.terrainParams); + } + if(propertyName == 'terrainParams') + { + this.terrainParams = propertyValue; + } + if(propertyName == 'Extents') + { + worldExtents = parseFloat(propertyValue); + self.setMeshParams(totalmintilesize,maxTileSize,worldExtents,tileres); + } + if(propertyName == 'tileRes') + { + tileres = parseFloat(propertyValue);; + self.setMeshParams(totalmintilesize,maxTileSize,worldExtents,tileres); + } + if(propertyName == 'maxTileSize') + { + maxTileSize = parseFloat(propertyValue);; + self.setMeshParams(totalmintilesize,maxTileSize,worldExtents,tileres); + } + if(propertyName == 'minTileSize') + { + totalmintilesize = parseFloat(propertyValue);; + self.setMeshParams(totalmintilesize,maxTileSize,worldExtents,tileres); + } + } + this.gettingProperty = function(propertyName) + { + + if( this.terrainGenerator) + { + var algoprops = this.terrainGenerator.getAlgorithmData(); + if(algoprops && algoprops[propertyName] != undefined) + return algoprops[propertyName]; + } + if(propertyName == 'controlPoints') + { + return this.controlPoints ; + } + if(propertyName == 'type') + { + return 'Terrain'; + } + if(propertyName == 'terrainType') + { + return this.terrainType; + } + if(propertyName == 'terrainParams') + { + return this.terrainParams; + } + if(propertyName == 'Extents') + { + return worldExtents; + } + if(propertyName == 'tileRes') + { + return tileres; + } + if(propertyName == 'maxTileSize') + { + return maxTileSize; + } + if(propertyName == 'minTileSize') + { + return totalmintilesize; + } + if(propertyName == 'EditorData') + { + + var editordata = { + a_:{displayname:'Meshing Parameters',type:'sectionTitle'}, + a__active:{displayname : 'Enabled',property:'enabled',type:'check',min:-10,max:10,step:.01}, + a_extents:{ + displayname : 'Extents (meters^2)', + property:'Extents', + type:'prompt', + min:1024, + max:1048576, + step:1024 + }, + a_tileRes:{ + displayname : 'Tile Res', + property:'tileRes', + type:'choice', + values:[2,4,8,16,26], + labels:['2','4','8','16','26'], + }, + a_maxTileSize:{ + displayname : 'Max Tile Size (m^2)', + property:'maxTileSize', + type:'choice', + values:[128,256,512,1024,2048,4096,8192], + labels:['128','256','512','1024','2048','4096','8192'], + }, + a_minTileSize:{ + displayname : 'Min Tile Size (m^2)', + property:'minTileSize', + type:'choice', + values:[16,32,64,128,256,512], + labels:['16','32','64','128','256','512'], + }, + a__generator:{ + displayname : 'Terrain Generator', + property:'terrainType', + type:'choice', + labels:['Height Map','Random Noise','Paging Database'], + values:['heightmapTerrainAlgorithm','NoiseTerrainAlgorithm','CesiumTerrainAlhorithm'] + }, + + } + + var algodata = this.terrainGenerator.getEditorData(); + for(i in algodata) + { + editordata[i] = algodata[i]; + } + return editordata; + + } + } + + //must be defined by the object + this.getRoot = function() + { + return this.rootnode; + } + this.rootnode = new THREE.Object3D(); + this.inherits = ['vwf/model/threejs/transformable.js']; + //this.Build(); + } + //default factory code + return function(childID, childSource, childName) { + //name of the node constructor + + //GUI should prevent us from getting here. + if(window._dTerrain) + { + console.log('Only one terrain can be created at a time'); + return; + } + + return new terrain(childID, childSource, childName); + } +})(); \ No newline at end of file diff --git a/support/client/lib/vwf/model/threejs/terrain/terrainGenerator.js b/support/client/lib/vwf/model/threejs/terrain/terrainGenerator.js new file mode 100644 index 000000000..68dd754a8 --- /dev/null +++ b/support/client/lib/vwf/model/threejs/terrain/terrainGenerator.js @@ -0,0 +1,356 @@ +new (function(){ + + + var MAXWORKERS = 8; + var IDLE = 0; + var RUNNING =1; + var CANCELED = 2; + + this.terrainDataReceived = function(dataBack,mesh,readers,cb) + { + + + var now = performance.now(); + this.regencount++; + var geo = mesh.geometry; + + var vertices = readers[0]; + var normals = readers[1]; + var everyOtherZ =readers[2]; + var everyOtherNormal =readers[3]; + + var ai,aj,ac,bi,bj,bc,ci,cj,cc; + var res = mesh.res; + + + for(var i = 0; i < mesh.res * mesh.res; i++) + { + mesh.material.attributes.ONormal.value[i].x = normals[i*3]; + mesh.material.attributes.ONormal.value[i].y = normals[i*3+1]; + mesh.material.attributes.ONormal.value[i].z = normals[i*3+2]; + } + for(var i = 0; i < mesh.res * mesh.res; i++) + { + mesh.material.attributes.everyOtherNormal.value[i].x = everyOtherNormal[i*3]; + mesh.material.attributes.everyOtherNormal.value[i].y = everyOtherNormal[i*3+1]; + mesh.material.attributes.everyOtherNormal.value[i].z = everyOtherNormal[i*3+2]; + } + + + + for(var i = 0; i < mesh.res * mesh.res; i++) + { + mesh.material.attributes.everyOtherZ.value[i] = everyOtherZ[i]; + } + for(var i = 0; i < mesh.res * mesh.res; i++) + { + mesh.geometry.vertices[i].z = vertices[i*3+2]; + mesh.material.attributes.everyZ.value[i] = mesh.geometry.vertices[i].z; + } + + for(var i = mesh.res * mesh.res; i < mesh.geometry.vertices.length; i++) + { + mesh.material.attributes.everyOtherNormal.value[i].x = everyOtherNormal[(mesh.geometry.vertices[i].topedge)*3]; + mesh.material.attributes.everyOtherNormal.value[i].y = everyOtherNormal[(mesh.geometry.vertices[i].topedge)*3+1]; + mesh.material.attributes.everyOtherNormal.value[i].z = everyOtherNormal[(mesh.geometry.vertices[i].topedge)*3+2]; + } + for(var i = mesh.res * mesh.res; i < mesh.geometry.vertices.length; i++) + { + mesh.material.attributes.everyOtherZ.value[i] = everyOtherZ[(mesh.geometry.vertices[i].topedge)] - (mesh.scale.x) * 5; + } + for(var i = mesh.res * mesh.res; i < mesh.geometry.vertices.length; i++) + { + //set the position cpu side for collision + mesh.geometry.vertices[i].z = vertices[(mesh.geometry.vertices[i].topedge)*3+2] - (mesh.scale.x) * 5; + //but upload vert z via attribute to move less data over bus + mesh.material.attributes.everyZ.value[i] = mesh.geometry.vertices[i].z; + } + for(var i = mesh.res * mesh.res; i < mesh.geometry.vertices.length; i++) + { + mesh.material.attributes.ONormal.value[i].x = normals[(mesh.geometry.vertices[i].topedge)*3]; + mesh.material.attributes.ONormal.value[i].y = normals[(mesh.geometry.vertices[i].topedge)*3+1]; + mesh.material.attributes.ONormal.value[i].z = normals[(mesh.geometry.vertices[i].topedge)*3+2]; + } + + mesh.material.attributes.everyOtherNormal.needsUpdate = true; + mesh.material.attributes.everyOtherZ.needsUpdate = true; + mesh.material.attributes.everyZ.needsUpdate = true; + mesh.material.attributes.ONormal.needsUpdate = true; + + //geo.verticesNeedUpdate = true; + geo.computeBoundingSphere(); + geo.computeBoundingBox(); + + // geo.normalsNeedUpdate = true; + // geo.dirtyMesh = true; + + var time = performance.now() - now; + this.totalregentime += time; + //console.log(this.totalregentime/this.regencount); + if(cb) + cb(); + + } + + this.countFreeWorkers = function() + { + if(this.waitingForInit === true) + return 0; + var i = 0; + for(var j = 0; j < MAXWORKERS; j++) + { + if(this.currentMesh[j] === null) + i++; + } + return i; + } + this.countBusyWorkers = function() + { + if(this.waitingForInit === true) + return MAXWORKERS; + return MAXWORKERS - this.countFreeWorkers(); + } + this.sendTerrainRequest = function(data,mesh,cb) + { + var i = -1; + for(var j = 0; j < MAXWORKERS; j++) + { + if(this.currentMesh[j] === null) + var i = j; + } + //if(this.currentMesh[i]) + // debugger; + this.currentCB[i] = cb; + this.currentMesh[i] = mesh; + this.currentID[i] = Math.floor(Math.random()*10000000); + this.currentState[i] = RUNNING; + var request = {command:'generateTerrain',data:data,id:this.currentID[i],buffers:this.currentBuffers[i]}; + this.worker[i].postMessage(request,request.buffers); + + } + this.sample = function(vert,matrix,res) + { + if(this.waitingForInit == true) return 0; + return this.terrainAlgorithm.displace(vert); + } + this.message = function(e) + { + if(e.data.command == "console") + console.log(e.data.data); + if(e.data.command == "terrainData") + { + + var i = this.currentID.indexOf(e.data.id); + this.currentBuffers[i]=[e.data.data.vertices,e.data.data.normals,e.data.data.everyOtherZ,e.data.data.everyOtherNormal]; + if(this.currentCB[i] && this.currentState[i] != CANCELED) + { + + this.currentState[i] = IDLE; + var cb = this.currentCB[i]; + var mesh = this.currentMesh[i]; + this.currentCB[i] = null; + this.currentMesh[i] = null; + // console.log("response from tile"); + + + + + if(mesh) + { + + this.readers[i] = []; + this.readers[i][0] = new Float32Array(e.data.data.vertices); + this.readers[i][1] = new Float32Array(e.data.data.normals); + this.readers[i][2] = new Float32Array(e.data.data.everyOtherZ); + this.readers[i][3] = new Float32Array(e.data.data.everyOtherNormal); + + this.terrainDataReceived(e.data.data,mesh,this.readers[i],cb); + } + }else + { + this.currentState[i] = IDLE; + this.currentCB[i] = null; + this.currentMesh[i] = null; + console.log("response from canceled tile"); + } + } + + } + this.cancel = function() + { + for(var i = 0; i < MAXWORKERS; i++) + { + if(this.currentCB[i]) + this.currentCB[i](true); + + this.currentState[i] = CANCELED; + + } + } + this.deinit = function() + { + for(var i = 0; i < MAXWORKERS; i++) + { + if(this.worker && this.worker[i]) + this.worker[i].terminate(); + } + } + this.init = function(type,params) + { + for(var i = 0; i < MAXWORKERS; i++) + { + if(this.worker && this.worker[i]) + this.worker[i].terminate(); + } + + this.regencount = 0; + this.totalregentime = 0; + + this.worker = []; + this.currentCB = []; + this.currentMesh = []; + this.currentID = []; + this.currentBuffers = []; + this.readers = []; + this.currentState = []; + + try{ + loadScript('vwf/model/threejs/terrain/' + type + '.js'); + }catch(e) + { + type = 'NoiseTerrainAlgorithm'; + loadScript('vwf/model/threejs/terrain/' + type + '.js'); + } + this.terrainAlgorithm = new (eval(type))(); + + + + this.terrainAlgorithm.importScript = function(url) + { + + var xhr = $.ajax("vwf/model/threejs/" + url,{async:false}); + return eval(xhr.responseText); + + + }.bind(this.terrainAlgorithm); + + this.terrainAlgorithm.materialRebuildCB = function() + { + self.TileCache.rebuildAllMaterials(); + } + + var poolSideData; + + + var init = function(psd) + { + this.waitingForInit = false; + this.terrainAlgorithm.init(psd); + this.terrainAlgorithm.setAlgorithmDataPool(params); + + + for(var i = 0; i < MAXWORKERS; i++) + { + this.worker[i] = new Worker("vwf/model/threejs/terrain/terrainGeneratorThread.js"); + this.worker[i].addEventListener('message',this.message.bind(this)); + this.worker[i].postMessage({command:'init',data:{type:type,params:(params)}}); + this.worker[i].postMessage({command:'threadInit',data:psd}); + this.worker[i].postMessage({command:'setAlgorithmData',data:(params)}); + this.currentCB[i] = null; + this.currentMesh[i] = null; + this.currentID[i] = null; + this.currentBuffers[i] = []; + } + }.bind(this); + + if(this.terrainAlgorithm.poolInit) + poolSideData = this.terrainAlgorithm.poolInit(init,params); + + + + if(poolSideData === false) + this.waitingForInit = true; + else + { + init(poolSideData); + } + + + } + + this.getMaterialUniforms = function(mesh,matrix) + { + if(this.terrainAlgorithm.getMaterialUniforms) + return this.terrainAlgorithm.getMaterialUniforms(mesh,matrix); + return null; + } + this.getDiffuseFragmentShader = function(mesh,matrix) + { + if(this.terrainAlgorithm.getDiffuseFragmentShader) + return this.terrainAlgorithm.getDiffuseFragmentShader(mesh,matrix); + return null; + } + this.getNormalFragmentShader = function(mesh,matrix) + { + if(this.terrainAlgorithm.getNormalFragmentShader) + return this.terrainAlgorithm.getNormalFragmentShader(mesh,matrix); + return null; + + } + this.updateMaterial = function(m,d) + { + + if(this.terrainAlgorithm.updateMaterial) + this.terrainAlgorithm.updateMaterial(m,d); + } + this.reInit = function(type,params) + { + this.init(type,params); + } + this.getAlgorithmData = function() + { + return this.terrainAlgorithm.getAlgorithmDataPool(); + } + this.getEditorData = function() + { + return this.terrainAlgorithm.getEditorData(); + } + this.setAlgorithmData = function(params,updatelist) + { + var list = this.terrainAlgorithm.setAlgorithmDataPool(params,updatelist); + for(var i = 0; i < MAXWORKERS; i++) + { + this.worker[i].postMessage({command:'setAlgorithmData',data:(params)}); + } + return list; + } + this.test = function() + { + this.worker[0].postMessage({command:'test',data:null}); + } + + + this.generateTerrain=function(mesh, normlen, cb){ + + + var vertices = []; + + for(var i = 0; i < mesh.res * mesh.res; i++) + { + vertices.push(mesh.geometry.vertices[i].x); + vertices.push(mesh.geometry.vertices[i].y); + vertices.push(mesh.geometry.vertices[i].z); + } + var data = {}; + data.vertices = vertices; + mesh.updateMatrix(); + + data.matrix = mesh.matrix.clone().elements; + + this.sendTerrainRequest(data,mesh,cb); + //var dataBack = this.generateTerrainSimWorker(data) + + + } + +})() \ No newline at end of file diff --git a/support/client/lib/vwf/model/threejs/terrain/terrainGeneratorThread.js b/support/client/lib/vwf/model/threejs/terrain/terrainGeneratorThread.js new file mode 100644 index 000000000..9649ea332 --- /dev/null +++ b/support/client/lib/vwf/model/threejs/terrain/terrainGeneratorThread.js @@ -0,0 +1,335 @@ +self.init = function(data) +{ + var generateType = data.type; + if(!self.THREE) + { + + importScripts('../three.js'); + } + importScripts(generateType + '.js'); + + this.terrainAlgorithm = new (eval(generateType))(data.params); + this.terrainAlgorithm.importScript = function(url) + { + importScripts("../"+url); + } + console.log('init complete'); + +} + +self.threadInit = function(data) +{ + this.terrainAlgorithm.init(data); + console.log('terrainAlgorithm init complete'); +} + +self.setAlgorithmData = function(data) +{ + this.terrainAlgorithm.setAlgorithmData(data); + console.log('terrainAlgorithm setdata complete'); +} + + +this.vertices = []; +this.normals = []; +this.everyOtherZ = []; +this.everyOtherNormal = []; +this.normals = []; +this.normalsL2 = []; +this.heights = []; +this.generateTerrainSimWorker = function(datain,buffers) +{ + this.vertices.length=0; + this.normals.length=0; + this.everyOtherZ.length=0; + this.everyOtherNormal.length=0; + var matrix = new THREE.Matrix4(); + matrix.elements = datain.matrix; + + for(var i =0; i < datain.vertices.length; i+=3) + { + var ver = new THREE.Vector3(); + ver.x = datain.vertices[i]; + ver.y = datain.vertices[i+1]; + ver.z = datain.vertices[i+2]; + vertices.push(ver); + } + + + var vertoffset = 4 * datain.matrix[0]; + vertoffset2 = vertoffset * 2; + var invmat = new THREE.Matrix4(); + + invmat = invmat.getInverse(matrix.clone().setPosition(new THREE.Vector3())); + invmat.elements[12] = 0; + invmat.elements[13] = 0; + invmat.elements[14] = 0; + var res = Math.floor(Math.sqrt(vertices.length)); + normals.length=0; + normalsL2.length=0; + heights.length=0; + + + for(var j = 0; j < res; j++) + { + if(!normals[j]) + normals[j] = []; + else + normals[j].length = 0; + + if(!normalsL2[j]) + normalsL2[j] = []; + else + normalsL2[j].length = 0; + + if(!heights[j]) + heights[j] = []; + else + heights[j].length = 0; + for(var l = 0; l < res; l++) + { + + var i = j * res + l; + var vertn = vertices[i].clone(); + + vertn = vertn.applyMatrix4(matrix); + + var vertx0 = new THREE.Vector3(vertn.x-vertoffset,vertn.y,vertn.z); + var verty0 = new THREE.Vector3(vertn.x,vertn.y-vertoffset,vertn.z); + var vertx1 = new THREE.Vector3(vertn.x+vertoffset,vertn.y,vertn.z); + var verty1 = new THREE.Vector3(vertn.x,vertn.y+vertoffset,vertn.z); + var vert11 = new THREE.Vector3(vertn.x+vertoffset,vertn.y+vertoffset,vertn.z); + var vert00 = new THREE.Vector3(vertn.x,vertn.y+vertoffset,vertn.z); + + var vertx02 = new THREE.Vector3(vertn.x-vertoffset2,vertn.y,vertn.z); + var verty02 = new THREE.Vector3(vertn.x,vertn.y-vertoffset2,vertn.z); + var vertx12 = new THREE.Vector3(vertn.x+vertoffset2,vertn.y,vertn.z); + var verty12 = new THREE.Vector3(vertn.x,vertn.y+vertoffset2,vertn.z); + var vert112 = new THREE.Vector3(vertn.x,vertn.y+vertoffset2,vertn.z); + var vert002 = new THREE.Vector3(vertn.x+vertoffset2,vertn.y+vertoffset2,vertn.z); + + var verts = [vertn,vertx0,verty0,vertx1,verty1,vert11,vert00,vertx02,verty02,vertx12,verty12,vert112,vert002]; + var norms = []; + for(var k = 0; k < verts.length; k++) + { + + var vert = verts[k].clone(); + var vert2 = verts[k].clone(); + var vert3 = verts[k].clone(); + if(k < 6) + { + vert2.x += vertoffset; + vert3.y += vertoffset; + } + else + { + vert2.x += vertoffset2; + vert3.y += vertoffset2; + } + + var z1 = this.terrainAlgorithm.displace(vert,matrix,res); + var z2 = this.terrainAlgorithm.displace(vert2,matrix,res); + var z3 = this.terrainAlgorithm.displace(vert3,matrix,res); + + var n; + if(k < 6) + n = new THREE.Vector3((z1-z2),(z1-z3),vertoffset); + else + n = new THREE.Vector3((z1-z2),(z1-z3),vertoffset2); + n.normalize(); + norms.push(n); + verts[k].z = z1; + } + vertices[i].z = vertn.z + //var n = vertn.clone().sub(vertx).cross(vertn.clone().sub(verty)).normalize(); + var n = norms[1].add(norms[2]).add(norms[3]).add(norms[4]).add(norms[5]); + n = n.multiplyScalar(1/5); + n.normalize(); + + var n2 = norms[6].add(norms[7]).add(norms[8]).add(norms[9]).add(norms[10]); + n2 = n2.multiplyScalar(1/5); + n2.normalize(); + //n = n.applyMatrix4(invmat); + + + normals[j][l] = n; + normalsL2[j][l] = n2; + heights[j][l] = vertn.z; + } + } + + for(var j = 0; j < res; j++) + { + + for(var l = 0; l < res; l++) + { + //remove when not perect stitching + + { + if(l % 2 ==1 && j % 2 !=1) + { + var z00 = heights[j-0 >= 0? j-0 : j][l+1 < res? l+1 : l]; + var z11 = heights[j+0 < res? j+0 : j][l-1 >= 0? l-1 : l]; + var z = (z00+ z11)/2; + + + var n00 = normalsL2[j-0 >= 0? j-0 : j][l+1 < res? l+1 : l]; + var n11 = normalsL2[j+0 < res? j+0 : j][l-1 >= 0? l-1 : l]; + + + var norm = n00.clone().add(n11).multiplyScalar(.5).normalize(); + + everyOtherNormal[j * res + l] = norm; + everyOtherZ[j * res + l] = z; + } + else if(l % 2 !=1 && j % 2 ==1) + { + var z00 = heights[j-1 >= 0? j-1 : j][l+0 < res? l+0 : l]; + var z11 = heights[j+1 < res? j+1 : j][l-0 >= 0? l-0 : l]; + var z = (z00+ z11)/2; + + + var n00 = normalsL2[j-1 >= 0? j-1 : j][l+0 < res? l+0 : l]; + var n11 = normalsL2[j+1 < res? j+1 : j][l-0 >= 0? l-0 : l]; + + + var norm = n00.clone().add(n11).multiplyScalar(.5).normalize(); + + everyOtherNormal[j * res + l] = norm; + everyOtherZ[j * res + l] = z; + } + else if(l % 2 ==1 && j % 2 ==1) + { + var z00 = heights[j-1 >= 0? j-1 : j][l+1 < res? l+1 : l]; + var z11 = heights[j+1 < res? j+1 : j][l-1 >= 0? l-1 : l]; + var z001 = heights[j+1 < res? j+1 : j][l+1 < res? l+1 : l]; + var z111 = heights[j-1 >= 0? j-1 : j][l-1 >= 0? l-1 : l]; + var z = (z00+ z11 + z001 + z111)/4; + + + var n00 = normalsL2[j-1 >= 0? j-1 : j][l+1 < res? l+1 : l]; + var n11 = normalsL2[j+1 < res? j+1 : j][l-1 >= 0? l-1 : l]; + var n001 = normalsL2[j+1 < res? j+1 : j][l+1 < res? l+1 : l]; + var n111 = normalsL2[j-1 >= 0? j-1 : j][l-1 >= 0? l-1 : l]; + + var norm = n00.clone().add(n11).add(n111).add(n001).multiplyScalar(.25).normalize(); + + everyOtherNormal[j * res + l] = norm; + everyOtherZ[j * res + l] = z; + } + else + { + + + everyOtherNormal[j * res + l] = normalsL2[j][l]; + everyOtherZ[j * res + l] = heights[j][l]; + } + + if(vertices[j * res + l].x > .95 || vertices[j * res + l].x < .05 || vertices[j * res + l].y > .95 || vertices[j * res + l].y < .05) + { + var n00 = normalsL2[j-1 >= 0? j-1 : j][l+1 < res? l+1 : l]; + var n11 = normalsL2[j+1 < res? j+1 : j][l-1 >= 0? l-1 : l]; + var n001 = normalsL2[j+1 < res? j+1 : j][l+1 < res? l+1 : l]; + var n111 = normalsL2[j-1 >= 0? j-1 : j][l-1 >= 0? l-1 : l]; + + var norm = n00.clone().add(n11).add(n111).add(n001).multiplyScalar(.25).normalize(); + everyOtherNormal[j * res + l] = norm;; + //everyOtherZ[j * res + l] = heights[j][l]; + } + + + } + + } + } + if(buffers.length == 0) + { + console.log('new buffers'); + buffers[0] = new ArrayBuffer(vertices.length * 3 * 4); + buffers[1] = new ArrayBuffer(vertices.length * 3* 4); + buffers[2] = new ArrayBuffer(vertices.length* 4); + buffers[3] = new ArrayBuffer(vertices.length * 3* 4); + } + var ret = + { + "vertices" : buffers[0], + "normals" : buffers[1], + "everyOtherZ" : buffers[2], + "everyOtherNormal" : buffers[3] + }; + + var retVertices = new Float32Array(ret.vertices); + var retNormals = new Float32Array(ret.normals); + var retEveryOtherZ = new Float32Array(ret.everyOtherZ); + var retEveryOtherNormal = new Float32Array(ret.everyOtherNormal); + + var c = 0; + for(var i =0; i < vertices.length; i++) + { + retVertices[c] = (vertices[i].x); + retVertices[c+1] = (vertices[i].y); + retVertices[c+2] = (vertices[i].z); + c += 3; + } + c = 0; + for(var i =0; i < everyOtherZ.length; i++) + { + retEveryOtherZ[c] = (everyOtherZ[i]); + c++; + } + c = 0; + for(var i =0; i < everyOtherNormal.length; i++) + { + retEveryOtherNormal[c] = (everyOtherNormal[i].x); + retEveryOtherNormal[c+1] = (everyOtherNormal[i].y); + retEveryOtherNormal[c+2] = (everyOtherNormal[i].z); + c += 3; + } + c = 0; + for(var j = 0; j < res; j++) + { + for(var l = 0; l < res; l++) + { + retNormals[c] = (normals[j][l].x); + retNormals[c+1] = (normals[j][l].y); + retNormals[c+2] = (normals[j][l].z); + c += 3; + } + } + return ret; +} + + + + +var console = self; + +self.log = function(data) +{ + self.postMessage({command:'console',data:data}); +} +self.generateTerrain = function(terraindata,id,buffers) +{ + + var data = self.generateTerrainSimWorker(terraindata,buffers); + + self.setTimeout(function() + { + var response = {command:'terrainData',data:data,id:id}; + self.postMessage(response,[response.data.vertices,response.data.normals,response.data.everyOtherZ,response.data.everyOtherNormal]); + },Math.random() * 100); +} + +self.addEventListener('message', function(e) { + + var window = self; + + var data = e.data; + var command = e.data.command; + + if(self[command]) + self[command](e.data.data,e.data.id,e.data.buffers); + + + +}, false); \ No newline at end of file diff --git a/support/client/lib/vwf/model/threejs/terrain/terrainQuadtree.js b/support/client/lib/vwf/model/threejs/terrain/terrainQuadtree.js new file mode 100644 index 000000000..fabbcb171 --- /dev/null +++ b/support/client/lib/vwf/model/threejs/terrain/terrainQuadtree.js @@ -0,0 +1,815 @@ + var tileres = 32; + var SW = 0; + var SE = 1; + var NW = 3; + var NE = 2; + var self; + var perfectstitch = false; + +function quadtreesetSelf(s) { self = s}; +function quadtreesetRes(s) {tileres = s;} +function QuadtreeNode(min,max,root,depth,quad,minsize,maxsize) + { + + + this.minTileSize = minsize; + this.maxTileSize = maxsize; + if(!depth) + this.depth = 1; + else + this.depth = depth; + this.children = []; + this.mesh = null; + this.min = min; + this.max = max; + this.quadrent = quad; + + this.THREENode = root; + this.c = [this.min[0] + (this.max[0]-this.min[0])/2,this.min[1] + (this.max[1]-this.min[1])/2] + } + QuadtreeNode.prototype.SW = function() + { + return this.children[SW]; + } + QuadtreeNode.prototype.SE = function() + { + return this.children[SE]; + } + QuadtreeNode.prototype.NW = function() + { + return this.children[NW]; + } + QuadtreeNode.prototype.NE = function() + { + return this.children[NE]; + } + QuadtreeNode.prototype.child = function(quad) + { + return this.children[quad]; + } + QuadtreeNode.prototype.sibling = function(quad) + { + return this.parent.child(quad); + } + QuadtreeNode.prototype.twodeep = function() + { + if(!this.isSplit()) + return false; + + for(var i = 0; i < 4; i++) + { + if(this.children[i].isSplit()) + return true; + + } + return false; + } + QuadtreeNode.prototype.balance = function(removelist) + { + + + + var leaves = this.getLeavesB(); + var leafcount = leaves.length; + var count = 0; + var expectedLeaves = 4 * this.maxDepth(); + while(leaves.length > 0) + { + var l = leaves.shift(); + if(!l) continue; + + var nn = l.NN(); + var sn = l.SN(); + var en = l.EN(); + var wn = l.WN(); + if((nn && nn.twodeep() )||(sn && sn.twodeep())||(en && en.twodeep())||(wn && wn.twodeep())) + { + + l.split(removelist); + leaves.splice(0,0,l.NW()); + leaves.splice(0,0,l.NE()); + leaves.splice(0,0,l.SW()); + leaves.splice(0,0,l.SE()); + + + } + + count++; + + + + } + var newleafcount = this.getLeavesB().length; + console.log('quadtree balance splits', count,"expected leaves",expectedLeaves, "starting leaves",leafcount,"ending leaves", newleafcount); + } + + QuadtreeNode.prototype.northNeighbor = function() + { + var p = this; + while(p.quadrent != SW && p.quadrent != SE) + { + if(!p.parent) return null; + p = p.parent; + } + var walk = -1; + if(p.quadrent == SW) + { + p = p.sibling(NW); + walk = SE; + } + else if(p.quadrent == SE) + { + p = p.sibling(NE); + walk = SW; + } + while(p && p.depth < this.depth && p.isSplit()) + { + if(p.c[0] > this.c[0]) + p = p.child(SW); + else if(p.c[0] < this.c[0]) + p = p.child(SE); + } + + return p; + + } + QuadtreeNode.prototype.NN = QuadtreeNode.prototype.northNeighbor; + + QuadtreeNode.prototype.southNeighbor = function() + { + var p = this; + while(p.quadrent != NW && p.quadrent != NE) + { + if(!p.parent) return null; + p = p.parent; + } + var walk = -1; + if(p.quadrent == NW) + { + p = p.sibling(SW); + walk = NE; + } + else if(p.quadrent == NE) + { + p = p.sibling(SE); + walk = NW; + } + while(p && p.depth < this.depth && p.isSplit()) + { + if(p.c[0] > this.c[0]) + p = p.child(NW); + else if(p.c[0] < this.c[0]) + p = p.child(NE); + } + + return p; + + } + QuadtreeNode.prototype.SN = QuadtreeNode.prototype.southNeighbor; + + QuadtreeNode.prototype.eastNeighbor = function() + { + var p = this; + while(p.quadrent != NW && p.quadrent != SW) + { + if(!p.parent) return null; + p = p.parent; + } + var walk = -1; + if(p.quadrent == NW) + { + p = p.sibling(NE); + walk = SW; + } + else if(p.quadrent == SW) + { + p = p.sibling(SE); + walk = NW; + } + while(p && p.depth < this.depth && p.isSplit()) + { + if(p.c[1] > this.c[1]) + p = p.child(SW); + else if(p.c[1] < this.c[1]) + p = p.child(NW); + } + return p; + + } + QuadtreeNode.prototype.EN = QuadtreeNode.prototype.eastNeighbor; + + QuadtreeNode.prototype.westNeighbor = function() + { + var p = this; + while(p.quadrent != NE && p.quadrent != SE) + { + if(!p.parent) return null; + p = p.parent; + } + var walk = -1; + if(p.quadrent == NE) + { + p = p.sibling(NW); + walk = NE; + } + else if(p.quadrent == SE) + { + p = p.sibling(SW); + walk = SE; + } + while(p && p.depth < this.depth && p.isSplit()) + { + if(p.c[1] > this.c[1]) + p = p.child(SE); + else if(p.c[1] < this.c[1]) + p = p.child(NE); + } + return p; + + } + QuadtreeNode.prototype.WN = QuadtreeNode.prototype.westNeighbor; + + QuadtreeNode.prototype.northEastNeighbor = function() + { + var nn = this.NN(); + if(nn) + return nn.EN(); + return null; + + } + QuadtreeNode.prototype.NEN = QuadtreeNode.prototype.northEastNeighbor; + + QuadtreeNode.prototype.southEastNeighbor = function() + { + var sn = this.SN(); + if(sn) + return sn.EN(); + return null; + + } + QuadtreeNode.prototype.SEN = QuadtreeNode.prototype.southEastNeighbor; + + QuadtreeNode.prototype.northWestNeighbor = function() + { + var nn = this.NN(); + if(nn) + return nn.WN(); + return null; + + } + QuadtreeNode.prototype.NWN = QuadtreeNode.prototype.northWestNeighbor; + + QuadtreeNode.prototype.southWestNeighbor = function() + { + var sn = this.SN(); + if(sn) + return sn.WN(); + return null; + + } + QuadtreeNode.prototype.SWN = QuadtreeNode.prototype.southWestNeighbor; + + QuadtreeNode.prototype.maxDepth = function() + { + if(!this.isSplit()) + { + return this.depth; + } + else + { + return Math.max( + this.children[0].maxDepth(), + this.children[1].maxDepth(), + this.children[2].maxDepth(), + this.children[3].maxDepth() + + ); + } + + + } + + QuadtreeNode.prototype.getLeavesB = function(list) + { + if(!list) + list = []; + + + if(!this.isSplit()) + list.push(this); + else + { + for(var i = 0; i < this.children.length; i ++) + { + this.children[i].getLeavesB(list); + } + //so, there once was a bug with the balance algorithm that caused certain tiles to not split when they should have + //this fixed it, but at the cost of dramatically increasing the cost of the algorithm. + //currently calling balance twice, wich fixes the issue with much less overhead. + //this.children[0].getLeavesB(list); + } + + return list; + } + QuadtreeNode.prototype.getLeaves = function(list) + { + if(!list) + list = []; + + + if(!this.isSplit()) + list.push(this); + else + { + for(var i = 0; i < this.children.length; i ++) + { + this.children[i].getLeaves(list); + } + + } + + return list; + } + QuadtreeNode.prototype.sideNeeded = function() + { + var nn = this.NN(); + var sn = this.SN(); + var wn = this.WN(); + var en = this.EN(); + + var lowresside = 0; + if(nn && nn.depth < this.depth) + nn = true; + else + nn = false; + + if(sn && sn.depth < this.depth) + sn = true; + else + sn = false; + if(en && en.depth < this.depth) + en = true; + else + en = false; + if(wn && wn.depth < this.depth) + wn = true; + else + wn = false; + + if(!nn && !sn && !wn &&!en) + return 0; + if(nn && !sn && !wn &&!en) + return 1; + if(!nn && sn && !wn &&!en) + return 2; + if(!nn && !sn && wn &&!en) + return 3; + if(!nn && !sn && !wn &&en) + return 4; + + if(nn && !sn && !wn &&en) + return 5; + if(!nn && sn && !wn &&en) + return 6; + if(nn && !sn && wn &&!en) + return 7; + if(!nn && sn && wn &&!en) + return 8; + + return 0; + } + QuadtreeNode.prototype.meshNeeded = function(i) + { + if(i == 0) return 0; + if(i<=4) return 1; + if(i<=8) return 2; + + } + QuadtreeNode.prototype.getRotation = function(i) + { + + if(i ==0) return 0; + if(i== 1) return -Math.PI/2; + if(i== 2) return Math.PI/2; + if(i== 4) return Math.PI; + if(i== 7) return -Math.PI/2; + if(i== 5) return -Math.PI; + if(i== 6) return Math.PI/2; + return 0; + } + QuadtreeNode.prototype.debug = function(r,g,b) + { + if(this.mesh) + { + this.mesh.material.uniforms.debugColor.value.r = r; + this.mesh.material.uniforms.debugColor.value.g = g; + this.mesh.material.uniforms.debugColor.value.b = b; + } + } + QuadtreeNode.prototype.intersectBounds = function(o,d) + { + //TODO: are these loose bounds necessary? + var min = [this.min[0]-.00,this.min[1]-.00,-Infinity]; + var max = [this.max[0]+.00,this.max[1]+.00,Infinity]; + var dirfrac = [0,0,0]; + var t; + // d is unit direction vector of ray + dirfrac[0] = 1.0 / d[0]; + dirfrac[1] = 1.0 / d[1]; + dirfrac[2] = 1.0 / d[2]; + // this.min is the corner of AABB with Math.minimal coordinates - left bottom, this.max is Math.maximal corner + // o is origin of ray + var t1 = (min[0] - o[0])*dirfrac[0]; + var t2 = (max[0] - o[0])*dirfrac[0]; + var t3 = (min[1] - o[1])*dirfrac[1]; + var t4 = (max[1] - o[1])*dirfrac[1]; + var t5 = (min[2] - o[2])*dirfrac[2]; + var t6 = (max[2] - o[2])*dirfrac[2]; + + var tMathmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6)); + var tMathmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6)); + + // if tMath.max < 0, ray (line) is intersecting AABB, but whole AABB is behing us + if (tMathmax < 0) + { + t = tMathmax; + return false; + } + + // if tMath.min > tMath.max, ray doesn't intersect AABB + if (tMathmin > tMathmax) + { + t = tMathmax; + return false; + } + + t = tMathmin; + return true; + + + return true; // if we made it here, there was an intersection - YAY + + } + QuadtreeNode.prototype.CPUPick = function(o,d,opts) + { + + + var hits = []; + + if(!this.intersectBounds(o,d)) + return hits; + + if(this.mesh) + { + hits = this.mesh.CPUPick(o,d,opts); + }else + { + if(this.NE()) + hits = hits.concat(this.NE().CPUPick(o,d,opts)); + if(this.NW()) + hits = hits.concat(this.NW().CPUPick(o,d,opts)); + if(this.SE()) + hits = hits.concat(this.SE().CPUPick(o,d,opts)); + if(this.SW()) + hits = hits.concat(this.SW().CPUPick(o,d,opts)); + } + return hits; + } + QuadtreeNode.prototype.updateMesh = function(cb,force) + { + var rebuilt = false; + if(!this.isSplit()) + { + //if I'm a leaf node, but the side I need (for sticthing) is not the one I have, or I have no mesh (because I'm new) generate my mesh + var neededSide = this.sideNeeded(); + + if(this.mesh && (neededSide != this.side && perfectstitch == false) ) + { + this.side = neededSide; + this.mesh.material.uniforms.side.value = this.side; + + + cb(this,force); + return; + } + else if(!this.mesh || (neededSide != this.side) || force ) + { + //if were just switching sides, backup the old mesh + //it will be shown when the new one is ready + this.badsidemesh = null; + if((this.mesh && neededSide != this.side && perfectstitch == true)) + { + this.debug(1,0,0); + this.badsidemesh = this.mesh; + this.mesh = null; + } + + //if I'm a leaf, and I'm small enough + //note, we should never arrive here for meshes that don't need an update. + if(this.max[0] - this.min[0] < this.maxTileSize) + { + if(!force) + { + var res = tileres; + + var scale = this.max[0] - this.min[0]; + + this.side = neededSide; + //get the right mesh off the cache + + if(perfectstitch == true) + this.mesh = self.TileCache.getMesh(res,this.meshNeeded(this.side)); + else + this.mesh = self.TileCache.getMesh(res,0); + if(perfectstitch == false) + this.mesh.material.uniforms.side.value = this.side; + else + this.mesh.material.uniforms.side.value = -1; + //scale and rotate to fit + this.mesh.scale.x = scale/100; + this.mesh.scale.y = scale/100; + this.mesh.scale.z = 1;//scale/100; + if(perfectstitch == true) + this.mesh.rotation.z = this.getRotation(this.side); + this.debug(0,0,0); + this.mesh.quadnode = this; + if(self.removelist.indexOf(this.mesh)>-1) + self.removelist.splice(self.removelist.indexOf(this.mesh),1); + + this.mesh.position.x = this.c[0]; + this.mesh.position.y = this.c[1]; + this.mesh.position.z = 1; + + //go ahead and add it to the world + rebuilt = true; + this.mesh.updateMatrix(); + this.mesh.updateMatrixWorld(true); + + this.THREENode.add(this.mesh,true); + this.mesh.visible = false; + this.mesh.waitingForRebuild = true; + self.terrainGenerator.updateMaterial(this.mesh,this.depth); + } + + // displace the mesh + // the callback will indicate if this mesh was canceled before the thread returned with the updated mesh + self.BuildTerrainInner(this.mesh,(this.max[0] - this.min[0])/tileres,function(cancel) + { + + + if(!cancel) + { + this.debug(.5,.5,.5); + this.mesh.waitingForRebuild = false; + //so, the mesh has been updated, go ahead and make it visible + this.mesh.visible = true; + //if there is a badside mesh, then this is an instant swap, and badside mesh can be removed + if(this.badsidemesh) + { + this.badsidemesh.parent.remove(this.badsidemesh); + this.badsidemesh.quadnode = null; + self.TileCache.returnMesh(this.badsidemesh); + this.badsidemesh = null; + + } + this.mesh.geometry.dirtyMesh = true; + this.mesh.geometry.BoundingSphere = null; + this.mesh.geometry.BoundingBox = null; + this.mesh.geometry.RayTraceAccelerationStructure = null; + //go head and callback into the rebuild look to deal with fadein/out stuff, and dispatch the next tile update + cb(this,force); + }else + { + //so, we got canceled before the worker returned + //the worker will finish, but the callback will not fire when it does, and the data will just be lost + if(this.badsidemesh) + { + //if we were doing a side swap (for seam stitching) just forgot it, hide the new tile, link back to the old + this.mesh.parent.remove(this.mesh); + self.TileCache.returnMesh(this.mesh); + this.mesh.quadnode = null; + this.mesh = this.badsidemesh; + this.badsidemesh = null; + + } + } + }.bind(this)); + + //prevents CB from happening immediately. very important + return; + } + } + }else + { + //if I'm not split (not a leaf) but have a mesh, that mesh needs to be removed + //changes in the way updates are dispatched means we should never execute here + if(this.mesh ) + { + this.mesh.quadnode = null; + if(this.mesh.parent) + this.mesh.parent.remove(this.mesh); + self.TileCache.returnMesh(this.mesh); + this.mesh.quadnode = null; + this.mesh = null; + } + } + + //go ahead and callback (should not get here) + if(cb) + cb(rebuilt,force); + } + QuadtreeNode.prototype.cleanup = function(removelist) + { + this.walk(function(n) + { + if(n.setForDesplit) + { + + for(var i=0; i < n.children.length; i++) + n.children[i].destroy(removelist); + n.children = []; + n._issplit = false; + delete n.setForDesplit; + } + }); + } + QuadtreeNode.prototype.isSplit = function() {if(this.setForDesplit) return false; return this.children.length > 0} + QuadtreeNode.prototype.split = function(removelist) + { + + if(this.setForDesplit) + { + delete this.setForDesplit; + + } + if(this.isSplit()) + return; + if(this.mesh) + { + //this.mesh.parent.remove(this.mesh); + //removelist.push(this.mesh); + this.backupmesh = this.mesh; + this.mesh = null; + + } + + var sw = new QuadtreeNode([this.min[0],this.min[1]],[this.c[0],this.c[1]],this.THREENode,this.depth+1,SW,this.minTileSize,this.maxTileSize); + var se = new QuadtreeNode([this.c[0],this.min[1]],[this.max[0],this.c[1]],this.THREENode,this.depth+1,SE,this.minTileSize,this.maxTileSize); + var nw = new QuadtreeNode([this.min[0],this.c[1]],[this.c[0],this.max[1]],this.THREENode,this.depth+1,NW,this.minTileSize,this.maxTileSize); + var ne = new QuadtreeNode([this.c[0],this.c[1]],[this.max[0],this.max[1]],this.THREENode,this.depth+1,NE,this.minTileSize,this.maxTileSize); + + sw.parent = this; + se.parent = this; + nw.parent = this; + ne.parent = this; + + this.children[SW] = sw; + this.children[SE] = se; + this.children[NW] = nw; + this.children[NE] = ne; + + this._issplit = true; + } + QuadtreeNode.prototype.updateMinMax = function(min,max) + { + this.minTileSize = min; + this.maxTileSize = max; + for(var i = 0; i < this.children.length; i++) + this.children[i].updateMinMax(min,max); + } + + QuadtreeNode.prototype.deSplit = function(removelist) + { + //this.walk(function(n) + //{ + + + //}); + + for(var i=0; i < this.children.length; i++) + this.children[i].deSplit(removelist); + this.setForDesplit = true; + } + QuadtreeNode.prototype.destroy = function(removelist) + { + if(this.mesh) + { + //this.mesh.parent.remove(this.mesh); + removelist.push(this.mesh); + if(this.backupmesh) + removelist.push(this.backupmesh); + this.oldmesh = this.mesh; + this.mesh = null; + } + for(var i=0; i < this.children.length; i++) + this.children[i].destroy(removelist); + } + QuadtreeNode.prototype.contains = function(point) + { + + var tempmin = this.min; + var tempmax = this.max; + if(tempmin[0] <= point[0] && tempmax[0] > point[0] && + tempmin[1] <= point[1] && tempmax[1] > point[1]) + return true; + return false; + } + QuadtreeNode.prototype.loosecontains = function(point) + { + + var tempmin = [this.min[0] - (this.max[0] - this.min[0])/2 , this.min[1] - (this.max[1] - this.min[1])/2] + var tempmax = [this.max[0] + (this.max[0] - this.min[0])/2 , this.max[1] + (this.max[1] - this.min[1])/2] + if(tempmin[0] < point[0] && tempmax[0] > point[0] && + tempmin[1] < point[1] && tempmax[1] > point[1]) + return true; + return false; + } + QuadtreeNode.prototype.containing = function(point) + { + if(this.contains(point) && !this.isSplit()) + return this; + if(this.isSplit()) + { + if(this.NW().contains(point)) + return this.NW().containing(point); + if(this.NE().contains(point)) + return this.NE().containing(point); + if(this.SW().contains(point)) + return this.SW().containing(point); + if(this.SE().contains(point)) + return this.SE().containing(point); + + } + return null; + } + QuadtreeNode.prototype.walk = function(cb) + { + cb(this); + if(this.isSplit()) + for(var i =0 ; i < this.children.length; i++) + { + this.children[i].walk(cb); + + } + + } + QuadtreeNode.prototype.getBottom = function(list) + { + if(!list) + list = []; + this.walk(function(node) + { + if(node.bottom) + list.push(node); + + }); + return list; + } + //walk down the graph, unspliting nodes that to not contain target points, and spliting nodes that do + QuadtreeNode.prototype.update = function(campos,removelist) + { + var cont = false + for(var i =0; i < campos.length; i++) + { + cont = cont || this.contains(campos[i]); + } + if(cont) + { + + if(!this.isSplit()) + { + if(this.max[0]-this.min[0] > this.minTileSize) + { + this.split(removelist); + + for(var i=0; i < this.children.length; i++) + if(this.children[i].max[0]-this.children[i].min[0] < this.minTileSize) + this.children[i].bottom = true;; + + + }else + { + + } + + }else + { + if(this.max[0]-this.min[0] < this.minTileSize) + { + this.deSplit(removelist); + } + } + }else + { + if(this.isSplit()) + { + this.deSplit(removelist); + + } + + } + if(this.isSplit()) + for(var i=0; i < this.children.length; i++) + this.children[i].update(campos,removelist); + } + \ No newline at end of file diff --git a/support/client/lib/vwf/model/threejs/terrain/terrainTileCache.js b/support/client/lib/vwf/model/threejs/terrain/terrainTileCache.js new file mode 100644 index 000000000..6224a241a --- /dev/null +++ b/support/client/lib/vwf/model/threejs/terrain/terrainTileCache.js @@ -0,0 +1,615 @@ +function TileCache() + { + this.tiles = {}; + + + //default material expects all computation done cpu side, just renders + // note that since the color, size, spin and orientation are just linear + // interpolations, they can be done in the shader + var vertShader_default = + + + + + "vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); } float snoise(vec2 v) { const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); vec2 i = floor(v + dot(v, C.yy) ); vec2 x0 = v - i + dot(i, C.xx); vec2 i1; i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); vec4 x12 = x0.xyxy + C.xxzz; x12.xy -= i1; i = mod289(i); vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + i.x + vec3(0.0, i1.x, 1.0 )); vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); m = m*m ; m = m*m ; vec3 x = 2.0 * fract(p * C.www) - 1.0; vec3 h = abs(x) - 0.5; vec3 ox = floor(x + 0.5); vec3 a0 = x - ox; m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); vec3 g; g.x = a0.x * x0.x + h.x * x0.y; g.yz = a0.yz * x12.xz + h.yz * x12.yw; return 130.0 * dot(m, g); }"+ + + "float getNoise(vec2 tpos)"+ + "{"+ + "float res = 0.0;"+ + "res += snoise(vec2(tpos.y / 10000.0,tpos.x/10000.0)) * 1000.0;\n" + + "res += snoise(vec2(tpos.y / 1000.0,tpos.x/1000.0)) * 100.0;\n" + + "res += snoise(vec2(tpos.y / 100.0,tpos.x/100.0)) * 10.0;\n" + + "res += snoise(vec2(tpos.y / 10.0,tpos.x/10.0)) * 1.0;\n" + + "return res;"+ + "}"+ + "varying vec3 vFogPosition;"+ + "varying vec3 opos;"+ + "varying vec3 npos;"+ + "varying vec3 n;"+ + "varying vec3 wN;"+ + "varying vec3 coordsScaleB;"+ + "varying vec3 coordsScaleA;"+ + "uniform float blendPercent;\n" + + "uniform float coordA;\n" + + "uniform float coordB;\n" + + + + "varying vec4 mvPosition;\n"+ + "varying vec3 debug;\n"+ + "uniform vec3 debugColor;\n"+ + "uniform float side;\n"+ + "attribute vec3 everyOtherNormal;\n"+ + "attribute vec3 ONormal;\n"+ + "attribute float everyOtherZ;\n"+ + "attribute float everyZ;\n"+ + "void main() {\n"+ + " float z = mix(everyOtherZ,everyZ,blendPercent);\n"+ + " vFogPosition = (modelMatrix * vec4(position.xy,z,1.0)).xyz; \n"+ + "opos = vec3(position.xy,z);\n"+ + "npos = vFogPosition;\n"+ + //"npos.z += getNoise(vFogPosition.xy*200.0)/50.0; \n"+ + "coordsScaleB = npos/coordB;\n"+ + "coordsScaleA = npos/coordA;\n"+ + "float edgeblend = 0.0;"+ + + "debug = vec3(0.0,0.0,0.0);\n"+ + " if(side == 1.0 && position.y > 49.0) {edgeblend = 1.0; debug = vec3(1.0,1.0,1.0);}\n" + + " if(side == 2.0 && position.y < -49.0) {edgeblend = 1.0; debug = vec3(1.0,1.0,1.0);}\n" + + " if(side == 3.0 && position.x < -49.0) {edgeblend = 1.0; debug = vec3(1.0,1.0,1.0);}\n" + + " if(side == 4.0 && position.x > 49.0) {edgeblend = 1.0; debug = vec3(1.0,1.0,1.0);}\n" + + " if(side == 5.0 && (position.y > 49.0 || position.x > 49.0)) {edgeblend = 1.0; debug = vec3(1.0,1.0,1.0);}\n" + + " if(side == 5.0 && (position.y > 49.0 || position.x > 49.0)) {edgeblend = 1.0; debug = vec3(1.0,1.0,1.0);}\n" + + " if(side == 6.0 && (position.y < -49.0 || position.x > 49.0)) {edgeblend = 1.0; debug = vec3(1.0,1.0,1.0);}\n" + + " if(side == 7.0 && (position.y > 49.0 || position.x < -49.0)) {edgeblend = 1.0; debug = vec3(1.0,1.0,1.0);}\n" + + " if(side == 8.0 && (position.y < -49.0 || position.x < -49.0)) {edgeblend = 1.0; debug = vec3(1.0,1.0,1.0);}\n" + + + + "wN = mix(everyOtherNormal,ONormal,blendPercent);\n"+ + "if(edgeblend == 1.0) {z=everyOtherZ;wN = everyOtherNormal; }\n"+ + + "n = normalMatrix * wN\n;"+ + + "n = normalize(n);\n"+ + " mvPosition = modelViewMatrix * vec4( position.x,position.y,z, 1.0 );\n"+ + + "debug = wN;\n"+ + " gl_Position = projectionMatrix * mvPosition;\n"+ + "} \n"; + var fragShader_default_start = + + + + "#extension GL_OES_standard_derivatives : enable\n" + + "#if MAX_DIR_LIGHTS > 0\n"+ + + + //"#define USE_FOG" : "", + //"#define FOG_EXP2" : "", + + "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\n"+ + "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n"+ + + + "uniform vec3 fogColor;"+ + "uniform int fogType;"+ + "uniform int renderMode;"+ + + "uniform float fogDensity;"+ + "uniform float fogNear;"+ + "uniform float fogFar;"+ + "uniform float vFalloff;"+ + "uniform float vFalloffStart;"+ + "uniform vec3 vAtmosphereColor;\n" + //vec3(0.0, 0.02, 0.04); + "uniform vec3 vHorizonColor;\n" + //vec3(0.88, 0.94, 0.999); + "uniform vec3 vApexColor;\n" + //vec3(0.78, 0.82, 0.999) + "uniform float vAtmosphereDensity;\n" + //.0005 + + + "uniform float coordA;\n" + + "uniform float coordB;\n" + + + //"horizonColor = fogColor;\n"+ + //"zenithColor = vec3(0.78, 0.82, 0.999);\n"+ + //"gl_FragColor.xyz = aerialPerspective(gl_FragColor.xyz, distance(pos,cameraPosition),cameraPosition.xzy, normalize(pos-cameraPosition).xzy);\n"+ + "vec3 horizonColor = vHorizonColor;\n"+ + "vec3 zenithColor = vApexColor;\n"+ + + "vec3 atmosphereColor(vec3 rayDirection){\n"+ + " float a = max(0.0, dot(rayDirection, vec3(0.0, 1.0, 0.0)));\n"+ + " vec3 skyColor = mix(horizonColor, zenithColor, a);\n"+ + " float sunTheta = max( dot(rayDirection, directionalLightDirection[0].xzy), 0.0 );\n"+ + " return skyColor+directionalLightColor[0]*4.0*pow(sunTheta, 16.0)*0.5;\n"+ + "}\n"+ + + "vec3 applyFog(vec3 albedo, float dist, vec3 rayOrigin, vec3 rayDirection){\n"+ + " float fogDensityA = fogDensity ;\n"+ + " float fog = exp((-rayOrigin.y*vFalloff)*fogDensityA) * (1.0-exp(-dist*rayDirection.y*vFalloff*fogDensityA))/(rayDirection.y*vFalloff);\n"+ + " return mix(albedo, fogColor, clamp(fog, 0.0, 1.0));\n"+ + "}\n"+ + + "vec3 aerialPerspective(vec3 albedo, float dist, vec3 rayOrigin, vec3 rayDirection){\n"+ + " rayOrigin.y += vFalloffStart;\n" + + " vec3 atmosphere = atmosphereColor(rayDirection)+vAtmosphereColor; \n"+ + " atmosphere = mix( atmosphere, atmosphere*.75, clamp(1.0-exp(-dist*vAtmosphereDensity), 0.0, 1.0));\n"+ + " vec3 color = mix( applyFog(albedo, dist, rayOrigin, rayDirection), atmosphere, clamp(1.0-exp(-dist*vAtmosphereDensity), 0.0, 1.0));\n"+ + " return color;\n"+ + "} \n"+ + + + + "#endif\n"+ + + + + (["const float C1 = 0.429043;", + "const float C2 = 0.511664;", + "const float C3 = 0.743125;", + "const float C4 = 0.886227;", + "const float C5 = 0.247708;", + + // Constants for Old Town Square lighting + "const vec3 L00 = vec3( 0.871297, 0.875222, 0.864470);", + "const vec3 L1m1 = vec3( 0.175058, 0.245335, 0.312891);", + "const vec3 L10 = vec3( 0.034675, 0.036107, 0.037362);", + "const vec3 L11 = vec3(-0.004629, -0.029448, -0.048028);", + "const vec3 L2m2 = vec3(-0.120535, -0.121160, -0.117507);", + "const vec3 L2m1 = vec3( 0.003242, 0.003624, 0.007511);", + "const vec3 L20 = vec3(-0.028667, -0.024926, -0.020998);", + "const vec3 L21 = vec3(-0.077539, -0.086325, -0.091591);", + "const vec3 L22 = vec3(-0.161784, -0.191783, -0.219152);"].join('\n'))+ + + "varying vec4 mvPosition;\n"+ + "varying vec3 debug;\n"+ + "varying vec3 vFogPosition;"+ + "varying vec3 n;"+ + "varying vec3 wN;"+ + "varying vec3 npos;"+ + "varying vec3 opos;"+ + "uniform vec3 ambientLightColor;"+ + "varying vec3 coordsScaleB;"+ + "varying vec3 coordsScaleA;"; + + var fragShader_default_end = + + "vec4 packFloatVec4(const in float depth)\n"+ + "{\n"+ + " const vec4 bit_shift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);\n"+ + " const vec4 bit_mask = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);\n"+ + " vec4 res = fract(depth * bit_shift);\n"+ + " res -= res.xxyz * bit_mask;\n"+ + " return res;\n"+ + "}\n"+ + + "void main() {\n"+ + " if(renderMode == 1){ gl_FragColor = packFloatVec4(vFogPosition.z/1000.0); return; }\n"+ + " vec4 diffuse = getTexture(npos,normalize(wN),opos.xy/100.0 + 0.5);\n"+ + " diffuse.a = 1.0;\n"+ + " vec3 nn = (viewMatrix * normalize(vec4(wN,0.0))).xyz;\n"+ + " nn = getNormal(npos,nn,opos.xy/100.0 + 0.5,wN);\n"+ + " vec3 light = vec3(0.0,0.0,0.0);\n"+ + "vec3 tnorm = ( vec4(nn,0.0) * viewMatrix).xyz;\n"+ + "vec3 shAmbient = C1 * L22 * (tnorm.x * tnorm.x - tnorm.y * tnorm.y) +"+ + " C3 * L20 * tnorm.z * tnorm.z +"+ + " C4 * L00 -"+ + " C5 * L20 +"+ + " 2.0 * C1 * L2m2 * tnorm.x * tnorm.y +"+ + " 2.0 * C1 * L21 * tnorm.x * tnorm.z +"+ + " 2.0 * C1 * L2m1 * tnorm.y * tnorm.z +"+ + " 2.0 * C2 * L11 * tnorm.x +"+ + " 2.0 * C2 * L1m1 * tnorm.y + "+ + " 2.0 * C2 * L10 * tnorm.z;"+ + + " vec4 ambient = vec4(shAmbient * length(ambientLightColor) * .5 ,1.0);\n"+ + + + " #if MAX_DIR_LIGHTS > 0\n"+ + " vec3 vLightDir = normalize(viewMatrix * vec4(directionalLightDirection[0],0.0)).xyz;\n"+ + " vec3 vEyeDir = normalize((viewMatrix * vec4(normalize(vFogPosition-cameraPosition ),0.0)).xyz);\n"+ + " vec3 vReflectDir = normalize(reflect(vLightDir,nn));\n"+ + " float phong =pow( min(1.0,max(0.0,dot(vReflectDir,vEyeDir) + .3)),1.0 );\n"+ + " light += directionalLightColor[0] * clamp(dot(nn, vLightDir),0.0,1.0);\n"+ + " #endif\n"+ + + + " gl_FragColor = ambient * diffuse + diffuse * vec4(light.xyz,1.0);\n"+ + "gl_FragColor.a = 1.0;\n"+ + // "#ifdef USE_FOG\n"+ + + + + //"gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );\n"+ + + //"gl_FragColor.xyz = nn;\n"+ + "gl_FragColor.xyz = aerialPerspective(gl_FragColor.xyz, distance(vFogPosition,cameraPosition),cameraPosition.xzy, normalize(vFogPosition-cameraPosition).xzy);\n"+ + // "#endif\n"+ + //"gl_FragColor = vec4(nn.rgb,1.0);\n"+ + "}\n"; + + //the default shader - the one used by the analytic solver, just has some simple stuff + //note that this could be changed to do just life and lifespan, and calculate the + //size and color from to uniforms. Im not going to bother + + + //uniforms_default.texture.value.wrapS = uniforms_default.texture.value.wrapT = THREE.RepeatWrapping; + + this.getDefaultDiffuseString = function() + { + return "vec4 getTexture(vec3 coords, vec3 norm, vec2 uv) {return vec4(1.0,1.0,1.0,1.0);}\n"; + + } + this.getDefaultNormalString = function() + { + return "vec3 getNormal(vec3 coords, vec3 norm, vec2 uv) {return norm;}\n"; + } + this.getMat = function() + { + + + + var algorithmShaderStringDiffuse = this.terrainGenerator.getDiffuseFragmentShader?this.terrainGenerator.getDiffuseFragmentShader():''; + var algorithmShaderStringNormal = this.terrainGenerator.getNormalFragmentShader? this.terrainGenerator.getNormalFragmentShader() : ''; + var algorithmUniforms = this.terrainGenerator.getMaterialUniforms(); + + var uniforms_default = THREE.UniformsUtils.merge( [ + THREE.UniformsLib[ "fog" ],{ + + + ambientLightColor: { type: "fv", value: [] }, + + directionalLightColor: { type: "fv", value: [] }, + directionalLightDirection: { type: "fv", value: [] }, + + pointLightColor: { type: "fv", value: [] }, + pointLightPosition: { type: "fv", value: [] }, + pointLightDistance: { type: "fv1", value: [] }, + + spotLightColor: { type: "fv", value: [] }, + spotLightPosition: { type: "fv", value: [] }, + spotLightDistance: { type: "fv", value: [] }, + spotLightDirection: { type: "fv1", value: [] }, + spotLightAngleCos: { type: "fv1", value: [] }, + spotLightExponent: { type: "fv1", value: [] }, + + //amosphere settings + vAtmosphereColor: { type: "c", value: new THREE.Color(0x9999CC) }, + vHorizonColor: { type: "c", value: new THREE.Color(0x9999CC) }, + vApexColor: { type: "c", value: new THREE.Color(0x9999CC) }, + vFalloffStart : { type: "f", value: 0.00000 }, + vAtmosphereDensity : { type: "f", value: 0.001 }, + + hemisphereLightSkyColor: { type: "fv", value: [] }, + hemisphereLightGroundColor: { type: "fv", value: [] }, + hemisphereLightDirection: { type: "fv", value: [] }, + + noiseSampler: { type: "t", value: THREE.ImageUtils.loadTexture( "terrain/bestnoise.png" ) }, + "side" : { type: "f", value: 0 }, + + "blendPercent" : { type: "f", value: 0.00000 }, + "coordA" : { type: "f", value: 100 }, + "coordB" : { type: "f", value: 10 }, + "renderMode" : { type: "i", value: 0 }, + debugColor : { type: "c", value: new THREE.Color( 0xffff0f ) }, + + }]); + for(var i in algorithmUniforms) + uniforms_default[i] = algorithmUniforms[i]; + var attributes_default = { + everyOtherNormal: { type: 'v3', value: [] }, + everyOtherZ: { type: 'f', value: [] }, + everyZ: { type: 'f', value: [] }, + ONormal: { type: 'v3', value: [] }, + }; + var mat = new THREE.ShaderMaterial( { + uniforms: uniforms_default, + attributes: attributes_default, + vertexShader: vertShader_default, + fragmentShader: (fragShader_default_start + (algorithmShaderStringDiffuse || this.getDefaultDiffuseString()) + (algorithmShaderStringNormal || this.getDefaultNormalString()) + fragShader_default_end) + + }); + mat.lights = true; + mat.fog = true; + + mat.uniforms.fogDensity.value = .001; + mat.uniforms.fogColor.value = new THREE.Color('#EEEEFF'); + + + + + uniforms_default.noiseSampler.value.wrapS = uniforms_default.noiseSampler.value.wrapT = THREE.RepeatWrapping; + //mat.wireframe = true; + + this.mat = mat; + return mat; + + // this.mat = new THREE.MeshPhongMaterial(); + // this.mat.color.r = .5; + // this.mat.color.g = .5; + // this.mat.color.b = .5; + // this.mat.depthCheck = false; + // this.mat.wireframe = false; + // this.mat.transparent = true; + } + + + this.buildMesh0 = function(size,res) + { + + var geo = new THREE.Geometry(); + var step = size/(res); + var count = 0; + for(var i=0; i <= size+step+step; i += step) + { + + for(var j=0; j <= size+step+step; j +=step) + { + var z = 0; + var x = i-size/2; + var y = j-size/2; + var v = new THREE.Vector3(x,y,z); + geo.vertices.push(v); + } + count++; + } + + for(var i=0; i < count-3; i++) + { + for(var j=0; j < count-3; j++) + { + + var x = i; + var y = j; + + var f = new THREE.Face3(x*(count) + y,(x+1)*count+y,(x+1)*count+y+1);//,x*count+y+1) + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + f.vertexNormals.push(new THREE.Vector3(1,0,0)); + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + + f.normal = new THREE.Vector3(0,0,1); + geo.faces.push(f); + + var f = new THREE.Face3(x*(count) + y,(x+1)*count+y+1,(x)*count+y+1);//,x*count+y+1) + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + f.vertexNormals.push(new THREE.Vector3(1,0,0)); + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + + f.normal = new THREE.Vector3(0,0,1); + geo.faces.push(f); + //f.vertexNormals.push(new THREE.Vector3(0,0,1)); + } + } + + for(var i=0; i < count-3; i++) + { + for(var j=0; j < count-3; j++) + { + if(j == 0) + { + + var x = i; + var y = j; + + var A = x*(count) + y; + var B = (x+1)*count+y + var vertC = geo.vertices[B].clone(); + vertC.topedge = B; + vertC.z = -10; + geo.vertices.push(vertC); + var C = geo.vertices.length-1; + var f = new THREE.Face3(B,A,C);//,x*count+y+1) + + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + f.vertexNormals.push(new THREE.Vector3(1,0,0)); + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + + f.normal = new THREE.Vector3(0,0,1); + geo.faces.push(f); + + + var vertD = geo.vertices[A].clone(); + vertD.topedge = A; + vertD.z = -10; + geo.vertices.push(vertD); + var D = geo.vertices.length-1; + var f = new THREE.Face3(C,A,D);//,x*count+y+1) + + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + f.vertexNormals.push(new THREE.Vector3(1,0,0)); + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + + f.normal = new THREE.Vector3(0,0,1); + geo.faces.push(f); + //f.vertexNormals.push(new THREE.Vector3(0,0,1)); + } + if(i == 0) + { + + var x = i; + var y = j; + + var A = x*(count) + y; + var B = (x)*count+y+1 + var vertC = geo.vertices[B].clone(); + vertC.topedge = B; + vertC.z = -10; + geo.vertices.push(vertC); + var C = geo.vertices.length-1; + var f = new THREE.Face3(A,B,C);//,x*count+y+1) + + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + f.vertexNormals.push(new THREE.Vector3(1,0,0)); + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + + f.normal = new THREE.Vector3(0,0,1); + geo.faces.push(f); + + var vertD = geo.vertices[A].clone(); + vertD.topedge = A; + vertD.z = -10; + geo.vertices.push(vertD); + var D = geo.vertices.length-1; + var f = new THREE.Face3(C,D,A);//,x*count+y+1) + + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + f.vertexNormals.push(new THREE.Vector3(1,0,0)); + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + + f.normal = new THREE.Vector3(0,0,1); + geo.faces.push(f); + + } + + if(i == count - 4) + { + + var x = i+1; + var y = j; + + var A = x*(count) + y; + var B = (x)*count+y+1 + var vertC = geo.vertices[B].clone(); + vertC.z = -10; + vertC.topedge = B; + geo.vertices.push(vertC); + var C = geo.vertices.length-1; + var f = new THREE.Face3(B,A,C);//,x*count+y+1) + + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + f.vertexNormals.push(new THREE.Vector3(1,0,0)); + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + + f.normal = new THREE.Vector3(0,0,1); + geo.faces.push(f); + + var vertD = geo.vertices[A].clone(); + vertD.z = -10; + geo.vertices.push(vertD); + var D = geo.vertices.length-1; + var f = new THREE.Face3(D,C,A);//,x*count+y+1) + vertD.topedge = A; + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + f.vertexNormals.push(new THREE.Vector3(1,0,0)); + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + + f.normal = new THREE.Vector3(0,0,1); + geo.faces.push(f); + + } + + if(j == count-4) + { + + var x = i; + var y = j+1; + + var A = x*(count) + y; + var B = (x+1)*count+y + var vertC = geo.vertices[B].clone(); + vertC.topedge = B; + vertC.z = -10; + geo.vertices.push(vertC); + var C = geo.vertices.length-1; + var f = new THREE.Face3(B,C,A);//,x*count+y+1) + + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + f.vertexNormals.push(new THREE.Vector3(1,0,0)); + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + + f.normal = new THREE.Vector3(0,0,1); + geo.faces.push(f); + + + var vertD = geo.vertices[A].clone(); + vertD.topedge = A; + vertD.z = -10; + geo.vertices.push(vertD); + var D = geo.vertices.length-1; + var f = new THREE.Face3(C,D,A);//,x*count+y+1) + + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + f.vertexNormals.push(new THREE.Vector3(1,0,0)); + f.vertexNormals.push(new THREE.Vector3(0,0,1)); + + f.normal = new THREE.Vector3(0,0,1); + geo.faces.push(f); + //f.vertexNormals.push(new THREE.Vector3(0,0,1)); + } + } + } + geo.computeCentroids(); + return geo; + } + this.returnMesh = function(mesh) + { + + if(mesh.quadnode) + mesh.quadnode = null; + if(mesh.parent) + mesh.parent.remove(mesh); + } + this.clear = function() + { + this.tiles = []; + } + this.rebuildAllMaterials = function() + { + for(var j in this.tiles) + { + if(this.tiles[j]) + for(var i = 0; i < this.tiles[j].length; i++) + { + var mat = this.getMat(); + mat.attributes = this.tiles[j][i].material.attributes; + this.tiles[j][i].material = mat; + } + } + + } + this.getMesh = function(res,side) + { + if(this.tiles[res]) + for(var i = 0; i < this.tiles[res].length; i++) + if(this.tiles[res][i].quadnode == null) + { + return this.tiles[res][i]; + } + if(!this.tiles[res]) + this.tiles[res] = []; + + + var newtile; + + newtile = new THREE.Mesh(this.buildMesh0(100,res),this.getMat()); + newtile.res = res + 3; + newtile.geometry.dynamic = true; + newtile.doublesided = false; + newtile.side = side; + newtile.receiveShadow = true; + newtile.castShadow = false; + newtile.material.uniforms.side.value = side; + newtile.matrixAutoUpdate = false; + for(var i = 0; i < newtile.geometry.vertices.length; i++) + { + if(i < newtile.res * newtile.res) + { + newtile.material.attributes.everyOtherZ.value.push(0); + newtile.material.attributes.everyZ.value.push(0); + newtile.material.attributes.everyOtherNormal.value.push(new THREE.Vector3(0,0,1)); + newtile.material.attributes.ONormal.value.push(new THREE.Vector3(0,0,1)); + + } + else + { + newtile.material.attributes.everyOtherZ.value.push(-10); + newtile.material.attributes.everyZ.value.push(-10); + newtile.material.attributes.everyOtherNormal.value.push(new THREE.Vector3(0,0,1)); + newtile.material.attributes.ONormal.value.push(new THREE.Vector3(0,0,1)); + } + } + newtile.material.attributes.everyOtherZ.needsUpdate = true; + newtile.material.attributes.everyOtherNormal.needsUpdate = true; + //so, it appears that it might just be better to generate a new one than store it + //memory / cpu tradeoff + this.tiles[res].push(newtile); + return newtile; + } + } \ No newline at end of file diff --git a/support/client/lib/vwf/view/threejs.js b/support/client/lib/vwf/view/threejs.js index 7beb3a1b1..22afb8ce8 100644 --- a/support/client/lib/vwf/view/threejs.js +++ b/support/client/lib/vwf/view/threejs.js @@ -677,6 +677,7 @@ define( [ "module", "vwf/view", "vwf/utility", "hammer", "jquery" ], function( m hovering = false; } else if(self.lastEventData && self.mouseOverCanvas && !hovering && newPick) { + view.kernel.dispatchEvent( newPickId, "pointerHover", self.lastEventData.eventData, self.lastEventData.eventNodeData ); hovering = true; } diff --git a/support/proxy/vwf.example.com/terrain.vwf.yaml b/support/proxy/vwf.example.com/terrain.vwf.yaml new file mode 100644 index 000000000..b3c60ce4d --- /dev/null +++ b/support/proxy/vwf.example.com/terrain.vwf.yaml @@ -0,0 +1,15 @@ +# Copyright 2012 United States Government, as represented by the Secretary of Defense, Under +# Secretary of Defense (Personnel & Readiness). +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. + +--- +extends: http://vwf.example.com/node.vwf