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;
}