The height map method for plane displacement is experiencing issues

The heightmap I have selected:

Scene without grass.jpg map :

Scene with grass.jpg map:

https://i.sstatic.net/q6ScO.png

import * as THREE from 'three';
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js';
import * as dat from 'dat.gui';

// dimensions of the plane x, y
const planeDim = new THREE.Vector2(20, 20);
const planeSeg = new THREE.Vector2(100, 100);

// Cam start coordinates x, y, z
let camPos = new THREE.Vector3(-10, 30, 30);

// Cam settings
const camFOV = 45;
const camAspect = window.innerWidth / window.innerHeight;
let camNear = 0.1;
let camFar = 1000;

// AxesHelper size
let axesHelperSize = 5;

// Gridhelper dimensions x, y
const gridHelperDim = new THREE.Vector2(20, 20);

// Mouseover highligthed tile starting coordinates x, y, z
let tilePos = new THREE.Vector3(0.5, 0, 0.5);

// Mouseover highlighted tile dimensions x, y
const tileDim = new THREE.Vector2(1, 1);


// Creating variables to work with raycasting from mouseposition
const mousePosition = new THREE.Vector2();
const raycaster = new THREE.Raycaster();
let intersects;

// Array of all sphere objects placed on the plane
const objects = [];

//creating the renderer
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);

// enable shadows in the scene
renderer.shadowMap.enabled = true;

document.body.appendChild(renderer.domElement);

// Creating the Scene
const scene = new THREE.Scene();

// Creating the camera
const camera = new THREE.PerspectiveCamera(
    camFOV,
    camAspect,
    camNear,
    camFar
);

// Declaring the Camera as an OrbitCamera
const orbit = new OrbitControls(camera, renderer.domElement);
camera.position.set(camPos.x, camPos.y, camPos.z);
orbit.update();

// creating and adding the AxesHelper to the scene
const axesHelper = new THREE.AxesHelper(axesHelperSize);
scene.add(axesHelper);


// Loading the heightmap-texture
const loader = new THREE.TextureLoader();
const displacementMap = loader.load('res/heightmap.jpg');
const map = loader.load('res/grass.jpg');

// creating the plane with displacement
const planeMesh = new THREE.Mesh(
  new THREE.PlaneGeometry(planeDim.x, planeDim.y, planeSeg.x, planeSeg.y),
  new THREE.MeshPhongMaterial({
    color: 0xFFFFFF,
    side: THREE.DoubleSide,
    visible: true,
    displacementMap: displacementMap,
    displacementScale: 20,
    map: map,
    flatShading: false
  })
);
// enable recieving shadows on the plane
planeMesh.receiveShadow = true;

// giving the plane a name
planeMesh.name = 'ground';
// adding the plane to the scene
scene.add(planeMesh);
// rotate the plane 90 degrees
planeMesh.rotation.x = -Math.PI / 2;

// creating the gridHelper on the plane
const gridHelper = new THREE.GridHelper(gridHelperDim.x, gridHelperDim.y);
// adding the gridhelper into the scene
scene.add(gridHelper);

// creating the highlighted tile, setting its position and adding it to the scene
const highlightMesh = new THREE.Mesh(
  new THREE.PlaneGeometry(tileDim.x, tileDim.y),
  new THREE.MeshBasicMaterial({
    color: 0x00FF00,
    side: THREE.DoubleSide,
    transparent: true
  })
);
highlightMesh.position.set(tilePos.x, tilePos.y, tilePos.z);
highlightMesh.rotation.x = -Math.PI / 2;
scene.add(highlightMesh);

// raycasting function. Tile on mouseposition will be highlighted
window.addEventListener('mousemove', function(e){
  mousePosition.x = (e.clientX / this.window.innerWidth) * 2 - 1;
  mousePosition.y = -(e.clientY / this.window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mousePosition, camera);
  intersects = raycaster.intersectObjects(scene.children);
  intersects.forEach(function(intersect){
    if(intersect.object.name === 'ground'){
      const highlightPos = new THREE.Vector3().copy(intersect.point).floor().addScalar(0.5);
      highlightMesh.position.set(highlightPos.x, 0, highlightPos.z);
    
      // returns true if tilespace is already used
      const objectExists = objects.find(function(object){
        return (object.position.x === highlightMesh.position.x)
        && (object.position.z === highlightMesh.position.z);
      });

      // changes tile color to white if tile is empty and red if not
      if(!objectExists){
        highlightMesh.material.color.setHex(0x00FF00);
      }else{
        highlightMesh.material.color.setHex(0xFF0000);
      }
    }
  });
});

// Creating the sphere object
const sphereMesh = new THREE.Mesh(
  new THREE.SphereGeometry(0.4, 4, 2),
  new THREE.MeshStandardMaterial({
    wireframe: true,
    color: 0xAEAEDB
  })
);
// enabling sphere to cast a shadow
sphereMesh.castShadow = true;

