Auz / Bug

Add bugs to your website
https://auz.github.io/Bug/
435 stars 80 forks source link

Adding interaction between the bugs controller and the spiders controller #37

Closed jlbocquet closed 1 month ago

jlbocquet commented 6 months ago

// ==ClosureCompiler== // @compilation_level SIMPLE_OPTIMIZATIONS // @output_file_name bug-min.js // ==/ClosureCompiler== /**

/**

"use strict";

var BugDispatch = {

options: {
    minDelay: 500,
    maxDelay: 10000,
    minBugs: 2,
    maxBugs: 20,
    minSpeed: 5,
    maxSpeed: 10,
    maxLargeTurnDeg: 150,
    maxSmallTurnDeg: 10,
    maxWiggleDeg: 5,
    imageSprite: './js/Auz-Bug/fly-sprite.png',
    bugWidth: 13,
    bugHeight: 14,
    num_frames: 5,
    zoom: 10, // random zoom variation from 1 to 10 - 10 being full size.
    canFly: true,
    canDie: true,
    numDeathTypes: 3,
    monitorMouseMovement: false,
    eventDistanceToBug: 40,
    minTimeBetweenMultipy: 1000,
    mouseOver: 'random', // can be 'fly', 'flyoff' (if the bug can fly), die', 'multiply', 'nothing' or 'random'
    // @@@ JLB @@@
    bugsToKill:null,
    gettingFatterWhenKill:true,
    reproduceWhenKill:true,
    reproduceWhenKillTrigger:3,
    killWhenStarvation:true,
},

// @@@ JLB @@@@
// Replace JSON.parse(JSON.stringify(obj));
// Avoid infinite recursive loop ...
_duplicateObject(obj) {
    const dup = {};
    for (const property in obj) {
        dup[property] = obj[property];
    }
    return dup;
},

initialize: function(options) {

    this.options = mergeOptions(this.options, options);
    // @@@ JLB @@@
    this.options.bugDispatcher = this;
    this.multiplyDelay = false;

    // sanity check:
    if (this.options.minBugs > this.options.maxBugs) {
        this.options.minBugs = this.options.maxBugs;
    }

    this.modes = ['multiply', 'nothing'];

    if (this.options.canFly) {
        this.modes.push('fly', 'flyoff');
    }
    if (this.options.canDie) {
        this.modes.push('die');
    }

    if (this.modes.indexOf(this.options.mouseOver) == -1) {
        // invalid mode: use random:
        this.options.mouseOver = 'random';
    }

    // can we transform?
    this.transform = null;

    this.transforms = {
        'Moz': function(s) {
            this.bug.style.MozTransform = s;
        },
        'webkit': function(s) {
            this.bug.style.webkitTransform = s;
        },
        'O': function(s) {
            this.bug.style.OTransform = s;
        },
        'ms': function(s) {
            this.bug.style.msTransform = s;
        },
        'Khtml': function(s) {
            this.bug.style.KhtmlTransform = s;
        },
        'w3c': function(s) {
            this.bug.style.transform = s;
        }
    };

    // check to see if it is a modern browser:

    if ('transform' in document.documentElement.style) {
        this.transform = this.transforms.w3c;
    } else {

        // feature detection for the other transforms:
        var vendors = ['Moz', 'webkit', 'O', 'ms', 'Khtml'],
            i = 0;

        for (i = 0; i < vendors.length; i++) {
            if (vendors[i] + 'Transform' in document.documentElement.style) {
                this.transform = this.transforms[vendors[i]];
                break;
            }
        }
    }

    // dont support transforms... quit
    if (!this.transform) return;

    // make bugs:
    this.bugs = [];
    var numBugs = (this.options.mouseOver === 'multiply') ? this.options.minBugs : this.random(this.options.minBugs, this.options.maxBugs, true),
        i = 0,
        that = this;

    for (i = 0; i < numBugs; i++) {
        // @@@ JLB @@@ var options = JSON.parse(JSON.stringify(this.options));
        var options = this._duplicateObject(this.options);
        var b = SpawnBug();

        options.wingsOpen = (this.options.canFly) ? ((Math.random() > 0.5) ? true : false) : true,
            options.walkSpeed = this.random(this.options.minSpeed, this.options.maxSpeed),

            b.initialize(this.transform, options);

        this.bugs.push(b);
    }

    // fly them in staggered:
    this.spawnDelay = [];
    for (i = 0; i < numBugs; i++) {
        var delay = this.random(this.options.minDelay, this.options.maxDelay, true),
            thebug = this.bugs[i];
        // fly the bug onto the page:
        this.spawnDelay[i] = setTimeout((function(thebug) {
            return function() {
                if (that.options.canFly) {
                    thebug.flyIn();
                } else {
                    thebug.walkIn();
                }
            };
        }(thebug)), delay);

        // add mouse over events:
        that.add_events_to_bug(thebug);
    }

    // add window event if required:
    if (this.options.monitorMouseMovement) {
        window.onmousemove = function() {
            that.check_if_mouse_close_to_bug();
        };
    }

},

// @@@@ JLB @@@
killBugsUnder: function (bug, bugsToKill) {
    if (bugsToKill === null) return;

    function buildRect($div) {
        const x = $div.offset().left;
        const y = $div.offset().top;
        const h = $div.outerHeight(true);
        const w = $div.outerWidth(true);
        const b = y + h;
        const r = x + w;
        const rout = {
            x:x,
            y:y,
            h:h,
            w:w,
            b:b,
            r:r,
        };
        return rout;
    }

    function stretchRect(rin, ratio) {
        const centerx = rin.x + rin.w/2;
        const centery = rin.y + rin.h/2;
        const w = rin.w * ratio;
        const h = rin.h * ratio;

        const x = centerx - w/2;
        const y = centery - h/2;
        const b = y + h;
        const r = x + w;

        const rout = {
            x:x,
            y:y,
            h:h,
            w:w,
            b:b,
            r:r,
        };
        return rout;
    }

    function collisionRect(r1, r2) {
        if (r1.b < r2.y || r1.y > r2.b || r1.r < r2.x || r1.x > r2.r) return false;
        return true;
    }

    var r1 = buildRect($(bug.bug));
    r1 = stretchRect(r1, bug.zoom * 0.5);

    var numBugsAlive = 0;
    for (var i = 0; i < bugsToKill.bugs.length; i++) {
        const victim = bugsToKill.bugs[i];
        if (victim.alive) {
            const r2 = buildRect($(victim.bug));
            if (collisionRect(r1, r2)) {
                victim.die();
                if (this.options.gettingFatterWhenKill) {
                    // Getting fatter ...
                    bug.zoom += 0.05;
                }
                bug.numBugsKilled += 1;
                if (this.options.reproduceWhenKill) {
                    if ((bug.numBugsKilled % this.options.reproduceWhenKillTrigger) === 0) {
                        // Multiply ...
                        this.multiply_bug(bug);
                    }
                }
            } else {
                numBugsAlive += 1;
            }
        }
    }

    if (this.options.killWhenStarvation && numBugsAlive === 0) {
        for (var i = 0; i < this.bugs.length; i++) {
            const victim = this.bugs[i];
            if (victim.alive) {
                var r2 = buildRect($(victim.bug));
                r2 = stretchRect(r2, victim.zoom * 0.5);
                if (collisionRect(r1, r2)) {
                    if (victim.zoom < bug.zoom) {
                        // Only smaller victim dies
                        victim.die();
                    }
                }
            }
        }
    }
},

stop: function() {
    for (var i = 0; i < this.bugs.length; i++) {
        if(this.spawnDelay[i]) clearTimeout(this.spawnDelay[i]);
        this.bugs[i].stop();
    }
},

end: function() {
    for (var i = 0; i < this.bugs.length; i++) {
        if(this.spawnDelay[i]) clearTimeout(this.spawnDelay[i]);
        this.bugs[i].stop();
        this.bugs[i].remove();
    }
},

reset: function() {
    this.stop();
    for (var i = 0; i < this.bugs.length; i++) {
        this.bugs[i].reset();
        this.bugs[i].walkIn();
    }
},

killAll: function() {
    for (var i = 0; i < this.bugs.length; i++) {
        if(this.spawnDelay[i]) clearTimeout(this.spawnDelay[i]);
        this.bugs[i].die();
    }
},

add_events_to_bug: function(thebug) {
    var that = this;
    if (thebug.bug) {
        if (thebug.bug.addEventListener) {
            thebug.bug.addEventListener('mouseover', function(e) {
                that.on_bug(thebug);
            });
        } else if (thebug.bug.attachEvent) {
            thebug.bug.attachEvent('onmouseover', function(e) {
                that.on_bug(thebug);
            });
        }
    }
},

check_if_mouse_close_to_bug: function(e) {
    e = e || window.event;
    if (!e) {
        return;
    }

    var posx = 0,
        posy = 0;
    if (e.client && e.client.x) {
        posx = e.client.x;
        posy = e.client.y;
    } else if (e.clientX) {
        posx = e.clientX;
        posy = e.clientY;
    } else if (e.page && e.page.x) {
        posx = e.page.x - (document.body.scrollLeft + document.documentElement.scrollLeft);
        posy = e.page.y - (document.body.scrollTop + document.documentElement.scrollTop);
    } else if (e.pageX) {
        posx = e.pageX - (document.body.scrollLeft + document.documentElement.scrollLeft);
        posy = e.pageY - (document.body.scrollTop + document.documentElement.scrollTop);
    }
    var numBugs = this.bugs.length,
        i = 0;
    for (i = 0; i < numBugs; i++) {
        var pos = this.bugs[i].getPos();
        if (pos) {
            if (Math.abs(pos.top - posy) + Math.abs(pos.left - posx) < this.options.eventDistanceToBug && !this.bugs[i].flyperiodical) {
                this.near_bug(this.bugs[i]);
            }
        }
    }

},

near_bug: function(bug) {
    this.on_bug(bug);
},

// @@@ JLB @@@
multiply_bug : function(bug) {
    if (!this.multiplyDelay && this.bugs.length < this.options.maxBugs) {
        // spawn another: 
        // create new bug:
        var b = SpawnBug(),
            // @@@ JLB @@@ options = JSON.parse(JSON.stringify(this.options)),
            options = this._duplicateObject(this.options),
            pos = bug.getPos(),
            that = this;

        options.wingsOpen = (this.options.canFly) ? ((Math.random() > 0.5) ? true : false) : true;
        options.walkSpeed = this.random(this.options.minSpeed, this.options.maxSpeed);

        b.initialize(this.transform, options);
        b.drawBug(pos.top, pos.left);
        // fly them both away:
        if (options.canFly) {
            b.flyRand();
            bug.flyRand();
        } else {
            b.go();
            bug.go();
        }
        // store new bug:
        this.bugs.push(b);
        // watch out for spawning too quickly:
        this.multiplyDelay = true;
        setTimeout(function() {
            // add event to this bug:
            that.add_events_to_bug(b);
            that.multiplyDelay = false;
        }, this.options.minTimeBetweenMultipy);
    }
},

on_bug: function(bug) {
    if (!bug.alive) {
        return;
    }

    var mode = this.options.mouseOver;

    if (mode === 'random') {
        mode = this.modes[(this.random(0, (this.modes.length - 1), true))];
    }

    if (mode === 'fly') {
        // fly away!
        bug.stop();
        bug.flyRand();
    } else if (mode === 'nothing') {
        return;
    } else if (mode === 'flyoff') {
        // fly away and off the page
        bug.stop();
        bug.flyOff();
    } else if (mode === 'die') {
        // drop dead!
        bug.die();
    } else if (mode === 'multiply') {
        // @@@ JLB @@@
        // Code moved in function 'multiply_bug'
        this.multiply_bug(bug);
    }
},

random: function(min, max, round) {
    if (min == max) return ((round) ? Math.round(min) : min);

    var result = ((min - 0.5) + (Math.random() * (max - min + 1)));
    if (result > max) {
        result = max;
    } else if (result < min) {
        result = min;
    }
    return ((round) ? Math.round(result) : result);
}

};

var BugController = function() { this.initialize.apply(this, arguments); } BugController.prototype = BugDispatch;

var SpiderController = function() { var spiderOptions = { imageSprite: './js/Auz-Bug/spider-sprite.png', bugWidth: 69, bugHeight: 90, num_frames: 7, canFly: false, canDie: true, numDeathTypes: 2, zoom: 6, minDelay: 200, maxDelay: 3000, minSpeed: 6, maxSpeed: 13, minBugs: 3, maxBugs: 10 }; this.options = mergeOptions(this.options, spiderOptions); this.initialize.apply(this, arguments);

} SpiderController.prototype = BugDispatch;

// / Bug / //

var Bug = {

options: {
    wingsOpen: false,
    walkSpeed: 2,
    flySpeed: 40,
    edge_resistance: 50,
    zoom: 10

},

initialize: function(transform, options) {

    this.options = mergeOptions(this.options, options);

    this.NEAR_TOP_EDGE = 1;
    this.NEAR_BOTTOM_EDGE = 2;
    this.NEAR_LEFT_EDGE = 4;
    this.NEAR_RIGHT_EDGE = 8;
    this.directions = {}; // 0 degrees starts on the East
    this.directions[this.NEAR_TOP_EDGE] = 270;
    this.directions[this.NEAR_BOTTOM_EDGE] = 90;
    this.directions[this.NEAR_LEFT_EDGE] = 0;
    this.directions[this.NEAR_RIGHT_EDGE] = 180;
    this.directions[this.NEAR_TOP_EDGE + this.NEAR_LEFT_EDGE] = 315;
    this.directions[this.NEAR_TOP_EDGE + this.NEAR_RIGHT_EDGE] = 225;
    this.directions[this.NEAR_BOTTOM_EDGE + this.NEAR_LEFT_EDGE] = 45;
    this.directions[this.NEAR_BOTTOM_EDGE + this.NEAR_RIGHT_EDGE] = 135;

    this.angle_deg = 0;
    this.angle_rad = 0;
    this.large_turn_angle_deg = 0;
    this.near_edge = false;
    this.edge_test_counter = 10;
    this.small_turn_counter = 0;
    this.large_turn_counter = 0;
    this.fly_counter = 0;
    this.toggle_stationary_counter = Math.random() * 50;
    this.zoom = this.random(this.options.zoom, 10) / 10;

    this.stationary = false;
    this.bug = null;
    this.active = true;
    this.wingsOpen = this.options.wingsOpen;
    this.transform = transform;
    this.walkIndex = 0;
    this.flyIndex = 0;
    this.alive = true;
    this.twitchTimer = null;

    this.rad2deg_k = 180 / Math.PI;
    this.deg2rad_k = Math.PI / 180;

    this.makeBug();

    this.angle_rad = this.deg2rad(this.angle_deg);

    this.angle_deg = this.random(0, 360, true);

    // @@@ JLB @@@
    this.numBugsKilled = 0;
},

go: function() {
    if (this.transform) {
        this.drawBug();
        var that = this;

        this.animating = true;

        this.going = requestAnimFrame(function(t) {
            that.animate(t);
        });
    }
},

stop: function() {
    this.animating = false;
    if (this.going) {
        clearTimeout(this.going);
        this.going = null;
    }
    if (this.flyperiodical) {
        clearTimeout(this.flyperiodical);
        this.flyperiodical = null;
    }
    if (this.twitchTimer) {
        clearTimeout(this.twitchTimer);
        this.twitchTimer = null;
    }
},

remove: function() {
    this.active = false;
    if (this.inserted && this.bug.parentNode) {
        this.bug.parentNode.removeChild(this.bug);
        this.inserted = false;
    }
},

reset: function() {
    this.alive = true;
    this.active = true;
    this.bug.style.bottom = '';
    this.bug.style.top = 0;
    this.bug.style.left = 0;
    this.bug.classList.remove('bug-dead');
},

animate: function(t) {

    if (!this.animating || !this.alive || !this.active) return;

    var that = this;
    this.going = requestAnimFrame(function(t) {
        that.animate(t);
    });

    if (!('_lastTimestamp' in this)) this._lastTimestamp = t;

    var delta = t - this._lastTimestamp;

    if (delta < 40) return; // don't animate too frequently

    // sometimes if the browser doesnt have focus, or the delta in request animation 
    // frame can be very large. We set a sensible max so that the bugs dont spaz out.

    if (delta > 200) delta = 200;

    this._lastTimestamp = t;

    if (--this.toggle_stationary_counter <= 0) {
        this.toggleStationary();
    }
    if (this.stationary) {
        return;
    }

    if (--this.edge_test_counter <= 0 && this.bug_near_window_edge()) {
        // if near edge, go away from edge
        this.angle_deg %= 360;
        if (this.angle_deg < 0) this.angle_deg += 360;

        if (Math.abs(this.directions[this.near_edge] - this.angle_deg) > 15) {
            var angle1 = this.directions[this.near_edge] - this.angle_deg;
            var angle2 = (360 - this.angle_deg) + this.directions[this.near_edge];
            this.large_turn_angle_deg = (Math.abs(angle1) < Math.abs(angle2) ? angle1 : angle2);

            this.edge_test_counter = 10;
            this.large_turn_counter = 100;
            this.small_turn_counter = 30;
        }
    }
    if (--this.large_turn_counter <= 0) {
        this.large_turn_angle_deg = this.random(1, this.options.maxLargeTurnDeg, true);
        this.next_large_turn();
    }
    if (--this.small_turn_counter <= 0) {
        this.angle_deg += this.random(1, this.options.maxSmallTurnDeg);
        this.next_small_turn();
    } else {
        var dangle = this.random(1, this.options.maxWiggleDeg, true);
        if ((this.large_turn_angle_deg > 0 && dangle < 0) || (this.large_turn_angle_deg < 0 && dangle > 0)) {
            dangle = -dangle; // ensures both values either + or -
        }
        this.large_turn_angle_deg -= dangle;
        this.angle_deg += dangle;
    }

    this.angle_rad = this.deg2rad(this.angle_deg);

    var dx = Math.cos(this.angle_rad) * this.options.walkSpeed * (delta / 100);
    var dy = -Math.sin(this.angle_rad) * this.options.walkSpeed * (delta / 100);

    this.moveBug((this.bug.left + dx), (this.bug.top + dy), (90 - this.angle_deg));
    this.walkFrame();

    // @@@ JLB @@@
    this.options.bugDispatcher.killBugsUnder(this, this.options.bugsToKill);
},

makeBug: function() {
    if (!this.bug && this.active) {
        var row = (this.wingsOpen) ? '0' : '-' + this.options.bugHeight + 'px',
            bug = document.createElement('div');
        bug.className = 'bug';
        bug.style.background = 'transparent url(' + this.options.imageSprite + ') no-repeat 0 ' + row;
        bug.style.width = this.options.bugWidth + 'px';
        bug.style.height = this.options.bugHeight + 'px';
        bug.style.position = 'fixed';
        bug.style.top = 0;
        bug.style.left = 0;
        bug.style.zIndex = '9999999';

        this.bug = bug;
        this.setPos();

    }

},

setPos: function(top, left) {
    this.bug.top = top || this.random(this.options.edge_resistance, document.documentElement.clientHeight - this.options.edge_resistance);

    this.bug.left = left || this.random(this.options.edge_resistance, document.documentElement.clientWidth - this.options.edge_resistance);

    this.moveBug(this.bug.left, this.bug.top, (90 - this.angle_deg));
},

moveBug: function(x, y, deg) {
    // keep track of where we are:
    this.bug.left = x;
    this.bug.top = y;

    // transform:
    var trans = "translate(" + parseInt(x) + "px," + parseInt(y) + "px)";
    if (deg) {
        //console.log("translate("+(x)+"px, "+(y)+"px) rotate("+deg+"deg)");
        trans += " rotate(" + deg + "deg)";
    }
    trans += " scale(" + this.zoom + ")";

    this.transform(trans);
},

drawBug: function(top, left) {

    if (!this.bug) {
        this.makeBug();
    }
    if(!this.bug) return;

    if (top && left) {
        this.setPos(top, left);
    } else {
        this.setPos(this.bug.top, this.bug.left)
    }
    if (!this.inserted) {
        this.inserted = true;
        document.body.appendChild(this.bug);
    }
},

toggleStationary: function() {
    this.stationary = !this.stationary;
    this.next_stationary();
    var ypos = (this.wingsOpen) ? '0' : '-' + this.options.bugHeight + 'px';
    if (this.stationary) {

        this.bug.style.backgroundPosition = '0 ' + ypos;
    } else {
        this.bug.style.backgroundPosition = '-' + this.options.bugWidth + 'px ' + ypos;
    }
},

walkFrame: function() {
    var xpos = (-1 * (this.walkIndex * this.options.bugWidth)) + 'px',
        ypos = (this.wingsOpen) ? '0' : '-' + this.options.bugHeight + 'px';
    this.bug.style.backgroundPosition = xpos + ' ' + ypos;
    this.walkIndex++;
    if (this.walkIndex >= this.options.num_frames) this.walkIndex = 0;
},

fly: function(landingPosition) {
    var currentTop = this.bug.top,
        currentLeft = this.bug.left,
        diffx = (currentLeft - landingPosition.left),
        diffy = (currentTop - landingPosition.top),
        angle = Math.atan(diffy / diffx);

    if (Math.abs(diffx) + Math.abs(diffy) < 50) {
        this.bug.style.backgroundPosition = (-2 * this.options.bugWidth) + 'px -' + (2 * this.options.bugHeight) + 'px';
    }
    if (Math.abs(diffx) + Math.abs(diffy) < 30) {
        this.bug.style.backgroundPosition = (-1 * this.options.bugWidth) + 'px -' + (2 * this.options.bugHeight) + 'px';
    }
    if (Math.abs(diffx) + Math.abs(diffy) < 10) {
        // close enough:
        this.bug.style.backgroundPosition = '0 0'; //+row+'px'));

        this.stop();
        this.go();
        //this.go.delay(100, this);

        return;

    }

    // make it wiggle: disabled becuase its just too fast to see... better would be to make its path wiggly.
    //angle = angle - (this.deg2rad(this.random(0,10)));
    //console.log('angle: ',this.rad2deg(angle));

    var dx = Math.cos(angle) * this.options.flySpeed,
        dy = Math.sin(angle) * this.options.flySpeed;

    if ((currentLeft > landingPosition.left && dx > 0) || (currentLeft > landingPosition.left && dx < 0)) {
        // make sure angle is right way
        dx = -1 * dx;
        if (Math.abs(diffx) < Math.abs(dx)) {
            dx = dx / 4;
        }
    }
    if ((currentTop < landingPosition.top && dy < 0) || (currentTop > landingPosition.top && dy > 0)) {
        dy = -1 * dy;
        if (Math.abs(diffy) < Math.abs(dy)) {
            dy = dy / 4;
        }
    }

    this.moveBug((currentLeft + dx), (currentTop + dy));

},

flyRand: function() {
    this.stop();
    var landingPosition = {};
    landingPosition.top = this.random(this.options.edge_resistance, document.documentElement.clientHeight - this.options.edge_resistance);
    landingPosition.left = this.random(this.options.edge_resistance, document.documentElement.clientWidth - this.options.edge_resistance);

    this.startFlying(landingPosition);
},

startFlying: function(landingPosition) {

    var currentTop = this.bug.top,
        currentLeft = this.bug.left,
        diffx = (landingPosition.left - currentLeft),
        diffy = (landingPosition.top - currentTop);

    this.bug.left = landingPosition.left;
    this.bug.top = landingPosition.top;

    this.angle_rad = Math.atan(diffy / diffx);
    this.angle_deg = this.rad2deg(this.angle_rad);

    if (diffx > 0) {
        // going left: quadrant 1 or 2
        this.angle_deg = 90 + this.angle_deg;
    } else {
        // going right: quadrant 3 or 4
        this.angle_deg = 270 + this.angle_deg;
    }

    this.moveBug(currentLeft, currentTop, this.angle_deg);

    // start animation:
    var that = this;
    this.flyperiodical = setInterval(function() {
        that.fly(landingPosition);
    }, 10);
},

flyIn: function() {
    if (!this.bug) {
        this.makeBug();
    }

    if(!this.bug) return;

    this.stop();
    // pick a random side:
    var side = Math.round(Math.random() * 4 - 0.5),
        d = document,
        e = d.documentElement,
        g = d.getElementsByTagName('body')[0],
        windowX = window.innerWidth || e.clientWidth || g.clientWidth,
        windowY = window.innerHeight || e.clientHeight || g.clientHeight;
    if (side > 3) side = 3;
    if (side < 0) side = 0;
    var style = {},
        s;
    if (side === 0) {
        // top:
        style.top = (-2 * this.options.bugHeight);
        style.left = Math.random() * windowX;
    } else if (side === 1) {
        // right:
        style.top = Math.random() * windowY;
        style.left = windowX + (2 * this.options.bugWidth);
    } else if (side === 2) {
        // bottom:
        style.top = windowY + (2 * this.options.bugHeight);
        style.left = Math.random() * windowX;
    } else {
        // left: 
        style.top = Math.random() * windowY;
        style.left = (-3 * this.options.bugWidth);
    }
    var row = (this.wingsOpen) ? '0' : '-' + this.options.bugHeight + 'px';
    this.bug.style.backgroundPosition = (-3 * this.options.bugWidth) + 'px ' + row;
    this.bug.top = style.top
    this.bug.left = style.left

    this.drawBug();

    // landing position:
    var landingPosition = {};
    landingPosition.top = this.random(this.options.edge_resistance, document.documentElement.clientHeight - this.options.edge_resistance);
    landingPosition.left = this.random(this.options.edge_resistance, document.documentElement.clientWidth - this.options.edge_resistance);

    this.startFlying(landingPosition);
},

walkIn: function() {
    if (!this.bug) {
        this.makeBug();
    }

    if(!this.bug) return;

    this.stop();
    // pick a random side:
    var side = Math.round(Math.random() * 4 - 0.5),
        d = document,
        e = d.documentElement,
        g = d.getElementsByTagName('body')[0],
        windowX = window.innerWidth || e.clientWidth || g.clientWidth,
        windowY = window.innerHeight || e.clientHeight || g.clientHeight;
    if (side > 3) side = 3;
    if (side < 0) side = 0;
    var style = {},
        s;
    if (side === 0) {
        // top:
        style.top = (-1.3 * this.options.bugHeight);
        style.left = Math.random() * windowX;
    } else if (side === 1) {
        // right:
        style.top = Math.random() * windowY;
        style.left = windowX + (0.3 * this.options.bugWidth);
    } else if (side === 2) {
        // bottom:
        style.top = windowY + (0.3 * this.options.bugHeight);
        style.left = Math.random() * windowX;
    } else {
        // left: 
        style.top = Math.random() * windowY;
        style.left = (-1.3 * this.options.bugWidth);
    }
    var row = (this.wingsOpen) ? '0' : '-' + this.options.bugHeight + 'px';
    this.bug.style.backgroundPosition = (-3 * this.options.bugWidth) + 'px ' + row;
    this.bug.top = style.top
    this.bug.left = style.left

    this.drawBug();

    // start walking:
    this.go();

},

flyOff: function() {
    this.stop();
    // pick a random side to fly off to, where 0 is top and continuing clockwise.
    var side = this.random(0, 3),
        style = {},
        d = document,
        e = d.documentElement,
        g = d.getElementsByTagName('body')[0],
        windowX = window.innerWidth || e.clientWidth || g.clientWidth,
        windowY = window.innerHeight || e.clientHeight || g.clientHeight;

    if (side === 0) {
        // top:
        style.top = -200;
        style.left = Math.random() * windowX;
    } else if (side === 1) {
        // right:
        style.top = Math.random() * windowY;
        style.left = windowX + 200;
    } else if (side === 2) {
        //bottom:
        style.top = windowY + 200;
        style.left = Math.random() * windowX;
    } else {
        // left: 
        style.top = Math.random() * windowY;
        style.left = -200;
    }
    this.startFlying(style);
},

die: function() {
    this.stop();
    //pick death style:
    var deathType = this.random(0, this.options.numDeathTypes - 1);

    this.alive = false;
    this.drop(deathType);
},

drop: function(deathType) {
    var startPos = this.bug.top,
        d = document,
        e = d.documentElement,
        g = d.getElementsByTagName('body')[0],
        finalPos = window.innerHeight || e.clientHeight || g.clientHeight,
        finalPos = finalPos - this.options.bugHeight,
        rotationRate = this.random(0, 20, true),
        startTime = Date.now(),
        that = this;

    this.bug.classList.add('bug-dead');

    this.dropTimer = requestAnimFrame(function(t) {
        that._lastTimestamp = t;
        that.dropping(t, startPos, finalPos, rotationRate, deathType);
    });

},

dropping: function(t, startPos, finalPos, rotationRate, deathType) {
    var elapsedTime = t - this._lastTimestamp,
        deltaPos = (0.002 * (elapsedTime * elapsedTime)),
        newPos = startPos + deltaPos;
    //console.log(t, elapsedTime, deltaPos, newPos);

    var that = this;

    if (newPos >= finalPos) {
        newPos = finalPos;
        clearTimeout(this.dropTimer);

        this.angle_deg = 0;
        this.angle_rad = this.deg2rad(this.angle_deg);
        this.transform("rotate(" + (90 - this.angle_deg) + "deg) scale(" + this.zoom + ")");
        this.bug.style.top = null;
        // because it is (or might be) zoomed and rotated, we cannot just just bottom = 0. Figure out real bottom position:
        var rotationOffset = ((this.options.bugWidth * this.zoom) - (this.options.bugHeight * this.zoom)) / 2;
        var zoomOffset = ((this.options.bugHeight) / 2) * (1 - this.zoom);
        this.bug.style.bottom = Math.ceil((rotationOffset - zoomOffset)) + 'px'; // because its rotated and zoomed.
        this.bug.style.left = this.bug.left + 'px';
        this.bug.style.backgroundPosition = '-' + ((deathType * 2) * this.options.bugWidth) + 'px 100%';

        this.twitch(deathType);

        return;
    }

    this.dropTimer = requestAnimFrame(function(t) {
        that.dropping(t, startPos, finalPos, rotationRate, deathType);
    });

    if (elapsedTime < 20) return;

    this.angle_deg = ((this.angle_deg + rotationRate) % 360);
    this.angle_rad = this.deg2rad(this.angle_deg);

    this.moveBug(this.bug.left, newPos, this.angle_deg);
},

twitch: function(deathType, legPos) {
    //this.bug.style.back
    if (!legPos) legPos = 0;
    var that = this;
    if (deathType === 0 || deathType === 1) {
        that.twitchTimer = setTimeout(function() {
            that.bug.style.backgroundPosition = '-' + ((deathType * 2 + (legPos % 2)) * that.options.bugWidth) + 'px 100%';
            that.twitchTimer = setTimeout(function() {
                legPos++;
                that.bug.style.backgroundPosition = '-' + ((deathType * 2 + (legPos % 2)) * that.options.bugWidth) + 'px 100%';
                that.twitch(deathType, ++legPos);
            }, that.random(300, 800));
        }, this.random(1000, 10000));
    }
},

/* helper methods: */
rad2deg: function(rad) {
    return rad * this.rad2deg_k;
},
deg2rad: function(deg) {
    return deg * this.deg2rad_k;
},
random: function(min, max, plusminus) {
    if (min == max) return min;
    var result = Math.round(min - 0.5 + (Math.random() * (max - min + 1)));
    if (plusminus) return Math.random() > 0.5 ? result : -result;
    return result;
},

