Calculating vertex position after rotating in 3D space - javascript

I am trying to rotate on Y axis in javascript ( In my own 3D engine )
and i tried this function :
function rotateY(amount) {
for (var i = 0; i < points.length; i++) {
points[i].z = Math.sin(amount) * points[i].x + Math.cos(amount) * points[i].z;
points[i].x = Math.cos(amount) * points[i].x - Math.sin(amount) * points[i].z;
}
}
It is rotating, but every time it rotates it changes it's x and z scale so it is getting thinner.. Can you help me how to rotate it properly ? Thanks :)

Assuming i) the rotation is respective to the global origin and not the object itself and ii) that you want to apply a delta (if we can't take these, see below):
For each point:
1. Find the distance of the point relative to the axis.
2. Find the current angle of the point relative to the axis.
3. Use basic 2-D cos/sin polar projection, since the excluded axis is a unit vector.
function rotateY( points, deltaAngle ) {
const _points = points;
if ( ! Array.isArray( points ) ) points = [ points ];
for ( let i = 0; i < points.length; i ++ ) {
const newAngle = Math.atan2( points[ i ].z, points[ i ].x ) + deltaAngle;
const distance = ( points[ i ].x ** 2 + points[ i ].z ** 2 ) ** ( 1 / 2 );
points[ i ].x = distance * Math.cos( newAngle );
points[ i ].z = distance * Math.sin( newAngle );
}
return _points;
}
The algorithm is the same for X and Z rotation, so long as the first axis used in Math.atan2 is the same axis that uses Math.sin.
NOTE: I used the exponentiation operator. I wouldn't use this in production unless you're using Babel/something similar or don't care about IE/old users.
If assumption ii) cannot be taken, we simply want to store the original angles of the points and have newAngle defined as the original angle plus the new angle.
If assumption i) cannot be taken, it gets complicated. If the object's axes are simply offset, you can subtract that offset in newAngle and distance and add it back when setting x and z. If the axes themselves are not respectively parallel to the global axes, you'll want to switch to using a quaternion to avoid gimbal lock. I would suggest copying or at least looking three.js's implementation.

Related

Converting an equirectangular depth map into 3d point cloud

I have a 2D equirectangular depth map that is a 1024 x 512 array of floats, each ranging between 0 to 1. Here example (truncated to grayscale):
I want to convert it to a set of 3D points but I am having trouble finding the right formula to do so - it's sort of close - pseudocode here (using a vec3() library):
for(var y = 0; y < array_height; ++y) {
var lat = (y / array_height) * 180.0 - 90.0;
var rho = Math.cos(lat * Math.PI / 180.0);
for(var x = 0; x < array_width; ++x) {
var lng = (x / array_width) * 360.0 - 180.0;
var pos = new vec3();
pos.x = (r * Math.cos(lng * Math.PI / 180.0));
pos.y = (Math.sin(lat * Math.PI / 180.0));
pos.z = (r * Math.sin(lng * Math.PI / 180.0));
pos.norm();
var depth = parseFloat(depth[(y * array_width) + x] / 255);
pos.multiply(depth);
// at this point I can plot pos as an X, Y, Z point
}
}
What I end up with isn't quite right and I can't tell why not. I am certain the data is correct. Can anyone suggest what I am doing wrong.
Thank you.
Molly.
Well looks like the texture is half-sphere in spherical coordinates:
x axis is longitude angle a <0,180> [deg]
y axis is latitude angle b <-45,+45> [deg]
intensity is radius r <0,1> [-]
So for each pixel simply:
linearly convert x,y to a,b
in degrees:
a = x*180 / (width -1)
b = -45 + ( y* 90 / (height-1) )
or in radians:
a = x*M_PI / (width -1)
b = -0.25*M_PI + ( 0.5*y*M_PI / (height-1) )
apply spherical to cartesian conversion
x=r*cos(a)*cos(b);
y=r*sin(a)*cos(b);
z=r* sin(b);
Looks like you have wrongly coded this conversion as latitude angle should be in all x,y,z not just y !!! Also you should not normalize the resulting position that would corrupt the shape !!!
store point into point cloud.
When I put all together in VCL/C++ (sorry do not code in javascript):
List<double> pnt; // 3D point list x0,y0,z0,x1,y1,z1,...
void compute()
{
int x,y,xs,ys; // texture positiona and size
double a,b,r,da,db; // spherical positiona and angle steps
double xx,yy,zz; // 3D point
DWORD *p; // texture pixel access
// load and prepare BMP texture
Graphics::TBitmap *bmp=new Graphics::TBitmap;
bmp->LoadFromFile("map.bmp");
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
xs=bmp->Width;
ys=bmp->Height;
/*
// 360x180 deg
da=2.0*M_PI/double(xs-1);
db=1.0*M_PI/double(ys-1);
b=-0.5*M_PI;
*/
// 180x90 deg
da=1.0*M_PI/double(xs-1);
db=0.5*M_PI/double(ys-1);
b=-0.25*M_PI;
// proces all its pixels
pnt.num=0;
for ( y=0; y<ys; y++,b+=db)
for (p=(DWORD*)bmp->ScanLine[y],a=0.0,x=0; x<xs; x++,a+=da)
{
// pixel access
r=DWORD(p[x]&255); // obtain intensity from texture <0..255>
r/=255.0; // normalize to <0..1>
// convert to 3D
xx=r*cos(a)*cos(b);
yy=r*sin(a)*cos(b);
zz=r* sin(b);
// store to pointcloud
pnt.add(xx);
pnt.add(yy);
pnt.add(zz);
}
// clean up
delete bmp;
}
Here preview for 180x90 deg:
and preview for 360x180 deg:
Not sure which one is correct (as I do not have any context to your map) but the first option looks more correct to me ...
In case its the second just use different numbers (doubled) for the interpolation in bullet #1
Also if you want to remove the background just ignore r==1 pixels:
simply by testing the intensity to max value (before normalization) in my case by adding this line:
if (r==255) continue;
after this one
r=DWORD(p[x]&255);
In your case (you have <0..1> already) you should test r>=0.9999 or something like that instead.

