How to get real line width from 2D geometry? - three.js

how can I get line width from 2D sheet to create custom line with correct width or relative original line width.
VertexBufferReader returns is two point without line width.

Are you looking for this?
onLineSegment(x1, y1, x2, y2, vpId, lineWidth) has a lineWidth argument at the end of the function argument
onTexQuad(centerX, centerY, width, height, rotation, vpId) has a width argument in the function argument
Snapshots:
Demo code:
function GeometryCallback(viewer,copiedGeometryName,curveMaterial) {
this.viewer = viewer;
this.opiedGeometryName = copiedGeometryName;
// linewidth does not take effect in Chrome and Firefox
// It is a known issue with OpenGL core
// try with Safari
this.curveMaterial = curveMaterial;
this.is2PITimes = function(angle1,angle2){
return Math.abs(angle1-angle2)/Math.PI % 2 == 0
}
}
GeometryCallback.prototype.onLineSegment = function(x1, y1, x2, y2, vpId, lineWidth) {
var vpXform = this.viewer.model.getPageToModelTransform(vpId);
//if in CAD coordinate system, applyMatrix4 with vpXform
var pt1 = new THREE.Vector3().set(x1, y1, 0)//.applyMatrix4(vpXform);
var pt2 = new THREE.Vector3().set(x2, y2, 0)//.applyMatrix4(vpXform);
console.log('Line segment vertices coordinates: ', {
pointX1: pt1.x,
pointY1: pt1.y,
pointX2: pt2.x,
pointY2: pt2.y
},
'width:', lineWidth
);
//add overlay geometry
var geometry = new THREE.Geometry ()
geometry.vertices.push (new THREE.Vector3 ( pt1.x, pt1.y, 0))
geometry.vertices.push (new THREE.Vector3 ( pt2.x, pt2.y, 0))
var lines = new THREE.Line (geometry,
this.curveMaterial,
THREE.LinePieces)
this.viewer.impl.addOverlay (this.opiedGeometryName, lines)
this.viewer.impl.invalidate (false,false,true)
}
GeometryCallback.prototype.onCircularArc = function(cx, cy, start, end, radius, vpId) {
var vpXform = this.viewer.model.getPageToModelTransform(vpId);
//if in CAD coordinate system, applyMatrix4 with vpXform
var center = new THREE.Vector3().set(cx, cy, 0)//.applyMatrix4(vpXform);
console.log('CircleArc segment: ', {
centerX: center.x,
centerY: center.y,
radius: radius,
startAngle: start,
endAngle: end
});
//add overlay geometry
var curve = new THREE.EllipseCurve(
center.x, center.y,
radius, radius,
start, end,
false
);
var path = new THREE.Path(curve.getPoints(50));
var geometry = path.createPointsGeometry(50);
//remove last vertex if it an arc
if(!this.is2PITimes(start,end))
geometry.vertices.pop();
var circularArc = new THREE.Line(geometry, this.curveMaterial);
this.viewer.impl.addOverlay (this.opiedGeometryName, circularArc)
this.viewer.impl.invalidate (false,false,true)
};
GeometryCallback.prototype.onEllipticalArc = function(cx, cy, start, end, major, minor, tilt, vpId) {
var vpXform = this.viewer.model.getPageToModelTransform(vpId);
//if in CAD coordinate system, applyMatrix4 with vpXform
var center = new THREE.Vector3().set(cx, cy, 0)//.applyMatrix4(vpXform);
console.log('EllipticalArc segment: ', {
centerX: center.x,
centerY: center.y,
radius: radius,
startAngle: start,
endAngle: end
});
//add overlay geometry
var curve = new THREE.EllipseCurve(
center.x, center.y,
major, minor,
start, end,
false
);
var path = new THREE.Path(curve.getPoints(50));
var geometry = path.createPointsGeometry(50);
//remove last vertex if it an arc
if(!this.is2PITimes(start,end))
geometry.vertices.pop();
var ellipticalArc = new THREE.Line(geometry, this.curveMaterial);
this.viewer.impl.addOverlay (this.opiedGeometryName, ellipticalArc)
this.viewer.impl.invalidate (false,false,true)
};
GeometryCallback.prototype.onOneTriangle = function(x1, y1, x2, y2, x3, y3, vpId){
//Similar logic as above
};
GeometryCallback.prototype.onTexQuad = function(centerX, centerY, width, height, rotation, vpId){
//from VertexBufferReader.js:
//Currently this case does not actually come up
};
//extension of MyShow2dCurve
function MyShow2dCurve(viewer, options) {
Autodesk.Viewing.Extension.call(this, viewer, options)
var _viewer = this.viewer
var _copiedGeometryName = 'copiedGeometryName'
// linewidth does not take effect in Chrome and Firefox
// It is a known issue with OpenGL core
// try with Safari
var _curveMaterial = new THREE.LineBasicMaterial ({
color: new THREE.Color (0xFF0000),
transparent: true,
depthWrite: false,
depthTest: false,
linewidth: 5,
opacity: 1.0
})
//when extension is loaded
this.load = function() {
console.log('MyShow2dCurve is loaded!');
//bind keyup event
$(document).bind('keyup', onKeyUp);
_viewer.impl.invalidate(true);
return true;
};
//when extension is unloaded
this.unload = function() {
console.log('MyShow2dCurve is now unloaded!');
//unbind keyup event
$(document).unbind('keyup', this.onKeyUp);
return true;
};
//when key up
function onKeyUp(evt) {
console.log('onKeyUp:' + evt.keyCode);
//when key 'S' is pressed
if(evt.keyCode == 83){
//create overlay
_viewer.impl.createOverlayScene (_copiedGeometryName, _curveMaterial)
//start to monitor select event
_viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, onSelectObj)
}
//when key 'Q' is pressed
if(evt.keyCode == 81){
//undelegate selection event
_viewer.removeEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, onSelectObj)
//remove overlay
_viewer.impl.removeOverlayScene(_copiedGeometryName);
_viewer.impl.invalidate(false, false, true);
}
return true;
}
//when selecting object
function onSelectObj(evt){
console.log(evt)
//selected object id
var dbId = evt.dbIdArray[0]
//get instance tree
var it = _viewer.model.getData().instanceTree;
//dump fragments of the object
it.enumNodeFragments( dbId, function( fragId ) {
//get each fragment
var m = _viewer.impl.getRenderProxy(_viewer.model, fragId);
//initialize VertexBufferReader
var vbr = new Autodesk.Viewing.Private.VertexBufferReader(m.geometry, _viewer.impl.use2dInstancing);
//dump geometry of this fragment
vbr.enumGeomsForObject(dbId, new GeometryCallback(_viewer,_copiedGeometryName, _curveMaterial));
});
}
}
MyShow2dCurve.prototype = Object.create(Autodesk.Viewing.Extension.prototype);
MyShow2dCurve.prototype.varructor = MyShow2dCurve;
Autodesk.Viewing.theExtensionManager.registerExtension('MyShow2dCurve', MyShow2dCurve);
Modified from: https://forge.autodesk.com/blog/dump-geometries-2d-curve

