I’ve continued work on my hexagon planet (see other thread here) and I am trying to use a texture atlas to add terrain for each tile. However, it results in some strange kind of offset of mapping:
As you can see, it is as if there is a 4-tile deep “offset” or “switch“ along the edges of the larger triangles of the icosahedron.
If I don’t use a texture atlas, and just make two separate materials for land and ocean, everything looks fine:
I’ve looked at the UV-mapping for each tile in the merged geometry, and it looks correct. Is this some sort of indexing issue in the grouping of the tiles in the merged geometry?
My code (still very much WIP):
// GlobeMap.js
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { mergeGeometries, mergeVertices } from '../../node_modules/three/examples/jsm/utils/BufferGeometryUtils.js';
import * as GameTurn from 'GameTurn';
import * as GlobeMapUtility from 'GlobeMapUtility';
var scene;
var renderer;
var hexasphere;
var mergedGeometries;
var omniMaterial;
var planetMesh;
var indexedMaterials = [];
var markupMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, transparent: true});
var landMaterial = new THREE.MeshBasicMaterial({color: 0x7cfc00, transparent: true});
var oceanMaterial = new THREE.MeshBasicMaterial({color: 0x0f2342, transparent: true});
var globalTextureMaterial;
var oldTileMaterial;
var oldTileIndex;
var globalUVMapTextureLayout = {
'ocean': { x: 0, y: 0, w: 256, h: 256 },
'land': { x: 256, y: 256, w: 256, h: 256 }
}
export function initiateGlobeMap() {
var globeMapAreaWindow = document.getElementById('globe_map_area_window');
globeMapAreaWindow.style.display = 'grid';
var width = window.innerWidth;
var height = window.innerHeight;
// Renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( width, height);
// Camera
var cameraDistance = 70;
var camera = new THREE.PerspectiveCamera( cameraDistance, width / height, 1, 200);
camera.position.z = -cameraDistance;
// Control
var controls = new OrbitControls( camera, renderer.domElement );
controls.update();
// Raycasting/interaction
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
// Scene
scene = new THREE.Scene();
scene.fog = new THREE.Fog( 0x000000, cameraDistance * 0.4, cameraDistance * 1.2);
var img = document.getElementById("projection");
var projectionCanvas = document.getElementById('temp_canvas');
var projectionContext = projectionCanvas.getContext('2d');
projectionCanvas.width = img.width;
projectionCanvas.height = img.height;
projectionContext.drawImage(img, 0, 0, img.width, img.height);
var pixelData = null;
var maxLat = -100;
var maxLon = 0;
var minLat = 0;
var minLon = 0;
var isLand = function(lat, lon){
var x = parseInt(img.width * (lon + 180) / 360);
var y = parseInt(img.height * (lat + 90) / 180);
if(pixelData == null){
pixelData = projectionContext.getImageData(0,0,img.width, img.height);
}
return pixelData.data[(y * pixelData.width + x) * 4] === 0;
};
var createScene = function(radius, divisions, tileSize){
while(scene.children.length > 0){
scene.remove(scene.children[0]);
}
hexasphere = new Hexasphere(radius, divisions, tileSize);
var geometries = [];
var texture = new THREE.TextureLoader().load('graphics/special/uv_map_test.png' );
globalTextureMaterial = new THREE.MeshBasicMaterial({
map: texture,
wireframe: false
});
for(var i = 0; i < hexasphere.tiles.length; i++){
var tile = hexasphere.tiles[i];
tile.indexId = i;
var latLon = tile.getLatLon(hexasphere.radius);
var boundaryPoints = tile.boundary; // array of Points
var geometry = new THREE.BufferGeometry();
// Pentagon
var points = [
new THREE.Vector3(boundaryPoints[0].x, boundaryPoints[0].y, boundaryPoints[0].z), // 1
new THREE.Vector3(boundaryPoints[1].x, boundaryPoints[1].y, boundaryPoints[1].z), // 2
new THREE.Vector3(boundaryPoints[2].x, boundaryPoints[2].y, boundaryPoints[2].z), // 3
new THREE.Vector3(boundaryPoints[0].x, boundaryPoints[0].y, boundaryPoints[0].z), // 1
new THREE.Vector3(boundaryPoints[2].x, boundaryPoints[2].y, boundaryPoints[2].z), // 3
new THREE.Vector3(boundaryPoints[3].x, boundaryPoints[3].y, boundaryPoints[3].z), // 4
new THREE.Vector3(boundaryPoints[0].x, boundaryPoints[0].y, boundaryPoints[0].z), // 1
new THREE.Vector3(boundaryPoints[3].x, boundaryPoints[3].y, boundaryPoints[3].z), // 4
new THREE.Vector3(boundaryPoints[4].x, boundaryPoints[4].y, boundaryPoints[4].z), // 5
]
// Hexagon
if(boundaryPoints.length > 5) {
points.push(new THREE.Vector3(boundaryPoints[0].x, boundaryPoints[0].y, boundaryPoints[0].z)); // 1
points.push(new THREE.Vector3(boundaryPoints[4].x, boundaryPoints[4].y, boundaryPoints[4].z)); // 5
points.push(new THREE.Vector3(boundaryPoints[5].x, boundaryPoints[5].y, boundaryPoints[5].z)); // 6
}
geometry.setFromPoints(points);
// UV mapping
var uvArray;
// Pentagon
if(boundaryPoints.length == 5) {
uvArray = new Float32Array(10);
indexedMaterials.push(oceanMaterial);
}
// Hexagon
else if(boundaryPoints.length == 6) {
// Hexagon UV
var textureTileType;
if(isLand(latLon.lat, latLon.lon)) {
textureTileType = 'land';
}
else {
textureTileType = 'ocean';
}
tile.textureTileType = textureTileType;
uvArray = GlobeMapUtility.getTextureUVCoordinates(globalUVMapTextureLayout, textureTileType, 'hexagon', 2048);
tile.uvArray = uvArray;
indexedMaterials.push(globalTextureMaterial);
}
else {
console.log('No proper type detected');
}
geometry.setAttribute('uv', new THREE.BufferAttribute(uvArray, 2));
geometry.attributes.uv.needsUpdate = true;
geometry.computeVertexNormals(); // does nothing?
geometry.lookAt(new THREE.Vector3(0,0,0)); // does nothing?
geometries.push(geometry);
}
mergedGeometries = mergeGeometries(geometries,true);
planetMesh = new THREE.Mesh(mergedGeometries, indexedMaterials);
scene.add(planetMesh);
const axesHelper = new THREE.AxesHelper(50);
scene.add( axesHelper );
window.hexasphere = hexasphere;
};
createScene(35, 75, 1);
var startTime = Date.now();
var lastTime = Date.now();
function render(){
// update the picking ray with the camera and pointer position
raycaster.setFromCamera( pointer, camera );
// calculate objects intersecting the picking ray
/*const intersects = raycaster.intersectObjects( scene.children );
for ( let i = 0; i < intersects.length; i ++ ) {
//console.log(intersects[i].faceIndex);
//intersects[i].object.material.color.set( 0xff0000 );
}
*/
requestAnimationFrame(render);
controls.update();
renderer.render( scene, camera );
}
document.getElementById("globe_map_area").append(renderer.domElement);
window.addEventListener( 'resize', onWindowResize, false );
window.addEventListener( 'pointermove', onPointerMove );
window.scene = scene;
window.createScene = createScene;
requestAnimationFrame(render);
}

