2

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.

enter image description here

The overall performance increased tremendously, however I have noticed that the texture fits my grid cells sizes only around the center of the plane.

enter image description here

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.

enter image description here

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>
gman
  • 100,619
  • 31
  • 269
  • 393
Alexus
  • 1,887
  • 1
  • 23
  • 50
  • This is due to float precision errors. The texture has hard integers. the points use floats/doubles. how further away you go, the larger the disrepancy. maybe use a predictive integer set to build up your points based up on texture coordinates of the possible points. – Tschallacka Aug 23 '16 at 12:37
  • do you mean in the texture repeat calculations? – Alexus Aug 23 '16 at 12:40
  • no, your texture has fixed numbers. a point will always be for example on x = 14, y = 6. If the texture is repeated it will be at x = retpeatnumside * texturewidth + 14 and y = repeatnumtop * textureheight + 14, however your dynamic hexacon calculations do some math to decide where the ideal hexagon points are. and if any of those numbers end up as a float you get precision errors, what was 14 might end up as 13.48377213 due to math. three renders both at their own places. which shows up as a difference for you. because one is pure integer and other is floaty – Tschallacka Aug 23 '16 at 12:42
  • well I generated the texture using the same technique in JS, so I suppose it should be equal.I was kind of hoping that the repeat values can be calculated to fit the generated hexagon sizes. – Alexus Aug 23 '16 at 12:51
  • can you show us the code where you generate the points for the hexagon to draw? – Tschallacka Aug 23 '16 at 12:53
  • Thing is, hexagon is caclulated from the center point outward. So the distance from center point may be 0.54 2.23 for left up corner, 1 3 for top corner etc... The hexagons on your texture however are NOT a dynamically generated data set. points always live at their static coordinates, and not relative to the center point. So the closer you are to the offset of 0,0, the more they match, the more you deviate from the 0,0 the bigger the difference incaclulations from the center points. – Tschallacka Aug 23 '16 at 13:02
  • updated my post, your arguments really make sense .. I will try to implement your suggestion and see if it solves my problem.. – Alexus Aug 23 '16 at 13:30
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/121631/discussion-between-michael-dibbets-and-alexus). – Tschallacka Aug 23 '16 at 13:35

1 Answers1

0

The problem you are facing is due to rounding errors. The code that calculates where to draw a hexagon usually does this from the offset of a beginning. Borrow the code form Calculate 6 vertices of randomely generated hexagon I can provide you the console output of how it calculates.

Becuase this is dynamically calculated, there are rounding errors, and the browser has to instruct the display to draw it somewhere, which hands it off to the graphics card and the graphics card decides which pixel(s) will actually draw the lines.

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");

// draw your original hexagon
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(30,-50);
ctx.lineTo(83,-50);
ctx.lineTo(113,0);
ctx.lineTo(83,50);
ctx.lineTo(30,50);
ctx.closePath();
ctx.lineWidth=3;
ctx.stroke();

// same hexagon using drawHexagon()
ctx.strokeStyle='red';   
ctx.lineWidth=1;
drawHexagon(0,0,113/2);

function drawHexagon(leftX,middleY,radius){
  var centerX=leftX+radius;
  var centerY=middleY;    
  ctx.beginPath();
  ctx.moveTo (centerX+radius*Math.cos(0), centerY+radius*Math.sin(0));          
  for (var i=1; i<=6;i++) {
    console.log("FLoaty coordinates, not absolute pixel: "+(centerX+radius*Math.cos(i*2*Math.PI/6)));
    ctx.lineTo(centerX+radius*Math.cos(i*2*Math.PI/6), centerY+radius*Math.sin(i*2*Math.PI/6));
  }
  ctx.closePath();
  ctx.stroke();
}
drawHexagon(0,100,113/2);
drawHexagon(0,200,113/2);
body{ background-color: ivory; padding:10px; }
canvas{border:1px solid red;}
<h4>Fn() to draw hexagon with specified radius and left-midpoint.</h4>
<canvas id="canvas" width=300 height=300></canvas>

Some times they wil line up with a full pixel fromt he texture image, sometimes they will not because the rounding will shift them somewhere to the side.

To solve this you have to make sure the hexagons you draw in three.js align perfectly with coordinate points in your texture. What you have to have for that is an offset map for all points int he texture and then find the nearsest texture map hexagon and and just accordingly.

pseudo code:

 center = hexagon.pos();
 left = center.posX % texturewidth;
 realcenter = null;
 for(c=0;c<textureHexagons.length;c++) {
     current = textureHexagon[c];
     if(left >= current.posX && left <= current.posX + width) {
        realcenter = textureHexagon[c];
     }
 }
 baseOffset = textureWidth * Math.round((center.posX / textureWidth))
 hexagon.topPoint = baseOffset + realcenter.topPoint;
 hexagon.leftTopPoint = baseOffset + realcenter.leftTopPoint;
 etc...
Community
  • 1
  • 1
Tschallacka
  • 27,901
  • 14
  • 88
  • 133