// Click event, clicking tile will spawn the sphere object on it
window.addEventListener('mousedown', function(){
  // returns true if the clicked tile already has a sphere
  const objectExists = objects.find(function(object){
    return (object.position.x === highlightMesh.position.x)
    && (object.position.z === highlightMesh.position.z);
  })

  // if tile is empty, spawn a shpere, else - console log
  if(!objectExists){
    intersects.forEach(function(intersect){
      if(intersect.object.name === 'ground'){
        const sphereClone = sphereMesh.clone();
        sphereClone.position.copy(highlightMesh.position);
        scene.add(sphereClone);
        objects.push(sphereClone);
        //make tile red instantly after clicking to indicate the tile space is already in use
        highlightMesh.material.color.setHex(0xFF0000);
      }
    }) 
  }else{
    console.log('Can not place, space is already used!')
  }
});


// adding ambient light to the scene
const ambientLight = new THREE.AmbientLight(0x333333);
scene.add(ambientLight);

// adding a spotlight to the scene
const spotLight = new THREE.SpotLight(0xFFFFFF);
scene.add(spotLight);
spotLight.position.set(-100, 100, 0);
spotLight.castShadow = true;
spotLight.angle = 0.2;
// ading a lighthelper to help see the light settings
const sLightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(sLightHelper);


// creating the light gui itself
const gui = new dat.GUI();

// adding options to dat.gui to change seetings of the light and plane
const options = {
  angle: 0.2,
  penumbra: 0,
  intensity: 1,
  wireframe: false
};
// creating the light gui settings and setting its boundries
gui.add(options, 'angle', 0, 1);
gui.add(options, 'penumbra', 0, 1);
gui.add(options, 'intensity', 0, 1);

// enables wireframemode for the plane
gui.add(options, 'wireframe').onChange(function(e){
  planeMesh.material.wireframe = e;
});