Isometric tilemap using canvas (with click detection)

I am currently developing a game, which requires a map consisting of various tile images. I managed to make them display correctly (see second image) but I am now unsure of how to calculate the clicked tile from the mouse position.
Are there any existing libraries for this purpose?
Please also note, that the tile images aren't drawn perfectly "corner-facing-camera", they are slightly rotated clockwise.
Isometric Transformations
Define a projection
Isometric display is the same as standard display, the only thing that has changed is the direction of the x and y axis. Normally the x axis is defined as (1,0) one unit across and zero down and the y axis is (0,1) zero units across and one down. For isometric (strictly speaking your image is a dimetric projection) you will have something like x axis (0.5,1) and y axis (-1,0.5)
The Matrix
From this you can create a rendering matrix with 6 values Two each for both axes and two for the origin, which I will ignore for now (the origin) and just use the 4 for the axis and assume that the origin is always at 0,0
var dimetricMatrix = [0.5,1.0,-1,0.5]; // x and y axis
Matrix transformation
From that you can get a point on the display that matches a given isometric coordinate. Lets say the blocks are 200 by 200 pixels and that you address each block by the block x and y. Thus the block in the bottom of your image is at x = 2 and y = 1 (the first top block is x = 0, y = 0)
Using the matrix we can get the pixel location of the block
var blockW = 200;
var blockH = 200;
var locX = 2;
var locY = 1;
function getLoc(x,y){
var xx,yy; // intermediate results
var m = dimetricMatrix; // short cut to make code readable
x *= blockW; // scale up
y *= blockH;
// now move along the projection x axis
xx = x * m[0];
yy = x * m[1];
// then add the distance along the y axis
xx += y * m[2];
yy += y * m[3];
return {x : xx, y : yy};
}
Befoer I move on you can see that I have scaled the x and y by the block size. We can simplify the above code and include the scale 200,200 in the matrix
var xAxis = [0.5, 1.0];
var yAxis = [-1, 0.5];
var blockW = 200;
var blockH = 200;
// now create the matrix and scale the x and y axis
var dimetricMatrix = [
xAxis[0] * blockW,
xAxis[1] * blockW,
yAxis[0] * blockH,
yAxis[1] * blockH,
]; // x and y axis
The matrix holds the scale in the x and y axis so that the two numbers for x axis tell us the direction and length of a transformed unit.
Simplify function
And redo the getLoc function for speed and efficiency
function transformPoint(point,matrix,result){
if(result === undefined){
result = {};
}
// now move along the projection x axis
result.x = point.x * matrix[0] + point.y * matrix[2];
result.y = point.x * matrix[1] + point.y * matrix[3];
return result;
}
So pass a point and get a transformed point back. The result argument allows you to pass an existing point and that saves having to allocate a new point if you are doing it often.
var point = {x : 2, y : 1};
var screen = transformPoint(point,dimetricMatrix);
// result is the screen location of the block
// next time
screen = transformPoint(point,dimetricMatrix,screen); // pass the screen obj
// to avoid those too
// GC hits that kill
// game frame rates
Inverting the Matrix
All that is handy but you need the reverse of what we just did. Luckily the way matrices work allows us to reverse the process by inverting the matrix.
function invertMatrix(matrix){
var m = matrix; // shortcut to make code readable
var rm = [0,0,0,0]; // resulting matrix
// get the cross product of the x and y axis. It is the area of the rectangle made by the
// two axis
var cross = m[0] * m[3] - m[1] * m[2]; // I call it the cross but most will call
// it the determinate (I think that cross
// product is more suited to geometry while
// determinate is for maths geeks)
rm[0] = m[3] / cross; // invert both axis and unscale (if cross is 1 then nothing)
rm[1] = -m[1] / cross;
rm[2] = -m[2] / cross;
rm[3] = m[0] / cross;
return rm;
}
Now we can invert our matrix
var dimetricMatrixInv = invertMatrix(dimetricMatrix); // get the invers
And now that we have the inverse matrix we can use the transform function to convert from a screen location to a block location
var screen = {x : 100, y : 200};
var blockLoc = transformPoint(screen, dimetricMatrixInv );
// result is the location of the block
The Matrix for rendering
For a bit of magic the transformation matrix dimetricMatrix can also be used by the 2D canvas, but you need to add the origin.
var m = dimetricMatrix;
ctx.setTransform(m[0], m[1], m[2], m[3], 0, 0); // assume origin at 0,0
Now you can draw a box around the block with
ctx.strokeRect(2,1,1,1); // 3rd by 2nd block 1 by 1 block wide.
The origin
I have left out the origin in all the above, I will leave that up to you to find as there is a trillion pages online about matrices as all 2D and 3D rendering use them and getting a good deep knowledge of them is important if you wish to get into computer visualization.

