Combine multiple rotation in matrix - javascript

I would like to use this JavaScript matrix library: Matrix3D
My target is to implement a function which takes the CSS transform properties as arguments and returns with the proper matrix3d() CSS transform declaration.
function 3d(x, y, z, rotateX, rotateY, rotateZ){
var m = Matrix3D.create();
Matrix3D.translateX(m, x);
Matrix3D.translateY(m, y);
Matrix3D.translateZ(m, z);
Matrix3D.rotateX(m,this.data.rotateX);
Matrix3D.rotateY(m,this.data.rotateY);
Matrix3D.rotateZ(m,this.data.rotateZ);
return Matrix3D.toTransform3D(m);
}
It works fine for the x,y,z and the rotateZ parameters, but it is unable to merge the rotation matrices into one matrix, instead it overwrites the rotation.
Could you help me how should I combine matrices to behave in the right way?
UPDATE #1
I just found out that I should need to create a quaternion from the three euler rotation axis. euler to quaternion
function eulerToQuaternion(rotateX, rotateY, rotateZ) {
// Assuming the angles are in radians.
var c1 = Math.cos(rotateX / 2),
s1 = Math.sin(rotateX / 2),
c2 = Math.cos(rotateY / 2),
s2 = Math.sin(rotateY / 2),
c3 = Math.cos(rotateZ / 2),
s3 = Math.sin(rotateZ / 2),
c1c2 = c1 * c2,
s1s2 = s1 * s2,
w = c1c2 * c3 - s1s2 * s3,
x = c1c2 * s3 + s1s2 * c3,
y = s1 * c2 * c3 + c1 * s2 * s3,
z = c1 * s2 * c3 - s1 * c2 * s3;
return [w, x, y, z]
}
function deg2rad(deg) {
return deg * (Math.PI / 180);
};
console.log(eulerToQuaternion(deg2rad(45), 0, deg2rad(45)));
But here I'm stuck again. How can I add this quaternion to my matrix?

Found the solution:
function a(x, y, z, scaleX, scaleY, rotateX, rotateY, rotateZ) {
var D = 2;
var Y = Math.cos(rotateX * (Math.PI / 180)).toFixed(D),
Z = Math.sin(rotateX * (Math.PI / 180)).toFixed(D),
b = Math.cos(rotateY * (Math.PI / 180)).toFixed(D),
F = Math.sin(rotateY * (Math.PI / 180)).toFixed(D),
I = Math.cos(rotateZ * (Math.PI / 180)).toFixed(D),
P = Math.sin(rotateZ * (Math.PI / 180)).toFixed(D);
var a = new Array(16);
a[0] = b * I * scaleX;
a[1] = -1 * P;
a[2] = F;
a[3] = 0;
a[4] = P;
a[5] = Y * I * scaleY;
a[6] = Z;
a[7] = 0;
a[8] = -1 * F;
a[9] = -1 * Z;
a[10] = b * Y;
a[11] = 0;
a[12] = x;
a[13] = y;
a[14] = z;
a[15] = 1;
console.log("transform: matrix3d(" + a[0] + "," + a[1] + "," + a[2] + "," + a[3] + "," + a[4] + "," + a[5] + "," + a[6] + "," + a[7] + "," + a[8] + "," + a[9] + "," + a[10] + "," + a[11] + "," + a[12] + "," + a[13] + "," + a[14] + "," + a[15] + ");");
}

What about passing 2 parameters to the Matrix3D.rotateXYZ() method like below
Matrix3D.rotateX(m, this.data.rotateX)
I don't know which version you are using, but that method needs 2 parameters according to https://gist.github.com/f5io/7466669.
If you omit the first parameter, this.data.rotateX will be understood as a result array, not a rotation, and this is not what you wanted to do.

Related

Balls bouncing off of each other

