Three.js. Get localToWorld from BufferGeometry vertices - three.js

I get faces from geometry attributes
get the coordinates for each point of the face
draw lines for each edge of the face
And everything is fine if the object is not moved:
without moving
But if you move, then the lines remain in place:
after moving
If I try to get the world coordinates of points after moving, then I see the same picture:
after moving + localToWorld
If I update matrix world for cube, then I see this:
after moving + localToWorld + updateMatrixWorld
var scene = new THREE.Scene();
scene.background = new THREE.Color().setStyle('#e0e0e0');
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000);
camera.position.set(0, 500, 3000);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
///////////////////////CREATE CUBE////////////////////////////////
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(1000, 1000, 1000),
new THREE.MeshBasicMaterial({
color: 'red',
transparent: true,
opacity: 0.4
})
);
scene.add(cube);
//MOVE
cube.position.set(100, 100, 100);
//UPDATE
cube.updateMatrixWorld(true);
//GET EDGE LINES(THREE.Line3) FOR EDGES OF FACES
const lines = getLinesFromFaces(cube);
//CONVERT FROM LOCAL TO WORLD
lines.map(l => {
//l.applyMatrix4(cube.matrixWorld);
//l.start.applyMatrix4(cube.matrixWorld);
//l.end.applyMatrix4(cube.matrixWorld);
cube.localToWorld(l.start);
cube.localToWorld(l.end);
});
//DRAW
drawLines(lines);
function drawLines(lines) {
for (let i = 0; i < lines.length; i += 1) {
addLine(lines[i].start, lines[i].end);
}
}
function addLine(p1, p2) {
const material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
const points = [p1, p2];
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(geometry, material);
scene.add(line);
}
function getLinesFromFaces(object) {
const facesWithPos = getFacesWithPos(object.geometry);
const lines = [];
for (let i = 0; i < facesWithPos.length; i += 1) {
const f = facesWithPos[i];
const lineAB = new THREE.Line3(f.a, f.b);
let isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineAB.start) && l.end.equals(lineAB.end)) ||
(l.start.equals(lineAB.end) && l.end.equals(lineAB.start));
});
if (!isExist) lines.push(lineAB);
const lineBC = new THREE.Line3(f.b, f.c);
isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineBC.start) && l.end.equals(lineBC.end)) ||
(l.start.equals(lineBC.end) && l.end.equals(lineBC.start));
});
if (!isExist) lines.push(lineBC);
const lineCA = new THREE.Line3(f.c, f.a);
isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineCA.start) && l.end.equals(lineCA.end)) ||
(l.start.equals(lineCA.end) && l.end.equals(lineCA.start));
});
if (!isExist) lines.push(lineCA);
}
return lines;
}
function getFacesWithPos(geometry) {
const faces = getFaces(geometry);
const facesWithPos = [];
const position = geometry.getAttribute('position');
for (let i = 0; i < faces.length; i += 1) {
const f = faces[i];
facesWithPos.push({
a: new THREE.Vector3(position.array[f.a * 3], position.array[f.a * 3 + 1], position.array[f.a * 3 + 2]),
b: new THREE.Vector3(position.array[f.b * 3], position.array[f.b * 3 + 1], position.array[f.b * 3 + 2]),
c: new THREE.Vector3(position.array[f.c * 3], position.array[f.c * 3 + 1], position.array[f.c * 3 + 2])
});
}
return facesWithPos;
}
function getFaces(geometry) {
const faces = [];
const index = geometry.getIndex();
for (let i = 0; i < index.count; i += 3) {
faces.push({
a: index.getX(i),
b: index.getX(i + 1),
c: index.getX(i + 2)
});
}
return faces;
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.117.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.117.0/examples/js/controls/OrbitControls.js"></script>
How to get world coordinates? what am I doing wrong?

Some of the line points were common objects. If you apply localToWorld to them, then the method was applied to them several times and the result was not correct. Below is the solution
var scene = new THREE.Scene();
scene.background = new THREE.Color().setStyle('#e0e0e0');
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000);
camera.position.set(0, 500, 3000);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
///////////////////////CREATE CUBE////////////////////////////////
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(1000, 1000, 1000),
new THREE.MeshBasicMaterial({
color: 'red',
transparent: true,
opacity: 0.4
})
);
scene.add(cube);
//MOVE
cube.position.set(100, 100, 100);
//UPDATE
cube.updateMatrixWorld(true);
//GET EDGE LINES(THREE.Line3) FOR EDGES OF FACES
const lines = getLinesFromFaces(cube);
//CONVERT FROM LOCAL TO WORLD
lines.map(l => {
//l.applyMatrix4(cube.matrixWorld);
//l.start.applyMatrix4(cube.matrixWorld);
//l.end.applyMatrix4(cube.matrixWorld);
cube.localToWorld(l.start);
cube.localToWorld(l.end);
});
//DRAW
drawLines(lines);
function drawLines(lines) {
for (let i = 0; i < lines.length; i += 1) {
addLine(lines[i].start, lines[i].end);
}
}
function addLine(p1, p2) {
const material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
const points = [p1, p2];
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(geometry, material);
scene.add(line);
}
function getLinesFromFaces(object) {
const facesWithPos = getFacesWithPos(object.geometry);
const lines = [];
for (let i = 0; i < facesWithPos.length; i += 1) {
const f = facesWithPos[i];
const lineAB = new THREE.Line3(f.a, f.b);
let isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineAB.start) && l.end.equals(lineAB.end)) ||
(l.start.equals(lineAB.end) && l.end.equals(lineAB.start));
});
if (!isExist) lines.push(lineAB.clone());
const lineBC = new THREE.Line3(f.b, f.c);
isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineBC.start) && l.end.equals(lineBC.end)) ||
(l.start.equals(lineBC.end) && l.end.equals(lineBC.start));
});
if (!isExist) lines.push(lineBC.clone());
const lineCA = new THREE.Line3(f.c, f.a);
isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineCA.start) && l.end.equals(lineCA.end)) ||
(l.start.equals(lineCA.end) && l.end.equals(lineCA.start));
});
if (!isExist) lines.push(lineCA.clone());
}
return lines;
}
function getFacesWithPos(geometry) {
const faces = getFaces(geometry);
const facesWithPos = [];
const position = geometry.getAttribute('position');
for (let i = 0; i < faces.length; i += 1) {
const f = faces[i];
facesWithPos.push({
a: new THREE.Vector3(position.array[f.a * 3], position.array[f.a * 3 + 1], position.array[f.a * 3 + 2]),
b: new THREE.Vector3(position.array[f.b * 3], position.array[f.b * 3 + 1], position.array[f.b * 3 + 2]),
c: new THREE.Vector3(position.array[f.c * 3], position.array[f.c * 3 + 1], position.array[f.c * 3 + 2])
});
}
return facesWithPos;
}
function getFaces(geometry) {
const faces = [];
const index = geometry.getIndex();
for (let i = 0; i < index.count; i += 3) {
faces.push({
a: index.getX(i),
b: index.getX(i + 1),
c: index.getX(i + 2)
});
}
return faces;
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.117.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.117.0/examples/js/controls/OrbitControls.js"></script>

Related

Draw a 2D line with width in three.js

I'm looking to draw a continuous line with a given thickness showing only the edges using three.js. I have achieved it. I'm trying to add thickness to the line but it is not getting reflected in the scene due to some angle in three.js. Can anyone help me out with the issue.
Here's the fiddle https://jsfiddle.net/16vhjm0y/1/
var renderer, scene, camera;
var line;
var count = 0;
var mouse = new THREE.Vector3();
var mesh3D;
var maxPoint = 6;
var height = window.innerHeight * .99;
var plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); // facing us for mouse intersection
var raycaster = new THREE.Raycaster();
var point3ds = [];
var usePerspectiveCamera = false; // toggles back and forth
var perspOrbit;
var perspCam;
var orthoOrbit;
var orthoCam;
var labelRenderer, labelAjay;
var testBoolean = false;
var mouseDownBoolean = false;
var distanceData, showDistanceData;
var ajay;
var arrAjay = [];
var arrAjayFinal = [];
var mouseUpBoolean = false;
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, height);
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
// camera perspective
perspCam = new THREE.PerspectiveCamera(45, window.innerWidth / height, 1, 10000);
perspCam.position.set(0, 0, 200);
// camera ortho
var width = window.innerWidth;
//var height = window.innerHeight;
orthoCam = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, 0, 1200);
// assign cam
camera = perspCam;
someMaterial = new THREE.MeshBasicMaterial({ color: 0xA9A9A9, side: THREE.DoubleSide, transparent: true, opacity: 0.3 });
// grid
var grid = new THREE.GridHelper(1024, 56);
grid.rotateX(Math.PI / 2);
// scene.add(grid);
// geometry
var geometry = new THREE.BufferGeometry();
var MAX_POINTS = 500;
positions = new Float32Array(MAX_POINTS * 3);
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
// material
var material = new THREE.LineBasicMaterial({
color: 0xff0000,
linewidth: 10
});
// line
line = new THREE.Line(geometry, material);
// line.position.z = 20;
scene.add(line);
// var geometry = new THREE.BoxBufferGeometry( 10, 2, 20 );
// var edgesPavement = new THREE.EdgesGeometry( geomPavement );
// var lineGeometry = new THREE.LineSegmentsGeometry().setPositions( edgesPavement.attributes.position.array );
// var lineMaterial = new THREE.LineMaterial( { color: 0xff0000, linewidth: 10 } );
// lineMaterial.resolution.set( window.innerWidth, window.innerHeight ); // important, for now...
// var line = new THREE.LineSegments2( lineGeometry, lineMaterial );
// scene.add( line );
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener('mousedown', onMouseDown, false);
document.addEventListener('mouseup', onMouseUp, false);
createUI();
labelRenderer = new THREE.CSS2DRenderer();
ajay = document.createElement('div');
ajay.className = 'ajay';
ajay.style.color = "black";
ajayInsert = document.createElement('div');
ajayInsert.className = 'ajay';
ajayInsert.style.color = "black";
// ajay.style.color = "black";
// console.log(ajay)
labelAjay = new THREE.CSS2DObject(ajay);
labelAjayFinal = new THREE.CSS2DObject(ajayInsert);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
ajay.style.display = "none";
ajayInsert.style.display = "none";
}
// update line
function updateLine() {
positions[count * 3 - 3] = mouse.x;
positions[count * 3 - 2] = mouse.y;
positions[count * 3 - 1] = mouse.z;
line.geometry.attributes.position.needsUpdate = true;
}
// mouse move handler
function onMouseMove(event) {
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = (event.clientX - rect.left) / (rect.right - rect.left) * 2 - 1;
mouse.y = - ((event.clientY - rect.top) / (rect.bottom - rect.top)) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
mouse = raycaster.ray.intersectPlane(plane, mouse);
if (count !== 0 && count < maxPoint) {
updateLine();
}
testBoolean = true;
if (testBoolean == true) {
// scene.remove(labelAjay);
var geometry = line.geometry;
geometry.computeBoundingBox();
center = geometry.boundingBox.getCenter();
// line.localToWorld(center);
// console.log(center);
if (mouseDownBoolean == true) {
labelAjay.position.set(mouse.x, mouse.y, mouse.z);
// console.log(line.position)
scene.add(labelAjay);
document.body.appendChild(labelRenderer.domElement);
// console.log(positions);
distanceData = point3ds[0].distanceTo(new THREE.Vector3(mouse.x, mouse.y, mouse.z));
showDistanceData = Math.round(distanceData * 1000);
// console.log(point3ds[0]);
// console.log(point3ds[1]);
// console.log(distanceData);
// console.log(showDistanceData)
ajay.textContent = showDistanceData + ' mm';
// console.log(labelRenderer)
}
// console.log(labelRenderer.domElement)
// document.getElementsByClassName("ajay").remove();
// document.getElementsByClassName("ajay").outerHTML = "";
}
}
// add point
function addPoint(event) {
if (count < maxPoint) {
console.log("point nr " + count + ": " + mouse.x + " " + mouse.y + " " + mouse.z);
positions[count * 3 + 0] = mouse.x;
positions[count * 3 + 1] = mouse.y;
positions[count * 3 + 2] = mouse.z
count++;
line.geometry.setDrawRange(0, count);
updateLine();
point3ds.push(new THREE.Vector3(mouse.x, mouse.y, mouse.z));
} else {
console.log('max points reached: ' + maxPoint);
}
}
function getPointInBetweenByLen(pointA, pointB, length) {
var dir = pointB.clone().sub(pointA).normalize().multiplyScalar(length);
return pointA.clone().add(dir);
}
// mouse down handler
function onMouseDown(evt) {
mouseDownBoolean = true;
// force add an extra point on first click so buffer line can display
// buffer geometry requires two points to display, so first click should add two points
if (count === 0) {
addPoint();
}
if (count < maxPoint) {
addPoint();
}
}
function onMouseUp(event){
mouseUpBoolean = true;
if(mouseUpBoolean == true){
// showDistanceData = Math.round(distanceData * 1000);
arrAjay.push(showDistanceData);
console.log(arrAjay);
arrAjayFinal = arrAjay.splice(-1)[0];
var geometry = line.geometry;
geometry.computeBoundingBox();
center = geometry.boundingBox.getCenter();
if (mouseDownBoolean == true) {
labelAjayFinal.position.set(center.x, center.y, center.z);
scene.add(labelAjayFinal);
document.body.appendChild(labelRenderer.domElement);
// distanceData = point3ds[0].distanceTo(new THREE.Vector3(mouse.x, mouse.y, mouse.z));
// showDistanceData = Math.round(distanceData * 1000);
console.log('arrAjayFinal', arrAjayFinal);
ajayInsert.textContent = arrAjayFinal;
}
}
}
// render
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
// animate
function animate() {
requestAnimationFrame(animate);
render();
}
// loop through all the segments and create their 3D
function create3D() {
if (!mesh3D && point3ds && point3ds.length) {
console.log('creating 3D');
mesh3D = new THREE.Mesh(); // metpy mesh but is the root mesh for all 3D
scene.add(mesh3D);
// prepare create segments from point3ds - every two points create a segement
var index = 1;
var segmentHeight = 56;
point3ds.forEach(point3d => {
if (index < point3ds.length) {
var seg = new Segment(point3d, point3ds[index], someMaterial, segmentHeight);
mesh3D.add(seg.mesh3D);
index++;
}
});
}
}
function createUI() {
// create3D
var btn = document.createElement('button');
document.body.appendChild(btn);
btn.innerHTML = 'Create3D';
btn.addEventListener('mousedown', () => {
create3D();
// add orbiting controls to both cameras
var controls;
if (!perspOrbit) {
perspOrbit = new THREE.OrbitControls(perspCam, renderer.domElement);
perspOrbit.screenSpacePanning = true;
// raotation is enabled once create3D is pressed
setToFullOrbit(perspOrbit);
perspOrbit.enabled = true; // set to true by default
}
// add orbit to orthocam
if (!orthoOrbit) {
orthoOrbit = new THREE.OrbitControls(orthoCam, renderer.domElement);
orthoOrbit.screenSpacePanning = true;
orthoOrbit.enabled = false; // set to false by default
//orthoOrbit.enableDamping = true;
//orthoOrbit.dampingFactor = .15;
}
});
}
function switchCam() {
usePerspectiveCamera = !usePerspectiveCamera;
if (usePerspectiveCamera) {
if (perspCam) {
camera = perspCam;
perspOrbit.enabled = true;
orthoOrbit.enabled = false;
} else {
throw new Error('Switch to perspective cam failed, perspective cam is null');
}
} else {
if (orthoCam) {
camera = orthoCam;
orthoOrbit.enabled = true;
perspOrbit.enabled = false;
} else {
throw new Error('Switch to ortho cam failed, orthoCam is null');
}
}
}
function rotateCam90() {
if (camera instanceof THREE.OrthographicCamera) {
orthoOrbit.update();
camera.applyMatrix(new THREE.Matrix4().makeRotationZ(Math.PI / 2));
}
}
function reset() {
scene.remove(mesh3D);
mesh3D = null;
for (var i = 0; i < 3 * 8; i++) {
positions[i] = 0;
}
count = 0;
line.geometry.setDrawRange(0, count);
updateLine();
point3ds = [];
}
function setToFullOrbit(orbitControl) {
// how far you can orbit vertically
orbitControl.minPolarAngle = 0;
orbitControl.maxPolarAngle = Math.PI;
// How far you can dolly in and out ( PerspectiveCamera only )
orbitControl.minDistance = 0;
orbitControl.maxDistance = Infinity;
orbitControl.enableZoom = true; // Set to false to disable zooming
orbitControl.zoomSpeed = 1.0;
orbitControl.enableRotate = true;
// allow keyboard arrows
orbitControl.enableKeys = true;
// Set to false to disable panning (ie vertical and horizontal translations)
orbitControl.enablePan = true;
}
// each segment knows how to create its 3D
class Segment {
constructor(start, end, material, height) {
this.start = start;
this.end = end;
this.height = height; // height of the segment's 3D
this.material = material;
this.mesh3D = null;
this.create3D();
}
create3D() {
if (this.start && this.end) {
//create the shape geometry
var distStartToEnd = this.start.distanceTo(this.end);
var vec2s = [
new THREE.Vector2(),
new THREE.Vector2(0, this.height),
new THREE.Vector2(distStartToEnd, this.height),
new THREE.Vector2(distStartToEnd, 0)
];
console.log('vec2s', vec2s);
var shape = new THREE.Shape(vec2s);
var geo = new THREE.BoxGeometry(5, 5, 5);
// console.log('shape', shape);
var geo = new THREE.ShapeGeometry(shape);
geo.applyMatrix(new THREE.Matrix4().makeRotationX(THREE.Math.degToRad(90)));
this.mesh3D = new THREE.Mesh(geo, this.material);
this.alignRotation();
this.alignPosition();
// the mesh3D should be added to the scene outside of this class
}
}
alignRotation() {
var p1 = this.start.clone();
var p2 = this.end.clone();
var direction = new THREE.Vector3();
direction.subVectors(p2, p1);
direction.normalize();
this.mesh3D.quaternion.setFromUnitVectors(new THREE.Vector3(1, 0, 0), direction);
}
alignPosition() {
if (this.mesh3D) {
this.mesh3D.position.copy(this.start);
} else {
throw new Error('mesh3D null');
}
}
}
The linewidth parameter relies on native WebGL support for drawing line thickness, but its performance is very spotty across browsers & operating systems. I think Windows doesn't support it, but MacOS does, so it shouldn't be relied upon. See this discussion on the Three.js Github for several bug reports.
As a workaround, they've created LineGeometry, which sort of re-builds a line with geometry to allow for thickness. See this example for how to use it. It even allows for dashed lines. After importing the module, you can implement it with:
const geometry = new LineGeometry();
geometry.setPositions( positions );
geometry.setColors( colors );
matLine = new LineMaterial( {
color: 0xffffff,
linewidth: 5, // in pixels
vertexColors: true,
dashed: false
} );
line = new Line2( geometry, matLine );
line.computeLineDistances();
scene.add( line );

