2

I have an object3D which is invisible. I can freely look around by using the webvr-boilerplate. On click, I want to position this object3D in front of the camera, make it visible, but at a certain y position above the "ground".

I tried the following but it places the object3D in mid-air if I look there, and no at a certain position above the "ground":

var updateMatrix = camera.matrixWorld.clone(); object3D.applyMatrix(updateMatrix);

I think I need to extract certain camera rotation and position components from it and apply it to my object3D in order to get what I want.

Any hints are very much welcome!

Chris C
  • 35
  • 1
  • 6

1 Answers1

4

One way of doing this is to put your object onto a pivot at the position that you want it to appear and then rotate and position the pivot instead of trying to calculate the object's rotation and position simultaneously.

var pivot = new THREE.Object3D();
scene.add(pivot);
pivot.add(object3D);
// position the object on the pivot, so that it appears 5 meters 
// in front of the user.
object3D.position.z = -5;

Next, generally speaking, you want to decompose the camera's quaternion into its components with respect to a plane. In your case the plane is the ground (X-Z, I assume).

Deriving from another Stack Overflow answer, you can do this in three.js with a few operations:

var yaxis = new THREE.Vector3(0, 1, 0);
var zaxis = new THREE.Vector3(0, 0, 1);
var direction = zaxis.clone();
// Apply the camera's quaternion onto the unit vector of one of the axes
// of our desired rotation plane (the z axis of the xz plane, in this case).
direction.applyQuaternion(camera.quaternion);
// Project the direction vector onto the y axis to get the y component
// of the direction.
var ycomponent = yaxis.clone().multiplyScalar(direction.dot(yaxis));
// Subtract the y component from the direction vector so that we are
// left with the x and z components.
direction.sub(ycomponent);
// Normalize the direction into a unit vector again.
direction.normalize();
// Set the pivot's quaternion to the rotation required to get from the z axis
// to the xz component of the camera's direction.
pivot.quaternion.setFromUnitVectors(zaxis, direction);
// Finally, set the pivot's position as well, so that it follows the camera.
pivot.position.copy(camera.position);

Full example code:

var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
var controls = new THREE.VRControls(camera);
var effect = new THREE.VREffect(renderer);
effect.setSize(window.innerWidth, window.innerHeight);
var manager = new WebVRManager(renderer, effect, {hideButton: false});

// Add a repeating grid as a skybox.
var boxWidth = 5;
THREE.ImageUtils.crossOrigin = '';
var texture = THREE.ImageUtils.loadTexture(
  'http://cdn.rawgit.com/borismus/webvr-boilerplate/0.3.3/img/box.png'
);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(boxWidth, boxWidth);
var skybox = new THREE.Mesh(
  new THREE.BoxGeometry(boxWidth, boxWidth, boxWidth),
  new THREE.MeshBasicMaterial({
    map: texture,
    color: 0x01BE00,
    side: THREE.BackSide
  })
);
scene.add(skybox);

var floorPos = -1.5;
var floor = new THREE.Mesh(
  new THREE.PlaneGeometry(100, 100, 2, 2),
  new THREE.MeshBasicMaterial({color: 'white'})
);
floor.position.set(0, floorPos, 0);
floor.rotation.x = -Math.PI / 2;
scene.add(floor);

var thingDistance = -1;

var box = new THREE.Mesh(
  new THREE.BoxGeometry(0.5, 0.5, 0.5),
  new THREE.MeshNormalMaterial()
  );
box.position.set(-0.5, floorPos, thingDistance);
var boxPos = box.position.clone();
scene.add(box);

var ball = new THREE.Mesh(
  new THREE.SphereGeometry(0.3),
  new THREE.MeshNormalMaterial()
  );
ball.position.set(0.5, floorPos, thingDistance);
var ballPos = ball.position.clone();
scene.add(ball);

var pivot = new THREE.Object3D();
scene.add(pivot);