I am working on this script where I have x-number bouncing balls (in this case 20 balls) in a canvas.
My question is, how do I make them bounce off each other when they hit, as well as bounce off the yellow rectangle when they hit it?
var mycanvas =document.getElementById("mycanvas");
var ctx=mycanvas.getContext("2d");
var w=500,h=500;
mycanvas.height=h;
mycanvas.width=w;
var ball=[];
function Ball(x,y,r,c,vx,vy){
this.x=x; //starting x coordinate
this.y=y; //starting x coordinate
this.r=r; //radius
this.c=c; //color
this.vx=vx; // x direction speed
this.vy=vy; // y direction speed
this.update=function(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI*2, false);
ctx.fillStyle = this.c;
ctx.fill();
ctx.closePath();
this.x += this.vx;
this.y += this.vy;
//changing direction on hitting wall
if(this.y>=(w-10)||this.y<=10){
this.vy=-this.vy;
}
if(this.x>=(h-10)||this.x<=10){
this.vx=-this.vx;
}
}
}
function clearCanvas(){
ctx.clearRect(0, 0, w, h);
}
var count;
for (count = 0; count < 20; count++) {
var rndColor=Math.floor((Math.random() * 9) + 1); //random color
ball[count]= new Ball(Math.floor((Math.random() * 490) + 1),Math.floor((Math.random() * 490)+1),5,'red',5,5);
}
function update(){
var i;
clearCanvas();
//draw rectangle
ctx.rect(250, 200, 10, 100);
ctx.fillStyle = 'yellow';
ctx.fill();
for(i=0;i<count;i++){
ball[i].update();
}
}
setInterval(update, 1000/60);
There are several methods you can use. The following methods are about the simplest.
Update
I have added an example that uses the second method. See snippet at the bottom.
Defining the balls
Each example is as an object called Ball.
// x,y position of center,
// vx,vy is velocity,
// r is radius defaults 45,
// m is mass defaults to the volume of the sphere of radius r
function Ball(x, y, vx, vy, r = 45, m = (4 / 3 * Math.PI * (r ** 3)) {
this.r = r;
this.m = m;
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
}
Ball.prototype = {
// add collision functions here
};
The code assumes the balls are touching.
Elastic collisions
The logic used can be found at wikis elastic collision page
The calculation splits the forces into two parts for each ball. (4 in total for 2 balls)
The transfer of energy along the line between the balls,
The adjustment of energy per ball along the tangent of the collision point
Equal mass
Each ball has the same mass which means that the transfer of energy is balanced and can be ignored
After the function is called each ball has a new velocity vector.
Note that if you call collision and the velocities mean that the balls are moving away from each other (collision paradox) then the result will have the balls moving into each other (resolution paradox)
To keep the math simple the vector magnitudes u1, u2, u3, and u4 are converted into a unit that is the length of the line between the ball centers (square root of d)
collide(b) { // b is the ball that the collision is with
const a = this;
const x = a.x - b.x;
const y = a.y - b.y;
const d = x * x + y * y;
const u1 = (a.vx * x + a.vy * y) / d; // From this to b
const u2 = (x * a.vy - y * a.vx) / d; // Adjust self along tangent
const u3 = (b.vx * x + b.vy * y) / d; // From b to this
const u4 = (x * b.vy - y * b.vx) / d; // Adjust b along tangent
// set new velocities
b.vx = x * u1 - y * u4;
b.vy = y * u1 + x * u4;
a.vx = x * u3 - y * u2;
a.vy = y * u3 + x * u2;
},
Different masses
Each ball has its own mass and thus the transfer needs to calculate the amount of energy related to the mass that is transferred.
Only the energy transferred along the line between the balls is effect by the mass differences
collideMass(b) {
const a = this;
const m1 = a.m;
const m2 = b.m;
const x = a.x - b.x;
const y = a.y - b.y;
const d = x * x + y * y;
const u1 = (a.vx * x + a.vy * y) / d;
const u2 = (x * a.vy - y * a.vx) / d;
const u3 = (b.vx * x + b.vy * y) / d;
const u4 = (x * b.vy - y * b.vx) / d;
const mm = m1 + m2;
const vu3 = (m1 - m2) / mm * u1 + (2 * m2) / mm * u3;
const vu1 = (m2 - m1) / mm * u3 + (2 * m1) / mm * u1;
b.vx = x * vu1 - y * u4;
b.vy = y * vu1 + x * u4;
a.vx = x * vu3 - y * u2;
a.vy = y * vu3 + x * u2;
},
Example
Simple ball collision example. Balls bound by lines (Note lines have an outside and inside, if looking from the start to the end the inside is on the right)
Collisions are fully resolved in chronological order between frames. The time used is a frame where 0 is the previous frame and 1 is the current frame.
canvas.width = innerWidth -20;
canvas.height = innerHeight -20;
const ctx = canvas.getContext("2d");
const GRAVITY = 0;
const WALL_LOSS = 1;
const BALL_COUNT = 20; // approx as will not add ball if space can not be found
const MIN_BALL_SIZE = 13;
const MAX_BALL_SIZE = 20;
const VEL_MIN = 1;
const VEL_MAX = 5;
const MAX_RESOLUTION_CYCLES = 100;
Math.TAU = Math.PI * 2;
Math.rand = (min, max) => Math.random() * (max - min) + min;
Math.randI = (min, max) => Math.random() * (max - min) + min | 0; // only for positive numbers 32bit signed int
Math.randItem = arr => arr[Math.random() * arr.length | 0]; // only for arrays with length < 2 ** 31 - 1
// contact points of two circles radius r1, r2 moving along two lines (a,e)-(b,f) and (c,g)-(d,h) [where (,) is coord (x,y)]
Math.circlesInterceptUnitTime = (a, e, b, f, c, g, d, h, r1, r2) => { // args (x1, y1, x2, y2, x3, y3, x4, y4, r1, r2)
const A = a * a, B = b * b, C = c * c, D = d * d;
const E = e * e, F = f * f, G = g * g, H = h * h;
var R = (r1 + r2) ** 2;
const AA = A + B + C + F + G + H + D + E + b * c + c * b + f * g + g * f + 2 * (a * d - a * b - a * c - b * d - c * d - e * f + e * h - e * g - f * h - g * h);
const BB = 2 * (-A + a * b + 2 * a * c - a * d - c * b - C + c * d - E + e * f + 2 * e * g - e * h - g * f - G + g * h);
const CC = A - 2 * a * c + C + E - 2 * e * g + G - R;
return Math.quadRoots(AA, BB, CC);
}
Math.quadRoots = (a, b, c) => { // find roots for quadratic
if (Math.abs(a) < 1e-6) { return b != 0 ? [-c / b] : [] }
b /= a;
var d = b * b - 4 * (c / a);
if (d > 0) {
d = d ** 0.5;
return [0.5 * (-b + d), 0.5 * (-b - d)]
}
return d === 0 ? [0.5 * -b] : [];
}
Math.interceptLineBallTime = (x, y, vx, vy, x1, y1, x2, y2, r) => {
const xx = x2 - x1;
const yy = y2 - y1;
const d = vx * yy - vy * xx;
if (d > 0) { // only if moving towards the line
const dd = r / (xx * xx + yy * yy) ** 0.5;
const nx = xx * dd;
const ny = yy * dd;
return (xx * (y - (y1 + nx)) - yy * (x -(x1 - ny))) / d;
}
}
const balls = [];
const lines = [];
function Line(x1,y1,x2,y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
Line.prototype = {
draw() {
ctx.moveTo(this.x1, this.y1);
ctx.lineTo(this.x2, this.y2);
},
reverse() {
const x = this.x1;
const y = this.y1;
this.x1 = this.x2;
this.y1 = this.y2;
this.x2 = x;
this.y2 = y;
return this;
}
}
function Ball(x, y, vx, vy, r = 45, m = 4 / 3 * Math.PI * (r ** 3)) {
this.r = r;
this.m = m
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
}
Ball.prototype = {
update() {
this.x += this.vx;
this.y += this.vy;
this.vy += GRAVITY;
},
draw() {
ctx.moveTo(this.x + this.r, this.y);
ctx.arc(this.x, this.y, this.r, 0, Math.TAU);
},
interceptLineTime(l, time) {
const u = Math.interceptLineBallTime(this.x, this.y, this.vx, this.vy, l.x1, l.y1, l.x2, l.y2, this.r);
if(u >= time && u <= 1) {
return u;
}
},
checkBallBallTime(t, minTime) {
return t > minTime && t <= 1;
},
interceptBallTime(b, time) {
const x = this.x - b.x;
const y = this.y - b.y;
const d = (x * x + y * y) ** 0.5;
if(d > this.r + b.r) {
const times = Math.circlesInterceptUnitTime(
this.x, this.y,
this.x + this.vx, this.y + this.vy,
b.x, b.y,
b.x + b.vx, b.y + b.vy,
this.r, b.r
);
if(times.length) {
if(times.length === 1) {
if(this.checkBallBallTime(times[0], time)) { return times[0] }
return;
}
if(times[0] <= times[1]) {
if(this.checkBallBallTime(times[0], time)) { return times[0] }
if(this.checkBallBallTime(times[1], time)) { return times[1] }
return
}
if(this.checkBallBallTime(times[1], time)) { return times[1] }
if(this.checkBallBallTime(times[0], time)) { return times[0] }
}
}
},
collideLine(l, time) {
const x1 = l.x2 - l.x1;
const y1 = l.y2 - l.y1;
const d = (x1 * x1 + y1 * y1) ** 0.5;
const nx = x1 / d;
const ny = y1 / d;
const u = (this.vx * nx + this.vy * ny) * 2;
this.x += this.vx * time;
this.y += this.vy * time;
this.vx = (nx * u - this.vx) * WALL_LOSS;
this.vy = (ny * u - this.vy) * WALL_LOSS;
this.x -= this.vx * time;
this.y -= this.vy * time;
},
collide(b, time) {
const a = this;
const m1 = a.m;
const m2 = b.m;
const x = a.x - b.x
const y = a.y - b.y
const d = (x * x + y * y);
const u1 = (a.vx * x + a.vy * y) / d
const u2 = (x * a.vy - y * a.vx ) / d
const u3 = (b.vx * x + b.vy * y) / d
const u4 = (x * b.vy - y * b.vx ) / d
const mm = m1 + m2;
const vu3 = (m1 - m2) / mm * u1 + (2 * m2) / mm * u3;
const vu1 = (m2 - m1) / mm * u3 + (2 * m1) / mm * u1;
a.x = a.x + a.vx * time;
a.y = a.y + a.vy * time;
b.x = b.x + b.vx * time;
b.y = b.y + b.vy * time;
b.vx = x * vu1 - y * u4;
b.vy = y * vu1 + x * u4;
a.vx = x * vu3 - y * u2;
a.vy = y * vu3 + x * u2;
a.x = a.x - a.vx * time;
a.y = a.y - a.vy * time;
b.x = b.x - b.vx * time;
b.y = b.y - b.vy * time;
},
doesOverlap(ball) {
const x = this.x - ball.x;
const y = this.y - ball.y;
return (this.r + ball.r) > ((x * x + y * y) ** 0.5);
}
}
function canAdd(ball) {
for(const b of balls) {
if (ball.doesOverlap(b)) { return false }
}
return true;
}
function create(bCount) {
lines.push(new Line(-10, 10, ctx.canvas.width + 10, 5));
lines.push((new Line(-10, ctx.canvas.height - 2, ctx.canvas.width + 10, ctx.canvas.height - 10)).reverse());
lines.push((new Line(10, -10, 4, ctx.canvas.height + 10)).reverse());
lines.push(new Line(ctx.canvas.width - 3, -10, ctx.canvas.width - 10, ctx.canvas.height + 10));
while (bCount--) {
let tries = 100;
debugger
while (tries--) {
const dir = Math.rand(0, Math.TAU);
const vel = Math.rand(VEL_MIN, VEL_MAX);
const ball = new Ball(
Math.rand(MAX_BALL_SIZE + 10, canvas.width - MAX_BALL_SIZE - 10),
Math.rand(MAX_BALL_SIZE + 10, canvas.height - MAX_BALL_SIZE - 10),
Math.cos(dir) * vel,
Math.sin(dir) * vel,
Math.rand(MIN_BALL_SIZE, MAX_BALL_SIZE),
);
if (canAdd(ball)) {
balls.push(ball);
break;
}
}
}
}
function resolveCollisions() {
var minTime = 0, minObj, minBall, resolving = true, idx = 0, idx1, after = 0, e = 0;
while(resolving && e++ < MAX_RESOLUTION_CYCLES) { // too main ball may create very lone resolution cycle. e limits this
resolving = false;
minObj = undefined;
minBall = undefined;
minTime = 1;
idx = 0;
for(const b of balls) {
idx1 = idx + 1;
while(idx1 < balls.length) {
const b1 = balls[idx1++];
const time = b.interceptBallTime(b1, after);
if(time !== undefined) {
if(time <= minTime) {
minTime = time;
minObj = b1;
minBall = b;
resolving = true;
}
}
}
for(const l of lines) {
const time = b.interceptLineTime(l, after);
if(time !== undefined) {
if(time <= minTime) {
minTime = time;
minObj = l;
minBall = b;
resolving = true;
}
}
}
idx ++;
}
if(resolving) {
if (minObj instanceof Ball) {
minBall.collide(minObj, minTime);
} else {
minBall.collideLine(minObj, minTime);
}
after = minTime;
}
}
}
create(BALL_COUNT);
mainLoop();
function mainLoop() {
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
resolveCollisions();
for(const b of balls) { b.update() }
ctx.strokeStyle = "#000";
ctx.beginPath();
for(const b of balls) { b.draw() }
for(const l of lines) { l.draw() }
ctx.stroke();
requestAnimationFrame(mainLoop);
}
<canvas id="canvas"></canvas>
To bounce balls off of one another, he's what you need to know
Have the balls collided? The way to determine is to measure the distance between the centers of the two circles. If this is less than the combined radiuses, the balls have collided
What direction should they have after colliding? Use use atan2 to calculate the angle between the centers of the two balls. Then set them in opposite directions on that angle, in a way that they don't end up deeper within each other. Of course, this simple solution ignores existing momentum that the balls may have. But doing the momentum calculation (which involves mass, speed, and current angle) is more complicated.

Significant error when approximating elliptical arcs with bezier curves on canvas with javascript

I'm trying to convert svg path to canvas in javascript, however it's really hard to map svg path elliptical arcs to canvas path. One of the ways is to approximate using multiple bezier curves.
I have successfully implemented the approximation of elliptical arcs with bezier curves however the approximation isn't very accurate.
My code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
ctx.strokeWidth = 2;
ctx.strokeStyle = "#000000";
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max)
}
function svgAngle(ux, uy, vx, vy ) {
var dot = ux*vx + uy*vy;
var len = Math.sqrt(ux*ux + uy*uy) * Math.sqrt(vx*vx + vy*vy);
var ang = Math.acos( clamp(dot / len,-1,1) );
if ( (ux*vy - uy*vx) < 0)
ang = -ang;
return ang;
}
function generateBezierPoints(rx, ry, phi, flagA, flagS, x1, y1, x2, y2) {
var rX = Math.abs(rx);
var rY = Math.abs(ry);
var dx2 = (x1 - x2)/2;
var dy2 = (y1 - y2)/2;
var x1p = Math.cos(phi)*dx2 + Math.sin(phi)*dy2;
var y1p = -Math.sin(phi)*dx2 + Math.cos(phi)*dy2;
var rxs = rX * rX;
var rys = rY * rY;
var x1ps = x1p * x1p;
var y1ps = y1p * y1p;
var cr = x1ps/rxs + y1ps/rys;
if (cr > 1) {
var s = Math.sqrt(cr);
rX = s * rX;
rY = s * rY;
rxs = rX * rX;
rys = rY * rY;
}
var dq = (rxs * y1ps + rys * x1ps);
var pq = (rxs*rys - dq) / dq;
var q = Math.sqrt( Math.max(0,pq) );
if (flagA === flagS)
q = -q;
var cxp = q * rX * y1p / rY;
var cyp = - q * rY * x1p / rX;
var cx = Math.cos(phi)*cxp - Math.sin(phi)*cyp + (x1 + x2)/2;
var cy = Math.sin(phi)*cxp + Math.cos(phi)*cyp + (y1 + y2)/2;
var theta = svgAngle( 1,0, (x1p-cxp) / rX, (y1p - cyp)/rY );
var delta = svgAngle(
(x1p - cxp)/rX, (y1p - cyp)/rY,
(-x1p - cxp)/rX, (-y1p-cyp)/rY);
delta = delta - Math.PI * 2 * Math.floor(delta / (Math.PI * 2));
if (!flagS)
delta -= 2 * Math.PI;
var n1 = theta, n2 = delta;
// E(n)
// cx +acosθcosη−bsinθsinη
// cy +asinθcosη+bcosθsinη
function E(n) {
var enx = cx + rx * Math.cos(phi) * Math.cos(n) - ry * Math.sin(phi) * Math.sin(n);
var eny = cy + rx * Math.sin(phi) * Math.cos(n) + ry * Math.cos(phi) * Math.sin(n);
return {x: enx,y: eny};
}
// E'(n)
// −acosθsinη−bsinθcosη
// −asinθsinη+bcosθcosη
function Ed(n) {
var ednx = -1 * rx * Math.cos(phi) * Math.sin(n) - ry * Math.sin(phi) * Math.cos(n);
var edny = -1 * rx * Math.sin(phi) * Math.sin(n) + ry * Math.cos(phi) * Math.cos(n);
return {x: ednx, y: edny};
}
var n = [];
n.push(n1);
var interval = Math.PI/4;
while(n[n.length - 1] + interval < n2)
n.push(n[n.length - 1] + interval)
n.push(n2);
function getCP(n1, n2) {
var en1 = E(n1);
var en2 = E(n2);
var edn1 = Ed(n1);
var edn2 = Ed(n2);
var alpha = Math.sin(n2 - n1) * (Math.sqrt(4 + 3 * Math.pow(Math.tan((n2 - n1)/2), 2)) - 1)/3;
console.log(en1, en2);
return {
cpx1: en1.x + alpha*edn1.x,
cpy1: en1.y + alpha*edn1.y,
cpx2: en2.x - alpha*edn2.x,
cpy2: en2.y - alpha*edn2.y,
en1: en1,
en2: en2
};
}
var cps = []
for(var i = 0; i < n.length - 1; i++) {
cps.push(getCP(n[i],n[i+1]));
}
return cps;
}
// M100,200
ctx.moveTo(100,200)
// a25,100 -30 0,1 50,-25
var rx = 25, ry=100 ,phi = -30 * Math.PI / 180, fa = 0, fs = 1, x = 100, y = 200, x1 = x + 50, y1 = y - 25;
var cps = generateBezierPoints(rx, ry, phi, fa, fs, x, y, x1, y1);
var limit = 4;
for(var i = 0; i < limit && i < cps.length; i++) {
ctx.bezierCurveTo(cps[i].cpx1, cps[i].cpy1,
cps[i].cpx2, cps[i].cpy2,
i < limit - 1 ? cps[i].en2.x : x1, i < limit - 1 ? cps[i].en2.y : y1);
}
ctx.stroke()
With the result:
The red line represents the svg path elliptical arc and the black line represents the approximation
How can I accurately draw any possible elliptical arc on canvas?
Update:
Forgot to mention the original source of the algorithm: https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/
So both bugs are simply:
n2 should be declare n2 = theta + delta;
The E and Ed functions should use rX rY rather than rx ry.
And that fixes everything. Though the original should have obviously opted to divide up the arcs into equal sized portions rather than pi/4 sized elements and then appending the remainder. Just find out how many parts it will need, then divide the range into that many parts of equal size, seems like a much more elegant solution, and because error goes up with length it would also be more accurate.
See: https://jsfiddle.net/Tatarize/4ro0Lm4u/ for working version.
It's not just off in that one respect it doesn't work most anywhere. You can see that depending on phi, it does a lot of variously bad things. It's actually shockingly good there. But, broken everywhere else too.
https://jsfiddle.net/Tatarize/dm7yqypb/
The reason is that the declaration of n2 is wrong and should read:
n2 = theta + delta;
https://jsfiddle.net/Tatarize/ba903pss/
But, fixing the bug in the indexing, it clearly does not scale up there like it should. It might be that arcs within the svg standard are scaled up so that there can certainly be a solution whereas in the relevant code they seem like they are clamped.
https://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
"If rx, ry and φ are such that there is no solution (basically, the
ellipse is not big enough to reach from (x1, y1) to (x2, y2)) then the
ellipse is scaled up uniformly until there is exactly one solution
(until the ellipse is just big enough)."
Testing this, since it does properly have code that should scale it up, I changed it green when that code got called. And it turns green when it screws up. So yeah, it's failure to scale for some reason:
https://jsfiddle.net/Tatarize/tptroxho/
Which means something is using rx rather than the scaled rX and it's the E and Ed functions:
var enx = cx + rx * Math.cos(phi) * Math.cos(n) - ry * Math.sin(phi) * Math.sin(n);
These rx references must read rX and rY for ry.
var enx = cx + rX * Math.cos(phi) * Math.cos(n) - rY * Math.sin(phi) * Math.sin(n);
Which finally fixes the last bug, QED.
https://jsfiddle.net/Tatarize/4ro0Lm4u/
I got rid of the canvas, moved everything to svg and animated it.
var svgNS = "http://www.w3.org/2000/svg";
var svg = document.getElementById("svg");
var arcgroup = document.getElementById("arcgroup");
var curvegroup = document.getElementById("curvegroup");
function doArc() {
while (arcgroup.firstChild) {
arcgroup.removeChild(arcgroup.firstChild);
} //clear old svg data. -->
var d = document.createElementNS(svgNS, "path");
//var path = "M100,200 a25,100 -30 0,1 50,-25"
var path = "M" + x + "," + y + "a" + rx + " " + ry + " " + phi + " " + fa + " " + fs + " " + " " + x1 + " " + y1;
d.setAttributeNS(null, "d", path);
arcgroup.appendChild(d);
}
function doCurve() {
var cps = generateBezierPoints(rx, ry, phi * Math.PI / 180, fa, fs, x, y, x + x1, y + y1);
while (curvegroup.firstChild) {
curvegroup.removeChild(curvegroup.firstChild);
} //clear old svg data. -->
var d = document.createElementNS(svgNS, "path");
var limit = 4;
var path = "M" + x + "," + y;
for (var i = 0; i < limit && i < cps.length; i++) {
if (i < limit - 1) {
path += "C" + cps[i].cpx1 + " " + cps[i].cpy1 + " " + cps[i].cpx2 + " " + cps[i].cpy2 + " " + cps[i].en2.x + " " + cps[i].en2.y;
} else {
path += "C" + cps[i].cpx1 + " " + cps[i].cpy1 + " " + cps[i].cpx2 + " " + cps[i].cpy2 + " " + (x + x1) + " " + (y + y1);
}
}
d.setAttributeNS(null, "d", path);
d.setAttributeNS(null, "stroke", "#000");
curvegroup.appendChild(d);
}
setInterval(phiClock, 50);
function phiClock() {
phi += 1;
doCurve();
doArc();
}
doCurve();
doArc();

