I have previously done couple of experiments to find the best way to draw large-scale hexagon grids.
I have tried drawing the hexes using THREE.Line
, THREE.LineSegments
.
It was working quite well for small grids, but if I had more than 1000+ cells in a grid, fps started dropping down really hard.
So I've come up with this idea to apply a texture (containing a hexagon grid pattern) to a simple plane. I just needed to set the Texture's repeat()
function to specify as how many repeats will be performed vertically and horizontally.
The overall performance increased tremendously, however I have noticed that the texture fits my grid cells sizes only around the center of the plane.
The dots you can see in the grid above is THREE.Points, it's just to show all corner for each hex. This helps me to see whether the texture matches the grid cells or not.
The further I look from center , the larger is the offset between the actual hexagon and the corresponding hexagon on a texture.
I believe there is more clever way to pre-calculate the size so that there will be no differences between these two grids.
This is an example how this grid looks like and how it's being generated ...
window.onload = function() {
var renderer, camera, scene, controls, all_loaded = false, controls, tiles = {};
var imgDataURI = "data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAGgAAABaCAYAAABUrhMHAAAFhklEQVR4Xu2dT2gcdRTH34tpLS2Ipx70VkFRmpLZnlRSK+6MIkIqmIvFYnfWqgc9FBQFq2C9WKHoyT/spFKplwgmIKI7q9YG9ZSdpSmWCvamh55ESaltuk9+kU12k2x2fr/f/N5OlrenkJ3ve2++n9mZ3/x+O28x9L0fojh5GAbg9VxQ+L5JhIOyP+Wg8BYKoPwemQIov2yWKhNAAojXAbkG8fqtnU0AaVvGKxBAvH5rZxNA2pbxCgQQr9/a2QSQtmW8AgHE67d2NgGkbRmvQADx+q2dbSABlQLv4mQ1uVfbjRwKyn7hVwKCKE7uy2F52iWFgTeNYeD9A0RXbtx6dd/pr377QztKDgSlB++5A7fvmEWgnaocArxCVxfGJn+69GcOytMu4dATd9+55d/t5wBxJyp16HsfAeIRbNJ3lVria0fsoyD0C98CkI8AH1fi5EVVStn3PiSA5wEwjuL6o30sTzt1uejFNISPANEnUZy8sASo9Qr90VlAfACaeDKq1V/Rjs4oKBcLJ2AIjhI1f4nixth6qdX+IA7dD004WanVX2UsTztVWCy8B0N0FIh+bt+fDkAq6qGx3Xu2btvyNQHsAKTDUbUxrZ3NoaBcHB0nxE8RYIHw5mNR9fyFjdKFwZ7dSLd8o/YHiZ6t1BozDsvTDh0GoweQ8BQALFy/duPx07MXzrcHWQOo9eaRwHv5JuG7QHT598Vk79mzcE07e4aCiQnYettfXgKAuwCar0Vx4wOd8KXAewkJTwDQ5b9vT7ypKbiuo8962/37Ydtdw94cIO5Car5eqTXeXy9HV0DLp73AO4OETwPSTKWaHMi60DTxwmLhS0AaR8Azlbj+TBpNt23KfuEzAjoIhDNRrf6kTSxTbTnwpoFwnJA+j6rJwY3i9ATUEpcCbw4JRwjo+GScHDctTkcXFr03EPFNQJqvVJO9Otpe25YDbw7U/hC9HdWSd3ptn8X7Jd87hoDHCGl+MuX+pAakCjzsjzw0hMNT6u8mLU6ciud/zKLw1THCYGQfwPAX//9/8amoOn9uM+ex8U0L0PKnyeBISGtwP45sl59U2zOPEaCW2Trn0l6A8nBtyPJaF2Z07bYCpExPOxrpBihvoyvb0WLWo19rQCujvY3H82uvM/m+P9G933J1/5gZoGVQXe6I2wFtpjv8tDMWrmZgMge0fH1aNaek/r+Z58jWm/PjmMN0BkgB6ZiVpc0/y9w+a07KOYZVAKeAVkZ7hYtERIOyTqPWnZpIyLGOxgJo0FY6OfdHAPW6QVvnfQFkYBqnRABxum2QSwAZmMYpEUCcbhvkEkAGpnFKBBCn2wa5BJCBaZwSAcTptkEuAWRgGqdEAHG6bZBLABmYxikRQJxuG+QSQAamcUoEEKfbBrkEkIFpnBIBxOm2QS4BZGAap0QAcbptkEsAGZjGKRFAnG4b5BJABqZxSgQQp9sGuQSQgWmcEgHE6bZBLgFkYBqnRABxum2Qa+AAlQP58rzBcbAkcfrdbHn8xBTLis4ZoNVNgVRKeYBLH1jmgLo1BWovTR6BTA8qM0C9mgKtLinvTY4G5iFieQy/89DL1WP40sii+6mqr40sTJoCpT3rSiuYTqe0rkE2TYHSAlLbSTMlg2G2bVMgHUCtbV02OerHJ9XkzNPzE5TVudQE0Aooaei3xr+sRyM2gJTWtslR3po2pR39SlNZ2yPHUt/r/lHaMlsanJV8w7bMHE2BstqRNTMSg97YXH4awNWhYx63YxVAflzD3EjXSrWOJj8V7dpli/hq5VYAWRjoWiqAXDtsGV8AWRroWi6AXDtsGV8AWRroWi6AXDtsGV8AWRroWi6AXDtsGV8AWRroWi6AXDtsGV8AWRroWi6AXDtsGV8AWRroWq4A/QeCYJEAFo/atwAAAABJRU5ErkJggg=="
function initRenderer(){
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize( window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.setClearColor(0x264d73, 1);
}
function initScene(){
scene = new THREE.Scene();
//scene.fog = new THREE.Fog( 0x264d73, 500, 900 )
}
function initCamera(){
camera = new THREE.PerspectiveCamera( 45, window.innerWidth/window.innerHeight, 1, 10000 );
camera.position.set( 0.0, 211 ,40 );
camera.lookAt(scene.position);
scene.add(camera);
controls = new THREE.OrbitControls( camera );
}
function initLights(){
var aLight = new THREE.AmbientLight(0xD0D0D0, 0.5);
scene.add(aLight);
}
////// Initializers ////////////////////////
var Cell = function( q, r, s ){
this.q = q;
this.r = r;
this.s = s;
this.h = 0;
this._hashID = this.q+"."+this.r+"."+this.s;
};
Cell.prototype.constructor = Cell;
function cellToPixel ( layout, cell ){ //// point ///
var M = layout.orientation;
var v = new THREE.Vector3();
v.x = ( M.f0 * cell.q + M.f1 * cell.r ) * layout.size;
v.y = cell.h ;
v.z = ( M.f2 * cell.q + M.f3 * cell.r ) * layout.size;
return v;
};
var Tile = function( coords, layout ){
this.cell = new Cell( coords[0], coords[1], coords[2] );
this.pos = cellToPixel( layout, this.cell );
};
Tile.prototype.constructor = Tile;
function generateGrid( grid_size, cell_size , origin ){
//// set layout type ( pointy ) ///
var layout = {
orientation: {
//// forward matrix ////
f0: Math.sqrt(3.0),
f1: Math.sqrt(3.0) / 2.0,
f2: 0.0,
f3: 3.0 / 2.0,
/// reverse matrix ////
b0: Math.sqrt(3.0) / 3.0,
b1: -1.0 / 3.0,
b2: 0.0,
b3: 2.0 / 3.0,
start_angle: 0.5
},
size: cell_size,
origin: origin
}
///generate tiles ///
var r1, r2, t, q, r;
for ( q = -grid_size; q <= grid_size; q++ ) {
r1 = Math.max(-grid_size, -q - grid_size);
r2 = Math.min(grid_size, -q + grid_size);
for ( r = r1; r <= r2; r++ ) {
t = new Tile( [ q, r, -q-r ] , layout );
tiles[ t.cell._hashID ] = t;
}
}
/// hex-corners ///
var hex_corners = [], offset_x , offset_y, angle = 0;
for (var i = 0; i < 6; i++) {
angle = 2.0 * Math.PI * ( i + layout.orientation.start_angle ) / 6;
offset_x = layout.size * Math.cos( angle );
offset_y = layout.size * Math.sin( angle );
hex_corners.push( [parseFloat( offset_x.toFixed()) , parseFloat(offset_y.toFixed())] );
}
//// hex corner points ///
var points_Geo = new THREE.Geometry();
for( var t in tiles ){
for( var c = 0, c_l = hex_corners.length; c < c_l; c++ ){
points_Geo.vertices.push( new THREE.Vector3( tiles[t].pos.x + hex_corners[c][0],0, tiles[t].pos.z + hex_corners[c][1] ) );
}
}
points_Geo.mergeVertices();
var points_Mat = new THREE.PointsMaterial( { size: 3, color: 0xFFFFFF, sizeAttenuation: true, transparent: true, opacity: 1 } );
var points_Mesh = new THREE.Points( points_Geo, points_Mat );
scene.add( points_Mesh )
}
function add_plane(){
var plane, material, image = new Image(), texture = new THREE.Texture();
image.onload = function(){
texture.image = image;
texture.needsUpdate = true;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.anisotropy = 16;
texture.repeat.set( 9.587, 11 );
console.log(texture)
material = new THREE.MeshLambertMaterial({transparent: true, emissive:0xb3cce6, color: 0xFFFFFF, opacity: 0.7, map : texture });
plane = new THREE.Mesh(new THREE.PlaneGeometry( 500, 500 ), material);
plane.rotateX( -(Math.PI/2) )
plane.position.set( -11.1,-0.1,0)
scene.add(plane);
camera.lookAt( plane );
all_loaded = true;
};
image.src = imgDataURI;
}
///// Mouse events ////////
///// Main /////////
function main(){
initRenderer(window.innerWidth, window.innerHeight );
initScene();
initCamera(window.innerWidth, window.innerHeight );
initLights();
add_plane();
generateGrid( 8, 15, {x:0, y:0, z: 0});
animate();
}
function animate(){
window.requestAnimationFrame( animate );
//if( all_loaded ){
render_all();
//}
controls.update()
}
function render_all(){
renderer.render(scene, camera);
}
main();
}
body , canvas{
width: 100%;
height: 100%;
margin:0;
padding:0;
}
<script src="https://ajax.googleapis.com/ajax/libs/threejs/r76/three.min.js"></script>
<script src="https://dl.dropboxusercontent.com/u/3587259/Code/Threejs/OrbitControls.js"></script>