What is the process for mapping a texture or shape to a particular section of a mesh in Three.js?

Is there a way to project a texture or shape onto a specific part of a mesh, such as a transparent ring or circle? I want to achieve a similar effect to what we see in games when selecting an enemy or NPC, where a circle appears under the character to indicate selection. How can I make this circle change shape based on the mesh's height and slope? I have tried raycasting the mesh and rotating the vertices normal, but this method does not work well for more complex parts of the mesh. Do I need to use shaders for this? Are there any resources I can refer to for help?

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

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

Answer №1

It's not as challenging as it may appear at first glance.

Adjust the ground material using the .onBeforeCompile method, passing the position of the selected object as a uniform, and then manipulate it in the shaders.

In the provided code snippet, you can click on a button to choose the corresponding object, causing a selection marker to track its movement on the ground:

body{
  overflow: hidden;
  margin: 0;
}
#selections {
  width: 100px;
  display: flex;
  flex-direction: column;
}
button.selected{
  color: #00ff32;
  background: blue;
}
<div id="selections" style="position: absolute;border: 1px solid yellow;"></div>
<script type="module">
import * as THREE from "https://cdn.skypack.dev/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f18599839494b1c1dfc0c2c2">[email protected]</a>";
import {
  OrbitControls
} from "https://cdn.skypack.dev/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dca8b4aeb9b99cecf2edefef">[email protected]</a>/examples/jsm/controls/OrbitControls.js";

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 5, 8);
camera.lookAt(scene.position);
let renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(innerWidth, innerHeight);
renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);

let controls = new OrbitControls(camera, renderer.domElement);

let light = new THREE.DirectionalLight(0xffffff, 1);
light.position.setScalar(1);
scene.add(
    light,
  new THREE.AmbientLight(0xffffff, 0.5)
);

let objects = new Array(5).fill(0).map((p,idx)=>{return setObject(idx)});
//console.log(objects);
let selected = objects[0];

let g = new THREE.PlaneGeometry(10, 10, 5, 5);
g.rotateX(-Math.PI * 0.5);
for(let i = 0; i < g.attributes.position.count; i++){
    g.attributes.position.setY(i, (Math.random() * 2 - 1) * 0.75);
}
g.computeVertexNormals();
let uniforms = {
    selection: {value: new THREE.Vector3()}
}
let m = new THREE.MeshLambertMaterial({
    color: 0x003264,
    map: new THREE.TextureLoader().load("https://threejs.org/examples/textures/water.jpg"),
  onBeforeCompile: shader => {
    shader.uniforms.selection = uniforms.selection;
    shader.vertexShader = `
        varying vec3 vPos;
      ${shader.vertexShader}
    `.replace(
        `#include <begin_vertex>`,
      `#include <begin_vertex>
        vPos = transformed;
      `
    );
    shader.fragmentShader = `
        #define ss(a, b, c) smoothstep(a, b, c)
        uniform vec3 selection;
      varying vec3 vPos;
      ${shader.fragmentShader}
    `.replace(
        `#include <dithering_fragment>`,
      `#include <dithering_fragment>
      
        // shape
        float dist = distance(selection.xz, vPos.xz);
        float r = 0.25;
        
        float shape = (ss(r-0.1, r, dist)*0.75 + 0.25) - ss(r, r + 0.1, dist);
        
        vec3 col = mix(gl_FragColor.rgb, vec3(0, 1, 0.25), shape);
        gl_FragColor = vec4(col, gl_FragColor.a);
      `
    );
    //console.log(shader.fragmentShader)
  }
});

let o = new THREE.Mesh(g, m);
scene.add(o);

window.addEventListener("resize", onResize);

let clock = new THREE.Clock();

renderer.setAnimationLoop(_ => {
    
  let t = clock.getElapsedTime() * 0.5;
  
  objects.forEach(obj => {
    let ud = obj.userData;
    obj.position.x = Math.cos(t * ud.scaleX + ud.initPhase) * 4.75;
    obj.position.y = 1;
    obj.position.z = Math.sin(t * ud.scaleZ + ud.initPhase) * 4.75;
  })
  
  o.worldToLocal(uniforms.selection.value.copy(selected.position));
  
  renderer.render(scene, camera);
  
})

function setObject(idx){
    let g = new THREE.SphereGeometry(0.25);
  let m = new THREE.MeshLambertMaterial({color: 0x7f7f7f * Math.random() + 0x7f7f7f});
  let o = new THREE.Mesh(g, m);
  o.userData = {
    initPhase: Math.PI * 2 * Math.random(),
    scaleX: Math.random() * 0.5 + 0.5,
    scaleZ: Math.random() * 0.5 + 0.5
  }
  scene.add(o);
  
  let btn = document.createElement("button");
  btn.innerText = "Object " + idx;
  selections.appendChild(btn);
  btn.addEventListener("click", event => {
    selections.querySelectorAll("button").forEach(b => {b.classList.remove("selected")});
    btn.classList.add("selected");
    selected = o
  });
  
  return o;
}

function onResize(event) {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
}

