AFRAME screen to world position - three.js

I'm trying to convert Mouse position to world coordinates in Three via Aframe
Using something like
let mouse = new three.Vector2()
let camera = document.querySelector('#camera')
let rect = document.querySelector('#sceneContainer').getBoundingClientRect()
mouse.x = ( (event.clientX - rect.left) / rect.width ) * 2 - 1
mouse.y = - ( (event.clientY - rect.top) / rect.height ) * 2 + 1
let vector = new three.Vector3( mouse.x, mouse.y, -1 ).unproject( camera )
However it doesn't seem to be able to handle the camera, I get
TypeError: Cannot read property 'elements' of undefined
From Matrix4.getInverse
9550 |
9551 | // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
9552 | var te = this.elements,
> 9553 | me = m.elements,
9554 |
9555 | n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ], n41 = me[ 3 ],
9556 | n12 = me[ 4 ], n22 = me[ 5 ], n32 = me[ 6 ], n42 = me[ 7 ],
I presume it's not reading the camera properly, any ideas on how to get the three camera out of the aframe camera if that's the problem?

Using Piotr's info about accessing the camera and fixing up the 'three' to 'THREE' seems to work:
https://glitch.com/edit/#!/aframe-mouse-to-world
AFRAME.registerComponent('mouse-to-world', {
init: function () {
document.addEventListener('click', (e) => {
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 )
console.log(vector)
})
}
});

Related

How to make a not squared texture fit in a "background-size:cover" way to a geometry plane in Three.js?

I want my texture to have the same behaviour than the "background-size:cover" css property.
I'd like to work with uvs coordinates.
I looked at this answer and start to work on a solution : Three.js Efficiently Mapping Uvs to Plane
I try to have the same dimension/position planes that some div of my DOM.
This is what I want :
And this is the result I get with this code : the dimension and position are good, the ratio of my texture looks good too but it seems like there's a scale issue :
let w = domElmt.clientWidth / window.innerHeight;
let h = domElmt.clientHeight / window.innerHeight;
geometry = new THREE.PlaneGeometry(w, h);
var uvs = geometry.faceVertexUvs[ 0 ];
uvs[ 0 ][ 0 ].set( 0, h );
uvs[ 0 ][ 1 ].set( 0, 0 );
uvs[ 0 ][ 2 ].set( w, h );
uvs[ 1 ][ 0 ].set( 0, 0 );
uvs[ 1 ][ 1 ].set( w, 0 );
uvs[ 1 ][ 2 ].set( w, h );
tex = new THREE.TextureLoader().load('image.jpg'));
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
material = new THREE.MeshBasicMaterial( { map: tex } );
mesh = new THREE.Mesh( geometry, material );
Should I play with the repeat attribute of my texture or can I fully made this behaviour using uvs ? Thank you
https://en.wikipedia.org/wiki/UV_mapping
UV mapping values range from 0 to 1, inclusive, and represent a percentage mapping across your texture image.
You're using a ratio of the div size vs the window size, which is likely much smaller than 1, and would result in the "zoomed in" effect you're seeing.
For example, if your w and h result in the value 0.5, then The furthest top-right corner of the mapped texture will be the exact center of the image.
background-style: cover:
Scales the image as large as possible without stretching the image. If the proportions of the image differ from the element, it is cropped either vertically or horizontally so that no empty space remains.
In other words, it will scale the image based on the size of the short side, and crop the rest. So let's assume you have a nice 128x512 image, and a 64x64 space. cover would scale the width of 128 down to 64 (a scale factor of 0.5), so multiply 512 by 0.5 to get the new height (128). Now your w would still be 1, but your h will be 128 / 512 = 0.25. Your texture will now fit to the width, and crop the height.
You'll need to perform this calculation for each image-to-container size relationship to find the proper UVs, keeping in mind that the scaling is always relevant to the short side.
You don't need to generate UVs, you can just use texture.repeat and texture.offset
const aspectOfPlane = planeWidth / planeHeight;
const aspectOfImage = image.width / image.height;
let yScale = 1;
let xScale = aspectOfPlane / aspectOfImage;
if (xScale > 1) { // it doesn't cover so based on x instead
xScale = 1;
yScale = aspectOfImage / aspectOfPlane;
}
texture.repeat.set(xScale, yScale);
texture.offset.set((1 - xScale) / 2, (1 - yScale) / 2);
'use strict';
/* global THREE */
async function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 50;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 4;
const scene = new THREE.Scene();
const loader = new THREE.TextureLoader();
function loadTexture(url) {
return new Promise((resolve, reject) => {
loader.load(url, resolve, undefined, reject);
});
}
const textures = await Promise.all([
"https://i.imgur.com/AyOufBk.jpg",
"https://i.imgur.com/ZKMnXce.png",
"https://i.imgur.com/TSiyiJv.jpg",
"https://i.imgur.com/v38pV.jpg",
].map(loadTexture));
const geometry = new THREE.PlaneBufferGeometry(1, 1);
const material = new THREE.MeshBasicMaterial({map: textures[0]});
const planeMesh = new THREE.Mesh(geometry, material);
scene.add(planeMesh);
let texIndex = 0;
function setTexture() {
const texture = textures[texIndex];
texIndex = (texIndex + 1) % textures.length;
// pick and random width and height for plane
const planeWidth = rand(1, 4);
const planeHeight = rand(1, 4);
planeMesh.scale.set(planeWidth, planeHeight, 1);
const image = texture.image;
const aspectOfPlane = planeWidth / planeHeight;
const aspectOfImage = image.width / image.height;
let yScale = 1;
let xScale = aspectOfPlane / aspectOfImage;
if (xScale > 1) { // it doesn't cover so based on x instead
xScale = 1;
yScale = aspectOfImage / aspectOfPlane;
}
texture.repeat.set(xScale, yScale);
texture.offset.set((1 - xScale) / 2, (1 - yScale) / 2);
material.map = texture;
}
setTexture();
setInterval(setTexture, 1000);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r105/three.min.js"></script>
texture.repeat and texture.offset are really just applied to the UVs so if you really want UVs it's
newU = u * repeat.x + offset.x;
newV = v * repeat.y + offset.y;
so using the code above
offsetX = (1 - xScale) / 2;
offsetY = (1 - yScale) / 2;
u0 = offsetX;
v0 = offsetY;
u1 = offsetX + xScale;
v1 = offsetY + yScale;
so
var uvs = geometry.faceVertexUvs[ 0 ];
uvs[ 0 ][ 0 ].set( u0, v1 );
uvs[ 0 ][ 1 ].set( u0, v0 );
uvs[ 0 ][ 2 ].set( u1, v1 );
uvs[ 1 ][ 0 ].set( u0, v0 );
uvs[ 1 ][ 1 ].set( u1, v0 );
uvs[ 1 ][ 2 ].set( u1, v1 );