Handling Proper Rotation of Cannon Body Based on Quaternion?

This one is bugging me quite a bit.
I'm trying to achieve rotation of a Cannon.Body based on the mouse input.
By using the (Cannon) Three FPS example to demonstrate, you can see what the issue is.
https://codepen.io/Raggar/pen/EggaZP
https://github.com/RaggarDK/Baby/blob/baby/pl.js
When you run the code and enable pointerlockcontrols by clicking on the "click to play" area and press W for 1 second to get the sphere into the view of the camera, you'll see that the sphere moves according to the WASD keys by applying velocity. If you move the mouse, the quaternion is applied to the Body, and the proper velocity is calculated.
Now turn 180 degrees, and the rotation on the X axis is now negated somehow.
When moving the mouse up, the sphere rotates down.
How would one fix such issue? Maybe I'm doing something wrong elsewhere, that might mess with the quaternion?
Maybe I should mention, in the playercontroller(pl.js), I'm applying the rotation to the sphereBody, instead of the yaw- and pitchObjects.
Relevant code from pl.js (Line 49):
var onMouseMove = function ( event ) {
if ( scope.enabled === false ) return;
var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
cannonBody.rotation.y -= movementX * 0.002;
cannonBody.rotation.x -= movementY * 0.002;
cannonBody.rotation.x = Math.max( - PI_2, Math.min( PI_2, cannonBody.rotation.x ) );
//console.log(cannonBody.rotation);
};
And (Line 174):
euler.x = cannonBody.rotation.x;
euler.y = cannonBody.rotation.y;
euler.order = "XYZ";
quat.setFromEuler(euler);
inputVelocity.applyQuaternion(quat);
cannonBody.quaternion.copy(quat);
velocity.x = inputVelocity.x;
velocity.z = inputVelocity.z;
Inside the animate() function, codepen (Line 305):
testballMesh.position.copy(sphereBody.position);
testballMesh.quaternion.copy(sphereBody.quaternion);
The problem is the way you assign angles to and from the Quaternions. The quaternion x,y,z,w properties are not directly compatible with angles, so you need to convert.
This is how to set the angle around a given axis for a CANNON.Quaternion:
var axis = new CANNON.Vec3(1,0,0);
var angle = Math.PI / 3;
body.quaternion.setFromAxisAngle(axis, angle);
Extracting the Euler angles from quaternions is probably not be the best way to attack the second part of the problem. You could instead just store the rotation around X and Y axes when the user moves the mouse:
// Declare variables outside the mouse handler
var angleX=0, angleY=0;
// Inside the handler:
angleY -= movementX * 0.002;
angleX -= movementY * 0.002;
angleX = Math.max( - PI_2, Math.min( PI_2, angleX ) );
And then to get the rotation as a quaternion, use two quaternions separately (one for X angle and one for Y) and then combine them to one:
var quatX = new CANNON.Quaternion();
var quatY = new CANNON.Quaternion();
quatX.setFromAxisAngle(new CANNON.Vec3(1,0,0), angleX);
quatY.setFromAxisAngle(new CANNON.Vec3(0,1,0), angleY);
var quaternion = quatY.mult(quatX);
quaternion.normalize();
To apply the quaternion to your velocity vector:
var rotatedVelocity = quaternion.vmult(inputVelocity);
Pro tip: don't use Euler angles if you can avoid them. They usually cause more problems than they solve.

