javascript - Calculating angular velocity after a collision -
i've got linear component of collision resolution down relatively well, can't quite figure out how same angular one. i've read, it's like... torque
= point of collision
x linear velocity
. (cross product) tried incorporate example found code don't see rotation @ when objects collide. other fiddle works rudimentary implementation of seperating axis theorem , angular velocity calculations. here's i've come with...
property definitions (orientation, angular velocity, , angular acceleration):
rotation: 0, angularvelocity: 0, angularacceleration: 0
calculating angular velocity in collision response:
var pivota = this.vector(bodya.x, bodya.y); bodya.angularvelocity = 1 * 0.2 * (bodya.angularvelocity / math.abs(bodya.angularvelocity)) * pivota.subtract(iscircle ? pivota.add(bodya.radius) : { x: pivota.x + boundsa.width, y: pivota.y + boundsa.height }).vcross(bodya.velocity); var pivotb = this.vector(bodyb.x, bodyb.y); bodyb.angularvelocity = 1 * 0.2 * (bodyb.angularvelocity / math.abs(bodyb.angularvelocity)) * pivotb.subtract(iscircle ? pivotb.add(bodyb.radius) : { x: pivotb.x + boundsb.width, y: pivotb.y + boundsb.height }).vcross(bodyb.velocity);
updating orientation in update loop:
var torque = 0; torque += core.objects[o].angularvelocity * -1; core.objects[o].angularacceleration = torque / core.objects[o].momentofinertia(); core.objects[o].angularvelocity += core.objects[o].angularacceleration; core.objects[o].rotation += core.objects[o].angularvelocity;
i post code have calculating moments of inertia there's seperate 1 every object bit... lengthy. nonetheless, here's 1 circle example:
return this.mass * this.radius * this.radius / 2;
just show result, here's fiddle. shown, objects not rotate on collision. (not visible circles, should work 0 , seven)
what doing wrong?
edit: reason weren't rotating @ because of error groups in response function -- rotates now, not correctly. however, i've commented out messes things up.
also, i've tried method rotation. here's code in response:
_bodya.angularvelocity = direction.vcross(_bodya.velocity) / (iscircle ? _bodya.radius : boundsa.width); _bodyb.angularvelocity = direction.vcross(_bodyb.velocity) / (iscircle ? _bodyb.radius : boundsb.width);
note direction
refers "collision normal".
angular , linear acceleration due force vector
angular , directional accelerations due applied force 2 components of same thing , can not separated. 1 need solve both.
define calculations
from simple physics , standing on shoulders know following.
f force (equivalent inertia) fv linear force fa angular force acceleration linear or rotational depending on used v velocity. angular situations tangential component m mass r radius
for linear forces
f = m * v
from derive
m = f / v v = f / m
for rotational force (v tangential velocity)
f = r * r * m * (v / r) , simplify f = r * m * v
from derive
m = f / ( r * v ) v = f / ( r * m ) r = f / ( v * m )
because forces apply instantaneous can interchange a
acceleration , v
velocity give following formulas
linear
f = m * m = f / a = f / m
rotational
f = r * m * m = f / ( r * ) = f / ( r * m ) r = f / ( * m )
as interested in change in velocity both linear , rotation solutions
a1 = f / m a2 = f / ( r * m )
where a1
acceleration in pixels per frame2 , a2
acceleration in radians per frame2 ( frame squared denotes acceleration)
from 1d 2d
because 2d solution , above 1d need use vectors. problem use 2 forms of 2d vector. polar has magnitude (length, distance, like...) , direction. cartesian has x , y. vector represents depends on how used.
the following functions used helpers in solution. written in es6 non compliant browsers have adapt them, though not ever suggest use these written convenience, inefficient , lot of redundant calculations.
converts vector polar cartesian returning new one
function polartocart(pvec, retv = {x : 0, y : 0}) { retv.x = math.cos(pvec.dir) * pvec.mag; retv.y = math.sin(pvec.dir) * pvec.mag; return retv; }
converts vector cartesian polar returning new one
function carttopolar(vec, retv = {dir : 0, mag : 0}) { retv.dir = math.atan2(vec.y, vec.x); retv.mag = math.hypot(vec.x, vec.y); return retv; }
creates polar vector
function polar(mag = 1, dir = 0) { return validatepolar({dir : dir,mag : mag}); }
create vector cartesian
function vector(x = 1, y = 0) { return {x : x, y : y}; }
true arg vec vector in polar form
function ispolar(vec) { if (vec.mag !== undefined && vec.dir !== undefined) {return true;} return false; }
returns true if arg vec vector in cartesian form
function iscart(vec) { if (vec.x !== undefined && vec.y !== undefined) {return true;} return false; }
returns new vector in polar form ensures vec.mag positive
function aspolar(vec){ if(iscart(vec)){ return carttopolar(vec); } if(vec.mag < 0){ vec.mag = - vec.mag; vec.dir += pi; } return { dir : vec.dir, mag : vec.mag }; }
copy , converts unknown vec cart if not already
function ascart(vec){ if(ispolar(vec)){ return polartocart(vec); } return { x : vec.x, y : vec.y}; }
calculations can result in negative magnitude though valid calculations results in incorrect vector (reversed) validates polar vector has positive magnitude not change vector sign , direction
function validatepolar(vec) { if (ispolar(vec)) { if (vec.mag < 0) { vec.mag = - vec.mag; vec.dir += pi; } } return vec; }
the box
now can define object can use play with. simple box has position, size, mass, orientation, velocity , rotation
function createbox(x,y,w,h){ var box = { x : x, // pos y : y, r : 0.1, // rotation aka orientation or direction in radians h : h, // height w : w, // width dx : 0, // delta x in pixels per frame 1/60th second dy : 0, // delta y dr : 0.0, // deltat rotation in radians per frame 1/60th second mass : w * h, // mass in things update :function(){ this.x += this.dx; this.y += this.dy; this.r += this.dr; }, } return box; }
applying force object
so can redefine terms
f (force) vector force magnitude force , has direction
var force = polar(100,0); // create force 100 units right (0 radians)
the force meaningless without position applied.
position vector holds , x , y location
var location = vector(canvas.width/2, canvas.height/2); // defines point in middle of canvas
directional vector holds direction , distance between positional vectors
var l1 = vector(canvas.width/2, canvas.height/2); // defines point in middle of canvas var l2 = vector(100,100); var direction = aspolar(vector(l2.x - l1.x, l2.y - l1.y)); // direction polar vector
direction
has direction canvas center point (100,100) , distance.
the last thing need extract components force vector along directional vector. when apply force object force split two, 1 force along line object center , adds object acceleration, other force @ 90deg line object center (the tangent) , force changes rotation.
to 2 components difference in direction between force vector , directional vector force applied object center.
var force = polar(100,0); // force var forceloc = vector(50,50); // location force applied var direction2center = aspolar(vector(box.x - forceloc.x, box.y - forceloc.y)); // direction polar vector var pheta = direction2center - force.dir; // angle between force , object center
now have angle pheta force can split rotational , linear components trig.
var f = force.mag; // force magnitude var fv = math.cos(pheta) * f; // linear force var fa = math.sin(pheta) * f; // angular force
now forces can converted accelerations linear = f/m , angular = f/(m*r)
accelv = fv / box.mass; // linear acceleration in pixels accela = fa / (box.mass * direction2center.mag); // angular acceleration in radians
you convert linear force vector has direction center of object
var forcev = polar(fv, direction2center);
convert cartesian can add object deltax , deltay
forcev = ascart(forcev);
and add acceleration box
box.dx += forcev.x; box.dy += forcev.y;
rotational acceleration 1 dimensional add delta rotation of box
box.dr += accela;
and it.
function apply force box
the function if attached box apply force vector @ location box.
attach box so
box.applyforce = applyforce; // bind function box;
you can call function via box
box.applyforce(force, locationofforce); function applyforce(force, loc){ // force vector, loc coordinate var tocenter = aspolar(vector(this.x - loc.x, this.y - loc.y)); // vector center var pheta = tocenter.dir - force.dir; // angle between force , line center var fv = math.cos(pheta) * force.mag; // split force velocity force along line center var fa = math.sin(pheta) * force.mag; // , angular force @ tangent line center var accel = aspolar(tocenter); // copy direction center accel.mag = fv / this.mass; // use f = m * in form = f/m acceleration var deltav = ascart(accel); // convert acceleration cartesian this.dx += deltav.x // update box delta v this.dy += deltav.y // var accela = fa / (tocenter.mag * this.mass); // angular component rotation // acceleration f=m*a*r in // form = f/(m*r) this.dr += accela;// add box delta r }
the demo
the demo function applyforce
stuff gravity , bouncing bad approximations , should not used physic type of stuff not conserve energy.
click , drag apply force object in direction mouse moved.
const pi90 = math.pi / 2; const pi = math.pi; const pi2 = math.pi * 2; const inset = 10; // playfeild inset const arrow_size = 6 const scale_vec = 10; const scale_force = 0.15; const line_w = 2; const life = 12; const font_size = 20; const font = "arial black"; const wall_norms = [pi90,pi,-pi90,0]; // dirction of wall normals var box = createbox(200, 200, 50, 100); box.applyforce = applyforce; // add function box // render / update function var mouse = (function(){ function preventdefault(e) { e.preventdefault(); } var i; var mouse = { x : 0, y : 0,buttonraw : 0, bm : [1, 2, 4, 6, 5, 3], // masks setting , clearing button raw bits; mouseevents : "mousemove,mousedown,mouseup".split(",") }; function mousemove(e) { var t = e.type, m = mouse; m.x = e.offsetx; m.y = e.offsety; if (m.x === undefined) { m.x = e.clientx; m.y = e.clienty; } if (t === "mousedown") { m.buttonraw |= m.bm[e.which-1]; } else if (t === "mouseup") { m.buttonraw &= m.bm[e.which + 2];} e.preventdefault(); } mouse.start = function(element = document){ if(mouse.element !== undefined){ mouse.removemouse();} mouse.element = element; mouse.mouseevents.foreach(n => { element.addeventlistener(n, mousemove); } ); } mouse.remove = function(){ if(mouse.element !== undefined){ mouse.mouseevents.foreach(n => { mouse.element.removeeventlistener(n, mousemove); } ); mouse.element = undefined; } } return mouse; })(); var canvas,ctx; function createcanvas(){ canvas = document.createelement("canvas"); canvas.style.position = "absolute"; canvas.style.left = "0px"; canvas.style.top = "0px"; canvas.style.zindex = 1000; document.body.appendchild(canvas); } function resizecanvas(){ if(canvas === undefined){ createcanvas(); } canvas.width = window.innerwidth; canvas.height = window.innerheight; ctx = canvas.getcontext("2d"); if(box){ box.w = canvas.width * 0.10; box.h = box.w * 2; box.mass = box.w * box.h; } } window.addeventlistener("resize",resizecanvas); resizecanvas(); mouse.start(canvas) var tempvecs = []; function addtempvec(v,vec,col,life = life,scale = scale_vec){tempvecs.push({v:v,vec:vec,col:col,scale:scale,life:life,slife:life});} function drawtempvecs(){ for(var = 0; < tempvecs.length; ++ ){ var t = tempvecs[i]; t.life -= 1; if(t.life <= 0){tempvecs.splice(i, 1); i--; continue} ctx.globalalpha = (t.life / t.slife)*0.25; drawvec(t.v, t.vec ,t.col, t.scale) } } function drawvec(v,vec,col,scale = scale_vec){ vec = aspolar(vec) ctx.settransform(1,0,0,1,v.x,v.y); var d = vec.dir; var m = vec.mag; ctx.rotate(d); ctx.beginpath(); ctx.linewidth = line_w; ctx.strokestyle = col; ctx.moveto(0,0); ctx.lineto(m * scale,0); ctx.moveto(m * scale-arrow_size,-arrow_size); ctx.lineto(m * scale,0); ctx.lineto(m * scale-arrow_size,arrow_size); ctx.stroke(); } function drawtext(text,x,y,font,size,col){ ctx.font = size + "px "+font; ctx.textalign = "center"; ctx.textbaseline = "middle"; ctx.settransform(1,0,0,1,x,y); ctx.globalalpha = 1; ctx.fillstyle = col; ctx.filltext(text,0,0); } function createbox(x,y,w,h){ var box = { x : x, // pos y : y, r : 0.1, // rotation aka orientation or direction in radians h : h, // height, , assume depth equal height w : w, // width dx : 0, // delta x in pixels per frame 1/60th second dy : 0, // delta y dr : 0.0, // deltat rotation in radians per frame 1/60th second getdesc : function(){ var vel = math.hypot(this.dx ,this.dy); var radius = math.hypot(this.w,this.h)/2 var rvel = math.abs(this.dr * radius); var str = "v " + (vel*60).tofixed(0) + "pps "; str += math.abs(this.dr * 60 * 60).tofixed(0) + "rpm "; str += "va " + (rvel*60).tofixed(0) + "pps "; return str; }, mass : function(){ return (this.w * this.h * this.h)/1000; }, // mass in k things draw : function(){ ctx.globalalpha = 1; ctx.settransform(1,0,0,1,this.x,this.y); ctx.rotate(this.r); ctx.fillstyle = "#444"; ctx.fillrect(-this.w/2, -this.h/2, this.w, this.h) ctx.strokerect(-this.w/2, -this.h/2, this.w, this.h) }, update :function(){ this.x += this.dx; this.y += this.dy; this.dy += 0.061; // alittle gravity this.r += this.dr; }, getpoint : function(which){ var dx,dy,x,y,xx,yy,velocitya,velocityt,velocity; dx = math.cos(this.r); dy = math.sin(this.r); switch(which){ case 0: x = -this.w /2; y = -this.h /2; break; case 1: x = this.w /2; y = -this.h /2; break; case 2: x = this.w /2; y = this.h /2; break; case 3: x = -this.w /2; y = this.h /2; break; case 4: x = this.x; y = this.y; } var xx,yy; xx = x * dx + y * -dy; yy = x * dy + y * dx; var details = aspolar(vector(xx, yy)) xx += this.x; yy += this.y; velocitya = polar(details.mag * this.dr, details.dir + pi90); velocityt = vectoradd(velocity = vector(this.dx, this.dy), velocitya); return { velocity : velocity, // directional velocityt : velocityt, // total velocitya : velocitya, // angular pos : vector(xx, yy), radius : details.mag, } }, } box.mass = box.mass(); // mass remains same set function return box; } // calculations can result in negative magnitude though valide // calculations results in incorrect vector (reversed) // validates polat vector has positive magnitude // not change vector sign , direction function validatepolar(vec){ if(ispolar(vec)){ if(vec.mag < 0){ vec.mag = - vec.mag; vec.dir += pi; } } return vec; } // converts vector polar cartesian returning new 1 function polartocart(pvec, retv = {x : 0, y : 0}){ retv.x = math.cos(pvec.dir) * pvec.mag; retv.y = math.sin(pvec.dir) * pvec.mag; return retv; } // converts vector cartesian polar returning new 1 function carttopolar(vec, retv = {dir : 0, mag : 0}){ retv.dir = math.atan2(vec.y,vec.x); retv.mag = math.hypot(vec.x,vec.y); return retv; } function polar (mag = 1, dir = 0) { return validatepolar({dir : dir, mag : mag}); } // create polar vector function vector (x= 1, y= 0) { return {x: x, y: y}; } // create cartesian vector function ispolar (vec) { if(vec.mag !== undefined && vec.dir !== undefined) { return true; } return false; }// returns true if polar function iscart (vec) { if(vec.x !== undefined && vec.y !== undefined) { return true; } return false; }// returns true if cartesian // copy , converts unknown vec polar if not function aspolar(vec){ if(iscart(vec)){ return carttopolar(vec); } if(vec.mag < 0){ vec.mag = - vec.mag; vec.dir += pi; } return { dir : vec.dir, mag : vec.mag }; } // copy , converts unknown vec cart if not function ascart(vec){ if(ispolar(vec)){ return polartocart(vec); } return { x : vec.x, y : vec.y}; } // normalise makes vector unit length , returns cartesian function normalise(vec){ var vp = aspolar(vec); vap.mag = 1; return ascart(vp); } function vectoradd(vec1, vec2){ var v1 = ascart(vec1); var v2 = ascart(vec2); return vector(v1.x + v2.x, v1.y + v2.y); } // splits vector (polar or cartesian) components along dir , tangent dir function vectorcomponentsfordir(vec,dir){ var v = aspolar(vec); // polar var pheta = v.dir - dir; var fv = math.cos(pheta) * v.mag; var fa = math.sin(pheta) * v.mag; var d1 = dir; var d2 = dir + pi90; if(fv < 0){ d1 += pi; fv = -fv; } if(fa < 0){ d2 += pi; fa = -fa; } return { along : polar(fv,d1), tangent : polar(fa,d2) }; } function docollision(pointdetails, wallindex){ var vv = aspolar(pointdetails.velocity); // cartesian v make sure velocity in cartesian form var va = aspolar(pointdetails.velocitya); // angular v make sure velocity in cartesian form var vvc = vectorcomponentsfordir(vv, wall_norms[wallindex]) var vac = vectorcomponentsfordir(va, wall_norms[wallindex]) vvc.along.mag *= 1.18; // elastic collision requiers 2 equal forces wall vac.along.mag *= 1.18; // against box , box against wall summed. // wall can not move result force twice // force box applies wall (yes , force in // velocity form untill next line) vvc.along.mag *= box.mass; // convert force //vac.along.mag/= pointdetails.radius vac.along.mag *= box.mass vvc.along.dir += pi; // force in oppisite direction turn 180 vac.along.dir += pi; // force in oppisite direction turn 180 // split force components based on wall normal. 1 along norm // other along wall vvc.tangent.mag *= 0.18; // add friction along wall vac.tangent.mag *= 0.18; vvc.tangent.mag *= box.mass // vac.tangent.mag *= box.mass vvc.tangent.dir += pi; // force in oppisite direction turn 180 vac.tangent.dir += pi; // force in oppisite direction turn 180 // apply force out wall box.applyforce(vvc.along, pointdetails.pos) // apply force along wall box.applyforce(vvc.tangent, pointdetails.pos) // apply force out wall box.applyforce(vac.along, pointdetails.pos) // apply force along wall box.applyforce(vac.tangent, pointdetails.pos) //addtempvec(pointdetails.pos, vvc.tangent, "red", life, 10) //addtempvec(pointdetails.pos, vac.tangent, "red", life, 10) } function applyforce(force, loc){ // force vector, loc coordinate validatepolar(force); // make sure force valid polar // addtempvec(loc, force,"white", life, scale_force) // show force var l = ascart(loc); // make sure location in cartesian form var tocenter = aspolar(vector(this.x - l.x, this.y - l.y)); var pheta = tocenter.dir - force.dir; var fv = math.cos(pheta) * force.mag; var fa = math.sin(pheta) * force.mag; var accel = aspolar(tocenter); // copy direction center accel.mag = fv / this.mass; // use f = m * in form = f/m var deltav = ascart(accel); // convert cartesian this.dx += deltav.x // update box delta v this.dy += deltav.y var accela = fa / (tocenter.mag * this.mass); // angular component rotation // acceleration this.dr += accela;// add box delta r } // make box ctx.globalalpha = 1; var lx,ly; function update(){ // clearlog(); ctx.settransform(1, 0, 0, 1, 0, 0); ctx.clearrect(0, 0, canvas.width, canvas.height); ctx.settransform(1, 0, 0, 1, 0, 0); ctx.linewidth = 1; ctx.strokestyle = "black"; ctx.fillstyle = "#888"; ctx.fillrect(inset, inset, canvas.width - inset * 2, canvas.height - inset * 2); ctx.strokerect(inset, inset, canvas.width - inset * 2, canvas.height - inset * 2); ctx.linewidth = 2; ctx.strokestyle = "black"; box.update(); box.draw(); if(mouse.buttonraw & 1){ var force = aspolar(vector(mouse.x - lx, mouse.y - ly)); force.mag *= box.mass * 0.1; box.applyforce(force,vector(mouse.x, mouse.y)) addtempvec(vector(mouse.x, mouse.y), aspolar(vector(mouse.x - lx, mouse.y - ly)), "cyan", life, 5); } lx = mouse.x; ly = mouse.y; for(i = 0; < 4; i++){ var p = box.getpoint(i); // 1 collision per frame or end adding energy if(p.pos.x < inset){ box.x += (inset) - p.pos.x; docollision(p,3) }else if( p.pos.x > canvas.width-inset){ box.x += (canvas.width - inset) - p.pos.x; docollision(p,1) }else if(p.pos.y < inset){ box.y += (inset) -p.pos.y; docollision(p,0) }else if( p.pos.y > canvas.height-inset){ box.y += (canvas.height - inset) -p.pos.y; docollision(p,2) } drawvec(p.pos,p.velocity,"blue") } drawtempvecs(); ctx.globalalpha = 1; drawtext(box.getdesc(),canvas.width/2,font_size,font,font_size,"black"); drawtext("click drag apply force box",canvas.width/2,font_size +17,font,14,"black"); requestanimationframe(update) } update();
Comments
Post a Comment