Skip to main content

Terrain Generation from Heightmap

This project uses the pc.Mesh API to procedurally generate and texture a rolling hillside from a heightmap texture.

Try it from the Editor in the tutorial project.

The script below performs the terrain generation.

  • ESM
  • Classic
varTerrain= pc.createScript('terrain');

Terrain.attributes.add('heightMap',{
type:'asset',
assetType:'texture'
});

Terrain.attributes.add('minHeight',{
type:'number',
default:0
});

Terrain.attributes.add('maxHeight',{
type:'number',
default:10
});

Terrain.attributes.add('width',{
type:'number',
default:100
});

Terrain.attributes.add('depth',{
type:'number',
default:100
});

Terrain.attributes.add('subdivisions',{
type:'number',
default:250
});

Terrain.attributes.add('material',{
type:'asset',
assetType:'material'
});

// initialize code called once per entity
Terrain.prototype.initialize=function(){
var img =this.heightMap.resource.getSource();
var visualMesh =this.createTerrainFromHeightMap(img,this.subdivisions);
var collisionMesh =this.createTerrainFromHeightMap(img,this.subdivisions/2);

this.entity.addComponent('render',{
meshInstances:[newpc.MeshInstance(visualMesh,this.material.resource)]
});

this.entity.addComponent('collision',{
type:'mesh'
});

// We still have to create a model resource to create a runtime
// collision mesh
var node =newpc.GraphNode();
var meshInstance =newpc.MeshInstance(node, collisionMesh,this.material.resource);
var collisionModel =newpc.Model();
collisionModel.graph= node;
collisionModel.meshInstances.push(meshInstance);

this.entity.collision.model= collisionModel;

this.entity.addComponent('rigidbody',{
friction:0.5,
type:'static'
});
};

Terrain.prototype.createTerrainVertexData=function(options){
var positions =[];
var uvs =[];
var indices =[];
var row, col;

for(row =0; row <= options.subdivisions; row++){
for(col =0; col <= options.subdivisions; col++){
var position =newpc.Vec3((col * options.width)/ options.subdivisions-(options.width/2.0),0,((options.subdivisions- row)* options.height)/ options.subdivisions-(options.height/2.0));

var heightMapX =(((position.x+ options.width/2)/ options.width)*(options.bufferWidth-1))|0;
var heightMapY =((1.0-(position.z+ options.height/2)/ options.height)*(options.bufferHeight-1))|0;

var pos =(heightMapX + heightMapY * options.bufferWidth)*4;
var r = options.buffer[pos]/255.0;
var g = options.buffer[pos +1]/255.0;
var b = options.buffer[pos +2]/255.0;

var gradient = r *0.3+ g *0.59+ b *0.11;

position.y= options.minHeight+(options.maxHeight- options.minHeight)* gradient;

positions.push(position.x, position.y, position.z);
uvs.push(col / options.subdivisions,1.0- row / options.subdivisions);
}
}

for(row =0; row < options.subdivisions; row++){
for(col =0; col < options.subdivisions; col++){
indices.push(col + row *(options.subdivisions+1));
indices.push(col +1+ row *(options.subdivisions+1));
indices.push(col +1+(row +1)*(options.subdivisions+1));

indices.push(col + row *(options.subdivisions+1));
indices.push(col +1+(row +1)*(options.subdivisions+1));
indices.push(col +(row +1)*(options.subdivisions+1));
}
}

var normals = pc.calculateNormals(positions, indices);

return{
indices: indices,
positions: positions,
normals: normals,
uvs: uvs
};
};

Terrain.prototype.createTerrainFromHeightMap=function(img, subdivisions){
var canvas =document.createElement("canvas");
var context = canvas.getContext("2d");
var bufferWidth = img.width;
var bufferHeight = img.height;
canvas.width= bufferWidth;
canvas.height= bufferHeight;

context.drawImage(img,0,0);

var buffer = context.getImageData(0,0, bufferWidth, bufferHeight).data;
var vertexData =this.createTerrainVertexData({
width:this.width,
height:this.depth,
subdivisions: subdivisions,
minHeight:this.minHeight,
maxHeight:this.maxHeight,
buffer: buffer,
bufferWidth: bufferWidth,
bufferHeight: bufferHeight
});

var mesh =newpc.Mesh(this.app.graphicsDevice);
mesh.setPositions(vertexData.positions);
mesh.setNormals(vertexData.normals);
mesh.setUvs(0, vertexData.uvs);
mesh.setIndices(vertexData.indices);
mesh.update();

return mesh;
};

AltStyle によって変換されたページ (->オリジナル) /