</script>

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

Is there a way to dynamically change the height in jQuery/JavaScript?

I am encountering an issue with my embed code where the height changes randomly after a few seconds, depending on certain parameters. Here is an example of the code I am using: $( <embed></embed>) .attr("src", "http://www.bbs.com") ...

Conquering challenges arising from organizing subdirectories for js/css/xml files

During the process of developing a website with html, css, js and xml files stored on my computer (not yet online), I initially kept all the files in one folder along with an images folder. However, as the project progressed and things started to get mes ...

React.js: The function useDef has not been defined

Attempting to create a React.js calculator application, my initial step was to delete the entire src folder and replace it with a new one containing the necessary elements for the web app. Here is the content of the index.js file: import React,{ useEffect, ...

Is it possible to only make the text in a Bootstrap button act like a link, instead of the whole button itself?

As I start learning HTML and CSS with the help of Bootstrap, I'm encountering an issue with button functionality. The text within the button is acting like a link, but the entire button should be clickable. I'm not sure if I need more than just t ...

Performing a task once elements are added to an array

I created a function that calls an API to retrieve information for each product and then stores it in an array. After looping through all the products, I want to perform an action with the array. Here is my code: newProducts: any = [] loadOfferRelated(o ...

Is it possible for setInterval to trigger restarts in Apache?

Although I am not a professional coder, I have been teaching myself by experimenting with code snippets. I enjoy playing around with coding experiments as a way to gain experience. I attempted to load PHP into a div, which worked fine. However, when I tri ...

The React ternary operator within HTML does not display the correct HTML output

I'm currently learning React and facing a challenge with using a ternary operator. My goal is to display a minus sign by default, and then switch it to a plus sign when clicked. I implemented the ternary operator in my JSX and set the initial state of ...

React: Error parsing - Missing closing JSX tag for <input> element

I am currently learning React and am in the process of working on a new component. Here is the code snippet that I have so far: order-item.component.jsx: import React, { Component } from 'react'; import { connect } from 'react-redux'; ...

Form validation has not passed the necessary checks

Struggling to create a new user POST form with Bootstrap form validation but encountering an issue. The button code is as follows: <button class="btn btn-primary btn-addUser" type="submit">Add User</button> This button trig ...

Ensure to pass the object as a prop while navigating to the new route

There's a function located outside the router view component. goToMarkets(){ this.$router.push({path: '/markets', params: {stock: this.model}}) } However, when this function is triggered, the prop remains undefined in the "Markets ...

Issues with npm @material-ui/core Button and TextField functionality causing errors and failures

I'm currently working on a React project and decided to incorporate the material-ui framework. After creating a component that includes a textfield and a button, I've encountered an issue where I can't interact with either of them as they s ...

The Android app fails to load web pages on the mobile device

Encountering an issue - I can access a website from my app on a web browser without any problems. However, when I install the same app on my smartphone, it fails to function. The cause of this problem eludes me. ...

Make sure to prevent losing the global status in Vuex and VueRouter when the page is refreshed

As I develop a Single Page Application (SPA), I am utilizing Vuex to manage session states on the client side. However, I have noticed that the state resets whenever the browser is manually refreshed. Is there a way to prevent this behavior without relying ...

Creating a specialized Angular directive for handling input of positive numbers

I am working on an application that requires a text field to only accept positive integers (no decimals, no negatives). The user should be restricted to entering values between 1 and 9999. <input type="text" min="0" max="99" number-mask=""> While s ...

Incorporating Error Management in Controller and Service: A Step-by-Step Guide

Take a look at the structure of my angular application outlined below: Within my 'firm.html' page, there is a button that triggers the code snippet provided. Controller The controller initiates a Service function. The use of generationInProgre ...

Suggestions for autocomplete in a textarea within an HTML element

<!DOCTYPE html> <html> <head> <base href="http://demos.telerik.com/kendo-ui/autocomplete/index"> <style>html { font-size: 14px; font-family: Arial, Helvetica, sans-serif; }</style> <title></title> ...

Can dates in the form of a String array be transmitted from the server to the client?

Struggling to send a String array from the server side to the client using Nodejs and Pug. Encounter errors like "SyntaxError: expected expression, got '&'" or "SyntaxError: identifier starts immediately after numeric literal". Server runs o ...

The browser automatically adds a backslash escape character to a JavaScript object

When attempting to send an MQTT message to a topic from my angular app, the message needs to be in a specific syntax: { "Message": "hello" //the space after : is mandatory } However, upon sending the message in the correct format, the browser aut ...

How to trigger a force reload on a VueJS route with a different query parameter?

Is there a method to refresh the page component when two pages utilize the same Component? I have encountered an issue where the router does not reload and the previous edit values persist. { path: "/products/new", component: ProductPage, meta: { ...

The React client is unable to establish a connection with the server socket

Recently diving into the world of socket-io and react, utilizing express for the backend. The socket.io-client version being used is 3.0.3, while the backend's socket-io package matches at 3.0.3 as well. Interestingly enough, the server handles connec ...