Get the index from a hexagon on mouse hover and the immediate neighbors

Hey everyone,

I’m new to Three.js and I despair of obtaining the index of a hexagon on mouse hover and that of the immediate neighbors.

Does anyone have a solution or tip for me?

CodePen: https://codepen.io/deoostfrees/pen/poLqBxj

Code:

const pointer = new THREE.Vector2();
let camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 100);
let scene = new THREE.Scene();
let mesh;
let raycaster = new THREE.Raycaster();
let gLayout, gMat, gGeo;
let bGeo;

let CONFIG = {
  coord: null,
  height: null,
};

var config = {
  webgl2: true,
  grid: false
};

const VERT_SRC = `#version 300 es
in vec3  position;
in vec3  normal;
in vec3  i_pos;
in float i_height;

uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat3 normalMatrix;
uniform mat4 projectionMatrix;

out vec3 frag_wpos;
out vec3 frag_norm;
out vec3 frag_color;

out float frag_test;

///////////////////////////////////////////////////////////////////////////////

uniform vec2 UYOffset;
uniform vec3 UColor;

///////////////////////////////////////////////////////////////////////////////

void main(){
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // HEIGHT MAP
    vec3 pos    = position;
    float n     = i_height;

    if( gl_VertexID >= 36 ) pos.y += i_height;  // Move Pillar Cap up/down

    pos += i_pos;                               // Move to Instanced Position

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // RESULTS
    vec4 ws_pos	= modelMatrix * vec4( pos, 1.0 );

    frag_wpos   = ws_pos.xyz;
    frag_norm   = normalMatrix * normal;
    frag_color  = UColor;

    gl_Position	= projectionMatrix * viewMatrix * ws_pos;
}
`;

const FRAG_SRC = `#version 300 es
precision mediump float;

out vec4 out_color;

in vec3 frag_wpos;
in vec3 frag_norm;
in float frag_test;
in vec3 frag_color;

uniform vec3 cameraPosition;
uniform vec3 color;

///////////////////////////////////////////////////////////////////////////////

#define RECIPROCAL_PI 0.3183098861837907
#define saturate( a ) clamp( a, 0.0, 1.0 )

vec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {
	float fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );
	return f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );
}

vec3 BRDF_Lambert( const in vec3 diffuseColor ){ return RECIPROCAL_PI * diffuseColor; }

vec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {
	vec3 halfDir    = normalize( lightDir + viewDir );
	float dotNH     = saturate( dot( normal, halfDir ) );
	float dotVH     = saturate( dot( viewDir, halfDir ) );
	vec3 F          = F_Schlick( specularColor, 1.0, dotVH );
	float G         = 0.25;

	// return F * ( G * D );
  return F * G; // D Causes the big Spot light kind of thing to appear
}

///////////////////////////////////////////////////////////////////////////////

void main(){
    // STRIPPED DOWN MeshPhongMaterial ( Prob screwed up somewhere )

    vec3 baseColor          = frag_color; //color;

    vec3 lightPos           = vec3( 4.0, 10.0, 40.0 );// BE vec3( 4.0, 10.0, 4.0 )
    vec3 lightColor         = vec3( 1.0 );
    vec3 lightDir           = normalize( lightPos - frag_wpos );

    vec3  viewDir           = normalize( cameraPosition - frag_wpos );
    vec3  norm              = normalize( frag_norm );
    float dotNL             = saturate( dot( norm, lightDir ) );

    vec3 indirectDiffuse    = vec3( 0.0 );
    vec3 directDiffuse      = vec3( 0.0 );
    vec3 directSpecular     = vec3( 0.0 );

    vec3 specularColor      = vec3( 1.0 );
    float specularShininess = 400.0;
    float specularStrength  = 1.0;

    vec3 ambientLightColor  = vec3( 1.1 );
    vec3 irradiance         = dotNL * lightColor;

    directDiffuse           += irradiance * BRDF_Lambert( baseColor );
    directSpecular          += irradiance * BRDF_BlinnPhong( lightDir, viewDir, norm, specularColor, specularShininess ) * specularStrength;
    indirectDiffuse         += ambientLightColor * BRDF_Lambert( baseColor );

    out_color.rgb           = directDiffuse + indirectDiffuse + directSpecular;
    out_color.a             = 1.0;
}`;

