乗車日記

自転車ときのこ

MTBシミュレーター

とりあえず、フルリジッドだけど動くようになったけど、同時にだいぶんコードが長くなってきた。
http://jsdo.it/tasanokona/s7Q2/Chromeだとスムースに動きますが、それ以外のブラウザでは遅い、あるいは全く動かないかもしれません。)
ここまでは完全剛体なので、簡単だが、ここから後が難しい。サスペンションがストローク方向には動くが、それに垂直な方向には固定されているのをどう矛盾なく、かつ計算量を増やさずに再現できるか。もうしばらくかかりそうだ。

// forked from tasanokona's "太陽系の形成" http://jsdo.it/tasanokona/vJu0
// forked from tasanokona's "太陽系の形成" http://jsdo.it/tasanokona/vJu0
// forked from tasanokona's "銀河の形成" http://jsdo.it/tasanokona/oQgm
// forked from tasanokona's "2013-05-23 熊たたき" http://jsdo.it/tasanokona/ymX2
enchant();
game_size=1000;
dt=1/10000.0;
dt2=0.5*dt*dt;
scale=100.0/1.0; //dots/meter
Gravity=-9.8;

earth={
    height: 2.0,
    touch:function(x,y){
        var h=this.height-this.height/(game_size/scale)*x;
        if(y<h) return(true);
        else return(false);
    },
    initialize:function(){
        this.sprite=new Sprite(game_size, game_size);
        var surface=new Surface(game_size,game_size);
        surface.context.strokeStyle="blue";
        surface.context.lineWidth=2;
        surface.context.beginPath();
        surface.context.moveTo(0,game_size-this.height*scale);
        surface.context.lineTo(game_size,game_size);
        surface.context.stroke();
        this.sprite.image=surface;
        game.rootScene.addChild(this.sprite);
    }
};
    

function NODE(type_,x_,y_,mass_){
    this.type=type_;
    this.x=x_/1000;
    this.y=y_/1000;
    this.mass=mass_;
}

NODE.prototype.set_wheel=function(r_){
    this.wheel={radius:r_/1000, pressure:2.0*1024*100, width:2.1*25.4/1000*0.5, x:this.x, y:this.y};
    var rad=this.wheel.radius*scale;
    var is=Math.floor(rad*2.0*1.1);
    this.wheel.sprite=new Sprite(is, is);
   
    var surface=new Surface(is,is);
    surface.context.fillStyle="red";
    surface.context.strokeStyle="red";
    surface.context.lineWidth=2;
    surface.context.beginPath();
    surface.context.arc(is/2,is/2,rad,0.0,Math.PI*2);
    surface.context.stroke();
    this.wheel.sprite.image=surface;
    this.wheel.sprite.x=-is/2+this.x*scale;
    this.wheel.sprite.y=-is/2-this.y*scale+game_size;
    this.is=is;
    game.rootScene.addChild(this.wheel.sprite);
};

NODE.prototype.reposition_wheel=function(){
    if(this.wheel!==undefined) {
        this.wheel.sprite.x=-this.is/2+this.x*scale;
        this.wheel.sprite.y=-this.is/2-this.y*scale+game_size;
        this.wheel.x=this.x;
        this.wheel.y=this.y;
  //      console.log(x,y);
    }
};

