Kits87
Kits87

Reputation: 31

How do I plot random meshes on top of a terrain using a heightmap in three.js?

So as the title states I'd like to know how to plot randomly generated meshes at the y-position that matches the terrain's corresponding y-position in three.js. I've looked through the docs and feel like using a raycaster might work, but I can only see examples that uses the detection as a mouse event, and not before render, so I'm not sure how to implement it.

Here is my code for the terrain, heightmap, and mesh plotting so far. It all works technically, but as you can see the plotAssets meshes y-positions are just sitting at zero right now. Any insights would be very much appreciated, I'm pretty new to three.js.

Terrain:

        var heightmaploader = new THREE.ImageLoader();
    heightmaploader.load(
        "assets/noisemaps/cloud.png",
        function(img) {
            data = getHeightData(img);

            var terrainG = new THREE.PlaneBufferGeometry(700, 700, worldWidth - 1, worldDepth - 1);
            terrainG.rotateX(-Math.PI / 2);

            var vertices = terrainG.attributes.position.array;

            for (var i = 0, j = 0, l = vertices.length; i < l; i++, j += 3) {
                vertices[j + 1] = data[i] * 5;
            }

            terrainG.computeFaceNormals();
            terrainG.computeVertexNormals();

            var material = new THREE.MeshLambertMaterial({
                map: terrainT,
                //side: THREE.DoubleSide,
                color: 0xffffff,
                transparent: false,
            });

            terrain = new THREE.Mesh(terrainG, material);
            terrain.receiveShadow = true;
            terrain.castShadow = true;
            terrain.position.y = 0;
            scene.add(terrain);

            plotAsset('boulder-photo-01.png', 30, 18, data);
            plotAsset('boulder-outline-01.png', 20, 20, data);
            plotAsset('birch-outline-01.png', 10, 50, data);
            plotAsset('tree-photo-01.png', 20, 50, data);
            plotAsset('grass-outline-01.png', 10, 20, data);
            plotAsset('grass-outline-02.png', 10, 20, data);
        }
    );

Plot Assets:

    function plotAsset(texturefile, amount, size, array) {
    console.log(array);
    var loader = new THREE.TextureLoader();
    loader.load(
        "assets/textures/objects/" + texturefile,
        function(texturefile) {
            var geometry = new THREE.PlaneGeometry(size, size, 10, 1);
            var material = new THREE.MeshBasicMaterial({
                color: 0xFFFFFF,
                map: texturefile,
                side: THREE.DoubleSide,
                transparent: true,
                depthWrite: false,
                depthTest: false,
                alphaTest: 0.5,
            });

            var uniforms = { texture:  { value: texturefile } };
            var vertexShader = document.getElementById( 'vertexShaderDepth' ).textContent;
            var fragmentShader = document.getElementById( 'fragmentShaderDepth' ).textContent;

            // add bunch o' stuff
            for (var i = 0; i < amount; i++) {
                var scale = Math.random() * (1 - 0.8 + 1) + 0.8;
                var object = new THREE.Mesh(geometry, material);
                var x = Math.random() * 400 - 400 / 2;
                var z = Math.random() * 400 - 400 / 2;
                object.rotation.y = 180 * Math.PI / 180;
                //object.position.y = size * scale / 2;
                object.position.x = x;
                object.position.z = z;
                object.position.y = 0;
                object.castShadow = true;

                object.scale.x = scale; // random scale
                object.scale.y = scale;
                object.scale.z = scale;

                scene.add(object);

                object.customDepthMaterial = new THREE.ShaderMaterial( {
                uniforms: uniforms,
                vertexShader: vertexShader,
                fragmentShader: fragmentShader,
                side: THREE.DoubleSide
                } );
            }
        }
    );
}

Height Data:

    function getHeightData(img) {
    var canvas = document.createElement('canvas');
    canvas.width = 2048 / 8;
    canvas.height = 2048 / 8;
    var context = canvas.getContext('2d');

    var size = 2048 / 8 * 2048 / 8,
        data = new Float32Array(size);

    context.drawImage(img, 0, 0);

    for (var i = 0; i < size; i++) {
        data[i] = 0
    }

    var imgd = context.getImageData(0, 0, 2048 / 8, 2048 / 8);
    var pix = imgd.data;

    var j = 0;
    for (var i = 0, n = pix.length; i < n; i += (4)) {
        var all = pix[i] + pix[i + 1] + pix[i + 2];
        data[j++] = all / 40;
    }

    return data;
}

Upvotes: 0

Views: 309

Answers (1)

prisoner849
prisoner849

Reputation: 17576

Yes, using of THREE.Raycaster() works well. A raycaster has the .set(origin, direction) method. The only thing you have to do here is to set the point of origin higher than the highest point of the height map.

var n = new THREE.Mesh(...); // the object we want to aling along y-axis
var collider = new THREE.Raycaster();
var shiftY = new THREE.Vector3();
var colliderDir = new THREE.Vector3(0, -1, 0); // down along y-axis to the mesh of height map

shiftY.set(n.position.x, 100, n.position.z); // set the point of the origin
collider.set(shiftY, colliderDir);  //set the ray of the raycaster
colliderIntersects = collider.intersectObject(plane); // plane is the mesh of height map
if (colliderIntersects.length > 0){
    n.position.y = colliderIntersects[0].point.y; // set the position of the object
}

jsfiddle example

Upvotes: 1

Related Questions