Related

Three.js: Rotate object with lookAt() while located at the current lookAt() position

I'm trying to implement a simple turn-around-and-move feature with Three.js. On mouse click, the object is supposed to first turn around and then move to the clicked location.
Codepen
The rotation is achieved with raycasting and lookAt(). It works by itself and it always works on the first click. If you remove the translation, it works continuously. The issue occurs when rotation and translation are implemented together. If you click a second time, after the object has moved to the previous clicked location, it doesn't rotate as expected. Depending on the mouse location it can flip to the other side without rotating at all.
Clarification: When you click the first time, notice how the object slowly and steadily turns around to face that direction? But the second time, after the object has moved, the rotation is quicker and/or flimsier or it simply flips over and there is no rotation at all. It depends on where you click in relation to the object.
I believe the issue stems from trying to implement lookAt while being located at the current lookAt location? If I stop the translation half way, the next rotation will work better. But of course I need it to go all the way.
I'm somewhat lost on how to proceed with this issue. Any help would be appreciated.
/*** Setup scene ***/
let width = 800
let height = 600
let scene
let renderer
let worldAxis
let box
let angle
let boxAxes
scene = new THREE.Scene()
worldAxis = new THREE.AxesHelper(200);
scene.add(worldAxis);
// Setup renderer
renderer = new THREE.WebGLRenderer({alpha: true, antialias: true})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
document.body.appendChild(renderer.domElement)
// Setup camera
const camera = new THREE.OrthographicCamera(
width / - 2, // left
width / 2, // right
height / 2, // top
height / - 2, // bottom
0, // near
1000 ); // far
camera.position.set(0, 0, 500)
camera.updateProjectionMatrix()
// Setup box
let geometry = new THREE.BoxGeometry( 15, 15, 15 );
let material = new THREE.MeshBasicMaterial( { color: "grey" } );
box = new THREE.Mesh( geometry, material );
box.position.set(100, 150, 0)
box.lookAt(getPointOfIntersection(new THREE.Vector2(0, 0)))
addAngle()
boxAxes = new THREE.AxesHelper(50);
box.add(boxAxes)
scene.add(box)
renderer.render(scene, camera);
/*** Setup animation ***/
let animate = false
let currentlyObservedPoint = new THREE.Vector2();
let rotationIncrement = {}
let translationIncrement = {}
let frameCount = 0
document.addEventListener('click', (event) => {
let mousePosForRotate = getMousePos(event.clientX, event.clientY)
rotationIncrement.x = (mousePosForRotate.x - currentlyObservedPoint.x)/100
rotationIncrement.y = (mousePosForRotate.y - currentlyObservedPoint.y)/100
let mousePosForTranslate = getMousePosForTranslate(event)
translationIncrement.x = (mousePosForTranslate.x - box.position.x)/100
translationIncrement.y = (mousePosForTranslate.y - box.position.y)/100
animate = true
})
function animationLoop() {
if (animate === true) {
if (frameCount < 100) {
rotate()
} else if (frameCount < 200) {
translate()
} else {
animate = false
frameCount = 0
}
frameCount++
renderer.render(scene, camera)
}
requestAnimationFrame(animationLoop)
}
function rotate() {
currentlyObservedPoint.x += rotationIncrement.x
currentlyObservedPoint.y += rotationIncrement.y
let pointOfIntersection = getPointOfIntersection(currentlyObservedPoint)
box.lookAt(pointOfIntersection)
addAngle()
}
function translate() {
box.position.x += translationIncrement.x
box.position.y += translationIncrement.y
}
function getMousePos(x, y) {
let mousePos = new THREE.Vector3(
(x / width) * 2 - 1,
- (y / height) * 2 + 1,
0)
return mousePos
}
function getMousePosForTranslate(event) {
let rect = event.target.getBoundingClientRect();
let mousePos = { x: event.clientX - rect.top, y: event.clientY - rect.left }
let vec = getMousePos(mousePos.x, mousePos.y)
vec.unproject(camera);
vec.sub(camera.position).normalize();
let distance = - camera.position.z / vec.z;
let pos = new THREE.Vector3(0, 0, 0);
pos.copy(camera.position).add(vec.multiplyScalar(distance));
return pos
}
function getPointOfIntersection(mousePos) {
let plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
let pointOfIntersection = new THREE.Vector3()
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mousePos, camera)
raycaster.ray.intersectPlane(plane, pointOfIntersection)
return pointOfIntersection
}
function addAngle() {
let angle = box.rotation.x - 32
box.rotation.x = angle
}
animationLoop()
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.min.js'></script>