Make the plane draggable in ThreeJS

How to make the plane draggable in X , Y direction. Here I'm trying to work on box clipping where I draw a plane on X and Y direction. But I have no idea how to make it draggable to the particular direction. Can anyone help me put with the issue.
I want to make the plane to be draggable only in the own direction on mouse event
import * as THREE from '../build/three.module.js';
import Stats from './jsm/libs/stats.module.js';
import { GUI } from './jsm/libs/dat.gui.module.js';
import { OrbitControls } from './jsm/controls/OrbitControls.js';
import { DragControls } from './jsm/controls/DragControls.js';
var camera, scene, renderer, startTime, object, stats;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(36, window.innerWidth / window.innerHeight, 0.0001, 100000);
camera.position.set(0, 1.3, 8);
scene = new THREE.Scene();
// Lights
scene.add(new THREE.AmbientLight(0x505050));
scene.background = new THREE.Color('white')
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.angle = Math.PI / 5;
spotLight.penumbra = 0.2;
spotLight.position.set(2, 3, 3);
spotLight.castShadow = true;
spotLight.shadow.camera.near = 3;
spotLight.shadow.camera.far = 10;
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
scene.add(spotLight);
var dirLight = new THREE.DirectionalLight(0x55505a, 1);
dirLight.position.set(0, 3, 0);
dirLight.castShadow = true;
dirLight.shadow.camera.near = 1;
dirLight.shadow.camera.far = 10;
dirLight.shadow.camera.right = 1;
dirLight.shadow.camera.left = - 1;
dirLight.shadow.camera.top = 1;
dirLight.shadow.camera.bottom = - 1;
dirLight.shadow.mapSize.width = 1024;
dirLight.shadow.mapSize.height = 1024;
scene.add(dirLight);
// ***** Clipping planes: *****
var localPlane = new THREE.Plane(new THREE.Vector3(0, - 1, 0), 1.5);
var helper1 = new THREE.PlaneHelper(localPlane, 3, 0xffff00);
scene.add(helper1);
var globalPlane = new THREE.Plane(new THREE.Vector3(- 1, 0, 0), 1);
var helper = new THREE.PlaneHelper(globalPlane, 3, 0xffff00);
scene.add(helper); // Geometry
var material = new THREE.MeshPhongMaterial({
color: 0x80ee10,
shininess: 100,
side: THREE.DoubleSide,
// ***** Clipping setup (material): *****
clippingPlanes: [localPlane],
clipShadows: true
});
var geometry = new THREE.TorusKnotBufferGeometry(0.4, 0.08, 95, 20);
object = new THREE.Mesh(geometry, material);
object.castShadow = true;
scene.add(object);
var ground = new THREE.Mesh(
new THREE.PlaneBufferGeometry(9, 9, 1, 1),
new THREE.MeshPhongMaterial({ color: 0xa0adaf, shininess: 150 })
);
ground.rotation.x = - Math.PI / 2; // rotates X/Y to X/Z
ground.receiveShadow = true;
// scene.add( ground );
// Stats
stats = new Stats();
document.body.appendChild(stats.dom);
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.shadowMap.enabled = true;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
window.addEventListener('resize', onWindowResize, false);
document.body.appendChild(renderer.domElement);
// ***** Clipping setup (renderer): *****
var globalPlanes = [globalPlane],
Empty = Object.freeze([]);
renderer.clippingPlanes = Empty; // GUI sets it to globalPlanes
renderer.localClippingEnabled = true;
renderer.clippingPlanes = globalPlanes;
// Controls
var controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1, 0);
controls.update();
// controls1 = new DragControls([...globalPlane], camera, renderer.domElement);
// controls1.addEventListener('drag', render);
// GUI
var gui = new GUI(),
folderLocal = gui.addFolder('Local Clipping'),
propsLocal = {
get 'Enabled'() {
return renderer.localClippingEnabled;
},
set 'Enabled'(v) {
renderer.localClippingEnabled = v;
},
get 'Shadows'() {
return material.clipShadows;
},
set 'Shadows'(v) {
material.clipShadows = v;
},
get 'Plane'() {
return localPlane.constant;
},
set 'Plane'(v) {
localPlane.constant = v;
}
},
folderGlobal = gui.addFolder('Global Clipping'),
propsGlobal = {
get 'Enabled'() {
console.log('hitting 1')
return renderer.clippingPlanes !== Empty;
console.log(renderer.clippingPlanes);
},
set 'Enabled'(v) {
console.log('hitting 2')
renderer.clippingPlanes = v ? globalPlanes : Empty;
console.log(renderer.clippingPlanes);
},
get 'Plane'() {
return globalPlane.constant;
},
set 'Plane'(v) {
globalPlane.constant = v;
}
};
folderLocal.add(propsLocal, 'Enabled');
folderLocal.add(propsLocal, 'Shadows');
folderLocal.add(propsLocal, 'Plane', 0.3, 1.25);
folderGlobal.add(propsGlobal, 'Enabled');
folderGlobal.add(propsGlobal, 'Plane', - 0.4, 3);
// Start
startTime = Date.now();
document.addEventListener('click', onClick, false);
}
function onClick(event) {
event.preventDefault();
if (enableSelection === true) {
var draggableObjects = controls.getObjects();
draggableObjects.length = 0;
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
var intersections = raycaster.intersectObjects(objects, true);
if (intersections.length > 0) {
var object = intersections[0].object;
if (group.children.includes(object) === true) {
object.material.emissive.set(0x000000);
scene.attach(object);
} else {
object.material.emissive.set(0xaaaaaa);
group.attach(object);
}
controls.transformGroup = true;
draggableObjects.push(group);
}
if (group.children.length === 0) {
controls.transformGroup = false;
draggableObjects.push(...objects);
}
}
render();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
// var currentTime = Date.now();
// var time = ( currentTime - startTime ) / 1000;
requestAnimationFrame(animate);
// object.position.y = 0.8;
// object.rotation.x = time * 0.5;
// object.rotation.y = time * 0.2;
// object.scale.setScalar( Math.cos( time ) * 0.125 + 0.875 );
stats.begin();
renderer.render(scene, camera);
stats.end();
}
In order to move things in three.js you use TransformControls. Check out https://threejs.org/docs/#examples/en/controls/TransformControls for its documentation and https://github.com/mrdoob/three.js/blob/master/examples/misc_controls_transform.html for implementation example.
Here you can set the mode to "translate" and scale to "XY" to restrict the movement in X and Y direction only.
controls.axis ="XY";

