Show Extents Algorithm? - javascript

OH Great and Knowledgeable Stack Overflow, I humbly request your great minds assistance...
I'm using the three js library, and I need to implement a 'show extents' button. It will move the camera to a position such that all the objects in the world are visible in the camera view (given they are not blocked of course).
I can find the bounding box of all the objects in the world, say they are w0x,w0y,w0z and w1x,w1y,w1z
How can I, given these to bounds, place the camera such that it will have a clear view of the edges of the box?
Obviously there will have to be a 'side' chosen to view from...I've googled for an algorithm to no avail!
Thanks!

So Let's say that you have picked a face. and that you are picking a camera position so that the camera's line-of-sight is parallel to one of the axes.
Let's say that the face has a certain width, "w", and let's say that your camera has a horizontal field-of-view "a". What you want to figure out is what is the distance, "d" from the center of the face that the camera should be to see the whole width.
If you draw it out you will see that you basically have an isosceles triangle whose base is length w and with the angle a at the apex.
Not only that but the angle bisector of the apex angle forms two identical right triangles and it's length (to the base) is the distance we need to figure out.
Trig tells us that the tangent of an angle is the ratio of the oposite and adjacent sides of the triangle. So
tan(a/2) = (w/2) / d
simplifying to:
d = w / 2*tan(a/2)
So if you are placing the camera some axis-aligned distance from one of your bounding box faces then you just need to move d distance along the axis of choice.
Some caveats, make sure you are using radians for the javascript trig function input. Also you may have to compute this again for your face height and camera's vertical field-of-view and pick the farther distance if your face is not square.
If you want to fit the bounding box from an arbitrary angle you can use the same ideas - but first you have to find the (aligned) bounding box of the scene projected onto a plane perpendicular to the camera's line of sight

Related

3D orienting particles using velocity and position vectors in p5js

I'm currently writting a 3D implementation of the boids algorithm in P5.js but I'm having trouble orienting my boids according to their direction (velocity). Rotations are limited to RotateX(), RotateY() and RotateZ(). The simplest solution that I feel should work goes along these lines :
push();
translate(this.pos);
rotateZ(createVector(this.vel.x, this.vel.y).heading());
rotateY(createVector(this.vel.x, this.vel.z).heading());
beginShape();
// Draw Boid Vertices..
endShape();
pop();
But it doesn't.
I've written a much smaller version of the program which contains only the orientation for randomly generated particles that go in a single direction. It is available here directly on the p5js website : https://editor.p5js.org/itsKaspar/sketches/JvypSPGGh
There is a default orbit control so you can zoom and drag the mouse to check the orientation of the particles.
Thanks so much, I've been stuck on this for half a day now
From your demo, the z component is flipped, and you can test this from only trying one of the rotations at a time. Second, chaining rotations in 3D this way will usually not do what you want, as rotating will change the "up" or "right" vector of the coordinate system attached to a certain object. For example, rotating about the up (-y for p5) vector, or the yaw angle, will rotate the right vector. The second rotation then needs to be about the rotated right vector (now pitch), so you can't just use rotateX/Y/Z as they are still in world space instead of object space. Note that I'm completely ignoring roll in this solution, but if you look at the boids from the front and top angles, it should be aligned with the velocities
var right = p5.Vector(this.vel.x, 0, this.vel.z);
rotate(atan(this.vel.y/ this.vel.x), right);
rotateY(atan2(-this.vel.z, this.vel.x));

How to get the position of object in Three.js relative to the camera to draw a trail?