How do I make image face mouse in javascript?

I'm trying to use javascript to make an image face the mouse of the user, I'm close but my calculations are a bit off (the rotation on the x-axis seems to be inverted). I based my code of this link: rotate3d shorthand
javascript:
$(document).mousemove(function(e){
pageheight = $(document).height();
pagewidth = $(document).width();
widthpercentage = e.pageX / pagewidth;
heightpercentage = e.pageY / pageheight;
specialW = (widthpercentage * 180 - 90);
specialH = (heightpercentage * 180 - 90);
function toRadians(degrees) {
radians = degrees * (Math.PI /180);
return radians;
}
function matrix(x, y, z, rads) {
var sc = Math.sin(rads / 2) * Math.cos(rads / 2);
var sq = Math.pow(Math.sin(rads / 2), 2);
var array = [];
var a1 = 1 - 2 * (Math.pow(y, 2) + Math.pow(z, 2)) * sq,
a2 = 2 * (x * y * sq - z * sc),
a3 = 2 * (x * z * sq + y * sc),
a4 = 0,
b1 = 2 * (x * y * sq + z * sc),
b2 = 1 - 2 * (Math.pow(x, 2) + Math.pow(z, 2)) * sq,
b3 = 2 * (y * z * sq - x * sc),
b4 = 0,
c1 = 2 * (x * z * sq - y * sc),
c2 = 2 * (y * z * sq + x * sc),
c3 = 1 - 2 * (Math.pow(x, 2) + Math.pow(y, 2)) * sq,
c4 = 0,
d1 = 0,
d2 = 0,
d3 = 0,
d4 = 1;
array.push(a1, b1, c1, d1, a2, b2, c2, d2, a3, b3, c3, d3, a4, b4, c4, d4);
return array;
}
xmatrix = matrix(1, 0, 0, toRadians(specialH));
ymatrix = matrix(0, 1, 0, toRadians(specialW));
function multiply(xarray, yarray) {
var newarray = [];
var a1 = (xarray[0] * yarray[0]) + (xarray[4] * yarray[1]) + (xarray[8] * yarray[2]) + (xarray[12] * yarray[3]),
a2 = (xarray[0] * yarray[4]) + (xarray[4] * yarray[5]) + (xarray[8] * yarray[6]) + (xarray[12] * yarray[7]),
a3 = (xarray[0] * yarray[8]) + (xarray[4] * yarray[9]) + (xarray[8] * yarray[10]) + (xarray[12] * yarray[11]),
a4 = (xarray[0] * yarray[12]) + (xarray[4] * yarray[13]) + (xarray[8] * yarray[14]) + (xarray[12] * yarray[15]),
b1 = (xarray[1] * yarray[0]) + (xarray[5] * yarray[1]) + (xarray[9] * yarray[2]) + (xarray[13] * yarray[3]),
b2 = (xarray[1] * yarray[4]) + (xarray[5] * yarray[5]) + (xarray[9] * yarray[6]) + (xarray[13] * yarray[7]),
b3 = (xarray[1] * yarray[8]) + (xarray[5] * yarray[9]) + (xarray[9] * yarray[10]) + (xarray[13] * yarray[11]),
b4 = (xarray[1] * yarray[12]) + (xarray[5] * yarray[13]) + (xarray[9] * yarray[14]) + (xarray[13] * yarray[15]),
c1 = (xarray[2] * yarray[0]) + (xarray[6] * yarray[1]) + (xarray[10] * yarray[2]) + (xarray[14] * yarray[3]),
c2 = (xarray[2] * yarray[4]) + (xarray[6] * yarray[5]) + (xarray[10] * yarray[6]) + (xarray[14] * yarray[7]),
c3 = (xarray[2] * yarray[8]) + (xarray[6] * yarray[9]) + (xarray[10] * yarray[10]) + (xarray[14] * yarray[11]),
c4 = (xarray[2] * yarray[12]) + (xarray[6] * yarray[13]) + (xarray[10] * yarray[14]) + (xarray[14] * yarray[15]),
d1 = (xarray[3] * yarray[0]) + (xarray[7] * yarray[1]) + (xarray[11] * yarray[2]) + (xarray[15] * yarray[3]),
d2 = (xarray[3] * yarray[4]) + (xarray[7] * yarray[5]) + (xarray[11] * yarray[6]) + (xarray[15] * yarray[7]),
d3 = (xarray[3] * yarray[8]) + (xarray[7] * yarray[9]) + (xarray[11] * yarray[10]) + (xarray[15] * yarray[11]),
d4 = (xarray[3] * yarray[12]) + (xarray[7] * yarray[13]) + (xarray[11] * yarray[14]) + (xarray[15] * yarray[15]);
newarray.push(a1, b1, c1, d1, a2, b2, c2, d2, a3, b3, c3, d3, a4, b4, c4, d4);
return newarray;
}
var newmatrix = multiply(xmatrix, ymatrix);
$('#page1 img').css('transform', 'matrix3d(' + newmatrix[0] + ',' + newmatrix[1] + ',' + newmatrix[2] + ',' + newmatrix[3] + ',' + newmatrix[4] + ',' + newmatrix[5] + ',' + newmatrix[6] + ',' + newmatrix[7] + ',' + newmatrix[8] + ',' + newmatrix[9] + ',' + newmatrix[10] + ',' + newmatrix[11] + ',' + newmatrix[12] + ',' + newmatrix[13] + ',' + newmatrix[14] + ',' + newmatrix[15] + ')');
});
I know it's a lot of code, but if I could get a second a opinion, It would be greatly appreciated. Thanks
No plugins please.
I actually just edited the matrix() function and added this right before array.push:
if ( y = 1 ) {
a3 = a3 * -1;
c1 = c1 * -1;
}
And this fixed the issue.

