Three.js Vector.angleTo: How to also get the bigger value - three.js

Given a vector v1=(0,0,1) and two vectors v2=(1,0,0) and v3=(-1,0,0) I would expect v1.angleTo(v2) and v1.angleTo(v3) to to return different results, i.e. 1/2 PI and 3/2 PI.
However, both return 1/2 PI:
var v1 = new THREE.Vector3(0, 0, 1);
var v2 = new THREE.Vector3(1, 0, 0);
var v3 = new THREE.Vector3(-1, 0, 0);
v1.angleTo(v2)
1.5707963267948966
v1.angleTo(v3)
1.5707963267948966
It seems that angleTo always returns the smaller angle, i.e. values between 0 and PI.
How can I get the expected value/behavior?

angleTo always returns the smaller angle. See the implementation of angleTo in https://github.com/mrdoob/three.js/blob/master/src/math/Vector3.js.
If the angle should always be determined in one direction (i.e. either counterclockwisely or clockwisely), a simple solution for 2d vectors (or n-d vectors in a 2d plane parallel to a two-axes-plane of the coordinate system, as in the example given in the question) is:
var orientation = v1.x * v2.z - v1.z * v2.x;
if(orientation > 0) angle = 2*Math.PI - angle;

Related

Rotation snapping, find next closest quaternion from an array in THREE.js

I am rotating a cube around a particular axis (x or y or z) by dragging my mouse, lets say while dragging I calculate how much angle rotation I have to appy.
Now I have my current cube rotation in quaternion, and also an array of quaternions containing quaternions of 0, 45, 90, 135, 180, 225, 270, 315, and 360 degrees.
When I am rotating my cube I want to find the closest quaternion from the array, lets say I am rotating in anti-clock and my cube is at 30, then the closest will be quaternion of 90 degrees from the array., similarly for 170 I should get 180 deg quaternion from the array.
currently I am maintaining a variable and depending upon the direction (clock or anti-clock) I am rotating the cube I am managing that variable and finding the next required quaternion from the array. But I need a more efficient way if it exists.
currently My code is doing something like this, If anyone have some solution about this, or a new way of doing this, then please help me
function handleDrag() {
let target = new THREE.Vector3();
raycaster.setFromCamera(mouseNDC, camera);
raycaster.ray.intersectPlane(Zplane, target);
let temp = target
target.sub(mesh.position); // final vector after drag
initial.sub(mesh.position); // initial vector
// get rotation direction using cross product
let xx = new THREE.Vector3().copy(target).normalize()
let yy = new THREE.Vector3().copy(prevDir).normalize()
let dir = yy.cross(xx).z
const angleBtwVec = angleInPlane(initial, target, Zplane.normal);
let quaternion = new THREE.Quaternion();
quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), angleBtwVec);
let effectiveQuatChange = new THREE.Quaternion().copy(initialCubeQuat)
effectiveQuatChange.multiply(quaternion)
// find next quaterion for which we need to compare for snapping
let check = next
if (dir > 0) { // then anti-clock
check = (next + 1) % 8
} else { // else rotation is in clock direction
check = (next - 1 + 8) % 8
}
// apply quaternion change
mesh.quaternion.copy(effectiveQuatChange)
let reqQuatArray = quaternionArrayYR
let angleDiff = toDegrees(mesh.quaternion.angleTo(reqQuatArray[check]))
console.log(angleDiff, check);
if (angleDiff <= 15) { // if mesh angle with next req quaternion is less than 15 degree, then set mesh quaternion to required quaternion
next = check
mesh.quaternion.copy(reqQuatArray[next]);
initialCubeQuat.copy(reqQuatArray[next]);
initial = temp;
}
prevDir = temp
}

Confusion about zFar and zNear plane offsets using glm::perspective