Emulate texture2D in Javascript

I have written a shader than transforms vertex positions by a heightmap texture. Because the geometry is being transformed on the vertex shader, I can't use traditional picking algorithms in javascript without reverse engineering the shader to get the vertices into their transformed positions. I seem to be having a problem with my understanding of the texture2D function in GLSL. If you ignore the texture wrapping, how would you go about emulating the same function in JS? This is how I currently do it:
/**
* Gets the normalized value of an image's pixel from the x and y coordinates. The x and y coordinates expected here must be between 0 and 1
*/
sample( data, x, y, wrappingS, wrappingT )
{
var tempVec = new Vec2();
// Checks the texture wrapping and modifies the x and y accordingly
this.clampCoords( x, y, wrappingS, wrappingT, tempVec );
// Convert the normalized units into pixel coords
x = Math.floor( tempVec.x * data.width );
y = Math.floor( tempVec.y * data.height );
if ( x >= data.width )
x = data.width - 1;
if ( y >= data.height )
y = data.height - 1;
var val= data.data[ ((data.width * y) + x) * 4 ];
return val / 255.0;
}
This function seems to produce the right results. I have a texture that is 409 pixels wide by 434 pixels high. I coloured the image black except the very last pixel which I coloured red (408, 434). So when I call my sampler function in JS:
this.sample(imgData, 0.9999, 0.9999. wrapS, wrapT)
The result is 1. Which to me is correct as its refering to the red pixel.
However this doesn't seem to be what GLSL gives me. In GLSL I use this (as a test):
float coarseHeight = texture2D( heightfield, vec2( 0.9999, 0.9999 ) ).r;
Which I would expect coarseHeight should be 1 as well - but instead its 0. I don't understand this... Could someone give me some insight into where I'm going wrong?
You may already noticed that any rendered textures are y mirrored.
OpenGL and by that WebGL texture origin is in the lower-left corner, where as your buffer data when loaded using a canvas 2d method has a upper-left corner origin.
So you either need to rewrite your buffer or invert your v coord.

2D game algorithm to calculate a bullet's needed speed to hit target?