var ballInFront = false;
document.addEventListener('click', function () {
  if (ballInFront) {
    scene.add(ball);
    ball.position.copy(ballPos);
    
    pivot.add(box);
    box.position.set(0, 0, thingDistance);
    ballInFront = false;
  }
  else {
    scene.add(box);
    box.position.copy(boxPos);
    
    pivot.add(ball);
    ball.position.set(0, 0, thingDistance);
    ballInFront = true;
  }
});


var yaxis = new THREE.Vector3(0, 1, 0);
var zaxis = new THREE.Vector3(0, 0, 1);
function animate(timestamp) {
  controls.update();
  
  var direction = zaxis.clone();
  // Apply the camera's quaternion onto the unit vector of one of the axes
  // of our desired rotation plane (the z axis of the xz plane, in this case).
  direction.applyQuaternion(camera.quaternion);
  // Project the direction vector onto the y axis to get the y component
  // of the direction.
  var ycomponent = yaxis.clone().multiplyScalar(direction.dot(yaxis));
  // Subtract the y component from the direction vector so that we are
  // left with the x and z components.
  direction.sub(ycomponent);
  // Normalize the direction into a unit vector again.
  direction.normalize();
  // Set the pivot's quaternion to the rotation required to get from the z axis
  // to the xz component of the camera's direction.
  pivot.quaternion.setFromUnitVectors(zaxis, direction);
  
  pivot.position.copy(camera.position);
  
  manager.render(scene, camera, timestamp);
  requestAnimationFrame(animate);
}
animate();

function onKey(event) {
  if (event.keyCode == 90) { // z
    controls.resetSensor();
  }
}
window.addEventListener('keydown', onKey, true);
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="SO 34447119">
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  <meta name="mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
  <title>JS Bin</title>
  <script src="https://cdn.rawgit.com/borismus/webvr-boilerplate/0.2.2/bower_components/threejs/build/three.js"></script>
  <script src="https://cdn.rawgit.com/borismus/webvr-boilerplate/0.2.2/bower_components/threejs/examples/js/controls/VRControls.js"></script>
  <script src="https://cdn.rawgit.com/borismus/webvr-boilerplate/0.2.2/bower_components/threejs/examples/js/effects/VREffect.js"></script>
  <script src="https://cdn.rawgit.com/borismus/webvr-boilerplate/0.2.2/bower_components/webvr-polyfill/build/webvr-polyfill.js"></script>
  <script src="https://cdn.rawgit.com/borismus/webvr-boilerplate/0.2.2/build/webvr-manager.js"></script>
</head>
<body>
</body> 
</html>
brianpeiris
  • 10,735
  • 1
  • 31
  • 44
  • 1
    Ha! I just realized this is almost identical to the answer I gave on an earlier SO question of yours. It really is the same underlying problem though. – brianpeiris Dec 28 '15 at 22:52
  • Thanks for your answer! I know, I seem to have the same underlying problems all the time. ;-) Your example does what I was looking for but is it a good practice to add the ball to the scene and the ball to the pivot on every click? I saw that on the floor there are the shapes appearing again. – Chris C Dec 30 '15 at 03:34
  • I applied your code to my example, but I have an issue (with the position of the pivot). If you would like to take a look here: http://dev.ideaspacevr.org/photos Your code is in http://dev.ideaspacevr.org/includes/js/aframe/components/hud.js In the last statement the camera's position is always 0 0 0. Could this be an aframe issue? Thanks! – Chris C Dec 30 '15 at 04:08
  • Looks like the camera object is actually part of the scene in A-frame. You could either use `this.el.sceneEl.cameraEl.object3D.getWorldPosition()` or add the pivot to the camera instead of the scene. – brianpeiris Dec 30 '15 at 06:12
  • Thanks a lot! getWorldPosition() did the trick, I should have thought of that myself. – Chris C Dec 30 '15 at 14:35