Three JS: Add Zoom function to the Bounding Box (Rect)

Here I have created a rect in which it gets zoom in to the mid point of the scene. Here I'm looking to zoom in to the center of the rect (where we select the object) towards the object.
I have an idea to get the center point of the rect and and pass it to the zoom function where I'm struggling in.
Please help me out
Heres the snippet
/**
* #author HypnosNova / https://www.threejs.org.cn/gallery
* This is a class to check whether objects are in a selection area in 3D space
*/
THREE.SelectionBox = (function() {
var frustum = new THREE.Frustum();
var center = new THREE.Vector3();
function SelectionBox(camera, scene, deep) {
this.camera = camera;
this.scene = scene;
this.startPoint = new THREE.Vector3();
this.endPoint = new THREE.Vector3();
this.collection = [];
this.deep = deep || Number.MAX_VALUE;
}
SelectionBox.prototype.select = function(startPoint, endPoint) {
this.startPoint = startPoint || this.startPoint;
this.endPoint = endPoint || this.endPoint;
this.collection = [];
this.updateFrustum(this.startPoint, this.endPoint);
this.searchChildInFrustum(frustum, this.scene);
return this.collection;
};
SelectionBox.prototype.updateFrustum = function(startPoint, endPoint) {
startPoint = startPoint || this.startPoint;
endPoint = endPoint || this.endPoint;
this.camera.updateProjectionMatrix();
this.camera.updateMatrixWorld();
var tmpPoint = startPoint.clone();
tmpPoint.x = Math.min(startPoint.x, endPoint.x);
tmpPoint.y = Math.max(startPoint.y, endPoint.y);
endPoint.x = Math.max(startPoint.x, endPoint.x);
endPoint.y = Math.min(startPoint.y, endPoint.y);
var vecNear = this.camera.position.clone();
var vecTopLeft = tmpPoint.clone();
var vecTopRight = new THREE.Vector3(endPoint.x, tmpPoint.y, 0);
var vecDownRight = endPoint.clone();
var vecDownLeft = new THREE.Vector3(tmpPoint.x, endPoint.y, 0);
vecTopLeft.unproject(this.camera);
vecTopRight.unproject(this.camera);
vecDownRight.unproject(this.camera);
vecDownLeft.unproject(this.camera);
var vectemp1 = vecTopLeft.clone().sub(vecNear);
var vectemp2 = vecTopRight.clone().sub(vecNear);
var vectemp3 = vecDownRight.clone().sub(vecNear);
vectemp1.normalize();
vectemp2.normalize();
vectemp3.normalize();
vectemp1.multiplyScalar(this.deep);
vectemp2.multiplyScalar(this.deep);
vectemp3.multiplyScalar(this.deep);
vectemp1.add(vecNear);
vectemp2.add(vecNear);
vectemp3.add(vecNear);
var planes = frustum.planes;
planes[0].setFromCoplanarPoints(vecNear, vecTopLeft, vecTopRight);
planes[1].setFromCoplanarPoints(vecNear, vecTopRight, vecDownRight);
planes[2].setFromCoplanarPoints(vecDownRight, vecDownLeft, vecNear);
planes[3].setFromCoplanarPoints(vecDownLeft, vecTopLeft, vecNear);
planes[4].setFromCoplanarPoints(vecTopRight, vecDownRight, vecDownLeft);
planes[5].setFromCoplanarPoints(vectemp3, vectemp2, vectemp1);
planes[5].normal.multiplyScalar(-1);
};
SelectionBox.prototype.searchChildInFrustum = function(frustum, object) {
if (object.isMesh) {
if (object.material !== undefined) {
object.geometry.computeBoundingSphere();
center.copy(object.geometry.boundingSphere.center);
center.applyMatrix4(object.matrixWorld);
if (frustum.containsPoint(center)) {
this.collection.push(object);
}
}
}
if (object.children.length > 0) {
for (var x = 0; x < object.children.length; x++) {
this.searchChildInFrustum(frustum, object.children[x]);
}
}
};
return SelectionBox;
})();
/**
* #author HypnosNova / https://www.threejs.org.cn/gallery
*/
THREE.SelectionHelper = (function() {
function SelectionHelper(selectionBox, renderer, cssClassName) {
this.element = document.createElement('div');
this.element.classList.add(cssClassName);
this.element.style.pointerEvents = 'none';
this.renderer = renderer;
this.startPoint = new THREE.Vector2();
this.pointTopLeft = new THREE.Vector2();
this.pointBottomRight = new THREE.Vector2();
this.isDown = false;
this.renderer.domElement.addEventListener('mousedown', function(event) {
this.isDown = true;
this.onSelectStart(event);
}.bind(this), false);
this.renderer.domElement.addEventListener('mousemove', function(event) {
if (this.isDown) {
this.onSelectMove(event);
}
}.bind(this), false);
this.renderer.domElement.addEventListener('mouseup', function(event) {
this.isDown = false;
this.onSelectOver(event);
}.bind(this), false);
}
SelectionHelper.prototype.onSelectStart = function(event) {
this.renderer.domElement.parentElement.appendChild(this.element);
this.element.style.left = event.clientX + 'px';
this.element.style.top = event.clientY + 'px';
this.element.style.width = '0px';
this.element.style.height = '0px';
this.startPoint.x = event.clientX;
this.startPoint.y = event.clientY;
};
SelectionHelper.prototype.onSelectMove = function(event) {
this.pointBottomRight.x = Math.max(this.startPoint.x, event.clientX);
this.pointBottomRight.y = Math.max(this.startPoint.y, event.clientY);
this.pointTopLeft.x = Math.min(this.startPoint.x, event.clientX);
this.pointTopLeft.y = Math.min(this.startPoint.y, event.clientY);
this.element.style.left = this.pointTopLeft.x + 'px';
this.element.style.top = this.pointTopLeft.y + 'px';
this.element.style.width = (this.pointBottomRight.x - this.pointTopLeft.x) + 'px';
this.element.style.height = (this.pointBottomRight.y - this.pointTopLeft.y) + 'px';
};
SelectionHelper.prototype.onSelectOver = function() {
this.element.parentElement.removeChild(this.element);
};
return SelectionHelper;
})();
var container;
var camera, scene, renderer;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 5000);
camera.position.z = 1000;
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
scene.add(new THREE.AmbientLight(0x505050));
var light = new THREE.SpotLight(0xffffff, 1.5);
light.position.set(0, 500, 2000);
light.angle = Math.PI / 9;
light.castShadow = true;
light.shadow.camera.near = 1000;
light.shadow.camera.far = 4000;
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
scene.add(light);
var geometry = new THREE.BoxBufferGeometry(20, 20, 20);
for (var i = 0; i < 200; i++) {
var object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
color: Math.random() * 0xffffff
}));
object.position.x = Math.random() * 1600 - 800;
object.position.y = Math.random() * 900 - 450;
object.position.z = Math.random() * 900 - 500;
object.rotation.x = Math.random() * 2 * Math.PI;
object.rotation.y = Math.random() * 2 * Math.PI;
object.rotation.z = Math.random() * 2 * Math.PI;
object.scale.x = Math.random() * 2 + 1;
object.scale.y = Math.random() * 2 + 1;
object.scale.z = Math.random() * 2 + 1;
object.castShadow = true;
object.receiveShadow = true;
scene.add(object);
}
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFShadowMap;
container.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
var selectionBox = new THREE.SelectionBox(camera, scene);
var helper = new THREE.SelectionHelper(selectionBox, renderer, 'selectBox');
document.addEventListener('mousedown', function(event) {
for (var item of selectionBox.collection) {
item.material.emissive = new THREE.Color(0x000000);
}
selectionBox.startPoint.set(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1,
0.5);
});
document.addEventListener('mousemove', function(event) {
if (helper.isDown) {
for (var i = 0; i < selectionBox.collection.length; i++) {
selectionBox.collection[i].material.emissive = new THREE.Color(0x000000);
}
selectionBox.endPoint.set(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1,
0.5);
var allSelected = selectionBox.select();
for (var i = 0; i < allSelected.length; i++) {
allSelected[i].material.emissive = new THREE.Color(0x0000ff);
}
}
});
document.addEventListener('mouseup', function(event) {
selectionBox.endPoint.set(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1,
0.5);
var allSelected = selectionBox.select();
for (var i = 0; i < allSelected.length; i++) {
allSelected[i].material.emissive = new THREE.Color(0x0000ff);
}
});
body {
background-color: #f0f0f0;
color: #000;
margin: 0;
}
canvas {
display: block;
}
a {
color: #08e;
}
.selectBox {
border: 1px solid #55aaff;
background-color: rgba(75, 160, 255, 0.3);
position: fixed;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.min.js"></script>
You want to zoom in to the selected area? Based on what you're doing, you could try something like this:
function zoomToSelection() {
topLeft = helper.pointTopLeft;
bottomRight = helper.pointBottomRight;
// Get the centerpoint of the selection.
let center = new THREE.Vector2(topLeft.x + (bottomRight.x - topLeft.x) / 2, topLeft.y + (bottomRight.y - topLeft.y) / 2);
// Get the center position in world space.
var vector = new THREE.Vector3(
(center.x / window.innerWidth) * 2 - 1,
-(center.y / window.innerHeight) * 2 + 1,
0.5
);
vector.unproject(camera);
camera.lookAt(vector);
var movement = vector.clone();
// Get the ratio between the box size and the window.
let zoomNeeded = (bottomRight.y - topLeft.y) / window.innerHeight;
// Get a scalar by which to move the camera in the direction it's looking.
let distanceToOrigin = camera.position.distanceTo(new THREE.Vector3(0, 0, 0))
let distance = distanceToOrigin - distanceToOrigin * zoomNeeded;
movement.sub(camera.position).normalize().multiplyScalar(-zoom);
var toDirection = camera.position.clone().sub(movement);
camera.position.set(toDirection.x, toDirection.y, toDirection.z);
}
Now just add to your 'mouseup' event listener:
document.addEventListener('mouseup', function(event) {
//...
zoomToSelection();
});

How does raycasting in three.js work with an offscreen canvas?

I can't seem to get raycasting to work in an offscreen canvas.
A click event sends data to the worker like this:
var r = document.getElementById('webGL').getBoundingClientRect()
offscreen.postMessage({
action: 'set_scene',
mesh: mesh.toJSON(),
camera: camera.toJSON(),
canvasSize: {
width: document.getElementById('webGL').clientWidth,
height: document.getElementById('webGL').clientHeight
},
coordinates: { x: e.originalEvent.clientX - r.x, y: e.originalEvent.clientY - r.y },
time: (new Date())
});

while the worker looks like this:
self.importScripts( './three.min.js' );
var loader = new THREE.ObjectLoader();
var scene = new THREE.Scene();
self.onmessage = function(e) {
// var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
var canvas = new OffscreenCanvas(e.data.canvasSize.width, e.data.canvasSize.height);
var renderer = new THREE.WebGLRenderer( { antialias: true, canvas: canvas, preserveDrawingBuffer: true } );
Promise.all([
(new Promise(function(resolve, reject) {
loader.parse(
e.data.mesh,
function ( obj ) {
resolve( obj );
})
})),
(new Promise(function(resolve, reject) {
loader.parse(
e.data.camera,
function ( obj ) {
resolve( obj );
})
}))
]).then(obj => {
var mesh, camera
[mesh, camera] = obj;
scene.add( mesh );
renderer.render( scene, camera );
var raycaster = new THREE.Raycaster();
p = { x: e.data.coordinates.x, y: e.data.coordinates.y };
var m = {};
m.x = (p.x) / e.data.canvasSize.width * 2 - 1;
m.y = 1 - (p.y) / e.data.canvasSize.height * 2;
raycaster.setFromCamera( m, camera );
var intersects = raycaster.intersectObjects( [ mesh ], true );
return intersects;
}).then(r => {
self.postMessage(r);
}).catch(e => {
console.log(e);
})
}
Same code onscreen works ok, and the values resulting from the transforms check out ok.
Is it possible to do such a thing at all, or what am I getting wrong?
I don't see the issue in your code but there is absolutely nothing special about picking offscreen. Here's a working example to prove it. It doesn't have any three or camera or mesh in the main page, only in the worker.
All the main page does is start the worker, transfer control of the canvas to the worker, then send resize and mouse events to the worker. That's it. Otherwise the code in the worker is 99% the same as the code would be in the main page. The only major difference is resizeCanvasToDisplaySize gets the display size from state.width and state.height. The non-offscreen version code comes from here
You need to post more code.
function main() {
const canvas = document.querySelector("#c");
if (!canvas.transferControlToOffscreen) {
alert('no offscreen canvas support');
return;
}
const offscreen = canvas.transferControlToOffscreen();
const workerScript = document.querySelector('#foo').text;
const blob = new Blob([workerScript], {type: 'application/javascript'});
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
worker.postMessage({type: 'init', canvas: offscreen}, [offscreen]);
function sendSize() {
worker.postMessage({
type: 'size',
width: canvas.clientWidth,
height: canvas.clientHeight,
});
}
sendSize();
window.addEventListener('resize', sendSize);
function getNormaizedMousePosition(element, e) {
const rect = element.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
return {
x: x / canvas.clientWidth * 2 - 1,
y: y / canvas.clientHeight * -2 + 1,
}
}
canvas.addEventListener('mousemove', (e) => {
const pos = getNormaizedMousePosition(canvas, e);
worker.postMessage({
type: 'mousemove',
x: pos.x,
y: pos.y,
});
});
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="foo" id="foo">
'use strict';
importScripts('https://threejsfundamentals.org/threejs/resources/threejs/r103/three.min.js');
/* global THREE */
const state = {
width: 300,
height: 150,
mouse: {
x: -2,
y: -2,
},
};
function init(data) {
const {canvas} = data;
const renderer = new THREE.WebGLRenderer({
canvas
});
state.width = canvas.width;
state.height = canvas.height;
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 4;
const scene = new THREE.Scene();
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function makeInstance(geometry, color, x) {
const material = new THREE.MeshPhongMaterial({
color
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.position.x = x;
return cube;
}
const cubes = [
makeInstance(geometry, 0x44aa88, 0),
makeInstance(geometry, 0x8844aa, -2),
makeInstance(geometry, 0xaa8844, 2),
];
class PickHelper {
constructor() {
this.raycaster = new THREE.Raycaster();
this.pickedObject = null;
this.pickedObjectSavedColor = 0;
}
pick(normalizedPosition, scene, camera, time) {
// restore the color if there is a picked object
if (this.pickedObject) {
this.pickedObject.material.emissive.setHex(this.pickedObjectSavedColor);
this.pickedObject = undefined;
}
// cast a ray through the frustum
this.raycaster.setFromCamera(normalizedPosition, camera);
// get the list of objects the ray intersected
const intersectedObjects = this.raycaster.intersectObjects(scene.children);
if (intersectedObjects.length) {
// pick the first object. It's the closest one
this.pickedObject = intersectedObjects[0].object;
// save its color
this.pickedObjectSavedColor = this.pickedObject.material.emissive.getHex();
// set its emissive color to flashing red/yellow
this.pickedObject.material.emissive.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
}
}
}
const pickHelper = new PickHelper();
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = state.width;
const height = state.height;
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.width / canvas.height;
camera.updateProjectionMatrix();
}
cubes.forEach((cube, ndx) => {
const speed = 1 + ndx * .1;
const rot = time * speed;
cube.rotation.x = rot;
cube.rotation.y = rot;
});
pickHelper.pick(state.mouse, scene, camera, time);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
function size(data) {
state.width = data.width;
state.height = data.height;
}
function mousemove(data) {
state.mouse.x = data.x;
state.mouse.y = data.y;
}
const handlers = {
init,
size,
mousemove,
};
self.onmessage = function(e) {
const fn = handlers[e.data.type];
if (!fn) {
throw new Error('no handler for type: ' + e.data.type);
}
fn(e.data);
};
</script>
For your particular case though you shouldn't need a camera. Send the ray. You don't need the canvas size either. Picking in three.js is CPU based
function main() {
const canvas = document.querySelector("#c");
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 60;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 200;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 4;
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function makeInstance(geometry, color, x, name) {
const material = new THREE.MeshPhongMaterial({
color
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.name = name;
cube.position.x = x;
cube.rotation.x = x;
cube.rotation.z = x + .7;
return cube;
}
makeInstance(geometry, 0x44aa88, 0, 'cyan cube');
makeInstance(geometry, 0x8844aa, -2, 'purple cube');
makeInstance(geometry, 0xaa8844, 2, 'brown cube');
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() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.width / canvas.height;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
}
render();
window.addEventListener('resize', render);
const workerScript = document.querySelector('#foo').text;
const blob = new Blob([workerScript], {type: 'application/javascript'});
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
const msgElem = document.querySelector('#msg');
worker.onmessage = (e) => {
msgElem.textContent = e.data;
};
worker.postMessage({type: 'init', scene: scene.toJSON()});
function getNormaizedMousePosition(element, e) {
const rect = element.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
return {
x: x / canvas.clientWidth * 2 - 1,
y: y / canvas.clientHeight * -2 + 1,
}
}
const raycaster = new THREE.Raycaster();
canvas.addEventListener('mousemove', (e) => {
const pos = getNormaizedMousePosition(canvas, e);
raycaster.setFromCamera(pos, camera);
worker.postMessage({
type: 'intersect',
origin: raycaster.ray.origin.toArray(),
direction: raycaster.ray.direction.toArray(),
});
});
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
#msg {
position: absolute;
left: 1em;
top: 1em;
}
<canvas id="c"></canvas>
<div id="msg"></div>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r103/three.min.js"></script>
<script type="foo" id="foo">
'use strict';
importScripts('https://threejsfundamentals.org/threejs/resources/threejs/r103/three.min.js');
/* global THREE */
function loadObject(json) {
return new Promise((resolve) => {
const loader = new THREE.ObjectLoader();
loader.parse(json, resolve);
});
}
const renderer = new THREE.WebGLRenderer({
canvas: new OffscreenCanvas(1, 1),
});
// settings not important
const camera = new THREE.PerspectiveCamera(1, 1, 0.1, 100);
const raycaster = new THREE.Raycaster();
let scene;
let lastIntersectedObject;
async function init(data) {
scene = await loadObject(data.scene);
// we only need to render once to init the scene
renderer.render(scene, camera);
}
function intersect(data) {
raycaster.set(
new THREE.Vector3(...data.origin),
new THREE.Vector3(...data.direction));
const intersections = raycaster.intersectObjects(scene.children);
const intersectedObject = intersections.length
? intersections[0].object
: null;
if (intersectedObject !== lastIntersectedObject) {
lastIntersectedObject = intersectedObject;
log('intersection:', lastIntersectedObject ? lastIntersectedObject.name : 'none');
}
}
const handlers = {
init,
intersect,
};
self.onmessage = function(e) {
const fn = handlers[e.data.type];
if (!fn) {
throw new Error('no handler for type: ' + e.data.type);
}
fn(e.data);
};
function log(...args) {
postMessage([...args].join(' '));
}
</script>

Wrong location of texture on the second triangle of BufferGeometry square

I'm trying to add texture to a THREE.BufferGeometry. This is a working fiddle of what I'm accomplished so far.
This is my code:
var Test = new function () {
var scene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera(10, window.innerWidth / window.innerHeight, 1, 1000),
renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true
camera.position.set(-5, 5, 5);
camera.rotation.order = 'YXZ';
camera.rotation.y = -Math.PI / 4;
camera.rotation.x = Math.atan(-1 / Math.sqrt(2));
scene.add(camera)
scene.add(new THREE.AmbientLight(0x222222));
var light = new THREE.PointLight(0xffffff, 0.7);
camera.add(light);
this.init = function () {
$('body').append(renderer.domElement);
Test.render();
}
this.render = function () {
requestAnimationFrame(Test.render);
renderer.render(scene, camera);
}
this.getScene = function getScene() {
return scene
}
}
var CreateSimpleMesh = new function () {
var grassGeometry = new THREE.BufferGeometry(),
grassVertexPositions = [],
imageArray = [
[0, 0],
[1, 0],
[1, 1],
[0, 1]
],
image = new Image()
this.init = function () {
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wwLAi03pl5F1AAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAEkklEQVR42u3dTWzTdRzH8XfXdW03t3XPbmyMlLkNZA9kcwuKBBVBEjXEGDExEAwH44GDJpp4QYnCQcIZoiZGUaKeCFHjIBB5UnnSKW5zj7AVWvfvxsbWre22Dg+1SmHA/iwbHD7v2+/fZYfvK/+n9vCzrDmx7hrqvilBIxCIuk2JGsHsNth0hcvf9dB/1iBkBLFnOkgrc+HeWEr6oow7gwy1DuJt8NB/zs+oJ4DFmoAjx4GrMoviF92klqRryiY69dqxuHXICBIyghgnfFS/X0feyoLbg/y8+ccbjkwy0hNgpCfA5e+7qdxaS/6qQk16mrmWZFL4fDGZNTnYM+wEuodp2fUHg39eof2j5juDpJW7mLd2PlmP5ODMT2FyPMJIT4AL+9rpPeKlbXeTQExUv2dF/HwfclGxtYbjLx0i+PfonS9Zyz5ZGX/XtyWQXp7B4jer6D3iZWJkQlOe6Y3bGR178rwU8zf1ieAEwcsjXNjXDkD+ap0dM+3iVx0APDjFleaWIA3L98etnQXJlL6+mOL1JZroDPIe9HDhy3aSC1NY8HLJ3b+HjA2ECfeHQe/1M8I4/8E5rA4r1dvrsNqt0z9D1pxYF4UYGmPor0HadjfR/U0nWKB8S4WmazLPgYs072wEC1S+W0vqwvS7e1NPSksiuy6XpTvqAPAduqTpmr1nfN1B84eNcA0efnspuY/nz/xNfXI8eq2KjOopy0ydn7XS8XELAIvfqqbw2WJzX52cfeMk819wk74kE1uqjYnhcQabB2jf0xR90anM0pRNFMMAaN7ZGL1sXdcT364lyWW/NUj/GT/9Z/xT/nNbmo3yLUs05VnMcuPvIQPn+7l0oJsrv/oJ94VIsCXgLEghuz6XBetLsGc7NLXZfGm88UBGRRYZFbos3av0e4hAlEAEogQiECUQgSiBCEQJRAlEIEogAlECEYgSiECUQJRABKIEIhAlEIEogQhECUQJRCBKIAJRAhGIEohAlECUQASiBCIQJRCBKIEIRAlECUQgSiACUQIRiBKIQJRAlEAEogQiECUQgSiBCETdu6bcFGxyLELX3ja8P3gI+YM4cpwUPFOEe2MZCTYZmi3QPYxxzIdx3MfV5gHg/20JpwXy+7azGEd9/62DvlE6P20l0DVM9fY6TdhkJ185fPeXLP8vvRhHfViTE6nZtYxVh5+jZtcyrMmJ9B710nfa0IRNlrIglYWvlvHY50+aB/Ed9ADg3lBKdn0eVruV7Po83BtKAfA2eDRhky3/4ilKNi/iAXeaeZCrLYMA5DyaF3c8th5qGdCE5/IpK9wXBMBZEL/Xd2wd8oc0tbkEiYQiADftZBxbR8LaenVOQayO2OAj8VDhGFSipjaXIPZsZ/RR1zsSdzy2duRop885BUlf5Io+/v7UG/84/O86rdylqc0lSP7TRQB07W2j77TB5FiEvtMGXXvbop+vLtLUZrGbNicG+O2dUxjHfTf9ce6KfJbuqNfUTNawfP9tP7/+a5Qpv5iq2laLe1MZjjwnFqsFR54T96Yyqt6r1XTvxRmi7qN7iBKIEohA1DT7B5cwUaoY0hehAAAAAElFTkSuQmCC'
var texture = new THREE.Texture();
texture.image = image;
image.onload = function () {
texture.needsUpdate = true;
};
// first triangle
grassVertexPositions.push([0, 0, 0])
grassVertexPositions.push([1, 0, 0])
grassVertexPositions.push([0, 1, 0])
// second triangle
grassVertexPositions.push([1, 1, 0])
grassVertexPositions.push([0, 1, 0])
grassVertexPositions.push([1, 0, 0])
var grassVertices = new Float32Array(grassVertexPositions.length * 3),
normals = new Float32Array(grassVertexPositions.length * 3),
colors = new Float32Array(grassVertexPositions.length * 3),
uvs = new Float32Array(grassVertexPositions.length * 2)
for (var i = 0; i < grassVertexPositions.length; i++) {
var index = 3 * i
grassVertices[index + 0] = grassVertexPositions[i][0]
grassVertices[index + 1] = grassVertexPositions[i][1]
grassVertices[index + 2] = grassVertexPositions[i][2]
}
uvs[0] = imageArray[0][0]
uvs[1] = imageArray[0][1]
uvs[2] = imageArray[1][0]
uvs[3] = imageArray[1][1]
uvs[4] = imageArray[3][0]
uvs[5] = imageArray[3][1]
uvs[6] = imageArray[1][0]
uvs[7] = imageArray[1][1]
uvs[8] = imageArray[2][0]
uvs[9] = imageArray[2][1]
uvs[10] = imageArray[3][0]
uvs[11] = imageArray[3][1]
grassGeometry.addAttribute('position', new THREE.BufferAttribute(grassVertices, 3))
grassGeometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3))
grassGeometry.addAttribute('color', new THREE.BufferAttribute(colors, 3))
grassGeometry.addAttribute('uv', new THREE.BufferAttribute(uvs, 2))
grassGeometry.computeVertexNormals()
//var textureLoader = new THREE.TextureLoader();
//textureLoader.load('/img/testface.png', function (texture) {
var grassMaterial = new THREE.MeshLambertMaterial({map: texture}),
grassMesh = new THREE.Mesh(grassGeometry, grassMaterial)
grassMesh.rotation.x = -Math.PI / 2;
Test.getScene().add(grassMesh)
var helper = new THREE.WireframeHelper(grassMesh, 0xff00ff); // alternate
helper.material.linewidth = 1;
Test.getScene().add(helper);
console.log(grassMesh.geometry.attributes)
//});
}
}
$(document).ready(function () {
Test.init()
CreateSimpleMesh.init()
});
The problem is that texture on first triangle of the rectangle is correct but on the second triangle something strange is happening with the texture.
This is how it should look like
This is how it look now
Try these texture coordinates (same as x and y of vertex coordinates):
uvs[0] = 0
uvs[1] = 0
uvs[2] = 1
uvs[3] = 0
uvs[4] = 0
uvs[5] = 1
uvs[6] = 1
uvs[7] = 1
uvs[8] = 0
uvs[9] = 1
uvs[10] = 1
uvs[11] = 0

Resources