I have a rather simple bird's-view 2D game where tower sprites defend against incoming moving sprites by shooting a bullet at them. My question: How do I calculate the needed bullet speed for the bullet to reach its moving target, provided that the bullet will always have the same defined speed?
I'm using JavaScript and have these sprite variables (among others):
sprite.x, sprite.y, sprite.width, sprite.height, sprite.speedX (i.e. velocity), sprite.speedY... so I have the objects originSprite, targetSprite and bulletSprite, all with these type of values, and I need to set the right bulletSprite speed values.
Probably for it to look good, the bullet would start at the outside of the originSprite (or some defined radius, though I guess starting from the originSprite center would also work), but its bullet center would try hit into the center of the targetSprite or so. Note there's no gravity or anything in this world. (Perhaps I should have my sprites variables using angle and velocity but right now I'm using speedX and speedY...)
Thanks so much!
Treat the targets sprite as a straight line in a 2 dimensional room where:
A(time) = (sprite.positionX + sprite.speedX * time, sprite.positionX + sprite.speedX * time)
As your bullet have constant speed you also know:
bullet.speedX^2 + bullet.speedY^2 = bullet.definedSpeed^2
Then you can also calculate a straight line for the bullet:
B(time) = (bullet.positionX + bullet.speedX * time, bullet.positionX + bullet.speedX * time)
And you know that both lines interset somewhere:
A(time) = B(time)
Then it's up to you to solve those equations with your given values and seek a minimum for time.
Some physical insight
1 ) For the target being a "Point Object"
So you have to solve the VECTOR equation
Positionbullet [ time=t1 > t0 ] == Positiontarget [ time=t1 > t0 ] -- (Eq 1)
Where the positions are given by the motion (also VECTOR) equations
Positionobject [ t ] = Positionobject [ t0 ] + Speedobject * ( t - t0 )
Now, the condition for the bullet to be able to reach the target is that the Eq 1 has solutions for x and y. Let's write down the equation for x:
Xbullet [ t0 ] + SpeedXbullet * ( t - t0 ) = Xtarget [ t0 ] + SpeedXtarget * ( t - t0 )
So for the collision time we have
( tCollision - t0 ) = (xtarget [ t 0 ] - xbullet [ t0 ] ) / (SpeedXbullet - SpeedXtarget) -- (Eq 2)
As we need solutions with t > t0, that means that for having an intercept is enough that>
Sign ( xtarget[ t0 ] - xbullet[ t0 ] ) = Sign ( SpeedXbullet - SpeedXtarget ) -- (Eq 3)
Which tells us the evident fact that if an object is moving faster than the other, and in the same direction, they will eventually collide.
From Eq 2, you can see that for a given SpeedXtarget there exist infinite solutions (as already pointed out in other answers) for t and SpeedXbullet, so I think your specifications are not complete.
I guess (as stated in a commentary I made in another answer) thinking in a "tower defense" kind of game, that your bullets have a limited range.
So you need also another constraint:
Distance [ Positiontarget [ tCollision - t0 ] - Positionbullet [ t0 ] ] < BulletRange -- (Eq 4)
Which still permits infinite solutions, but bounded by an upper value for the Collision time, given by the fact that the target may abandon the range.
Further, the distance is given by
Distance[v,u]= +Sqrt[ (Vx-Ux)^2 + (Vx-Vy)^2 ]
So, Eq 4 becomes,
(Xtarget[tCollision - t0] - Xbullet[t0])2 + (Ytarget[tCollision - t0] - Ybullet[t0])2 < BulletRange2 -- (Eq 5)
Note that { Xbullet[t0] , Ybullet[t0} is the tower position.
Now, replacing in Eq 5 the values for the target position:
(Xtarget[t0] + SpeedXtarget * (t-t0) - Xbullet[t0])2 + (Ytarget[t0] + SpeedYtarget * (t-t0) - Ybullet[t0])2 < BulletRange2 -- (Eq 6)
Calling the initial distances:
Dxt0 = Xtarget[t0] - Xbullet[t0]
and
Dyt0 = Ytarget[t0] - Ybullet[t0]
Equation 6 becomes
(Dtx0 + SpeedXtarget * (t-t0) )2 + (Dty0 + SpeedYtarget * (t-t0))2 < BulletRange2 -- (Eq 7)
Which is a quadratic equation to be solved in t-t0. The positive solution will give us the largest time allowed for the collision. Afterwards the target will be out of range.
Now calling
Speedtarget 2 = SpeedXtarget 2 + SpeedYtarget 2
and
H = Dtx0 * SpeedXtarget + Dty0 * SpeedYtarget
TCollision Max = t0 - ( H
+/- Sqrt ( BulletRange2 * Speedtarget 2 - H2 ) ) / Speedtarget 2
So you need to produce the collision BEFORE this time. The sign of the
square root should be taken such as the time is greater than t0
After you select an appropriate flying time for your bullet from the visual
effects point of view, you can calculate the SpeedX and SpeedY for the bullet
from
SpeedXbullet = ( Xtarget [ t0 ] - Xbullet [ t0 ] ) / ( tCollision - t0 ) + SpeedXtarget
and
SpeedYbullet = ( Ytarget [ t0 ] - Ybullet [ t0 ] ) / ( tCollision - t0 ) + SpeedYtarget
2 ) For the target and tower being "Extensive Objects"
Now, it is trivial to generalize for the case of the target being a circle of radius R. What you get, is the equivalent of an "extended range" for the bullets. That extension is just R.
So, replacing BulletRange by (BulletRange + R) you get the new equations for the maximum allowed collision time.
If you also want to consider a radius for the cannons, the same considerations apply, giving a "double extended range
NewBulletRange = BulletRange + RTarget + RTower
Unlimited Range Bullets
In the case that you decide that some special bullets should not have range (and detection) limitations, there is still the screen border constraint. But it is a little more difficult to tackle. Should you need this kind of projectile, leave a comment and I'll try to do some math.
Using vectors can make the math around this seem a little simpler. Sylvester seems to be a promising implementation of vectors in JavaScript, but for the purpose of my example, I'll write my own vector functions. I'm also going to assume .x / .y are measured top/left corner.
// this is a "constant" - representing 10px motion per "time unit"
var bulletSpeed = 10;
// calculate the vector from our center to their center
var enemyVec = vec_sub(targetSprite.getCenter(), originSprite.getCenter());
// measure the "distance" the bullet will travel
var dist = vec_mag(enemyVec);
// adjust for target position based on the amount of "time units" to travel "dist"
// and the targets speed vector
enemyVec = vec_add(enemyVec, vec_mul(targetSprite.getSpeed(), dist/bulletSpeed));
// calculate trajectory of bullet
var bulletTrajectory = vec_mul(vec_normal(enemyVec), bulletSpeed);
// assign values
bulletSprite.speedX = bulletTrajectory.x;
bulletSprite.speedY = bulletTrajectory.y;
// functions used in the above example:
// getCenter and getSpeed return "vectors"
sprite.prototype.getCenter = function() {
return {
x: this.x+(this.width/2),
y: this.y+(this.height/2)
};
};
sprite.prototype.getSpeed = function() {
return {
x: this.speedX,
y: this.speedY
};
};
function vec_mag(vec) { // get the magnitude of the vector
return Math.sqrt( vec.x * vec.x + vec.y * vec.y);
}
function vec_sub(a,b) { // subtract two vectors
return { x: a.x-b.x, y: a.y-b.y };
}
function vec_add(a,b) { // add two vectors
return { x: a.x + b.x, y: a.y + b.y };
}
function vec_mul(a,c) { // multiply a vector by a scalar
return { x: a.x * c, y: a.y * c };
}
function vec_div(a,c) { // divide == multiply by 1/c
return vec_mul(a, 1.0/c);
}
function vec_normal(a) { // normalize vector
return vec_div(a, vec_mag(a));
}
Compute the distance between shooter and target: dist = sqrt((xt - xs)^2 + (yt - ys)^2)
Divide the x and y distances by the above one: nx = (xt - xs)/dist; ny = (yt - ys)/dist; (normalization of the vector)
Multiply the results by a factor to get n pixels per time unit, ie. a speed in each direction. It should give a constant speed in the wanted direction.
I assume that the target will move on a straight line with constant velocity.
If both the direction and the speed of the bullet are variable (i.e. you try to calculation speedX and speedY for the bullet), there are infinitely many solutions.
If you set a fixed direction, you simply intersect the two lines of the bullet and the target. From the distance between the current point of the target and the intersection point (and the target's speed) you can calculate the time the target will take to reach this intersection point.
From the distance between the origin of the bullet and the intersection point (and the previously calculated time) you can calculate the speed of the bullet.

Categories

Resources