initialize: function(options) {
            this.mvt = options.mvt;
            this.simulation = options.simulation;
            this.electron = options.electron;
            this.titleText = options.title;
            this.minY = options.minY;
            this.maxY = options.maxY;

            this.panelColor = Colors.parseHex('#fff');
            this.gridColor  = Colors.parseHex('#ccc');
            this.lineColor  = Colors.parseHex('#2575BA');

            this.lineWidth  = 1;
            this.gridLineWidth = 1;

            // Cached objects
            this._dragOffset = new PIXI.Point();

            this.initGraphics();
        },
Ejemplo n.º 2
0
        getInternalReactorColor: function() {
            var reactorTemperature = this.simulation.get('temperature');
            if (reactorTemperature > NuclearReactorView.MAX_TEMPERATURE)
                reactorTemperature = NuclearReactorView.MAX_TEMPERATURE;
            
            // Blend the hot and cold colors together based on the current temp.
            var weighting = (NuclearReactorView.MAX_TEMPERATURE - reactorTemperature) / NuclearReactorView.MAX_TEMPERATURE;
            
            var hex = Colors.interpolateHex(
                NuclearReactorView.COOL_REACTOR_CHAMBER_COLOR, 
                NuclearReactorView.HOT_REACTOR_CHAMBER_COLOR, 
                weighting
            );

            return Colors.parseHex(hex);
        },
Ejemplo n.º 3
0
        drawDashedLine: function(){

            var lineTo = 0;
            var dashWidth;

            this.referenceLine.lineStyle(this.thickness, Colors.parseHex(this.color), this.opacity);

            while(lineTo < this.width){

                dashWidth = Math.min(this.dashWidth, this.width - lineTo);

                this.referenceLine.moveTo(lineTo, 0);
                this.referenceLine.lineTo(lineTo + dashWidth, 0);

                lineTo += 2 * this.dashWidth;
            }
        },
Ejemplo n.º 4
0
        initButton: function() {
            this.pressedButtonTexture   = Assets.Texture(Assets.Images.FIRE_BUTTON_PRESSED);
            this.unpressedButtonTexture = Assets.Texture(Assets.Images.FIRE_BUTTON_UNPRESSED);

            var targetWidth = NuclearReactorView.BUTTON_WIDTH;
            var scale = targetWidth / this.pressedButtonTexture.width;
            var paddingLeft = 14;
            var paddingTop = 10;

            this.button = new PIXI.Sprite(this.unpressedButtonTexture);
            this.button.buttonMode = true;
            this.button.defaultCursor = 'pointer';
            this.button.anchor.x = 0.5;
            this.button.anchor.y = 0.5;
            this.button.scale.x = this.button.scale.y = scale;
            this.button.x = paddingLeft + this.button.width / 2;
            this.button.y = paddingTop + this.button.height / 2;

            var label = new PIXI.Text('Fire Neutrons', {
                font: NuclearReactorView.BUTTON_LABEL_FONT,
                fill: NuclearReactorView.BUTTON_LABEL_COLOR
            });
            label.x = NuclearReactorView.BUTTON_WIDTH + paddingLeft * 2;
            label.y = NuclearReactorView.BUTTON_PANEL_HEIGHT / 2;
            label.anchor.y = 0.5;
            label.resolution = this.getResolution();

            this.buttonPanel = new PIXI.Graphics();
            this.buttonPanel.addChild(this.button);
            this.buttonPanel.addChild(label);

            this.buttonPanel.lineStyle(NuclearReactorView.BUTTON_PANEL_BORDER_WIDTH, REACTOR_WALL_COLOR, 1);
            this.buttonPanel.beginFill(Colors.parseHex(NuclearReactorView.COOL_REACTOR_CHAMBER_COLOR), 1);
            this.buttonPanel.drawRect(0, 0, NuclearReactorView.BUTTON_PANEL_WIDTH, NuclearReactorView.BUTTON_PANEL_HEIGHT);
            this.buttonPanel.endFill();

            this.displayObject.addChild(this.buttonPanel);
        },