var FRAME = enchant.Class.create(Sprite,{
        initialize: function(){
            this.num_node=7;
            this.X=0.0;
            this.Y=0.0;
            this.M=0.0;
            this.Vx=0.0;
            this.Vy=0.0;
            this.Ax=0.0;
            this.Ay=0.0;
            this.I=0.0;
            this.Theta=0.0;
            this.Omega=0.0;
            this.Ao=0.0;

            this.node=Array(this.num_node);    

            this.node[0]=new NODE("RearEnd",0,   0   ,3.0);
            this.node[1]=new NODE("BB",425, -20   ,1.8);
            this.node[2]=new NODE("TopChainSeat",302, 333   ,0.5);
            this.node[3]=new NODE("Head",814, 582   ,1.0);
            this.node[4]=new NODE("Saddle",210, 582   ,0.4);
            this.node[5]=new NODE("ForkEnd",1085,0 ,2.8);
            this.node[6]=new NODE("Handle",924,600, 0.8);

            for(var i=0;i<this.num_node;i++)  {
                this.X+=this.node[i].x*this.node[i].mass;
                this.Y+=this.node[i].y*this.node[i].mass;
                this.M+=this.node[i].mass;
            }
            this.X/=this.M;
            this.Y/=this.M;
 //           console.log("COM",this.X,this.Y);
            for(i=0;i<this.num_node;i++)  {
                var dx=this.node[i].x-this.X;
                var dy=this.node[i].y-this.Y;
                this.I+=(dx*dx+dy*dy)*this.node[i].mass;
                this.node[i].x=dx;
                this.node[i].y=dy;
            }

            var x_size=0.0,y_size=0.0;
            for(i=0;i<this.num_node;i++)  {
                var absx=Math.abs(this.node[i].x);
                if(absx>x_size) x_size=absx;
                var absy=Math.abs(this.node[i].y);
                if(absy>y_size) y_size=absy;
            }
            this.image_size_x=Math.floor(2*x_size*scale*1.2);
            this.image_size_y=Math.floor(2*y_size*scale*1.2);
            
            enchant.Sprite.call(this, this.image_size_x, this.image_size_y);
            
            this.X=0.8;
            this.Y=3.0;

            this.x=-this.image_size_x/2+this.X*scale;
            this.y=-this.image_size_y/2+game_size-this.Y*scale;

            for(i=0;i<this.num_node;i++)  {
                this.node[i].x+=this.X;
                this.node[i].y+=this.Y;
            }
           
            var surface=new Surface(this.image_size_x,this.image_size_y);
            surface.context.fillStyle="red";
            surface.context.strokeStyle="red";
            surface.context.lineWidth=2;
            
            for(i=0;i<this.num_node;i++)  {
                var x=(this.node[i].x-this.X)*scale+this.image_size_x/2;
                var y=this.image_size_y/2-(this.node[i].y-this.Y)*scale;
                surface.context.beginPath();
                surface.context.arc(x,y,3,0,Math.PI*2);
                surface.context.fill();
            }
                
            this.connect(0,1,surface);
            this.connect(0,2,surface);
            this.connect(1,2,surface);
            this.connect(1,3,surface);
            this.connect(2,3,surface);
            this.connect(2,4,surface);
            this.connect(3,5,surface);            
            this.connect(3,6,surface);
            this.image=surface;
            this.node[0].set_wheel(315);
            this.node[5].set_wheel(315);
            
            earth.initialize();

            this.addEventListener("enterframe", function(){
                for(i=0;i<100;i++){
                    var force=this.new_force();
                    this.new_accel(force);
                    this.new_position();
                    this.new_velosity();
                }
                this.x=-this.image_size_x/2+this.X*scale;
                this.y=-this.image_size_y/2+game_size-this.Y*scale;
                this.node[0].reposition_wheel();
                this.node[5].reposition_wheel();
            });
            game.rootScene.addChild(this);
        },    

    connect: function(i,j,surf){
            var x1=(this.node[i].x-this.X)*scale+this.image_size_x/2;
            var y1=this.image_size_y/2-(this.node[i].y-this.Y)*scale;
            var x2=(this.node[j].x-this.X)*scale+this.image_size_x/2;
            var y2=this.image_size_y/2-(this.node[j].y-this.Y)*scale;
            surf.context.beginPath();
            surf.context.moveTo(x1,y1);
            surf.context.lineTo(x2,y2);
            surf.context.stroke();
    },


    new_force: function(){
        var force={x:0.0, y:0.0, m:0.0};
      
        for(var i=0;i<this.num_node;i++){
            var node=this.node[i];
            var force_={x:0.0, y:0.0, m:0.0};
            var dx_node=this.X-node.x;
            var dy_node=this.Y-node.y;
            
            if(this.node[i].wheel===undefined){
                force_.x=0.0;
                force_.y=Gravity*node.mass;
            }
            else {
                var dTheta=Math.PI/1000;
                for(var j=0;j<=1000;j++){
                    var theta=dTheta*j;
                    var cos=Math.cos(theta);
                    var sin=Math.sin(theta);
                    var x=node.x-node.wheel.radius*Math.cos(theta);
                    var y=node.y-node.wheel.radius*Math.sin(theta);
                    var vx=-(y-this.Y)*this.Omega+this.Vx;
                    var vy=(x-this.X)*this.Omega+this.Vy;
                    if(earth.touch(x,y)===true){
                        var dS=node.wheel.width * node.wheel.radius * dTheta;
                        var force_abs=node.wheel.pressure*dS;
                        var damp=(vx*(-cos)+vy*(-sin))/0.00001*dS;
                        force_.x+=(force_abs+damp)*cos;
                        force_.y+=(force_abs+damp)*sin;
                    }
                }
                force.y+=Gravity*this.node[i].mass;
            }
            force.x+=force_.x;
            force.y+=force_.y;
            force.m+=-dx_node*force_.y+dy_node*force_.x;
        }
        return(force);
    },
    
    new_accel: function(force){
        this.Ax=force.x/this.M;
        this.Ay=force.y/this.M;
        this.Ao=force.m/this.I;
    },
    
    new_velosity: function(){
        this.Vx+=this.Ax*dt;
        this.Vy+=this.Ay*dt;
        this.Omega+=this.Ao*dt;
    },
    
    new_position: function(){
        var X=this.X;
        var Y=this.Y;
        this.X+=this.Vx*dt+this.Ax*dt2;
        this.Y+=this.Vy*dt+this.Ay*dt2;
        var dTheta=this.Omega*dt+this.Ao*dt2;
        var cos=Math.cos(dTheta);
        var sin=Math.sin(dTheta);
        for(var i=0;i<this.num_node;i++){
            var dx=this.node[i].x-X;
            var dy=this.node[i].y-Y;
            this.node[i].x=dx*cos-dy*sin+this.X;
            this.node[i].y=dy*cos+dx*sin+this.Y;
        }
        this.Theta+=dTheta;
        this.rotate(-180/Math.PI*dTheta);
    } 

    });

window.onload = function(){
    game = new Core(game_size, game_size);
    game.fps = 100;
    game.onload = function(){
        game.rootScene.backgroundColor = "black";
        var frame=new FRAME();
    };
    game.start();
};