define( function( require ) {
  'use strict';

  // modules
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var DragListener = require( 'SCENERY/listeners/DragListener' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Util = require( 'DOT/Util' );
  var Vector2 = require( 'DOT/Vector2' );

  /**
   * This is the drag handler for the capacitor plate separation
   * property. Plate separation is a vertical quantity, so we're dragging along the y axis. Other axes are ignored.
   * @constructor
   *
   * @param {Capacitor} capacitor
   * @param {CLBModelViewTransform3D} modelViewTransform
   * @param {Range} valueRange
   * @param {Tandem} tandem
   */
  function PlateSeparationDragHandler( capacitor, modelViewTransform, valueRange, tandem ) {
    // @private {Vector2}
    var clickYOffset = new Vector2( 0, 0 );

    DragListener.call( this, {
      allowTouchSnag: false,
      tandem: tandem,
      start: function( event ) {
        var pMouse = event.pointer.point;
        var pOrigin = modelViewTransform.modelToViewXYZ( 0, -( capacitor.plateSeparationProperty.value / 2 ), 0 );
        clickYOffset = pMouse.y - pOrigin.y;
      },
      drag: function( event ) {
        var pMouse = event.pointer.point;
        var yView = pMouse.y - clickYOffset;

        var separation = Util.clamp( 2 * modelViewTransform.viewToModelDeltaXY( 0, -yView ).y,
          valueRange.min, valueRange.max );

        // Discretize the plate separation to integral values by scaling m -> mm, rounding, and un-scaling.
        capacitor.plateSeparationProperty.value = Util.roundSymmetric( 5e3 * separation ) / 5e3;
      }
    } );
  }

  capacitorLabBasics.register( 'PlateSeparationDragHandler', PlateSeparationDragHandler );

  return inherit( DragListener, PlateSeparationDragHandler );
} );
define( function( require ) {
  'use strict';

  // modules
  var CapacitanceModel = require( 'CAPACITOR_LAB_BASICS/capacitance/model/CapacitanceModel' );
  var CapacitanceScreenView = require( 'CAPACITOR_LAB_BASICS/capacitance/view/CapacitanceScreenView' );
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var CLBConstants = require( 'CAPACITOR_LAB_BASICS/common/CLBConstants' );
  var CLBModelViewTransform3D = require( 'CAPACITOR_LAB_BASICS/common/model/CLBModelViewTransform3D' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Property = require( 'AXON/Property' );
  var Screen = require( 'JOIST/Screen' );

  // strings
  var screenCapacitanceString = require( 'string!CAPACITOR_LAB_BASICS/screen.capacitance' );

  // images
  var capacitorIconImage = require( 'mipmap!CAPACITOR_LAB_BASICS/capacitance_screen_icon.png' );

  /**
   * @constructor
   *
   * @param {Property.<boolean>} switchUsedProperty - whether switch has been changed by user. Affects both screens.
   * @param {Tandem} tandem
   */
  function CapacitanceScreen( switchUsedProperty, tandem ) {

    var options = {
      name: screenCapacitanceString,
      backgroundColorProperty: new Property( CLBConstants.SCREEN_VIEW_BACKGROUND_COLOR ),
      homeScreenIcon: new Image( capacitorIconImage ),
      tandem: tandem
    };

    Screen.call( this,
      function() { return new CapacitanceModel( switchUsedProperty, new CLBModelViewTransform3D(), tandem.createTandem( 'model' ) ); },
      function( model ) { return new CapacitanceScreenView( model, tandem.createTandem( 'view' ) ); },
      options );
  }

  capacitorLabBasics.register( 'CapacitanceScreen', CapacitanceScreen );

  return inherit( Screen, CapacitanceScreen );
} );
define( function( require ) {
  'use strict';

  // modules
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Property = require( 'AXON/Property' );

  /**
   * @constructor
   *
   * @param {Property.<boolean>} visibleProperty - model property that determines if the entire meter is visible.
   * @param {Property.<number>} valueProperty - property containing model quantity to display
   */
  function BarMeter( visibleProperty, valueProperty ) {
    assert && assert( visibleProperty instanceof Property );
    assert && assert( valueProperty instanceof Property );

    // @public {Property.<number>}
    this.valueProperty = valueProperty;

    // @public {Property.<boolean>}
    this.visibleProperty = visibleProperty;
  }

  capacitorLabBasics.register( 'BarMeter', BarMeter );

  return inherit( Object, BarMeter, {

    /**
     * Reset the BarMeter
     * @public
     */
    reset: function() {
      this.visibleProperty.reset();
    }
  } );
} );
define( function( require ) {
  'use strict';

  // modules
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );

  // constants
  var MILLIMETERS_PER_METER = 1000;

  var UnitsUtils = {
    /**
     * Utility function to convert from meters to millimeters.
     * @public
     *
     * @param {number} d
     * @returns {number}
     */
    metersToMillimeters: function( d ) {
      return d * MILLIMETERS_PER_METER;
    },

    /**
     * Utility function to convert meters squared to millimeters squared.
     * @public
     *
     * @param {number} d
     * @returns {number}
     */
    metersSquaredToMillimetersSquared: function( d ) {
      return d * ( MILLIMETERS_PER_METER * MILLIMETERS_PER_METER );
    }
  };

  capacitorLabBasics.register( 'UnitsUtils', UnitsUtils );

  return UnitsUtils;
} );
define( function( require ) {
  'use strict';

  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var CircuitLocation = require( 'CAPACITOR_LAB_BASICS/common/model/CircuitLocation' );

  var ProbeTarget = {
    NONE: 'NONE',

    OTHER_PROBE: 'OTHER_PROBE',

    BATTERY_TOP_TERMINAL: 'BATTERY_TOP_TERMINAL',

    LIGHT_BULB_TOP: 'LIGHT_BULB_TOP',
    LIGHT_BULB_BOTTOM: 'LIGHT_BULB_BOTTOM',

    CAPACITOR_TOP: 'CAPACITOR_TOP',
    CAPACITOR_BOTTOM: 'CAPACITOR_BOTTOM',

    SWITCH_CONNECTION_TOP: 'SWITCH_CONNECTION_TOP',
    SWITCH_CONNECTION_BOTTOM: 'SWITCH_CONNECTION_BOTTOM',

    WIRE_SWITCH_TOP: 'WIRE_SWITCH_TOP',
    WIRE_SWITCH_BOTTOM: 'WIRE_SWITCH_BOTTOM',

    WIRE_CAPACITOR_TOP: 'WIRE_CAPACITOR_TOP',
    WIRE_CAPACITOR_BOTTOM: 'WIRE_CAPACITOR_BOTTOM',

    WIRE_BATTERY_TOP: 'WIRE_BATTERY_TOP',
    WIRE_BATTERY_BOTTOM: 'WIRE_BATTERY_BOTTOM',

    WIRE_LIGHT_BULB_TOP: 'WIRE_LIGHT_BULB_TOP',
    WIRE_LIGHT_BULB_BOTTOM: 'WIRE_LIGHT_BULB_BOTTOM'
  };

  // @public {Array.<ProbeTarget>}
  ProbeTarget.VALUES = [
    ProbeTarget.NONE,
    ProbeTarget.OTHER_PROBE,
    ProbeTarget.BATTERY_TOP_TERMINAL,
    ProbeTarget.LIGHT_BULB_TOP,
    ProbeTarget.LIGHT_BULB_BOTTOM,
    ProbeTarget.CAPACITOR_TOP,
    ProbeTarget.CAPACITOR_BOTTOM,
    ProbeTarget.SWITCH_TOP,
    ProbeTarget.SWITCH_BOTTOM,
    ProbeTarget.SWITCH_CONNECTION_TOP,
    ProbeTarget.SWITCH_CONNECTION_BOTTOM,
    ProbeTarget.WIRE_CAPACITOR_TOP,
    ProbeTarget.WIRE_CAPACITOR_BOTTOM,
    ProbeTarget.WIRE_BATTERY_TOP,
    ProbeTarget.WIRE_BATTERY_BOTTOM,
    ProbeTarget.WIRE_LIGHT_BULB_TOP,
    ProbeTarget.WIRE_LIGHT_BULB_BOTTOM,
    ProbeTarget.WIRE_SWITCH_TOP,
    ProbeTarget.WIRE_SWITCH_BOTTOM
  ];

  /**
   * Given a probe target, it returns the general {CircuitLocation} which it is part of. This ignores the
   * CIRCUIT_SWITCH locations, only using the CAPACITOR ones for simplicity (since they are connected)
   * @public
   *
   * @param {ProbeTarget} probeTarget
   * @returns {CircuitLocation}
   */
  ProbeTarget.getCircuitLocation = function( probeTarget ) {
    switch ( probeTarget ) {
      case ProbeTarget.BATTERY_TOP_TERMINAL: return CircuitLocation.BATTERY_TOP;
      case ProbeTarget.LIGHT_BULB_TOP: return CircuitLocation.LIGHT_BULB_TOP;
      case ProbeTarget.LIGHT_BULB_BOTTOM: return CircuitLocation.LIGHT_BULB_BOTTOM;
      case ProbeTarget.CAPACITOR_TOP: return CircuitLocation.CAPACITOR_TOP;
      case ProbeTarget.CAPACITOR_BOTTOM: return CircuitLocation.CAPACITOR_BOTTOM;
      case ProbeTarget.SWITCH_TOP: return CircuitLocation.CAPACITOR_TOP;
      case ProbeTarget.SWITCH_BOTTOM: return CircuitLocation.CAPACITOR_BOTTOM;
      case ProbeTarget.SWITCH_CONNECTION_TOP: return CircuitLocation.CAPACITOR_TOP;
      case ProbeTarget.SWITCH_CONNECTION_BOTTOM: return CircuitLocation.CAPACITOR_BOTTOM;
      case ProbeTarget.WIRE_CAPACITOR_TOP: return CircuitLocation.CAPACITOR_TOP;
      case ProbeTarget.WIRE_CAPACITOR_BOTTOM: return CircuitLocation.CAPACITOR_BOTTOM;
      case ProbeTarget.WIRE_BATTERY_TOP: return CircuitLocation.BATTERY_TOP;
      case ProbeTarget.WIRE_BATTERY_BOTTOM: return CircuitLocation.BATTERY_BOTTOM;
      case ProbeTarget.WIRE_LIGHT_BULB_TOP: return CircuitLocation.LIGHT_BULB_TOP;
      case ProbeTarget.WIRE_LIGHT_BULB_BOTTOM: return CircuitLocation.LIGHT_BULB_BOTTOM;
      case ProbeTarget.WIRE_SWITCH_TOP: return CircuitLocation.CAPACITOR_TOP;
      case ProbeTarget.WIRE_SWITCH_BOTTOM: return CircuitLocation.CAPACITOR_BOTTOM;
      default: throw new Error( 'Unsupported probe target (no circuit location for it): ' + probeTarget );
    }
  };

  // Verify that enum is immutable without runtime penalty in production code
  if ( assert ) {
    Object.freeze( ProbeTarget );
  }

  capacitorLabBasics.register( 'ProbeTarget', ProbeTarget );

  return ProbeTarget;
} );
define( function( require ) {
  'use strict';

  // modules
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var CircuitLocation = require( 'CAPACITOR_LAB_BASICS/common/model/CircuitLocation' );
  var CircuitState = require( 'CAPACITOR_LAB_BASICS/common/model/CircuitState' );
  var CLBConstants = require( 'CAPACITOR_LAB_BASICS/common/CLBConstants' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LightBulb = require( 'CAPACITOR_LAB_BASICS/common/model/LightBulb' );
  var ParallelCircuit = require( 'CAPACITOR_LAB_BASICS/common/model/ParallelCircuit' );
  var Property = require( 'AXON/Property' );
  var Vector3 = require( 'DOT/Vector3' );

  // During exponential voltage drop, circuit voltage crosses this threshold,
  // below which we no longer call discharge() so I and V don't tail off forever.
  var MIN_VOLTAGE = 1e-3; // Volts. Minimum readable value on voltmeter.

  /**
   * @constructor
   *
   * @param {CircuitConfig} config
   * @param {Tandem} tandem
   */
  function LightBulbCircuit( config, tandem ) {

    var self = this;

    var bulbLocation = new Vector3(
      CLBConstants.BATTERY_LOCATION.x + config.capacitorXSpacing + CLBConstants.LIGHT_BULB_X_SPACING,
      CLBConstants.BATTERY_LOCATION.y + config.capacitorYSpacing,
      CLBConstants.BATTERY_LOCATION.z
    );

    // @public {LightBulb}
    this.lightBulb = new LightBulb( bulbLocation, config.modelViewTransform );

    ParallelCircuit.call( this, config, tandem );

    // Make sure that the charges are correct when the battery is reconnected to the circuit.
    this.circuitConnectionProperty.link( function( circuitConnection ) {
      /*
       * When disconnecting the battery, set the disconnected plate charge to whatever the total plate charge was with
       * the battery connected.  Need to do this before changing the plate voltages property.
       */
      if ( circuitConnection !== CircuitState.BATTERY_CONNECTED ) {
        self.disconnectedPlateChargeProperty.set( self.getTotalCharge() );
      }
      self.updatePlateVoltages();

      // if light bulb connected, reset values for transient calculations
      if ( circuitConnection === CircuitState.LIGHT_BULB_CONNECTED ) {
        self.capacitor.transientTime = 0;
        self.capacitor.voltageAtSwitchClose = self.capacitor.plateVoltageProperty.value;
      }
    } );

    // Allow the capacitor to discharge when adjusting the plate geometry without the.
    Property.multilink( [ this.capacitor.plateSizeProperty, this.capacitor.plateSeparationProperty ],
      function() {
        if ( Math.abs( self.capacitor.plateVoltageProperty.value ) > MIN_VOLTAGE ) {
          self.capacitor.discharge( self.lightBulb.resistance, 0 );
        }
      } );
  }

  capacitorLabBasics.register( 'LightBulbCircuit', LightBulbCircuit );

  return inherit( ParallelCircuit, LightBulbCircuit, {

    /**
     * LightBulbCircuit model update function
     * @public
     * @override
     *
     * @param {number} dt time step in seconds
     */
    step: function( dt ) {

      // Step through common circuit components
      ParallelCircuit.prototype.step.call( this, dt );

      // Discharge the capacitor when it is in parallel with the light bulb,
      // but don't allow the voltage to taper to zero forever.
      // This is both for performance, and for better timing control.
      // The current arrows should start fading when the voltmeter reading drops
      // below MIN_VOLTAGE.
      if ( this.circuitConnectionProperty.value === CircuitState.LIGHT_BULB_CONNECTED ) {
        if ( Math.abs( this.capacitor.plateVoltageProperty.value ) > MIN_VOLTAGE ) {
          this.capacitor.discharge( this.lightBulb.resistance, dt );
        }

        else {
          this.capacitor.plateVoltageProperty.set( 0 );
          this.currentAmplitudeProperty.set( 0 );
          this.previousTotalCharge = 0; // This fixes #130
        }
      }

    },

    /**
     * Updates the plate voltage, depending on whether the battery is connected.
     * Null check required because superclass calls this method from its constructor.
     * Remember to call this method at the end of this class' constructor.
     * @public
     */
    updatePlateVoltages: function() {
      // If the battery is connected, the voltage is equal to the battery voltage
      if ( this.circuitConnectionProperty !== undefined ) {
        if ( this.circuitConnectionProperty.value === CircuitState.BATTERY_CONNECTED ) {
          this.capacitor.plateVoltageProperty.value = this.battery.voltageProperty.value;
        }
        // If circuit is open, use V = Q/C
        else if ( this.circuitConnectionProperty.value === CircuitState.OPEN_CIRCUIT ) {
          this.capacitor.plateVoltageProperty.value =
            this.disconnectedPlateChargeProperty.value / this.capacitor.capacitanceProperty.value;
        }
        // the capacitor is discharging, but plate geometry is changing at the same time so we need
        // to update the parameters of the transient discharge equation parameters
        else if ( this.circuitConnectionProperty.value === CircuitState.LIGHT_BULB_CONNECTED ) {
          this.capacitor.updateDischargeParameters();
        }
      }
    },

    /**
     * Assert that location is either CircuitLocation.LIGHT_BULB_TOP or BOTTOM.
     * @private
     *
     * @param {CircuitLocation} location
     */
    validateLocation: function( location ) {

      assert && assert( location === CircuitLocation.LIGHT_BULB_TOP || location === CircuitLocation.LIGHT_BULB_BOTTOM,
        'location should be LIGHT_BULB_TOP or LIGHT_BULB_BOTTOM, received ' + location );
    },

    /**
     * Update the current amplitude depending on the circuit connection.  If the capacitor is connected to the light
     * bulb, find the current by I = V / R.  Otherwise, current is found by dQ/dT.
     * @public
     *
     * @param {number} dt
     */
    updateCurrentAmplitude: function( dt ) {

      // if the circuit is connected to the light bulb, I = V / R
      if ( this.circuitConnectionProperty.value === CircuitState.LIGHT_BULB_CONNECTED ) {
        var current = this.capacitor.plateVoltageProperty.value / this.lightBulb.resistance;

        // The cutoff is doubled here for #58
        if ( Math.abs( current ) < 2 * MIN_VOLTAGE / this.lightBulb.resistance ) {
          current = 0;
        }

        this.currentAmplitudeProperty.set( current );
      }

      // otherise, I = dQ/dT
      else {
        ParallelCircuit.prototype.updateCurrentAmplitude.call( this, dt );
      }
    }
  } );
} );
define( function( require ) {
  'use strict';

  // Modules
  var BooleanProperty = require( 'AXON/BooleanProperty' );
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var Circle = require( 'SCENERY/nodes/Circle' );
  var CircuitState = require( 'CAPACITOR_LAB_BASICS/common/model/CircuitState' );
  var CircuitSwitchDragHandler = require( 'CAPACITOR_LAB_BASICS/common/view/drag/CircuitSwitchDragHandler' );
  var CLBConstants = require( 'CAPACITOR_LAB_BASICS/common/CLBConstants' );
  var ConnectionNode = require( 'CAPACITOR_LAB_BASICS/common/view/ConnectionNode' );
  var HingePointNode = require( 'CAPACITOR_LAB_BASICS/common/view/HingePointNode' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' );
  var ShadedSphereNode = require( 'SCENERY_PHET/ShadedSphereNode' );
  var Vector2 = require( 'DOT/Vector2' );
  var WireNode = require( 'CAPACITOR_LAB_BASICS/common/view/WireNode' );

  // Images
  var switchCueArrowImage = require( 'image!CAPACITOR_LAB_BASICS/switch_cue_arrow.png' );

  // Constants
  var SWITCH_CUE_ARROW_WIDTH = 25;
  var SWITCH_CUE_ARROW_OFFSET = new Vector2( -80, -250 ); // View coords

  /**
   * @constructor
   *
   * @param {CircuitSwitch} circuitSwitch
   * @param {CLBModelViewTransform3D} modelViewTransform
   * @param {Property.<boolean>} switchLockedProperty
   * @param {Tandem} tandem
   */
  function SwitchNode( circuitSwitch, modelViewTransform, switchLockedProperty, tandem ) {

    assert && assert( circuitSwitch.connections.length === 2 || circuitSwitch.connections.length === 3,
      'circuitSwitch should have 2 or three connections only' );

    Node.call( this, { tandem: tandem } );
    var self = this;

    // @public {CircuitSwitch}
    this.circuitSwitch = circuitSwitch;

    // @private {CLBModelViewTransform3D}
    this.modelViewTransform = modelViewTransform;

    // @private {WireNode}
    this.wireSwitchNode = new WireNode( circuitSwitch.switchWire, tandem.createTandem( 'wireSwitchNode' ) );
    this.wireSwitchNode.cursor = 'pointer';

    // add a shaded sphere to the end of the wire node to represent a connection point at the end of the switch.
    var shadedSphereNode = new ShadedSphereNode( 2 * CLBConstants.CONNECTION_POINT_RADIUS ); // Diameter

    // Dashed circle on tip of switch used as a contact indicator
    var tipCircle = new Circle( CLBConstants.CONNECTION_POINT_RADIUS, {
      lineWidth: 2,
      lineDash: [ 3, 3 ],
      stroke: PhetColorScheme.RED_COLORBLIND
    } );

    var endPoint = circuitSwitch.switchSegment.endPointProperty.value;

    shadedSphereNode.translation = modelViewTransform.modelToViewPosition( endPoint );
    this.wireSwitchNode.addChild( shadedSphereNode );

    tipCircle.translation = modelViewTransform.modelToViewPosition( endPoint );
    this.wireSwitchNode.addChild( tipCircle );

    // add the hinge
    var hingeNode = new HingePointNode();
    hingeNode.translation = modelViewTransform.modelToViewPosition( circuitSwitch.hingePoint );

    // @public {Node} create connection points and clickable areas
    this.connectionAreaNodes = [];

    var userControlledProperty = new BooleanProperty( false );

    userControlledProperty.link( function( controlled ) {
      tipCircle.fill = controlled ? 'yellow' : null;
    } );

    var dragHandler = new CircuitSwitchDragHandler( self, switchLockedProperty, userControlledProperty, tandem.createTandem( 'dragHandler' ) );

    // prefixes for tandem IDs
    var connectionLabels = [ 'battery', 'open', 'lightBulb' ];

    circuitSwitch.connections.forEach( function( connection, index ) {
      var connectionTandem = tandem.createTandem( connectionLabels[ index ] + 'ConnectionNode' );

      // add the clickable area for the connection point
      self.connectionAreaNodes.push( new ConnectionNode( connection, circuitSwitch, modelViewTransform, connectionTandem, dragHandler, switchLockedProperty ) );
    } );

    circuitSwitch.angleProperty.link( function( angle ) {

      // Endpoint, hinge point, and a vector between them
      var hingePoint = circuitSwitch.switchSegment.hingePoint;
      var delta = Vector2.createPolar( CLBConstants.SWITCH_WIRE_LENGTH, angle ).toVector3();

      // Make sure that the shaded sphere snaps to the correct position when connection property changes.
      shadedSphereNode.translation = modelViewTransform.modelToViewPosition( hingePoint.plus( delta ) );
      tipCircle.translation = modelViewTransform.modelToViewPosition( hingePoint.plus( delta ) );

    } );

    // Circuit connection change listener
    circuitSwitch.circuitConnectionProperty.link( function( circuitConnection ) {

      // Solder joint visibility
      if ( circuitConnection === CircuitState.SWITCH_IN_TRANSIT ||
           circuitConnection === CircuitState.OPEN_CIRCUIT ) {
        shadedSphereNode.setVisible( false );
        tipCircle.radius = CLBConstants.CONNECTION_POINT_RADIUS;
      }
      else {
        shadedSphereNode.setVisible( true );
        tipCircle.radius = CLBConstants.CONNECTION_POINT_RADIUS;
      }

      // Connection circle color
      if ( circuitConnection === CircuitState.SWITCH_IN_TRANSIT ) {
        tipCircle.stroke = PhetColorScheme.RED_COLORBLIND;
      }
      else {
        tipCircle.stroke = 'rgb(0,0,0)'; // black when not in transit
      }
    } );

    // Add arrow for a visual cue
    var switchCueArrow = new Image( switchCueArrowImage );
    switchCueArrow.scale( SWITCH_CUE_ARROW_WIDTH / switchCueArrow.bounds.height );
    switchCueArrow.leftTop = this.wireSwitchNode.center;

    // Reflect bottom arrow about the horizontal axis.
    var segment = circuitSwitch.switchSegment;
    if ( segment.endPointProperty.value.y > segment.hingePoint.y ) {
      switchCueArrow.scale( 1, -1 );
    }

    // Since the y-coordinate for the bottom switch is now inverted, a single translation
    // offset conveniently moves the top arrow up and the bottom arrow down.
    switchCueArrow.translate( SWITCH_CUE_ARROW_OFFSET );

    // @public {Image}
    this.switchCueArrow = switchCueArrow;

    this.addChild( switchCueArrow );

    // rendering order important for behavior of click areas and drag handlers
    _.each( this.connectionAreaNodes, function( connectionAreaNode ) {
      self.addChild( connectionAreaNode.backStrokeNode );
    } );
    this.addChild( this.wireSwitchNode );
    this.addChild( hingeNode );
    _.each( this.connectionAreaNodes, function( connectionAreaNode ) {
      self.addChild( connectionAreaNode.highlightNode );
    } );
  }

  capacitorLabBasics.register( 'SwitchNode', SwitchNode );

  return inherit( Node, SwitchNode );

} );
define( function( require ) {
  'use strict';

  // modules
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' );
  var Shape = require( 'KITE/Shape' );
  var Vector2 = require( 'DOT/Vector2' );

  // constants
  // wire is a cubic curve, these are the control point offsets
  var BODY_CONTROL_POINT_OFFSET = new Vector2( 0, 100 );
  var PROBE_CONTROL_POINT_OFFSET = new Vector2( -80, 100 );
  var POSITIVE_WIRE_COLOR = PhetColorScheme.RED_COLORBLIND;
  var NEGATIVE_WIRE_COLOR = 'black';

  /**
   * @param {VoltmeterBodyNode} bodyNode
   * @param {VoltmeterProbeNode} probeNode
   * @param {boolean} isPositive
   * @constructor
   */
  function ProbeWireNode( bodyNode, probeNode, isPositive ) {

    var self = this;

    // @private {VoltmeterBodyNode}
    this.bodyNode = bodyNode;

    // @private {VoltmeterProbeNode}
    this.probeNode = probeNode;

    // @private {Vector2}
    this.bodyControlPointOffset = BODY_CONTROL_POINT_OFFSET;
    this.probeControlPointOffset = PROBE_CONTROL_POINT_OFFSET;

    // @private {Vector2}
    this.bodyConnectionOffset = isPositive ? bodyNode.positiveConnectionOffset : bodyNode.negativeConnectionOffset;
    this.probeConnectionOffset = probeNode.connectionOffset;

    // supertype constructor with lazily passed wire shape.
    Path.call( this, null, {
      stroke: isPositive ? POSITIVE_WIRE_COLOR : NEGATIVE_WIRE_COLOR,
      lineWidth: 3
    } );

    // update wire when body or probe moves
    probeNode.locationProperty.link( function( location ) {
      self.update();
    } );

    bodyNode.bodyLocationProperty.link( function( location ) {
      self.update();
    } );
  }

  capacitorLabBasics.register( 'ProbeWireNode', ProbeWireNode );

  return inherit( Path, ProbeWireNode, {

    /**
     * Update the wire path.
     * @public
     */
    update: function() {

      var pBody = this.getConnectionPoint( this.bodyNode, this.bodyConnectionOffset );
      var pProbe = this.getConnectionPoint( this.probeNode, this.probeConnectionOffset );

      // control points
      var ctrl1 = new Vector2( pBody.x + this.bodyControlPointOffset.x, pBody.y + this.bodyControlPointOffset.y );
      var ctrl2 = new Vector2( pProbe.x + this.probeControlPointOffset.x, pProbe.y + this.probeControlPointOffset.y );

      this.setShape( new Shape().moveToPoint( pBody ).cubicCurveToPoint( ctrl1, ctrl2, pProbe ) );
    },

    /**
     * Get the connection point for either the voltmeter body or probe.  Adds the node location to the offset connection
     * point vector for a given node.
     * @public
     *
     * @param {VoltmeterBodyNode|VoltmeterProbeNode} node
     * @param {Vector2} connectionOffset
     * @returns {Vector2}
     */
    getConnectionPoint: function( node, connectionOffset ) {
      return node.translation.plus( connectionOffset );
    }
  } );
} );
define( function( require ) {
  'use strict';

  // modules
  var AlignBox = require( 'SCENERY/nodes/AlignBox' );
  var BooleanProperty = require( 'AXON/BooleanProperty' );
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var CLBConstants = require( 'CAPACITOR_LAB_BASICS/common/CLBConstants' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var NumberProperty = require( 'AXON/NumberProperty' );
  var Panel = require( 'SUN/Panel' );
  var PhetioObject = require( 'TANDEM/PhetioObject' );
  var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  var TimerNode = require( 'SCENERY_PHET/TimerNode' );
  var Vector2 = require( 'DOT/Vector2' );
  var VoltmeterNode = require( 'CAPACITOR_LAB_BASICS/common/view/meters/VoltmeterNode' );
  var VoltmeterToolboxPanelIO = require( 'CAPACITOR_LAB_BASICS/common/view/control/VoltmeterToolboxPanelIO' );

  /**
   * @constructor
   *
   * @param {Bounds2} dragBounds
   * @param {TimerNode} timerNode
   * @param {VoltmeterNode} voltmeterNode
   * @param {CLModelViewTransform3D} modelViewTransform
   * @param {Property.<boolean>} isDraggedProperty
   * @param {Property.<boolean>} timerVisibleProperty
   * @param {Property.<boolean>} voltmeterVisibleProperty
   * @param {Tandem} tandem
   * @param {object} Options
   */
  function VoltmeterToolboxPanel( dragBounds, timerNode, voltmeterNode, modelViewTransform, isDraggedProperty,
                                  timerVisibleProperty, voltmeterVisibleProperty, tandem, options ) {
    options = _.extend( {
      includeTimer: true,
      alignGroup: null
    }, options );

    var self = this;

    // @private {VoltmeterNode}
    this.voltmeterNode = voltmeterNode;

    // create the icon for the toolbox.
    var voltmeterScale = options.includeTimer === true ? 0.6 : 1;
    var voltmeterIconNode = VoltmeterNode.createVoltmeterIconNode( voltmeterScale );
    voltmeterIconNode.cursor = 'pointer';

    voltmeterIconNode.addInputListener( SimpleDragHandler.createForwardingListener( function( event ) {
      self.phetioStartEvent( 'dragged' );
      voltmeterVisibleProperty.set( true );

      // initial position of the pointer in the screenView coordinates
      var initialPosition = self.globalToParentPoint( event.pointer.point );

      // make sure that the center of the voltmeter body is offset by the body dimensions
      var offsetPosition = new Vector2( -voltmeterNode.bodyNode.width / 2, -voltmeterNode.bodyNode.height / 2 );

      var voltmeterBodyPosition = initialPosition.plus( offsetPosition );
      voltmeterNode.bodyNode.bodyLocationProperty.set( modelViewTransform.viewToModelPosition( voltmeterBodyPosition ) );

      // start drag from the body node's movable drag handler
      voltmeterNode.bodyNode.movableDragHandler.startDrag( event );
      self.phetioEndEvent();
    } ) );

    // Create timer to be turned into icon
    var secondsProperty = new NumberProperty( 0, {
      tandem: tandem.createTandem( 'secondsProperty' ),
      units: 'seconds'
    } );
    var isRunningProperty = new BooleanProperty( false, {
      tandem: tandem.createTandem( 'isRunningProperty' )
    } );
    var timer = new TimerNode( secondsProperty, isRunningProperty, {
      scale: .60,
      tandem: tandem.createTandem( 'timerNode' )
    } );

    // {Node} Create timer icon. Visible option is used only for reset() in ToolboxPanel.js
    var timerIconNode = timer.rasterized( {
      cursor: 'pointer',
      resolution: 5,
      pickable: true,
      tandem: tandem.createTandem( 'timerIcon' )
    } );

    // Drag listener for event forwarding: timerIcon ---> timerNode
    timerIconNode.addInputListener( new SimpleDragHandler.createForwardingListener( function( event ) {

      // Toggle visibility
      timerVisibleProperty.set( true );

      // Now determine the initial position where this element should move to after it's created, which corresponds
      // to the location of the mouse or touch event.
      var initialPosition = timerNode.globalToParentPoint( event.pointer.point )
        .minus( new Vector2( timerNode.width / 2, timerNode.height * 0.4 ) );

      timerNode.positionProperty.set( initialPosition );

      // Sending through the startDrag from icon to timerNode causes it to receive all subsequent drag events.
      timerNode.timerNodeMovableDragHandler.startDrag( event );
    }, {

      // allow moving a finger (on a touchscreen) dragged across this node to interact with it
      allowTouchSnag: true,
      dragBounds: dragBounds,
      tandem: tandem.createTandem( 'dragHandler' )
    } ) );

    timerVisibleProperty.link( function( visible ) {
      timerIconNode.visible = !visible;
    } );

    // wrap all off this content inside of a node that will hold the input element and its descriptions
    Node.call( this, {
      tandem: tandem,
      phetioType: VoltmeterToolboxPanelIO,
      phetioEventType: PhetioObject.EventType.USER
    } );

    var toolbox = new HBox( { spacing: 13, align: 'center', xMargin: 0 } );
    if ( options.includeTimer ) {
      toolbox.addChild( voltmeterIconNode );
      toolbox.addChild( timerIconNode );
    }
    else {
      toolbox.addChild( voltmeterIconNode );
    }

    // {AlignBox|HBox}
    var content = options.alignGroup ? new AlignBox( toolbox, {
      group: options.alignGroup,
      xAlign: 'center'
    } ) : toolbox;
    this.addChild( new Panel( content, {
      xMargin: 10,
      yMargin: 15,
      align: 'center',
      minWidth: 175,
      fill: CLBConstants.METER_PANEL_FILL,
      tandem: tandem.createTandem( 'voltmeterIconNodePanel' )
    } ) );

    voltmeterVisibleProperty.link( function( voltmeterVisible ) {
      voltmeterIconNode.visible = !voltmeterVisible;
    } );

    // track user control of the voltmeter and place the voltmeter back in the toolbox if bounds collide
    // panel exists for lifetime of sim, no need for dispose
    isDraggedProperty.link( function( isDragged ) {
      if ( !isDragged && self.bounds.intersectsBounds( voltmeterNode.bodyNode.bounds.eroded( 40 ) ) ) {
        voltmeterVisibleProperty.set( false );
      }
    } );
  }

  capacitorLabBasics.register( 'VoltmeterToolboxPanel', VoltmeterToolboxPanel );

  return inherit( Node, VoltmeterToolboxPanel );
} );
define( function( require ) {
  'use strict';

  // modules
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' );
  var Range = require( 'DOT/Range' );
  var RangeWithValue = require( 'DOT/RangeWithValue' );
  var Vector3 = require( 'DOT/Vector3' );

  var NEGATIVE = 'NEGATIVE';
  var POSITIVE = 'POSITIVE';
  var CLBConstants = {

    //----------------------------------------------------------------------------
    // Model
    //----------------------------------------------------------------------------

    EPSILON_0: 8.854E-12, // vacuum permittivity, aka electric constant (Farads/meter)

    // world
    WORLD_DRAG_MARGIN: 0.001, // meters

    // battery
    BATTERY_VOLTAGE_RANGE: new RangeWithValue( -1.5, 1.5, 0 ), // Volts
    BATTERY_VOLTAGE_SNAP_TO_ZERO_THRESHOLD: 0.15, // Volts

    // TODO: Convert to enum
    POLARITY: {
      POSITIVE: POSITIVE,
      NEGATIVE: NEGATIVE,
      VALUES: [ POSITIVE, NEGATIVE ]
    },

    // capacitor
    PLATE_WIDTH_RANGE: new RangeWithValue( 0.01, 0.02, Math.sqrt( 200 / 1000 / 1000 ) ), // meters, with default value at 200 mm^2
    PLATE_HEIGHT: 0.0005, // meters
    PLATE_SEPARATION_RANGE: new RangeWithValue( 0.002, 0.01, 0.006 ), // meters
    CAPACITANCE_RANGE: new Range( 1E-13, 3E-13 ), // Farads

    LIGHT_BULB_X_SPACING: 0.023, // meters
    BATTERY_LOCATION: new Vector3( 0.0065, 0.030, 0 ), // meters
    LIGHT_BULB_RESISTANCE: 5e12, // Ohms. Artificially large to stretch discharge time

    // switch
    SWITCH_WIRE_LENGTH: 0.0064, // in meters
    SWITCH_Y_SPACING: 0.0025, // spacing between circuit components and the switch

    // dielectric constants (dimensionless)
    EPSILON_VACUUM: 1,

    // Wire
    WIRE_THICKNESS: 0.0005, // meters

    //----------------------------------------------------------------------------
    // View
    //----------------------------------------------------------------------------

    // colors used by ConnectionNode
    DISCONNECTED_POINT_COLOR: 'rgb( 200, 230, 255 )',
    DISCONNECTED_POINT_STROKE: PhetColorScheme.RED_COLORBLIND,
    CONNECTION_POINT_HIGHLIGHTED: 'yellow',

    // model-view transform.  Note explicit conversion to radians
    MVT_SCALE: 12000, // scale factor when going from model to view
    MVT_YAW: -45 * Math.PI / 180, // rotation about the vertical axis, right-hand rule determines sign.
    MVT_PITCH: 30 * Math.PI / 180, // rotation about the horizontal axis, right-hand rule determines sign

    DRAG_HANDLE_ARROW_LENGTH: 45, // pixels

    // Model values at which the bar meters have their maximum length in the view.
    // They are currently set to follow a common scale.
    CAPACITANCE_METER_MAX_VALUE: 2.7e-12,
    PLATE_CHARGE_METER_MAX_VALUE: 2.7e-12,
    STORED_ENERGY_METER_MAX_VALUE: 2.7e-12,

    CONNECTION_POINT_RADIUS: 8, // px - dashed circles at switch contacts

    // plate charges
    NUMBER_OF_PLATE_CHARGES: new Range( 1, 625 ),
    NEGATIVE_CHARGE_SIZE: new Dimension2( 7, 2 ),
    ELECTRON_CHARGE: 1.60218E-19,
    MIN_PLATE_CHARGE: 0.01E-12, // absolute minimum plate charge in coulombs

    // E-field
    NUMBER_OF_EFIELD_LINES: new Range( 1, 900 ), // number of lines on smallest plate
    DIRECTION: {
      UP: 'UP',
      DOWN: 'DOWN'
    },

    // capacitance control
    CAPACITANCE_CONTROL_EXPONENT: -13,

    // colors used throughout the sim, each representing a physical quantity
    CAPACITANCE_COLOR: 'rgb( 61, 179, 79 )',
    E_FIELD_COLOR: 'black',
    STORED_ENERGY_COLOR: 'yellow',
    POSITIVE_CHARGE_COLOR: PhetColorScheme.RED_COLORBLIND,
    NEGATIVE_CHARGE_COLOR: 'blue',

    // other common colors
    METER_PANEL_FILL: 'rgb( 255, 245, 237)',
    CONNECTION_POINT_COLOR: 'black',
    PIN_COLOR: 'lightgray',
    SCREEN_VIEW_BACKGROUND_COLOR: 'rgb( 153, 193, 255 )'
  };

  capacitorLabBasics.register( 'CLBConstants', CLBConstants );

  return CLBConstants;
} );
define( function( require ) {
  'use strict';

  // modules
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var Color = require( 'SCENERY/util/Color' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );
  var Vector2 = require( 'DOT/Vector2' );

  var BATTERY_PERSPECTIVE_RATIO = 0.304;
  var BATTERY_MAIN_HEIGHT = 511;
  var BATTERY_MAIN_RADIUS = 158;
  var BATTERY_SECONDARY_RADIUS = BATTERY_MAIN_RADIUS * BATTERY_PERSPECTIVE_RATIO;
  var BATTERY_POSITIVE_TERMINAL_RADIUS = 55;
  var BATTERY_POSITIVE_TERMINAL_HEIGHT = 26;
  var BATTERY_POSITIVE_SIDE_HEIGHT = 152;
  var BATTERY_NEGATIVE_TERMINAL_RADIUS = 81;

  var POSITIVE_COLOR = new Color( 251, 176, 59 );
  var POSITIVE_HIGHLIGHT_COLOR = Color.WHITE;
  var POSITIVE_SHADOW_COLOR = POSITIVE_COLOR;

  var NEGATIVE_COLOR = new Color( 53, 53, 53 );
  var NEGATIVE_HIGHLIGHT_COLOR = new Color( 142, 142, 142 );
  var NEGATIVE_SHADOW_COLOR = Color.BLACK;

  var TERMINAL_COLOR = new Color( 190, 190, 190 );
  var TERMINAL_SIDE_COLOR = new Color( 168, 168, 168 );
  var TERMINAL_HIGHLIGHT_COLOR = new Color( 227, 227, 227 );

  var STROKE_COLOR = new Color( 33, 33, 33 );
  var LINE_WIDTH = 2;

  function createGradient( radius, leftColor, midColor, rightColor ) {
    function blend( highlightLocation, farLocation, location ) {
      var distance = Math.abs( ( farLocation - location ) / ( highlightLocation - farLocation ) );
      return distance * distance;
    }
    return new LinearGradient( -radius, 0, radius, 0 )
      .addColorStop( 0, leftColor )
      .addColorStop( 0.1, Color.interpolateRGBA( leftColor, midColor, blend( 0.3, 0, 0.1 ) ) )
      .addColorStop( 0.15, Color.interpolateRGBA( leftColor, midColor, blend( 0.3, 0, 0.15 ) ) )
      .addColorStop( 0.2, Color.interpolateRGBA( leftColor, midColor, blend( 0.3, 0, 0.2 ) ) )
      .addColorStop( 0.25, Color.interpolateRGBA( leftColor, midColor, blend( 0.3, 0, 0.25 ) ) )
      .addColorStop( 0.3, midColor )
      .addColorStop( 0.325, Color.interpolateRGBA( rightColor, midColor, blend( 0.3, 0.5, 0.325 ) ) )
      .addColorStop( 0.35, Color.interpolateRGBA( rightColor, midColor, blend( 0.3, 0.5, 0.35 ) ) )
      .addColorStop( 0.375, Color.interpolateRGBA( rightColor, midColor, blend( 0.3, 0.5, 0.375 ) ) )
      .addColorStop( 0.4, Color.interpolateRGBA( rightColor, midColor, blend( 0.3, 0.5, 0.4 ) ) )
      .addColorStop( 0.425, Color.interpolateRGBA( rightColor, midColor, blend( 0.3, 0.5, 0.425 ) ) )
      .addColorStop( 0.45, Color.interpolateRGBA( rightColor, midColor, blend( 0.3, 0.5, 0.45 ) ) )
      .addColorStop( 0.475, Color.interpolateRGBA( rightColor, midColor, blend( 0.3, 0.5, 0.475 ) ) )
      .addColorStop( 0.5, rightColor );
  }

  var POSITIVE_GRADIENT = createGradient( BATTERY_MAIN_RADIUS, POSITIVE_SHADOW_COLOR, POSITIVE_HIGHLIGHT_COLOR, POSITIVE_COLOR );
  var NEGATIVE_GRADIENT = createGradient( BATTERY_MAIN_RADIUS, NEGATIVE_SHADOW_COLOR, NEGATIVE_HIGHLIGHT_COLOR, NEGATIVE_COLOR );
  var TERMINAL_GRADIENT = createGradient( BATTERY_POSITIVE_TERMINAL_RADIUS, TERMINAL_SIDE_COLOR, TERMINAL_HIGHLIGHT_COLOR, TERMINAL_SIDE_COLOR );

  /**
   * @constructor
   *
   * @param {boolean} isPositiveDown
   * @param {Object} [options]
   */
  function BatteryGraphicNode( isPositiveDown, options ) {
    Node.call( this );

    // @private {boolean}
    this.isPositiveDown = isPositiveDown;

    var middleY = isPositiveDown ? ( BATTERY_MAIN_HEIGHT - BATTERY_POSITIVE_SIDE_HEIGHT ) : BATTERY_POSITIVE_SIDE_HEIGHT;
    var terminalTopY = isPositiveDown ? 0 : -BATTERY_POSITIVE_TERMINAL_HEIGHT;
    var terminalRadius = isPositiveDown ? BATTERY_NEGATIVE_TERMINAL_RADIUS : BATTERY_POSITIVE_TERMINAL_RADIUS;

    var bottomSideShape = new Shape().ellipticalArc( 0, middleY, BATTERY_MAIN_RADIUS, BATTERY_SECONDARY_RADIUS, 0, 0, Math.PI, false )
                                     .ellipticalArc( 0, BATTERY_MAIN_HEIGHT, BATTERY_MAIN_RADIUS, BATTERY_SECONDARY_RADIUS, 0, Math.PI, 0, true )
                                     .close();
    var topSideShape = new Shape().ellipticalArc( 0, middleY, BATTERY_MAIN_RADIUS, BATTERY_SECONDARY_RADIUS, 0, 0, Math.PI, false )
                                  .ellipticalArc( 0, 0, BATTERY_MAIN_RADIUS, BATTERY_SECONDARY_RADIUS, 0, Math.PI, 0, true )
                                  .close();
    var topShape = new Shape().ellipticalArc( 0, 0, BATTERY_MAIN_RADIUS, BATTERY_SECONDARY_RADIUS, 0, 0, Math.PI * 2, false ).close();
    var terminalTopShape = new Shape().ellipticalArc( 0, terminalTopY, terminalRadius, terminalRadius * BATTERY_PERSPECTIVE_RATIO, 0, 0, Math.PI * 2, false ).close();
    var terminalSideShape = new Shape().ellipticalArc( 0, terminalTopY, terminalRadius, terminalRadius * BATTERY_PERSPECTIVE_RATIO, 0, 0, Math.PI, false )
                                     .ellipticalArc( 0, 0, terminalRadius, terminalRadius * BATTERY_PERSPECTIVE_RATIO, 0, Math.PI, 0, true )
                                     .close();

    // @public {Shape}
    this.terminalShape = terminalTopShape;

    var bottomSide = new Path( bottomSideShape, {
      fill: isPositiveDown ? POSITIVE_GRADIENT : NEGATIVE_GRADIENT,
      stroke: STROKE_COLOR,
      lineWidth: LINE_WIDTH
    } );
    var topSide = new Path( topSideShape, {
      fill: isPositiveDown ? NEGATIVE_GRADIENT : POSITIVE_GRADIENT,
      stroke: STROKE_COLOR,
      lineWidth: LINE_WIDTH
    } );
    var top = new Path( topShape, {
      fill: isPositiveDown ? NEGATIVE_COLOR : POSITIVE_COLOR,
      stroke: STROKE_COLOR,
      lineWidth: LINE_WIDTH
    } );
    var terminal = new Path( terminalTopShape, {
      fill: TERMINAL_COLOR,
      stroke: STROKE_COLOR,
      lineWidth: LINE_WIDTH
    } );

    if ( !isPositiveDown ) {
      this.terminalShape = this.terminalShape.shapeUnion( terminalSideShape );
      terminal.addChild( new Path( terminalSideShape, {
        fill: TERMINAL_GRADIENT,
        stroke: STROKE_COLOR,
        lineWidth: LINE_WIDTH
      } ) );
    }

    this.children = [
      top, topSide, bottomSide, terminal
    ];

    this.mutate( options );
  }

  capacitorLabBasics.register( 'BatteryGraphicNode', BatteryGraphicNode );

  inherit( Node, BatteryGraphicNode, {
    /**
     * Returns (in the local coordinate frame) the location of the center-top of the top terminal.
     * @public
     *
     * @returns {Vector2}
     */
    getTopLocation: function() {
      return new Vector2( 0, this.isPositiveDown ? 0 : -BATTERY_POSITIVE_TERMINAL_HEIGHT );
    }
  } );

  // @public {Node}
  BatteryGraphicNode.POSITIVE_UP = new BatteryGraphicNode( false );
  BatteryGraphicNode.POSITIVE_DOWN = new BatteryGraphicNode( true );

  return BatteryGraphicNode;
} );
define( function( require ) {
  'use strict';

  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );

  var CircuitLocation = {
    BATTERY_TOP: 'BATTERY_TOP',
    BATTERY_BOTTOM: 'BATTERY_BOTTOM',
    LIGHT_BULB_TOP: 'LIGHT_BULB_TOP',
    LIGHT_BULB_BOTTOM: 'LIGHT_BULB_BOTTOM',
    CAPACITOR_TOP: 'CAPACITOR_TOP',
    CAPACITOR_BOTTOM: 'CAPACITOR_BOTTOM',
    CIRCUIT_SWITCH_TOP: 'CIRCUIT_SWITCH_TOP',
    CIRCUIT_SWITCH_BOTTOM: 'CIRCUIT_SWITCH_BOTTOM'
  };

  // @public {Array.<CircuitLocation>}
  CircuitLocation.VALUES = [
    CircuitLocation.BATTERY_TOP,
    CircuitLocation.BATTERY_BOTTOM,
    CircuitLocation.LIGHT_BULB_TOP,
    CircuitLocation.LIGHT_BULB_BOTTOM,
    CircuitLocation.CAPACITOR_TOP,
    CircuitLocation.CAPACITOR_BOTTOM,
    CircuitLocation.CIRCUIT_SWITCH_TOP,
    CircuitLocation.CIRCUIT_SWITCH_BOTTOM
  ];

  CircuitLocation.isTop = function( circuitLocation ) {
    assert && assert( _.includes( CircuitLocation.VALUES, circuitLocation ) );
    return circuitLocation === CircuitLocation.BATTERY_TOP ||
           circuitLocation === CircuitLocation.LIGHT_BULB_TOP ||
           circuitLocation === CircuitLocation.CAPACITOR_TOP ||
           circuitLocation === CircuitLocation.CIRCUIT_SWITCH_TOP;
  };

  CircuitLocation.isBattery = function( circuitLocation ) {
    assert && assert( _.includes( CircuitLocation.VALUES, circuitLocation ) );
    return circuitLocation === CircuitLocation.BATTERY_TOP ||
           circuitLocation === CircuitLocation.BATTERY_BOTTOM;
  };

  CircuitLocation.isLightBulb = function( circuitLocation ) {
    assert && assert( _.includes( CircuitLocation.VALUES, circuitLocation ) );
    return circuitLocation === CircuitLocation.LIGHT_BULB_TOP ||
           circuitLocation === CircuitLocation.LIGHT_BULB_BOTTOM;
  };

  CircuitLocation.isCapacitor = function( circuitLocation ) {
    assert && assert( _.includes( CircuitLocation.VALUES, circuitLocation ) );
    return circuitLocation === CircuitLocation.CAPACITOR_TOP ||
           circuitLocation === CircuitLocation.CAPACITOR_BOTTOM;
  };

  // Verify that enum is immutable without runtime penalty in production code
  if ( assert ) {
    Object.freeze( CircuitLocation );
  }

  capacitorLabBasics.register( 'CircuitLocation', CircuitLocation );

  return CircuitLocation;
} );
Example #13
0
define( function( require ) {
  'use strict';

  // modules
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Property = require( 'AXON/Property' );
  var WireShapeCreator = require( 'CAPACITOR_LAB_BASICS/common/model/shapes/WireShapeCreator' );

  /**
   * @param {CLBModelViewTransform3D} modelViewTransform
   * @param {WireSegment[]} segments
   * @param {CircuitLocation} connectionPoint
   *
   * @constructor
   */
  function Wire( modelViewTransform, segments, connectionPoint ) {

    this.segments = segments; // @public
    this.connectionPoint = connectionPoint; // @public
    this.shapeCreator = new WireShapeCreator( this, modelViewTransform ); // @private

    // @public
    this.shapeProperty = new Property( this.shapeCreator.createWireShape() );

    var self = this;

    // Whenever a segment changes, update the shape.
    this.segments.forEach( function( segment ) {
      Property.multilink( [ segment.startPointProperty, segment.endPointProperty ], function() {
        self.shapeProperty.set( self.shapeCreator.createWireShape() );
      } );
    } );
  }

  capacitorLabBasics.register( 'Wire', Wire );

  return inherit( Object, Wire, {

    /**
     * Update all segments of the wire
     * @public
     */
    update: function() {
      this.segments.forEach( function( segment ) {
        segment.update();
      } );
    },

    /**
     * Whether the given shape intersects with the wire.
     * @public
     *
     * @param {Shape} shape
     */
    contacts: function( shape ) {
      return shape.bounds.intersectsBounds( this.shapeProperty.value.bounds ) &&
             shape.shapeIntersection( this.shapeProperty.value ).getNonoverlappingArea() > 0;
    }
  } );
} );
define( function( require ) {
  'use strict';

  // modules
  var BatteryNode = require( 'CAPACITOR_LAB_BASICS/common/view/BatteryNode' );
  var BooleanProperty = require( 'AXON/BooleanProperty' );
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var CapacitorNode = require( 'CAPACITOR_LAB_BASICS/common/view/CapacitorNode' );
  var CircuitState = require( 'CAPACITOR_LAB_BASICS/common/model/CircuitState' );
  var CLBConstants = require( 'CAPACITOR_LAB_BASICS/common/CLBConstants' );
  var CurrentIndicatorNode = require( 'CAPACITOR_LAB_BASICS/common/view/CurrentIndicatorNode' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PlateAreaDragHandleNode = require( 'CAPACITOR_LAB_BASICS/common/view/drag/PlateAreaDragHandleNode' );
  var PlateSeparationDragHandleNode = require( 'CAPACITOR_LAB_BASICS/common/view/drag/PlateSeparationDragHandleNode' );
  var SwitchNode = require( 'CAPACITOR_LAB_BASICS/common/view/SwitchNode' );
  var Vector2 = require( 'DOT/Vector2' );
  var WireNode = require( 'CAPACITOR_LAB_BASICS/common/view/WireNode' );

  /**
   * @constructor
   *
   * @param {CLBModel} model
   * @param {Tandem} tandem
   */

  function CLBCircuitNode( model, tandem ) {

    // Validate number of switches in model
    assert && assert( model.circuit.circuitSwitches.length === 2,
      'This circuit should have two switches: top and bottom.' );

    Node.call( this );
    var self = this;

    // @public {Circuit}
    this.circuit = model.circuit;

    // circuit components

    // @private {BatteryNode}
    this.batteryNode = new BatteryNode( this.circuit.battery, CLBConstants.BATTERY_VOLTAGE_RANGE, tandem.createTandem( 'batteryNode' ) );

    var capacitorNode = new CapacitorNode(
      this.circuit,
      model.modelViewTransform,
      model.plateChargesVisibleProperty,
      model.electricFieldVisibleProperty,
      tandem.createTandem( 'capacitorNode' )
    );

    // @public {Node}
    this.topWireNode = new Node();
    this.bottomWireNode = new Node();

    this.circuit.topWires.forEach( function( topWire ) {
      self.topWireNode.addChild( new WireNode( topWire ) );
    } );
    this.circuit.bottomWires.forEach( function( bottomWire ) {
      self.bottomWireNode.addChild( new WireNode( bottomWire ) );
    } );

    // Don't allow both switches to be controlled at once
    var switchControlledProperty = new BooleanProperty( false );

    // @private {Array.<SwitchNode>}
    this.circuitSwitchNodes = [];
    self.circuitSwitchNodes.push( new SwitchNode(
      this.circuit.circuitSwitches[ 0 ],
      model.modelViewTransform,
      switchControlledProperty,
      tandem.createTandem( 'topSwitchNode' )
    ) );
    self.circuitSwitchNodes.push( new SwitchNode(
      this.circuit.circuitSwitches[ 1 ],
      model.modelViewTransform,
      switchControlledProperty,
      tandem.createTandem( 'bottomSwitchNode' )
    ) );

    // Once the circuit has been built, if the circuit connection has changed, the switch has been used.
    this.circuitSwitchNodes.forEach( function( switchNode ) {
      switchNode.circuitSwitch.circuitConnectionProperty.lazyLink( function( connection ) {
        if ( connection !== switchNode.circuitSwitch.circuitConnectionProperty.initialValue &&
             connection !== CircuitState.SWITCH_IN_TRANSIT ) {
          model.switchUsedProperty.set( true );
        }
      } );
    } );

    // Make the switch "hint" arrows disappear after first use of the switch (#94). This affects both screens
    // because a common reference to the switchUsedProperty is shared between the models.
    model.switchUsedProperty.link( function( switchUsed ) {
      self.circuitSwitchNodes.forEach( function( switchNode ) {
        switchNode.switchCueArrow.setVisible( !switchUsed );
      } );
    } );

    // drag handles
    var plateSeparationDragHandleNode = new PlateSeparationDragHandleNode( this.circuit.capacitor, model.modelViewTransform,
      CLBConstants.PLATE_SEPARATION_RANGE, tandem.createTandem( 'plateSeparationDragHandleNode' ) );
    var plateAreaDragHandleNode = new PlateAreaDragHandleNode( this.circuit.capacitor, model.modelViewTransform,
      CLBConstants.PLATE_WIDTH_RANGE, tandem.createTandem( 'plateAreaDragHandleNode' ) );

    // current indicators
    this.batteryTopCurrentIndicatorNode = new CurrentIndicatorNode(
      this.circuit.currentAmplitudeProperty,
      0,
      model.currentOrientationProperty,
      model.arrowColorProperty,
      model.stepEmitter,
      tandem.createTandem( 'batteryTopCurrentIndicatorNode' ) );
    this.batteryBottomCurrentIndicatorNode = new CurrentIndicatorNode(
      this.circuit.currentAmplitudeProperty,
      Math.PI,
      model.currentOrientationProperty,
      model.arrowColorProperty,
      model.stepEmitter,
      tandem.createTandem( 'batteryBottomCurrentIndicatorNode' ) );

    // rendering order
    this.circuitSwitchNodes.forEach( function( switchNode ) {
      switchNode.connectionAreaNodes.forEach( function( connectionAreaNode ) {
        self.addChild( connectionAreaNode );
      } );
    } );
    this.addChild( this.bottomWireNode );
    this.addChild( this.batteryNode );
    this.addChild( capacitorNode );
    this.addChild( this.topWireNode );
    this.addChild( this.circuitSwitchNodes[ 0 ] );
    this.addChild( this.batteryTopCurrentIndicatorNode );
    this.addChild( this.batteryBottomCurrentIndicatorNode );
    this.addChild( plateSeparationDragHandleNode );
    this.addChild( plateAreaDragHandleNode );
    this.addChild( this.circuitSwitchNodes[ 1 ] );

    // layout

    // battery
    this.batteryNode.center = model.modelViewTransform.modelToViewPosition( this.circuit.battery.location );

    // capacitor
    capacitorNode.center = model.modelViewTransform.modelToViewPosition( this.circuit.capacitor.location );

    // top current indicator
    var x = this.batteryNode.right + ( this.circuitSwitchNodes[ 0 ].left - this.batteryNode.right ) / 2;

    // current indicator offset
    var indicatorOffset = 7 / 2;
    var y = this.topWireNode.bounds.minY + indicatorOffset;
    this.batteryTopCurrentIndicatorNode.translate( x, y );

    // bottom current indicator
    y = this.bottomWireNode.bounds.getMaxY() - indicatorOffset;
    this.batteryBottomCurrentIndicatorNode.translate( x, y );

    // wires shapes are in model coordinate frame, so the nodes live at (0,0) the following does nothing but it
    // explicitly defines the layout.
    this.topWireNode.translation = new Vector2( 0, 0 );
    this.bottomWireNode.translation = new Vector2( 0, 0 );

    // observer for visibility of the current indicators
    model.currentVisibleProperty.link( function( currentIndicatorsVisible ) {
      self.batteryTopCurrentIndicatorNode.setVisible( currentIndicatorsVisible );
      self.batteryBottomCurrentIndicatorNode.setVisible( currentIndicatorsVisible );
    } );
  }

  capacitorLabBasics.register( 'CLBCircuitNode', CLBCircuitNode );

  return inherit( Node, CLBCircuitNode );
} );
define( function( require ) {
  'use strict';

  // modules
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var CLBConstants = require( 'CAPACITOR_LAB_BASICS/common/CLBConstants' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LightBulbShapeCreator = require( 'CAPACITOR_LAB_BASICS/common/model/shapes/LightBulbShapeCreator' );
  var Vector3 = require( 'DOT/Vector3' );

  // constants
  var BULB_BASE_SIZE = new Dimension2( 0.0050, 0.0035 );

  /**
   * @constructor
   *
   * @param {Vector3} location
   * @param {CLModelViewTransform3D} modelViewTransform
   */
  function LightBulb( location, modelViewTransform ) {

    // @public {Vector3} (read-only)
    this.location = location;

    // @public {number} (read-only)
    this.resistance = CLBConstants.LIGHT_BULB_RESISTANCE;

    // @public {LightBulbShapeCreator}
    this.shapeCreator = new LightBulbShapeCreator( this, modelViewTransform );
  }

  capacitorLabBasics.register( 'LightBulb', LightBulb );

  return inherit( Object, LightBulb, {

    /**
     * Does the base shape intersect the top shape of the bulb base?
     * @public
     *
     * @param {Shape} shape
     * @returns {boolean}
     */
    intersectsBulbTopBase: function( shape ) {
      var bulbBaseShape = this.shapeCreator.createTopBaseShape();
      return shape.bounds.intersectsBounds( bulbBaseShape.bounds ) &&
             shape.shapeIntersection( bulbBaseShape ).getNonoverlappingArea() > 0;
    },

    /**
     * Does the base shape intersect the bottom shape of the bulb base?
     * @public
     *
     * @param {Shape} shape
     * @returns {boolean}
     */
    intersectsBulbBottomBase: function( shape ) {
      var bulbBaseShape = this.shapeCreator.createBottomBaseShape();
      return shape.bounds.intersectsBounds( bulbBaseShape.bounds ) &&
             shape.shapeIntersection( bulbBaseShape ).getNonoverlappingArea() > 0;
    },

    /**
     * The top connection point is the top center of light bulb
     * @public
     *
     * @returns {Vector3}
     */
    getTopConnectionPoint: function() {
      return this.location.copy();
    },

    /**
     * The bottom tip of the light bulb base is its leftmost point, since the bulb
     * is rotated 90 degrees clockwise from vertical.
     * @public
     *
     * @returns {Vector3}
     */
    getBottomConnectionPoint: function() {
      return new Vector3( this.location.x - BULB_BASE_SIZE.width * 3 / 5, this.location.y, this.location.z );
    },

    /**
     * Calculate the current flowing through this lightbulb using Ohm's Law, V = I R
     * @public
     *
     * @param {number} voltage - voltage across the resistor
     * @returns {number}
     */
    getCurrent: function( voltage ) {
      return voltage / this.resistance;
    }
  } );
} );
define( function( require ) {
  'use strict';

  // modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var capacitorLabBasics = require( 'CAPACITOR_LAB_BASICS/capacitorLabBasics' );
  var DynamicProperty = require( 'AXON/DynamicProperty' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var MovableDragHandler = require( 'SCENERY_PHET/input/MovableDragHandler' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Property = require( 'AXON/Property' );
  var Vector2 = require( 'DOT/Vector2' );

  // images
  var blackVoltmeterProbeImage = require( 'image!CAPACITOR_LAB_BASICS/probe_black.png' );
  var redVoltmeterProbeImage = require( 'image!CAPACITOR_LAB_BASICS/probe_red.png' );

  /**
   * @constructor
   *
   * @param {Image} image image of the probe
   * @param {Property.<Vector3>} locationProperty property to observer for the probe's location
   * @param {CLBModelViewTransform3D} modelViewTransform model-view transform
   * @param {Bounds2} dragBounds Node bounds in model coordinates
   * @param {Tandem} tandem
   */
  function VoltmeterProbeNode( image, locationProperty, modelViewTransform, dragBounds, tandem ) {

    Node.call( this );
    var self = this;

    // @public {Property.<Vector3>}
    this.locationProperty = locationProperty;

    var imageNode = new Image( image, {
      scale: 0.25
    } );
    this.addChild( imageNode );
    imageNode.translate( -imageNode.bounds.width / 2, 0 );

    // @public {Vector2}
    this.connectionOffset = imageNode.centerBottom; // @public connect wire to bottom center

    // image is vertical, rotate into pseudo-3D perspective after computing the connection offset
    this.rotate( -modelViewTransform.yaw );
    this.connectionOffset.rotate( -modelViewTransform.yaw );

    // update position with model
    locationProperty.link( function( location ) {
      if ( location instanceof Vector2 ) {
        self.translation = modelViewTransform.modelToViewPosition( location.toVector3() );
      }
      else {
        self.translation = modelViewTransform.modelToViewPosition( location );
      }
    } );

    // Don't allow pushing the probes too far to the left, see https://github.com/phetsims/capacitor-lab-basics/issues/202
    var adjustedViewBounds = new Bounds2( 40, 0, dragBounds.maxX - imageNode.width, dragBounds.maxY - 0.4 * imageNode.height );

    // Convert the 3d property to a 2d property for use in the MovableDragHandler
    var location2DProperty = new DynamicProperty( new Property( locationProperty ), {
      bidirectional: true,
      useDeepEquality: true,
      map: function( vector3 ) { return vector3.toVector2(); },
      inverseMap: function( vector2 ) { return vector2.toVector3(); }
    } );

    // Drag handler accounting for boundaries
    this.movableDragHandler = new MovableDragHandler( location2DProperty, {
      tandem: tandem.createTandem( 'dragHandler' ),
      dragBounds: modelViewTransform.viewToModelBounds( adjustedViewBounds ),
      modelViewTransform: modelViewTransform.modelToViewTransform2D,
      useDeepEquality: true
    } );
    this.addInputListener( this.movableDragHandler );

    // set the cursor
    this.cursor = 'pointer';

  }

  capacitorLabBasics.register( 'VoltmeterProbeNode', VoltmeterProbeNode );

  return inherit( Node, VoltmeterProbeNode, {}, {

    /**
     * Factory for a positive VoltmeterProbeNode
     * @public
     *
     * @param {Voltmeter} voltmeter
     * @param {CLBModelViewTransform3D} modelViewTransform
     * @param {Tandem} tandem
     */
    createPositiveVoltmeterProbeNode: function( voltmeter, modelViewTransform, tandem ) {
      return new VoltmeterProbeNode( redVoltmeterProbeImage,
        voltmeter.positiveProbeLocationProperty, modelViewTransform, voltmeter.dragBounds, tandem );
    },

    /**
     * Factory for a positive VoltmeterProbeNode
     * @public
     *
     * @param {Voltmeter} voltmeter
     * @param {CLBModelViewTransform3D} modelViewTransform
     * @param {Tandem} tandem
     */
    createNegativeVoltmeterProbeNode: function( voltmeter, modelViewTransform, tandem ) {
      return new VoltmeterProbeNode( blackVoltmeterProbeImage,
        voltmeter.negativeProbeLocationProperty, modelViewTransform, voltmeter.dragBounds, tandem );
    }
  } );
} );