three.js: Calculate faceVertexUvs on custom Geometry for texture mapping

I have this example here:
https://jsfiddle.net/NiklasKnaack/L1cqbdr9/82/
function createPlanetFace( radiusX, radiusY, radiusZ, localUp, resolution ) {
const face = {};
face.geometry = new THREE.Geometry();
face.geometry.faceVertexUvs[ 0 ] = [];
face.verticesOriginal = [];
face.verticesNormalized = [];
const axisA = new THREE.Vector3( localUp.y, localUp.z, localUp.x );
const axisB = new THREE.Vector3().crossVectors( localUp, axisA );
for ( let y = 0; y < resolution; y++ ) {
for ( let x = 0; x < resolution; x++ ) {
const index = x + y * resolution;
const percent = new THREE.Vector2( x, y );
percent.x /= ( resolution - 1 );
percent.y /= ( resolution - 1 );
const vertex = new THREE.Vector3();
vertex.x = ( localUp.x + ( percent.x - 0.5 ) * 2 * axisA.x + ( percent.y - 0.5 ) * 2 * axisB.x ) * radiusX;
vertex.y = ( localUp.y + ( percent.x - 0.5 ) * 2 * axisA.y + ( percent.y - 0.5 ) * 2 * axisB.y ) * radiusY;
vertex.z = ( localUp.z + ( percent.x - 0.5 ) * 2 * axisA.z + ( percent.y - 0.5 ) * 2 * axisB.z ) * radiusZ;
face.verticesOriginal[ index ] = new THREE.Vector3( vertex.x, vertex.y, vertex.z );
vertex.normalize();//create a sphere
vertex.x += vertex.x * radiusX / 2;
vertex.y += vertex.y * radiusY / 2;
vertex.z += vertex.z * radiusZ / 2;
face.verticesNormalized[ index ] = new THREE.Vector3( vertex.x, vertex.y, vertex.z );
face.geometry.vertices[ index ] = vertex;
//if ( index % 6 === 0 && index > 0 && x !== resolution - 1 && y !== resolution - 1 ) {
if ( x !== resolution - 1 && y !== resolution - 1 ) {
const triangle1 = new THREE.Face3( index, index + resolution + 1, index + resolution );
const triangle2 = new THREE.Face3( index, index + 1, index + resolution + 1 );
face.geometry.faces.push( triangle1, triangle2 );
}
}
}
//face.geometry.computeBoundingSphere();
//face.geometry.computeVertexNormals();
//face.geometry.computeFaceNormals();
return face;
};
For this I would like to calculate the UVs so that the loaded texture can be displayed correctly.
In principle, the createPlanetFace function creates a plane. From these 6 planes, a cube or sphere is created. (See it in the example)
So far it already works, only the texture is not displayed because the UVs are missing.
After a lot of research and trying, I get either errors in the console, a totally distorted texture, or just no texture at all. That's why I erased my miserable attempts calculating the UVs.
The examples I have found on this topic are all different. At least most of them. I have now reached a point where I can't get any further and need your help.
Thank you in advance.
Here is a box unwrap I wrote a while ago for regular geometries:
function boxUnwrapUVs(geometry) {
for (var i = 0; i < geometry.faces.length; i++) {
var face = geometry.faces[i];
var faceUVs = geometry.faceVertexUvs[0][i]
var va = geometry.vertices[geometry.faces[i].a]
var vb = geometry.vertices[geometry.faces[i].b]
var vc = geometry.vertices[geometry.faces[i].c]
var vab = new THREE.Vector3().copy(vb).sub(va)
var vac = new THREE.Vector3().copy(vc).sub(va)
//now we have 2 vectors to get the cross product of...
var vcross = new THREE.Vector3().copy(vab).cross(vac);
//Find the largest axis of the plane normal...
vcross.set(Math.abs(vcross.x), Math.abs(vcross.y), Math.abs(vcross.z))
var majorAxis = vcross.x > vcross.y ? (vcross.x > vcross.z ? 'x' : vcross.y > vcross.z ? 'y' : vcross.y > vcross.z) : vcross.y > vcross.z ? 'y' : 'z'
//Take the other two axis from the largest axis
var uAxis = majorAxis == 'x' ? 'y' : majorAxis == 'y' ? 'x' : 'x';
var vAxis = majorAxis == 'x' ? 'z' : majorAxis == 'y' ? 'z' : 'y';
faceUVs[0].set(va[uAxis], va[vAxis])
faceUVs[1].set(vb[uAxis], vb[vAxis])
faceUVs[2].set(vc[uAxis], vc[vAxis])
}
geometry.elementsNeedUpdate = geometry.verticesNeedUpdate = true;
}
Is that helpful?
edit: I rewrote your example because it was too complicated for me to understand...
https://jsfiddle.net/manthrax/dL6kxuf2/1/

