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(); },
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); },
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; } },
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); },
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; });
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; });
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; });
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; });
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; });
getColorFromWavelength: function(wavelength) { var key = '' + wavelength; if (this.colors[key] === undefined) this.colors[key] = Colors.parseHex(WavelengthColors.nmToHex(wavelength)); return this.colors[key]; },