//#region Shader uses noise to create height map at runtime for the instanced hex columns.
const getRawShader = function getRawShader() {
  const mat = new THREE.RawShaderMaterial({
    vertexShader: VERT_SRC,
    fragmentShader: FRAG_SRC,
    transparent: true,
    side: THREE.DoubleSide,
    uniforms: {
      UYOffset: {
        value: 0.5
      },
      UColor: {
        value: new THREE.Color(0x3F70AE)
      },
    }
  });

  return mat;
}

const render = function render() {
  requestAnimationFrame(render_bind);

  //deltaTime = clock.getDelta();
  //elapsedTime = clock.elapsedTime;

  raycaster.setFromCamera(pointer, camera);

  const intersects = raycaster.intersectObject(mesh);

  // console.log(intersects);

  if (intersects.length > 0) {
    const instanceId = intersects[ 0 ].instanceId;

    console.log(instanceId);
/*
    const attrib = mesh.geometry.attributes.i_pos;
    console.log(attrib);

    attrib.setY(instanceId, 1.5);

    attrib.needsUpdate = true; */
  }

  //if (onRender) onRender(deltaTime, elapsedTime);
  renderer.render(scene, camera);
}

const onResize = function onResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize( window.innerWidth, window.innerHeight );
};

const init = function init () {
  set_camera(0, 90, 20, [0, 0.0, 0])
  //camera.position.set(0, 10, 40);

  gMat = getRawShader();
  gGeo = createInstancedHex();

  mesh = new THREE.Mesh(gGeo, gMat);

  scene.add(mesh);

  window.addEventListener( 'resize', onResize );
  document.addEventListener('mousemove', onPointerMove);

  //updateNoiseHeight();

  render();
};

var renderer = null;
var orbit = null;
var render_bind = render.bind(this);
var onRender = null;
var deltaTime = 0;
var elapsedTime = 0;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// LIGHTING
let light = new THREE.DirectionalLight(0xffffff, 0.8);
light.position.set(4, 10, 4);

// scene.add(light);
// scene.add(new THREE.AmbientLight(0x404040));

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RENDERER
let options = {
  antialias: true,
  alpha: true
};

// THREE.JS can't handle loading into WebGL2 on its own
// Need to create canvas & get the proper context, pass those 2 into 3js
if (config.webgl2) {
  let canvas = document.createElement("canvas");
  options.canvas = canvas;
  options.context = canvas.getContext("webgl2");
}

renderer = new THREE.WebGLRenderer(options);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x24F8FF, 1);

//---------------------------------
// where to add the cnavas object, in a container or in the body.
if (config.container) config.container.appendChild(renderer.domElement);
else document.body.appendChild(renderer.domElement);

