What is the best approach for overlaying random meshes onto a terrain using a heightmap in three.js?

I'm interested in plotting randomly generated meshes at the y-position that corresponds to the terrain's height in three.js. While browsing through the documentation, I came across the raycaster method, which seems like it could be useful. However, I only found examples that utilize raycasting for mouse events, not for pre-rendering, so I'm unsure about the implementation.

Below is the snippet of code I've been working on for the terrain, heightmap, and mesh plotting. Although everything technically functions, the y-positions of the plotAssets meshes are currently stuck at zero. Any advice or insights would be greatly appreciated as I'm relatively 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;
}

Answer №1

A successful technique is to utilize the THREE.Raycaster(). This raycaster includes the useful method .set(origin, direction). To ensure effectiveness, simply establish the origin point higher than the highest point on the height map.

var n = new THREE.Mesh(...); // represents the object we aim to align along the y-axis
var collider = new THREE.Raycaster();
var shiftY = new THREE.Vector3();
var colliderDir = new THREE.Vector3(0, -1, 0); // downward direction along the y-axis towards the height map mesh

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

Sample on jsfiddle

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using HTML5 chunks and web workers will not involve any uploading of files

I encountered an issue while working with html5 slice and webworker. It seems that when I try to upload a file using the uploadFile function, nothing is happening and the file is not being uploaded. <html> <head> <title>Uploa ...

Using Vue 3, Bootstrap, and Pinia to create an innovative Global Modal experience

After creating a ModalComponent.vue file that I intend to use across different sections, I encountered an issue with closing the modal after calling my Pinia stores. The modal includes slots for the title, body, and footer, along with a standard close butt ...

The backbone module is experiencing formatting issues

I'm new to learning backbone.js. I've created the example below, but unfortunately, it's not functioning properly. Can someone please help me understand why? The goal is to simply display the name within my div element. (function($) { ...

I am having trouble getting my AngularJS client to properly consume the RESTful call

As I venture into the world of AngularJS and attempt to work with a RESTful service, I am encountering a challenge. Upon making a REST call to http://localhost:8080/application/webapi/myApp/, I receive the following JSON response: { "content": "Hel ...

Obtain Page Parameters within a Nested Next.js Layout using App Router

My Next.js App Router has a nested dynamic page structure using catchall routes configured like this: app/stay/ |__[...category] | |__page.js |__[uid] | |__page.js |__layout.js Within the 'layout.js' file, there is a con ...

Creating a Yeoman application with a personalized Node.js server

As I embark on the journey of developing a node.js and angular application using the powerful Yeoman tool, I can't help but wonder about one thing. Upon generating my application, I noticed that there are predefined tasks for grunt, such as a server ...

Ajax successful event fails to trigger

Having Trouble Implementing Okta Authentication with WebForms The login functionality is working, but the redirect part is not functioning correctly I have attempted to use void and return a JSON object/string, but it does not seem to work If I remove th ...

Backing up a mongodb collection can be easily achieved with the help of express.js and Node.js

I am looking to create a monthly backup of my userdatas collection. The backup process involves: I intend to transfer the data from the userdatas collection to a designated backupuserdatas collection. A batch program should be scheduled to run automatica ...

What are the advantages of choosing express.js over Ruby on Sinatra?

Currently brainstorming for a social app and contemplating the switch from my initial option, Sinatra/Ruby to express.js/nodejs. My main focus is on the abundance of open source projects in Ruby that can expedite development. Another major consideration i ...

Tips for initiating a component within a loop and terminating it after completing all 3 iterations

Looking for a solution to open and close tags in a loop every 3 iterations. The objective is to create a grid using container, row, and column elements. However, I am unsure how to achieve this. Example: render(){ const arrayName = ["john", " ...

By default, the D3 hierarchy will collapse to the first level

I've been working on code to create an indented tree structure. My goal is to initially collapse the tree and show only the first level children or the root node. I tried a few different approaches, but none were successful. The current portion I have ...

What are some creative ways to customize and animate the cursor or caret within an input field?

Just to clarify, I am working with React and Material-UI. I have a task at hand where I need to modify the appearance of the caret within an input element by adding an animation to it. I have managed to change its color using caret-color and set a default ...

When attempting to register a custom Gamepad class using GamepadEvent, the conversion of the value to 'Gamepad' has failed

I have been working on developing a virtual controller in the form of a Gamepad class and registering it. Currently, my implementation is essentially a duplicate of the existing Gamepad class: class CustomController { readonly axes: ReadonlyArray<nu ...

Create and export a React component with dual properties

Currently, I am utilizing MaterialUI in my project and exporting components in the following manner: import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles"; ... export default withStyles(styles)(Users); Recently, I have integrated ...

Toggle the visibility of an element using a checkbox in JavaScript

In my scenario, there are 8 checkboxes where only one is checked by default. If you click on the unchecked checkboxes twice, the table linked to them will hide. Ideally, I want the unchecked checkboxes to be hidden by default and the checked one to be sh ...

The withRouter function in React Router does not automatically inject the router

Desiring to implement withRouter on my primary React component named 'App'. You can view the documentation here. This is how I utilize it: import React from "react"; import { render } from "react-dom"; import {Router, Link, hashHistory, Rout ...

How can I load only specific images on a webpage using HTML?

I attempted to implement an image filter for my website by using the code below: <script> function myFunction() { // Initialize variables var input, filter, ul, li, a, i; input = document.getElementById('myInput'); filter = input.value.toU ...

Setting up Quill in a Nuxt project (a VUE component)

I'm struggling to remove the toolbar in Quill despite my efforts. This is the HTML snippet I am working with: <template> <quill v-model="content" :config="config"></quill> </template Here's what I have inside the scri ...

Using NodeJS with the Express framework to send a GET request

Can express be used as a client-module for making http-requests to another server? Currently, I'm handling requests like this: var req = http.get({host, path}, function(res) { res.on('data', function(chunk) { .... } } This ...

What is the best way to fill an array within an object using React Hooks?

I am encountering an issue with an object that includes an array. Here is the code snippet in question: const [data, setData] = useState({ jobs: [] }); Currently, I am retrieving data from an API and need to append this fetched information to the jobs arr ...