Sprite positioning in 3D scene

object3d
sprites

#1

Hi,

23-13-07-49

I am trying to place delete/remove image ( i.e sprite) on top left of hotspot image ( i.e sprite) in a 3d scene. How can I achieve this?

//parent object3d for manipulation
var layer = new THREE.Object3D(); 
scene.add(layer);

//sprite for remove/delete 
var textureLoader     = new THREE.TextureLoader();
var map               = textureLoader.load("img/close.png");
var deleteObjMaterial = new THREE.SpriteMaterial({ map: this.map });

//sprite for hotspot
var textureLoader = new THREE.TextureLoader();
var map           = textureLoader.load("img/hotspot.png");
var arrowMaterial = new THREE.SpriteMaterial({map: this.map});
 
//onclick place hotspot sprite 
var sprite = new THREE.Sprite(arrowMaterial);
sprite.scale.set(40,40,40);  // imageWidth, imageHeight
sprite.position.set(posX, posY, posZ); //onclick 2d to 3d position
layer.add(sprite);

//onclick place delete image sprite on top left of hotspot
sprite = new THREE.Sprite(deleteObjMaterial);
sprite.scale.set(10, 10,10);  // imageWidth, imageHeight

if (posX > 450) {
    posX = posX - 40 ; 
}
if (posX < -450) {
    posX = posX + 40 ; 
}
 
if (posY > 0) {
    posY = posY - 40; 
}
if (posY < 0) {
    posY = posY + 40;
}
 
if (posZ > 0) {
    posZ = posZ - 40;
}
if (posZ < 0) {
    posZ = posZ + 40; 
}
 
sprite.position.set(posX, posY, posZ);  //onclick 2d to 3d position

layer.add(sprite);

#2

Have you tried to add the sprite for deletion relative to the hotspot sprite? Something like:

deletionSprite.position.set( - 1, 1, 0 ); // position relative to the parent sprite
hotspotSprite.add( deletionSprite );
layer.add( hotspotSprite );

#3

Hi,

Your given solution works when camera quaternion is not changed. Also I want to change delete sprite (i.e delete / remove image ) dimension.

Here it works fine.

27-13-30-08

But when camera quaternion is changed it moves.

27-13-31-07

Below is the code to place hotspot ( i.e arrowMaterial ) on mouse click position ( i.e intersect) and delete image

var sprite = new THREE.Sprite(arrowMaterial);
sprite.scale.set(40,40,1);  // imageWidth, imageHeight

var point = new THREE.Matrix4().getInverse(intersects.object.matrixWorld )
                            .multiplyVector3(intersects.point.clone() )
                            .add( intersects.face.normal.clone().multiplyScalar(20) ); 

sprite.position.set(point.x, point.y, point.z);

sprite.quaternion.copy(camera.quaternion); // to look at camera

var deletionSprite = new THREE.Sprite(deleteObjMaterial);

deletionSprite.position.set(  1, 1, 0 ); // position relative to the parent sprite, top right side

sprite.add(deletionSprite );
layer.add(sprite);

#4

Why do you have this line in your code? Sprites are automatically oriented towards the camera. This code should only be necessary if you don’t use sprites but simple plane meshes (based on PlaneBufferGeometry).


#5

How can I can change angle of hotspot with XY plane always faces the camera?.. Like below examples…
( Floating axis helper is layer’s i.e object3d axis helper)

27-17-19-52

27-17-20-33


#6

Hi,

I have created the below codepen to demonstrate my issue.

issue demo

I am aware that Sprites are better than Mesh in my scenario and I am using Sprite for the same reason that they always point to camera.

However, I am unable to make my code work without the line that you have highlighted. Checkout function addConnector() in my codepen. Double click to create new connector in the demo.


#7

I think the problem is that setting a fixed position for your deletion sprite is not correct. As you can see, when I remove the line connectorSprite.quaternion.copy(camera.quaternion); from your demo, the deletion sprites are not evenly placed along the markers.

So instead of setting deletionSprite.position always to ( 1, 1, 0 ), it has to be a special value. Unfortunately, I don’t know how to compute such an offset vector.


#8

TBH, I’m surprised that you do the things in the way that is as complicated as possible.

You can use just one sprite that has a texture of marker and deletion glyphs. The idea: when you raycast an array of sprites, you can also get UV coordinates, so just use it for layout of the sprite, when you know where your deletion mark is in UV coordinates :slight_smile:

I’ve created a small example, based on the same code from the official example.
Keep in mind: it’s just a concept, not the ultimate solution.

Additional variables in the beginning:

  raycaster = new THREE.Raycaster(),
  mouse = new THREE.Vector2(),
  intersects = [];
  var spriteMaterial = new THREE.SpriteMaterial({
  map: new THREE.TextureLoader().load(
    "https://cywarr.github.io/small-shop/Marker.png"
    )
  });
  var markers = [], markersCounter = 0;

So, the event for double click is:

  document.addEventListener(
    "dblclick",
    event => {
      mouse.x = event.clientX / window.innerWidth * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

      raycaster.setFromCamera(mouse, camera);

      let marker = new THREE.Sprite(spriteMaterial);
      marker.scale.set(20, 20, 1);
      marker.name = "marker" + markersCounter;
      raycaster.ray.at(210, marker.position);
      scene.add(marker);
      markers.push(marker);
      markersCounter++;
    },
    false
  );

And I’ve added the logic of deletion to the mouse down event:

  // Deletion
  mouse.x = clientX / window.innerWidth * 2 - 1;
  mouse.y = -(clientY / window.innerHeight) * 2 + 1;
  
  raycaster.setFromCamera(mouse, camera);
  
  intersects = raycaster.intersectObjects(markers);
  
  if (intersects.length > 0){
    let obj = intersects[0];
    let uv = obj.uv;
    if (Math.min(uv.x, uv.y) > 0.75) { 
      obj.object.visible = false; // you have to do the stuff for real clearance
    }
  }

Local cursor position in sprite