Following my post last week three.js How to programatically produce a plane from dataset I come back to the community to solve a problem of definition of surface occupied on the ground by a 3D building.
The solution proposed in comments in this post works for this building but is not universal.
To make it universal I chose the following method: when the walls are built I create their clone in another group (see this previous post for walls creation)
// prepare the clones
var clones = new THREE.Group();
scene.add(clones);
var num=0;
// drawing the real walls
var wallGeometry = new THREE.PlaneGeometry(size,(hstair*batims[i][1]));
val = 0xFFFFFF;
opa = 0.5;
if(deltaX > deltaY){val = 0x000000; opa = 0.05;} // shaded wall
var wallMaterial = new THREE.MeshBasicMaterial({color:val,transparent:true, opacity:opa, side:THREE.DoubleSide});
var walls = new THREE.Mesh(wallGeometry, wallMaterial);
walls.position.set((startleft+endleft)/2,(hstair*batims[i][1])/2,(startop+endtop)/2);
walls.rotation.y = -rads;
scene.add(walls);
// add the pseudo-walls to scene
var cloneGeometry=new THREE.PlaneGeometry(long,3);
var cloneMaterial=new THREE.MeshBasicMaterial({color:0xff0000,transparent:true,opacity:0.5,side:THREE.DoubleSide});
var clone=new THREE.Mesh(pseudomursGeometry,pseudomursMaterial);
clone.position.set((startleft+endleft)/2,3,(startop+endtop)/2);
clone.rotation.y=-rads;
clones.add(clone);
num++;
The idea is now to rotate this pseudo-building so that the longest wall is vertical, which allows me to determine the exact floor area occupied with its boundingBox:
var angle=turn=0;
for(i=0; i<dists.length; i++) { // dists is the array of wall lengths
if(dists[i]==longs[0]){ // longs is the reordered lengths array
angle=angles[i][1]; // angle of the longest wall
}
}
// we can now rotate the whole group to put the longest wall vertical
if(angle>0){
turn = angle*-1+(Math.PI/2);
}
else {
turn = angle+(Math.PI/2);
}
clones.rotation.y=turn;
It works perfectly as long as the building has a right angle, whatever its shape: triangle, rectangle, bevel, right angle polygons,
var boundingBox = new THREE.Box3().setFromObject(clones);
var thisarea = boundingBox.getSize();
// area size gives the expected result
console.log('AREA SIZE = '+thisarea.x+' '+thisarea.y+' '+thisarea.z);
...but not when there are no more right angles, for example a trapezoid
The reason is that we rotate the group, and not the cloned walls. I can access and rotate each wall by
for(n=0;n<num;n++){
thisangle = clones.children[n].rotation.y;
clones.children[n].rotation.y = turn-thisangle;
}
But the result is wrong for the others pseudo-walls:
So the question is: how to turn each red pseudo-wall so that the longest one is vertical and the others remain correctly positioned in relation to it? In this way, any building with any shape can be reproduced in 3D with its internal equipment. Any idea on how to achieve this result?
A weird & ugly but well-working solution:
// 1. determines which is the longest side
for(i=0; i<dists.length; i++) {
if(dists[i]==longs[0]){
longest=i;
break; // avoid 2 values if rectangle
}
}
// 2. the group is rotated until the longest side has an angle in degrees
// close to 0 or 180
var letsturn = setInterval(function() {
clones.rotation.y += 0.01;
var group_rotation = THREE.Math.radToDeg(clones.rotation.y); // degrees
var stop = Math.round(angles[longest][0] - group_rotation);
// 3. stop when longest wall is vertical
if( (stop>=179 && stop<=181) || (stop>=-1 && stop<=1) ) {
clearInterval(letsturn);
createPlane() // we can now use boundingBox in reliability
}
}, 1);
et voilĂ .
Related
I'm trying to implement "bullet and target collision" problem and create an explosion when collision occurs. I managed to do it using aframe-physics-system which was working good: the explosion was rendering at the exact point of the collision and in the exact time. Now I decided to get rid of the physics system as I don't need such overhead - my only goal is to render an explosion.
I tried to use box.containsPoint as well as Raycaster:
tick(time: number, delta: number): void {
// bullet-component
// ...
// Update speed based on acceleration
this.speed = this.currentAcceleration * .01 * delta;
if (this.speed > this.data.maxSpeed) {
this.speed = this.data.maxSpeed;
}
// there is an initial position and direction set in data property.
const newBulletPosition = this.position.add(this.direction.multiplyScalar(this.speed));
// targets is an array of boxes
const found = this._detectCollision(newBulletPosition, this.targets);
if (found) {
console.log("found!");
this.resetBullet();
this.el.emit("collide", {
coordinates: newBulletPosition//found
});
return;
}
this.el.object3D.position.set(newBulletPosition.x, newBulletPosition.y, newBulletPosition.z);
},
_detectCollision(point: THREE.Vector3, obj: THREE.Object3D[]): THREE.Vector3 | null {
const ray = new THREE.Raycaster(point,
this.temps.direction.clone().multiplyScalar(-1).normalize());
const intersects = ray.intersectObjects(obj, true);
return intersects.length % 2 === 1 ? intersects[0].point : null;
},
_box: new THREE.Box3(),
_inverseWorldMatrix: new THREE.Matrix4(),
_detectCollision2(point: THREE.Vector3, obj: THREE.Object3D): THREE.Vector3 | null {
obj.updateMatrixWorld(true);
this._inverseWorldMatrix.copy(obj.matrix).invert();
this._box.setFromObject(obj);
this._inverseBulletPosition.set(point.x, point.y, point.z);
this._inverseBulletPosition.applyMatrix4(this._inverseWorldMatrix);
return this._box.containsPoint(this._inverseBulletPosition);
}
But both approaches have the following flaw:
On frame X the bullet is just in front of a box, but in frame X+1 it is already behind this box. For some reason in this case there might be desirable intersections, but the last bullet position is different than the intersection. Which causes the explosion to be rendered in a wrong position. So, the second approach works only if bullet during it's "jumps" appears inside of a box which is far from being frequent.
The question is how in this case I can repeat the behaviour I had with physics system:
Bullet is moving relatively fast
The intersection is being detected instantly once a bullet crosses any face of a box, so there is no "jump" in bullet's movement.
Thanks in advance.
This is a common problem when trying to recreate the calculations of a physics engine. Since your bullet is too small and sometimes travels beyond the wall in between frames, I see two options:
On frame x+1 you could calculate how much distance has been traveled since frame x, and use that as the size of the bullet. If the plane is crossed in the distance travelled between x -> x1, then you know you've had a collision.
If collision points don't move, you could use a THREE.Raycaster and calculate the point of collision pre-emptively, so you'll know where the bullet will hit before that point is reached:
const raycaster = new THREE.Raycaster();
shoot() {
raycaster.set(origin, direction);
const intersects = raycaster.intersectObjects(arrayOfWalls);
// No intersection took place
if (intersects[0] == undefined) return;
// How far away from origin the collision takes place.
intersects[0].distance;
// The Vector3 where the bullet crosses the wall
intersects[0].point;
}
You can read more about Raycasters in the docs.
Thanks to #Marquizzo, I ended up with the following solution:
I'm casting a ray from the bullet position to the position of the gun. If there is 1 intersection, then the bullet is inside of the box, so I can render an explosion at the intersection position. But if there are two intersections I will take the second one as it will be more far from the ray origin point and hence closer to the gun. But also I had to calculate the distance between the bullet position and the intersection which as was advised should be less than the distance bullet passed between the frames:
tick(time: number, delta: number): void {
const el = this.el;
if (!el) {
console.warn("AFRAME entity is undefined.");
return;
}
this.el.object3D.lookAt(this.direction.clone().multiplyScalar(1000));
// Update acceleration based on the friction
this.temps.position.copy(this.el.object3D.position);
// Update speed based on acceleration
this.speed = this.currentAcceleration * 0.05 * delta;
if (this.speed > this.data.maxSpeed) {
this.speed = this.data.maxSpeed;
}
// Set new position
this.temps.direction.copy(this.direction);
const newBulletPosition = this.temps.position.add(this.temps.direction.multiplyScalar(this.speed));
if (newBulletPosition.length() >= FADE_DISTANCE) {
this.resetBullet();
return;
}
const found = this._detectCollision(newBulletPosition, this.targetCollisionShapes);
if (found) {
const jumpDistance = newBulletPosition.clone().sub(this.el.object3D.position).length();
const collisionDistance = newBulletPosition.clone().sub(found).length();
if (collisionDistance < jumpDistance) {
console.log("found!");
this.resetBullet();
this.el.emit("collide", {
target: this.target,
coordinates: found
} as CollisionEvent);
return;
}
this.el.object3D.position.set(newBulletPosition.x, newBulletPosition.y, newBulletPosition.z);
},
_detectCollision(point: THREE.Vector3, obj: THREE.Object3D[]): THREE.Vector3 | null {
const ray = new THREE.Raycaster(point, this.direction.clone().multiplyScalar(-1).normalize());
const intersects = ray.intersectObjects(obj, true);
return intersects.length % 2 === 1
? intersects[0].point
: intersects.length > 1 ? intersects[1].point : null;
}
I'm new to threejs
I need to draw a sphere connected with triangles. I use Icosahedron to construct the sphere in the following way
var material = new THREE.MeshPhongMaterial({
emissive : 0xffffff,
transparent: true,
opacity : 0.5,
wireframe : true
});
var icogeo = new THREE.IcosahedronGeometry(80,2);
var mesh = new THREE.Mesh(icogeo, material);
scean.add(mesh);
But i need the width of the line to be more but line width won't show up in windows so i taught of looping through the vertices and draw a cylinder/tube between the vertices. (I can't draw lines because the LineBasicMaterial was not responding to Light.)
for(i=0;i<icogeo.faces.length;i++){
var face = icogeo.faces[i];
//get vertices from face and draw cylinder/tube between the three vertices
}
Can some one please help on drawing the tube/cylinder between two vector3 vertices?
**the problem i'm facing with wireframe was it was not smooth and i can't increase width of it in windows.
If you really want to create a cylinder between two points one way to do is to create it in a unit space and then transform it to your line. But that is very mathy.
An intuitive way to create it is to think about how would you do it in a unit space? A circle around the z axis (in x,y) and another one a bit down z.
Creating a circle in 2d is easy: for ( angle(0,360,360/numsteps) ) (x,y)=(sin(angle),cos(angle))*radius. (see for example Calculating the position of points in a circle).
Now the two butt ends of your cylinder are not in x,y! But If you have two vectors dx,dy you can just multiply your x,y with them and get a 3d position!
So how to get dx, dy? One way is http://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
which reads way more scary than it is. You start with your forward direction, which is your line. forward = normalize(end-start). Then you just pick a direction "up". Usually (0,1,0). Unless forward is already close to up, then pick another one like (1,0,0). Take their cross product. This gives you "left". Then take the cross product between "left" and "forward" to get "right". Now "left" and "right" are you dx and dy!
That way you can make two circles at the two ends of your line. Add triangles in between and you have a cylinder!
Even though I do believe it is an overkill for what you are trying to achieve, here is code that draws a capsule (cylinder with spheres at the end) between two endpoints.
/**
* Returns a THREE.Object3D cylinder and spheres going from top to bottom positions
* #param radius - the radius of the capsule's cylinder
* #param top, bottom - THREE.Vector3, top and bottom positions of cone
* #param radiusSegments - tessellation around equator
* #param openTop, openBottom - whether the end is given a sphere; true means they are not
* #param material - THREE.Material
*/
function createCapsule (radius, top, bottom, radiusSegments, openTop, openBottom, material)
{
radiusSegments = (radiusSegments === undefined) ? 32 : radiusSegments;
openTop = (openTop === undefined) ? false : openTop;
openBottom = (openBottom === undefined) ? false : openBottom;
var capsule = new THREE.Object3D();
var cylinderAxis = new THREE.Vector3();
cylinderAxis.subVectors (top, bottom); // get cylinder height
var cylinderGeom = new THREE.CylinderGeometry (radius, radius, cylinderAxis.length(), radiusSegments, 1, true); // open-ended
var cylinderMesh = new THREE.Mesh (cylinderGeom, material);
// get cylinder center for translation
var center = new THREE.Vector3();
center.addVectors (top, bottom);
center.divideScalar (2.0);
// pass in the cylinder itself, its desired axis, and the place to move the center.
makeLengthAngleAxisTransform (cylinderMesh, cylinderAxis, center);
capsule.add (cylinderMesh);
if (! openTop || ! openBottom)
{
// instance geometry
var hemisphGeom = new THREE.SphereGeometry (radius, radiusSegments, radiusSegments/2, 0, 2*Math.PI, 0, Math.PI/2);
// make a cap instance of hemisphGeom around 'center', looking into some 'direction'
var makeHemiCapMesh = function (direction, center)
{
var cap = new THREE.Mesh (hemisphGeom, material);
makeLengthAngleAxisTransform (cap, direction, center);
return cap;
};
// ================================================================================
if (! openTop)
capsule.add (makeHemiCapMesh (cylinderAxis, top));
// reverse the axis so that the hemiCaps would look the other way
cylinderAxis.negate();
if (! openBottom)
capsule.add (makeHemiCapMesh (cylinderAxis, bottom));
}
return capsule;
}
// Transform object to align with given axis and then move to center
function makeLengthAngleAxisTransform (obj, align_axis, center)
{
obj.matrixAutoUpdate = false;
// From left to right using frames: translate, then rotate; TR.
// So translate is first.
obj.matrix.makeTranslation (center.x, center.y, center.z);
// take cross product of axis and up vector to get axis of rotation
var yAxis = new THREE.Vector3 (0, 1, 0);
// Needed later for dot product, just do it now;
var axis = new THREE.Vector3();
axis.copy (align_axis);
axis.normalize();
var rotationAxis = new THREE.Vector3();
rotationAxis.crossVectors (axis, yAxis);
if (rotationAxis.length() < 0.000001)
{
// Special case: if rotationAxis is just about zero, set to X axis,
// so that the angle can be given as 0 or PI. This works ONLY
// because we know one of the two axes is +Y.
rotationAxis.set (1, 0, 0);
}
rotationAxis.normalize();
// take dot product of axis and up vector to get cosine of angle of rotation
var theta = -Math.acos (axis.dot (yAxis));
// obj.matrix.makeRotationAxis (rotationAxis, theta);
var rotMatrix = new THREE.Matrix4();
rotMatrix.makeRotationAxis (rotationAxis, theta);
obj.matrix.multiply (rotMatrix);
}
I have a number of shapes in my kinetc layer. If I drag and drop a shape somewhere else in the layer, how to determine the dropped shape touches another shape or not?
The thing you need to do is create a mathematical representation of the shapes you have. For most simple collision detection, you can use bounding-boxes.
Basically, if you have a circle, you can create a representation of it as being bounded in a box.
Then if you have a square, you can check if the bounding box of the square (which is the square itself) is intersecting with the bounding box of the circle.
I wrote an answer to this a while ago: HTML5 / kineticJS getIntersection function implementation
function checkCollide(pointX, pointY, objectx, objecty, objectw, objecth) { // pointX, pointY belong to one rectangle, while the object variables belong to another rectangle
var oTop = objecty;
var oLeft = objectx;
var oRight = objectx+objectw;
var oBottom = objecty+objecth;
if(pointX > oLeft && pointX < oRight){
if(pointY > oTop && pointY < oBottom ){
return 1;
}
}
else
return 0;
};
used like this:
var children = layer.getChildren();
for( var i=0; i<children.length; i++){ // for each single shape
for( var j=0; j<children.length; j++){ //check each other shape
if(i != j){ //skip if shape is the same
if(checkCollide(children[i].getX(), children[i].getY(), children[j].getX(), children[j].getY(), children[j].getWidth(), children[j].getHeight()))
alert('top left corner collided');
}
}
}
This works great if the shape you have is a rectangle of some sort, but not that great if you have two circles, as they have a radius. So this suffices for a quick check of collision, next you need another function which will check collisions more precisely.
You can also try using kineticjs with box2d, there are a bunch of tutorials out there on the topic.
I'm trying to make a static 3D prism out of point clouds with specific numbers of particles in each. I've got the the corner coordinates of each side of the prism based on the angle of turn, and tried spawning the particles in the area bound by these coordinates. Instead, the resulting point clouds have kept only the bottom left coordinate.
Screenshot: http://i.stack.imgur.com/uQ7Q8.png
I've tried to set the rotation of each cloud object such that their edges meet, but they will rotate only around the world centre. I gather this is something to do with rotation matrices and Euler angles, but, having been trying to work them out for 3 solid days, I've despaired. (I'm a sociologist, not a dev, and haven't touched graphics before this project.)
Please help? How do I set the rotation on each face of the prism? Or maybe there is a more sensible way to get the particles to spawn in the correct area in the first place?
The code:
// draw the particles
var n = 0;
do {
var geom = new THREE.Geometry();
var material = new THREE.PointCloudMaterial({size: 1, vertexColors: true, color: 0xffffff});
for (i = 0; i < group[n]; i++) {
if (geom.vertices.length < group[n]){
var particle = new THREE.Vector3(
Math.random() * screens[n].bottomrightback.x + screens[n].bottomleftfront.x,
Math.random() * screens[n].toprightback.y + screens[n].bottomleftfront.y,
Math.random() * screens[n].bottomrightfront.z + screens[n].bottomleftfront.z);
geom.vertices.push(particle);
geom.colors.push(new THREE.Color(Math.random() * 0x00ffff));
}
}
var system = new THREE.PointCloud(geom, material);
scene.add(system);
**// something something matrix Euler something?**
n++
}
while (n < numGroups);
I've tried to set the rotation of each cloud object such that their
edges meet, but they will rotate only around the world centre.
It is true they only rotate around 0,0,0. The simple solution then is to move the object to the center, rotate it, and then move it back to its original position.
For example (Code not tested so might take a bit of tweaking):
var m = new THREE.Matrix4();
var movetocenter = new THREE.Matrix4();
movetocenter.makeTranslation(-x, -y, -z);
var rotate = new THREE.Matrix4();
rotate.makeRotationFromEuler(); //Build your rotation here
var moveback = new THREE.Matrix4();
moveback .makeTranslation(x, y, z);
m.multiply(movetocenter);
m.multiply(rotate);
m.multiply(moveback);
//Now you can use geometry.applyMatrix(m)
I have made the game, "Breakout". A small fun side-project.
Now, I usually do not make games, so collision-handling is not something I normally think about.
I have a paddle, a ball and some bricks.
For now, when there is a collision (I draw rectangles around each of the objects mentioned), I simply change the Y value of the ball to -Y.
This works fine, EXCEPT if the ball hits a brick from the side (either East or West). The side-effect is not pretty and ruins the gameplay.
I think I can safely assume that instead of the above technique, I need to change the X value to -X when this happens.
So far I have: if (ballRect.IntersectsWith(brickRect))
ballRect and brickRect being rectangles around each object.
Now, what if I created a rectangle around the eastern border of the brick, the western border, etc? I guess the width would be about a pixel.
If collision happens with western or eastern rectangle, then the balls X value should be -X.
And vice versa.
What about the corners though? Should I just randomly choose which rectangle to control of x corner?
Or perhaps should I make a rectangle around each corner? the rectangle being 1*1 in side.
If there is a collision => -x AND -y values of the ball?
Please share your thoughts.
Here is the process so far:
foreach (var brick in Bricks)
{
if (brick.IsAlive)
{
var brickRect = new Rectangle(brick.X, brick.Y, BrickWidth, BrickHeight);
if (ballRect.IntersectsWith(brickRect)) //Ball has hit brick. lets find out which side of the brick
{
var brickRectNorth = new Rectangle(brick.X, brick.Y + BrickHeight, BrickWidth, 1);
var brickRectSouth = new Rectangle(brick.X, brick.Y, BrickWidth, 1);
var brickRectEast = new Rectangle(brick.X, brick.Y, 1, BrickHeight);
var brickRectWest = new Rectangle(brick.X + BrickWidth, brick.Y, 1, BrickHeight);
if (ballRect.IntersectsWith(brickRectNorth) || ballRect.IntersectsWith(brickRectSouth))
{
//STUFF that makes ball.y = -ball.y
}
if (ballRect.IntersectsWith(brickRectWest) || ballRect.IntersectsWith(brickRectEast))
{
//STUFF that makes ball.x = -ball.x
}
}
}
}
Rather than looking for rectangle intersections, I'd intersect the actual edges. At the corner, your ball is touching two edges simultaneously, so its motion vector should be affected by both.
I would keep the single rectangle for collision detection, since that reduces the number of rectangles you need to test in your outer loop, but then once a collision with a brick has been detected, go into an inner loop to detect which edge it was that was hit. If you just test each edge and adjust the vector accordingly for each one, the corner will come for free (as long as you don't break out of the loop when you find the first intersecting edge).
Edit: In response to your updated question:
Actually, this is how I would do it (given your code, this appears to be C# 3.0, so that's what I've assumed below):
foreach(var brick in Bricks) {
if(brick.IsAlive) {
var brickRect = new Rectangle(brick.X, brick.Y, BrickWidth, BrickHeight);
if(ballRect.IntersectsWith(brickRect)) {
// Ball has hit brick. Now let's adjust the ball's vector accordingly
// Convenience variables. Compiler will probably inline.
var brickLeft = brick.X;
var brickRight = brick.X + BrickWidth;
var brickTop = brick.Y;
var brickBottom = brick.Y + BrickHeight;
var ballLeft = ball.X - ball.Radius;
var ballRight = ball.X + ball.Radius;
var ballTop = ball.Y - ball.Radius;
var ballBottom = ball.Y + ball.Radius;
// Test which vector(s) we need to flip
bool flipX = (ballRight >= brickLeft || ballLeft <= brickRight);
bool flipY = (ballTop >= brickBottom || ballBottom <= brickTop);
// Flip the vectors (there are probably ways to optimize this,
// too, but without seeing your code I can't tell).
if(flipY) {
// Stuff that makes ball.y = -ball.y
}
if(flipX) {
// Stuff that makes ball.x = -ball.x
}
}
}
}
Basically, the point is that since you already know the ball actually intersects the brick, you can simplify to a simple box test, which is much faster. Also, there's no need to create extra rectangles for the edges -- just use the edges of the rectangle you already have.