// animation loop with time parameter
function animate(time){
  // bind the gui options to the spotlight
  spotLight.angle = options.angle;
  spotLight.penumbra = options.penumbra;
  spotLight.intensity = options.intensity;
  // bind the gui options to the plane
  planeMesh.wireframe = options.wireframe;
  // update lighthelper appearence according to the settings
  sLightHelper.update();
  // make the tile blinking
  highlightMesh.material.opacity = 1 + Math.sin(time / 120);
  // rotation animation on every sphere object on the plane
  objects.forEach(function(object){
    object.rotation.x = time / 500;
    object.rotation.y = time / 500;
    object.position.y = 0.5 + 0.5 * Math.abs(Math.sin(time / 1000));
  })
  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

// logging in the browser console
console.log();

I am facing difficulties with my approach to using displacement maps for generating a plane with a displaced surface. Despite trying different textures and materials in ThreeJS, the plane remains either flat and white or turns black.

I attempted changing the path to the heightmap.jpg file and experimenting with various ThreeJS materials, including flat shading on the planeMesh.

Answer №1

If you want to correctly use an image as a texture, the first step is to import the image into your project:

import image from "./j1wxR.jpg";

After importing the image, you will then need to load it using a texture loader like this:

// Loading the heightmap-texture
const loader = new THREE.TextureLoader();
const displacementMap = loader.load(image);

In order to apply the texture on the material and utilize "displacementMap", make sure to include both map and displacementMap (similarly for loading the grass texture)

// Creating the plane with displacement
const planeMesh = new THREE.Mesh(
  new THREE.PlaneGeometry(planeDim.x, planeDim.y, planeSeg.x, planeSeg.y),
  new THREE.MeshPhongMaterial({
    side: THREE.DoubleSide,
    displacementMap: displacementMap,
    map: displacementMap,
    displacementScale: 5
  })
);

Note: I set the displacementScale to 5 for better visibility.

For a live example, check out this link to codesandbox: Codesandbox - example

Next steps may involve positioning the grid helper above the mesh and aligning spheres according to the mesh's structure. These could be topics for future questions!

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

Can anyone please guide me on how to extract the IP address of a specific individual using Node.js?

There's an individual running some kind of exploit scanner on my server. I'm receiving strange requests like: IP ADDRESS: ::ffff:127.0.0.1 www-0 (out): POST /cgi-bin/php5?%2D%64+%61%6C%6C%6F%77%5F%75%72%6C%5F%69%6E%63%6C%75%64%65%3D%6F%6E+%2D%64 ...

Chrome experiencing conflicts with backstretch when using multiple background images

In order to dynamically apply fullscreen background images in a WordPress site, I am utilizing Backstretch.js along with a custom field. Everything is functioning properly for the body's background image. However, there seems to be an issue with anot ...

What steps can be taken to execute a function when a button remains unclicked?

$("#RunCode").click(function(){ var $this = $(this); if($this.data('clicked')) { console.log("Is clicked"); $(".documentWrite").text("Is clicked"); } else { $this.data('clicked', true); consol ...

Disabling specific time slots in the mat select functionality

I have a scenario where I need to display time slots at 30-minute intervals using Mat Select. export const TIME=["12:00 AM","12:30 AM","01:00 AM","01:30 AM","02:00 AM","02:30 AM","03:00 AM&qu ...

React router will only replace the last route when using history.push

I'm working on implementing a redirect in React Router that triggers after a specific amount of time has elapsed. Here's the code I've written so far: submitActivity(){ axios.post('/tiles', { activityDate:thi ...

The interface vanishes upon the integration of TinyMCE into the module

Currently, I am working on a project using Angular-fullstack and attempting to integrate ui-TinyMCE. However, I encountered an issue when I made the following changes: angular.module('academiaUnitateApp') .controller('NewEntryCtrl', ...

Exploring the process of iterating through arrays within an object in vue.js using the v-for directive

Is there a way to iterate through an output object using the v-for template in Vue.js? new Vue({ el: app, data: { output: { player: [1, 5, 61, 98, 15, 315, 154, 65], monster: [14, 165, 113, 19, 22], }, }, }); <script src= ...

The useEffect function is failing to execute, leading to an issue with an undefined variable

Attempting to retrieve a specific string with the help of useRouter, then utilizing that same string to access a particular document from Firebase is my current goal. This sequence of actions is supposed to take place within the confines of the useEffect f ...

Samsung S4 Android device experiencing interruption in HTML5 video playback

When using Android Webview to play html5 videos, including Youtube videos (using my own tags and Youtube embedded iFrames), I came across an issue with the Samsung Galaxy S4. The problem occurs in the following scenario: Play a video. Press 'back&ap ...

What is the process of integrating Bootstrap into a Node project?

I recently set up a MEAN stack project using npm and am currently including Bootstrap via CDN: link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css') However, I want to add Bootstrap using ...

Locate a specific data point within an array of JSON objects

After receiving an array of JSON objects from JSP, I now have a set of data that contains book titles. "Titles":[ { "Book3" : "BULLETIN 3" } , { "Book1" : "BULLETIN 1" } , { "Book2" : "B ...

Guide to linking a specific event with JQuery event handlers

I have a Javascript function named Howto that is triggered by a button press: function howto(){ $('#elementtoupdate').ajaxSend(function() { //Code to run }); $('#elementtoupdate').ajaxComplete(function() { //Co ...

What is the reason behind the failure of executing "this.$refs.inputField.focus()"?

I've set up an input field with a ref="inputField" as shown below: <input ref="inputField"> <button @click="btn">Click</button> When the button is clicked, I want the input field to receive focus. Here& ...

Using AngularJS to pass radio button value to a $http.post request

Can you please advise on how to extract the value from a radio button and send it using $http.post in AngularJS? Here is an example: HTML <input name="group1" type="radio" id="test1" value="4"/> <label for="test1">Four paintings</label ...

NodeJS: The module failed to automatically register itself

Exploring the capabilities of IBM Watson's Speech to Text API, I encountered an issue while running my NodeJS application. To handle the input audio data and utilize IBM Watson's SpeechToText package, I integrated the line-in package for streami ...

Guide on attaching an onclick event to a button created with a string in Next.js?

<div onClick={(event) => this.addToCart(event)}> <ReactMarkdownWithHtml children={this.props.customButton} allowDangerousHtml /> </div> In my current situation, I am facing an issue with the code above. The button is being rendered ...

Creating a GIF file using Node.js leads to a corrupted image

I have been experimenting with creating a simple GIF. I followed the example provided in this NPM library for guidance. Despite my efforts, every time I generate the GIF, it appears as a corrupted image file. CODE const CanvasGifEncoder = require('ca ...

Combining JSON parameter and multipart/form-data file in a single request using Vue.js

Is there a way to send both JSON parameters and multipart/form-data files in a single request using Vue, similar to how Postman does it? https://i.sstatic.net/CMi3D.png I've been looking into how to submit "multipart/form-data" from VueJs. What I nee ...

Javascript functions function properly only if they contain the 'alert()' command

My aim is to utilize Ajax (Javascript + php) for checking user name availability when a user moves focus to another form field. The strange part is that my functions only work when I include some alert(), without them, the functions fail to operate. Anoth ...

Utilize jQuery to extract data from a JSON object

While I have come across numerous examples of parsing JSON objects in jQuery using $.parseJSON and have grasped the concept, there are some fundamental aspects missing that are preventing me from successfully parsing the following VALID JSON: { "studen ...