I have an object (a pen) in my scene, which is rotating around its axis in the render loop.
groupPen.rotation.y += speed;
groupPen.rotation.x += speed;
and I have also a TrackballControls, which allows the user to rotate the whole scene.
What I now want is to get the "real" position of the pen (or its pick) and place small spheres to create a trail behind it.
This means I need to know where the camera is looking at and place the trail spheres behind the peak of the pen and exclude them from the animation and the TrackballControls.
What I tried is:
groupSphereTrail.lookAt(camera.position);
didn't work. Means no reaction at all.
camera.add(groupSphereTrail);
didn't work. groupSphereTrail is than not in the view area, couldn't make it visible - manipulating position.z didn't help.
Then I tried something like sending a tray with traycaster. The idea was to send a ray from the center of the camera through the peak of the pen and then draw the trail there. But then I still doesn't have the "real" position.
Another idea was to create a 2d vector of the current position of the pen peak and just draw an html element on top of the canvas:
var p = penPeak.position.clone();
var vector = p.project(camera);
vector.x = (vector.x + 1) / 2 * width;
vector.y = -(vector.y - 1) / 2 * height;
but this also doesn't work.
What could be another working solution?
Current progress:
https://zhaw.swissmade.xyz
(click on the cap of the pen to see the writing - this writing trail should stay at its place when you rotate the camera)
If i understood the question right, you want to show the trail as if it were draw on the screen itself (screen space)?
yourTrailParticle.position.project(camera)
camera.add(yourTrailParticle)
That's the basic idea, but it gets a bit tricky with PerspectiveCamera. You could set up a whole new THREE.Scene to hold the trail, and render it with a fixed size orthographic camera.
The point is .project() will give you a normalized screen space coordinate of a world space vector, and you need to keep it somehow in sync with that camera (since the screen is too). The perspective camera has distortion so you need to figure out the appropriate distance to map the coordinate to. With a separate scene, this may become easier.

Rotating around dodecahedron

I am trying to make a Three.js scene where I've got a dodecahedron. I want the camera to be zoomed in on one side of the dodecahedron and when a button is pressed I want it to zoom out, rotate until it is standing across another side and then zoom in again.
To make this clear:
If the camera would be fully zoomed in on side 1 and I pressed "5", I would want the camera to zoom out - showing the dodecahedron - then rotate towards 5 (or let the dodecahedron rotate side 5 facing the camera?) and zoom in again. It's important that the camera is always set parallel with the base of the pentagon it's facing, not the top or any other rotation.
I thought it would be smart to start off with just a cube, to not start too complicated. I added some tweens (when pressing G) to illustrate some basic movement, but that doesn't look too good anymore in the fiddle. jsfiddle
Because I feel like I should have a function that does all this movement and calculating for me I first tried to write down each position and rotation each side-view had from the cube so I might detect a pattern. I can see some pattern in the values I wrote down for the cube, but I do not know how to convert this into a working function, let alone for a dodecahedron. My noted values are
side1 (0, 0, 600) (0, 0, 0)
side2 (600, 0, 0) (0, pi/2, 0)
side3 (0, 0, -600) (0, pi, 0);
side4 (-600, 0, 0) (0, -pi/2, 0);
side5 (0, 600, 0) (-pi/2, 0, 0);
side6 (0, -600, 0) (pi/2, 0, 0);
I can see some sort of recurrence happening and some relationships, but I wouldn't see how to link them in a function. I think that would be a first step in getting to a function doing the same but for a more complex shape. Could anyone guide me into some direction I should be looking right now? Because I could of course work with a lot of if clauses, but that's not the correct way to go I feel.
To solve the problem, the first we would do is getting the center coordinate of each side of the dodecahedron.
as you know in three.js, a mesh consists of triangles, every triangle has three points, all the faces and vertices can be found in mesh.geometry.faces and mesh.geometry.vertices. in dodecahedron, each side has three faces and five vertices, and I use each face normal to divide them into 12 groups which have same normal and in the same plane. Then, we got 5 points of each side, calculate the average coordinate to get the center coordinate.
After getting coordinates we need to rotate, one way is rotating the dodecahedron and keep the camera, another way is keeping the dodecahedron and translate camera, in this case, I select the second one.
Now camera face to centerA, the green circle is the track of camera, because we need to keep the distance from camera to the dodecahedron center.
To get the target position, we just scale the centerB vector, cause the coordinate of the centerB is object system coordinate, we need to apply the matrix to change the coordinate to world system coordinate.
Then, we translate the camera in an animation, the camera needs to take an arc.
I use the parametric equation of a circle in 3D space to do that, about the equation you can see Parametric Equation of a Circle in 3D Space. With this formula, I got the parameter θ1 on camera position and θ2 on target position. I update the camera position with θ1 in every animation frame loop.
I add some comments on jsfiddle.(Still has some bugs, need to update.)
Here is another solution by keeping the camera and rotate the object.

Drawing a point to point ellipse to allow painting in GUI

