如何在模拟城市5中实现一个物理效果,即当建筑物被移动时摇摆?

7

很难用简单的文字描述,因此我录制了一个GIF来演示。

https://public.lightpic.info/image/2B1F_582465841.gif

我目前正在进行一个需要这种效果的项目。 到目前为止,我已经使用SpriteKit在iOS上完成了类似的效果,但不幸的是,结果并不令人满意。
这是我的工作:在我的项目中,有一个具有不规则形状的对象,恒定的力被施加在对象重心下方的某个位置,并且其方向是向下的。 我制定了一个规则,即该力不能改变物体的坐标,以使物体不会被拉向下方。 当物体倾斜时,它的惯性和力会产生角动量,使物体保持竖直。 详细信息:

Force Demo

当物体被操纵时,作用点高于重心。由于惯性和力的组合,角动量会使物体倾斜。
然而,没有证据证明我是正确的,因为SpriteKit的物理引擎给出的结果是,物体变成了一个简单的摆。但直觉告诉我我并没有错,实际上我没有将物体的重心固定在背景上,我真正做的是每次模拟物理时将物体放回原来的位置。但结果真的打了我的脸 :(。因此,摆荡不断,最终的物理效果非常糟糕。
然后我想出了一个临时解决方案:每次模拟物理时将角速度乘以0.95。这个解决方案显然不是理想的,因为当旋转角度趋向于水平时,角速度不够高,会慢慢变直立。但至少有一个进步:物体最终能停止摆动。
我的进一步解决方案是,作用在物体上的力随倾斜程度而改变。当物体趋于水平时,力趋于大,当物体趋于垂直时,力趋于小。一个简单的函数可以很好地描述它:F=1000N×|sin[旋转角度]|。这对于解决问题有很大帮助,但不幸的是,结果似乎完全不符合物理规律。
结论:经过多天的研究,我未能实现GIF中展示的效果,我感到非常惭愧。我真的希望任何具有出色能力的人能够帮助我。谢谢您阅读我漫长的描述,我非常感激您的耐心。
补充:
有一张截图显示了我实现此效果的方法。

My implementation

补充2:

我已经上传了我的实现,这是一个Swift Playground文件。请在以下链接下载:http://www.mediafire.com/file/qrct5sty2cyvwsy/Swing.playground.zip

附注:由于我的母语不是英语,请原谅我的语法错误。


这个摇动的轴是x轴,或者差不多是x轴。这需要使用一个带有3D变换的3D场景。你能截屏或以其他方式展示你如何在2D环境中设置场景吗?这样我可以描述如何解决这个问题。 - Confused
@Confused 是的,轴是x。我添加了一张屏幕截图,它可能会有用。非常感谢您回答我的问题。 - Source
嘿,Source,那个链接已经失效了,或者很奇怪,或者是本地链接,总之什么也没有。你可以将图片内联到你的问题中,只需在编辑时点击图像按钮,在文本框上方的样式/编辑行中即可。 - Confused
@Confused 很抱歉给您带来不便。我已经修复了,现在您能看到图片了吗? - Source
抱歉,我已经看到了这个问题。我想看看你正在做什么,以及你如何使用二维和三维空间,这样我才能提出建议,不仅是如何获得你想要的感觉,还有如何让它在你的空间中发挥作用。编辑:糟糕,刷新浏览器后,我现在可以看到你的图片了。 - Confused
显示剩余2条评论
1个回答

2

一根弹簧,一个摆臂和一些锈迹...

你需要的是一个连接着弹簧和阻尼器,并可以在小车上移动的摆臂。

弹簧施加力,将摆臂归位到垂直位置。

阻尼(在演示中以摩擦形式施加在旋转关节处,例如生锈的关节)只是为了防止其永久振荡。

鼠标移动会在摆臂顶部施加反方向的力。

钟摆对比摆臂

钟摆和带弹簧的摆臂之间的差异之一是,振荡的频率将根据角动量的大小、弹簧上的最小张力以及摆臂的位置而变化。

交互式演示

该演示展示了摆臂的运动,但摆臂的特性取决于很多因素;摆臂的高度、弹簧的强度、阻尼、摆臂的质量、弹簧安装在臂上的位置以及固定到移动小车上。我添加了一些滑块,让你看到不同的行为。

该演示并不是一个答案,只是为了说明这个概念,你需要在使用的任何软件或库中实现解决方案。

弹簧非常简单,弹簧长度和力之间有线性关系。查找胡克定律。

在示例中,阻尼只是应用于角度变化的标量。dr *= 1-damping

力以加速度和力(牛顿单位为像素)在摆臂上施加。支点固定,因此任何线性加速度都会丢失。

更新。第一篇文章中有一些错误。我将弹簧力作为加速度而不是力施加,弹簧无法张紧,鼠标的移动被错误地转换为加速度。差异微妙但重要。所有问题都已修复,祝使用愉快。

var canvas = document.createElement("canvas");
canvas.width = innerWidth - 40;
canvas.height = innerHeight - 40;
canvas.style.border = "1px solid black";
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
var sliderChanged = true;
function createSlider(name,val,min,max){
var div = document.createElement("div");    
div.textContent = name;
var slider = document.createElement("input");
var valSpan = document.createElement("span");    
slider.type = "range";
slider.min = min;
slider.max = max;

slider.step = (max-min)/Math.floor(canvas.width * 0.7);
slider.value = val;
valSpan.textContent = val;
slider.addEventListener("mousemove",function(){
    if(slider.value !== slider.lastValue){
        slider.lastValue = slider.value;
        valSpan.textContent = Number(slider.value).toFixed(3);
        sliderChanged = true;
     }
});
div.appendChild(slider);
div.appendChild(valSpan);
document.body.appendChild(div);
return slider;
}

var springTension = createSlider("Spring tension :",0.5,0,1);
var springStrength = createSlider("Spring strength :",5,0.1,20);
var damping = createSlider("Damping :",0.1,0.01,1.0);
var armMass = createSlider("Swing arm mass:",200,1,1000);
var armHeight = createSlider("Swing arm height:",Math.floor(canvas.height * 0.6),Math.floor(canvas.height * 0.1),Math.floor(canvas.height * 0.8));



var mouse = (function () {
function preventDefault(e) {
    e.preventDefault();
}
var mouse = {
    x : 0,
    y : 0,
    bounds : null,
    mouseEvents : "mousemove".split(",")
};
var m = mouse;
function mouseMove(e) {
    var t = e.type;
    m.bounds = m.element.getBoundingClientRect();
    m.x = e.pageX - m.bounds.left;
    m.y = e.pageY - m.bounds.top;
}
m.updateBounds = function () {
}
m.start = function (element) {
    m.element = element === undefined ? document : element;
    m.mouseEvents.forEach(n => {
        m.element.addEventListener(n, mouseMove);
    });
    m.updateBounds();
}
return mouse;
})();
mouse.start(canvas);

//=====================================================================================================================
// Answer start here

const springCof = 0.3;  // characteristic of the spring see Hooks law
const dampingC = 0.05;  // amount of damping as a factor of rotational speed.
const springTensionC = 0.5; // min tension on the spring ( 1 subtract the amount the spring is stretched from relaxed length)

// details of the swing arm

var pole = {};
pole.mass = 200;
pole.dr = 0;
pole.rot = 0;
pole.piviotRadius = canvas.height * 0.01;
pole.topWidth = canvas.height * 0.02
pole.centerWidth = canvas.height * 0.04
pole.baseWidth = canvas.height * 0.02
pole.x = canvas.width / 2;
pole.y = canvas.height * 0.7;
pole.height = canvas.height * 0.6; // from rotation point to top
pole.baseHeight = canvas.height * 0.1;
pole.spring = {};
pole.spring.y = canvas.height * 0.1;
pole.spring.x = 0;
pole.spring.baseY = canvas.height * 0.2;
pole.spring.baseX = 0;
pole.spring.relaxLength = Math.hypot(pole.spring.x -pole.spring.baseX, pole.spring.y - pole.spring.baseY);
pole.spring.relaxLength *= springTensionC;
pole.spring.cof = springCof;  // characteristic of the spring see Hooks law
pole.spring.damp = dampingC;  // amount of damping as a factor of rotational speed.
                      // Basicly the pivot is rusty and provides the damping.
function setPoleValues(pole){
pole.height = Number(armHeight.value);
pole.mass = Number(armMass.value);
var lookRight = Math.pow(pole.mass,1/3)/Math.pow(1000,1/3);
pole.topWidth = canvas.height * (0.001 + 0.02 * lookRight);
pole.centerWidth = canvas.height * (0.004 + 0.04 * lookRight)
pole.baseWidth = canvas.height * (0.004 + 0.02 * lookRight)
pole.spring.relaxLength = Math.hypot(pole.spring.x -pole.spring.baseX, pole.spring.y - pole.spring.baseY);
pole.spring.relaxLength *= 1-Number(springTension.value);
pole.spring.cof = Number(springStrength.value);  
pole.spring.damp = Number(damping.value);
}

// draws a spring
function drawSpring(x1,y1,x2,y2,width){
var x = x2 - x1;
var y = y2 - y1;
var dist = Math.sqrt(x * x + y * y);

var nx = x / dist;
var ny = y / dist;
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo(x1,y1);
var step = 0.1;
for(var i = step; i < 1-step; i += step){
    for(var j = 0; j < 1; j += 0.1){
        var xx = x1 + x * (i + j * step);
        var yy = y1 + y * (i + j * step);
        xx -= Math.sin(j * Math.PI * 2) * ny * width;
        yy += Math.sin(j * Math.PI * 2) * nx * width;
        ctx.lineTo(xx,yy);
    }
}
ctx.lineTo(x2,y2);
ctx.stroke();
return dist;
}


// draws the pole and also calculates the position of the pole top
// and details about the spring
function drawPole(pole){
ctx.fillStyle = "red";
ctx.strokeStyle = "black";
ctx.lineWidth = 4;
ctx.lineJoin = "round";
ctx.setTransform(1,0,0,1,pole.x,pole.y)
ctx.rotate(pole.rot)
ctx.beginPath();
ctx.moveTo( - pole.topWidth,- pole.height);
ctx.lineTo(pole.topWidth,- pole.height);
ctx.lineTo(pole.centerWidth,0);
ctx.lineTo(pole.baseWidth, pole.baseHeight);
ctx.lineTo( - pole.baseWidth, pole.baseHeight);
ctx.lineTo( - pole.centerWidth,0);
ctx.closePath();
ctx.stroke();
ctx.fill();
ctx.fillStyle = "yellow";
ctx.beginPath();
ctx.arc(  pole.spring.x,pole.spring.y,pole.piviotRadius * 0.5,0,Math.PI*2);
ctx.stroke();
ctx.fill();
ctx.setTransform(1,0,0,1,0,0)
ctx.fillStyle = "blue";
ctx.beginPath();
ctx.arc(pole.x,pole.y,pole.piviotRadius,0,Math.PI*2);
ctx.stroke();
ctx.fill();
ctx.fillStyle = "yellow";
ctx.beginPath();
ctx.arc(pole.x + pole.spring.baseX,pole.y  + pole.spring.baseY,pole.piviotRadius * 0.5,0,Math.PI*2);
ctx.stroke();
ctx.fill();


var xdx = Math.cos(pole.rot);
var xdy = Math.sin(pole.rot);
var xx = pole.spring.realX = xdx * pole.spring.x - xdy * pole.spring.y;
var yy = pole.spring.realY = xdy * pole.spring.x + xdx * pole.spring.y;
pole.spring.length = Math.hypot(pole.x + xx -(pole.x + pole.spring.baseX), pole.y + yy- (pole.y + pole.spring.baseY));
pole.spring.direction = Math.atan2(pole.y + pole.spring.baseY - (pole.y + yy),pole.x + pole.spring.baseX-(pole.x + xx ))
pole.topX = pole.x + xdy * pole.height; // at 90 deg
pole.topY = pole.y - xdx * pole.height;    
   drawSpring(pole.x + xx,pole.y  + yy,pole.x + pole.spring.baseX,pole.y  + pole.spring.baseY,3);

}
// applies a force.
// As the the swing arm rotation point is fixed this only extracts the 
// angular acceleration from the force
function applyAccel(pole,x,y,ax, ay){ // x,y where the force is applied,
                                  // ax,ay the acceleration of the force
var direction = Math.atan2(ay,ax);
var toCenter = Math.atan2(pole.y - y, pole.x - x);
var pheta = toCenter - direction;
var dist = Math.hypot(x-pole.x,y-pole.y);
var force = Math.hypot(ax,ay) * pole.mass;
var Fa = Math.sin(pheta) * force; 
Fa = Fa / (pole.mass * dist);
pole.dr += Fa;// now add that to the box delta r    
}
function applyForce(pole, x, y, fx, fy){ // x,y where the force is applied, 
                                     // fx,fy the force
var direction = Math.atan2(fy,fx);
var toCenter = Math.atan2(pole.y - y, pole.x - x);
var pheta = toCenter - direction;
var dist = Math.hypot(x-pole.x,y-pole.y);
var force = Math.hypot(fx,fy) ;
var Fa = Math.sin(pheta) * force; 
Fa = Fa / (pole.mass * dist);
pole.dr += Fa;// now add that to the box delta r    
}





// for calculating the acceleration of the mouse
var lastX = 0;
var speed = {};
speed.x = 0;
speed.y = 0;
speed.lx = 0;
speed.ly = 0;




function update2(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1;           // reset alpha
ctx.clearRect(0,0,canvas.width,canvas.height);
if(sliderChanged){
    setPoleValues(pole);
    sliderChanged;
}

if(lastX == undefined){
    lastX = mouse.x;
    getPoleDetails(pole);
}
    drawPole(pole);
// move the pole
pole.x = mouse.x;
// get the acceleration of the mouse movement
speed.x = (lastX - mouse.x);
speed.y = 0;

// apply the mouse movement acceleration to the top of the pole
// Accel is the change in mouse speed
applyAccel(pole,pole.topX,pole.topY,speed.x - speed.lx, speed.y - speed.ly);

// apply the springs force (note the spring is never compressed)
applyForce(
    pole,
    pole.x + pole.spring.realX, 
    pole.y + pole.spring.realY,
    Math.cos(pole.spring.direction) * (pole.spring.length - pole.spring.relaxLength) * pole.spring.cof,
    Math.sin(pole.spring.direction) * (pole.spring.length - pole.spring.relaxLength) * pole.spring.cof
)
// add the change in rotation
pole.rot += pole.dr;
// dampen the rotation 
pole.dr *= 1-pole.spring.damp;

lastX = mouse.x
speed.lx = speed.x;
speed.ly = speed.y
if((mouse.buttonRaw & 4)!== 4){
    requestAnimationFrame(update2);
}else{
    log("done");
}
}
requestAnimationFrame(update2);


非常感谢!Spring确实很好地模拟了它! - Source

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接