//---------------------------------
// Have the canvas set as full screen or fill its container's space
if (config.fullscreen != false) {
  renderer.setSize(window.innerWidth, window.innerHeight);
} else {
  // Take the size of the parent element.
  let box = renderer.domElement.parentNode.getBoundingClientRect();
  renderer.setSize(box.width, box.height);

  // When changing the canvas size, need to update the Projection Aspect Ratio to render correctly.
  camera.aspect = box.width / box.height;
  camera.updateProjectionMatrix();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MISC
orbit = new OrbitControls(camera, renderer.domElement);

orbit.enablePan = false;
//orbit.enableZoom = false;
//orbit.enableRotate = false;

init();

// #region METHODS
function set_camera(lon, lat, radius, target) {
  let phi = (90 - lat) * Math.PI / 180,
    theta = (lon + 180) * Math.PI / 180;

  camera.position.set(
    -(radius * Math.sin(phi) * Math.sin(theta)),
    radius * Math.cos(phi),
    -(radius * Math.sin(phi) * Math.cos(theta))
  );

  if (target) orbit.target.fromArray(target);

  orbit.update();
}

function onPointerMove(event) {

  event.preventDefault();

  pointer.x = ( event.clientX / window.innerWidth ) * 2 - 1;
	pointer.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

  //console.log(pointer.x);
  //console.log(pointer.y);
}

//#region Creating Hex Map
// Compute XY Worldspace positions and Hex Grid Coordinates
function hexGrid(n) {
  let x, y, yn, p;

  gLayout = new Layout(0.51);
  const coord = [];
  const pos = [];
  //const n      = 1;

  for (x = -n; x <= n; x++) {
    y = Math.max(-n, -x - n);
    yn = Math.min(n, -x + n);

    for (y; y <= yn; y++) {
      p = gLayout.axialToPixel(x, y);

      pos.push(p[0], 0.5, p[1]);
      coord.push(x, y);
    }
  }

  return [pos, coord];
}

// Create all the hex columns in an instanced mesh
function createInstancedHex() {
  const [ipos, icoord] = hexGrid(32);
  CONFIG.coord = icoord;
  CONFIG.height = computeNoiseHeight();

  const inst_pos = new Float32Array(ipos);
  const inst_height = new Float32Array(CONFIG.height);

  //pointyUp = true, radius = 0.5, cornerScale = 0.2, cornerDiv = 3, capSize = 0.2, offsetHeight = 0.5
  const geo = HexagonPillar.get(false, 0.5, 0.04, 5, 0.04, 0);

  // Define Geometry Object
  bGeo = new THREE.InstancedBufferGeometry();

  bGeo.setIndex(geo.indices);
  bGeo.setAttribute("position", new THREE.BufferAttribute(new Float32Array(geo.vertices), 3));
  bGeo.setAttribute("normal", new THREE.BufferAttribute(new Float32Array(geo.normals), 3));
  bGeo.setAttribute("i_pos", new THREE.InstancedBufferAttribute(inst_pos, 3));
  bGeo.setAttribute("i_height", new THREE.InstancedBufferAttribute(inst_height, 1));

  bGeo.attributes.i_pos.needsUpdate = true;

  return bGeo;
}

// Make one Big Hex Board that hides under all the columns
function createHexBoard() {
  //pointyUp = true, radius = 0.5, cornerScale = 0.2, cornerDiv = 3, capSize = 0.2, offsetHeight = 0.5
  const geo = HexagonPillar.get(true, 28, 0.02, 10, 0.2, 0);

  // Define Geometry Object
  const bGeoU = new THREE.BufferGeometry();
  bGeoU.setIndex(geo.indices);
  bGeoU.setAttribute("position", new THREE.BufferAttribute(new Float32Array(geo.vertices), 3));
  bGeoU.setAttribute("normal", new THREE.BufferAttribute(new Float32Array(geo.normals), 3));

  return new THREE.Mesh(bGeoU, new THREE.MeshPhongMaterial({
    color: 0x00FFC2
  }));
}
//#endregion

//#region Handle Hexagon Height on the CPU
function computeNoiseHeight() {
  let ary = [];
  let c = CONFIG.coord;

  for (let i = 0; i < c.length; i += 2) {
    ary.push(0.1);
  }

  return ary;
}

function updateNoiseHeight() {
  const attrib = gGeo.attributes.i_pos;
  console.log(attrib.getY(1702));

  attrib.setY(0, 1);
  attrib.setY(1, 1.5);
  attrib.setY(3, 1);
  /*
  attrib.array.forEach((element, index) => {
      if (element === 0.5) {
          attrib.array[index] = 1;
      }
  }); */

  //attrib.array = new Float32Array( attrib );
  attrib.needsUpdate = true;
}

Not sure if this helps Hexagonal Grids (redblobgames.com)

Hey anidivr,

thanks for your answer. I know this Site, my main problem is getting the index of a hexagon via raycaster (?) at all.

You should be able to get the id from a raycast listener. “ Intersects[ 0 ].object.instanceId “ I believe. I do this on a hex grid no problem. However, once I have the ID i just use that in the instance[ instanceId ] reference and manipulate the instance directly. But you have the index regardless.

Hope it helps!