Pixel by pixel collision detection pinball

I'm currently working on a Pinball game using the HTML5 Canvas and JavaScript. Right now I'm getting a hard time with the pixel by pixel collision, which is fundamental because of the flippers.
Right now my Bounding Box Collision seems to be working
checkCollision(element) {
if (this.checkCollisionBoundingBox(element)) {
console.log("colision with the element bounding box");
if (this.checkCollisionPixelByPixel(element)) {
return true;
} else {
return false;
}
} else {
return false;
}
}
checkCollisionBoundingBox(element) {
if (this.pos.x < element.pos.x + element.width && this.pos.x + this.width > element.pos.x && this.pos.y < element.pos.y + element.height && this.pos.y + this.height > element.pos.y) {
return true;
} else {
return false;
}
}
I've tried several ways of implementing the pixel by pixel one but for some reason it does not work perfectly (on walls, on images, on sprites etc). I'll leave them here:
checkCollisionPixelByPixel(element) {
var x_left = Math.floor(Math.max(this.pos.x, element.pos.x));
var x_right = Math.floor(Math.min(this.pos.x + this.width, element.pos.x + element.width));
var y_top = Math.floor(Math.max(this.pos.y, element.pos.y));
var y_bottom = Math.floor(Math.min(this.pos.y + this.height, element.pos.y + element.height));
for (var y = y_top; y < y_bottom; y++) {
for (var x = x_left; x < x_right; x++) {
var x_0 = Math.round(x - this.pos.x);
var y_0 = Math.round(y - this.pos.y);
var n_pix = y_0 * (this.width * this.total) + (this.width * (this.actual-1)) + x_0; //n pixel to check
var pix_op = this.imgData.data[4 * n_pix + 3]; //opacity (R G B A)
var element_x_0 = Math.round(x - element.pos.x);
var element_y_0 = Math.round(y - element.pos.y);
var element_n_pix = element_y_0 * (element.width * element.total) + (element.width * (element.actual-1)) + element_x_0; //n pixel to check
var element_pix_op = element.imgData.data[4 * element_n_pix + 3]; //opacity (R G B A)
console.log(element_pix_op);
if (pix_op == 255 && element_pix_op == 255) {
console.log("Colision pixel by pixel");
/*Debug*/
/*console.log("This -> (R:" + this.imgData.data[4 * n_pix] + ", G:" + this.imgData.data[4 * n_pix + 1] + ", B:" + this.imgData.data[4 * n_pix + 2] + ", A:" + pix_op + ")");
console.log("Element -> (R:" + element.imgData.data[4 * element_n_pix] + ", G:" + element.imgData.data[4 * element_n_pix + 1] + ", B:" + element.imgData.data[4 * element_n_pix + 2] + ", A:" + element_pix_op + ")");
console.log("Collision -> (x:" + x + ", y:" + y +")");
console.log("This(Local) -> (x:" + x_0 + ", y:" + y_0+")");
console.log("Element(Local) -> (x:" + element_x_0 + ", y:" + element_y_0+")");*/
/*ball vector*/
var vector = {
x: (x_0 - Math.floor(this.imgData.width / 2)),
y: -(y_0 - Math.floor(this.imgData.height / 2))
};
//console.log("ball vector -> ("+vector.x+", "+vector.y+") , Angulo: "+ Math.atan(vector.y/vector.x)* 180/Math.PI);
// THIS WAS THE FIRST TRY, IT DIDN'T WORK WHEN THE BALL WAS GOING NORTHEAST AND COLLIDED WITH A WALL. DIDN'T WORK AT ALL WITH SPRITES
//this.angle = (Math.atan2(vector.y, vector.x) - Math.PI) * (180 / Math.PI);
// THIS WAS THE SECOND ATTEMPT, WORKS WORSE THAN THE FIRST ONE :/
//normal vector
var normal = {
x: (x_0 - (this.imgData.width / 2)),
y: -(y_0 - (this.imgData.height / 2))
};
//Normalizar o vetor
var norm = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
if (norm != 0) {
normal.x = normal.x / norm;
normal.y = normal.y / norm;
}
var n_rad = Math.atan2(normal.y, normal.x);
var n_deg = (n_rad + Math.PI) * 180 / Math.PI;
console.log("Vetor Normal -> (" + normal.x + ", " + normal.y + ") , Angulo: " + n_deg);
//Vetor Velocidade
var velocity = {
x: Math.cos((this.angle * Math.PI / 180) - Math.PI),
y: Math.sin((this.angle * Math.PI / 180) - Math.PI)
};
console.log("Vetor Velocidade -> (" + velocity.x + ", " + velocity.y + ") , Angulo: " + this.angle);
//Vetor Reflexao
var ndotv = normal.x * velocity.x + normal.y * velocity.y;
var reflection = {
x: -2 * ndotv * normal.x + velocity.x,
y: -2 * ndotv * normal.y + velocity.y
};
var r_rad = Math.atan2(reflection.y, reflection.x);
var r_deg = (r_rad + Math.PI) * 180 / Math.PI;
console.log("Vetor Reflexao -> (" + reflection.x + ", " + reflection.y + ") , Angulo: " + r_deg);
this.angle = r_deg;
return true;
}
}
}
return false;
}
}
The ball class
class Ball extends Element {
constructor(img, pos, width, height, n, sound, angle, speed) {
super(img, pos, width, height, n, sound);
this.angle = angle; //direction [0:360[
this.speed = speed;
}
move(ctx, cw, ch) {
var rads = this.angle * Math.PI / 180
var vx = Math.cos(rads) * this.speed / 60;
var vy = Math.sin(rads) * this.speed / 60;
this.pos.x += vx;
this.pos.y -= vy;
ctx.clearRect(0, 0, cw, ch);
this.draw(ctx, 1);
}
}
Assuming a "flipper" is composed of 2 arcs and 2 lines it would be much faster to do collision detection mathematically rather than by the much slower pixel-test method. Then you just need 4 math collision tests.
Even if your flippers are a bit more complicated than arcs+lines, the math hit tests would be "good enough" -- meaning in your fast-moving game, the user cannot visually notice the approximate math results vs the pixel-perfect results and the difference between the 2 types of tests will not affect gameplay at all. But the pixel-test version will take magnitudes more time and resources to accomplish. ;-)
First two circle-vs-circle collision tests:
function CirclesColliding(c1,c2){
var dx=c2.x-c1.x;
var dy=c2.y-c1.y;
var rSum=c1.r+c2.r;
return(dx*dx+dy*dy<=rSum*rSum);
}
Then two circle-vs-line-segment collision tests:
// [x0,y0] to [x1,y1] define a line segment
// [cx,cy] is circle centerpoint, cr is circle radius
function isCircleSegmentColliding(x0,y0,x1,y1,cx,cy,cr){
// calc delta distance: source point to line start
var dx=cx-x0;
var dy=cy-y0;
// calc delta distance: line start to end
var dxx=x1-x0;
var dyy=y1-y0;
// Calc position on line normalized between 0.00 & 1.00
// == dot product divided by delta line distances squared
var t=(dx*dxx+dy*dyy)/(dxx*dxx+dyy*dyy);
// calc nearest pt on line
var x=x0+dxx*t;
var y=y0+dyy*t;
// clamp results to being on the segment
if(t<0){x=x0;y=y0;}
if(t>1){x=x1;y=y1;}
return( (cx-x)*(cx-x)+(cy-y)*(cy-y) < cr*cr );
}

