Attach wings via physics engine (they're now... odd.)
[featherfall2.git] / src / main.js
index 26ec069..ecff045 100644 (file)
 "use strict";
 
+var b2Vec2 = yf.argcd(
+    function (a) {
+        return b2Vec2.call(this, a[0] || a.x || 0.0, a[1] || a.y || 0.0);
+    },
+    Box2D.b2Vec2
+);
+
+
+yf.ipairs(function (k, v) { Box2D[k] = Box2D[v]; }, {
+    DYNAMIC_BODY: 'b2_dynamicBody'
+});
+
+function camelizeCase (s) {
+    return s.replace(/_([a-z])/g, function (m) {
+        return m[1].toUpperCase();
+    });
+}
+
+function wrapEmscriptenType (T) {
+    var P = T.prototype;
+    var keys = Object.keys(P);
+    var members = keys.filter(function (k) { return k.startsWith("get_"); });
+    var accessors = keys.filter(function (k) { return k.startsWith("Get"); });
+    members.forEach(function (k) {
+        var name = camelizeCase(k.slice(4));
+        if (!name || name in P) return;
+        Object.defineProperty(P, name, {
+            get: P[k], set: P['s' + k.slice(1)]
+        });
+            
+    });
+    accessors.forEach(function (k) {
+        var name = k.slice(3);
+        name = name[0].toLowerCase() + name.slice(1);
+        if (!name || name in P) return;
+        Object.defineProperty(P, name, {
+            get: P[k], set: P['S' + k.slice(1)]
+        });
+            
+    });
+}
+
+yf.each(wrapEmscriptenType, [
+    Box2D.b2Vec2, Box2D.b2BodyDef, Box2D.b2Body,
+    Box2D.b2JointDef, Box2D.b2RevoluteJointDef,
+    Box2D.b2Joint, Box2D.b2DistanceJoint, Box2D.b2RevoluteJoint,
+]);
+
+yT.defineProperties(Box2D.b2Vec2.prototype, {
+    0: { alias: 'x' },
+    1: { alias: 'y' },
+});
+
+
 var storage;
 
-/*var PlayerController = new yT(yuu.C, {
-    constructor: function () {
-        
+var BodyC = yT(yuu.C, {
+    SLOTS: ['transform'],
+
+    constructor: function (body, size) {
+        this.body = body;
+        this._matrix = mat4.create();
+    },
+
+    position: { alias: 'body.position' },
+    linearVelocity: { alias: 'body.linearVelocity' },
+    ApplyForce: { proxy: 'body.ApplyForce' },
+
+    matrix: { get: function () {
+        var mat = this._matrix;
+        mat4.identity(mat);
+        var pos = this.body.position;
+        mat4.translate(mat, mat, [pos.x, pos.y, 0]);
+        mat4.rotateZ(mat, mat, this.body.angle);
+        return mat;
+    } }
+});
+
+var PlayerController = yT(yuu.C, {
+    constructor: function (body, left, right, leftJoint, rightJoint) {
+        this.body = body;
+        this.left = left;
+        this.right = right;
+        this.leftJoint = leftJoint;
+        this.rightJoint = rightJoint;
+        this.dleftLeft = this.dleftRight = 
+            this.drightLeft = this.drightRight = 0;
+        this.up = 0;
+        this.free = 0;
+        this.leftPivot = 0;
+        this.rightPivot = 0;
+        this.commands = {
+            dleftLeft: yuu.propcmd(this, 'dleftLeft'),
+            dleftRight: yuu.propcmd(this, 'dleftRight'),
+            drightLeft: yuu.propcmd(this, 'drightLeft'),
+            drightRight: yuu.propcmd(this, 'drightRight'),
+            up: yuu.propcmd(this, 'up'),
+            free: yuu.propcmd(this, 'free'),
+        };
+    },
+
+    _updatePivots: function () {
+        var PIVOT_SPEED = 0.05;
+        var leftSpeed = (this.dleftRight - this.dleftLeft) * PIVOT_SPEED;
+        var rightSpeed = (this.drightLeft - this.drightRight) * PIVOT_SPEED;
+        this.leftPivot = yf.clamp(this.leftPivot + leftSpeed, 0, 1);
+        this.rightPivot = yf.clamp(this.rightPivot + rightSpeed, 0, 1);
+    },
+
+    _updateTransforms: function () {
+        var gain = 1.0;
+        var leftTarget = this.leftPivot * Math.PI / 2;
+        var rightTarget = -this.rightPivot * Math.PI / 2;
+        var leftError = this.leftJoint.jointAngle - leftTarget;
+        var rightError = this.rightJoint.jointAngle - rightTarget;
+        this.leftJoint.motorSpeed = -gain * leftError;
+        this.rightJoint.motorSpeed = -gain * rightError;
+    },
+
+    tick: function () {
+        this._updatePivots();
+        var THRUST = 3.5;
+        var DRAG_FREE = 0.01;
+        var DRAG_OPEN = 0.5;
+        var DRAG_LOCK = 1;
+        var CORRECTION = 1;
+
+        var leftAngle = (1 - this.leftPivot) * Math.PI / 2;
+        var rightAngle = (1 - this.rightPivot) * Math.PI / 2;
+
+        var cleft = Math.cos(leftAngle);
+        var cright = Math.cos(rightAngle);
+        var sleft = Math.sin(leftAngle);
+        var sright = Math.sin(rightAngle); 
+
+        var thrust = +!this.free * +this.up * THRUST;
+        var ax = thrust * (cleft - cright); 
+        var ay = thrust * (sright + sleft);
+
+        var v = this.body.linearVelocity;
+        var drag = this.up ? DRAG_OPEN : this.free ? DRAG_FREE : DRAG_LOCK;
+        ax += drag * Math.max(cleft, cright) * v.x * v.x * -Math.sign(v.x);
+        ay += drag * (sleft + sright) * v.y * v.y * -Math.sign(v.y);
+
+        if (!this.up || this.free)
+            ax += CORRECTION * (cleft - cright) * v.y * v.y * Math.sign(v.y);
+
+        this.body.ApplyForce(new b2Vec2(ax, ay), this.body.position);
+
+        this._updateTransforms();
     },
 
     TAPS: ['tick'],
-});*/
+});
+
+function bodyFromAABB (world, position, aabb, density) {
+    var bd = new Box2D.b2BodyDef();
+    var shape = new Box2D.b2PolygonShape();
+    shape.SetAsBox(aabb.hw, aabb.hh);
+    if (density)
+        bd.type = Box2D.DYNAMIC_BODY;
+    bd.position = new b2Vec2(position[0], position[1]);
+    var body = world.CreateBody(bd);
+    body.CreateFixture(shape, density || 0);
+    return body;
+}
+
+function bodyFromLine (world, p0, p1) {
+    var bd = new Box2D.b2BodyDef();
+    var shape = new Box2D.b2EdgeShape();
+    shape.Set(new b2Vec2(p0), new b2Vec2(p1));
+    var body = world.CreateBody(bd);
+    body.CreateFixture(shape, 0);
+    return body;
+}
+
+function pinJoint (world, bodyA, bodyB, anchor) {
+    var dfn = new Box2D.b2RevoluteJointDef();
+    dfn.Initialize(bodyA, bodyB, new b2Vec2(anchor));
+    dfn.maxMotorTorque = 10.0;
+    dfn.motorSpeed = 0.0;
+    dfn.enableMotor = true;
+    return Box2D.castObject(world.CreateJoint(dfn), Box2D.b2RevoluteJoint);
+}
 
 var GameScene = yT(yuu.Scene, {
     constructor: function () {
         yuu.Scene.call(this);
 
-        this.layer0.resize(-0.5, -0.5, 1, 1);
+        var zoom = 10;
+        this.layer0.resize(
+            zoom * -1.3333333333/2, zoom * -0.2, zoom * 1.3333333333, zoom * 1);
+
+        var world = new Box2D.b2World(new b2Vec2(0, -5));
+
+        var body, left, right;
+        this.player = new yuu.E(
+            body = new BodyC(bodyFromAABB(
+                world, [0, 5], new yuu.AABB(0.89, 1.0), 1.0)),
+            new yuu.QuadC('@player')
+                .setSize([0.89, 1.0])
+        );
 
-        this.player = new yuu.E(new yuu.Transform(),
-                                new yuu.QuadC('@player'));
-        var leftWing = new yuu.E(new yuu.Transform(),
-                                 new yuu.QuadC('@left'));
-        var rightWing = new yuu.E(new yuu.Transform(),
-                                  new yuu.QuadC('@right'));
+        var leftWing = new yuu.E(left = new BodyC(
+            bodyFromAABB(world, [-0.50, 5.15], new yuu.AABB(0.45, 0.22), 1.0)),
+            new yuu.QuadC('@left')
+                .setZ(-1)
+                .setSize([0.45, 0.22]));
+        var leftJoint = pinJoint(world, left.body, body.body, [-0.1, 5.15]);
+        var rightWing = new yuu.E(right = new BodyC(
+            bodyFromAABB(world, [0.50, 5.15], new yuu.AABB(0.45, 0.22), 1.0)),
+            new yuu.QuadC('@right')
+                .setZ(-1)
+                .setSize([0.45, 0.22]));
+        var rightJoint = pinJoint(world, right.body, body.body, [0.1, 5.15]);
         this.player.addChildren(leftWing, rightWing);
         this.entity0.addChild(this.player);
 
+        var ground = new yuu.E(
+            new BodyC(bodyFromLine(world, [-100, 0], [100, 0])),
+            new yuu.QuadC()
+                .setAnchorAtPosition('top')
+                .setSize([100, 1])
+                .setColor([0, 0.5, 0, 1]));
+        this.entity0.addChild(ground);
+
+        this.player.attach(
+            this.controller = new PlayerController(body, left, right,
+                                                  leftJoint, rightJoint));
+        Object.assign(this.commands, this.controller.commands);
+
+        this.entity0.attach(new yuu.Ticker(function () {
+            world.Step(1/60, 8, 8);
+            return true;
+        }, 1));
+
         this.ready = yuu.ready([
             new yuu.Material('@player'),
             new yuu.Material('@left'),
             new yuu.Material('@right')]);
     },
 
+    init: function () {
+        var audio0 = new Audio();
+        audio0.src = audio0.canPlayType('audio/ogg')
+            ? "data/sound/starting-line.ogg"
+            : "data/sound/starting-line.mp3";
+        audio0.autoplay = true;
+        audio0.loop = true;
+        document.body.appendChild(audio0);
+        var source = yuu.audio.createMediaElementSource(audio0);
+        source.connect(yuu.audio.music);
+    },
+
     KEYBINDS: {
         space: '+up',
         up: '+up',
-        q: '+dleft_left',
-        w: '+dleft_right',
-        o: '+dright_left',
-        p: '+dright_right',
+        q: '+dleftLeft',
+        w: '+dleftRight',
+        o: '+drightLeft',
+        p: '+drightRight',
+        z: '+free',
+        x: '+up',
     }
 });
 
@@ -50,6 +275,7 @@ function load () {
     yuu.audio.storage = storage;
     var game = new GameScene();
     yuu.director.pushScene(game);
+    return game.ready;
 }
 
 window.addEventListener("load", function() {