I’m a novice to ThreeJS and I have a new problem. I place an arrow in the scene
, but then when I resize the window, the position of the arrow (relative to the scene background) changes. How do I prevent this?
Here is the “good” positioning, where the arrow comes out of the person’s head:
Here is the “bad” positioning, where the arrow has now moved to the side of the person’s head:
My index.html
code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- Imporant meta information to make the page as rigid as possible on mobiles, to avoid unintentional zooming on the page itself -->
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Character Tutorial</title>
</head>
<body>
<!-- The loading element overlays all else until the model is loaded, at which point we remove this element from the DOM -->
<div class="loading" id="js-loader"><div class="loader"></div></div>
<div class="wrapper">
<!-- The canvas element is used to draw the 3D scene -->
<canvas id="c"></canvas>
</div>
<!-- The main Three.js file -->
<!-- <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.min.js'></script>-->
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js'></script>
<!-- This brings in the ability to load custom 3D objects in the .gltf file format. Blender allows the ability to export to this format out the box -->
<!-- <script src='https://cdn.jsdelivr.net/gh/mrdoob/Three.js@r92/examples/js/loaders/GLTFLoader.js'></script>-->
<script src="js/script.js"></script>
</body>
</html>
My script.js
:
// noinspection DuplicatedCode
// Copied from https://stackoverflow.com/questions/63776448/threejs-applying-edge-geometry-to-arrowhelper
class CustomArrow extends THREE.Object3D {
constructor( dir, origin, length, color, edgeColor, headLength, headWidth ) {
super();
// dir is assumed to be normalized
this.type = 'CustomArrow';
if ( dir === undefined ) dir = new THREE.Vector3( 0, 0, 1 );
if ( origin === undefined ) origin = new THREE.Vector3( 0, 0, 0 );
if ( length === undefined ) length = 1;
if ( color === undefined ) color = 0xffff00;
if ( headLength === undefined ) headLength = 0.2 * length;
if ( headWidth === undefined ) headWidth = 0.2 * headLength;
if ( this._lineGeometry === undefined ) {
this._lineGeometry = new THREE.BufferGeometry();
this._lineGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) );
this._coneGeometry = new THREE.ConeBufferGeometry( 0.5, 1, 6);
this._coneGeometry.translate( 0, - 0.5, 0 );
this._axis = new THREE.Vector3();
}
this.position.copy( origin );
this.line = new THREE.Line( this._lineGeometry, new THREE.LineBasicMaterial( { color: edgeColor, toneMapped: false, linewidth: 4 } ) );
// this.line.matrixAutoUpdate = false;
this.add( this.line )
// base material
this.cone = new THREE.Mesh( this._coneGeometry, new THREE.MeshBasicMaterial( { color: color, toneMapped: false } ) );
this.add(this.cone);
// wire frame
this.wireframe = new THREE.Mesh( this._coneGeometry, new THREE.MeshBasicMaterial( {
color: edgeColor,
toneMapped: false,
wireframe: true,
wireframeLinewidth: 2 } ) );
this.add(this.wireframe);
this.setDirection( dir );
this.setLength( length, headLength, headWidth );
}
setDirection( dir ) {
// dir is assumed to be normalized
if ( dir.y > 0.99999 ) {
this.quaternion.set( 0, 0, 0, 1 );
} else if ( dir.y < - 0.99999 ) {
this.quaternion.set( 1, 0, 0, 0 );
} else {
this._axis.set( dir.z, 0, - dir.x ).normalize();
const radians = Math.acos( dir.y );
this.quaternion.setFromAxisAngle( this._axis, radians );
}
}
setLength( length, headLength, headWidth ) {
if ( headLength === undefined ) headLength = 0.2 * length;
if ( headWidth === undefined ) headWidth = 0.2 * headLength;
this.line.scale.set( 1, Math.max( 0.0001, length - headLength ), 1 ); // see #17458
this.line.updateMatrix();
this.cone.scale.set( headWidth, headLength, headWidth );
this.cone.position.y = length;
this.cone.updateMatrix();
this.wireframe.scale.set( headWidth, headLength, headWidth );
this.wireframe.position.y = length;
this.wireframe.updateMatrix();
}
setColor( color ) {
this.line.material.color.set( color );
// this.cone.material.color.set( color );
// this.wireframe.material.color.set( color );
}
copy( source ) {
super.copy( source, false );
this.line.copy( source.line );
this.cone.copy( source.cone );
this.wireframe.copy( source.wireframe );
return this;
}
}
(function() {
// Set our main variables
let scene = new THREE.Scene(),
renderer,
camera,
cone,
backgroundLoader = new THREE.TextureLoader(),
arrow, // Surface normal direction
canRotate = false,
arrowLength = 1,
arrowHeadLength = 0.6,
arrowHeadWidth = 0.4,
pointer = new THREE.Vector3(),
lookAt = new THREE.Vector3(),
intersectPoint = new THREE.Vector3(),
mouse = new THREE.Vector2(), // for reuse
plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0),
raycaster = new THREE.Raycaster(); // Used to detect the click on our character
init();
function init() {
const canvas = document.querySelector('#c');
// Init the scene.
scene.background = backgroundLoader.load('https://assets.imgix.net/hp/snowshoe.jpg');
// Init the renderer.
renderer = new THREE.WebGLRenderer({canvas, antialias: true}); // what is anti-aliasing?
renderer.shadowMap.enabled = true;
// What is difference between renderer.setSize and renderer.setPixelRatio?
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
1,
1000
);
camera.position.set(0, 0, 6);
// Add arrow variables
const arrowDirection = new THREE.Vector3();
// Wrist
// const arrowPosition = new THREE.Vector3(-0.14, .4, 0);
// Left snow
// const arrowPosition = new THREE.Vector3(-2, -1.5, 0);
// Backpack
const arrowPosition = new THREE.Vector3(0.3, 1.49, 0);
// Calf
// const arrowPosition = new THREE.Vector3(0.10, -0.85, 0);
// const arrowPosition = new THREE.Vector3(0, 1, 0);
arrowDirection.subVectors( scene.position, new THREE.Vector3(1, 1, 1)).normalize();
// arrow = new THREE.ArrowHelper( arrowDirection, arrowPosition, arrowLength, 0xfffff00, arrowHeadLength, arrowHeadWidth);
arrow = new CustomArrow( arrowDirection, arrowPosition, arrowLength, 0xfffff00, 0x000000, arrowHeadLength, arrowHeadWidth);
arrow.castShadow = true;
scene.add( arrow );
}
function update() {
const canvas = renderer.domElement;
let windowWidth = window.innerWidth;
let windowHeight = window.innerHeight;
let newAspect = canvas.clientWidth / canvas.clientHeight;
let canvasPixelWidth = canvas.width / window.devicePixelRatio;
let canvasPixelHeight = canvas.height / window.devicePixelRatio;
const needResize = canvasPixelWidth !== windowWidth || canvasPixelHeight !== windowHeight;
if (needResize) {
// renderer.setSize(windowWidth, windowHeight, false);
camera.aspect = newAspect;
// camera.top = newAspect / 2;
// camera.bottom = -newAspect / 2;
// camera.left = newAspect / -2;
// camera.right = newAspect / 2;
camera.updateProjectionMatrix();
renderer.setSize(windowWidth, windowHeight, false);
}
renderer.render(scene, camera);
requestAnimationFrame(update);
}
update();
// If user clicks, that toggles whether the arrow can move.
document.addEventListener('click', function (e) {
canRotate = ! canRotate;
})
document.addEventListener('mousemove', function (e) {
if (canRotate) {
pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
pointer.y = - (e.clientY / window.innerHeight) * 2 + 1;
// Approach 0: https://stackoverflow.com/a/36071100/4570472
let vector = new THREE.Vector3(pointer.x, pointer.y, 0.5);
vector.unproject( camera );
let remainder = arrowLength * arrowLength - vector.x * vector.x - vector.y * vector.y
if (remainder <= 0){
vector.z = 0;
} else {
vector.z = Math.sqrt(remainder);
}
let dir = vector.normalize();
// Working, but doesn't track mouse exactly
arrow.setDirection(dir);
}
});
})(); // Don't add anything below this line