I have a spinning Object3D with Sprite labels attached around it. The Sprites are positioned correctly and rotate around with the Object3D, always facing the camera.
I need to draw speech bubble style tails on the labels, and have them point back to the location on the Object3D. This is fairly simple with a line, but gets more complex with a shape.
- Green is Object3D cube
- Blue is the Shape which needs its rotation.y fixing
- White is the Sprite label
So far I have the blue Shape sized and positioned correctly. However the rotation y axis needs to dynamically update vs the Object3D rotation and the camera position. I've tried .lookAt(), but which doesn't work as it rotates all axis. I need only the y rotation to be affected.
Also questioning whether this is the best approach in general?
Thanks!
// Scene setup
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var ambientLight = new THREE.AmbientLight(0xffffff, .5);
scene.add(ambientLight);
var directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
var group = new THREE.Group();
scene.add(group);
// Cube
var cubeSize = 1;
var geometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
var material = new THREE.MeshLambertMaterial({
color: 0x00ff00
});
var cube = new THREE.Mesh(geometry, material);
group.add(cube);
// Text label
var config = {
fontface: 'Arial',
fontsize: 64,
fontweight: 500,
lineheight: 1,
padding: 20
};
var text = 'Hello world!';
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.font = `${config.fontweight} ${config.fontsize}px/${config.lineheight} ${config.fontface}`;
const textMetrics = ctx.measureText(text);
var textWidth = textMetrics.width;
var textHeight = config.fontsize * config.lineheight;
canvas.width = textWidth + config.padding * 2;
canvas.height = textHeight + config.padding * 2;
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, textWidth + config.padding * 2, textHeight + config.padding * 2);
ctx.fillStyle = 'black';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.font = `${config.fontweight} ${config.fontsize}px/${config.lineheight} ${config.fontface}`;
ctx.fillText(text, config.padding, config.padding);
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
var spriteMaterial = new THREE.SpriteMaterial({
map: texture
});
var aspectRatio = spriteMaterial.map.image.height / spriteMaterial.map.image.width;
var sprite = new THREE.Sprite(spriteMaterial);
sprite.position.set(0, cubeSize * 2, 0);
sprite.scale.set(1, aspectRatio, 1);
group.add(sprite);
// ShapeGeometry Mesh
var arrowWidth = textHeight / 220;
var arrowHeight = sprite.position.y / 1.41;
var arrowShape = new THREE.Shape();
arrowShape.moveTo(0, 0);
arrowShape.lineTo(-arrowWidth / 2, arrowHeight);
arrowShape.lineTo(arrowWidth / 2, arrowHeight);
arrowShape.lineTo(0, 0);
var arrowGeometry = new THREE.ShapeGeometry(arrowShape);
var arrowMaterial = new THREE.MeshBasicMaterial({
color: 0x0000ff,
// depthWrite: false,
side: THREE.DoubleSide
});
var arrow = new THREE.Mesh(arrowGeometry, arrowMaterial);
arrow.position.set(0, cubeSize / 2, 0);
/* arrow.up.copy(new THREE.Vector3(0, 1, 0)); */
group.add(arrow);
window.group = group;
var animate = function() {
requestAnimationFrame(animate);
group.rotation.x += 0.005;
group.rotation.y += 0.005;
group.rotation.z += 0.005;
// Using lookAt with depthWrite:false almost works
// arrow.lookAt(camera.position);
// arrow.rotation.x = 0;
// arrow.rotation.z = 0;
renderer.render(scene, camera);
};
animate();
body {
margin: 0;
}
canvas {
width: 100%;
height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>