initPolution: function() { if (this.pollution) this.foregroundLayer.removeChild(this.pollution); var canvas = document.createElement('canvas'); canvas.width = this.width; canvas.height = this.height; var ctx = canvas.getContext('2d'); var gradient = ctx.createLinearGradient(0, 0, 0, this.height); gradient.addColorStop(0, Constants.Atmosphere.POLLUTION_TOP_COLOR); gradient.addColorStop(1, Constants.Atmosphere.POLLUTION_BOTTOM_COLOR); ctx.fillStyle = gradient; ctx.fillRect(0, 0, this.width, this.height); this.pollution = new PIXI.Sprite(PIXI.Texture.fromCanvas(canvas)); this.foregroundLayer.addChild(this.pollution); this.pollutionRange = range({ min: Constants.Atmosphere.MIN_GREENHOUSE_GAS_CONCENTRATION, max: Constants.Atmosphere.MAX_GREENHOUSE_GAS_CONCENTRATION }); this.updatePollution(this.simulation.atmosphere, this.simulation.atmosphere.get('greenhouseGasConcentration')); },
updateMVT: function(mvt) { this.mvt = mvt; if (AppView.windowIsShort()) { this.particleRadiusRange = range({ min: 16, max: 56 }); } else { this.particleRadiusRange = range({ min: 16, max: 70 }); } },
drawEffects: function() { this.lowerEffects.removeChildren(); this.upperEffects.removeChildren(); // Create the closed FINISH tile to draw over the other one var finishClosedTile = Assets.createSprite(Assets.Images.FINISH_CLOSED); finishClosedTile.x = this.finishTile.x; finishClosedTile.y = this.finishTile.y; finishClosedTile.scale.x = this.tileScale; finishClosedTile.scale.y = this.tileScale; finishClosedTile.visible = false; this.lowerEffects.addChild(finishClosedTile); this.finishClosedTile = finishClosedTile; var finishWinTile = Assets.createSprite(Assets.Images.FINISH_WIN); finishWinTile.x = this.finishTile.x; finishWinTile.y = this.finishTile.y; finishWinTile.scale.x = this.tileScale; finishWinTile.scale.y = this.tileScale; finishWinTile.visible = false; this.lowerEffects.addChild(finishWinTile); this.finishWinTile = finishWinTile; // Create pulsing ring around the finish to get the player's attention this.finishPulse = this.createPulseSprite(this.finishTile.x, this.finishTile.y, Assets.Images.FINISH_PULSE); this.pulseIntervalCounter = 0; this.pulseDurationCounter = null; this.pulseScaleRange = range({ min: this.tileScale * 0.2, max: this.tileScale }); // Create a pulsing ring for when the player wins this.finishWinPulse = this.createPulseSprite(this.finishTile.x, this.finishTile.y, Assets.Images.FINISH_WIN_PULSE);; this.winPulseDurationCounter = null; },
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; },
initialize: function(options) { options = _.extend({ units: 'ATM', min: 0, // Minimum value max: 1, // Maximum value value: undefined, // The default pressure value decimals: 1, // Number of decimals to show minAngle: -30 * (Math.PI / 180), // Angle of the needle at 0% in radians maxAngle: 210 * (Math.PI / 180), // Angle of the needle at 100% in radians textColor: '#000', readoutFont: '10px Arial', unitsFont: '7px Arial', allowOverload: true, overloadColor: '#ff0000', overloadFont: '8px Arial', overloadText: 'OVERLOAD', tickColor: '#888', tickLength: 6, // Pixels tickWidth: 1, // Pixels tickMargin: 7, // Margin between ticks and the outline in pixels numTicks: 20, needleColor: '#121212', needleWidth: 2, // Pixels radius: 40, // Pixels backgroundColor: '#fff', outlineColor: '#e2e2e2', outlineThickness: 3, // Pixels // The connector is just a little protusion that visually connects it to something else showConnector: true, connectorAngle: Math.PI / 2, // Which direction it's coming out (bottom default) connectorLength: 9, // Pixels connectorWidth: 14, // Pixels connectorColor1: '#000', connectorColor2: '#e2e2e2' }, options); // Field assignments from options this.units = options.units; this.pressureRange = range({ min: options.min, max: options.max }); this.angleRange = range({ min: options.minAngle, max: options.maxAngle }); this.value = options.value === undefined ? options.min : options.value; this.decimals = options.decimals; this.textColor = options.textColor; this.readoutFont = options.readoutFont; this.unitsFont = options.unitsFont; this.allowOverload = options.allowOverload; this.overloadColor = options.overloadColor; this.overloadFont = options.overloadFont; this.overloadText = options.overloadText; this.tickColor = Colors.parseHex(options.tickColor); this.tickLength = options.tickLength; this.tickWidth = options.tickWidth; this.tickMargin = options.tickMargin; this.numTicks = options.numTicks; this.needleColor = Colors.parseHex(options.needleColor); this.needleWidth = options.needleWidth; this.radius = options.radius; this.backgroundColor = Colors.parseHex(options.backgroundColor); this.outlineColor = Colors.parseHex(options.outlineColor); this.outlineThickness = options.outlineThickness; this.showConnector = options.showConnector; this.connectorAngle = options.connectorAngle; this.connectorLength = options.connectorLength; this.connectorWidth = options.connectorWidth; this.connectorColor1 = options.connectorColor1; this.connectorColor2 = options.connectorColor2; this.initGraphics(); },
define(function (require) { 'use strict'; var Vector2 = require('common/math/vector2'); var Rectangle = require('common/math/rectangle'); var Functions = require('common/math/functions'); var PiecewiseCurve = require('common/math/piecewise-curve'); var range = require('common/math/range'); var ThermalContactArea = require('models/thermal-contact-area'); /************************************************************************* ** ** ** UNIVERSAL CONSTANTS ** ** ** *************************************************************************/ var Constants = {}; Constants.ROOM_TEMPERATURE = 296; // In Kelvin. Constants.FREEZING_POINT_TEMPERATURE = 273.15; // In Kelvin. Constants.BOILING_POINT_TEMPERATURE = 373.15; // In Kelvin. // Time values for normal and fast-forward motion. Constants.FRAMES_PER_SECOND = 30.0; Constants.FAST_FORWARD_TIMESCALE = 4; Constants.SIM_TIME_PER_TICK_NORMAL = 1 / Constants.FRAMES_PER_SECOND; Constants.SIM_TIME_PER_TICK_FAST_FORWARD = Constants.SIM_TIME_PER_TICK_NORMAL * Constants.FAST_FORWARD_TIMESCALE; Constants.MAX_HEAT_EXCHANGE_TIME_STEP = Constants.SIM_TIME_PER_TICK_NORMAL; // Constants used for creating projections that have a 3D-ish look. Constants.Z_TO_X_OFFSET_MULTIPLIER = -0.25; Constants.Z_TO_Y_OFFSET_MULTIPLIER = -0.25; Constants.MAP_Z_TO_XY_OFFSET = function(zValue) { return new Vector2(zValue * Constants.Z_TO_X_OFFSET_MULTIPLIER, zValue * Constants.Z_TO_Y_OFFSET_MULTIPLIER); }; Constants.PERSPECTIVE_ANGLE = Math.atan2(-Constants.Z_TO_Y_OFFSET_MULTIPLIER, -Constants.Z_TO_X_OFFSET_MULTIPLIER); Constants.PERSPECTIVE_EDGE_PROPORTION = Math.sqrt( Math.pow(Constants.Z_TO_X_OFFSET_MULTIPLIER, 2) + Math.pow(Constants.Z_TO_Y_OFFSET_MULTIPLIER, 2) ); // For comparing temperatures. Constants.SIGNIFICANT_TEMPERATURE_DIFFERENCE = 1E-3; // In degrees K. // Threshold for deciding when two temperatures can be considered equal. Constants.TEMPERATURES_EQUAL_THRESHOLD = 1E-6; // In Kelvin. Constants.WATER_FILL_COLOR = '#afeeee'; // Model-view transform scale factor for Energy Systems tab. Constants.ENERGY_SYSTEMS_MVT_SCALE_FACTOR = 2200; /************************************************************************* ** ** ** ENERGY CONTAINER CATEGORIES ** ** ** *************************************************************************/ var EnergyContainerCategory = { IRON: 'iron', BRICK: 'brick', WATER: 'water', AIR: 'air' }; Constants.EnergyContainerCategory = EnergyContainerCategory; /************************************************************************* ** ** ** INTRO SIMULATION ** ** ** *************************************************************************/ var IntroSimulation = {}; /** * Minimum distance allowed between two objects. This basically prevents * floating point issues. */ IntroSimulation.MIN_INTER_ELEMENT_DISTANCE = 1E-9; // In meters /** * Threshold of temperature difference between the bodies in a multi-body * system below which energy can be exchanged with air. */ IntroSimulation.MIN_TEMPERATURE_DIFF_FOR_MULTI_BODY_AIR_ENERGY_EXCHANGE = 2.0; // In degrees K, empirically determined // Initial thermometer location, intended to be away from any model objects. IntroSimulation.INITIAL_THERMOMETER_LOCATION = new Vector2( 100, 100 ); IntroSimulation.NUM_THERMOMETERS = 3; IntroSimulation.BEAKER_WIDTH = 0.085; // In meters. IntroSimulation.BEAKER_HEIGHT = IntroSimulation.BEAKER_WIDTH * 1.1; // Flag that can be turned on in order to print out some profiling info. IntroSimulation.ENABLE_INTERNAL_PROFILING = false; Constants.IntroSimulation = IntroSimulation; var IntroSimulationView = {}; IntroSimulationView.BURNER_WIDTH_SCALE = 0.7; IntroSimulationView.BURNER_HEIGHT_TO_WIDTH_RATIO = 0.8; Constants.IntroSimulationView = IntroSimulationView; /************************************************************************* ** ** ** ELEMENT ** ** ** *************************************************************************/ var IntroElementView = {}; IntroElementView.TEXT_FONT = '32px Arial'; IntroElementView.SMALL_TEXT_FONT = '22px Arial'; Constants.IntroElementView = IntroElementView; /************************************************************************* ** ** ** BLOCK ** ** ** *************************************************************************/ var Block = {}; // Height and width of all block surfaces, since it is a cube. Block.SURFACE_WIDTH = 0.045; // In meters // Number of slices where energy chunks may be placed. Block.NUM_ENERGY_CHUNK_SLICES = 4; Block.MAX_TEMPERATURE = 450; // Degrees Kelvin, value is pretty much arbitrary. Whatever works. Constants.Block = Block; var BlockView = {}; BlockView.PERSPECTIVE_ANGLE = Math.atan2(-Constants.Z_TO_Y_OFFSET_MULTIPLIER, -Constants.Z_TO_X_OFFSET_MULTIPLIER); BlockView.PERSPECTIVE_EDGE_PROPORTION = Math.sqrt( Math.pow(Constants.Z_TO_X_OFFSET_MULTIPLIER, 2) + Math.pow(Constants.Z_TO_Y_OFFSET_MULTIPLIER, 2) ); BlockView.LINE_WIDTH = 3; Constants.BlockView = BlockView; /************************************************************************* ** ** ** BRICK ** ** ** *************************************************************************/ var Brick = {}; Brick.SPECIFIC_HEAT = 840; // In J/kg-K, source = design document. Brick.DENSITY = 3300; // In kg/m^3, source = design document plus some tweaking to keep chunk numbers reasonable. // Some constants needed for energy chunk mapping. Brick.ENERGY_AT_ROOM_TEMPERATURE = Math.pow(Block.SURFACE_WIDTH, 3) * Brick.DENSITY * Brick.SPECIFIC_HEAT * Constants.ROOM_TEMPERATURE; // In joules. Brick.ENERGY_AT_WATER_FREEZING_TEMPERATURE = Math.pow(Block.SURFACE_WIDTH, 3) * Brick.DENSITY * Brick.SPECIFIC_HEAT * Constants.FREEZING_POINT_TEMPERATURE; // In joules. Brick.NUM_ENERGY_CHUNKS_AT_FREEZING = 1.25; Brick.NUM_ENERGY_CHUNKS_AT_ROOM_TEMP = 2.4; // Close to rounding to 3 so that little energy needed to transfer a chunk. Constants.Brick = Brick; var BrickView = {}; BrickView.FILL_COLOR = '#d6492e'; BrickView.TEXT_COLOR = '#000'; Constants.BrickView = BrickView; /************************************************************************* ** ** ** IRON ** ** ** *************************************************************************/ var Iron = {}; Iron.SPECIFIC_HEAT = 450; // In J/kg-K, source = design document. Iron.DENSITY = 7800; // In kg/m^3, source = design document Constants.Iron = Iron; var IronBlockView = {}; IronBlockView.FILL_COLOR = '#888'; IronBlockView.TEXT_COLOR = '#000'; Constants.IronBlockView = IronBlockView; /************************************************************************* ** ** ** BURNER ** ** ** *************************************************************************/ var Burner = {}; Burner.WIDTH = 0.075; // In meters. Burner.HEIGHT = Burner.WIDTH * 1; Burner.EDGE_TO_HEIGHT_RATIO = 0.2; // Multiplier empirically determined for best look. Burner.MAX_ENERGY_GENERATION_RATE = 5000; // joules/sec, empirically chosen. Burner.CONTACT_DISTANCE = 0.001; // In meters. Burner.ENERGY_CHUNK_CAPTURE_DISTANCE = 0.2; // In meters, empirically chosen. // Burner.PERSPECTIVE_ANGLE = Math.PI / 4; // Because of the way that energy chunks are exchanged between thermal // modeling elements within this simulation, things can end up looking a // bit odd if a burner is turned on with nothing on it. To account for // this, a separate energy generation rate is used when a burner is // exchanging energy directly with the air. Burner.MAX_ENERGY_GENERATION_RATE_INTO_AIR = Burner.MAX_ENERGY_GENERATION_RATE * 0.3; // joules/sec, multiplier empirically chosen. Constants.Burner = Burner; var BurnerView = {}; BurnerView.HOT_COLOR = '#ff4500'; BurnerView.COLD_COLOR = '#0000f0'; BurnerView.TEXT_FONT = 'bold 17px Arial'; BurnerView.TEXT_COLOR = '#000'; BurnerView.SMALL_TEXT_FONT = 'bold 12px Arial'; Constants.BurnerView = BurnerView; /************************************************************************* ** ** ** BEAKER ** ** ** *************************************************************************/ var Beaker = {}; Beaker.MATERIAL_THICKNESS = 0.001; // In meters. Beaker.NUM_SLICES = 6; Beaker.STEAMING_RANGE = 10; // Number of degrees Kelvin over which steam is emitted. // Constants that control the nature of the fluid in the beaker. Beaker.WATER_SPECIFIC_HEAT = 3000; // In J/kg-K. The real value for water is 4186, but this was adjusted so that there // aren't too many chunks and so that a chunk is needed as soon as heating starts. Beaker.WATER_DENSITY = 1000.0; // In kg/m^3, source = design document (and common knowledge). Beaker.INITIAL_FLUID_LEVEL = 0.5; Constants.Beaker = Beaker; var BeakerView = {}; BeakerView.LINE_COLOR = '#ccc'; BeakerView.LINE_WIDTH = 3; BeakerView.PERSPECTIVE_PROPORTION = -Constants.Z_TO_Y_OFFSET_MULTIPLIER; BeakerView.TEXT_FONT = '32px Arial'; BeakerView.SHOW_MODEL_RECT = false; BeakerView.FILL_COLOR = '#fff'; BeakerView.FILL_ALPHA = 0.30; BeakerView.WATER_FILL_COLOR = Constants.WATER_FILL_COLOR; BeakerView.WATER_FILL_ALPHA = 0.5; BeakerView.WATER_LINE_COLOR = '#82A09E'; BeakerView.WATER_LINE_WIDTH = 2; BeakerView.STEAMING_RANGE = 10; // Number of degrees Kelvin over which steam is visible. BeakerView.STEAM_PARTICLE_COLOR = '#fff'; BeakerView.STEAM_PARTICLE_SPEED_RANGE = range({ min: 100, max: 125 }); // In screen coords (basically pixels) per second. BeakerView.STEAM_PARTICLE_RADIUS_RANGE = range({ min: 15, max: 40 }); // In screen coords (basically pixels). BeakerView.STEAM_PARTICLE_LIFE_RANGE = range({ min: 2, max: 4 }); // Seconds to live BeakerView.STEAM_PARTICLE_PRODUCTION_RATE_RANGE = range({ min: 20, max: 40 }); // Particles per second. BeakerView.STEAM_PARTICLE_GROWTH_RATE = 0.2; // Proportion per second. BeakerView.MAX_STEAM_PARTICLE_OPACITY = 0.7; // Proportion, 1 is max. BeakerView.NUM_STEAM_PARTICLES = 200; Constants.BeakerView = BeakerView; /************************************************************************* ** ** ** BURNER ** ** ** *************************************************************************/ var BurnerStandView = {}; // Constants that control some aspect of appearance. These can be made // into constructor params if it is ever desirable to do so. BurnerStandView.LINE_WIDTH = 4; BurnerStandView.LINE_COLOR = '#000'; BurnerStandView.LINE_JOIN = 'bevel'; BurnerStandView.PERSPECTIVE_ANGLE = Math.PI / 4; // Positive is counterclockwise, a value of 0 produces a non-skewed rectangle. Constants.BurnerStandView = BurnerStandView; /************************************************************************* ** ** ** THERMOMETER ** ** ** *************************************************************************/ var ThermometerView = {}; ThermometerView.NUM_TICK_MARKS = 13; ThermometerView.TICK_MARK_THICKNESS = 2; // pixels ThermometerView.LIQUID_COLOR = '#ed1c24'; ThermometerView.HEIGHT_IN_METERS = 0.081; Constants.ThermometerView = ThermometerView; var ThermometerClipsView = {}; ThermometerClipsView.BASE_WIDTH = 0.118; // Meters ThermometerClipsView.CLIP_WIDTH = 0.038; // Meters tall Constants.ThermometerClipsView = ThermometerClipsView; /************************************************************************* ** ** ** AIR ** ** ** *************************************************************************/ var Air = {}; // 2D size of the air. It is sized such that it will extend off the left, // right, and top edges of screen for the most common aspect ratios of the // view. Air.WIDTH = 0.7; Air.HEIGHT = 0.3; // The thickness of the slice of air being modeled. This is basically the // z dimension, and is used solely for volume calculations. Air.DEPTH = 0.1; // In meters. // Constants that define the heat carrying capacity of the air. Air.SPECIFIC_HEAT = 1012; // In J/kg-K, source = design document. Air.DENSITY = 10; // In kg/m^3, far denser than real air, done to make things cool faster. // Derived constants. Air.VOLUME = Air.WIDTH * Air.HEIGHT * Air.DEPTH; Air.MASS = Air.VOLUME * Air.DENSITY; Air.INITIAL_ENERGY = Air.MASS * Air.SPECIFIC_HEAT * Constants.ROOM_TEMPERATURE; Air.THERMAL_CONTACT_AREA = new ThermalContactArea(new Rectangle(-Air.WIDTH / 2, 0, Air.WIDTH, Air.HEIGHT), true); Constants.Air = Air; /************************************************************************* ** ** ** ENERGY TYPES ** ** ** *************************************************************************/ Constants.EnergyTypes = { THERMAL: 0, ELECTRICAL: 1, MECHANICAL: 2, LIGHT: 3, CHEMICAL: 4, HIDDEN: 5 }; /************************************************************************* ** ** ** ENERGY CHUNKS ** ** ** *************************************************************************/ // Constant used by all of the "energy systems" in order to keep the amount // of energy generated, converted, and consumed consistent. Constants.MAX_ENERGY_PRODUCTION_RATE = 10000; // In joules/sec. // Constants that control the speed of the energy chunks Constants.ENERGY_CHUNK_VELOCITY = 0.04; // In meters/sec. // Constant function for energy chunk mapping. The basis for this function // is that the brick has 2 energy chunks at room temp, one at the freezing // point of water. var _energyToNumChunks = Functions.createLinearFunction( Brick.ENERGY_AT_WATER_FREEZING_TEMPERATURE, Brick.ENERGY_AT_ROOM_TEMPERATURE, Brick.NUM_ENERGY_CHUNKS_AT_FREEZING, Brick.NUM_ENERGY_CHUNKS_AT_ROOM_TEMP ); Constants.numChunksToEnergy = _energyToNumChunks.createInverse(); Constants.energyToNumChunks = function(energy) { return Math.max(Math.round(_energyToNumChunks(energy)), 0); }; Constants.ENERGY_PER_CHUNK = Constants.numChunksToEnergy(2) - Constants.numChunksToEnergy(1); var EnergyChunkCollectionView = {}; EnergyChunkCollectionView.Z_DISTANCE_WHERE_FULLY_FADED = 0.1; // In meters EnergyChunkCollectionView.WIDTH = 0.012; // In meters Constants.EnergyChunkCollectionView = EnergyChunkCollectionView; /************************************************************************* ** ** ** ENERGY CHUNK DISTRIBUTOR ** ** ** *************************************************************************/ var EnergyChunkDistributor = {}; EnergyChunkDistributor.OUTSIDE_CONTAINER_FORCE = 0.01; // In Newtons, empirically determined. EnergyChunkDistributor.ZERO_VECTOR = new Vector2(0, 0); // Parameters that can be adjusted to change they nature of the redistribution. EnergyChunkDistributor.MAX_TIME_STEP = 5E-3; // In seconds, for algorithm that moves the points. EnergyChunkDistributor.ENERGY_CHUNK_MASS = 1E-3; // In kilograms, chosen arbitrarily. EnergyChunkDistributor.FLUID_DENSITY = 1000; // In kg / m ^ 3, same as water, used for drag. EnergyChunkDistributor.ENERGY_CHUNK_DIAMETER = 1E-3; // In meters, chosen empirically. EnergyChunkDistributor.ENERGY_CHUNK_CROSS_SECTIONAL_AREA = Math.PI * Math.pow(EnergyChunkDistributor.ENERGY_CHUNK_DIAMETER, 2); // Treat energy chunk as if it is shaped like a sphere. EnergyChunkDistributor.DRAG_COEFFICIENT = 500; // Unitless, empirically chosen. // Thresholds for deciding whether or not to perform redistribution. These value // should be chosen such that particles spread out, then stop all movement. EnergyChunkDistributor.REDISTRIBUTION_THRESHOLD_ENERGY = 1E-4; // In joules, I think. // Number of times to attempt an even distribution of energy chunks on first load EnergyChunkDistributor.NUM_INITIAL_DISTRIBUTION_ATTEMPTS = 1000; // 1000 Constants.EnergyChunkDistributor = EnergyChunkDistributor; /************************************************************************* ** ** ** ENERGY CHUNK WANDER CONTROLLER ** ** ** *************************************************************************/ var EnergyChunkWanderController = {}; EnergyChunkWanderController.MIN_VELOCITY = 0.06; // In m/s. EnergyChunkWanderController.MAX_VELOCITY = 0.10; // In m/s. EnergyChunkWanderController.MIN_TIME_IN_ONE_DIRECTION = 0.4; EnergyChunkWanderController.MAX_TIME_IN_ONE_DIRECTION = 0.8; EnergyChunkWanderController.DISTANCE_AT_WHICH_TO_STOP_WANDERING = 0.05; // In meters, empirically chosen. EnergyChunkWanderController.MAX_ANGLE_VARIATION = Math.PI * 0.2; // Max deviation from angle to destination, in radians, empirically chosen. Constants.EnergyChunkWanderController = EnergyChunkWanderController; /************************************************************************* ** ** ** HEAT TRANSFER ** ** ** *************************************************************************/ /** * Constants that control the rate of heat transfer between the various * elements that can contain heat and maps for looking up transfer * rates for any two model elements that are capable of exchanging heat. * * @author John Blanco */ var HeatTransfer = { // Heat transfer values. NOTE: Originally, these were constants, but the // design team requested that they be changeable via a developer control, // which is why they are now properties. BRICK_IRON_HEAT_TRANSFER_FACTOR: 1000, BRICK_WATER_HEAT_TRANSFER_FACTOR: 1000, BRICK_AIR_HEAT_TRANSFER_FACTOR: 50, IRON_WATER_HEAT_TRANSFER_FACTOR: 1000, IRON_AIR_HEAT_TRANSFER_FACTOR: 50, WATER_AIR_HEAT_TRANSFER_FACTOR: 50, AIR_TO_SURROUNDING_AIR_HEAT_TRANSFER_FACTOR: 10000 }; // Maps for obtaining transfer constants for a given thermal element. var HEAT_TRANSFER_FACTORS_FOR_BRICK = {}; HEAT_TRANSFER_FACTORS_FOR_BRICK[EnergyContainerCategory.IRON] = HeatTransfer.BRICK_IRON_HEAT_TRANSFER_FACTOR; HEAT_TRANSFER_FACTORS_FOR_BRICK[EnergyContainerCategory.WATER] = HeatTransfer.BRICK_WATER_HEAT_TRANSFER_FACTOR; HEAT_TRANSFER_FACTORS_FOR_BRICK[EnergyContainerCategory.AIR] = HeatTransfer.BRICK_AIR_HEAT_TRANSFER_FACTOR; var HEAT_TRANSFER_FACTORS_FOR_IRON = {}; HEAT_TRANSFER_FACTORS_FOR_IRON[EnergyContainerCategory.BRICK] = HeatTransfer.BRICK_IRON_HEAT_TRANSFER_FACTOR; HEAT_TRANSFER_FACTORS_FOR_IRON[EnergyContainerCategory.WATER] = HeatTransfer.BRICK_WATER_HEAT_TRANSFER_FACTOR; HEAT_TRANSFER_FACTORS_FOR_IRON[EnergyContainerCategory.AIR] = HeatTransfer.BRICK_AIR_HEAT_TRANSFER_FACTOR; var HEAT_TRANSFER_FACTORS_FOR_WATER = {}; HEAT_TRANSFER_FACTORS_FOR_WATER[EnergyContainerCategory.BRICK] = HeatTransfer.BRICK_WATER_HEAT_TRANSFER_FACTOR; HEAT_TRANSFER_FACTORS_FOR_WATER[EnergyContainerCategory.IRON] = HeatTransfer.IRON_WATER_HEAT_TRANSFER_FACTOR; HEAT_TRANSFER_FACTORS_FOR_WATER[EnergyContainerCategory.AIR] = HeatTransfer.WATER_AIR_HEAT_TRANSFER_FACTOR; var HEAT_TRANSFER_FACTORS_FOR_AIR = {}; HEAT_TRANSFER_FACTORS_FOR_AIR[EnergyContainerCategory.BRICK] = HeatTransfer.BRICK_AIR_HEAT_TRANSFER_FACTOR; HEAT_TRANSFER_FACTORS_FOR_AIR[EnergyContainerCategory.IRON] = HeatTransfer.IRON_AIR_HEAT_TRANSFER_FACTOR; HEAT_TRANSFER_FACTORS_FOR_AIR[EnergyContainerCategory.WATER] = HeatTransfer.WATER_AIR_HEAT_TRANSFER_FACTOR; var CONTAINER_CATEGORY_MAP = {}; CONTAINER_CATEGORY_MAP[EnergyContainerCategory.BRICK] = HEAT_TRANSFER_FACTORS_FOR_BRICK; CONTAINER_CATEGORY_MAP[EnergyContainerCategory.IRON] = HEAT_TRANSFER_FACTORS_FOR_IRON; CONTAINER_CATEGORY_MAP[EnergyContainerCategory.WATER] = HEAT_TRANSFER_FACTORS_FOR_WATER; CONTAINER_CATEGORY_MAP[EnergyContainerCategory.AIR] = HEAT_TRANSFER_FACTORS_FOR_AIR; HeatTransfer.CONTAINER_CATEGORY_MAP = CONTAINER_CATEGORY_MAP; HeatTransfer.getHeatTransferFactor = function(container1, container2) { return this.CONTAINER_CATEGORY_MAP[container1][container2]; }; Constants.HeatTransfer = HeatTransfer; /************************************************************************* ** ** ** ENERGY SYSTEMS SIMULATION ** ** ** *************************************************************************/ var EnergySystemsSimulation = {}; EnergySystemsSimulation.OFFSET_BETWEEN_ELEMENTS = new Vector2(0, -0.4); EnergySystemsSimulation.ENERGY_SOURCE_POSITION = new Vector2(-0.15, 0); EnergySystemsSimulation.ENERGY_CONVERTER_POSITION = new Vector2(-0.025, 0); EnergySystemsSimulation.ENERGY_USER_POSITION = new Vector2( 0.089, 0); EnergySystemsSimulation.TRANSITION_DURATION = 0.5; Constants.EnergySystemsSimulation = EnergySystemsSimulation; var EnergySystemsSimulationView = {}; EnergySystemsSimulationView.DEFAULT_MVT_SCALE = Constants.ENERGY_SYSTEMS_MVT_SCALE_FACTOR; EnergySystemsSimulationView.SHORT_SCREEN_MVT_SCALE = EnergySystemsSimulationView.DEFAULT_MVT_SCALE * 0.74; Constants.EnergySystemsSimulationView = EnergySystemsSimulationView; /************************************************************************* ** ** ** FAUCET AND WATER ** ** ** *************************************************************************/ var Faucet = {}; Faucet.OFFSET_FROM_CENTER_TO_WATER_ORIGIN = new Vector2(0.065, 0.08); // was 0.065, 0.08 Faucet.FALLING_ENERGY_CHUNK_VELOCITY = 0.09; // In meters/second. Faucet.MAX_WATER_WIDTH = 0.015; // In meters. Faucet.ENERGY_CHUNK_TRANSFER_DISTANCE_RANGE = range({ min: 0.05, max: 0.06 }); Faucet.MAX_DISTANCE_FROM_FAUCET_TO_BOTTOM_OF_WATER = 0.5; // In meters. Constants.Faucet = Faucet; var WaterDrop = {}; WaterDrop.MAX_DISTANCE_FROM_FAUCET_TO_BOTTOM_OF_WATER = Faucet.MAX_DISTANCE_FROM_FAUCET_TO_BOTTOM_OF_WATER; // In meters. WaterDrop.ACCELERATION_DUE_TO_GRAVITY = new Vector2(0, -0.15); Constants.WaterDrop = WaterDrop; /************************************************************************* ** ** ** ELECTRICAL GENERATOR ** ** ** *************************************************************************/ var ElectricalGenerator = {}; // Attributes of the wheel and generator. ElectricalGenerator.WHEEL_MOMENT_OF_INERTIA = 5; // In kg. ElectricalGenerator.RESISTANCE_CONSTANT = 3; // Controls max speed and rate of slow down, empirically determined. ElectricalGenerator.MAX_ROTATIONAL_VELOCITY = Math.PI / 2; // In radians/sec, empirically determined. // Images used to represent this model element in the view. ElectricalGenerator.WHEEL_CENTER_OFFSET = new Vector2(0, 0.03); ElectricalGenerator.LEFT_SIDE_OF_WHEEL_OFFSET = new Vector2(-0.03, 0.03); ElectricalGenerator.CONNECTOR_OFFSET = new Vector2(0.057, -0.04); ElectricalGenerator.WHEEL_RADIUS = 0.0388; //WHEEL_HUB_IMAGE.getWidth() / 2; ElectricalGenerator.WIRE_OFFSET = new Vector2(0.0185, -0.015); // Offsets used to create the paths followed by the energy chunks. ElectricalGenerator.START_OF_WIRE_CURVE_OFFSET = new Vector2(ElectricalGenerator.WHEEL_CENTER_OFFSET).add(0.01, -0.05); ElectricalGenerator.WIRE_CURVE_POINT_1_OFFSET = new Vector2(ElectricalGenerator.WHEEL_CENTER_OFFSET).add(0.015, -0.06); ElectricalGenerator.WIRE_CURVE_POINT_2_OFFSET = new Vector2(ElectricalGenerator.WHEEL_CENTER_OFFSET).add(0.03, -0.07); ElectricalGenerator.CENTER_OF_CONNECTOR_OFFSET = ElectricalGenerator.CONNECTOR_OFFSET; Constants.ElectricalGenerator = ElectricalGenerator; /************************************************************************* ** ** ** LIGHT BULB ** ** ** *************************************************************************/ var LightBulb = {}; // Offsets need for creating the path followed by the energy chunks. These // were empirically determined based on images, will need to change if the // images are changed. LightBulb.OFFSET_TO_LEFT_SIDE_OF_WIRE = new Vector2(-0.04, -0.04); LightBulb.OFFSET_TO_LEFT_SIDE_OF_WIRE_BEND = new Vector2(-0.02, -0.04); LightBulb.OFFSET_TO_FIRST_WIRE_CURVE_POINT = new Vector2(-0.01, -0.0375); LightBulb.OFFSET_TO_SECOND_WIRE_CURVE_POINT = new Vector2(-0.001, -0.025); LightBulb.OFFSET_TO_THIRD_WIRE_CURVE_POINT = new Vector2(-0.0005, -0.0175); LightBulb.OFFSET_TO_BOTTOM_OF_CONNECTOR = new Vector2( 0, -0.01); LightBulb.OFFSET_TO_RADIATE_POINT = new Vector2( 0, 0.066); // Miscellaneous other constants. LightBulb.RADIATED_ENERGY_CHUNK_MAX_DISTANCE = 0.5; LightBulb.THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT = range({ min: 2, max: 2.5 }); LightBulb.ENERGY_TO_FULLY_LIGHT = Constants.MAX_ENERGY_PRODUCTION_RATE; LightBulb.LIGHT_CHUNK_LIT_BULB_RADIUS = 0.1; // In meters. LightBulb.LIGHT_CHANGE_RATE = 0.5; // In proportion per second. LightBulb.FILAMENT_WIDTH = 0.03; Constants.LightBulb = LightBulb; var IncandescentLightBulbView = {}; IncandescentLightBulbView.RAY_COLOR = '#fff71c'; Constants.IncandescentLightBulbView = IncandescentLightBulbView; var FluorescentLightBulbView = {}; FluorescentLightBulbView.RAY_COLOR = '#FFFEB0'; Constants.FluorescentLightBulbView = FluorescentLightBulbView; /************************************************************************* ** ** ** LIGHT RAY VIEW ** ** ** *************************************************************************/ var LightRayView = {}; LightRayView.LINE_WIDTH = 2; LightRayView.SEARCH_ITERATIONS = 15; LightRayView.FADE_COEFFICIENT_IN_AIR = 0.005; Constants.LightRayView = LightRayView; /************************************************************************* ** ** ** SUN ** ** ** *************************************************************************/ var Sun = {}; Sun.RADIUS = 0.02; // In meters, apparent size, not (obviously) actual size. Sun.OFFSET_TO_CENTER_OF_SUN = new Vector2(-0.05, 0.12); Sun.ENERGY_CHUNK_EMISSION_PERIOD = 0.11; // In seconds. Sun.MAX_DISTANCE_OF_E_CHUNKS_FROM_SUN = 0.5; // In meters. // Constants that control the nature of the emission sectors. These are // used to make emission look random yet still have a fairly steady rate // within each sector. One sector is intended to point at the solar panel. Sun.NUM_EMISSION_SECTORS = 10; Sun.EMISSION_SECTOR_SPAN = 2 * Math.PI / Sun.NUM_EMISSION_SECTORS; Sun.EMISSION_SECTOR_OFFSET = Sun.EMISSION_SECTOR_SPAN * 0.71; // Used to tweak sector positions to make sure solar panel gets consistent flow of E's. Constants.Sun = Sun; var SunView = {}; SunView.INNER_FILL_COLOR = '#fff'; SunView.OUTER_FILL_COLOR = '#ffd700'; SunView.GRADIENT_END = 0.7; SunView.LINE_WIDTH = 1; SunView.LINE_COLOR = '#ffff00'; SunView.RAY_COLOR = SunView.LINE_COLOR; SunView.RAY_DISTANCE = 1000; SunView.PANEL_WIDTH = 0.065; SunView.PANEL_HEIGHT = 0.088; SunView.PANEL_OFFSET = new Vector2(-0.05, 0.035); SunView.SLIDER_WIDTH = 8; SunView.SLIDER_BG_FILL_TOP = '#444'; SunView.SLIDER_BG_FILL_BOTTOM = '#fff'; SunView.SLIDER_BG_LINE_COLOR = '#333'; SunView.SLIDER_HANDLE_FILL_COLOR = '#fff'; SunView.SLIDER_HANDLE_LINE_COLOR = '#888'; SunView.LABEL_COLOR = '#000'; SunView.LABEL_FONT = '16px Arial'; SunView.LABEL_TITLE_FONT = '20px Arial'; SunView.FONT_FAMILY = 'Arial'; SunView.LABEL_FONT_SIZE = 16; SunView.TITLE_FONT_SIZE = 20; SunView.CLOUD_ICON_WIDTH = 50; Constants.SunView = SunView; /************************************************************************* ** ** ** CLOUD ** ** ** *************************************************************************/ var Cloud = {}; Cloud.CLOUD_WIDTH = 0.035; // In meters, though obviously not to scale. Empirically determined. Cloud.CLOUD_HEIGHT = 0.0191; // In meters, the height that, given the width above, will maintain the right aspect ratio for the image Constants.Cloud = Cloud; /************************************************************************* ** ** ** SOLAR PANEL ** ** ** *************************************************************************/ var SolarPanel = {}; SolarPanel.SOLAR_PANEL_OFFSET = new Vector2(0, 0.044); SolarPanel.CONVERTER_OFFSET = new Vector2(0.015, -0.040); SolarPanel.CONNECTOR_OFFSET = new Vector2(0.057, -0.040); SolarPanel.WIRE_OFFSET = new Vector2(SolarPanel.CONVERTER_OFFSET).add(0.009, 0.024); SolarPanel.POST_OFFSET = new Vector2(SolarPanel.CONVERTER_OFFSET).add(0, 0.04); // Constants used for creating the path followed by the energy chunks. // Many of these numbers were empirically determined based on the images, // and will need updating if the images change. SolarPanel.OFFSET_TO_CONVERGENCE_POINT = new Vector2(SolarPanel.CONVERTER_OFFSET.x, 0.01); SolarPanel.OFFSET_TO_FIRST_CURVE_POINT = new Vector2(SolarPanel.CONVERTER_OFFSET.x, -0.025); SolarPanel.OFFSET_TO_SECOND_CURVE_POINT = new Vector2(SolarPanel.CONVERTER_OFFSET.x + 0.005, -0.033); SolarPanel.OFFSET_TO_THIRD_CURVE_POINT = new Vector2(SolarPanel.CONVERTER_OFFSET.x + 0.015, SolarPanel.CONNECTOR_OFFSET.y); SolarPanel.OFFSET_TO_CONNECTOR_CENTER = SolarPanel.CONNECTOR_OFFSET; // Inter chunk spacing time for when the chunks reach the 'convergence // point' at the bottom of the solar panel. It is intended to // approximately match the rate at which the sun emits energy chunks. SolarPanel.MIN_INTER_CHUNK_TIME = 1 / (Sun.ENERGY_CHUNK_EMISSION_PERIOD * Sun.NUM_EMISSION_SECTORS); SolarPanel.PANEL_IMAGE_ASPECT_RATIO = 2.069149; // determined empirically and will have to change if the image changes SolarPanel.PANEL_IMAGE_WIDTH = 0.15; SolarPanel.PANEL_IMAGE_HEIGHT = SolarPanel.PANEL_IMAGE_WIDTH / SolarPanel.PANEL_IMAGE_ASPECT_RATIO; SolarPanel.PANEL_IMAGE_BOUNDS = new Rectangle( -SolarPanel.PANEL_IMAGE_WIDTH / 2, -SolarPanel.PANEL_IMAGE_HEIGHT / 2, SolarPanel.PANEL_IMAGE_WIDTH, SolarPanel.PANEL_IMAGE_HEIGHT ); // This would seem to make the opposite of what I would think is the // absoprtion width (0.8 * width) would be, but it's purposely not // the entire surface area of the panel so it will not always hit // at the left and top edges. var absorptionZoneWidth = SolarPanel.PANEL_IMAGE_WIDTH * 0.2; SolarPanel.ABSORPTION_SHAPE = new PiecewiseCurve() .moveTo(SolarPanel.PANEL_IMAGE_BOUNDS.left(), SolarPanel.PANEL_IMAGE_BOUNDS.bottom()) .lineTo(SolarPanel.PANEL_IMAGE_BOUNDS.right() - absorptionZoneWidth, SolarPanel.PANEL_IMAGE_BOUNDS.top()) .lineTo(SolarPanel.PANEL_IMAGE_BOUNDS.right(), SolarPanel.PANEL_IMAGE_BOUNDS.top()) .lineTo(SolarPanel.PANEL_IMAGE_BOUNDS.left() + absorptionZoneWidth, SolarPanel.PANEL_IMAGE_BOUNDS.bottom()) //.lineTo(SolarPanel.PANEL_IMAGE_BOUNDS.left(), SolarPanel.PANEL_IMAGE_BOUNDS.bottom()) .close(); Constants.SolarPanel = SolarPanel; /************************************************************************* ** ** ** BEAKER HEATER ** ** ** *************************************************************************/ var BeakerHeater = {}; BeakerHeater.HEATER_ELEMENT_OFFSET = new Vector2(-0.002, 0.022); BeakerHeater.BEAKER_OFFSET = new Vector2( 0, 0.025); BeakerHeater.THERMOMETER_OFFSET = new Vector2( 0.033, 0.035); // Offsets need for creating the path followed by the energy chunks. These // were empirically determined based on images, will need to change if the // images are changed. BeakerHeater.OFFSET_TO_LEFT_SIDE_OF_WIRE = new Vector2(-0.04, -0.04); BeakerHeater.OFFSET_TO_LEFT_SIDE_OF_WIRE_BEND = new Vector2(-0.02, -0.04); BeakerHeater.OFFSET_TO_FIRST_WIRE_CURVE_POINT = new Vector2(-0.01, -0.0375); BeakerHeater.OFFSET_TO_SECOND_WIRE_CURVE_POINT = new Vector2(-0.001, -0.025); BeakerHeater.OFFSET_TO_THIRD_WIRE_CURVE_POINT = new Vector2(-0.0005, -0.0175); BeakerHeater.OFFSET_TO_BOTTOM_OF_CONNECTOR = new Vector2(0, -0.01); BeakerHeater.OFFSET_TO_CONVERSION_POINT = new Vector2(0, 0.012); BeakerHeater.BEAKER_WIDTH = 0.075; // In meters. BeakerHeater.BEAKER_HEIGHT = BeakerHeater.BEAKER_WIDTH * 0.9; BeakerHeater.HEATING_ELEMENT_ENERGY_CHUNK_VELOCITY = 0.0075; // In meters/sec, quite slow. BeakerHeater.HEATER_ELEMENT_HEIGHT = 60 / Constants.ENERGY_SYSTEMS_MVT_SCALE_FACTOR; BeakerHeater.MAX_HEAT_GENERATION_RATE = 5000; // Joules/sec, not connected to incoming energy. BeakerHeater.HEAT_ENERGY_CHANGE_RATE = 0.5; // In proportion per second. BeakerHeater.RADIATED_ENERGY_CHUNK_TRAVEL_DISTANCE = 0.2; // In meters. BeakerHeater.RADIATION_NUM_DIRECTION_CHANGES = 4; // Empirically chosen BeakerHeater.RADIATION_NOMINAL_TRAVEL_VECTOR = new Vector2(0, BeakerHeater.RADIATED_ENERGY_CHUNK_TRAVEL_DISTANCE / BeakerHeater.RADIATION_NUM_DIRECTION_CHANGES); Constants.BeakerHeater = BeakerHeater; /************************************************************************* ** ** ** TEAPOT ** ** ** *************************************************************************/ var Teapot = {}; Teapot.TEAPOT_OFFSET = new Vector2(0.0, 0.015); // Offsets and other constants used for energy paths. These are mostly // empirically determined and coordinated with the image. Teapot.SPOUT_BOTTOM_OFFSET = new Vector2(0.03, 0.02); Teapot.SPOUT_TIP_OFFSET = new Vector2(0.25, 0.3); Teapot.ACTUAL_SPOUT_TIP_OFFSET = new Vector2(0.0475, 0.045); // I made this because the one above is nowhere close to the tip Teapot.DISTANT_TARGET_OFFSET = new Vector2(1, 1); Teapot.WATER_SURFACE_HEIGHT_OFFSET = 0; // From teapot position, in meters. Teapot.THERMAL_ENERGY_CHUNK_Y_ORIGIN = -0.05; // In meters, must be coordinated with heater position. Teapot.THERMAL_ENERGY_CHUNK_X_ORIGIN_RANGE = range({ min: -0.015, max: 0.015 }); // In meters, must be coordinated with heater position. // Miscellaneous other constants. Teapot.MAX_ENERGY_CHANGE_RATE = Constants.MAX_ENERGY_PRODUCTION_RATE / 5; // In joules/second Teapot.COOLING_CONSTANT = 0.1; // Controls rate at which tea pot cools down, empirically determined. Teapot.COOL_DOWN_COMPLETE_THRESHOLD = 30; // In joules/second Teapot.ENERGY_CHUNK_TRANSFER_DISTANCE_RANGE = range({ min: 0.12, max: 0.15 }); Teapot.ENERGY_CHUNK_WATER_TO_SPOUT_TIME = 0.7; // Used to keep chunks evenly spaced. Teapot.MECHANICAL_ENERGY_CHUNK_RATE = 0.2; // Proportion of energy chunks that will be mechanical vs thermal Constants.Teapot = Teapot; var TeapotView = {}; TeapotView.BURNER_WIDTH = 120; // Empirically determined. TeapotView.BURNER_HEIGHT = TeapotView.BURNER_WIDTH * 0.80; TeapotView.BURNER_OPENING_HEIGHT = TeapotView.BURNER_WIDTH * 0.2; TeapotView.NUM_STEAM_PARTICLES = 400; TeapotView.STEAM_PARTICLE_RADIUS_RANGE = range({ min: 8, max: 40 }); TeapotView.STEAM_PARTICLE_LIFE_RANGE = range({ min: 2.5, max: 3.5 }); // Seconds to live TeapotView.STEAM_PARTICLE_COLOR = BeakerView.STEAM_PARTICLE_COLOR; TeapotView.STEAM_PARTICLE_MAX_ALPHA = 0.2; TeapotView.STEAM_PARTICLE_FADE_POINT = 0.8; // percent of life at which to start fading TeapotView.STEAM_EMISSION_ANGLE = Math.PI / 7; TeapotView.MAX_STEAM_PARTICLE_EMISSION_RATE = 100; // particles per second TeapotView.MAX_STEAM_CLOUD_CENTER_DISTANCE = 140; // pixels, rotated TeapotView.STEAM_CLOUD_RADIUS = 50; TeapotView.STEAM_CLOUD_REPELLANT_FORCE = 0.3; // a velocity modifier TeapotView.STEAM_CLOUD_CONTAINMENT_FORCE = 0.0001; TeapotView.STEAM_PARTICLE_CLOUD_DAMPENING = 0.90; // decelleration Constants.TeapotView = TeapotView; /************************************************************************* ** ** ** BIKER ** ** ** *************************************************************************/ var Biker = {}; Biker.MAX_ANGULAR_VELOCITY_OF_CRANK = 3 * Math.PI; // In radians/sec. Biker.ANGULAR_ACCELERATION = Math.PI / 2; // In radians/(sec^2). Biker.MAX_ENERGY_OUTPUT_WHEN_CONNECTED_TO_GENERATOR = Constants.MAX_ENERGY_PRODUCTION_RATE; // In joules / sec Biker.MAX_ENERGY_OUTPUT_WHEN_RUNNING_FREE = Biker.MAX_ENERGY_OUTPUT_WHEN_CONNECTED_TO_GENERATOR / 5; // In joules / sec Biker.CRANK_TO_REAR_WHEEL_RATIO = 1; Biker.INITIAL_NUM_ENERGY_CHUNKS = 15; Biker.MECHANICAL_TO_THERMAL_CHUNK_RATIO = 5; Biker.REAR_WHEEL_RADIUS = 0.02; // In meters, must be worked out with the image. // Offset of the bike frame center. Most other image offsets are relative // to this one. Biker.FRAME_CENTER_OFFSET = new Vector2(0.0, 0.01); // Offsets used for creating energy chunk paths. These need to be // coordinated with the images. Biker.BIKER_BUTTOCKS_OFFSET = new Vector2(0.02, 0.04); Biker.TOP_TUBE_ABOVE_CRANK_OFFSET = new Vector2(0.007, 0.015); Biker.BIKE_CRANK_OFFSET = new Vector2(0.0052, -0.006); Biker.CENTER_OF_BACK_WHEEL_OFFSET = new Vector2(0.03, -0.01); Biker.BOTTOM_OF_BACK_WHEEL_OFFSET = new Vector2(0.03, -0.03); Biker.NEXT_ENERGY_SYSTEM_OFFSET = new Vector2(0.13, -0.01); Biker.NUM_LEG_IMAGES = 24; Constants.Biker = Biker; var BikerView = {}; BikerView.PANEL_WIDTH = 0.1; BikerView.PANEL_HEIGHT = 0.025; BikerView.PANEL_OFFSET = new Vector2(-BikerView.PANEL_WIDTH / 2, -BikerView.PANEL_HEIGHT - 0.018); BikerView.LABEL_COLOR = '#000'; BikerView.LABEL_FONT = '16px Arial'; BikerView.LABEL_FONT_FAMILY = 'Arial'; BikerView.LABEL_FONT_SIZE = 16; Constants.BikerView = BikerView; return Constants; });