I have been using glm to help build a software rasterizer for self education. In my camera class I am using glm::lookat() to create my view matrix and glm::perspective() to create my perspective matrix.
I seem to be getting what I expect for my left, right top and bottom clipping planes. However, I seem to be either doing something wrong for my near/far planes of there is an error in my understanding. I have reached a point in which my "google-fu" has failed me.
Operating under the assumption that I am correctly extracting clip planes from my glm::perspective matrix, and using the general plane equation:
aX+bY+cZ+d = 0
I am getting strange d or "offset" values for my zNear and zFar planes.
It is my understanding that the d value is the value of which I would be shifting/translatin the point P0 of a plane along the normal vector.
They are 0.200200200 and -0.200200200 respectively. However, my normals are correct orientated at +1.0f and -1.f along the z-axis as expected for a plane perpendicular to my z basis vector.
So when testing a point such as the (0, 0, -5) world space against these planes, it is transformed by my view matrix to:
(0, 0, 5.81181192)
so testing it against these plane in a clip chain, said example vertex would be culled.
Here is the start of a camera class establishing the relevant matrices:
static constexpr glm::vec3 UPvec(0.f, 1.f, 0.f);
static constexpr auto zFar = 100.f;
static constexpr auto zNear = 0.1f;
Camera::Camera(glm::vec3 eye, glm::vec3 center, float fovY, float w, float h) :
viewMatrix{ glm::lookAt(eye, center, UPvec) },
perspectiveMatrix{ glm::perspective(glm::radians<float>(fovY), w/h, zNear, zFar) },
frustumLeftPlane {setPlane(0, 1)},
frustumRighPlane {setPlane(0, 0)},
frustumBottomPlane {setPlane(1, 1)},
frustumTopPlane {setPlane(1, 0)},
frstumNearPlane {setPlane(2, 0)},
frustumFarPlane {setPlane(2, 1)},
The frustum objects are based off the following struct:
struct Plane
{
glm::vec4 normal;
float offset;
};
I have extracted the 6 clipping planes from the perspective matrix as below:
Plane Camera::setPlane(const int& row, const bool& sign)
{
float temp[4]{};
Plane plane{};
if (sign == 0)
{
for (int i = 0; i < 4; ++i)
{
temp[i] = perspectiveMatrix[i][3] + perspectiveMatrix[i][row];
}
}
else
{
for (int i = 0; i < 4; ++i)
{
temp[i] = perspectiveMatrix[i][3] - perspectiveMatrix[i][row];
}
}
plane.normal.x = temp[0];
plane.normal.y = temp[1];
plane.normal.z = temp[2];
plane.normal.w = 0.f;
plane.offset = temp[3];
plane.normal = glm::normalize(plane.normal);
return plane;
}
Any help would be appreciated, as now I am at a loss.
Many thanks.
The d parameter of a plane equation describes how much the plane is offset from the origin along the plane normal. This also takes into account the length of the normal.
One can't just normalize the normal without also adjusting the d parameter since normalizing changes the length of the normal. If you want to normalize a plane equation then you also have to apply the division step to the d coordinate:
float normalLength = sqrt(temp[0] * temp[0] + temp[1] * temp[1] + temp[2] * temp[2]);
plane.normal.x = temp[0] / normalLength;
plane.normal.y = temp[1] / normalLength;
plane.normal.z = temp[2] / normalLength;
plane.normal.w = 0.f;
plane.offset = temp[3] / normalLength;
Side note 1: Usually, one would store the offset of a plane equation in the w-coordinate of a vec4 instead of a separate variable. The reason is that the typical operation you perform with it is a point to plane distance check like dist = n * x - d (for a given point x, normal n, offset d, * is dot product), which can then be written as dist = [n, d] * [x, -1].
Side note 2: Most software and also hardware rasterizer perform clipping after the projection step since it's cheaper and easier to implement.

Different Processing rendering between native and online sketch

I get different results when running this sample with Processing directly, and with Processing.js in a browser. Why?
I was happy about my result and wanted to share it on open Processing, but the rendering was totally different and I don't see why. Below is a minimal working example.
/* Program that rotates a triange and draws an ellipse when the third vertex is on top of the screen*/
float y = 3*height/2;
float x = 3*width/2;
float previous_1 = 0.0;
float previous_2 = 0.0;
float current;
float angle = 0.0;
void setup() {
size(1100, 500);
}
void draw() {
fill(0, 30);
// rotate triangle
angle = angle - 0.02;
translate(x, y);
rotate(angle);
// display triangle
triangle(-50, -50, -30, 30, -90, -60);
// detect whether third vertex is on top by comparing its 3 successive positions
current = screenY(-90, -60); // current position of the third vertex
if (previous_1 < previous_2 && previous_1 < current) {
// draw ellipse at the extrema position
fill(128, 9, 9);
ellipse(-90, -60, 7, 10);
}
// update the 2 previous positions of the third vertex
previous_2 = previous_1;
previous_1 = current;
}
In processing, the ellipse is drawn when a triangle vertex is on top, which is my goal.
In online sketching, the ellipse is drawn during the whole time :/
In order to get the same results online as you get by running Processing locally you will need to specify the rendering mode as 3d when calling size
For example:
void setup() {
size(1100, 500, P3D);
}
You will also need to specify the z coordinate in the call to screenY()
current = screenY(-90, -60, 0);
With these two changes you should get the same results online as you get running locally.
Online:
Triangle Ellipse Example
Local:
The problem lies in the screenY function. Print out the current variable in your processing sketch locally and online. In OpenProcessing, the variable current grows quickly above multiple thousands, while it stays between 0 and ~260 locally.
It seems like OpenProcessing has a bug inside this function.
To fix this however, I would recommend you to register differently when you drew a triangle at the top of the circle, for example by using your angle variable:
// Calculate angle and modulo it by 2 * PI
angle = (angle - 0.02) % (2 * PI);
// If the sketch has made a full revolution
if (previous_1 < previous_2 && previous_1 < angle) {
// draw ellipse at the extrema position
fill(128, 9, 9);
ellipse(-90, -60, 7, 10);
}
// update the 2 previous angles of the third vertex
previous_2 = previous_1;
previous_1 = angle;
However, because of how you draw the triangles, the ellipse is at an angle of about PI / 3. To fix this, one option would be to rotate the screen by angle + PI / 3 like so:
rotate(angle + PI / 3);
You might have to experiment with the angle offset a bit more to draw the ellipse perfectly at the top of the circle.