Executing a function after animate settimeout

Found this on stack overflow the other day http://codepen.io/anon/pen/LERrGG.
I think this is a great pen that could be really useful. The only problem is that there is no ability to call a function after the timer runs out. I was trying to implement this with no success.
How do I edit the code so that it becomes a useful timer i.e. it 'runs out'?
(function animate() {
theta += 0.5;
theta %= 360;
var x = Math.sin(theta * Math.PI / 180) * radius;
var y = Math.cos(theta * Math.PI / 180) * -radius;
var d = 'M0,0 v' + -radius + 'A' + radius + ',' + radius + ' 1 ' + ((theta > 180) ? 1 : 0) + ',1 ' + x + ',' + y + 'z';
timer.setAttribute('d', d);
setTimeout(animate, t)
})();
You can determine that a complete circle has been painted by checking to see if theta ends up smaller than when it started out:
(function animate() {
var oldTheta = theta;
theta += 0.5;
theta %= 360;
if (theta < oldTheta) {
// the timer has "run out"
}
else {
var x = Math.sin(theta * Math.PI / 180) * radius;
var y = Math.cos(theta * Math.PI / 180) * -radius;
var d = 'M0,0 v' + -radius + 'A' + radius + ',' + radius + ' 1 ' + ((theta > 180) ? 1 : 0) + ',1 ' + x + ',' + y + 'z';
timer.setAttribute('d', d);
setTimeout(animate, t);
}
})();
you have to check if theta smaller than 360.
(function animate() {
theta += 0.5;
var x = Math.sin(theta * Math.PI / 180) * radius;
var y = Math.cos(theta * Math.PI / 180) * -radius;
var d = 'M0,0 v' + -radius + 'A' + radius + ',' + radius + ' 1 ' + ((theta > 180) ? 1 : 0) + ',1 ' + x + ',' + y + 'z';
timer.setAttribute('d', d);
if(theta<360) setTimeout(animate, t);
else doSomething();
})();

Categories