ThreeJS - How do I scale down intersected object on "mouseout"

I have n+1 hexshapes in a honeycomb grid. The objects are stacked close together. With this code:
// Get intersected objects, a.k.a objects "hit" by mouse, a.k.a objects that are mouse-overed
const intersects = raycaster.intersectObjects(hexObjects);
// If there is one (or more) intersections
let scaleTween = null;
if (intersects.length > 0) {
// If mouse is not currently over an object
// Set cursor to pointer so that the user can see that the object is clickable
document.body.style.cursor = 'pointer';
// Get the last intersected object, it's most likely that object we are currently hovering
const is = intersects.length > 0 ? intersects.length - 1 : 0;
// Is the object hovered over for the first time?
if (INTERSECTED === null) {
// Save current hovered object
INTERSECTED = intersects[is].object;
// HIGHLIGHT
// Save current color
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// Set highlight color
INTERSECTED.material.color.setHex(COLOR_HIGHLIGHT);
// SCALE UP
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1.5,
y: 1.5
},
100
);
scaleTween.start();
// SET Z-INDEX
INTERSECTED.position.z = 10;
} else {
// If the mouse is over an object
// Do we have a previous hovered item?
if (INTERSECTED !== null) {
// Revert color
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// SCALE DOWN
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1,
y: 1
},
100
);
scaleTween.start();
// REVERT Z-INDEX
INTERSECTED.position.z = 1;
}
// Save current intersected object
INTERSECTED = intersects[is].object;
// HIGHLIGHT
// Save current color
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// Set highlight color
INTERSECTED.material.color.setHex(COLOR_HIGHLIGHT);
// SCALE UP
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1.5,
y: 1.5
},
100
);
scaleTween.start();
// SET Z-INDEX
INTERSECTED.position.z = 10;
}
} else {
// If there are no intersections
// Reset cursor
document.body.style.cursor = 'default';
// Restore previous intersection object (if it exists) to its original color
if (INTERSECTED !== null) {
// REVERT COLOR
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// SCALE DOWN
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1,
y: 1
},
100
);
scaleTween.start();
// REVERT "Z-INDEX"
INTERSECTED.position.z = 1;
}
// Remove previous intersection object reference by setting current intersection object to "nothing"
INTERSECTED = null;
}
I've managed to highlight the object and scale it up with a tween quite nicely, but when I move the mouse out of the object onto the next object (the scaled object is scaled over the next object a bit), the highlight is gone, but the scale persists. How do I manage to scale the object down? And preferably with a tween?
A pen for this code can be found here: https://codepen.io/phun-ky/pen/erBZZy, the relevant part is at about line 1284 or search for INTERSECTED.
I wrote my own one. It's hell imperfect, but, at least, it scales up and down the hexagons:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x101010);
document.body.appendChild(renderer.domElement);
var hexes = [];
var colCount = 5;
var rowCount = 4;
var hexDiameter = 3;
var xStart = -(colCount) * hexDiameter * 0.5;
var rowSpace = Math.sqrt(3) * hexDiameter * 0.5;
var yStart = (rowCount - 1) * rowSpace * 0.5;
var hexGeom = new THREE.CylinderGeometry(hexDiameter * 0.5, hexDiameter * 0.5, 0.0625, 6, 1);
hexGeom.rotateX(Math.PI * 0.5);
for (let j = 0; j < rowCount; j++) {
for (let i = 0; i < colCount + (j % 2 === 0 ? 0 : 1); i++) {
let hex = new THREE.Mesh(hexGeom, new THREE.MeshBasicMaterial({
color: Math.random() * 0x7e7e7e + 0x7e7e7e,
wireframe: false
}));
hex.position.set(xStart + i * hexDiameter + (j % 2 === 0 ? 0.5 * hexDiameter : 0), yStart - j * rowSpace, 0);
hex.userData.scaleUp = function(h) {
if (h.userData.scaleDownTween) h.userData.scaleDownTween.stop();
let initScale = h.scale.clone();
let finalScale = new THREE.Vector3().setScalar(2);
h.userData.scaleUpTween = new TWEEN.Tween(initScale).to(finalScale, 500).onUpdate(function(obj) {
h.scale.copy(obj)
}).start();
}
hex.userData.scaleDown = function(h) {
if (h.userData.scaleUpTween) h.userData.scaleUpTween.stop();
let initScale = h.scale.clone();
let finalScale = new THREE.Vector3().setScalar(1);
h.userData.scaleUpTween = new TWEEN.Tween(initScale).to(finalScale, 500).onUpdate(function(obj) {
h.scale.copy(obj)
}).start();
}
scene.add(hex);
hexes.push(hex);
}
}
window.addEventListener("mousemove", onMouseMove, false);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects = [];
var intersected;
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObjects(hexes);
if (intersects.length > 0) {
if (intersected != intersects[0].object) {
if (intersected) intersected.userData.scaleDown(intersected);
intersected = intersects[0].object;
intersected.userData.scaleUp(intersected);
}
} else {
if (intersected) intersected.userData.scaleDown(intersected);
intersected = null;
}
}
render();
function render() {
requestAnimationFrame(render);
TWEEN.update();
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>

Object rotating while travelling along a curve path

I am creating a flight animation, I have objects (airplanes) moving along a curve path. My problem is some of my objects are rotating while travelling along the curve path.
Screenshot
function createPlane(image) {
return new THREE.Mesh(
new THREE.PlaneGeometry(5, 5),
new THREE.MeshPhongMaterial({ map: new THREE.TextureLoader().load('textures/planes/' + image), side: THREE.DoubleSide, transparent: true })
);
}
// this is how we get the line.vertices
// var curve = new THREE.Curve();
// line.vertices = curve.getPoints(50);
function animatePlane(plane, line, key) {
if (key > 1) {
key = 1;
} else {
key = Math.round(key * 100) / 100;
}
var curve = new THREE.CatmullRomCurve3(line.vertices), up = new THREE.Vector3(0, 1, 0), axis = new THREE.Vector3();
var angle, duration = 300, position = curve.getPointAt(key), tangent = curve.getTangentAt(key).normalize();
// rotation
axis.crossVectors(up, tangent).normalize();
angle = Math.acos(up.dot(tangent));
plane.quaternion.setFromAxisAngle(axis, angle);
// position
new TWEEN.Tween(plane.position)
.to({ x: position.x, y: position.y, z: position.z }, duration)
.onUpdate(function() {
plane.position.set(this.x, this.y, this.z);
})
.onComplete(function() {
if (key < 1) {
key += 0.02;
} else {
key = 0;
}
animatePlane(plane, line, key);
})
.start();
}
// lines
var point = latLongToVector3(14.512274, 121.016508, radius, 0), // Philippines
line1 = createCurveLine(createSphereArc(point, latLongToVector3(2.745364, 101.707079, radius, 0), 0.3), green, 'MALAYSIA');
animatePlane(createPlane('airplane.png'), line1, 0);
The above code is how I create and what moves my objects along the curve path.
Note that curve paths were dynamically created, from the screenshot above it is from Philippines going to other countries but it could be from other countries.
Curve paths may lie in different part of the earth, for example, from Australia (which is at the bottom part of the earth) going to other countries.
Is there anything I can do to always keep my airplane upright as it is moving along the curve path?
I got it working now by using Frenet–Serret formulas (TNB Frame).
I used Tangent, Normal, and Binormal to create a matrix (Matrix4) then used that matrix to set the rotation of planes by using .setRotationFromMatrix().
This is now the code to animate the planes:
function animatePlane(plane, line, key) {
if (key > 1) {
key = 1;
} else {
key = Math.round(key * 100) / 100;
}
var curve = new THREE.CatmullRomCurve3(line.vertices), matrix = new THREE.Matrix4();
var duration = 300, position = curve.getPointAt(key);
if ((key > 0) && (key < 1)) {
var D = curve.getPointAt(1).normalize(), X = plane.position.clone().normalize();
var T = position.clone().normalize(), B = curve.getTangentAt(key), N = new THREE.Vector3();
N.crossVectors(D, T).normalize();
matrix.set(
N.x, B.x, T.x, X.x,
N.y, B.y, T.y, X.y,
N.z, B.z, T.z, X.z,
0, 0, 0, 1
);
plane.setRotationFromMatrix(matrix);
}
new TWEEN.Tween(plane.position)
.to({ x: position.x, y: position.y, z: position.z }, duration)
.onUpdate(function() {
plane.position.set(this.x, this.y, this.z);
})
.onComplete(function() {
if (key < 1) {
key += 0.02;
} else {
key = 0;
}
animatePlane(plane, line, key);
})
.start();
}
and added this to make the plane above the curve path:
var plane1 = createPlane('airplane.png');
plane1.renderOrder = 1;
plane1.onBeforeRender = function(renderer) {
renderer.clearDepth();
}
animatePlane(plane1, line1, 0);

Why is OrbitControls not working as expected ? (Three.js)

I am using THREE.OrbitControls in my experimental project and have something very similar to this example.
I have different radio buttons at the top and I want to only enable THREE.OrbitControls if the rotate radio button is active.
I have replaced the code inside the if statement form the code Pen example:
function doMouseMove(x,y,evt,prevX,prevY) {
if (mouseAction == ROTATE) {
var dx = x - prevX;
world.rotateY( dx/200 );
render();
}
with:
function doMouseMove(x,y,evt,prevX,prevY) {
if (mouseAction == ROTATE) {
controls = new THREE.OrbitControls(camera, canvas);
controls.rotateSpeed = 0.1;
controls.zoomSpeed = 1;
controls.update();
}
This works perfectly, however once I go back from the rotate button to the drag button (or any other button), the OrbitControls is still active, and the camera moves with the object being dragged/added/removed.
This was not the case with the original example (as can be seen) and so I was wondering if I have to add further functionality to disable the OrbitControls.
I have tried:
controls.reset();
However, the orbitControls is still active even after the rotate radio button is not pressed!
I would like to add that the orbitControls is not active (as expected) when the page is reloaded on the drag button (or any other button). However once the rotate button has been pressed, it remains active throughout the session regardless of which input is pressed.
Any pointers on how I can solve this functionality?
The following is the full code outline from the example (excluding HTML file with references) of the code:
var canvas, scene, renderer, camera, controls;
var raycaster; // A THREE.Raycaster for user mouse input.
var ground; // A square base on which the cylinders stand.
var cylinder; // A cylinder that will be cloned to make the visible
cylinders.
var world;
var ROTATE = 1,
DRAG = 2,
ADD = 3,
DELETE = 4; // Possible mouse actions
var mouseAction; // currently selected mouse action
var dragItem; // the cylinder that is being dragged, during a drag operation
var intersects; //the objects intersected
var targetForDragging; // An invisible object that is used as the target for
raycasting while
// call functions to initialise trackballcontrols
init();
// animate();
function init() {
canvas = document.getElementById("maincanvas");
renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true
});
document.getElementById("mouseDrag").checked = true;
mouseAction = DRAG;
document.getElementById("mouseRotate").onchange = doChangeMouseAction;
document.getElementById("mouseDrag").onchange = doChangeMouseAction;
document.getElementById("mouseAdd").onchange = doChangeMouseAction;
document.getElementById("mouseDelete").onchange = doChangeMouseAction;
createWorld();
setUpMouseHander(canvas, doMouseDown, doMouseMove);
setUpTouchHander(canvas, doMouseDown, doMouseMove);
raycaster = new THREE.Raycaster();
render();
}
// loop that causes the renderer to draw the scene 60 times per second.
function render() {
renderer.render(scene, camera);
}
function createWorld() {
renderer.setClearColor(0x222222);
// First parameter is FOV in degrees. Second: Aspect ratio. Third/Fourth:
Near/Far clipping plane
camera = new THREE.PerspectiveCamera(37, canvas.width / canvas.height, 1,
10000);
camera.position.z = 5;
camera.position.y = 60;
/**Creating the scene */
scene = new THREE.Scene();
camera.lookAt(new THREE.Vector3(0, 1, 0));
camera.add(new THREE.PointLight(0xffffff, 0.7)); // point light at camera
position
scene.add(camera);
scene.add(new THREE.DirectionalLight(0xffffff, 0.5)); // light shining from
above.
world = new THREE.Object3D();
ground = new THREE.Mesh(
new THREE.BoxGeometry(40, 1, 40),
new THREE.MeshLambertMaterial({ color: "gray" })
);
ground.position.y = -0.5; // top of base lies in the plane y = -5;
world.add(ground);
targetForDragging = new THREE.Mesh(
new THREE.BoxGeometry(1000, 0.01, 1000),
new THREE.MeshBasicMaterial()
);
targetForDragging.material.visible = false;
cylinder = new THREE.Mesh(
new THREE.CylinderGeometry(1, 2, 6, 16, 32),
new THREE.MeshLambertMaterial({ color: "yellow" })
);
cylinder.position.y = 3; // places base at y = 0;
addCylinder(10, 10);
addCylinder(0, 15);
addCylinder(-15, -7);
addCylinder(-8, 5);
addCylinder(5, -12);
}
function addCylinder(x, z) {
var obj = cylinder.clone();
obj.position.x = x;
obj.position.z = z;
world.add(obj);
}
function doMouseDown(x, y) {
//enable rotate
if (mouseAction == ROTATE) {
return true;
}
if (mouseAction != ROTATE) {
controls = 0;
controls.enabled = false;
}
// Affecting drag function
if (targetForDragging.parent == world) {
world.remove(targetForDragging); // I don't want to check for hits on
targetForDragging
}
var a = 2 * x / canvas.width - 1;
var b = 1 - 2 * y / canvas.height;
raycaster.setFromCamera(new THREE.Vector2(a, b), camera);
intersects = raycaster.intersectObjects(world.children); // no need for
recusion since all objects are top-level
if (intersects.length == 0) {
return false;
}
var item = intersects[0];
var objectHit = item.object;
switch (mouseAction) {
case DRAG:
if (objectHit == ground) {
return false;
} else {
dragItem = objectHit;
world.add(targetForDragging);
targetForDragging.position.set(0, item.point.y, 0);
render();
return true;
}
case ADD:
if (objectHit == ground) {
var locationX = item.point.x; // Gives the point of intersection
in world coords
var locationZ = item.point.z;
var coords = new THREE.Vector3(locationX, 0, locationZ);
world.worldToLocal(coords); // to add cylider in correct
position, neew local coords for the world object
addCylinder(coords.x, coords.z);
render();
}
return false;
default: // DELETE
if (objectHit != ground) {
world.remove(objectHit);
render();
}
return false;
}
}
//this function is used when dragging OR rotating
function doMouseMove(x, y, evt, prevX, prevY) {
if (mouseAction == ROTATE) {
controls = new THREE.OrbitControls(camera, canvas);
controls.rotateSpeed = 0.1;
controls.zoomSpeed = 1;
controls.addEventListener('change', render, renderer.domElement);
controls.update();
} else { // drag
var a = 2 * x / canvas.width - 1;
var b = 1 - 2 * y / canvas.height;
raycaster.setFromCamera(new THREE.Vector2(a, b), camera);
intersects = raycaster.intersectObject(targetForDragging);
if (intersects.length == 0) {
return;
}
var locationX = intersects[0].point.x;
var locationZ = intersects[0].point.z;
var coords = new THREE.Vector3(locationX, 0, locationZ);
world.worldToLocal(coords);
a = Math.min(19, Math.max(-19, coords.x)); // clamp coords to the range
-19 to 19, so object stays on ground
b = Math.min(19, Math.max(-19, coords.z));
dragItem.position.set(a, 3, b);
render();
}
}
function doChangeMouseAction() {
if (document.getElementById("mouseRotate").checked) {
mouseAction = ROTATE;
} else if (document.getElementById("mouseDrag").checked) {
mouseAction = DRAG;
} else if (document.getElementById("mouseAdd").checked) {
mouseAction = ADD;
} else {
mouseAction = DELETE;
}
}
window.requestAnimationFrame =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
function(callback) {
setTimeout(function() {
callback(Date.now());
}, 1000 / 60);
};
function setUpMouseHander(element, mouseDownFunc, mouseDragFunc,
mouseUpFunc) {
if (!element || !mouseDownFunc || !(typeof mouseDownFunc == "function")) {
throw "Illegal arguments in setUpMouseHander";
}
if (typeof element == "string") {
element = document.getElementById(element);
}
if (!element || !element.addEventListener) {
throw "first argument in setUpMouseHander is not a valid element";
}
var dragging = false;
var startX, startY;
var prevX, prevY;
function doMouseDown(evt) {
if (dragging) {
return;
}
var r = element.getBoundingClientRect();
var x = evt.clientX - r.left;
var y = evt.clientY - r.top;
prevX = startX = x;
prevY = startY = y;
dragging = mouseDownFunc(x, y, evt);
if (dragging) {
document.addEventListener("mousemove", doMouseMove);
document.addEventListener("mouseup", doMouseUp);
}
}
function doMouseMove(evt) {
if (dragging) {
if (mouseDragFunc) {
var r = element.getBoundingClientRect();
var x = evt.clientX - r.left;
var y = evt.clientY - r.top;
mouseDragFunc(x, y, evt, prevX, prevY, startX, startY);
}
prevX = x;
prevY = y;
}
}
function doMouseUp(evt) {
if (dragging) {
document.removeEventListener("mousemove", doMouseMove);
document.removeEventListener("mouseup", doMouseUp);
if (mouseUpFunc) {
var r = element.getBoundingClientRect();
var x = evt.clientX - r.left;
var y = evt.clientY - r.top;
mouseUpFunc(x, y, evt, prevX, prevY, startX, startY);
}
dragging = false;
}
}
element.addEventListener("mousedown", doMouseDown);
}
function setUpTouchHander(element, touchStartFunc, touchMoveFunc, t
touchEndFunc, touchCancelFunc) {
if (!element || !touchStartFunc || !(typeof touchStartFunc == "function")) {
throw "Illegal arguments in setUpTouchHander";
}
if (typeof element == "string") {
element = document.getElementById(element);
}
if (!element || !element.addEventListener) {
throw "first argument in setUpTouchHander is not a valid element";
}
var dragging = false;
var startX, startY;
var prevX, prevY;
function doTouchStart(evt) {
if (evt.touches.length != 1) {
doTouchEnd(evt);
return;
}
evt.preventDefault();
if (dragging) {
doTouchEnd();
}
var r = element.getBoundingClientRect();
var x = evt.touches[0].clientX - r.left;
var y = evt.touches[0].clientY - r.top;
prevX = startX = x;
prevY = startY = y;
dragging = touchStartFunc(x, y, evt);
if (dragging) {
element.addEventListener("touchmove", doTouchMove);
element.addEventListener("touchend", doTouchEnd);
element.addEventListener("touchcancel", doTouchCancel);
}
}
function doTouchMove(evt) {
if (dragging) {
if (evt.touches.length != 1) {
doTouchEnd(evt);
return;
}
evt.preventDefault();
if (touchMoveFunc) {
var r = element.getBoundingClientRect();
var x = evt.touches[0].clientX - r.left;
var y = evt.touches[0].clientY - r.top;
touchMoveFunc(x, y, evt, prevX, prevY, startX, startY);
}
prevX = x;
prevY = y;
}
}
function doTouchCancel() {
if (touchCancelFunc) {
touchCancelFunc();
}
}
function doTouchEnd(evt) {
if (dragging) {
dragging = false;
element.removeEventListener("touchmove", doTouchMove);
element.removeEventListener("touchend", doTouchEnd);
element.removeEventListener("touchcancel", doTouchCancel);
if (touchEndFunc) {
touchEndFunc(evt, prevX, prevY, startX, startY);
}
}
}
element.addEventListener("touchstart", doTouchStart);
}
You can instantiate controls once:
controls = new THREE.OrbitControls(camera, canvas);
controls.enableZoom = false;
controls.enablePan = false;
controls.enableRotate = false;
and then just switch controls.enableRotate between true and false. For example, in the doChangeMouseAction() function. Creativity is up to you.

Threejs - How to pick all objects in area?

I'm using Three.js and I wonder how to get all objects in a given area?
For example, get all objects that found in the green-square:
Solution:
getEntitiesInSelection: function(x, z, width, height, inGroup) {
var self = this,
entitiesMap = [],
color = 0,
colors = [],
ids = [],
pickingGeometry = new THREE.Geometry(),
pickingMaterial = new THREE.MeshBasicMaterial( { vertexColors: THREE.VertexColors } ),
pickingScene = new THREE.Scene(),
pickingTexture = new THREE.WebGLRenderTarget( this._renderer.domElement.width, this._renderer.domElement.height),
cloneMesh,
entities = inGroup ?
engine.getObjectsByGroup(inGroup) : engine.getRegisteredEntities();
pickingTexture.generateMipmaps = false;
//Go over each entity, change its color into its ID
_.forEach(entities, function(entity) {
if(undefined == entity.threeRenderable) {
return ;
}
//Clone entity
cloneMesh = entity.threeRenderable.mesh().clone();
cloneMesh.material = entity.threeRenderable.mesh().material.clone();
cloneMesh.material.map = null;
cloneMesh.material.vertexColors = THREE.VertexColors;
cloneMesh.geometry = entity.threeRenderable.mesh().geometry.clone();
cloneMesh.position.copy( entity.threeRenderable.mesh().position );
cloneMesh.rotation.copy( entity.threeRenderable.mesh().rotation );
cloneMesh.scale.copy( entity.threeRenderable.mesh().scale );
//Cancel shadow
cloneMesh.castShadow = false;
cloneMesh.receiveShadow = false;
//Set color as entity ID
entitiesMap[color] = entity.id();
self._applyVertexColors(cloneMesh.geometry, new THREE.Color( color ) );
color++;
THREE.GeometryUtils.merge( pickingGeometry, cloneMesh);
});
pickingScene.add( new THREE.Mesh( pickingGeometry, pickingMaterial ) );
//render the picking scene off-screen
this._renderer.render(pickingScene, this._objs[this._mainCamera], pickingTexture );
var gl = this._renderer.getContext();
//read the pixel under the mouse from the texture
var pixelBuffer = new Uint8Array( 4 * width * height );
gl.readPixels( x, this._renderer.domElement.height - z, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixelBuffer );
//Convert RGB in the selected area back to color
for(var i=0; i<pixelBuffer.length; i+=4) {
if( 0 == pixelBuffer[i] && 0 == pixelBuffer[i+1] && 0 == pixelBuffer[i+2] && 0 == pixelBuffer[i+3] ) {
continue;
}
color = ( pixelBuffer[i] << 16 ) | ( pixelBuffer[i+1] << 8 ) | ( pixelBuffer[i+2] );
colors.push(color);
}
colors = _.unique(colors);
//Convert colors to ids
_.forEach(colors, function(color) {
ids.push(entitiesMap[color]);
});
return ids;
}
The line engine.getObjectsByGroup(inGroup) : engine.getRegisteredEntities();
just return an array of entities, which in turn, I iterate over the entities:
_.forEach(entities, function(entity) { ...
Only entities that have the 'threeRenderable' property (object) are visible, therefore, I ignore those that doesn't have it:
if(undefined == entity.threeRenderable) {
return ;
}
then I merge the entity's cloned mesh with with the pickingGeometry:
THREE.GeometryUtils.merge( pickingGeometry, cloneMesh);
eventually, I add the pickingGeometry to the pickingScene:
pickingScene.add( new THREE.Mesh( pickingGeometry, pickingMaterial ) );
Then I read the colors of the selected area, and return an array of IDs.
You can checkout the Node.js game engine I wrote back then.
I've wanted to implement something like this and I choose a very different method - maybe much worse, I don't really know - but much easier to do IMO, so I put it here in case someone wants it.
Basically, I used only 2 raycasts to know the first and last points of the selection rectangle, projected on my ground plane, and iterate over my objects to know which ones are in.
Some very basic code:
function onDocumentMouseDown(event) {
// usual Raycaster stuff ...
// get the ground intersection
var intersects = raycaster.intersectObject(ground);
GlobalGroundSelection = {
screen: { x: event.clientX, y: event.clientY },
ground: intersects[0].point
};
}
function onDocumentMouseUp(event) {
// ends a ground selection
if (GlobalGroundSelection) {
// usual Raycaster stuff ...
// get the ground intersection
var intersects = raycaster.intersectObjects(ground);
var selection = {
begins: GlobalGroundSelection.ground,
ends: intersects[0].point
};
GlobalGroundSelection = null;
selectCharactersInZone(selection.begins, selection.ends);
}
}
function onDocumentMouseMove(event) {
if (GlobalGroundSelection) {
// in a selection, draw a rectangle
var p1 = GlobalGroundSelection.screen,
p2 = { x: event.clientX, y: event.clientY };
/* with these coordinates
left: p1.x > p2.x ? p2.x : p1.x,
top: p1.y > p2.y ? p2.y : p1.y,
width: Math.abs(p1.x - p2.x),
height: Math.abs(p1.y - p2.y)
*/
}
}
Here is my select function:
function selectCharactersInZone (start, end) {
var selected = _.filter( SELECTABLE_OBJECTS , function(object) {
// warning: this ignore the Y elevation value
var itsin = object.position.x > start.x
&& object.position.z > start.z
&& object.position.x < end.x
&& object.position.z < end.z;
return itsin;
});
return selected;
}
Some warnings: as far as I know, this technique is only usable when you don't care about Y positions AND your selection is a basic rectangle.
My 2c

Resources