next_small_turn: function() {
    this.small_turn_counter = Math.round(Math.random() * 10);
},
next_large_turn: function() {
    this.large_turn_counter = Math.round(Math.random() * 40);
},
next_stationary: function() {
    this.toggle_stationary_counter = this.random(50, 300);
},

bug_near_window_edge: function() {
    this.near_edge = 0;
    if (this.bug.top < this.options.edge_resistance)
        this.near_edge |= this.NEAR_TOP_EDGE;
    else if (this.bug.top > document.documentElement.clientHeight - this.options.edge_resistance)
        this.near_edge |= this.NEAR_BOTTOM_EDGE;
    if (this.bug.left < this.options.edge_resistance)
        this.near_edge |= this.NEAR_LEFT_EDGE;
    else if (this.bug.left > document.documentElement.clientWidth - this.options.edge_resistance)
        this.near_edge |= this.NEAR_RIGHT_EDGE;
    return this.near_edge;
},

getPos: function() {
    if (this.inserted && this.bug && this.bug.style) {
        return {
            'top': parseInt(this.bug.top, 10),
            'left': parseInt(this.bug.left, 10)
        };
    }
    return null;
}

};

var SpawnBug = function() { var newBug = {}, prop; for (prop in Bug) { if (Bug.hasOwnProperty(prop)) { newBug[prop] = Bug[prop]; } } return newBug; };

// debated about which pattern to use to instantiate each bug... // see http://jsperf.com/obj-vs-prototype-vs-other

/**

var mergeOptions = function(obj1, obj2, clone) { if (typeof(clone) == 'undefined') { clone = true; } var newobj = (clone) ? cloneOf(obj1) : obj1; for (var key in obj2) { if (obj2.hasOwnProperty(key)) { newobj[key] = obj2[key]; } } return newobj; };

var cloneOf = function(obj) { if (obj == null || typeof(obj) != 'object') return obj;

var temp = obj.constructor(); // changed

for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
        temp[key] = cloneOf(obj[key]);
    }
}
return temp;

}

/ Request animation frame polyfill / / http://paulirish.com/2011/requestanimationframe-for-smart-animating/ / window.requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( / function / callback, / DOMElement / element) { window.setTimeout(callback, 1000 / 60); }; })();

Auz commented 1 month ago

This doesn't seem to be an issue.