旋转旋转的计算(没有物理引擎)
Calculation of a swivel rotation (without physics engine)
给定连接体的旋转角度,我尝试获得旋转轮的旋转角度:
我第一次使用车轮角度 attempt:i 已经存储了这些信息。为了获得车轮的旋转角度,我尝试通过使用系数作为简单的摩擦模拟来计算与旋转轴的距离:
var dx = cartCX - wheelCX,
dy = cartCY - wheelCY,
dist = Math.sqrt(cartCX * wheelCX + cartCY * wheelCY);
var wheelRotation = Math.atan2(-dy, -dx) * dist * friction;
考虑以下情况:
车轮处于稳定位置,与旋转对齐
整个系统顺时针旋转足够长时间后的汽车周长。
现在,整个系统逆时针旋转:那么旋转应该领先
一些轮子执行初始的 CCW 旋转(轮子
示例图片中的右上角)然后应充分
补间直到车轮对齐。车轮左上、左下和右下应为
顺时针旋转。
有没有不用物理引擎就能计算出车轮旋转角度的简单方法?
我不需要精确的物理刚体模拟,只需要一个简单的旋转效果。
简单旋转
假设轮子是脚轮。
描述一个
// ? for numbers save me making up numbers
var wheel = {
swivel : {x : ?, y : ?}, // world position of swivel (rotation point
length : ?, // distance from swivel to ground Contact when viewed from above
angle : ?, // current angle
}
当小车移动时旋转 and/or 行驶时,车轮旋转处有一个局部运动矢量
var delta = { x : ? , y : ? };
然后将此运动沿相反方向应用到与地面接触的车轮上。
var wheelForce = {};
wheelForce.x = -delta.x;
wheelForce.y = -delta.y;
轮子上会有两种运动,一种是旋转,一种是平移。它受到摇臂的限制,所以我们只需要旋转,因为平移将来自推车的运动。
首先归一化wheelForce
nwf = {}; // for (n)ormalised (w)heel (f)orce
var dist = Math.hypot(wheelForce.x,wheelForce.y);
nwf.x = wheelForce.x / dist;
nwf.y = wheelForce.y / dist;
然后得到从转环到车轮接地点的归一化向量
var dir = {};
dir.x = Math.cos(wheel.angle);
dir.y = Math.sin(wheel.angle);
然后用叉积得到nwf和dir夹角的sin
var fs = nwf.x * dir.y - nwf.y * dir.x;
现在只需从逆罪中获得角度并将轮子旋转该量。
wheel.angle -= Math.asin(fs); // I never get this right (may have to subtract, been up to long to think this out see demo)
更新
OP 要求进行一些改进。跳过更新
上面的行
允许轮子打滑(或转动)将减少转环上的转动力。
滑移量与车轮力和车轮方向之间的角度的余弦值有关。如果角度为 0,则 cos 将 return 1,这意味着车轮可以完全自由滚动/打滑,如果车轮方向与力成 90 度,则它们不会转动(就像横向拖动汽车)cos 90 return 0
但是如果我们允许轮子在没有阻力的情况下完全转动,那效果就不好了。因此,在限制因素的情况下,此修改将改善模拟。
从而计算出滑动量
var wheelTurnResistance = 0.8;
var slip = Math.abs(Math.cos(Math.asin(fs))) * wheelTurnResistance;
现在应用减少的转向力来解决打滑问题,我还添加了额外的减少来缓和旋转速度
var beNiceFactor = 0.6; // this further reduces the tendency to turn valid values 1-0 where smaller number reduce the tendency to swivel
wheel.angle -= Math.asin(fs) * Math.abs(fs) * (1 - slip) * beNiceFactor;
还将把它添加到下面的演示代码中。
这是此方法允许的范围。如果您想要更好的模拟,我们将不得不从头开始并使用更复杂的解决方案
就是这样。
演示版
它接缝方式很简单所以不得不在代码中尝试,下面是使用上述方法的演示。您可以通过限制添加车轮角度的量来改进它,乘以您添加的角度的正弦值会使它的响应速度稍差一些,并且添加滑移只需乘以另一个分数。
因此在函数updateTrolly change中改变了车轮角度
ww.angle -= Math.asin(cross);
到
ww.angle -= Math.asin(cross) * Math.abs(cross) * 0.1;
其中 Math.abs(cross)
是所加角度的正弦值,0.1
是滑动量(< 1 和 > 0 的任何值,1 没有滑动)
在购物车上或附近点击并拖动以移动它。轮子会跟着走。
该演示有一个 updateTrolly
函数来处理轮子,但取决于 displayTrolly
函数中计算的轮子位置。
您感兴趣的代码大约从一半开始,剩下的只是处理鼠标和 canvas。
DEMO 已更新 请参阅关于滑点的答案中的更新。
/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars
var canvas, ctx, mouse;
var globalTime = 0;
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
c.id = CANVAS_ELEMENT_ID;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === U) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
}
}
return mouse;
})();
var done = function(){
window.removeEventListener("resize",resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = U;
L("All done!")
}
resizeCanvas(); // create and size canvas
mouse.start(canvas,true); // start mouse on canvas and block context menu
window.addEventListener("resize",resizeCanvas); // add resize event
// ================================================================================
// Start of answer code
const SYSTEM_DRAG = 0.99; // add drag to stop everything flying around. value > 0 and < 1 the closer to 1 the less the drag (friction)
const MOUSE_FORCE = 600; // multiplies mouse movement force bigger number more force
const TROLLY_WIDTH = 100;
const TROLLY_LENGTH = 200;
const WHEEL_INSET = 0;
const WHEEL_WIDTH = 10;
const WHEEL_SWING_LENGTH = 20;
//const WHEEL_LENGTH = WHEEL_SWING_LENGTH * (2/3);
const WHEEL_LENGTH =30;
const PIXEL_MASS = 2; // mass per pixel. Need mass for better sim
var trolly = {
wheels : [],
x : 200,
y : 200,
r : 0,
dx : 0,
dy : 0,
dr : 0,
w : TROLLY_WIDTH,
l : TROLLY_LENGTH,
mass : TROLLY_WIDTH * TROLLY_LENGTH * PIXEL_MASS
};
function addWheel(t,x,y,dist,angle){
t.wheels.push({
x:x,
y:y, // relative to the trolly
rx : x, // to keep it simple r is for real worl position
ry : y,
lrx : x, // and lr is for last real world position. That will give delta at wheel
lry : y,
length : dist,
angle : angle, // absolute angle relative to the world
})
t.mass += WHEEL_WIDTH * WHEEL_LENGTH * PIXEL_MASS;
}
addWheel(trolly,-(TROLLY_LENGTH / 2 - WHEEL_INSET),-(TROLLY_WIDTH / 2 - WHEEL_INSET),WHEEL_SWING_LENGTH, 0);
addWheel(trolly,(TROLLY_LENGTH / 2 - WHEEL_INSET),-(TROLLY_WIDTH / 2 - WHEEL_INSET),WHEEL_SWING_LENGTH, 0);
addWheel(trolly,(TROLLY_LENGTH / 2 - WHEEL_INSET),(TROLLY_WIDTH / 2 - WHEEL_INSET),WHEEL_SWING_LENGTH, 0);
addWheel(trolly,-(TROLLY_LENGTH / 2 - WHEEL_INSET),(TROLLY_WIDTH / 2 - WHEEL_INSET),WHEEL_SWING_LENGTH, 0);
function drawTrolly(t){
ctx.setTransform(1,0,0,1,t.x,t.y);
ctx.rotate(t.r);
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(-t.l/2,-t.w/2);
ctx.lineTo(t.l/2,-t.w/2);
ctx.lineTo(t.l/2,t.w/2);
ctx.lineTo(-t.l/2,t.w/2);
ctx.closePath();
ctx.setTransform(1,0,0,1,0,0); // reset transform
var dx = Math.cos(t.r); // x axis
var dy = Math.sin(t.r);
for(var i = 0; i < t.wheels.length; i ++){
var w = t.wheels[i];
var x = w.x * dx + w.y * - dy;
var y = w.x * dy + w.y * dx;
var wx = Math.cos(w.angle); // vector to the wheel
var wy = Math.sin(w.angle);
w.lrx = w.rx; // save last pos
w.lry = w.ry;
w.rx = t.x + x; // save new pos
w.ry = t.y + y;
// get ground contact point
var gx = t.x + x + wx * w.length;
var gy = t.y + y + wy * w.length;
ctx.setTransform(1,0,0,1,w.rx, w.ry); // reset transform
ctx.moveTo(0,0);
ctx.setTransform(1,0,0,1,gx,gy); // move to the wheel
ctx.lineTo(0,0);
ctx.rotate(w.angle);
ctx.moveTo(-WHEEL_LENGTH / 2, -WHEEL_WIDTH / 2);
ctx.lineTo(WHEEL_LENGTH / 2, -WHEEL_WIDTH / 2);
ctx.lineTo(WHEEL_LENGTH / 2, WHEEL_WIDTH / 2);
ctx.lineTo(-WHEEL_LENGTH / 2, WHEEL_WIDTH / 2);
ctx.closePath();
}
ctx.stroke();
}
function updateTrolly(t){
for(var i = 0; i < t.wheels.length; i ++){
var ww = t.wheels[i];
var dx = ww.rx - ww.lrx; // get delta change at wheels
var dy = ww.ry - ww.lry;
var dist = Math.hypot(dx,dy);
if(dist > 0.00001){ // not to small to bother
var nx = -dx / dist;
var ny = -dy / dist;
var wx = Math.cos(ww.angle);
var wy = Math.sin(ww.angle);
var cross = nx * wy - ny * wx;
var slip = Math.abs(Math.cos(Math.asin(cross))) * 0.7;
ww.angle -= Math.asin(cross) * Math.abs(cross) * (1-slip) * 0.6;
}
}
t.x += t.dx;
t.y += t.dy;
t.r += t.dr;
t.dx *= SYSTEM_DRAG;
t.dy *= SYSTEM_DRAG;
t.dr *= SYSTEM_DRAG;
t.x = ((t.x % w) + w) % w; // keep onscreen
t.y = ((t.y % h) + h) % h; // keep onscreen
}
function applyForceCenter(object, force, direction){ // force is a vector
force /= object.mass; // now use F = m * a in the form a = F/m
object.dx += Math.cos(direction) * force;
object.dy += Math.sin(direction) * force;
}
function applyForce(object, force, direction, locx,locy){ // force is a vector, loc is a coordinate
var radius = Math.hypot(object.y - locy, object.x - locx);
if(radius <= 0.00001){
applyForceCenter(object,force,direction);
return;
}
var toCenter = Math.atan2(object.y - locy, object.x - locx);
var pheta = toCenter - direction;
var Fv = Math.cos(pheta) * force;
var Fa = Math.sin(pheta) * force;
Fv /= object.mass; // now use F = m * a in the form a = F/m
var Fvx = Math.cos(toCenter) * Fv;
var Fvy = Math.sin(toCenter) * Fv;
object.dx += Fvx;
object.dy += Fvy;
Fa /= (radius * object.mass); // for the angular component get the rotation
// acceleration
object.dr += Fa;// now add that to the box delta r
}
function applyForceToTrolly(t,x,y,dx,dy){
var f = Math.hypot(dx,dy) * MOUSE_FORCE;
var dir = Math.atan2(dy,dx);
applyForce(t,f,dir,x,y);
}
var lx,ly;
function display(){ // put code in here
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
if(mouse.buttonRaw & 1){
applyForceToTrolly(trolly,mouse.x,mouse.y,mouse.x-lx,mouse.y-ly);
}
updateTrolly(trolly);
drawTrolly(trolly);
lx = mouse.x;
ly = mouse.y;
}
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/
给定连接体的旋转角度,我尝试获得旋转轮的旋转角度:
我第一次使用车轮角度 attempt:i 已经存储了这些信息。为了获得车轮的旋转角度,我尝试通过使用系数作为简单的摩擦模拟来计算与旋转轴的距离:
var dx = cartCX - wheelCX,
dy = cartCY - wheelCY,
dist = Math.sqrt(cartCX * wheelCX + cartCY * wheelCY);
var wheelRotation = Math.atan2(-dy, -dx) * dist * friction;
考虑以下情况:
车轮处于稳定位置,与旋转对齐 整个系统顺时针旋转足够长时间后的汽车周长。
现在,整个系统逆时针旋转:那么旋转应该领先 一些轮子执行初始的 CCW 旋转(轮子 示例图片中的右上角)然后应充分 补间直到车轮对齐。车轮左上、左下和右下应为 顺时针旋转。
有没有不用物理引擎就能计算出车轮旋转角度的简单方法?
我不需要精确的物理刚体模拟,只需要一个简单的旋转效果。
简单旋转
假设轮子是脚轮。
描述一个
// ? for numbers save me making up numbers
var wheel = {
swivel : {x : ?, y : ?}, // world position of swivel (rotation point
length : ?, // distance from swivel to ground Contact when viewed from above
angle : ?, // current angle
}
当小车移动时旋转 and/or 行驶时,车轮旋转处有一个局部运动矢量
var delta = { x : ? , y : ? };
然后将此运动沿相反方向应用到与地面接触的车轮上。
var wheelForce = {};
wheelForce.x = -delta.x;
wheelForce.y = -delta.y;
轮子上会有两种运动,一种是旋转,一种是平移。它受到摇臂的限制,所以我们只需要旋转,因为平移将来自推车的运动。
首先归一化wheelForce
nwf = {}; // for (n)ormalised (w)heel (f)orce
var dist = Math.hypot(wheelForce.x,wheelForce.y);
nwf.x = wheelForce.x / dist;
nwf.y = wheelForce.y / dist;
然后得到从转环到车轮接地点的归一化向量
var dir = {};
dir.x = Math.cos(wheel.angle);
dir.y = Math.sin(wheel.angle);
然后用叉积得到nwf和dir夹角的sin
var fs = nwf.x * dir.y - nwf.y * dir.x;
现在只需从逆罪中获得角度并将轮子旋转该量。
wheel.angle -= Math.asin(fs); // I never get this right (may have to subtract, been up to long to think this out see demo)
更新
OP 要求进行一些改进。跳过更新
上面的行允许轮子打滑(或转动)将减少转环上的转动力。
滑移量与车轮力和车轮方向之间的角度的余弦值有关。如果角度为 0,则 cos 将 return 1,这意味着车轮可以完全自由滚动/打滑,如果车轮方向与力成 90 度,则它们不会转动(就像横向拖动汽车)cos 90 return 0
但是如果我们允许轮子在没有阻力的情况下完全转动,那效果就不好了。因此,在限制因素的情况下,此修改将改善模拟。
从而计算出滑动量
var wheelTurnResistance = 0.8;
var slip = Math.abs(Math.cos(Math.asin(fs))) * wheelTurnResistance;
现在应用减少的转向力来解决打滑问题,我还添加了额外的减少来缓和旋转速度
var beNiceFactor = 0.6; // this further reduces the tendency to turn valid values 1-0 where smaller number reduce the tendency to swivel
wheel.angle -= Math.asin(fs) * Math.abs(fs) * (1 - slip) * beNiceFactor;
还将把它添加到下面的演示代码中。
这是此方法允许的范围。如果您想要更好的模拟,我们将不得不从头开始并使用更复杂的解决方案
就是这样。
演示版
它接缝方式很简单所以不得不在代码中尝试,下面是使用上述方法的演示。您可以通过限制添加车轮角度的量来改进它,乘以您添加的角度的正弦值会使它的响应速度稍差一些,并且添加滑移只需乘以另一个分数。
因此在函数updateTrolly change中改变了车轮角度
ww.angle -= Math.asin(cross);
到
ww.angle -= Math.asin(cross) * Math.abs(cross) * 0.1;
其中 Math.abs(cross)
是所加角度的正弦值,0.1
是滑动量(< 1 和 > 0 的任何值,1 没有滑动)
在购物车上或附近点击并拖动以移动它。轮子会跟着走。
该演示有一个 updateTrolly
函数来处理轮子,但取决于 displayTrolly
函数中计算的轮子位置。
您感兴趣的代码大约从一半开始,剩下的只是处理鼠标和 canvas。
DEMO 已更新 请参阅关于滑点的答案中的更新。
/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars
var canvas, ctx, mouse;
var globalTime = 0;
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
c.id = CANVAS_ELEMENT_ID;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === U) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
}
}
return mouse;
})();
var done = function(){
window.removeEventListener("resize",resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = U;
L("All done!")
}
resizeCanvas(); // create and size canvas
mouse.start(canvas,true); // start mouse on canvas and block context menu
window.addEventListener("resize",resizeCanvas); // add resize event
// ================================================================================
// Start of answer code
const SYSTEM_DRAG = 0.99; // add drag to stop everything flying around. value > 0 and < 1 the closer to 1 the less the drag (friction)
const MOUSE_FORCE = 600; // multiplies mouse movement force bigger number more force
const TROLLY_WIDTH = 100;
const TROLLY_LENGTH = 200;
const WHEEL_INSET = 0;
const WHEEL_WIDTH = 10;
const WHEEL_SWING_LENGTH = 20;
//const WHEEL_LENGTH = WHEEL_SWING_LENGTH * (2/3);
const WHEEL_LENGTH =30;
const PIXEL_MASS = 2; // mass per pixel. Need mass for better sim
var trolly = {
wheels : [],
x : 200,
y : 200,
r : 0,
dx : 0,
dy : 0,
dr : 0,
w : TROLLY_WIDTH,
l : TROLLY_LENGTH,
mass : TROLLY_WIDTH * TROLLY_LENGTH * PIXEL_MASS
};
function addWheel(t,x,y,dist,angle){
t.wheels.push({
x:x,
y:y, // relative to the trolly
rx : x, // to keep it simple r is for real worl position
ry : y,
lrx : x, // and lr is for last real world position. That will give delta at wheel
lry : y,
length : dist,
angle : angle, // absolute angle relative to the world
})
t.mass += WHEEL_WIDTH * WHEEL_LENGTH * PIXEL_MASS;
}
addWheel(trolly,-(TROLLY_LENGTH / 2 - WHEEL_INSET),-(TROLLY_WIDTH / 2 - WHEEL_INSET),WHEEL_SWING_LENGTH, 0);
addWheel(trolly,(TROLLY_LENGTH / 2 - WHEEL_INSET),-(TROLLY_WIDTH / 2 - WHEEL_INSET),WHEEL_SWING_LENGTH, 0);
addWheel(trolly,(TROLLY_LENGTH / 2 - WHEEL_INSET),(TROLLY_WIDTH / 2 - WHEEL_INSET),WHEEL_SWING_LENGTH, 0);
addWheel(trolly,-(TROLLY_LENGTH / 2 - WHEEL_INSET),(TROLLY_WIDTH / 2 - WHEEL_INSET),WHEEL_SWING_LENGTH, 0);
function drawTrolly(t){
ctx.setTransform(1,0,0,1,t.x,t.y);
ctx.rotate(t.r);
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(-t.l/2,-t.w/2);
ctx.lineTo(t.l/2,-t.w/2);
ctx.lineTo(t.l/2,t.w/2);
ctx.lineTo(-t.l/2,t.w/2);
ctx.closePath();
ctx.setTransform(1,0,0,1,0,0); // reset transform
var dx = Math.cos(t.r); // x axis
var dy = Math.sin(t.r);
for(var i = 0; i < t.wheels.length; i ++){
var w = t.wheels[i];
var x = w.x * dx + w.y * - dy;
var y = w.x * dy + w.y * dx;
var wx = Math.cos(w.angle); // vector to the wheel
var wy = Math.sin(w.angle);
w.lrx = w.rx; // save last pos
w.lry = w.ry;
w.rx = t.x + x; // save new pos
w.ry = t.y + y;
// get ground contact point
var gx = t.x + x + wx * w.length;
var gy = t.y + y + wy * w.length;
ctx.setTransform(1,0,0,1,w.rx, w.ry); // reset transform
ctx.moveTo(0,0);
ctx.setTransform(1,0,0,1,gx,gy); // move to the wheel
ctx.lineTo(0,0);
ctx.rotate(w.angle);
ctx.moveTo(-WHEEL_LENGTH / 2, -WHEEL_WIDTH / 2);
ctx.lineTo(WHEEL_LENGTH / 2, -WHEEL_WIDTH / 2);
ctx.lineTo(WHEEL_LENGTH / 2, WHEEL_WIDTH / 2);
ctx.lineTo(-WHEEL_LENGTH / 2, WHEEL_WIDTH / 2);
ctx.closePath();
}
ctx.stroke();
}
function updateTrolly(t){
for(var i = 0; i < t.wheels.length; i ++){
var ww = t.wheels[i];
var dx = ww.rx - ww.lrx; // get delta change at wheels
var dy = ww.ry - ww.lry;
var dist = Math.hypot(dx,dy);
if(dist > 0.00001){ // not to small to bother
var nx = -dx / dist;
var ny = -dy / dist;
var wx = Math.cos(ww.angle);
var wy = Math.sin(ww.angle);
var cross = nx * wy - ny * wx;
var slip = Math.abs(Math.cos(Math.asin(cross))) * 0.7;
ww.angle -= Math.asin(cross) * Math.abs(cross) * (1-slip) * 0.6;
}
}
t.x += t.dx;
t.y += t.dy;
t.r += t.dr;
t.dx *= SYSTEM_DRAG;
t.dy *= SYSTEM_DRAG;
t.dr *= SYSTEM_DRAG;
t.x = ((t.x % w) + w) % w; // keep onscreen
t.y = ((t.y % h) + h) % h; // keep onscreen
}
function applyForceCenter(object, force, direction){ // force is a vector
force /= object.mass; // now use F = m * a in the form a = F/m
object.dx += Math.cos(direction) * force;
object.dy += Math.sin(direction) * force;
}
function applyForce(object, force, direction, locx,locy){ // force is a vector, loc is a coordinate
var radius = Math.hypot(object.y - locy, object.x - locx);
if(radius <= 0.00001){
applyForceCenter(object,force,direction);
return;
}
var toCenter = Math.atan2(object.y - locy, object.x - locx);
var pheta = toCenter - direction;
var Fv = Math.cos(pheta) * force;
var Fa = Math.sin(pheta) * force;
Fv /= object.mass; // now use F = m * a in the form a = F/m
var Fvx = Math.cos(toCenter) * Fv;
var Fvy = Math.sin(toCenter) * Fv;
object.dx += Fvx;
object.dy += Fvy;
Fa /= (radius * object.mass); // for the angular component get the rotation
// acceleration
object.dr += Fa;// now add that to the box delta r
}
function applyForceToTrolly(t,x,y,dx,dy){
var f = Math.hypot(dx,dy) * MOUSE_FORCE;
var dir = Math.atan2(dy,dx);
applyForce(t,f,dir,x,y);
}
var lx,ly;
function display(){ // put code in here
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
if(mouse.buttonRaw & 1){
applyForceToTrolly(trolly,mouse.x,mouse.y,mouse.x-lx,mouse.y-ly);
}
updateTrolly(trolly);
drawTrolly(trolly);
lx = mouse.x;
ly = mouse.y;
}
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/