Circle around the mouse? In three.js

When I Move mouse over an object it flies. How to make that object came off the circle? Radius is 100, I added a circle to the mouse.
Here is my code:
function onDocumentMouseMove(event){
fly.style.left = (event.clientX - maxR) + 'px';
fly.style.top = (event.clientY - maxR) + 'px';
event.preventDefault();
mouse.x = (( event.clientX / renderer.domElement.width ) * 2 - 1);
mouse.y = - ( event.clientY / renderer.domElement.height ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
new TWEEN.Tween( intersects[ 0 ].object.position ).to( {
x: Math.random()*750-375,
y: Math.random()*750-375,
/*z: Math.random() * 400 - 200 */}, 10000 )
.easing( TWEEN.Easing.Elastic.Out).start()
}
}

three.js webgl custom shader sharing texture with new offset

I am splitting a texture 1024 x 1024 over 32x32 tiles * 32, Im not sure if its possible to share the texture with an offset or would i need to create a new texture for each tile with the offset..
to create the offset i am using a uniform value = 32 * i and updating the uniform through each loop instance of creating tile, all the tiles seem to be the same offset? as basically i wanting an image to appear like its one image not broken up into little tiles.But the current out-put is the same x,y-offset on all 32 tiles..Im using the vertex-shader with three.js r71...
Would i need to create a new texture for each tile with the offset?
for ( j = 0; j < row; j ++ ) {
for ( t = 0; t < col; t ++ ) {
customUniforms.tX.value = tX;
customUniforms.tY.value = tY;
console.log(customUniforms.tX.value);
customUniforms.tX.needsUpdate = true;
customUniforms.tY.needsUpdate = true;
mesh = new THREE.Mesh( geometry,mMaterial);// or new material
}
}
//vertex shader :
vec2 uvOffset = vUV + vec2( tX, tY) ;
Image example:
Each image should have an offset of 10 0r 20 px but they are all the same.... this is from using one texture..
As suggested i have tried to manipulate the uv on each object with out luck, it seems to make all the same vertexes have the same position for example 10x10 segmant plane all faces will be the same
var geometry = [
[ new THREE.PlaneGeometry( w, w ,64,64),50 ],
[ new THREE.PlaneGeometry( w, w ,40,40), 500 ],
[ new THREE.PlaneGeometry( w, w ,30,30), 850 ],
[ new THREE.PlaneGeometry( w, w,16,16 ), 1200 ]
];
geometry[0][0].faceVertexUvs[0] = [];
for(var p = 0; p < geometry[0][0].faces.length; p++){
geometry[0][0].faceVertexUvs[0].push([
new THREE.Vector2(0.0, 0.0),
new THREE.Vector2(0.0, 1),
new THREE.Vector2( 1, 1 ),
new THREE.Vector2(1.0, 0.0)]);
}
image of this result, you will notice all vertices are the same when they shouldn't be
Update again:
I have to go through each vertices of faces as two triangles make a quad to avoid the above issue, I think i may have this solved... will update
Last Update Hopfully:
Below is the source code but i am lost making the algorithm display the texture as expected.
/*
j and t are rows & columns looping by 4x4 grid
row = 4 col = 4;
*/
for( i = 0; i < geometry.length; i ++ ) {
var mesh = new THREE.Mesh( geometry[ i ][ 0 ], customMaterial);
mesh.geometry.computeBoundingBox();
var max = mesh.geometry.boundingBox.max;
var min = mesh.geometry.boundingBox.min;
var offset = new THREE.Vector2(0 - min.x*t*j+w, 0- min.y*j+w);//here is my issue
var range = new THREE.Vector2(max.x - min.x*row*2, max.y - min.y*col*2);
mesh.geometry.faceVertexUvs[0] = [];
var faces = mesh.geometry.faces;
for (p = 0; p < mesh.geometry.faces.length ; p++) {
var v1 = mesh.geometry.vertices[faces[p].a];
var v2 = mesh.geometry.vertices[faces[p].b];
var v3 = mesh.geometry.vertices[faces[p].c];
mesh.geometry.faceVertexUvs[0].push([
new THREE.Vector2( ( v1.x + offset.x ) / range.x , ( v1.y + offset.y ) / range.y ),
new THREE.Vector2( ( v2.x + offset.x ) / range.x , ( v2.y + offset.y ) / range.y ),
new THREE.Vector2( ( v3.x + offset.x ) / range.x , ( v3.y + offset.y ) / range.y )
]);
}
You will notice the below image in the red is seamless as the other tiles are not aligned with the texture.
Here is the answer:
var offset = new THREE.Vector2(w - min.x-w+(w*t), w- min.y+w+(w*-j+w));
var range = new THREE.Vector2(max.x - min.x*7, max.y - min.y*7);
if you could simplify answer will award bounty too:

Three.js RayCaster.setFromCamera, the mouse's position and Z

Three.js r70 source code
setFromCamera: function ( coords, camera ) {
// camera is assumed _not_ to be a child of a transformed object
if ( camera instanceof THREE.PerspectiveCamera ) {
this.ray.origin.copy( camera.position );
this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( camera.position ).normalize();
}
this line,set the mouse's position and z's default value 0.5.
this.ray.direction.set( coords.x, coords.y, 0.5 )
most example code about raycasting like
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
I wonder how it works ,what does the ( event.clientX / window.innerWidth ) * 2 - 1 mean, and why set the z 0.5?
event its every time your mouse click client.x is the mouse position on x-axiz width.innerheight is how height your screen size is when you have a 2d mouse you have to separate it so when you are on left half of screen you go left and right you go right so you divide the screen height in 2 and compare where the 1 on the end mouse the mouse registration around so if you want to offset the crosshairs you can do to
function onDocumentMouseMove(e) {
e.preventDefault();
var fleece = window.document.body.scrollHeight;
var fleeceb = window.document.body.scrollWidth;
mouse.x = (e.clientX / fleeceb) * 2 - 1;
mouse.y = - (e.clientY / fleece) * 2 + 1;
}

Resources