OpenSceneGraph osg::Quat: shape not rotating

I have a small function to create a new instance of a WorldObject.
I want to use osg::ref_ptr<osg::PositionAttitudeTransform> for translation and rotation but there is a problem I can't figure out.
I use setTranslation() with a Vec3 which works very well. But the Quat with makeRotation() just does nothing.
Here is the code:
osg::ref_ptr <osg::PositionAttitudeTransform> getWorldObjectClone(const char* name, osg::Vec3 position = osg::Vec3(0, 0, 0), osg::Vec3 rotation = osg::Vec3(0, 0, 0))
{
osg::ref_ptr <osg::PositionAttitudeTransform> tmp = new osg::PositionAttitudeTransform;
osg::Quat q(0, osg::Vec3(0, 0, 0));
tmp = dynamic_cast<osg::PositionAttitudeTransform*>(WorldObjects[name]->clone(osg::CopyOp::DEEP_COPY_ALL));
tmp->setPosition(position);
q.makeRotate(rotation.x(), 1, 0, 0);
q.makeRotate(rotation.y(), 0, 1, 0);
q.makeRotate(rotation.z(), 0, 0, 1);
tmp->setAttitude(q);
return tmp;
}
I tried rotation = {90,0,0} (degrees) and rotation = {1,0,0} (radians) but both have no effect. Is there an mistake in how the code is using the Quat?
The rotation method you are using works with radians.
If you want to rotate 90 degrees around the X axis, you need to call:
q.makeRotate(osg::PI_2, 1, 0, 0 );
// or the equivalent
q.makeRotate(osg::PI_2, osg::X_AXIS);
Keep in mind that every call to makeRotate will reset the full quaternion to the given rotation. If you're trying to concatenate several rotations, you have to multiply the corresponding quaternions.
For instance:
osg::Quar xRot, yRot;
// rotate 90 degrees around x
xRot.makeRotate(osg::PI_2, osg::X_AXIS);
// rotate 90 degrees around y
yRot.makeRotate(osg::PI_2, osg::Y_AXIS);
// concatenate the 2 into a resulting quat
osg::Quat fullRot = xRot * yRot;

famo.us quaternion rotation around z axis

As far as I know a quaternion is a set of four values (W X Y Z) that are used to specify a rotation in 3D space. For a given axis (x y z) and angle (α), the quaternion representing a rotation around the axis from the origin (0,0,0) to (x,y,z). So a rotation of 90 degrees about the z axis (0 0 1) should be:
var quaternion = new Quaternion(Math.PI/2, 0, 0, 1);
but famo.us turns it for ~60 degrees...
I've also tried var quaternion = new Quaternion(90, 0, 0, 1); but in this case famo.us turns it for ~5 degrees
is it a bug of the framework?
How should I use it to turn it on 90 degreez around z axis?
Documentation is still totally useless..
Try using this method Quaternion.makeFromAngleAndAxis(angle, v)
I have found this to be the most straight forward approach to making it a little more readable and useable.
Example jsBin
Where
var degrees = 90;
var angle = Math.PI/180 * degrees;
var v = new Vector(0, 0, 1);
var quaternion = new Quaternion();
quaternion.makeFromAngleAndAxis(angle, v);
...To get the transform
quaternion.getTransform();
Something to remember from Math Class
A circle has 360 degrees. Each degree is represented by the unit circumference of a circle 2 * PI * r. We will assume we have a radius of 1. So divide your total circumference by 360 and you get one degrees 2PI/360 or PI/180.
In Summary:
one degrees of our circle is = Math.PI/180
your angle of direction is = Math.PI/180 * degrees
Just found answer in one wiki article:
var angle = Math.PI/2;
var axis = [0,0,1];
var w = Math.cos(.5 * angle);
var x = axis[0] * Math.sin(.5 * angle);
var y = axis[1] * Math.sin(.5 * angle);
var z = axis[2] * Math.sin(.5 * angle);
var quaternion = new Quaternion(w, x, y, z);
try this transformation - Transform.rotateZ(angle);
Refer to - http://famo.us/docs/reference/pages/0.3/transforms.html

Resources