Ejemplo n.º 5
0
define(function(require) {

    'use strict';

    var PIXI = require('pixi');

                   require('common/v3/pixi/draw-arrow');
    var PixiView = require('common/v3/pixi/view');
    var Colors   = require('common/colors/colors');
    
    var Constants = require('constants');

    var CONTAINMENT_VESSEL_COLOR       = Colors.parseHex(Constants.ContainmentVesselView.CONTAINMENT_VESSEL_COLOR);
    var CONTAINMENT_VESSEL_HOVER_COLOR = Colors.parseHex(Constants.ContainmentVesselView.CONTAINMENT_VESSEL_HOVER_COLOR);
    var ARROW_COLOR                    = Colors.parseHex(Constants.ContainmentVesselView.ARROW_COLOR);

    var renderDebugGraphics = false;

    /**
     * A view that represents the containment vessel
     */
    var ContainmentVesselView = PixiView.extend({

        events: {
            'touchstart      .containmentVesselGraphics': 'dragStart',
            'mousedown       .containmentVesselGraphics': 'dragStart',
            'touchmove       .containmentVesselGraphics': 'drag',
            'mousemove       .containmentVesselGraphics': 'drag',
            'touchend        .containmentVesselGraphics': 'dragEnd',
            'mouseup         .containmentVesselGraphics': 'dragEnd',
            'touchendoutside .containmentVesselGraphics': 'dragEnd',
            'mouseupoutside  .containmentVesselGraphics': 'dragEnd',

            'mouseover       .containmentVesselGraphics': 'hover',
            'mouseout        .containmentVesselGraphics': 'unhover'
        },

        /**
         * Initializes the new ContainmentVesselView.
         */
        initialize: function(options) {
            this.mvt = options.mvt;

            this.initGraphics();

            this.listenTo(this.model, 'change:radius',   this.draw);
            this.listenTo(this.model, 'change:enabled',  this.updateVisibility);
            this.listenTo(this.model, 'change:exploded', this.explodedChanged);

            this.updateVisibility();
        },

        /**
         * Initializes everything for rendering graphics
         */
        initGraphics: function() {
            this.containmentVesselGraphicsMask = new PIXI.Graphics();

            this.containmentVesselGraphics = new PIXI.Graphics();
            this.containmentVesselGraphics.mask = this.containmentVesselGraphicsMask;
            this.containmentVesselGraphics.buttonMode = true;

            this.containmentVesselHoverGraphics = new PIXI.Graphics();
            this.containmentVesselHoverGraphics.mask = this.containmentVesselGraphicsMask;
            this.containmentVesselHoverGraphics.visible = false;

            this.arrowContainer1 = this.createArrow();
            this.arrowContainer2 = this.createArrow();
            this.arrowContainer3 = this.createArrow();
            this.arrowContainer4 = this.createArrow();
            this.arrowContainer1.rotation = -ContainmentVesselView.ARROW_ANGLE;
            this.arrowContainer2.rotation =  ContainmentVesselView.ARROW_ANGLE;
            this.arrowContainer3.rotation = -ContainmentVesselView.ARROW_ANGLE - Math.PI / 2;
            this.arrowContainer4.rotation =  ContainmentVesselView.ARROW_ANGLE + Math.PI / 2;

            this.defaultLayer = new PIXI.Container();
            this.defaultLayer.addChild(this.containmentVesselGraphics);
            this.defaultLayer.addChild(this.containmentVesselGraphicsMask);
            this.defaultLayer.addChild(this.containmentVesselHoverGraphics);
            this.defaultLayer.addChild(this.arrowContainer1);
            this.defaultLayer.addChild(this.arrowContainer2);
            this.defaultLayer.addChild(this.arrowContainer3);
            this.defaultLayer.addChild(this.arrowContainer4);

            this.explosionLayer = new PIXI.Container();
            this.explosionLayer.visible = false;

            this.debugGraphics = new PIXI.Graphics();

            this.displayObject.addChild(this.defaultLayer);
            this.displayObject.addChild(this.explosionLayer);
            this.displayObject.addChild(this.debugGraphics);

            this.updateMVT(this.mvt);
        },

        createArrow: function() {
            var graphics = new PIXI.Graphics();
            var hoverGraphics = new PIXI.Graphics();
            hoverGraphics.visible = false;

            this.drawArrow(graphics, ARROW_COLOR);
            this.drawArrow(hoverGraphics, CONTAINMENT_VESSEL_HOVER_COLOR);

            var arrowGraphicsContainer = new PIXI.Container();
            arrowGraphicsContainer.addChild(graphics);
            arrowGraphicsContainer.addChild(hoverGraphics);

            var container = new PIXI.Container();
            container.addChild(arrowGraphicsContainer);

            container.showHoverGraphics = function() {
                graphics.visible = false;
                hoverGraphics.visible = true;
            };

            container.hideHoverGraphics = function() {
                graphics.visible = true;
                hoverGraphics.visible = false;
            };

            container.setRadius = function(radius) {
                arrowGraphicsContainer.x = radius;
            };

            return container;
        },

        drawArrow: function(graphics, color) {
            var length     = ContainmentVesselView.ARROW_LENGTH;
            var headWidth  = ContainmentVesselView.ARROW_HEAD_WIDTH;
            var headLength = ContainmentVesselView.ARROW_HEAD_LENGTH;
            var tailWidth  = ContainmentVesselView.ARROW_TAIL_WIDTH;
            var tailLength = length - headLength;

            graphics.beginFill(color, 1);
            
            // Draw the arrow tail in a special way
            var margin = 2; // Margin
            var sectionHeight = Math.floor(tailLength * 0.3);
            var minSectionHeight = 2;
            var falloff = 3;

            for (var x = tailLength - sectionHeight; x >= 0; x -= sectionHeight + margin) {
                graphics.drawRect(x, -tailWidth / 2, sectionHeight, tailWidth);
                sectionHeight = Math.max(sectionHeight - falloff, minSectionHeight);
            }

            // Draw the head the normal way
            graphics.drawArrow(tailLength, 0, tailLength + headLength, 0, tailWidth, headWidth, headLength);

            graphics.endFill();
        },

        draw: function() {
            var radius = this.getRadius();
            var thickness = this.getThickness();

            this.drawVessel(this.containmentVesselGraphics, CONTAINMENT_VESSEL_COLOR);
            this.drawVessel(this.containmentVesselHoverGraphics, CONTAINMENT_VESSEL_HOVER_COLOR);

            this.containmentVesselGraphics.hitArea = this.getRingHitArea(radius, thickness);

            this.drawMask(this.containmentVesselGraphicsMask);

            var x = radius + thickness + 6;
            this.arrowContainer1.setRadius(x);
            this.arrowContainer2.setRadius(x);
            this.arrowContainer3.setRadius(x);
            this.arrowContainer4.setRadius(x);
        },

        drawVessel: function(graphics, color) {
            var radius = this.getRadius();
            var thickness = this.getThickness();
            var halfThickness = thickness / 2;

            graphics.clear();
            graphics.lineStyle(thickness, color, 1);
            graphics.drawCircle(0, 0, radius + halfThickness);
        },

        drawMask: function(graphics) {
            var radius = this.getRadius();
            var thickness = this.getThickness();
            var apertureHeight = this.getApertureHeight();
            var halfApertureHeight = apertureHeight / 2;

            graphics.clear();
            graphics.beginFill();
            graphics.drawRect(-radius - thickness, -radius - thickness, (radius + thickness) * 2, radius + thickness - halfApertureHeight);
            graphics.drawRect(-radius - thickness, halfApertureHeight,  (radius + thickness) * 2, radius + thickness - halfApertureHeight);
            graphics.drawRect(0, -halfApertureHeight, radius + thickness, apertureHeight);
            graphics.endFill();
        },

        getRadius: function() {
            return this.mvt.modelToViewDeltaX(this.model.get('radius'));
        },

        getThickness: function() {
            return ContainmentVesselView.CONTAINMENT_VESSEL_THICKNESS;
        },

        getApertureHeight: function() {
            return this.mvt.modelToViewDeltaX(this.model.getApertureHeight());
        },

        getRingHitArea: function(radius, thickness) {
            var innerRadius = radius;
            var outerRadius = radius + thickness;
            
            // We need to find the angle between those points on the circle's
            //   circumference that correspond to the top and bottom of the
            //   container's aperture, relative to the center of the container.
            //   Once we've found that angle, we can use it to determine how
            //   many segments we should leave out.
            var apertureHeight = this.mvt.modelToViewDeltaY(this.model.getApertureHeight());
            var halfApertureHeight = apertureHeight / 2;
            var theta = Math.asin(halfApertureHeight / innerRadius) * 2;
            var defaultNumSegments = ContainmentVesselView.CONTAINMENT_VESSEL_RING_SEGMENTS;
            var radiansPerSegment = (Math.PI * 2) / defaultNumSegments;
            var numSegmentsToLeaveOut = Math.ceil((theta / radiansPerSegment) / 2) * 2;
            var numSegments = defaultNumSegments - numSegmentsToLeaveOut;
            var rotation = Math.PI + (numSegmentsToLeaveOut * radiansPerSegment) / 2;

            var startingX = -Math.sqrt(outerRadius * outerRadius - halfApertureHeight * halfApertureHeight);
            var startingY = -halfApertureHeight;
            var endingX = -Math.sqrt(innerRadius * innerRadius - halfApertureHeight * halfApertureHeight);
            var endingY = -halfApertureHeight;
            
            var i;
            var points = [];

            var graphics;
            if (renderDebugGraphics) {
                graphics = this.debugGraphics;
                graphics.clear();
                graphics.lineStyle(1, 0x0000FF, 1);
                graphics.moveTo(endingX, endingY);
                graphics.lineTo(startingX, startingY);
            }

            // Add the more exact points of intersection with the aperture
            points.push(startingX);
            points.push(startingY);

            // Create the outer ring of points
            for (i = 0; i <= numSegments; i++) {
                points.push(Math.cos(radiansPerSegment * i + rotation) * outerRadius);
                points.push(Math.sin(radiansPerSegment * i + rotation) * outerRadius);
                if (renderDebugGraphics) {
                    graphics.lineTo(
                        Math.cos(radiansPerSegment * i + rotation) * outerRadius,
                        Math.sin(radiansPerSegment * i + rotation) * outerRadius
                    );
                }
            }

            // Add the more exact points of intersection with the aperture
            points.push(startingX);
            points.push(-startingY);
            points.push(endingX);
            points.push(-endingY);

            if (renderDebugGraphics) {
                graphics.lineTo(startingX, -startingY);
                graphics.lineTo(endingX, -endingY);
            }

            // Create the inner ring of points, turning around and going the other way
            for (i = numSegments; i >= 0; i--) {
                points.push(Math.cos(radiansPerSegment * i + rotation) * innerRadius);
                points.push(Math.sin(radiansPerSegment * i + rotation) * innerRadius);
                if (renderDebugGraphics) {
                    graphics.lineTo(
                        Math.cos(radiansPerSegment * i + rotation) * innerRadius,
                        Math.sin(radiansPerSegment * i + rotation) * innerRadius
                    );
                }
            }

            // Then back to the beginning
            points.push(Math.cos(0 + rotation) * outerRadius);
            points.push(Math.sin(0 + rotation) * outerRadius);
            points.push(endingX);
            points.push(endingY);
            if (renderDebugGraphics) {
                graphics.lineTo(endingX, endingY);
            }

            return new PIXI.Polygon(points);
        },

        initExplosion: function() {
            // Clear out what's already there if it had previously exploded
            this.explosionLayer.removeChildren();

            var radius = this.getRadius();
            var sliceRadius = radius * 2;
            var minTheta = Math.PI / 6;
            var maxTheta = Math.PI / 3;
            var rotationalOffset = Math.PI;
            var cumulativeRadians = 0;

            while (cumulativeRadians < Math.PI * 2) {
                var theta = (cumulativeRadians > Math.PI * 1.7) ?
                    (Math.PI * 2) - cumulativeRadians + 0.05 :
                    Math.random() * (maxTheta - minTheta) + minTheta;

                var apertureMask = new PIXI.Graphics();
                this.drawMask(apertureMask);

                var graphics = new PIXI.Graphics();
                this.drawVessel(graphics, CONTAINMENT_VESSEL_COLOR);
                graphics.mask = apertureMask;
                
                var sliceMask = new PIXI.Graphics();
                sliceMask.beginFill();
                sliceMask.moveTo(0, 0);
                sliceMask.lineTo(
                    Math.cos(cumulativeRadians + rotationalOffset) * sliceRadius, 
                    Math.sin(cumulativeRadians + rotationalOffset) * sliceRadius
                );
                sliceMask.lineTo(
                    Math.cos(cumulativeRadians + theta + rotationalOffset) * sliceRadius, 
                    Math.sin(cumulativeRadians + theta + rotationalOffset) * sliceRadius
                );

                var offsetContainer = new PIXI.Container();
                offsetContainer.addChild(graphics);
                offsetContainer.addChild(apertureMask);
                offsetContainer.addChild(sliceMask);
                offsetContainer.mask = sliceMask;
                offsetContainer.x = -Math.cos(cumulativeRadians + theta / 2 + rotationalOffset) * radius;
                offsetContainer.y = -Math.sin(cumulativeRadians + theta / 2 + rotationalOffset) * radius;

                var container = new PIXI.Container();
                container.addChild(offsetContainer);
                container.x = -offsetContainer.x;
                container.y = -offsetContainer.y;
                container.vx = ContainmentVesselView.FRAGMENT_VELOCITY_RANGE.random();
                container.vy = ContainmentVesselView.FRAGMENT_VELOCITY_RANGE.random();
                container.spinRate = Math.random() * ContainmentVesselView.MAX_FRAGMENT_SPIN_RATE;

                if (container.x < 0)
                    container.vx *= -1;

                if (container.y < 0)
                    container.vy *= -1;

                if (Math.random() < 0.5)
                    container.spinRate *= -1;

                this.explosionLayer.addChild(container);

                cumulativeRadians += theta;
            }
        },

        dragStart: function(event) {
            this.dragging = true;

            this.showHoverGraphics();
        },

        drag: function(event) {
            if (this.dragging) {
                var dx = event.data.global.x - this.displayObject.x;
                var dy = event.data.global.y - this.displayObject.y;
                var distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
                var modelRadius = this.mvt.viewToModelDeltaX(distanceFromCenter);
                this.model.set('radius', modelRadius);
            }
        },

        dragEnd: function(event) {
            this.dragging = false;

            if (!this.hovering)
                this.hideHoverGraphics();
        },

        hover: function() {
            this.hovering = true;
            this.showHoverGraphics();
        },

        unhover: function() {
            this.hovering = false;
            if (!this.dragging)
                this.hideHoverGraphics();
        },

        showHoverGraphics: function() {
            this.containmentVesselHoverGraphics.visible = true;
            this.arrowContainer1.showHoverGraphics();
            this.arrowContainer2.showHoverGraphics();
            this.arrowContainer3.showHoverGraphics();
            this.arrowContainer4.showHoverGraphics();
        },

        hideHoverGraphics: function() {
            this.containmentVesselHoverGraphics.visible = false;
            this.arrowContainer1.hideHoverGraphics();
            this.arrowContainer2.hideHoverGraphics();
            this.arrowContainer3.hideHoverGraphics();
            this.arrowContainer4.hideHoverGraphics();
        },

        /**
         * Updates the model-view-transform and anything that relies on it.
         */
        updateMVT: function(mvt) {
            this.mvt = mvt;

            // Update position
            this.displayObject.x = this.mvt.modelToViewX(0);
            this.displayObject.y = this.mvt.modelToViewY(0);

            this.draw();
        },

        update: function(time, deltaTime, paused) {
            if (this.cooldownTimer > 0) {
                this.cooldownTimer -= deltaTime;

                // Check to see if it has cooled down
                if (this.cooldownTimer <= 0) {
                    this.cooldownTimer = 0;
                    this.showUnpressedButtonTexture();
                }
            }

            if (!paused && this.explosionLayer.children.length)
                this.updateExplosion(time, deltaTime);
        },

        updateExplosion: function(time, deltaTime) {
            var pieces = this.explosionLayer.children;

            for (var i = 0; i < pieces.length; i++) {
                pieces[i].x += pieces[i].vx * deltaTime;
                pieces[i].y += pieces[i].vy * deltaTime;
                pieces[i].rotation += pieces[i].spinRate * deltaTime;
            }
        },

        updateVisibility: function() {
            if (this.model.get('enabled'))
                this.displayObject.visible = true;
            else
                this.displayObject.visible = false;
        },

        explodedChanged: function(containmentVessel, exploded) {
            if (exploded) {
                this.defaultLayer.visible = false;
                this.initExplosion();
                this.explosionLayer.visible = true;    
            }
            else {
                this.defaultLayer.visible = true;
                this.explosionLayer.visible = false;  
            }
        }

    }, Constants.ContainmentVesselView);


    return ContainmentVesselView;
});
Ejemplo n.º 6
0
define(function(require) {

    'use strict';

    var _    = require('underscore');
    var PIXI = require('pixi');

    var PixiView        = require('common/v3/pixi/view');
    var ThermometerView = require('common/v3/pixi/view/thermometer');
    var Colors          = require('common/colors/colors');
    var Rectangle       = require('common/math/rectangle');

    var Nucleon = require('models/nucleon');

    var NucleonView          = require('views/nucleon');
    var ExplodingNucleusView = require('views/nucleus/exploding');
    
    var Assets = require('assets');
    var Constants = require('constants');

    var REACTOR_WALL_COLOR = Colors.parseHex(Constants.NuclearReactorView.REACTOR_WALL_COLOR);
    var CHAMBER_WALL_COLOR = Colors.parseHex(Constants.NuclearReactorView.CHAMBER_WALL_COLOR);
    var CONTROL_ROD_COLOR  = Colors.parseHex(Constants.NuclearReactorView.CONTROL_ROD_COLOR);
    var CONTROL_ROD_ADJUSTOR_COLOR = Colors.parseHex(Constants.NuclearReactorView.CONTROL_ROD_ADJUSTOR_COLOR);
    var CONTROL_ROD_ADJUSTOR_HANDLE_COLOR = Colors.parseHex(Constants.NuclearReactorView.CONTROL_ROD_ADJUSTOR_HANDLE_COLOR);

    /**
     * A view that represents a nuclear reactor
     */
    var NuclearReactorView = PixiView.extend({

        events: {
            'touchstart .button': 'click',
            'mousedown  .button': 'click',

            'touchstart      .controlRodAdjustor': 'dragStart',
            'mousedown       .controlRodAdjustor': 'dragStart',
            'touchmove       .controlRodAdjustor': 'drag',
            'mousemove       .controlRodAdjustor': 'drag',
            'touchend        .controlRodAdjustor': 'dragEnd',
            'mouseup         .controlRodAdjustor': 'dragEnd',
            'touchendoutside .controlRodAdjustor': 'dragEnd',
            'mouseupoutside  .controlRodAdjustor': 'dragEnd'
        },

        /**
         * Initializes the new NuclearReactorView.
         */
        initialize: function(options) {
            options = _.extend({
                cooldownTime: 0.4
            }, options);

            this.mvt = options.mvt;
            this.simulation = options.simulation;
            this.cooldownTime = options.cooldownTime;
            this.cooldownTimer = 0;

            // Cached objects
            this._dragOffset = new PIXI.Point();
            this._rect = new Rectangle();

            this.initGraphics();

            this.listenTo(this.simulation.freeNeutrons, 'add',     this.neutronAdded);
            this.listenTo(this.simulation.freeNeutrons, 'destroy', this.neutronDestroyed);

            this.listenTo(this.simulation, 'nucleus-added',        this.nucleusAdded);
            this.listenTo(this.simulation, 'nucleus-removed',      this.nucleusRemoved);
            this.listenTo(this.simulation, 'remove-all-particles', this.allParticlesRemoved);

            this.listenTo(this.simulation, 'change:temperature', this.temperatureChanged);

            this.listenTo(this.simulation.controlRods[0], 'change:position', this.updateControlRodsPosition);
        },

        /**
         * Initializes everything for rendering graphics
         */
        initGraphics: function() {
            this.initBackground();
            this.initOutline();
            this.initParticles();
            this.initButton();
            this.initStartingNuclei();
            this.initControlRods();
            this.initThermometer();

            this.updateMVT(this.mvt);
        },

        initBackground: function() {
            this.backgroundGraphics = new PIXI.Graphics();

            this.displayObject.addChild(this.backgroundGraphics);
        },

        initOutline: function() {
            this.outlineGraphics = new PIXI.Graphics();

            this.displayObject.addChild(this.outlineGraphics);
        },

        initParticles: function() {
            this.neutronViews = [];
            this.nucleusViews = [];

            this.neutronLayer = new PIXI.Container();
            this.nucleusLayer = new PIXI.Container();

            this.displayObject.addChild(this.neutronLayer);
            this.displayObject.addChild(this.nucleusLayer);
        },

        initButton: function() {
            this.pressedButtonTexture   = Assets.Texture(Assets.Images.FIRE_BUTTON_PRESSED);
            this.unpressedButtonTexture = Assets.Texture(Assets.Images.FIRE_BUTTON_UNPRESSED);

            var targetWidth = NuclearReactorView.BUTTON_WIDTH;
            var scale = targetWidth / this.pressedButtonTexture.width;
            var paddingLeft = 14;
            var paddingTop = 10;

            this.button = new PIXI.Sprite(this.unpressedButtonTexture);
            this.button.buttonMode = true;
            this.button.defaultCursor = 'pointer';
            this.button.anchor.x = 0.5;
            this.button.anchor.y = 0.5;
            this.button.scale.x = this.button.scale.y = scale;
            this.button.x = paddingLeft + this.button.width / 2;
            this.button.y = paddingTop + this.button.height / 2;

            var label = new PIXI.Text('Fire Neutrons', {
                font: NuclearReactorView.BUTTON_LABEL_FONT,
                fill: NuclearReactorView.BUTTON_LABEL_COLOR
            });
            label.x = NuclearReactorView.BUTTON_WIDTH + paddingLeft * 2;
            label.y = NuclearReactorView.BUTTON_PANEL_HEIGHT / 2;
            label.anchor.y = 0.5;
            label.resolution = this.getResolution();

            this.buttonPanel = new PIXI.Graphics();
            this.buttonPanel.addChild(this.button);
            this.buttonPanel.addChild(label);

            this.buttonPanel.lineStyle(NuclearReactorView.BUTTON_PANEL_BORDER_WIDTH, REACTOR_WALL_COLOR, 1);
            this.buttonPanel.beginFill(Colors.parseHex(NuclearReactorView.COOL_REACTOR_CHAMBER_COLOR), 1);
            this.buttonPanel.drawRect(0, 0, NuclearReactorView.BUTTON_PANEL_WIDTH, NuclearReactorView.BUTTON_PANEL_HEIGHT);
            this.buttonPanel.endFill();

            this.displayObject.addChild(this.buttonPanel);
        },

        initStartingNuclei: function() {
            for (var i = 0; i < this.simulation.u235Nuclei.length; i++)
                this.nucleusAdded(this.simulation.u235Nuclei.at(i));
        },

        initControlRods: function() {
            this.controlRodAdjustor = new PIXI.Graphics();
            this.controlRodAdjustor.buttonMode = true;
            this.controlRodAdjustor.defaultCursor = 'ns-resize';

            this.controlRods = new PIXI.Graphics();
            this.controlRods.addChild(this.controlRodAdjustor);

            this.displayObject.addChild(this.controlRods);
        },

        initThermometer: function() {
            this.thermometerView = new ThermometerView({
                bulbDiameter: 28,
                tubeWidth:    14,
                tubeHeight:   72,
                fillAlpha: 0.7
            });
            this.displayObject.addChild(this.thermometerView.displayObject);
        },

        createParticleView: function(particle) {
            if (particle instanceof Nucleon) {
                // Add a visible representation of the nucleon to the canvas.
                return new NucleonView({
                    model: particle,
                    mvt: this.mvt
                });
            }
            else {
                // There is some unexpected object in the list of constituents
                //   of the nucleus.  This should never happen and should be
                //   debugged if it does.
                throw 'unexpected particle';
            }
        },

        drawBackground: function() {
            var wallWidth = this.mvt.modelToViewDeltaX(this.simulation.getReactorWallWidth());
            var outerRect = this.mvt.modelToView(this.simulation.getReactorRect());
            var graphics = this.backgroundGraphics;

            graphics.clear();
            graphics.beginFill(this.getInternalReactorColor(), 1);
            graphics.drawRect(
                outerRect.left() + wallWidth / 2, outerRect.bottom() + wallWidth / 2,
                outerRect.w - wallWidth, outerRect.h - wallWidth
            );
            graphics.endFill();
        },

        drawOutline: function() {
            var wallWidth = this.mvt.modelToViewDeltaX(this.simulation.getReactorWallWidth());
            var outerRect = this._rect.set(this.mvt.modelToView(this.simulation.getReactorRect()));
            var graphics = this.outlineGraphics;

            graphics.clear();
            graphics.lineStyle(wallWidth, REACTOR_WALL_COLOR, 1);
            graphics.drawRect(
                outerRect.left() + wallWidth / 2, outerRect.bottom() + wallWidth / 2,
                outerRect.w - wallWidth, outerRect.h - wallWidth
            );

            graphics.lineStyle(1, CHAMBER_WALL_COLOR, 1);
            var y1 = outerRect.bottom() + wallWidth;
            var y2 = outerRect.top() - wallWidth;
            var controlRods = this.simulation.controlRods;
            for (var i = 0; i < controlRods.length; i++) {
                var rect = this.mvt.modelToView(controlRods[i].getRectangle());

                graphics.moveTo(Math.round(rect.left()), y1);
                graphics.lineTo(Math.round(rect.left()), y2);

                graphics.moveTo(Math.round(rect.right()), y1);
                graphics.lineTo(Math.round(rect.right()), y2);
            }
        },

        drawControlRods: function() {
            var wallWidth = this.mvt.modelToViewDeltaX(this.simulation.getReactorWallWidth());
            var outerRect = this._rect.set(this.mvt.modelToView(this.simulation.getReactorRect()));
            var graphics = this.controlRods;
            var minX = Number.POSITIVE_INFINITY;
            var rodWidth;

            graphics.clear();
            graphics.beginFill(CONTROL_ROD_COLOR, 1);

            var y1 = outerRect.bottom() + wallWidth;
            var y2 = outerRect.top() + wallWidth;
            var controlRods = this.simulation.controlRods;
            for (var i = 0; i < controlRods.length; i++) {
                var rect = this.mvt.modelToView(controlRods[i].getRectangle());
                var left = Math.round(rect.left()) - 1;
                var right = Math.round(rect.right()) + 1;
                rodWidth = right - left;

                graphics.drawRect(left, y1, rodWidth, (y2 - y1));

                if (left < minX)
                    minX = left;
            }

            graphics.endFill();

            // Draw the adjustor
            var adjustorRight = outerRect.right() + 10 + rodWidth;
            var adjustor = this.controlRodAdjustor;
            adjustor.clear();
            adjustor.beginFill(CONTROL_ROD_ADJUSTOR_COLOR, 1);
            adjustor.drawRect(minX, y2, (adjustorRight - minX), rodWidth);
            adjustor.drawRect(adjustorRight - rodWidth, y1, rodWidth, (y2 - y1));
            adjustor.endFill();

            // Draw the handle
            var handleHeight = 130;
            var handleWidth = 20;
            var handleThickness = 10;
            var handleRadius = handleThickness / 2;
            adjustor.beginFill(CONTROL_ROD_ADJUSTOR_HANDLE_COLOR, 1);
            adjustor.drawRect(adjustorRight, y1, handleWidth - handleRadius, handleThickness);
            adjustor.drawRect(adjustorRight, y1 + handleHeight - handleThickness, handleWidth - handleRadius, handleThickness);
            adjustor.drawRoundedRect(adjustorRight + handleWidth - handleThickness, y1, handleThickness, handleHeight, handleRadius);
            adjustor.endFill();

            // Create the label
            adjustor.removeChildren();
            var label = new PIXI.Text('Control Rod Adjustor', {
                font: NuclearReactorView.CONTROL_ROD_ADJUSTOR_LABEL_FONT,
                fill: NuclearReactorView.CONTROL_ROD_ADJUSTOR_LABEL_COLOR
            });
            label.resolution = this.getResolution();
            label.anchor.y = 0.5;
            label.rotation = -Math.PI / 2;
            label.x = adjustorRight - rodWidth / 2;
            label.y = y2;

            adjustor.addChild(label);
        },

        getInternalReactorColor: function() {
            var reactorTemperature = this.simulation.get('temperature');
            if (reactorTemperature > NuclearReactorView.MAX_TEMPERATURE)
                reactorTemperature = NuclearReactorView.MAX_TEMPERATURE;
            
            // Blend the hot and cold colors together based on the current temp.
            var weighting = (NuclearReactorView.MAX_TEMPERATURE - reactorTemperature) / NuclearReactorView.MAX_TEMPERATURE;
            
            var hex = Colors.interpolateHex(
                NuclearReactorView.COOL_REACTOR_CHAMBER_COLOR, 
                NuclearReactorView.HOT_REACTOR_CHAMBER_COLOR, 
                weighting
            );

            return Colors.parseHex(hex);
        },

        /**
         * Updates the model-view-transform and anything that
         *   relies on it.
         */
        updateMVT: function(mvt) {
            this.mvt = mvt;

            var outerRect = this.mvt.modelToView(this.simulation.getReactorRect());
            this.buttonPanel.x = outerRect.left() + outerRect.w / 2 - NuclearReactorView.BUTTON_PANEL_WIDTH / 2;
            this.buttonPanel.y = outerRect.bottom() - NuclearReactorView.BUTTON_PANEL_HEIGHT + NuclearReactorView.BUTTON_PANEL_BORDER_WIDTH / 2;

            this.thermometerView.displayObject.x = outerRect.right() - (outerRect.w * 0.258);
            this.thermometerView.displayObject.y = outerRect.bottom() + 48;

            this.drawBackground();
            this.drawOutline();
            this.drawControlRods();
        },

        update: function(time, deltaTime, paused) {
            if (this.cooldownTimer > 0) {
                this.cooldownTimer -= deltaTime;

                // Check to see if it has cooled down
                if (this.cooldownTimer <= 0) {
                    this.cooldownTimer = 0;
                    this.showUnpressedButtonTexture();
                }
            }

            var i;

            for (i = 0; i < this.neutronViews.length; i++)
                this.neutronViews[i].update(time, deltaTime, paused);

            for (i = 0; i < this.nucleusViews.length; i++)
                this.nucleusViews[i].update(time, deltaTime, paused);
        },

        updateControlRodsPosition: function() {
            // Because we could potentially redraw the control rods,
            //   we can't just make it all relative.  We need to absolutely base it off of the model positions I think
            var minY = this.simulation.getControlRodsMinY();
            var difference = this.simulation.controlRods[0].getY() - minY;
            var viewY = this.mvt.modelToViewDeltaY(difference);
            this.controlRods.y = viewY;
        },

        dragStart: function(event) {
            this.dragging = true;

            this.lastY = event.data.global.y;
        },

        drag: function(event) {
            if (this.dragging) {
                var dy = event.data.global.y - this.lastY;
                this.simulation.moveControlRods(this.mvt.viewToModelDeltaY(dy));
                this.lastY = event.data.global.y;
            }
        },

        dragEnd: function(event) {
            this.dragging = false;
        },

        click: function() {
            // Only fire it if it has cooled down
            if (this.cooldownTimer === 0) {
                this.cooldownTimer = this.cooldownTime;
                this.simulation.fireNeutrons();
                this.showPressedButtonTexture();
            }
        },

        showPressedButtonTexture: function() {
            this.button.texture = this.pressedButtonTexture;
        },

        showUnpressedButtonTexture: function() {
            this.button.texture = this.unpressedButtonTexture;
        },

        neutronAdded: function(neutron) {
            var nucleonView = this.createParticleView(neutron);
            this.neutronViews.push(nucleonView);
            this.neutronLayer.addChild(nucleonView.displayObject);
        },

        neutronDestroyed: function(nucleon) {
            for (var i = 0; i < this.neutronViews.length; i++) {
                if (this.neutronViews[i].model === nucleon) {
                    this.neutronViews[i].remove();
                    this.neutronViews.splice(i, 1);
                    return;
                }
            }
        },

        nucleusAdded: function(nucleus) {
            var nucleusView = new ExplodingNucleusView({
                model: nucleus,
                mvt: this.mvt,
                renderer: this.renderer
            });

            this.nucleusViews.push(nucleusView);
            this.nucleusLayer.addChild(nucleusView.displayObject);
        },

        nucleusRemoved: function(nucleus) {
            for (var i = 0; i < this.nucleusViews.length; i++) {
                if (this.nucleusViews[i].model === nucleus) {
                    this.nucleusViews[i].remove();
                    this.nucleusViews.splice(i, 1);
                    return;
                }
            }
        },

        nucleusChanged: function() {
            if (this.simulation.getChangedNucleiExist())
                this.showResetButtonWithDelay();
        },

        numReactiveNucleiChanged: function() {
            if (!this.simulation.getChangedNucleiExist())
                this.hideResetButton();
        },

        allParticlesRemoved: function() {
            var i;

            for (i = this.nucleusViews.length - 1; i >= 0; i--) {
                this.nucleusViews[i].remove();
                this.nucleusViews.splice(i, 1);
            }

            for (i = 0; i < this.neutronViews.length; i++) {
                this.neutronViews[i].remove();
                this.neutronViews.splice(i, 1);
            }

            this.drawBackground();
        },

        temperatureChanged: function() {
            this.drawBackground();
            this.thermometerView.val(this.simulation.get('temperature') / NuclearReactorView.MAX_TEMPERATURE);
        }

    }, Constants.NuclearReactorView);


    return NuclearReactorView;
});
Ejemplo n.º 7
0
define(function(require) {

    'use strict';

    var buzz = require('buzz');
    var PIXI = require('pixi');
    require('common/v3/pixi/extensions');
    
    var PixiView = require('common/v3/pixi/view');
    var Colors   = require('common/colors/colors');
    var range    = require('common/math/range');
    var Vector2  = require('common/math/vector2');

    var Assets = require('assets');

    var Constants = require('constants');
    var RADIANS_TO_DEGREES = 180 / Math.PI;
    var PEDESTAL_TOP_COLOR  = Colors.parseHex(Constants.CannonView.PEDESTAL_TOP_COLOR);
    var PEDESTAL_SIDE_COLOR = Colors.parseHex(Constants.CannonView.PEDESTAL_SIDE_COLOR);

    /**
     * A view that represents a cannon model
     */
    var CannonView = PixiView.extend({

        events: {
            'touchstart      .cannon': 'dragCannonStart',
            'mousedown       .cannon': 'dragCannonStart',
            'touchmove       .cannon': 'dragCannon',
            'mousemove       .cannon': 'dragCannon',
            'touchend        .cannon': 'dragCannonEnd',
            'mouseup         .cannon': 'dragCannonEnd',
            'touchendoutside .cannon': 'dragCannonEnd',
            'mouseupoutside  .cannon': 'dragCannonEnd',

            'touchstart      .pedestal': 'dragPedestalStart',
            'mousedown       .pedestal': 'dragPedestalStart',
            'touchmove       .pedestal': 'dragPedestal',
            'mousemove       .pedestal': 'dragPedestal',
            'touchend        .pedestal': 'dragPedestalEnd',
            'mouseup         .pedestal': 'dragPedestalEnd',
            'touchendoutside .pedestal': 'dragPedestalEnd',
            'mouseupoutside  .pedestal': 'dragPedestalEnd'
        },

        /**
         *
         */
        initialize: function(options) {
            this.mvt = options.mvt;
            this.time = 0;

            this.initGraphics();

            this._initialPosition = new Vector2();

            this.blastSound = new buzz.sound('audio/boom', {
                formats: ['ogg', 'mp3', 'wav'],
                volume: 80
            });

            // Listen to angle because the user can change that from the control panel,
            //   but don't listen to x or y because those will only ever be changed
            //   through this view.
            this.listenTo(this.model, 'change:angle', this.updateAngle);
            this.updateAngle(this.model, this.model.get('angle'));
            this.listenTo(this.model, 'fire', this.cannonFired);
        },

        initGraphics: function() {
            /*
             * The sprites layer will be used to scale all of our
             *   sprites at once instead of individually so they
             *   stay as a group with a common transform origin.
             */
            this.spritesLayer = new PIXI.DisplayObjectContainer();

            this.initCannon();
            this.initCarriage();
            this.initPedestal();
            this.initAxes();
            this.initParticles();

            this.displayObject.addChild(this.spritesLayer);

            this.updateMVT(this.mvt);
        },

        initCannon: function() {
            var cannon = Assets.createSprite(Assets.Images.CANNON);
            cannon.anchor.x = 0.34;
            cannon.anchor.y = 0.5;
            cannon.buttonMode = true;
            this.spritesLayer.addChild(cannon);
            this.cannon = cannon;
        },

        initCarriage: function() {
            var carriage = Assets.createSprite(Assets.Images.CANNON_CARRIAGE);
            carriage.anchor.x = 0.5;
            carriage.anchor.y = 1;
            carriage.y =  97;
            carriage.x = -26;
            
            this.spritesLayer.addChild(carriage);
        },

        initPedestal: function() {
            var pedestal = new PIXI.Graphics();
            pedestal.buttonMode = true;
            pedestal.defaultCursor = 'ns-resize';
            this.displayObject.addChild(pedestal);
            this.pedestal = pedestal;

            var pedestalSide = new PIXI.Graphics();
            this.displayObject.addChild(pedestalSide);
            this.pedestalSide = pedestalSide;
        },

        initAxes: function() {
            this.axes = new PIXI.Graphics();
            this.displayObject.addChild(this.axes);
        },

        initParticles: function() {
            /* 
             * The particles will be added to the sprites layer, which is always
             *   scaled with the images.  In this way we shouldn't ever have to
             *   reference the mvt object and do conversions when controlling
             *   the behavior of our particles or move their transform origin.
             */
            var particleContainer = new PIXI.Container();
            this.spritesLayer.addChildAt(particleContainer, 0);

            var flameParticleTexture = Assets.Texture(Assets.Images.FLAME_PARTICLE);
            var smokeParticleTexture = Assets.Texture(Assets.Images.SMOKE_PARTICLE);//PIXI.Texture.generateRoundParticleTexture(0, 20, CannonView.SMOKE_PARTICLE_COLOR);

            this.activeFlameParticles = [];
            this.dormantFlameParticles = [];
            this.activeSmokeParticles = [];
            this.dormantSmokeParticles = [];

            var i;
            var particle;

            // Smoke particles
            for (i = 0; i < CannonView.NUM_SMOKE_PARTICLES; i++) {
                particle = new PIXI.Sprite(smokeParticleTexture);
                particle.visible = false;
                particle.anchor.x = particle.anchor.y = 0.5;
                particle.velocity = new Vector2();

                particleContainer.addChild(particle);
                this.dormantSmokeParticles.push(particle);
            }

            // Flame particles (in front of smoke particles)
            for (i = 0; i < CannonView.NUM_FLAME_PARTICLES; i++) {
                particle = new PIXI.Sprite(flameParticleTexture);
                particle.visible = false;
                particle.anchor.x = particle.anchor.y = 0.5;
                particle.blendMode = PIXI.blendModes.ADD; // Get that good bright flame effect
                particle.velocity = new Vector2();

                particleContainer.addChild(particle);
                this.dormantFlameParticles.push(particle);
            }

            // Range of a particle's starting y before rotation
            this.flameParticleStartYRange = range({
                min: -CannonView.PARTICLE_EMISSION_AREA_WIDTH / 2 + CannonView.FLAME_PARTICLE_RADIUS_RANGE.min,
                max:  CannonView.PARTICLE_EMISSION_AREA_WIDTH / 2 - CannonView.FLAME_PARTICLE_RADIUS_RANGE.min
            });
            // End of cannon relative to origin minus the particle radius so it starts inside the bore
            this.flameParticleStartX = this.cannon.width * (1 - this.cannon.anchor.x) - CannonView.FLAME_PARTICLE_RADIUS_RANGE.min; 
        },        


        drawPedestal: function() {
            this.pedestal.clear();
            this.pedestalSide.clear();

            var pedestalHeight = this.model.get('y') + this.model.get('heightOffGround') + Constants.GROUND_Y;
            var pixelHeight  = Math.abs(this.mvt.modelToViewDeltaY(pedestalHeight));
            var pixelWidth   = this.mvt.modelToViewDeltaX(CannonView.PEDESTAL_WIDTH);
            var pixelYOffset = Math.abs(this.mvt.modelToViewDeltaY(this.model.get('heightOffGround')));
            var pixelXShift  = this.mvt.modelToViewDeltaX(CannonView.PEDESTAL_X_SHIFT);
            var pixelYShift  = this.mvt.modelToViewDeltaY(CannonView.PEDESTAL_Y_SHIFT);
            var pedestal = this.pedestal;
            var pedestalSide = this.pedestalSide;

            // Set a minimum height
            if (pixelHeight < 2)
                pixelHeight = 2;

            var horizontalRadius = pixelWidth / 2;
            var verticalRadius = (pixelWidth * CannonView.PEDESTAL_PERSPECTIVE_MODIFIER) / 2;

            // Draw grass top
            pedestal.beginFill(PEDESTAL_TOP_COLOR, 1);
            pedestal.drawEllipse(0, pixelYOffset, horizontalRadius, verticalRadius);
            pedestal.endFill();

            pedestalSide.beginFill(PEDESTAL_SIDE_COLOR, 1);
            pedestalSide.moveTo(-horizontalRadius, pixelYOffset)
            pedestalSide.bezierCurveTo(-horizontalRadius, pixelYOffset + verticalRadius, horizontalRadius, pixelYOffset + verticalRadius, horizontalRadius, pixelYOffset);
            pedestalSide.lineTo(horizontalRadius, pixelYOffset + pixelHeight);
            pedestalSide.bezierCurveTo(horizontalRadius, pixelYOffset + pixelHeight + verticalRadius, -horizontalRadius, pixelYOffset + pixelHeight + verticalRadius, -horizontalRadius, pixelYOffset + pixelHeight)
            pedestalSide.lineTo(-horizontalRadius, pixelYOffset);
            pedestalSide.endFill();

            pedestal.x = pixelXShift;
            pedestal.y = pixelYShift;
            pedestalSide.x = pixelXShift;
            pedestalSide.y = pixelYShift;
        },

        drawAxes: function() {
            var width  = 2000; // Arbitrarily large stage sizes. displayObject.stage.width wasn't giving correct values
            var height = 1000;

            var global = this.displayObject.position;
            var left   = Math.ceil(0 - global.x);
            var right  = Math.ceil(width - global.x);
            var top    = Math.ceil(0 - global.y);
            var bottom = Math.ceil(height - global.y);
            
            this.axes.clear();
            this.axes.lineStyle(CannonView.AXIS_LINE_WIDTH, CannonView.AXIS_LINE_COLOR, CannonView.AXIS_LINE_ALPHA);
            this.axes.moveTo(left, 0);
            this.axes.lineTo(right, 0);
            this.axes.moveTo(0, top);
            this.axes.lineTo(0, bottom);
        },

        dragCannonStart: function(event) {
            this.draggingCannon = true;
        },

        dragCannon: function(event) {
            if (this.draggingCannon) {
                var x = event.data.global.x - this.displayObject.x;
                var y = event.data.global.y - this.displayObject.y;
                
                var angle = Math.atan2(y, x);
                var degrees = -angle * RADIANS_TO_DEGREES;
                // Catch the case where we go into negatives at the 180deg mark
                if (degrees >= -180 && degrees < Constants.Cannon.MIN_ANGLE && this.model.get('angle') > 0)
                    degrees = 360 + degrees;

                // Make sure it's within bounds
                if (degrees < Constants.Cannon.MIN_ANGLE)
                    degrees = Constants.Cannon.MIN_ANGLE;
                if (degrees > Constants.Cannon.MAX_ANGLE)
                    degrees = Constants.Cannon.MAX_ANGLE;
                this.model.set('angle', degrees);
            }
        },

        dragCannonEnd: function(event) {
            this.draggingCannon = false;
        },

        dragPedestalStart: function(event) {
            this.previousPedestalY = event.data.global.y;
            this.draggingPedestal = true;
        },

        dragPedestal: function(event) {
            if (this.draggingPedestal) {
                var dy = event.data.global.y - this.previousPedestalY;
                this.previousPedestalY = event.data.global.y;

                dy = this.mvt.viewToModelDeltaY(dy);

                var y = this.model.get('y') + dy;
                if (y < 0)
                    y = 0;
                this.model.set('y', y);

                this.updatePosition();
                this.drawPedestal();
                this.drawAxes();
            }
        },

        dragPedestalEnd: function(event) {
            this.draggingPedestal = false;
        },

        updateAngle: function(cannon, angleInDegrees) {
            this.cannon.rotation = this.model.firingAngle();
        },

        updatePosition: function() {
            this.displayObject.x = this.mvt.modelToViewX(this.model.get('x'));
            this.displayObject.y = this.mvt.modelToViewY(this.model.get('y'));
        },

        updateMVT: function(mvt) {
            this.mvt = mvt;

            // Note: Maybe we don't need to ever update at all. Could we just scale the whole scene's displayObject?
            var targetCannonWidth = mvt.modelToViewDeltaX(this.model.get('width')); // in pixels
            var scale = targetCannonWidth / this.cannon.width;

            this.spritesLayer.scale.x = this.spritesLayer.scale.y = scale;

            this.updatePosition();
            this.drawPedestal();
            this.drawAxes();
        },

        update: function(time, deltaTime, paused) {
            if (!paused) {
                this.time += deltaTime;
                this.updateFlameParticles(this.time, deltaTime);
                this.updateSmokeParticles(this.time, deltaTime);
            }
        },

        cannonFired: function() {
            this.timeToStopFlameEmission = this.time + CannonView.FLAME_PARTICLE_EMISSION_DURATION;
            this.timeToStopSmokeEmission = this.time + CannonView.SMOKE_PARTICLE_EMISSION_DURATION;
            this.blastSound.stop();
            this.blastSound.play();
        },

        updateFlameParticles: function(time, deltaTime) {
            if (time < this.timeToStopFlameEmission) {
                var numParticlesToEmit = Math.floor(CannonView.FLAME_PARTICLE_EMISSION_RATE * deltaTime);
                while (numParticlesToEmit > 0) {
                    this.emitFlameParticle();
                    numParticlesToEmit--;
                }
            }

            var particle;
            var percentLifeLeft;
            var percentLifeSpent;
            var radius;
            for (var i = this.activeFlameParticles.length - 1; i >= 0; i--) {
                particle = this.activeFlameParticles[i];

                // Clean up dead particles
                if (time >= particle.lifeEndsAt) {
                    particle.visible = false;
                    this.activeFlameParticles.splice(i, 1);
                    this.dormantFlameParticles.push(particle);
                }

                // Move particles
                particle.x += particle.velocity.x * deltaTime;
                particle.y += particle.velocity.y * deltaTime;

                // To use in linear interpolation functions
                percentLifeLeft = ((particle.lifeEndsAt - time) / particle.timeToLive);
                percentLifeSpent = 1 - percentLifeLeft;

                // Grow particles
                radius = CannonView.FLAME_PARTICLE_RADIUS_RANGE.lerp(Math.min(percentLifeSpent * 2, 1));
                particle.scale.x = particle.scale.y = radius / (particle.texture.width / 2);

                particle.rotation += -20 * deltaTime;

                // Fade particles out when they reach the end of their lives
                if (percentLifeLeft < (1 - CannonView.FLAME_PARTICLE_FADE_POINT))
                    particle.alpha = (percentLifeLeft / (1 - CannonView.FLAME_PARTICLE_FADE_POINT));  
            }
        },

        updateSmokeParticles: function(time, deltaTime) {
            if (time < this.timeToStopSmokeEmission) {
                var numParticlesToEmit = Math.floor(CannonView.SMOKE_PARTICLE_EMISSION_RATE * deltaTime);
                while (numParticlesToEmit > 0) {
                    this.emitSmokeParticle();
                    numParticlesToEmit--;
                }
            }

            var particle;
            var percentLifeLeft;
            var percentLifeSpent;
            var radius;
            for (var i = this.activeSmokeParticles.length - 1; i >= 0; i--) {
                particle = this.activeSmokeParticles[i];

                // Clean up dead particles
                if (time >= particle.lifeEndsAt) {
                    particle.visible = false;
                    this.activeSmokeParticles.splice(i, 1);
                    this.dormantSmokeParticles.push(particle);
                }

                // Move particles
                particle.x += particle.velocity.x * deltaTime;
                particle.y += particle.velocity.y * deltaTime;

                // To use in linear interpolation functions
                percentLifeLeft = ((particle.lifeEndsAt - time) / particle.timeToLive);
                percentLifeSpent = 1 - percentLifeLeft;

                // Grow particles
                radius = CannonView.SMOKE_PARTICLE_RADIUS_RANGE.lerp(Math.min(percentLifeSpent * 2, 1));
                particle.scale.x = particle.scale.y = radius / (particle.texture.width / 2);

                particle.rotation += -1 * deltaTime;

                // Fade particles out when they reach the end of their lives
                if (percentLifeLeft < (1 - CannonView.SMOKE_PARTICLE_FADE_POINT))
                    particle.alpha = (percentLifeLeft / (1 - CannonView.SMOKE_PARTICLE_FADE_POINT)) * CannonView.SMOKE_PARTICLE_ALPHA;  
            }
        },

        emitFlameParticle: function() {
            if (!this.dormantFlameParticles.length)
                return null;

            var particle = this.dormantFlameParticles.pop();
            if (particle) {
                // Get the starting position of the particle if the cannon were not rotated.
                //   Then rotate that point around the cannon's rotational axis to get the 
                //   actual starting point.
                var initialPosition = this._initialPosition
                    .set(this.flameParticleStartX, this.flameParticleStartYRange.random())
                    .rotate(this.model.firingAngle());

                var scale = CannonView.FLAME_PARTICLE_RADIUS_RANGE.min / (particle.texture.width / 2);
                var angle = this.model.firingAngle() + CannonView.FLAME_PARTICLE_SPREAD_ANGLE_RANGE.random();

                particle.x = initialPosition.x;
                particle.y = initialPosition.y;
                particle.scale.x = particle.scale.y = scale;
                particle.alpha = 1;
                particle.visible = true;
                particle.timeToLive = CannonView.FLAME_PARTICLE_LIFE_SPAN.random();
                particle.lifeEndsAt = this.time + particle.timeToLive;
                particle.velocity.set(CannonView.FLAME_PARTICLE_VELOCITY_RANGE.random(), 0).rotate(angle);
                particle.rotation = Math.random() * Math.PI;

                this.activeFlameParticles.push(particle);    
            }

            return particle;
        },

        emitSmokeParticle: function() {
            if (!this.dormantSmokeParticles.length)
                return null;

            var particle = this.dormantSmokeParticles.pop();
            if (particle) {
                // Get the starting position of the particle if the cannon were not rotated.
                //   Then rotate that point around the cannon's rotational axis to get the 
                //   actual starting point.
                var initialPosition = this._initialPosition
                    .set(this.flameParticleStartX, this.flameParticleStartYRange.random())
                    .rotate(this.model.firingAngle());

                var scale = CannonView.SMOKE_PARTICLE_RADIUS_RANGE.min / (particle.texture.width / 2);
                var angle = this.model.firingAngle() + CannonView.SMOKE_PARTICLE_SPREAD_ANGLE_RANGE.random();

                particle.x = initialPosition.x;
                particle.y = initialPosition.y;
                particle.scale.x = particle.scale.y = scale;
                particle.alpha = CannonView.SMOKE_PARTICLE_ALPHA;
                particle.visible = true;
                particle.timeToLive = CannonView.SMOKE_PARTICLE_LIFE_SPAN.random();
                particle.lifeEndsAt = this.time + particle.timeToLive;
                particle.velocity.set(CannonView.SMOKE_PARTICLE_VELOCITY_RANGE.random(), 0).rotate(angle);
                particle.rotation = Math.random() * Math.PI;

                this.activeSmokeParticles.push(particle);
            }

            return particle;
        },

        muteVolume: function() {
            this.blastSound.setVolume(0);
        },

        lowVolume: function() {
            this.blastSound.setVolume(20);
        },

        highVolume: function() {
            this.blastSound.setVolume(80);
        }


    }, Constants.CannonView);

    return CannonView;
});
Ejemplo n.º 8
0
define(function(require) {

    'use strict';
    
    var PIXI = require('pixi');

    var PixiView = require('common/v3/pixi/view');
    var Vector2  = require('common/math/vector2');
    var Colors   = require('common/colors/colors');

    var Projectile = require('models/projectile');

    var Assets = require('assets');

    var Constants = require('constants');

    var RADIANS_TO_DEGREES = 180 / Math.PI;
    var AIR_RESISTANCE_ENABLED_COLOR  = Colors.parseHex(Constants.TrajectoryView.AIR_RESISTANCE_ENABLED_COLOR);
    var AIR_RESISTANCE_DISABLED_COLOR = Colors.parseHex(Constants.TrajectoryView.AIR_RESISTANCE_DISABLED_COLOR);
    var SECOND_MARKER_COLOR = Colors.parseHex(Constants.TrajectoryView.SECOND_MARKER_COLOR);

    var TrajectoryView = PixiView.extend({

        initialize: function(options) {
            this.mvt = options.mvt;
            this.times = [];
            this.xPoints = [];
            this.yPoints = [];
            this.airResistanceHistory = [];
            this.secondMarksX = [];
            this.secondMarksY = [];

            this.initGraphics();

            this.listenTo(this.model, 'change:time', this.recordState);

            this.updateMVT(this.mvt);
        },

        /**
         * Override this to draw different kinds of projectiles.
         */
        initGraphics: function() {
            this.graphics = new PIXI.Graphics();

            this.times.push(0);
            this.xPoints.push(this.model.x); 
            this.yPoints.push(this.model.y);
            this.airResistanceHistory.push(this.model.get('airResistanceEnabled'));

            this.drawTrajectoryPath();

            this.displayObject.addChild(this.graphics);
        },

        recordState: function(trajectory, time) {
            var lastTime = this.times[this.times.length - 1];

            this.times.push(time);
            this.xPoints.push(this.model.x);
            this.yPoints.push(this.model.y);
            this.airResistanceHistory.push(this.model.get('airResistanceEnabled'));

            // See if we just rolled over a second mark
            if (Math.floor(lastTime) !== Math.floor(time)) {
                /* We know we just rolled over a second mark because
                 *   the last time rounds down to a different integer
                 *   than our current time.  Now we can use linear
                 *   interpolation to estimate the position (x, y) of
                 *   the projectile when the time was ON the second.
                 */
                var alpha = (Math.floor(time) - lastTime) / (time - lastTime);
                var lastX = this.xPoints[this.xPoints.length - 2];
                var lastY = this.yPoints[this.yPoints.length - 2];
                var currX = this.xPoints[this.xPoints.length - 1];
                var currY = this.yPoints[this.yPoints.length - 1];
                this.secondMarksX.push((1 - alpha) * lastX + alpha * currX);
                this.secondMarksY.push((1 - alpha) * lastY + alpha * currY);
            }

            this.drawTrajectoryPath();
        },

        drawTrajectoryPath: function() {
            var graphics = this.graphics;
            graphics.clear();
            graphics.moveTo(
                this.mvt.modelToViewX(this.xPoints[0]), 
                this.mvt.modelToViewY(this.yPoints[0])
            );

            var airResistanceEnabled;
            var airResistanceHistory = this.airResistanceHistory;
            var xPoints = this.xPoints;
            var yPoints = this.yPoints;
            for (var i = 1; i < this.xPoints.length; i++) {
                if (airResistanceEnabled !== airResistanceHistory[i]) {
                    airResistanceEnabled = airResistanceHistory[i];
                    if (airResistanceEnabled)
                        this.graphics.lineStyle(TrajectoryView.LINE_WIDTH, AIR_RESISTANCE_ENABLED_COLOR, 1);
                    else
                        this.graphics.lineStyle(TrajectoryView.LINE_WIDTH, AIR_RESISTANCE_DISABLED_COLOR, 1);
                }

                graphics.lineTo(
                    this.mvt.modelToViewX(xPoints[i]),
                    this.mvt.modelToViewY(yPoints[i])
                );
            }

            this.markSeconds();
        },

        markSeconds: function() {
            var graphics = this.graphics;
            var radius = TrajectoryView.SECOND_MARKER_WIDTH / 2;
            var x;
            var y;

            graphics.lineStyle(TrajectoryView.SECOND_MARKER_LINE_WIDTH, SECOND_MARKER_COLOR, TrajectoryView.SECOND_MARKER_ALPHA);
            for (var i = 0; i < this.secondMarksX.length; i++) {
                x = this.mvt.modelToViewX(this.secondMarksX[i]);
                y = this.mvt.modelToViewY(this.secondMarksY[i]);
                graphics.moveTo(x - radius, y);
                graphics.lineTo(x + radius, y);
                graphics.moveTo(x, y - radius);
                graphics.lineTo(x, y + radius);
            }
        },

        updateMVT: function(mvt) {
            this.mvt = mvt;

            this.drawTrajectoryPath();
        }

    }, Constants.TrajectoryView);

    return TrajectoryView;
});
Ejemplo n.º 9
0
define(function(require) {

    'use strict';

    var _    = require('underscore');
    var PIXI = require('pixi');
    require('common/v3/pixi/extensions');
    
    var PixiView = require('common/v3/pixi/view');
    var Colors   = require('common/colors/colors');

    var Constants = require('constants');
    var FILL_COLOR = Colors.parseHex(Constants.GlassPaneView.FILL_COLOR);
    var FILL_ALPHA = Constants.GlassPaneView.FILL_ALPHA;

    /**
     * A view that represents a glass pane
     */
    var GlassPaneView = PixiView.extend({

        /**
         * Initializes the new GlassPaneView.
         */
        initialize: function(options) {
            this.initGraphics();
            this.updateMVT(options.mvt);
        },

        initGraphics: function() {
            this.glassPane = new PIXI.Graphics();
            this.displayObject.addChild(this.glassPane);
        },

        drawGlassPane: function() {
            var bounds = this.model.get('bounds');
            var viewRect = this.mvt.modelToView(bounds);

            this.glassPane.beginFill(FILL_COLOR, FILL_ALPHA);
            this.glassPane.drawRect(
                viewRect.x,
                viewRect.y - viewRect.h, // This is wrong, but for some reason it's necessary
                viewRect.w,
                viewRect.h
            );
            this.glassPane.endFill();
        },

        /**
         * Updates the model-view-transform and anything that
         *   relies on it.
         */
        updateMVT: function(mvt) {
            this.mvt = mvt;

            this.drawGlassPane();
        }

    });

    return GlassPaneView;
});
Ejemplo n.º 10
0
 getColorFromWavelength: function(wavelength) {
     var key = '' + wavelength;
     if (this.colors[key] === undefined)
         this.colors[key] = Colors.parseHex(WavelengthColors.nmToHex(wavelength));
     return this.colors[key];
 },