How to implement a physical effect in SimCity 5, that a building swings when it is being moved?

淺唱寂寞╮ 提交于 2019-11-30 09:34:08

A spring, a swing arm and some rust..

What you are after is a swing arm attached to a spring and a dampener that moves on a trolley.

The spring applies force to return the swing arm back to the upright position.

The dampening (in the demo it is applied at the rotation joint as friction such as found in a rusty joint) is there just to stop it oscillating forever.

Moving the mouse applies a force in the opposite direction at the top of the swing arm.

Pendulum V Swing arm

One of the differences between a pendulum and the spring loaded swing arm is that the frequency of the oscillation will change depending on the amount of angular momentum, minimum tension on the spring, and the position of the swing arm.

Interactive illistration

The illistration shows the swing arm in action, but the characteristics of the swingarm is dependent on many many factors; The height of the swing arm, the strength of the spring, the damping, the mass of the swing arm, where the spring is mounted on the arm and fixed to the moving trolley. I have added some sliders to let you see the different behaviours.

The illistration is not an answer, it is only there to illustrate the concept, you will have to implement the solution in whatever software or library you use.

Springs are very simple, with a linear relationship between spring length and force. Lookup Hook's law.

Damping in the example is just a scalar applied to the delta rotation. dr *= 1-damping

The forces are applied to the swing arm at a location as acceleration and force (newtons in pixel BS units). The pivot is fixed and thus any linear acceleration is lost.

Update. There were a few mistakes in the first post. I applied the spring force as acceleration rather than force, the spring could not be tensioned, and the movement of the mouse was incorrectly converted to acceleration. The differences are subtle but important. All fixed 🤞 enjoy 😀

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);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!