https://glitch.com/edit/#!/copper-past-property?path=index.html%3A13%3A76
under picture's pivot position is center
i want to pivot position to bottom center like under image.
i update example. https://glitch.com/edit/#!/copper-past-property?path=viewport.js%3A127%3A39
I want to move and resize a shape relative to the bottom, just like a 3d object.
In general, the easiest way to add an offset / pivot is by using a parent-child relation:
insert your object inside any parent entity
set the parents position where want your object (plus the pivot position)
move the child in the opposite direction of where the pivot should be (so pivot * -1)
perform any operations on the parent entity
For example a box at 0 0.5 -3 with a pivot at 0 -0.5 0:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<a-scene>
<!-- The sphere is the parent entity -->
<a-sphere position="0 0 -3" radius="0.1" color="blue"
animation="property: rotation; to: 360 0; loop: true; dur: 5000">
<!--- The original, but now off-centered box --->
<a-box position="0 0.5 0" material="wireframe:true; color: #aa00ff"></a-box>
</a-sphere>
<!--- axis --->
<a-box position="0 0 -3" scale="5 0.05 0.05" color="red"></a-box>
</a-scene>
Your case is quite specific, as you want a pivot for the TransformControls.
One way would be to apply a pivot where they are attached (viewport.js).
The entities could have a component defining a pivot point:
AFRAME.registerComponent("editor-config", {
schema: {
offset: {type: "vec3", default: {x: 0, y: 0, z: 0}}
},
init: function() {
// once the entity is loaded
this.el.addEventListener("loaded", evt => {
// save the offset within the THREE.Object3D
const mesh = this.el.getObject3D("mesh")
if (!mesh.userData) mesh.userData = {}
mesh.userData.editoroffset = this.data.offset
})
}
});
// <a-box position="-1 0.5 -3" editor-config="offset: 0 -0.5 0"></a-box>
Then when the TransformControls are attached - you can apply the pivot:
/*** setSelectEntity(entity) within viewport.js ***/
this.transformControls.attach(entity);
// reset the position
this.transformControls.position.multiplyScalar(0);
// if there is an offset to apply - use it
if (entity.userData && entity.userData.editoroffset)
this.transformControls.position.copy(entity.userData.editoroffset)
Check it out in this glitch
I wonder how can i zoom in to mouse selection with ThreeJs, i'm using raycaster for selecting the model areas but something isn't working when i click on areas - as you can see in My Example (thanks to Mugen87) , my goal is to zoom into faces when i click on them... i've added the following function :
function onClick(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObject( mesh, false, intersects );
if(mesh.type!= 'Mesh' || !intersects.length){
return
}
var n = intersects[ 0 ].face.normal.clone();
n.multiplyScalar(30);
n.add(intersects[ 0 ].point);
var p = intersects[ 0 ].point;
camera.position.copy(n);
camera.lookAt(p);
}
When i click outside the model - it's not keeping the position and it's spinning ...
That's because you're using OrbitControls, which pretty much takes over your camera and assigns its position and rotation once per frame. When you try to set its position manually, OrbitControls overrides anything you did.
The solution is to make your controls variable global. Then when you're ready to take over the camera position, set controls.enabled = false; in the onClick() function. Then when you're ready to return to the regular controls, set controls.enabled = true;
https://threejs.org/docs/#examples/en/controls/OrbitControls.enabled
I'm using a-frame to create a scene with a box that moves according to a sine function, but the box doesn't show up inside the viewport.
I already tried to update the box position by changing the "position" attribute and resetting the box.object3D.position.
HTML file
<a-scene log="hello scene">
<a-assets>
<img id="boxTexture" src="https://i.imgur.com/mYmmbrp.jpg">
</a-assets>
<a-box src="#boxTexture" position="0 0 -5" rotation="0 45 45" scale="0.5 0.5 0.5">
<!-- <a-animation attribute="position" to="0 2.2 -5" direction="alternate" dur="2000"
repeat="indefinite"></a-animation> -->
<a-animation attribute="scale" begin="mouseenter" dur="300" to="2.3 2.3 2.3"></a-animation>
<a-animation attribute="scale" begin="mouseleave" dur="300" to="0.5 0.5 0.5"></a-animation>
<a-animation attribute="rotation" begin="click" dur="2000" to="360 405 45"></a-animation>
</a-box>
<a-camera>
<a-cursor></a-cursor>
</a-camera>
JavaScript file
// low frequency oscillator
function lfo(amp, freq, phase) {
var date = new Date();
var millis = 0.001 * date.getMilliseconds();
var value = amp * Math.sin(millis * freq + phase);
return value;
}
var boxes = document.querySelectorAll('a-box');
function moveBoxes() {
// loop through all the boxes in the document
for (var i = 0; i < boxes.length; i++) {
let x = lfo(1, 1, 0);
let y = lfo(1, 1, 0.2);
let z = lfo(1, 1, 0.7);
boxes[i].object3D.position.set(x, y, z);
//boxes[i].setAttribute("position", x+" "+y+" "+z);
}
}
I'd except to see the box either on center of the scene or at least at the upper-left corner, but it's neither.
The 2D viewport does not map to the 3D positions within the 3D scene.
The A-Frame mouse cursor maps mouse clicks to in-3D clicks. I'm not sure, but you might be able to use this sort of method to convert 2D X/Y coordinate to 3D coordinate at your specified distance: https://github.com/aframevr/aframe/blob/master/src/components/cursor.js#L213
But otherwise, I would just make something a child of the camera.
<a-entity camera>
<a-box move-sin-wave-or-whatever-animation position="0 0 -1"></a-box>
</a-entity>
I am trying to make an object chase another moving object and stop when it reaches the position of the object being chased. I have tried using TranslateonAxis as shown below
this.el.object3D.translateOnAxis(targetposition, distance);
to make the chaser reach the position of the target position (position of the object being chased) but the chaser object ends up moving in another direction altogether. I think the reason could be due to difference in world position and the local position of the objects.
Here is my code
<a-scene physics="gravity: 0">
<a-assets>
<a-asset-item id="boat" src="../images/models/surfboard/scene.gltf"></a-
asset-item>
<a-asset-item id="orca1" src="../images/models/orca/scene.gltf"></a-asset-
item>
</a-assets>
<a-entity position="0 1.8 0">
<a-camera id="camera" look-controls="enabled: false">
<a-entity id="boats" position="0 -2 -4" rotation="0 -90 0"
scale=".02 .02 .02" gltf-model="#boat" static-body></a-entity>
</a-camera>
</a-entity>
<a-entity id="orca" position="-1 0.7 -40" gltf-model="#orca1" static-body move></a-entity>
</a-scene>
I try to move the #orca towards the #boats (which can be moved using WASD) using the move component. Code for that -
AFRAME.registerComponent('move', {
schema: {
speed: { type: 'number', default: 2 }
},
tick: function(t, dt) {
var target = this.el.sceneEl.querySelector('#boats');
var vec3 = new THREE.Vector3();
var currentPosition = this.el.object3D.position;
target.object3D.getWorldPosition(vec3);
var distance = dt*this.data.speed / 100;
this.el.object3D.translateOnAxis(vec3, distance);
if (currentPosition.z > 30) {
this.el.setAttribute('position', {
z: -60
});
}
}
});
On a side note - I wish there were better explanatory material available explaining worldToLocal and LocaltoWorld methods.
1) You need to get the boat's position by translating the world space to local space.
// Get orca's object in regard to the target object
vec3 = this.el.object3D.worldToLocal(target.object3D.position.clone())
To better understand this, consider the same setup seen from two points of view.
From the T point of view, it does not need to move towards [0, 0, 0]. It needs to calculate where the empty box is from its own point of view, which is [1, -0.7, 5]. Furthermore, the space is not only about position, but also about rotation (and scale, not important here). When T rotates, the empty box position will be different.
2) Check the distance using THREEs a.distanceTo(b). If the distance is bigger than you want, move the orca:
var target = this.el.sceneEl.querySelector('a-camera'); // this should be in the init()
var vec3 = new THREE.Vector3();
var currentPosition = this.el.object3D.position;
// clone the position to operate on a copy
vec3 = this.el.object3D.worldToLocal(target.object3D.position.clone())
var distance = dt*this.data.speed / 1000;
var camFromOrca = currentPosition.distanceTo( target.object3D.position );
// if the distance is more than one meter, move the orca
if (camFromOrca > 1) {
this.el.object3D.translateOnAxis(vec3, distance);
}
fiddle here. Actually quite spooky :)
var vec3 = new THREE.Vector3();
var obj = this.el.object3D;
var targ = target.object3D;
obj.position.add(vec3.copy(targ.position).sub(obj.position).multiplyScalar(0.1));
I'm working my way through this book, and I'm doing okay I guess, but I've hit something I do not really get.
Below is how you can log to the console and object in 3D space that you click on:
renderer.domElement.addEventListener('mousedown', function(event) {
var vector = new THREE.Vector3(
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
0
);
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(
camera.position,
vector.sub(camera.position).normalize()
);
var intersects = raycaster.intersectObjects(OBJECTS);
if (intersects.length) {
console.log(intersects[0]);
}
}, false);
Here's the book's explanation on how this code works:
The previous code listens to the mousedown event on the renderer's canvas.
Get that, we're finding the domElement the renderer is using by using renderer.domElement. We're then binding an event listener to it with addEventListner, and specifing we want to listening for a mousedown. When the mouse is clicked, we launch an anonymous function and pass the eventvariable into the function.
Then,
it creates a new Vector3 instance with the mouse's coordinates on the screen
relative to the center of the canvas as a percent of the canvas width.
What? I get how we're creating a new instance with new THREE.Vector3, and I get that the three arguments Vector3 takes are its x, y and z coordinates, but that's where my understanding completely and utterly breaks down.
Firstly, I'm making an assumption here, but to plot a vector, surely you need two points in space in order to project? If you give it just one set of coords, how does it know what direction to project from? My guess is that you actually use the Raycaster to plot the "vector"...
Now onto the arguments we're passing to Vector3... I get how z is 0. Because we're only interested in where we're clicking on the screen. We can either click up or down, left or right, but not into or out of the screen, so we set that to zero. Now let's tackle x:
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
We're getting the PixelRatio of the device, timsing it by where we clicked along the x axis, dividing by the renderer's domElement width, timsing this by two and taking away one.
When you don't get something, you need to say what you do get so people can best help you out. So I feel like such a fool when I say:
I don't get why we even need the pixel ratio I don't get why we times that by where we've clicked along the x
I don't get why we divide that by the width
I utterly do not get why we need to times by 2 and take away 1. Times by 2, take away 1. That could genuinely could be times by an elephant, take away peanut and it would make as much sense.
I get y even less:
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
Why are we now randomly using -devicePixelRatio? Why are now deciding to add one rather than minus one?
That vector is then un-projected (from 2D into 3D space) relative to the camera.
What?
Once we have the point in 3D space representing the mouse's location,
we draw a line to it using the Raycaster. The two arguments that it
receives are the starting point and the direction to the ending point.
Okay, I get that, it's what I was mentioning above. How we need two points to plot a "vector". In THREE talk, a vector appears to be called a "raycaster".
However, the two points we're passing to it as arguments don't make much sense. If we were passing in the camera's position and the vector's position and drawing the projection from those two points I'd get that, and indeed we are using the camera.position for the first points, but
vector.sub(camera.position).normalize()
Why are we subtracting the camera.position? Why are we normalizing? Why does this useless f***** book not think to explain anything?
We get the direction by subtracting the mouse and camera positions and
then normalizing the result, which divides each dimension by the
length of the vector to scale it so that no dimension has a value
greater than 1.
What? I'm not being lazy, not a word past by makes sense here.
Finally, we use the ray to check which objects are located in the
given direction (that is, under the mouse) with the intersectObjects
method. OBJECTS is an array of objects (generally meshes) to check; be
sure to change it appropriately for your code. An array of objects
that are behind the mouse are returned and sorted by distance, so the
first result is the object that was clicked. Each object in the
intersects array has an object, point, face, and distance property.
Respectively, the values of these properties are the clicked object
(generally a Mesh), a Vector3 instance representing the clicked
location in space, the Face3 instance at the clicked location, and the
distance from the camera to the clicked point.
I get that. We grab all the objects the vector passes through, put them to an array in distance order and log the first one, i.e. the nearest one:
console.log(intersects[0]);
And, in all honestly, do you think I should give up with THREE? I mean, I've gotten somewhere with it certainly, and I understand all the programming aspects of it, creating new instances, using data objects such as arrays, using anonymous functions and passing in variables, but whenever I hit something mathematical I seem to grind to a soul-crushing halt.
Or is this actually difficult? Did you find this tricky? It's just the book doesn't feel it's necessary to explain in much detail, and neither do other answers , as though this stuff is just normal for most people. I feel like such an idiot. Should I give up? I want to create 3D games. I really, really want to, but I am drawn to the poetic idea of creating an entire world. Not math. If I said I didn't find math difficult, I would be lying.
I understand your troubles and I'm here to help. It seems you have one principal question: what operations are performed on the vector to prepare it for click detection?
Let's look back at the original declaration of vector:
var vector = new THREE.Vector3(
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
0
);
renderer.devicePixelRatio relates to a ratio of virtual site pixels /
real device pixels
event.pageX and .pageY are mouseX, mouseY
The this context is renderer.domElement, so .width, .height, .offsetLeft/Right relate to that
1 appears to be a corrective "magic" number for the calculation (for the purpose of being as visually exact as possible)
We don't care about the z-value, THREE will handle that for us. X and Y are our chief concern. Let's derive them:
We first find the distance of the mouse to the edge of the canvas: event.pageX - this.offsetLeft
We divide that by this.width to get the mouseX as a percentage of the screen width
We multiply by renderer.devicePixelRatio to convert from device pixels to site pixels
I'm not sure why we multiply by 2, but it might have to do with an assumption that the user has a retina display (someone can feel free to correct me on this if it's wrong).
1 is, again, magic to fix what might be just an offset error
For y, we multiply the whole expression by -1 to compensate for the inverted coordinate system (0 is top, this.height is bottom)
Thus you get the following arguments for the vector:
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
0
Now, for the next bit, a few terms:
Normalizing a vector means simplifying it into x, y, and z components less than one. To do so, you simply divide the x, y, and z components of the vector by the magnitude of the vector. It seems useless, but it's important because it creates a unit vector (magnitude = 1) in the direction of the mouse vector!
A Raycaster casts a vector through the 3D landscape produced in the canvas. Its constructor is THREE.Raycaster( origin, direction )
With these terms in mind, I can explain why we do this: vector.sub(camera.position).normalize(). First, we get the vector describing the distance from the mouse position vector to the camera position vector, vector.sub(camera.position). Then, we normalize it to make it a direction vector (again, magnitude = 1). This way, we're casting a vector from the camera to the 3D space in the direction of the mouse position! This operation allows us to then figure out any objects that are under the mouse by comparing the object position to the ray's vector.
I hope this helps. If you have any more questions, feel free to comment and I will answer them as soon as possible.
Oh, and don't let the math discourage you. THREE.js is by nature a math-heavy language because you're manipulating objects in 3D space, but experience will help you get past these kinds of understanding roadblocks. I would continue learning and return to Stack Overflow with your questions. It may take some time to develop an aptitude for the math, but you won't learn if you don't try!
This is more universal no matter the render dom location, and the dom and its ancesters's padding margin.
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / ( rect.width - rect.left ) ) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
here is a demo, scroll to the bottom to click the cube.
<!DOCTYPE html>
<html>
<head>
<script src="http://threejs.org/build/three.min.js"></script>
<link rel="stylesheet" href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" />
<style>
body {
font-family: Monospace;
background-color: #fff;
margin: 0px;
}
#canvas {
background-color: #000;
width: 200px;
height: 200px;
border: 1px solid black;
margin: 10px;
padding: 0px;
top: 10px;
left: 100px;
}
.border {
padding:10px;
margin:10px;
height:3000px;
overflow:scroll;
}
</style>
</head>
<body>
<div class="border">
<div style="min-height:1000px;"></div>
<div class="border">
<div id="canvas"></div>
</div>
</div>
<script>
// Three.js ray.intersects with offset canvas
var container, camera, scene, renderer, mesh,
objects = [],
count = 0,
CANVAS_WIDTH = 200,
CANVAS_HEIGHT = 200;
// info
info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '30px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.style.color = '#f00';
info.style.backgroundColor = 'transparent';
info.style.zIndex = '1';
info.style.fontFamily = 'Monospace';
info.innerHTML = 'INTERSECT Count: ' + count;
info.style.userSelect = "none";
info.style.webkitUserSelect = "none";
info.style.MozUserSelect = "none";
document.body.appendChild( info );
container = document.getElementById( 'canvas' );
renderer = new THREE.WebGLRenderer();
renderer.setSize( CANVAS_WIDTH, CANVAS_HEIGHT );
container.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45, CANVAS_WIDTH / CANVAS_HEIGHT, 1, 1000 );
camera.position.y = 250;
camera.position.z = 500;
camera.lookAt( scene.position );
scene.add( camera );
scene.add( new THREE.AmbientLight( 0x222222 ) );
var light = new THREE.PointLight( 0xffffff, 1 );
camera.add( light );
mesh = new THREE.Mesh(
new THREE.BoxGeometry( 200, 200, 200, 1, 1, 1 ),
new THREE.MeshPhongMaterial( { color : 0x0080ff }
) );
scene.add( mesh );
objects.push( mesh );
// find intersections
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
// mouse listener
document.addEventListener( 'mousedown', function( event ) {
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / ( rect.width - rect.left ) ) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 ) {
info.innerHTML = 'INTERSECT Count: ' + ++count;
}
}, false );
function render() {
mesh.rotation.y += 0.01;
renderer.render( scene, camera );
}
(function animate() {
requestAnimationFrame( animate );
render();
})();
</script>
</body>
</html>