I am want to draw a plane that has an an array of points lying on it (including the origin). The three.js library draws the plane on the origin, facing the xy plane. Right now, I am having trouble of moving the it from the origin to a position such that it contains the points.
So far, I have managed to find a way to orient some planes that lie on the y-axis:
var directionalVectors = __getDirectionVectors(points);
var normal = __getNormalOfPlane(directionalVectors);
var angleXY = __getAngleBetweenPlanes( normal, new THREE.Vector3(0, 0, 1) );
plane.rotateY(- angleXY );
plane.translateY( planeDimensions.width /2.0);
plane.translateX( planeDimensions.height /2.0);
This is how I calculate the direction vectors:
var __getDirectionVectors = function( points ){
var numOfPoints = points.length, i;
var pointOne, pointTwo, directionalVectors = [], directionalVector;
for( i = 0; i < numOfPoints - 1; i++){
pointOne = points[i];
pointTwo = points[i + 1];
directionalVector = new THREE.Vector3().subVectors(pointOne, pointTwo);
directionalVectors.push(directionalVector);
}
return directionalVectors;
};
This is how I calculate the normal:
var __getNormalOfPlane = function(vectors){
var numOfVectors = vectors.length;
var vectorOne, vectorTwo, normal;
if( numOfVectors >= 2){
vectorOne = vectors[0];
vectorTwo = vectors[1];
normal = new THREE.Vector3().crossVectors(vectorOne, vectorTwo);
}
return normal;
};
This is how I calculate the angle between the plane and the XY plane:
//http://www.netcomuk.co.uk/~jenolive/vect14.html
var __getAngleBetweenPlanes = function( normalOne, normalTwo){
var dotPdt = normalOne.dot(normalTwo);
var angle = Math.acos( dotPdt / ( normalOne.length() * normalTwo.length() ) );
return angle;
}
Is there any way I could orient the plane properly for all types of planes?
Related
I'm running into an issue with my code to make a fillet shape because the code will seemingly randomly flip the coordinates of the intersection point for reasons I can't find. Even when it is using the same equations, and the same vector is found to be the shortest, just changing the waypoint location very slightly changes the intersection point massively. I've run through the code a bunch of times, and can't tell why this would happen. I can't post images without 10 reputation, but if you run my code and change only the x coordinate for waypoint1 from 250 to 150 it has been breaking. Any help would be greatly appreciated!
var straightpath1 = new Path();
var straightpath2 = new Path();
//User inputs waypoints
var waypoint1 = new Point(250,450);
var waypoint2 = new Point(300,300);
var waypoint3 = new Point(500,600);
//Creates vectors to better visualize waypoints
straightpath1.add(waypoint1);
straightpath1.add(waypoint2);
straightpath1.strokeColor = 'red';
straightpath2.add(waypoint2);
straightpath2.add(waypoint3);
straightpath2.strokeColor = 'red';
//Find waypoint locations relative to waypoint2
var veca = new Point(waypoint1.x-waypoint2.x,waypoint2.y-waypoint1.y);
var vecb = new Point(waypoint3.x-waypoint2.x,waypoint2.y-waypoint3.y);
//Uses a circle to find intersections on each path to have
//equal length vectors being added to find angle bisector
var minpathlength = Math.min(straightpath1.length,straightpath2.length);
var bisectcirc = new Path.Circle(waypoint2, minpathlength-0.1);
var intersections1 = straightpath1.getIntersections(bisectcirc);
var intersections2 = straightpath2.getIntersections(bisectcirc);
//Creates angle bisector that fillet circle will be placed on
var intervec1 = new Point(intersections1[0].point.x-waypoint2.x,waypoint2.y-intersections1[0].point.y);
var intervec2 = new Point(intersections2[0].point.x-waypoint2.x,waypoint2.y-intersections2[0].point.y);
var addvec = intervec1 + intervec2;
var addpoint = new Point(addvec.x, addvec.y);
//Calculates slope and b intercept for this added vector
var addslope = (addpoint.y/addpoint.x);
var addbintercept = addpoint.y - (addslope*addpoint.x);
if(minpathlength == straightpath1.length){
//Finds equation for tangent line from shortest path that
//will intersect with the angle bisector
var slope = (veca.y/veca.x);
var bintercept = veca.y - (slope*veca.x);
var tangentslope = -(veca.x/veca.y);
var tangentbintercept = veca.y - (tangentslope*veca.x);
} else {
//This code runs if waypoint2 to waypoint3 is a shorter distance
var slope = (vecb.y/vecb.x);
var bintercept = vecb.y - (slope*vecb.x);
var tangentslope = -(vecb.x/vecb.y);
var tangentbintercept = vecb.y - (tangentslope*vecb.x);
}
//Finds point of intersection between the angle bisector line
//and the tangent line
if(addslope > 0){
var intersectslope = tangentslope - addslope;
} else {
var intersectslope = tangentslope + addslope;
}
if(tangentbintercept > 0){
var intersectbintercept = addbintercept - tangentbintercept;
} else {
var intersectbintercept = addbintercept + tangentbintercept;
}
//Finds x and y coordinate of the point of intersection
var intersectx = intersectbintercept/intersectslope;
var intersecty = (addslope*intersectx) + addbintercept;
//Draws circle to visualize intersection point relative to waypoint2
var intersect = new Point(intersectx+waypoint2.x, waypoint2.y-intersecty);
var intersectcirc = new Path.Circle(intersect, 5);
intersectcirc.fillColor = 'black';
//Finds dot product and cross product of original vectors to
//find a value for the angle of the vectors
var dotprod = veca.x*vecb.x+veca.y*vecb.y;
var crossprod = veca.x*vecb.y-veca.y*vecb.x;
var angle = Math.atan2(crossprod, dotprod);
//Path from waypoint2 to intersection to better visualize
var midpath = new Path();
midpath.add(waypoint2);
midpath.add(intersect);
midpath.strokeColor = 'blue';
//Bendradius is a user input to determine where the fillet
//will be located along the two vectors
var bendradius = midpath.length;
//Sets center point for circle
var filletposition = midpath.getPointAt(bendradius)
//Finds dynamic radius for circle that creates the fillet
//based on the bendradius and calculated angle
var h = (bendradius*Math.sin(angle/2));
var absh = Math.abs(h);
absh = absh + 0.2;
//Draws circle used for fillet
var fillet = new Path.Circle(filletposition,absh);
fillet.strokeColor = 'blue'
//Finds intersections between the drawn circle and the original paths
var intersections4 = straightpath1.getIntersections(fillet);
var intersections5 = straightpath2.getIntersections(fillet);
var intersections6 = midpath.getIntersections(fillet);
//Draws final fillet in three parts: waypoint1 to first intersection,
//arc of circle, intersection to waypoint3
var final1 = new Path();
final1.add(waypoint1);
final1.add(intersections4[0].point);
final1.strokeColor = 'black';
var final2 = new Path.Arc(intersections4[0].point, intersections6[0].point, intersections5[0].point);
final2.strokeColor = 'black';
var final3 = new Path();
final3.add(waypoint3);
final3.add(intersections5[0].point);
final3.strokeColor = 'black';
I have made a collision detection. You can place objects at the raycaster/mouse position on a floor. Therefore you need to click on a button 'Add object', then you get a object(helper) that follows the mouse to see if the new object get a collision with another object. When you click on the position you want, the new object will be placed to the world if there is no collision.
The collision detection I have made works perfectly when the object that is already placed in the world has the same size as the helper/new object.
On the next screenshot you can see a big object and a small(red) helper. The color red means that there is a collision. When I move the mouse more to the right it turns green.
Why does my collision detection work only with 2 objects that have the same size and why doesn't it not with different ones?
Here is my code in the Click event to show the big object:
var geometry = new THREE.BoxGeometry(200, 200, 300);
var bigobject = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
color: 0xFBF5D7,
opacity: 1
}));
bigobject.position.copy(intersects[0].point);
bigobject.position.y = 100;
objects.push(bigobject);
scene.add(bigobject);
Here is my code to show the helper when the button 'Add object' is clicked:
var geometry = new THREE.BoxGeometry( 50, 50, 100 );
helper = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: 0x00ff00, opacity: 1 } ) );
helper.name = 'helper';
scene.add( helper );
Here is my code in the MouseMove event to detect the collision:
if(scene.getObjectByName( 'helper' )) {
helper.position.copy( intersects[ 0 ].point );
helper.position.y = 25;
var helperWidth = helper.geometry.parameters.width;
var helperLength = helper.geometry.parameters.depth;
var validpositionObject = true;
for (var i = 0; i < objects.length; i++) {
var objectWidth = objects[i].geometry.parameters.width;
var objectLength = objects[i].geometry.parameters.depth;
// MIN X
var helperMinX = helper.position.x;
var objectMinX = objects[i].position.x;
// MAX X
var helperMaxX = helperWidth + helper.position.x;
var objectMaxX = objectWidth + objects[i].position.x;
// MIN Z
var helperMinZ = helper.position.z;
var objectMinZ = objects[i].position.z;
// MAX Z
var helperMaxZ = helperLength + helper.position.z;
var objectMaxZ = objectLength + objects[i].position.z;
if (objectMinX <= helperMaxX && objectMaxX >= helperMinX && objectMinZ <= helperMaxZ && objectMaxZ >= helperMinZ) {
validpositionObject = false;
}
}
if ( validpositionObject === true ) {
helper.material.color.setHex( 0x00ff00 );
validposition = true;
}else{
helper.material.color.setHex( 0xff0000 );
validposition = false;
}
}
What goes wrong with the position when it is a big and a small object.. Can anyone help me in the right direction? Many thanks
Use this:
function collisonXZ(o1, o2) {
if (Math.abs(o1.position.x - o2.position.x) > (o1.geometry.parameters.width + o2.geometry.parameters.width) / 2)
return false;
if (Math.abs(o1.position.z - o2.position.z) > (o1.geometry.parameters.depth + o2.geometry.parameters.depth) / 2)
return false;
return true;
}
var validpositionObject = true;
for (var i = 0; i < objects.length; i++) {
if (collisionXZ(helper, objects[i])) {
validpositionObject = false;
break;
}
}
I want to extrude faces from a THREE.Geometry object, so my approach was to:
- specify the faces to extrude
- extract vertices on the outer edges
- Draw a THREE.Shape with those vertices
- Extrude it using THREE.ExtrudeGeometry (I'm actually using a modified version which is not generating front faces, as I'm cloning them by myself to keep original topology).
It seems to work having a CircleGeometry on a XY plane, so extruding on Z direction.
but if I rotate the circle a bit this is what I get. It extrudes on Z World Axes, not following its local orientation
I can I solve this? Here's the function I'm using:
function faceExtrude( geometry, face_group, amount ) {
if ( geometry.type === "undefined" ) {
console.log( "faceextrude error" );
return;
}
//convert face_group to vertex_group
var vertex_group = [];
for ( var i = 0; i < face_group.length; i++ ) {
//get vertices ID
var f = face_group[i];
var a = f.a;
var b = f.b;
var c = f.c;
//clone vertices
var v1 = geometry.vertices[a].clone();
var v2 = geometry.vertices[b].clone();
var v3 = geometry.vertices[c].clone();
//add them to an array
vertex_group.push(v1, v2, v3);
}
// Side Faces
var vertsToExtrude = [];
//Get only verts in outer edge
for ( var v = 0; v < vertex_group.length; v++ ) {
var shareCount = 0;
//count their presence in each face
for ( var f = 0; f < face_group.length; f++ ) {
if( v == face_group[ f ].a ||
v == face_group[ f ].b ||
v == face_group[ f ].c )
{
shareCount++;
}
}
//if < 4 than it's in an outer edge. Add it to an array
if ( shareCount < 4) {
vertsToExtrude.push( vertex_group[ v ].clone() );
}
}
//make a shape with them
var shape = new THREE.Shape();
shape.moveTo( vertsToExtrude[ 0 ].x, vertsToExtrude[ 0 ].y );
for ( var i = 1; i < vertsToExtrude.length; i++ ){
shape.lineTo( vertsToExtrude[ i ].x, vertsToExtrude[ i ].y );
}
shape.lineTo( vertsToExtrude[ 0 ].x, vertsToExtrude[ 0 ].y );
//extrude it
var extrudeSettings = { amount: amount, steps: 2, bevelEnabled: false };
var sideGeo = new THREE.MyExtrudeGeometry( shape, extrudeSettings );
//Front Faces
var frontGeo = new THREE.Geometry();
for ( var i = 0; i < face_group.length; i++ ) {
//get vertices ID
var f = face_group[i];
var a = f.a;
var b = f.b;
var c = f.c;
//new vertices IDs
var new_a = frontGeo.vertices.length;
var new_b = frontGeo.vertices.length + 1;
var new_c = frontGeo.vertices.length + 2;
//clone vertices
var v1 = geometry.vertices[a].clone();
var v2 = geometry.vertices[b].clone();
var v3 = geometry.vertices[c].clone();
frontGeo.vertices.push(v1, v2, v3);
//translate them depending on "amount"
v1.z = v1.z + amount;
v2.z = v2.z + amount;
v3.z = v3.z + amount;
//add them to a face
var f = new THREE.Face3(new_a, new_b, new_c);
frontGeo.faces.push(f);
}
frontGeo.computeFaceNormals();
geometry.merge( sideGeo );
geometry.merge( frontGeo );
};
I'm blind. Missed this while reading docs about ExtrudeGeometry:
"extrudePath — THREE.CurvePath. 3d spline path to extrude shape along. (creates Frames if (frames aren't defined)"
Think this is the solution I'm looking for :)
As quads faces are no longer supported in three.js. I am having trouble Raycast picking segments between 4 vertices points as a square as they are now made up of triangle faces.
So my picking returns triangle shape not square:
var faces = 10;
var myplane = new THREE.Mesh(
new THREE.PlaneGeometry(1000, 1000, faces, faces),
new THREE.MeshNormalMaterial({
color: 0x993333,
wireframe: false
}));
objects.push(myplane);
scene.add(myplane);
//raycast code
if ( intersects.length > 0 ) {
_controls.noRotate = true;
var face = intersects[0].face;
var temp = intersects[0].object;
var geo = temp.geometry;
geo.vertices[face.a].z -= 20;
geo.vertices[face.b].z -= 20;
geo.vertices[face.c].z -= 20;
//want other face verts below basically
geo.vertices[face.d].z -= 20;
geo.vertices[face.e].z -= 20;
geo.vertices[face.f].z -= 20;
geo.verticesNeedUpdate = true;
if(intersects[ 0 ].faceIndex % 2 == 0){
// geo.vertices[intersects[0].faceIndex + 1].z = 40;
// console.log("Even face Index: " + intersects[ 0 ].faceIndex);
}else{
// console.log("Odd face Index: " + intersects[ 0 ].faceIndex -1);
//geo.vertices[intersects[0].faceIndex - 1].z = 40, of course doest work
}
}
Below is a 2d example for a better vsual...
Currently I get the red out-come , but I'm wanting to pick like the green example:
try this code to get intersection faces
if ( intersects.length > 0 )
{
var faceIndex = intersects[0].faceIndex;
var obj = intersects[0].object;
var geom = obj.geometry;
var faces = obj.geometry.faces;
var facesIndices = ["a","b","c"];
facesIndices.forEach(function(indices){
geom.vertices[faces[faceIndex][indices]].setZ(-10);
});
if(faceIndex%2 == 0){
faceIndex = faceIndex+1;
}else{
faceIndex = faceIndex-1;
}
facesIndices.forEach(function(indices){
geom.vertices[faces[faceIndex][indices]].setZ(-10);
});
geom.verticesNeedUpdate = true;
}
In the general case, getting and changing the geometry of a raycast to mesh triangle intersection is just:
var face = intersects[0].face;
var obj = intersects[0].object;
var geom = obj.geometry;
var vertA = geom.vertices[face.a];
var vertB = geom.vertices[face.b];
var vertC = geom.vertices[face.c];
vertA.setY(100);
vertB.setY(110);
vertC.setY(105);
geom.verticesNeedUpdate = true;
No need to iterate through the faces array, or build a contrived face vertex array just to loop through it in the next step.
I'm using the Google Maps API to embed a map in a web page. The map fills the entire screen, but there's ~400px worth of content on the left side of the screen that mostly covers the map. When I want to pan the map, that area to the left should be treated as though it isn't visible.
I came up with the following code to calculate the "usable part" of the map, which I'd like to be 50px in from the map's edge, and should also avoid the 400px area to the left side of the map:
panIfNotClose = function(latLngs){
if(latLngs.length === 0)
return;
// Get some important values that may not be available yet
// http://stackoverflow.com/a/2833015/802335
var bounds = map.getBounds();
if(!bounds)
return setTimeout(panIfNotClose.bind(this, latLngs), 10);
var overlay = new google.maps.OverlayView();
overlay.draw = function(){};
overlay.setMap(map);
var proj = overlay.getProjection();
if(!proj)
return setTimeout(panIfNotClose.bind(this, latLngs), 10);
// Calculate the "usable part" of the map
var center = bounds.getCenter();
var northEast = bounds.getNorthEast();
var southWest = bounds.getSouthWest();
var northEastPx = proj.fromLatLngToContainerPixel(northEast);
var southWestPx = proj.fromLatLngToContainerPixel(southWest);
var menuPadding = 400;
var newNorthEastPx = new google.maps.Point(northEastPx.x + 50, northEastPx.y + 50);
var newSouthWestPx = new google.maps.Point(southWestPx.x - (50 + menuPadding), southWestPx.y - 50);
var newNorthEast = proj.fromContainerPixelToLatLng(newNorthEastPx);
var newSouthWest = proj.fromContainerPixelToLatLng(newSouthWestPx);
var centerBounds = new google.maps.LatLngBounds(newSouthWest, newSouthWest);
// Decide if any of the new LatLngs are far enough away that the map should move
var shouldMove = false;
var targetBounds = new google.maps.LatLngBounds();
for(var i = 0; i < latLngs.length; i++){
targetBounds.extend(latLngs[i]);
if(!centerBounds.contains(latLngs[i]))
shouldMove = true;
}
// If the LatLngs aren't all near the center of the map, pan to it
if(latLngs.length === 1){
if(shouldMove || map.getZoom() !== 18)
map.panTo(latLngs[0]);
map.setZoom(18);
}else{
var targetZoom = Math.min(getBoundsZoomLevel(targetBounds), 18);
if(shouldMove || map.getZoom() !== targetZoom)
map.panTo(targetBounds.getCenter());
map.setZoom(targetZoom);
}
}
This code should test the "valid" area to make sure all the given LatLngs fit inside, but it doesn't yet make any changes to panTo to "move" the center 200px to the right to account for the 400px worth of content on the left.
The code doesn't work as I intended, but I'm not sure why. I suspect I'm probably doing something wrong when converting from a LatLng to a Point or vice-versa. I may also be doing far more work than is necessary, although I can't think of a way to simplify it.
This turned out to be a pretty simple mistake. In the line var centerBounds = new google.maps.LatLngBounds(newSouthWest, newSouthWest);, I accidentally used the same LatLng twice, rather than the northeast and southwest corners. I also had a couple simple arithmetic issues when calculating newNorthEastPx and newSouthWestPx. Drawing a google.maps.Rectangle using centerBounds helped work that out quickly and easily. For anyone interested, here's the end result:
function panIfNotClose(latLngs, zoomOnly){
if(latLngs.length === 0)
return;
if(typeof latLngs !== "object")
latLngs = [latLngs];
// Calculate the "middle half" of the map
var bounds = map.getBounds();
if(!bounds) // http://stackoverflow.com/a/2833015/802335
return setTimeout(panIfNotClose.bind(this, latLngs, zoomOnly), 10);
var overlay = new google.maps.OverlayView();
overlay.draw = function(){};
overlay.setMap(map);
var proj = overlay.getProjection();
if(!proj)
return setTimeout(panIfNotClose.bind(this, latLngs, zoomOnly), 10);
var center = bounds.getCenter();
var northEast = bounds.getNorthEast();
var southWest = bounds.getSouthWest();
var northEastPx = proj.fromLatLngToContainerPixel(northEast);
var southWestPx = proj.fromLatLngToContainerPixel(southWest);
var menuPadding = 0;
if($navBar.css("display") !== "none")
menuPadding = 400;
var newNorthEastPx = new google.maps.Point(northEastPx.x - 100, northEastPx.y + 100);
var newSouthWestPx = new google.maps.Point(southWestPx.x + (100 + menuPadding), southWestPx.y - 100);
var newNorthEast = proj.fromContainerPixelToLatLng(newNorthEastPx);
var newSouthWest = proj.fromContainerPixelToLatLng(newSouthWestPx);
centerBounds = new google.maps.LatLngBounds(newSouthWest, newNorthEast);
var shouldMove = false;
var targetBounds = new google.maps.LatLngBounds();
for(var i = 0; i < latLngs.length; i++){
targetBounds.extend(latLngs[i]);
if(!centerBounds.contains(latLngs[i]))
shouldMove = true;
}
// If the marker isn't near the center of the map, pan to it
if(latLngs.length === 1){
if(!zoomOnly && (shouldMove || map.getZoom() !== 18))
map.panTo(correctCenter(latLngs[0], proj));
map.setZoom(18);
}else{
var targetZoom = Math.min(getBoundsZoomLevel(targetBounds), 18);
if(!zoomOnly && (shouldMove || map.getZoom() !== targetZoom))
map.panTo(correctCenter(targetBounds.getCenter(), proj));
map.setZoom(targetZoom);
}
}
function correctCenter(latLng, proj){
// $navBar references a jQuery pointer to a DOM element
if($navBar.css("display") === "none")
return latLng;
var latLngPx = proj.fromLatLngToContainerPixel(latLng);
var newLatLngPx = new google.maps.Point(latLngPx.x - 200, latLngPx.y)
return proj.fromContainerPixelToLatLng(newLatLngPx);
}
// Adapted from http://stackoverflow.com/a/13274361/802335
function getBoundsZoomLevel(bounds){
// $mapCanvas is a jQuery reference to the div containing the Google Map
var mapDim = {
width: $mapCanvas.width() * .6,
height: $mapCanvas.height() * .6
};
var ZOOM_MAX = 18;
function latRad(lat){
var sin = Math.sin(lat * Math.PI / 180);
var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
}
function zoom(mapPx, fraction){
return Math.floor(Math.log(mapPx / 256 / fraction) / Math.LN2);
}
var ne = bounds.getNorthEast();
var sw = bounds.getSouthWest();
var latFraction = (latRad(ne.lat()) - latRad(sw.lat())) / Math.PI;
var lngDiff = ne.lng() - sw.lng();
var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
var latZoom = zoom(mapDim.height, latFraction);
var lngZoom = zoom(mapDim.width, lngFraction);
return Math.min(latZoom, lngZoom, ZOOM_MAX);
}