I'm implementing some basic annotation draw features, such as arrows. Now I'm a little bit stuck with ellipse.
The methods to draw an ellipse usually address using it's two diameters and eventually a rotation:
However I want to display the ellipse between the point user clicked and the one he's hovering, therefore I need a function that calculates diameters and rotation based on two points:
How would I do that? Can it be achieved with sufficient performance (as it renders during mouse-hovering)?
the steps you shoul follow:
get the angle of the line (from this post: get angle of a line from horizon)
rotate the canvas or at least the part you currently drawing (live demo here: http://www.html5canvastutorials.com/advanced/html5-canvas-transform-rotate-tutorial)
draw an ellipse in canvas (http://www.scienceprimer.com/draw-oval-html5-canvas)
the resulted ellipse will be transformed as described
It can be done in the same way that it is normally done, just using different math to calculate the shape. Without writing the entire code for you, you can start by having an event trigger when the user clicks the mouse button down. The function will copy the users x and y position based on the screen. Then there is a second function which will handle mouse movement. This function will keep track of the x and y coords of the mouse while it is in motion. The final function will be a mouse up event, when a user lifts their finger from the mouse button (assuming this is when the event should be finished). Using the initial and final position of the x and y coordinates, you can calculate the length of the line the user created. That line is the long diameter of the ellipse. Half this number for the large radius. Then use whatever ratio you are using to calculate the smaller radius from the larger one. Then create an ellipse based on these numbers.
For the math: Suppose your first point is x1,y1 and the end point is x2,y2
I'm also assuming that we have a line going from bottom-left to top-right
Distance between two points = sqrt((x2-x1)^2 + (y2-y1)^2) ---> (we will call this d1)
half of this is the length of the large radius ---> (we will call this r1)
Midpoint formula = ((x1+x2)/2 , (y1+y2)/2) ---> axis of rotation (we will call it (m1, m2))
distance from midpoint to end is just the radius
radius is now the hypotenuse of constructed plane, y2-m2 is height of right triangle.
Find the angles between midpoint and one end of larger radius - sin((y2-m2)/r1).
Angle of smaller radius is this angle + pi/4 radians.
calculate length of smaller radius based on ratio.

Straightening a face image within Canvas using click-points

I am trying to build a small app where my users can straighten up a tilted face with just 2 clicks
I ask my users to click on the middle of the nose and the middle of the eyebrows of the face within the image.
From there I get 2 points eyebrowMiddle(x1,y1) and noseMiddle (x2,y2).
Is it possible via these 2 points to calculate how much Canvas
rotation I need to have to rotate the image and make the face straight
in relation to the canvas rectangle?
Also, how can I detect and adjust accordingly if the image is tilted
to the left or right?
Here is a more descriptive image to show you what I mean now.
PS:
x1,y1 and x2,y2 are in relation to the canvas perimeter of
course, not the browser window or anything else.
We have tried the line equation such as m = (x2-x1) / (y2-y1) but the
result is always near 1 so I don't think we are following the right
course at the moment.
We don't care if the image looks wrong in the canvas as long as the
face features are parallel in relation to the bottom of the canvas
(they should be looking straight).
To perform such a rotation, you need to decide of the pivot point. Here i choose the eyebrow.
Then you have to choose a point in the target canvas where this pivot point will be hooked. I decided to choose the point at middle x coordinates, and at fourth of the screen in y.
To compute the rotation angle, you have to use atan2, which will nicely give you the angle for a given deltaY / deltaX in between two points ( angle = Math.atan2 ( delta y , delta x ) ) .
Then to draw :
- Translate to the target point.
- rotate by right angle.
- draw the image centering on its pivot.
ET VOILA, it works :-)
function rotate() {
ctx.save();
// go to default center position
ctx.translate(eyeBrowTargetPosition.x, eyeBrowTargetPosition.y);
// compute angle
var yDelta = noseMiddle.y - eyebrowMiddle.y;
var xDelta = noseMiddle.x - eyebrowMiddle.x ;
var angle = Math.atan2 (yDelta ,xDelta);
// compensate for angle
ctx.rotate(angle);
//draw image centering input on eyebrow
ctx.drawImage(face, -eyebrowMiddle.x, -eyebrowMiddle.y);
ctx.restore();
};
jsbin is here :
http://jsbin.com/wavokaku/2/edit?js,output
result with an approximation of the existing green dots :

Categories