Animating 3D shapes in Three.js

I developed a futuristic snake game where snake-like entities move around in an animated fashion using various cube meshes and the THREE.BoxGeometry:

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

To enhance the game, I aim to create each snake with a single mesh and geometry for easy addition of textures and rounded edges.

My current challenge is transforming a set of 3D points into a unified geometry that resembles the segmented box-like snakes seen in the demonstration (similar to multiple connected instances of THREE.BoxGeometry).

In my attempt to achieve this, I experimented with utilizing THREE.CurvePath and THREE.ExtrudeGeometry:

_makeSnakeGeometry(positions) {
  const vectors = positions.map(p => new THREE.Vector3(...p));
  const curvePath = new THREE.CurvePath();
  const first = vectors[1];
  const last = vectors[vectors.length - 1];

  let curveStart = vectors[0];
  let previous = curveStart;
  let previousDirection;

  // Traversing through the positions. If there's a change in direction, insert a new curve on the path.
  for (let vector of vectors.slice(1)) {
    let direction = previous.clone().sub(vector);

    if (vector.equals(first)) {
      curveStart.sub(direction.clone().divideScalar(2));
    }

    if (vector.equals(last)) {
      previous.sub(previousDirection.clone().divideScalar(2));
    }

    if ((previousDirection && !previousDirection.equals(direction)) || vector.equals(last)) {
      curvePath.add(new THREE.LineCurve3(curveStart, previous));
      curveStart = previous;
    }

    previous = vector;
    previousDirection = direction;
  }

  const p = Const.TILE_SIZE / 2;
  const points = [[-p, -p], [-p, p], [p, p], [p, -p]];
  const shape = new THREE.Shape(points.map(p => new THREE.Vector2(...p)));
  const geometry = new THREE.ExtrudeGeometry(shape, {
    steps: 20,
    extrudePath: curvePath,
    amount: 20
  });

  return geometry;
}

Unfortunately, the result appears unattractive with rounded corners occurring at the intersection points:

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

Increasing the steps option improves the visual appeal, yet introduces a high vertex count due to the smoothing effect on the curved edges. This concerns me as recreating this geometry might be required on every animation frame.

For animating the geometry, I tested out geometry.morphTargets but encountered limited success. While the morphing takes place, it doesn't meet aesthetic standards. Perhaps manual manipulation of the vertices is necessary?

In conclusion, my queries are as follows:

  • How can I generate a low-vertex-count geometry resembling interconnected box geometries?
  • What's the recommended approach for animating a geometry when the number of vertices could vary?

Answer №1

When creating the snake's shape, I found it necessary to combine various geometries:

_generateSnakeGeometry(snakePositions) {
  snakePositions = snakePositions.map(pos => new THREE.Vector3(...pos));
  const finalGeometry = new THREE.Geometry();

  for (let i = 0; i < snakePositions.length; i += 1) {
    const currentPosition = snakePositions[i];
    const voxelMesh = createVoxelMesh(Constants.TILE_SIZE, { position: currentPosition });
    voxelMesh.updateMatrix();
    finalGeometry.merge(voxelMesh.geometry, voxelMesh.matrix);
  }

  return finalGeometry;
}

The function createVoxelMesh produces a single cube mesh for each segment of the snake. By utilizing finalGeometry.merge, I assemble them together and repeat the process throughout the animation. However, this method is not flawless as the resulting geometry contains unnecessary vertices and faces for such a straightforward shape. Is there a technique available to reduce these extra components later on?

To animate the snake, I pinpoint the four vertices forming the head face by examining their positions and face normals:

_locateHeadFaceVertices() {
  const movement = this._direction.clone().multiplyScalar(1 + (Constants.TILE_SIZE / 10));
  const headPosition = new THREE.Vector3(...this.head).add(movement);
  const { vertices, faces } = this.mesh.geometry;

  // Arrange vertices based on proximity to a point near the snake's head, retaining only those equidistant from it.
  const nearest = vertices
    .map(v => [v.distanceTo(headPosition), v])
    .sort((a, b) => a[0] - b[0])
    .filter((pair, i, sorted) => pair[0] === sorted[0][0])
    .map(pair => pair[1]);

  const outcome = [];
  const seenVert = {};

  // Select vertices belonging to faces oriented in the same direction as the snake.
  for (let f of faces) {
    for (let v of [vertices[f.a], vertices[f.b], vertices[f.c]]) {
      const key = v.toArray().toString();
      if (!seenVert[key] && nearest.includes(v) && f.normal.equals(this._direction)) {
        seenVert[key] = true;
        outcome.push(v);
      }
    }
  }

  return outcome;
}

In order for the animation to function properly, I had to include the line

this.mesh.geometry.verticesNeedUpdate = true;
after each frame.

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

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

Having trouble retrieving all JSON properties

I am facing an issue with my json structure where I am unable to access certain properties. I can only access the main properties like type, properties, and so on within that hierarchy level. However, I cannot seem to access icon, iconURL, or title. The da ...

