考虑到连接体的旋转角度,我试图获得旋转轮的旋转角度:
使用车轮角度是我的第一次尝试:我已经存储了这些信息。为了获得车轮的旋转角度,我尝试使用系数作为简单的摩擦模拟来计算与旋转轴的距离:
var dx = cartCX - wheelCX,
dy = cartCY - wheelCY,
dist = Math.sqrt(cartCX * wheelCX + cartCY * wheelCY);
var wheelRotation = Math.atan2(-dy, -dx) * dist * friction;
考虑以下案例:
车轮处于稳定位置,与旋转对齐 在整个系统顺时针旋转足够长的情况下,汽车的周长。
现在,整个系统正在旋转CCW:然后旋转应该导致 一些车轮执行初始CCW旋转(车轮 在示例图片中的右上角)应该是充分的 补丁直到车轮对齐。车轮左上,左下和右下应为 旋转CW。
有没有一种简单的方法可以在没有任何物理引擎的情况下计算车轮的旋转角度?
我不需要精确的物理刚体模拟,只需要简单的旋转效果。
答案 0 :(得分:1)
简单旋转
假设轮子是脚轮。
描述一个
// ? 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
}
当推车移动旋转和/或移动时,车轮上有一个局部运动矢量旋转
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之间角度的误差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已要求进行一些改进。跳过更新上方的行为了让车轮滑动(或转动),将减少旋转接头上的旋转力。
滑动量与wheelForce和车轮方向之间的角度的cos有关。如果角度为0则cos将返回1意味着车轮完全可以自由滚动/滑动,如果车轮方向与力相差90度,则它们将不会转动(如将车辆侧向拖动)cos 90返回0
但是如果我们允许车轮在没有阻力的情况下完全转动那么这种方法效果不佳。因此,通过限制因素,此修改将改善SIM卡。
因此计算出滑动量
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;
还会将此添加到下面的演示代码中。
就这种方法而言,这是允许的。如果你想要更好的SIM卡,我们必须从头开始并使用更复杂的解决方案
就是这样。
<强>样本强>
它接缝方式简单所以不得不在代码中尝试它,下面是使用上述方法的演示。您可以通过限制添加车轮角度的数量来改善它,乘以您添加的角度的误差使其响应性稍差,并且添加滑点只需乘以另一部分。
因此,在函数updateTrolly中改变车轮角度
ww.angle -= Math.asin(cross);
到
ww.angle -= Math.asin(cross) * Math.abs(cross) * 0.1;
其中Math.abs(cross)
是添加角度的正误,0.1
是滑点量(任何值&lt; 1和&gt; 0,1没有滑点)
单击并拖动购物车上或附近以移动它。车轮将随之而来。
该演示具有updateTrolly
功能,用于处理车轮,但取决于displayTrolly
功能中计算的车轮位置。
您感兴趣的代码大约在其余部分开始,只需处理鼠标和画布。
DEMO UPDATED 请参阅answer slippage中的更新。
/** 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 **/