Strange offset of atlas textured tiles in hexagon planet

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

Never mind, found out that I had forgotten to apply UV-mapping to the 12 pentagons, so that distorted the whole array.

1 Like