aframe.js How to change shape center position?(pivot) - three.js

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

Related

Is it possible to draw a line between two objects placed based on location in ar.js

I am using ar.js to show some object on camera, now I need to draw a line between two objects placed based on location. I used a frame for placing objects. Is there any possible solution??
Have you tried the Line Component?
You can create an entity, add a line component and pass in the points to the component:
<a-entity line="start: 0, 1, 0; end: 2 0 -5; color: red"
line__2="start: -2, 4, 5; end: 0 4 -3; color: green"></a-entity>

How to move a-entities relative to center of the viewport [with object3D.position.set(x, y, z) ]

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>

How to correctly position html elements in three js coordinate system?

I hopefully have a simple problem I can't get an answer to.
I have three js geometric spheres which move in a box. I place this box at the centre of the scene. The mechanics of how the spheres stay in the box is irrelevant. What is important is the spheres move about the origin (0,0) and the canvas always fills the page.
I want to draw a line from the moving spheres to a div or img element on the page. To do this I would assume I have to transform the css coordinates to three js coordinates. I found something I thought did something like this (Note: Over use of somethings to signify I am probably mistaken)
I can add a html element to the same scene/camera as webgl renderer but obviously using a different renderer but I am unsure how to proceed from there?
Basically I want to know:
How should I change the size of the div preserving aspect ratio if need be?
In essence I want the div or element to fill screen at some camera depth.
How to place the div at the centre of the scene by default?
Mines seems to be shifted 1000 in z direction but this might be the size of the div(img) which I have to bring into view.
How to draw a line between the webgl sphere and html div/img?
thanks in advance!
Unfortunately you have asked 3 questions, it is tricky to address them all at once.
I will explain how to position DIV element on top of some 3D object. My example would be a tooltip that appears when you hover the object by mouse: http://jsfiddle.net/mmalex/ycnh0wze/
So let's get started,
First of all you need to subscribe mouse events and convert 2D coordinates of a mouse to relative coordinates on the viewport. Very well explained you will find it here: Get mouse clicked point's 3D coordinate in three.js
Having 2D coordinates, raycast the object. These steps are quite trivial, but for completeness I provide the code chunk.
var raycaster = new THREE.Raycaster();
function handleManipulationUpdate() {
// cleanup previous results, mouse moved and they're obsolete now
latestMouseIntersection = undefined;
hoveredObj = undefined;
raycaster.setFromCamera(mouse, camera);
{
var intersects = raycaster.intersectObjects(tooltipEnabledObjects);
if (intersects.length > 0) {
// keep point in 3D for next steps
latestMouseIntersection = intersects[0].point;
// remember what object was hovered, as we will need to extract tooltip text from it
hoveredObj = intersects[0].object;
}
}
... // do anything else
//with some conditions it may show or hide tooltip
showTooltip();
}
// Following two functions will convert mouse coordinates
// from screen to three.js system (where [0,0] is in the middle of the screen)
function updateMouseCoords(event, coordsObj) {
coordsObj.x = ((event.clientX - renderer.domElement.offsetLeft + 0.5) / window.innerWidth) * 2 - 1;
coordsObj.y = -((event.clientY - renderer.domElement.offsetTop + 0.5) / window.innerHeight) * 2 + 1;
}
function onMouseMove(event) {
updateMouseCoords(event, mouse);
handleManipulationUpdate();
}
window.addEventListener('mousemove', onMouseMove, false);
And finally see the most important part, DIV element placement. To understand the code it is essential to get convenient with Vector3.project method.
The sequence of calculations is as follows:
Get 2D mouse coordinates,
Raycast object and remember 3D coordinate of intersection (if any),
Project 3D coordinate back into 2D (this step may seem redundant here, but what if you want to trigger object tooltip programmatically? You won't have mouse coordinates)
Mess around to place DIV centered above 2D point, with nice margin.
// This will move tooltip to the current mouse position and show it by timer.
function showTooltip() {
var divElement = $("#tooltip");
//element found and mouse hovers some object?
if (divElement && latestMouseIntersection) {
//hide until tooltip is ready (prevents some visual artifacts)
divElement.css({
display: "block",
opacity: 0.0
});
//!!! === IMPORTANT ===
// DIV element is positioned here
var canvasHalfWidth = renderer.domElement.offsetWidth / 2;
var canvasHalfHeight = renderer.domElement.offsetHeight / 2;
var tooltipPosition = latestMouseProjection.clone().project(camera);
tooltipPosition.x = (tooltipPosition.x * canvasHalfWidth) + canvasHalfWidth + renderer.domElement.offsetLeft;
tooltipPosition.y = -(tooltipPosition.y * canvasHalfHeight) + canvasHalfHeight + renderer.domElement.offsetTop;
var tootipWidth = divElement[0].offsetWidth;
var tootipHeight = divElement[0].offsetHeight;
divElement.css({
left: `${tooltipPosition.x - tootipWidth/2}px`,
top: `${tooltipPosition.y - tootipHeight - 5}px`
});
//get text from hovered object (we store it in .userData)
divElement.text(hoveredObj.userData.tooltipText);
divElement.css({
opacity: 1.0
});
}
}

Make an object chase another object

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));

AFRAME position far away from camera

Following this question
AFRAME screen to world position
I can get the position of the vector, but it seems to be very close to camera, how do I get it around 100px further away from the camera?
let mouse = new three.Vector2()
let camera = AFRAME.scenes[0].camera
let rect = document.querySelector('body').getBoundingClientRect()
mouse.x = ( (e.clientX - rect.left) / rect.width ) * 2 - 1
mouse.y = - ( (e.clientY - rect.top) / rect.height ) * 2 + 1
let vector = new three.Vector3( mouse.x, mouse.y, -1 ).unproject( camera )
this.el.setAttribute('vector',vector)
this.data.onVector(this.data.sceneId,vector)
this._removeListener()
I tried multiplying the values from mouse etc and setting the z access to further away, but that doesn't seem to make any difference
If you want a consistent z, perhaps you could place an invisible plane in front of the camera (it's a bit easier than trying to calculate from screen to world).
When the plane is clicked you could use the intersection point:
https://glitch.com/edit/#!/a-frame-intersection-point
Based on an example from the docs:
AFRAME.registerComponent('cursor-listener', {
init: function () {
this.el.addEventListener('mouseup', (evt) => {
let boxEl = document.createElement('a-box');
boxEl.setAttribute('position', evt.detail.intersection.point);
boxEl.setAttribute('color', 'red');
this.el.sceneEl.appendChild(boxEl);
console.log('I was clicked at: ', evt.detail.intersection.point);
});
}
});
Here you have the plane, with visible set to false:
<a-scene>
<a-entity id="camera" camera="userHeight: 1.6" wasd-controls look-controls cursor="rayOrigin: mouse"></a-entity>
<a-plane id="plane" cursor-listener class="collidable" width="100" height="100" position="0 0 -4" material="visible: false;"></a-plane>
<a-entity position="-1 0.5 -5">
<a-box class="collidable" position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" shadow></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" shadow></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>
</a-entity>
<a-sky color="#ECECEC"></a-sky>
</a-scene>

Resources