What is the process for incorporating the JavaScript "star" feature from Google's search results page?

In the Google search results, each entry has a star that users can click to indicate if the result is good or not. This feature is likely implemented using JavaScript. I'm interested in implementing a similar function in my own web application. I wou ...

Displayed in the xmlhttp.responseText are the tags

Why do tags appear when I input xmlhttp.responseText into my textbox? It displays <!DOCTYPE html><html><body></body></html> along with the desired content. Is there a way to prevent the tags from being displayed? Here is the ...

"Experiencing a callstack overflow due to multiple carousels on one page using Jquery or Vanilla

Currently, I am working on implementing a Jquery infinite autoplay multiple carousel. What I have done is created two separate carousel blocks on the same page within the body section. <div class="rotating_block"> <div class="bl ...

Ways to disable an AJAX loading animation

In my current script, I am loading external content using the following code: <script type="text/javascript"> var http_request = false; function makePOSTRequest(url, parameters) { // Code for making POST request } function alertContents() { ...

Implementing Batch File Uploads using Typescript

Is there a way to upload multiple files in TypeScript without using React or Angular, but by utilizing an interface and getter and setter in a class? So far I have this for single file upload: <input name="myfile" type="file" multi ...

Using Promises with setTimeout

I have encountered a challenge with the following question: Create a function that takes a number as a parameter and after x milliseconds (within an interval of 1 to 100 ms using setTimeout along with Math library's floor and random functions), either ...

Show a variety of randomly selected pictures from various sources using the command image/next

I'm in the process of building a basic news aggregation website using Next.js. I’ve run into an issue where I need to include specific domains in my next.config file for the Image component to work properly. The problem is, the URLs for the images o ...

Is there a method to access the variable name of v-model from a child component in the parent component?

In the scenario below, I am customizing a vue radio component and using the model option to retrieve the v-model value, which is expected to be a string '1'. Is there a way for me to access its variable name 'radio1' in the child compon ...

Steps to include a title next to a progress bar:

Is there a way to achieve something like this? https://i.sstatic.net/dhO2f.jpg I attempted to use bootstrap but I ran into an issue where the title was slightly misaligned below the progress bar. Can someone offer assistance with this matter? Apologies i ...

The use of Buffer() is no longer recommended due to concerns regarding both security vulnerabilities and

I'm encountering an issue while trying to run a Discord bot. The code I'm using involves Buffer and it keeps generating errors specifically with this code snippet: const app = express(); app.get("/", (req,res) => { if((new Buffer(req.quer ...

Even after unsubscribing with mqtt.js, the old listener continues to receive messages

Currently, I am utilizing mqtt.js to receive websocket data from an MQTT server. The subscription process is functioning properly, but the challenge lies in changing the topic dynamically by modifying the websocket configuration. The issue arises when, eve ...

What causes the variable within the useEffect function to delay its value update?

const [inputValue, setInputValue] = useState(""); const previousInputValue = useRef(""); useEffect(() => { previousInputValue.current = inputValue; }, [inputValue]); return ( <> <input type="text" value={ ...

Error Encountered While Submitting Email Form

I'm having trouble with my email submission form on my website. I've checked the code and it seems fine, but for some reason, the submissions are not going through successfully. Even when bypassing the JavaScript and directly using the PHP script ...

Guide to passing arguments to a function within Vue's v-for loop?

Is there a way to pass the parameter {{ item.id }} to my function productos.mostrarModal() within a v-for loop in vue.js? <div v-for="item of articulos"> <a href="#" onclick="productos.mostrarModal()" > <h2>{{item.name}}< ...

Converting a Finsweet hack #4 from jQuery to pure JavaScript in Webflow: Step-by-step guide

I have a jQuery code that I need to convert to pure JavaScript. Can you assist me in translating this code into pure JavaScript? I have left comments in the code to make it easier. ORIGINAL JQUERY CODE: // When the DOM is ready document.addEventListener(& ...

Retrieve the current height of the iFrame and then set that height to the parent div

Within a div, I have an iFrame that needs to have an absolute position for some reason. The issue is that when the iFrame's position is set to absolute, its content overlaps with the content below it. Is there a way to automatically adjust the height ...

Check if a certain object is devoid of any values based on the elements in an array - JavaScript

I have collected data related to various businesses: { "business": { "type": [ "LLC", "Corporation" ], "LLC": { "status": "acti ...

Received a JSON value that cannot be converted into a tuple of two numbers in Typescript

I'm a beginner when it comes to TypeScript and I am struggling to find the solution to my question. In my project, there is a config.json file structured like this: { "api": { "baseUrl": "http://localhost:9000/api", "list": { "itemsP ...

Checking time differences in MongoDB

Here is the data from my mongodb document: [ { startTime: "08:00", endTime: "09:00", id: 1 }, { startTime: "08:10", endTime: "09:00", id: 2 }, { ...