3dbasin
Version:
3d Basin Mapping Package for HRC
643 lines (509 loc) • 19.8 kB
JavaScript
Basin3D.Basin3D = function ( nCells ) {
this.nCells = nCells;
this.plane = null;
this.terrain = [];
this.basin = null;
this.planeMesh = null;
this.scale3D = 5 / nCells;
this.deltaBase = 0;
this.limits = {};
this.streamMat = new THREE.MeshLambertMaterial({color: 0x79a1d1});
this.streamNet = new THREE.Group();
this.cylinderUtil = new CylinderUtil.CylinderUtil();
this3D = this;
this.surfaceCover = [
{ name: "grass" , rgb: 0xE6DF73, fluveScale: 0.25 },
{ name: "chapparal", rgb: 0xC4BF6E, fluveScale: 0.50 },
{ name: "hardwood" , rgb: 0x7ea74f, fluveScale: 0.75 },
{ name: "conifer" , rgb: 0x469f4e, fluveScale: 1.00 },
{ name: "tundra" , rgb: 0xB9D39C, fluveScale: 1.25 },
{ name: "rock" , rgb: 0xAAA4AA, fluveScale: 1.50 },
{ name: "snow" , rgb: 0xF8FBFC, fluveScale: 2.00 }
];
this.buildBasin( nCells );
};
module.exports.Basin3d.Basin3d = Basin3d.Basin3d;
Basin3D.Basin3D.prototype = {
buildBasin: function( nCells ) {
this.nCells = nCells;
this.plane = null;
this.terrain = [];
this.basin = null;
this.planeMesh = null;
this.scale3D = 5 / nCells;
this.deltaBase = 0;
this.limits = {};
this.streamMat = new THREE.MeshLambertMaterial({color: 0x79a1d1});
this.streamNet = new THREE.Group();
this.cylinderUtil = new CylinderUtil.CylinderUtil();
this3D = this;
this.basin = new Basin.Basin(nCells);
this.basin.construct();
this.createTerrain();
this.computeElevations();
this.getMaxElev();
this.deltaSC = (this.limits.maxElev + 0.1) / this.surfaceCover.length;
//this.dumpTerrain();
//this.dumpCells();
this.createPlaneGeometry();
this.createPlaneMesh();
this.renderStreams();
this.renderSides();
},
deleteBasin: function() {
gfxScene.remove( this.planeMesh );
while (this.streamNet.children.length > 0) {
gfxScene.remove(this.streamNet.children[0]);
this.streamNet.remove(this.streamNet.children[0]);
}
},
/**
* T
*/
createTerrain: function() {
for ( var i = 0; i < this.nCells * 2 + 1; i++ ) {
this.terrain[i] = [];
for ( var j = 0; j < this.nCells * 2 + 1; j++ ) {
this.terrain[i][j] = new THREE.Vector3(i * this.scale3D, -1, j * this.scale3D);
}
}
},
createPlaneGeometry: function () {
this.plane = new THREE.Geometry();
for ( var i = 0; i < this.nCells * 2; i += 2 ) {
for ( var j = 0; j < this.nCells * 2; j += 2 ) {
this.createQuadPatch(i, j);
}
}
this.plane.computeFaceNormals();
this.plane.computeVertexNormals();
},
computeElevations: function () {
this.getDeltaBase();
for ( var i = 0; i < this.nCells; i++ ) {
for ( var j = 0; j < this.nCells; j++ ) {
this.computeCellElevations(i, j);
}
}
},
getDeltaBase: function() {
var maxBase = -1;
for ( var i = 0; i < this.nCells; i++ ) {
for ( var j = 0; j < this.nCells; j++ ) {
maxBase = Math.max(this.basin.geos[i][j].chanElev, maxBase );
}
}
this.deltaBase = (maxBase + 0.1) / this.surfaceCover.length;
},
createPlaneMesh: function() {
var vertexMat = new THREE.MeshLambertMaterial({ vertexColors: THREE.VertexColors });
this.planeMesh = new THREE.Mesh(this.plane, vertexMat);
// and add it to the scene
gfxScene.add(this.planeMesh);
this.planeMesh.position.set(-this.nCells * this.scale3D, 0, -this.nCells * this.scale3D);
},
getMaxElev: function () {
var maxRow = this.nCells * 2;
var maxElev = -1;
for ( var i = 0; i < maxRow + 1; i++ ) {
for ( var j = 0; j < maxRow + 1; j++ ) {
maxElev = Math.max( this.terrain[i][j].y, maxElev);
}
}
var maxOrder = -1;
var maxLen = 0;
var geos = this.basin.geos;
for ( i = 0; i < this.nCells; i++ ) {
for ( j = 0; j < this.nCells; j++ ) {
maxOrder = Math.max( geos[i][j].order, maxOrder);
maxLen = Math.max( geos[i][j].chanLen, maxLen);
}
}
this.limits.maxElev = maxElev;
this.limits.minOrder = maxOrder - Basin3D.MIN_ORDER;
this.limits.maxLen = maxLen;
this.limits.maxRow = maxRow;
this.limits.minZ = this.terrain[0][0].z;
this.limits.maxZ = this.terrain[0][this.limits.maxRow].z;
this.limits.minX = this.terrain[0][0].x;
this.limits.maxX = this.terrain[this.limits.maxRow][0].x;
this.limits.maxYX = this.terrain[this.limits.maxRow][0].y;
this.limits.maxYZ = this.terrain[0][this.limits.maxRow].y;
this.limits.maxYXZ = this.terrain[this.limits.maxRow][this.limits.maxRow].y ;
this.limits.nCS = this.nCells * this.scale3D;
},
computeCellElevations: function ( i, j ) {
// get the cell for convenience and the terrain indices
var bounds = this.basin.maze.cells[i * this.basin.maze.row + j];
var cell = this.basin.geos[i][j];
var it = i * 2 + 1;
var jt = j * 2 + 1;
// first set the center of the cell, which is already computed
this.terrain[it][jt].y = cell.chanElev;
// next, check each edge and see if this cell has a stream to the next
// if so, simply interpolate between the two slopes
this.computeStreamElev(bounds, cell, i, j);
// now check the other interpolated points
this.computeElevBounds(bounds, i, j);
},
computeStreamElev: function ( bounds, cell, i, j ) {
var it = i * 2 + 1;
var jt = j * 2 + 1;
var eI, eJ;
if ((bounds & Maze.SOUTH_BIT) === 0) {
eI = Math.max(i + Maze.YEdge[Maze.SOUTH], 0);
eJ = Math.max(j + Maze.XEdge[Maze.SOUTH], 0);
this.terrain[it - 1][jt].y = (cell.chanElev + this.basin.geos[eI][eJ].chanElev) / 2;
}
if ((bounds & Maze.WEST_BIT) === 0) {
eI = Math.max(i + Maze.YEdge[Maze.WEST], 0);
eJ = Math.max(j + Maze.XEdge[Maze.WEST], 0);
this.terrain[it][jt - 1].y = (cell.chanElev + this.basin.geos[eI][eJ].chanElev) / 2;
}
if ((bounds & Maze.NORTH_BIT) === 0) {
eI = Math.max(i + Maze.YEdge[Maze.NORTH], 0);
eJ = Math.max(j + Maze.XEdge[Maze.NORTH], 0);
this.terrain[it + 1][jt].y = (cell.chanElev + this.basin.geos[eI][eJ].chanElev) / 2;
}
if ((bounds & Maze.EAST_BIT) === 0) {
eI = Math.max(i + Maze.YEdge[Maze.EAST], 0);
eJ = Math.max(j + Maze.XEdge[Maze.EAST], 0);
this.terrain[it][jt + 1].y = (cell.chanElev + this.basin.geos[eI][eJ].chanElev) / 2;
}
},
addChannelSlopes: function ( n, i, j ) {
var rowLim = this.basin.maze.row - 1;
var colLim = this.basin.maze.col - 1;
var geos = this.basin.geos;
var slopes = [];
switch (n) {
case 0: // south
if (i !== 0) {
slopes.push(this.basin.geos[i - 1][j].chanSlope);
}
break;
case 1: // southwest
if (i !== 0 && j !== 0) {
slopes.push(geos[i - 1][j - 1].chanSlope);
}
if (j !== 0) {
slopes.push(geos[i][j - 1].chanSlope);
}
if (i !== 0) {
slopes.push(geos[i - 1][j].chanSlope);
}
break;
case 2: // west
if (j !== 0) {
slopes.push(geos[i][j - 1].chanSlope);
}
break;
case 3: // northwest
if (i !== 0) {
slopes.push(geos[i - 1][j].chanSlope);
}
if (i !== rowLim && j !== 0) {
slopes.push(geos[i + 1][j - 1].chanSlope);
}
if (i !== rowLim) {
slopes.push(geos[i + 1][j].chanSlope);
}
break;
case 4: // north
if (i !== rowLim) {
slopes.push(geos[i + 1][j].chanSlope);
}
break;
case 5: // northeast
if (i !== rowLim) {
slopes.push(geos[i + 1][j].chanSlope);
}
if (j !== colLim) {
slopes.push(geos[i][j + 1].chanSlope);
}
if (i !== rowLim && j !== colLim) {
slopes.push(geos[i + 1][j + 1].chanSlope);
}
break;
case 6: // east
if (j !== colLim) {
slopes.push(geos[i][j + 1].chanSlope);
}
break;
case 7: // southeast
if (i !== rowLim) {
slopes.push(geos[i + 1][j].chanSlope);
}
if (j !== colLim) {
slopes.push(geos[i][j + 1].chanSlope);
}
if (i !== 0 && j !== colLim) {
slopes.push(geos[i - 1][j + 1].chanSlope);
}
break;
}
slopes.push(geos[i][j].chanSlope);
return slopes;
},
/**
* See
*/
checkStreamBounds: function ( n, bounds ) {
return ((bounds & Maze.SOUTH_BIT) === 0 && n === 0 ) ||
((bounds & Maze.WEST_BIT) === 0 && n === 2 ) ||
((bounds & Maze.NORTH_BIT) === 0 && n === 4 ) ||
((bounds & Maze.EAST_BIT) === 0 && n === 6 );
},
computeElevBounds: function ( bounds, i, j ) {
var offset =
[
{i: -1, j: 0}, // south
{i: -1, j: -1},
{i: 0, j: -1}, // west
{i: 1, j: -1},
{i: 1, j: 0}, // north
{i: 1, j: 1},
{i: 0, j: 1}, // east
{i: -1, j: 1}
];
var it, jt;
var tRowLim = this.terrain.length;
var tColLim = this.terrain[0].length;
var base = this.basin.geos[i][j].chanElev;
for (var n = 0; n < 8; n++) {
it = i * 2 + offset[n].i + 1;
jt = j * 2 + offset[n].j + 1;
// if the current point is a stream, then obviously it's not an interfluve
var bStream = this.checkStreamBounds( n, bounds );
if (it >= 0 && it < tRowLim && jt >= 0 && jt < tColLim && !bStream) {
var slopes = this.addChannelSlopes( n, i, j );
this.terrain[it][jt].y = Math.max(this.terrain[it][jt].y, this.interfluveHeight(slopes, base));
}
}
},
/**
* Simple function to calc the height of the interfluve from surrounding
* calculated stream heights.
*/
interfluveHeight: function ( slopes, base ) {
var h = 0;
var n = slopes.length;
while (slopes.length > 0) {
h += slopes.pop();
}
var index = Math.floor(base / this.deltaBase);
return h / n * this.surfaceCover[index].fluveScale * (0.75 + (Math.random()/2)) + base;
},
getSurfColor: function ( i, j ) {
var terrainHt = this.terrain[i][j].y;
var index = Math.floor(terrainHt / this.deltaSC);
return new THREE.Color( this.surfaceCover[index].rgb );
},
/**
* This creates the new vertices and associated faces.
* @param i
* @param j
* @param offV
* @param indexF
*/
computeQuadFaces: function ( i, j, offV, indexF ) {
var vC = this.plane.vertices.length;
var face;
for ( var n=0; n<4; n++ )
this.plane.vertices.push(this.terrain[i + offV[n].i][j + offV[n].j]);
face = new THREE.Face3(vC + indexF[0].a, vC + indexF[0].b, vC + indexF[0].c);
var ia = i + offV[indexF[0].a].i;
var ja = j + offV[indexF[0].a].j;
face.vertexColors[0] = this.getSurfColor(ia,ja);
var ib = i + offV[indexF[0].b].i;
var jb = j + offV[indexF[0].b].j;
face.vertexColors[1] = this.getSurfColor(ib, jb);
var ic = i + offV[indexF[0].c].i;
var jc = j + offV[indexF[0].c].j;
face.vertexColors[2] = this.getSurfColor(ic, jc);
this.plane.faces.push(face);
face = new THREE.Face3(vC + indexF[1].a, vC + indexF[1].b, vC + indexF[1].c);
ia = i + offV[indexF[1].a].i;
ja = j + offV[indexF[1].a].j;
face.vertexColors[0] = this.getSurfColor(ia, ja);
ib = i + offV[indexF[1].b].i;
jb = j + offV[indexF[1].b].j;
face.vertexColors[1] = this.getSurfColor(ib, jb);
ic = i + offV[indexF[1].c].i;
jc = j + offV[indexF[1].c].j;
face.vertexColors[2] = this.getSurfColor(ic, jc);
this.plane.faces.push(face);
},
createQuadPatch: function ( i, j ) {
// first pair of triangles
var offV1 = [
{ i:0, j:0 },
{ i:0, j:1 },
{ i:1, j:1 },
{ i:1, j:0 }
];
var indexF1 = [
{ a:0, b:1, c:2 },
{ a:0, b:2, c:3 }
];
this.computeQuadFaces( i, j, offV1, indexF1);
// second pair of triangles
var offV2 = [
{ i:0, j:1 },
{ i:0, j:2 },
{ i:1, j:2 },
{ i:1, j:1 }
];
var indexF2 = [
{ a:0, b:1, c:3 },
{ a:1, b:2, c:3 }
];
this.computeQuadFaces( i, j, offV2, indexF2);
// third pair of triangles
var offV3 = [
{ i:1, j:1 },
{ i:1, j:2 },
{ i:2, j:2 },
{ i:2, j:1 }
];
var indexF3 = [
{ a:0, b:1, c:2 },
{ a:0, b:2, c:3 }
];
this.computeQuadFaces( i, j, offV3, indexF3);
// fourth pair of triangles
var offV4 = [
{ i:1, j:0 },
{ i:1, j:1 },
{ i:2, j:1 },
{ i:2, j:0 }
];
var indexF4 = [
{ a:0, b:1, c:3 },
{ a:1, b:2, c:3 }
];
this.computeQuadFaces( i, j, offV4, indexF4);
},
/**
*
*/
createSouthSide: function ( material ) {
var shapeS = new THREE.Shape();
var lim = this.limits;
shapeS.moveTo(lim.minZ - lim.nCS, 0);
shapeS.lineTo(lim.maxZ - lim.nCS, 0);
shapeS.lineTo(lim.maxZ - lim.nCS, lim.maxYZ);
for ( var i=lim.maxRow; i>=0; i-- ) {
var x = this.terrain[0][i].z;
var y = this.terrain[0][i].y;
shapeS.lineTo(x - lim.nCS, y);
}
var shapeSMesh = new THREE.Mesh(new THREE.ShapeGeometry(shapeS), material);
gfxScene.add(shapeSMesh);
shapeSMesh.rotateY( -Math.PI / 2 );
shapeSMesh.position.set(-lim.nCS, 0, 0);
},
createWestSide: function ( material ) {
var shapeW = new THREE.Shape();
var lim = this.limits;
shapeW.moveTo(lim.minX - lim.nCS, 0);
shapeW.lineTo(lim.maxX - lim.nCS, 0);
shapeW.lineTo(lim.maxX - lim.nCS, lim.maxYX);
for ( var i=lim.maxRow; i>=0; i-- ) {
var x = this.terrain[i][0].x;
var y = this.terrain[i][0].y;
shapeW.lineTo(x - lim.nCS, y);
}
var shapeWMesh = new THREE.Mesh(new THREE.ShapeGeometry(shapeW), material);
gfxScene.add(shapeWMesh);
shapeWMesh.position.set(0, 0, -lim.nCS);
},
createNorthSide: function ( material ) {
var shapeN = new THREE.Shape();
var lim = this.limits;
shapeN.moveTo(lim.minZ - lim.nCS, 0);
shapeN.lineTo(lim.maxZ - lim.nCS, 0);
shapeN.lineTo(lim.maxZ - lim.nCS, lim.maxYXZ);
for ( var i=lim.maxRow; i>=0; i-- ) {
var x = this.terrain[lim.maxRow][i].z;
var y = this.terrain[lim.maxRow][i].y;
shapeN.lineTo(x - lim.nCS, y);
}
var shapeNMesh = new THREE.Mesh(new THREE.ShapeGeometry(shapeN), material);
gfxScene.add(shapeNMesh);
shapeNMesh.rotateY( -Math.PI / 2 );
shapeNMesh.position.set(lim.nCS, 0, 0);
},
createEastSide: function ( material ) {
var shapeE = new THREE.Shape();
var lim = this.limits;
shapeE.moveTo(lim.minX - lim.nCS, 0);
shapeE.lineTo(lim.maxX - lim.nCS, 0);
shapeE.lineTo(lim.maxX - lim.nCS, lim.maxYXZ);
for ( var i=lim.maxRow; i>=0; i-- ) {
var x = this.terrain[i][lim.maxRow].x;
var y = this.terrain[i][lim.maxRow].y;
shapeE.lineTo(x - lim.nCS, y);
}
var shapeEMesh = new THREE.Mesh(new THREE.ShapeGeometry(shapeE), material);
gfxScene.add(shapeEMesh);
shapeEMesh.position.set( 0, 0, lim.nCS );
},
createBottom: function ( material ) {
var shapeB = new THREE.Shape();
var lim = this.limits;
shapeB.moveTo(lim.minX - lim.nCS, lim.minZ - lim.nCS);
shapeB.lineTo(lim.maxX - lim.nCS, lim.minZ - lim.nCS);
shapeB.lineTo(lim.maxX - lim.nCS, lim.maxZ - lim.nCS);
shapeB.lineTo(lim.minX - lim.nCS, lim.maxZ - lim.nCS);
shapeB.lineTo(lim.minX - lim.nCS, lim.minZ - lim.nCS);
var shapeBMesh = new THREE.Mesh(new THREE.ShapeGeometry(shapeB), material);
gfxScene.add(shapeBMesh);
shapeBMesh.rotateX( -Math.PI / 2 );
},
/**
* Render the "non-visible" sides of the basin
*/
renderSides: function() {
var material = new THREE.MeshBasicMaterial({color: 0x333333, side:THREE.DoubleSide});
this.createSouthSide( material );
this.createWestSide( material );
this.createNorthSide( material );
this.createEastSide( material );
this.createBottom( material );
},
/**
* Use the basin's rat to retrace the stream net and render all the streams
*/
renderStreams: function () {
this.basin.rat.initSolveObj(0x80, false, this.renderStream);
this.basin.rat.findSolution(-1, -1);
this.streamNet.position.set( -this.nCells * this.scale3D, 0, -this.nCells * this.scale3D);
gfxScene.add(this.streamNet);
},
/**
* Render each stream section as we are called
*/
renderStream: function (label, rat, i, j, nexi, nexj, pathlen, bsac) {
var streamWidth = [0, 0.01, 0.015, 0.02];
var cell = this3D.basin.geos[i][j];
var curPt = this3D.terrain[i * 2 + 1][j * 2 + 1];
var nexPt;
// have to handle the final cell specially as there is no "next" cell
if ( nexi >= 0 && nexj >= 0 )
nexPt = this3D.terrain[nexi * 2 + 1][nexj * 2 + 1];
else
nexPt = new THREE.Vector3(curPt.x, curPt.y, -curPt.z);
var order = cell.order - this3D.limits.minOrder;
if (order > 0 ) {
var stream = this3D.cylinderUtil.createCylinder(
curPt,
nexPt,
streamWidth[order],
8,
this3D.streamMat);
this3D.streamNet.add(stream);
}
},
};
module.exports.Basin3d.Basin3d.prototype = Basin3d.Basin3d.prototype;