Is it possible to rotate a Three.js group around a specific point within the group using Three.js?

Summary: The objective is to adjust the arrow's orientation around the circle.

We have a function that introduces a shape (https://i.sstatic.net/l3lZB.jpg) (comprising of three Meshes) into the scene. By default, it points to the right, but we desire to rotate it (90°, Pi/2 upwards, 180°, Pi to the right, and so forth). The central point (the two circles) should stay fixed while the triangle rotates around it.

We attempted the Euler rotation method and setting the axis to the central point. Most of the time, when we rotate the node, the position changes, but the x-y coordinates remain unchanged in the group object.

Does anyone have a solution on how we can rotate the node around the center accurately? Below is the code snippet used to add the node to the scene.

let scene;
let renderer;
let camera;

setup();

//params: xcoord, ycoord, rotation (in degrees)
drawNode(1, 1, 0);

renderer.render(scene, camera);

function setup() {
  scene = new THREE.Scene();

  renderer = new THREE.WebGLRenderer({
    alpha: true, //Transparent
    antialias: true //For smoother edges
  });

  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  //we use orthographic camera so perspective doesn't manipulate how we see the objects
  //example: https://i.sstatic.net/q1SNB.png
  camera = new THREE.OrthographicCamera(
    window.innerWidth / -100, //Camera frustum left plane.
    window.innerWidth / 100, // Camera frustum right plane.
    window.innerHeight / 100, //Camera frustum top plane.   
    window.innerHeight / -100, //Camera frustum bottom plane
    -1, //near — Camera frustum near plane.
    100 //far — Camera frustum far plane.
    //frustum:https://en.wikipedia.org/wiki/Viewing_frustum
  );

  //Creates background grid 
  const gridHelper = new THREE.GridHelper(50, 50);
  gridHelper.rotateX(-Math.PI / 2); //Rotation is necessary because we changed the axis so Z is aimed towards the screen
  scene.add(gridHelper);
}

function drawNode(xcoord, ycoord, rotation) {

  //Small white circle
  const innerCircleGeometry = new THREE.CircleGeometry(1 / 32, 32);
  const innerCircle = new THREE.Mesh(innerCircleGeometry, new THREE.MeshBasicMaterial({
    color: 0xFFFFFF
  }));

  innerCircle.position.set(xcoord, ycoord, 0);

  //Bigger grey circle
  const outerCircleGeometry = new THREE.CircleGeometry(1 / 16, 32);
  const outerCircle = new THREE.Mesh(outerCircleGeometry, new THREE.MeshBasicMaterial({
    color: 0xC4C4C4
  }));

  outerCircle.position.set(xcoord, ycoord, 0);

  //Points of the triangle
  const points = [];
  points.push(new THREE.Vector3(xcoord, ycoord - 1 / 4, 0));
  points.push(new THREE.Vector3(xcoord, ycoord + 1 / 4, 0));
  points.push(new THREE.Vector3(xcoord + 1 / 4, ycoord, 0));
  points.push(new THREE.Vector3(xcoord, ycoord - 1 / 4, 0));

  const geometrytriangle = new THREE.BufferGeometry().setFromPoints(points); //connects the points
  const triangle = new THREE.Mesh(geometrytriangle, new THREE.MeshBasicMaterial({
    side: THREE.DoubleSide,
    color: 0x5C5C5C
  }));

  //Set order so triangle is always at the bottom
  triangle.renderOrder = 0;
  outerCircle.renderOrder = 1;
  innerCircle.renderOrder = 2;

  const nodeGroup = new THREE.Group();
  nodeGroup.add(triangle);
  nodeGroup.add(outerCircle);
  nodeGroup.add(innerCircle);

  //TODO: rotate the node
  //Incorrect rotation: nodeGroup.rotateZ(THREE.MathUtils.degToRad(rotation));

  scene.add(nodeGroup);

  //Unique identifier of nodegroup, each child has its own uuid
  return nodeGroup.uuid;
}
body { min-height: 100vh; }
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
  <script src="https://github.com/mrdoob/three.js/blob/master/examples/jsm/controls/OrbitControls.js"></script>
  <title>Document</title>
</head>

<body>

</body>

</html>

Answer №1

The key concept here is that all rotations are performed around the point 0,0,0. To rotate the arrow, the first step is to center the desired rotation point at the origin, apply the rotation, and then translate it.

function drawNode(xcoord, ycoord, rotation) {

  //Creating a small white circle
  const innerCircleGeometry = new THREE.CircleGeometry(1 / 32, 32);
  const innerCircle = new THREE.Mesh(innerCircleGeometry, new THREE.MeshBasicMaterial({
    color: 0xFFFFFF
  }));

  innerCircle.position.set(0, 0, 0);

  //Creating a larger grey circle
  const outerCircleGeometry = new THREE.CircleGeometry(1 / 16, 32);
  const outerCircle = new THREE.Mesh(outerCircleGeometry, new THREE.MeshBasicMaterial({
    color: 0xC4C4C4
  }));

  outerCircle.position.set(0, 0, 0);

  //Defining the points of the triangle
  const points = [];
  points.push(new THREE.Vector3(0, 0 - 1 / 4, 0));
  points.push(new THREE.Vector3(0, 0 + 1 / 4, 0));
  points.push(new THREE.Vector3(0 + 1 / 4, 0, 0));
  points.push(new THREE.Vector3(0, 0 - 1 / 4, 0));

  const geometrytriangle = new THREE.BufferGeometry().setFromPoints(points); //creating the triangle
  const triangle = new THREE.Mesh(geometrytriangle, new THREE.MeshBasicMaterial({
    side: THREE.DoubleSide,
    color: 0x5C5C5C
  }));

  //Setting the render order to ensure triangle is at the bottom
  triangle.renderOrder = 0;
  outerCircle.renderOrder = 1;
  innerCircle.renderOrder = 2;

  const nodeGroup = new THREE.Group();
  nodeGroup.add(triangle);
  nodeGroup.add(outerCircle);
  nodeGroup.add(innerCircle);

  nodeGroup.rotateZ(rotation * 3.14 / 180); //rotate the node group
  nodeGroup.position.set(xcoord, ycoord, 0); //set the position

  scene.add(nodeGroup); //adding nodeGroup to the scene

  //Returning the unique identifier of the node group, each child has its own UUID.
  return nodeGroup.uuid;
}

You can view a live working example here

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

Tips for implementing a repeater item to function beyond the ng-repeat directive

Recently, I decided to play around with AngularJS and now I seem to be facing a small issue with ng-class. Specifically, I'm attempting to change the color of an awesome font icon. <div ng-controller="MyCtrl"> <i ng-class="{'test': ...

Transformation of data structures

Is there a way to transform the array structure that represents parent-relation into a 2D array format? const array = [{id: 1, parentId: 2}, {parentId: null, id: 2}]; The goal is to create a new array where the first element of each nested array is the pa ...

I encountered an issue when attempting to execute an action as I received an error message indicating that the dispatch function was not

I just started learning about redux and I am trying to understand it from the basics. So, I installed an npm package and integrated it into my form. However, when I attempt to dispatch an action, I encounter an error stating that 'dispatch is not defi ...

Upon updating my application from Angular 14 to 16, I encountered an overwhelming number of errors within the npm packages I had incorporated

After upgrading my angular application from v14 to v16, I encountered numerous peer dependencies issues, which led me to use the --force flag for the upgrade process. However, upon compiling, I am now faced with a multitude of errors as depicted in the scr ...

Location of Ajax callback function

I encountered an issue with a callback function that was placed within $(document).ready. The callback function wasn't functioning as expected. Surprisingly, when I moved it outside of $(document).ready, the code started working flawlessly. I am puzzl ...

JavaScript 3D Arrays

Is there a way to create a 3D array similar to runes['red'][0]['test']? If so, how can I accomplish this? var runes = {} runes['red'] = [ 'test': ['loh'] ] runes['blue'] = {} runes[&apo ...

The function defineCall does not exist, causing a sequelize error to occur

A challenge I've been facing is resolving an error that is popping up in my index.js file. I'm currently utilizing Node, Express, and sequelize in my project. /app/node_modules/sequelize/lib/sequelize.js:691 server_1 | this.importCache ...

Adjust the color of TextareaAutosize component using mui in React

Just starting out with React and getting acquainted with mui components. Experimenting with setting the color of a TextareaAutosize mui component using this code: import * as React from 'react'; import TextareaAutosize from '@mui/material/T ...

JavaScript code snippet for detecting key presses of 3 specific arrow keys on the document

For this specific action, I must press and hold the left arrow key first, followed by the right arrow key, and then the up arrow key. However, it seems that the up arrow key is not being triggered as expected. It appears that there may be some limitations ...

What is the most effective way to load data prior to the controller being loaded

Is there a way to fetch data from a service before the view and controller are loaded? I need assistance with this. resolve: { getAlbum: function(albumService){ return albumService.getAlbums(); },getAtum: function(albu ...

Refreshing Camera Movement with Three JS

Is it possible to create a smooth animation for the camera reset in Three JS? Currently, I am using this code to instantly reset the camera's position. However, I would like to find a way to animate the movement of the camera during the reset process ...

JavaScript function is returning 'undefined' instead of an integer

My JavaScript/jQuery function is not functioning correctly and instead of returning an integer, it returns undefined. function __getLastSelectedCategory(table_id) { if ( jQuery('.categories_table[data-table-id="1"]').find('td.active&apo ...

Is Javascript Profiling a feature available in Firebug Lite?

Exploring the world of JavaScript profiles, I decided to step away from the usual Chrome Developer tools. Can Firebug Lite for Google Chrome provide Javascript Profiling functionality? ...

Running a setTimeout function within the context of a jQuery each

My goal is to animate characters one by one, but for some reason the following code isn't functioning as expected: $('.overlay-listing').hover(function(){ var idx = 1; $('.strip-hov span', this).each(function(){ if ...

Can you explain the distinctions among 'data:', 'data: ()', and 'data()' when working with Vue.js?

While exploring the Vue.js documentation, I came across two ways to define data: data: {} and data() { return; }. data: { defaultLayout: 'default' } data() { return { defaultLayout: 'default' } } However, there is ...

ReactJS Error: Cannot find reference to 'require'

I'm currently implementing the DRY concept in React JS by attempting to reuse the same HTML partial across different files. Here is the partial: var AdminMenu = React.createClass({ getInitialState: function() { return {}; }, render: function() ...

Finding differences between two 24-hour format times using moment.js

Is there a way to compare two times in 24-hour format using the code below? $("#dd_start_timing, #dd_end_timing").on('keyup change keydown', function() { var DutyDayStartTime = $("#dd_start_timing").val().trim();// 13:05 var ...

Transform your data visualization with Highcharts enhanced with the stylish appearance of DHTML

I am currently using a dhtmlx menu with my charts, specifically the legendItemClick event. It worked perfectly when I was using highcharts 3.0.1. However, after upgrading to version 4.1.7, the function legendMenu_<?=$id?>.showContextMenu(x,y) in the ...

Storing past entries in a local storage using JavaScript

Currently, I am developing a JavaScript program that involves 3 input boxes. The program is designed to display whatever is typed into each input box on the page. To store and re-display previous submissions, I have implemented local storage. However, I en ...

Challenges with JavaScript calculations on dynamic HTML forms

Currently, I am immersed in a project focused on creating invoices. My progress so far involves a dynamic form where users can add and remove rows (as shown in the snippet below). I'm encountering an issue with my JavaScript and I could use some ass ...