reset: function() {
        // reset the left and right pipe position and scale
        this.leftPipeNode.setMatrix( Matrix3.translation( this.layoutBounds.minX - this.leftPipeXOffest, this.groundY + this.pipeNodeYOffset ) );
        this.leftPipeNode.scale( this.options.pipeScale, this.options.pipeScale, false );
        this.leftPipeBackNode.setMatrix( Matrix3.translation( this.layoutBounds.minX - this.leftPipeXOffest, this.groundY + this.pipeNodeYOffset ) );
        this.leftPipeBackNode.scale( this.options.pipeScale, this.options.pipeScale, false );
        this.rightPipeNode.setMatrix( Matrix3.translation( this.layoutBounds.maxX - this.rightPipeXOffest, this.groundY + this.pipeNodeYOffset ) );
        this.rightPipeNode.scale( this.options.pipeScale, this.options.pipeScale, false );
        this.leftPipeMainHandleNode.setTranslation( this.layoutBounds.minX - 10, this.leftPipeNode.getCenterY() );
        this.rightPipeMainHandleNode.setTranslation( this.layoutBounds.maxX - 50, this.rightPipeNode.getCenterY() );

        // mark pipe as dirty for getting new spline cross sections
        this.pipe.dirty = true;
        this.pipe.createSpline();
        this.updatePipeFlowLineShape();
        this.flowModel.fluxMeter.trigger( 'update' );

        // reset the distance from left/right pipe main drag handle to left/right pipe top/bottom control points
        this.yDiffFromLeftPipeDrageHandleToLeftTopControlPoint = 1.05; //model value
        this.yDiffFromLeftPipeDrageHandleToLeftBottomControlPoint = 1.05;
        this.yDiffFromRightPipeDrageHandleToRightTopControlPoint = 1.05;
        this.yDiffFromRightPipeDrageHandleToRightBottomControlPoint = 1.05;

        // reset the handle positions
        var numControlPoints = this.pipe.controlPoints.length;
        this.controlHandleNodes[ numControlPoints / 2 - 1 ].bottom = this.rightPipeNode.top + 2;
        this.controlHandleNodes[ numControlPoints / 2 ].top = this.rightPipeNode.bottom - 2;
        this.controlHandleNodes[ 0 ].bottom = this.leftPipeNode.top + 2;
        this.controlHandleNodes[ numControlPoints - 1 ].top = this.leftPipeNode.bottom - 2;

        this.gridInjectorNode.setTranslation(
          this.modelViewTransform.modelToViewX( this.gridInjectorX ) - this.gridInjectorNodeXOffset,
          this.modelViewTransform.modelToViewY( this.pipe.getCrossSection( this.gridInjectorX ).yTop ) - this.gridInjectorNodeYOffset );
      }
      this.positionProperty.link( position => {

        // shape used when determining if a given chunk of light energy should be absorbed. It is created at (0,0) relative
        // to the solar panel, so its position needs to be adjusted when the solar panel changes its position. It cannot
        // just use a relative position to the solar panel because energy chunks that are positioned globally need to check
        // to see if they are located within this shape, so it needs a global position as well. The untranslated version of
        // this shape is needed to draw the helper shape node in SolarPanelNode.
        // @private {Shape}
        this.absorptionShape = this.untranslatedAbsorptionShape.transformed( Matrix3.translation( position.x, position.y ) );
      } );
示例#3
0
文件: Util.js 项目: phetsims/scenery
      // attempts to map the bounds specified to the entire testing canvas (minus a fine border), so we can nail down the location quickly
      function idealTransform( bounds ) {
        // so that the bounds-edge doesn't land squarely on the boundary
        var borderSize = 2;

        var scaleX = ( resolution - borderSize * 2 ) / ( bounds.maxX - bounds.minX );
        var scaleY = ( resolution - borderSize * 2 ) / ( bounds.maxY - bounds.minY );
        var translationX = -scaleX * bounds.minX + borderSize;
        var translationY = -scaleY * bounds.minY + borderSize;

        return new Transform3( Matrix3.translation( translationX, translationY ).timesMatrix( Matrix3.scaling( scaleX, scaleY ) ) );
      }
示例#4
0
  QUnit.test( 'Inverse / Multiplication tests', function( assert ) {
    approximateMatrixEqual( assert, Matrix3.IDENTITY.inverted(), Matrix3.IDENTITY, 'I * I = I' );
    approximateMatrixEqual( assert, Matrix3.IDENTITY.timesMatrix( A() ), A(), 'I * A = A' );
    approximateMatrixEqual( assert, A().timesMatrix( Matrix3.IDENTITY ), A(), 'A * I = A' );

    var translation = Matrix3.translation( 2, -5 );
    var rotation = Matrix3.rotation2( Math.PI / 6 );
    var scale = Matrix3.scale( 2, 3 );
    approximateMatrixEqual( assert, translation.timesMatrix( translation.inverted() ), Matrix3.IDENTITY, 'translation inverse check' );
    approximateMatrixEqual( assert, rotation.timesMatrix( rotation.inverted() ), Matrix3.IDENTITY, 'rotation inverse check' );
    approximateMatrixEqual( assert, scale.timesMatrix( scale.inverted() ), Matrix3.IDENTITY, 'scale inverse check' );
    approximateMatrixEqual( assert, A().timesMatrix( A().inverted() ), Matrix3.IDENTITY, 'A inverse check' );
    approximateMatrixEqual( assert, B().timesMatrix( B().inverted() ), Matrix3.IDENTITY, 'B inverse check' );
    approximateMatrixEqual( assert, C().timesMatrix( C().inverted() ), Matrix3.IDENTITY, 'C inverse check' );
  } );
示例#5
0
  QUnit.test( 'Matrix scaling()', function( assert ) {
    var rotation = Matrix3.rotation2( Math.PI / 4 );
    var translation = Matrix3.translation( 20, 30 );
    var scale2 = Matrix3.scaling( 2 );
    var scale2x3y = Matrix3.scaling( 2, 3 );

    // the basics, just to make sure it is working
    assert.equal( scale2.getScaleVector().x, 2, 'normal x scale' );
    assert.equal( scale2.getScaleVector().y, 2, 'normal y scale' );
    assert.equal( scale2x3y.getScaleVector().x, 2, 'normal x scale' );
    assert.equal( scale2x3y.getScaleVector().y, 3, 'normal y scale' );

    var combination = rotation.timesMatrix( scale2 ).timesMatrix( translation );

    approximateEquals( assert, combination.getScaleVector().x, 2, 'rotated x scale' );
    approximateEquals( assert, combination.getScaleVector().y, 2, 'rotated x scale' );
  } );
示例#6
0
    ListenerTestUtils.simpleRectangleTest( function( display, rect, node ) {
      var locationProperty = new Vector2Property( Vector2.ZERO );
      var transform = new Transform3( Matrix3.translation( 5, 3 ).timesMatrix( Matrix3.scale( 2 ) ).timesMatrix( Matrix3.rotation2( Math.PI / 4 ) ) );

      // Starts at 5,3
      locationProperty.link( function( location ) {
        rect.translation = transform.transformPosition2( location );
      } );

      var listener = new DragListener( {
        locationProperty: locationProperty,
        transform: transform
      } );
      rect.addInputListener( listener );

      ListenerTestUtils.mouseMove( display, 10, 10 );
      ListenerTestUtils.mouseDown( display, 10, 10 );
      ListenerTestUtils.mouseMove( display, 20, 15 );
      ListenerTestUtils.mouseUp( display, 20, 15 );
      assert.equal( rect.x, 15, '[x] Started at 5, moved by 10' );
      assert.equal( rect.y, 8, '[y] Started at 3, moved by 5' );
    } );
示例#7
0
文件: Util.js 项目: phetsims/scenery
    canvasAccurateBounds: function( renderToContext, options ) {
      // how close to the actual bounds do we need to be?
      var precision = ( options && options.precision ) ? options.precision : 0.001;

      // 512x512 default square resolution
      var resolution = ( options && options.resolution ) ? options.resolution : 128;

      // at 1/16x default, we want to be able to get the bounds accurately for something as large as 16x our initial resolution
      // divisible by 2 so hopefully we avoid more quirks from Canvas rendering engines
      var initialScale = ( options && options.initialScale ) ? options.initialScale : ( 1 / 16 );

      var minBounds = Bounds2.NOTHING;
      var maxBounds = Bounds2.EVERYTHING;

      var canvas = document.createElement( 'canvas' );
      canvas.width = resolution;
      canvas.height = resolution;
      var context = canvas.getContext( '2d' );

      if ( debugChromeBoundsScanning ) {
        $( window ).ready( function() {
          var header = document.createElement( 'h2' );
          $( header ).text( 'Bounds Scan' );
          $( '#display' ).append( header );
        } );
      }

      // TODO: Don't use Transform3 unless it is necessary
      function scan( transform ) {
        // save/restore, in case the render tries to do any funny stuff like clipping, etc.
        context.save();
        transform.matrix.canvasSetTransform( context );
        renderToContext( context );
        context.restore();

        var data = context.getImageData( 0, 0, resolution, resolution );
        var minMaxBounds = Util.scanBounds( data, resolution, transform );

        function snapshotToCanvas( snapshot ) {
          var canvas = document.createElement( 'canvas' );
          canvas.width = resolution;
          canvas.height = resolution;
          var context = canvas.getContext( '2d' );
          context.putImageData( snapshot, 0, 0 );
          $( canvas ).css( 'border', '1px solid black' );
          $( window ).ready( function() {
            //$( '#display' ).append( $( document.createElement( 'div' ) ).text( 'Bounds: ' +  ) );
            $( '#display' ).append( canvas );
          } );
        }

        // TODO: remove after debug
        if ( debugChromeBoundsScanning ) {
          snapshotToCanvas( data );
        }

        context.clearRect( 0, 0, resolution, resolution );

        return minMaxBounds;
      }

      // attempts to map the bounds specified to the entire testing canvas (minus a fine border), so we can nail down the location quickly
      function idealTransform( bounds ) {
        // so that the bounds-edge doesn't land squarely on the boundary
        var borderSize = 2;

        var scaleX = ( resolution - borderSize * 2 ) / ( bounds.maxX - bounds.minX );
        var scaleY = ( resolution - borderSize * 2 ) / ( bounds.maxY - bounds.minY );
        var translationX = -scaleX * bounds.minX + borderSize;
        var translationY = -scaleY * bounds.minY + borderSize;

        return new Transform3( Matrix3.translation( translationX, translationY ).timesMatrix( Matrix3.scaling( scaleX, scaleY ) ) );
      }

      var initialTransform = new Transform3();
      // make sure to initially center our object, so we don't miss the bounds
      initialTransform.append( Matrix3.translation( resolution / 2, resolution / 2 ) );
      initialTransform.append( Matrix3.scaling( initialScale ) );

      var coarseBounds = scan( initialTransform );

      minBounds = minBounds.union( coarseBounds.minBounds );
      maxBounds = maxBounds.intersection( coarseBounds.maxBounds );

      var tempMin;
      var tempMax;
      var refinedBounds;

      // minX
      tempMin = maxBounds.minY;
      tempMax = maxBounds.maxY;
      while ( isFinite( minBounds.minX ) && isFinite( maxBounds.minX ) && Math.abs( minBounds.minX - maxBounds.minX ) > precision ) {
        // use maximum bounds except for the x direction, so we don't miss things that we are looking for
        refinedBounds = scan( idealTransform( new Bounds2( maxBounds.minX, tempMin, minBounds.minX, tempMax ) ) );

        if ( minBounds.minX <= refinedBounds.minBounds.minX && maxBounds.minX >= refinedBounds.maxBounds.minX ) {
          // sanity check - break out of an infinite loop!
          if ( debugChromeBoundsScanning ) {
            console.log( 'warning, exiting infinite loop!' );
            console.log( 'transformed "min" minX: ' + idealTransform( new Bounds2( maxBounds.minX, maxBounds.minY, minBounds.minX, maxBounds.maxY ) ).transformPosition2( p( minBounds.minX, 0 ) ) );
            console.log( 'transformed "max" minX: ' + idealTransform( new Bounds2( maxBounds.minX, maxBounds.minY, minBounds.minX, maxBounds.maxY ) ).transformPosition2( p( maxBounds.minX, 0 ) ) );
          }
          break;
        }

        minBounds = minBounds.withMinX( Math.min( minBounds.minX, refinedBounds.minBounds.minX ) );
        maxBounds = maxBounds.withMinX( Math.max( maxBounds.minX, refinedBounds.maxBounds.minX ) );
        tempMin = Math.max( tempMin, refinedBounds.maxBounds.minY );
        tempMax = Math.min( tempMax, refinedBounds.maxBounds.maxY );
      }

      // maxX
      tempMin = maxBounds.minY;
      tempMax = maxBounds.maxY;
      while ( isFinite( minBounds.maxX ) && isFinite( maxBounds.maxX ) && Math.abs( minBounds.maxX - maxBounds.maxX ) > precision ) {
        // use maximum bounds except for the x direction, so we don't miss things that we are looking for
        refinedBounds = scan( idealTransform( new Bounds2( minBounds.maxX, tempMin, maxBounds.maxX, tempMax ) ) );

        if ( minBounds.maxX >= refinedBounds.minBounds.maxX && maxBounds.maxX <= refinedBounds.maxBounds.maxX ) {
          // sanity check - break out of an infinite loop!
          if ( debugChromeBoundsScanning ) {
            console.log( 'warning, exiting infinite loop!' );
          }
          break;
        }

        minBounds = minBounds.withMaxX( Math.max( minBounds.maxX, refinedBounds.minBounds.maxX ) );
        maxBounds = maxBounds.withMaxX( Math.min( maxBounds.maxX, refinedBounds.maxBounds.maxX ) );
        tempMin = Math.max( tempMin, refinedBounds.maxBounds.minY );
        tempMax = Math.min( tempMax, refinedBounds.maxBounds.maxY );
      }

      // minY
      tempMin = maxBounds.minX;
      tempMax = maxBounds.maxX;
      while ( isFinite( minBounds.minY ) && isFinite( maxBounds.minY ) && Math.abs( minBounds.minY - maxBounds.minY ) > precision ) {
        // use maximum bounds except for the y direction, so we don't miss things that we are looking for
        refinedBounds = scan( idealTransform( new Bounds2( tempMin, maxBounds.minY, tempMax, minBounds.minY ) ) );

        if ( minBounds.minY <= refinedBounds.minBounds.minY && maxBounds.minY >= refinedBounds.maxBounds.minY ) {
          // sanity check - break out of an infinite loop!
          if ( debugChromeBoundsScanning ) {
            console.log( 'warning, exiting infinite loop!' );
          }
          break;
        }

        minBounds = minBounds.withMinY( Math.min( minBounds.minY, refinedBounds.minBounds.minY ) );
        maxBounds = maxBounds.withMinY( Math.max( maxBounds.minY, refinedBounds.maxBounds.minY ) );
        tempMin = Math.max( tempMin, refinedBounds.maxBounds.minX );
        tempMax = Math.min( tempMax, refinedBounds.maxBounds.maxX );
      }

      // maxY
      tempMin = maxBounds.minX;
      tempMax = maxBounds.maxX;
      while ( isFinite( minBounds.maxY ) && isFinite( maxBounds.maxY ) && Math.abs( minBounds.maxY - maxBounds.maxY ) > precision ) {
        // use maximum bounds except for the y direction, so we don't miss things that we are looking for
        refinedBounds = scan( idealTransform( new Bounds2( tempMin, minBounds.maxY, tempMax, maxBounds.maxY ) ) );

        if ( minBounds.maxY >= refinedBounds.minBounds.maxY && maxBounds.maxY <= refinedBounds.maxBounds.maxY ) {
          // sanity check - break out of an infinite loop!
          if ( debugChromeBoundsScanning ) {
            console.log( 'warning, exiting infinite loop!' );
          }
          break;
        }

        minBounds = minBounds.withMaxY( Math.max( minBounds.maxY, refinedBounds.minBounds.maxY ) );
        maxBounds = maxBounds.withMaxY( Math.min( maxBounds.maxY, refinedBounds.maxBounds.maxY ) );
        tempMin = Math.max( tempMin, refinedBounds.maxBounds.minX );
        tempMax = Math.min( tempMax, refinedBounds.maxBounds.maxX );
      }

      if ( debugChromeBoundsScanning ) {
        console.log( 'minBounds: ' + minBounds );
        console.log( 'maxBounds: ' + maxBounds );
      }

      var result = new Bounds2(
        // Do finite checks so we don't return NaN
        ( isFinite( minBounds.minX ) && isFinite( maxBounds.minX ) ) ? ( minBounds.minX + maxBounds.minX ) / 2 : Number.POSITIVE_INFINITY,
        ( isFinite( minBounds.minY ) && isFinite( maxBounds.minY ) ) ? ( minBounds.minY + maxBounds.minY ) / 2 : Number.POSITIVE_INFINITY,
        ( isFinite( minBounds.maxX ) && isFinite( maxBounds.maxX ) ) ? ( minBounds.maxX + maxBounds.maxX ) / 2 : Number.NEGATIVE_INFINITY,
        ( isFinite( minBounds.maxY ) && isFinite( maxBounds.maxY ) ) ? ( minBounds.maxY + maxBounds.maxY ) / 2 : Number.NEGATIVE_INFINITY
      );

      // extra data about our bounds
      result.minBounds = minBounds;
      result.maxBounds = maxBounds;
      result.isConsistent = maxBounds.containsBounds( minBounds );
      result.precision = Math.max(
        Math.abs( minBounds.minX - maxBounds.minX ),
        Math.abs( minBounds.minY - maxBounds.minY ),
        Math.abs( minBounds.maxX - maxBounds.maxX ),
        Math.abs( minBounds.maxY - maxBounds.maxY )
      );

      // return the average
      return result;
    },
define( require => {
  'use strict';

  // modules
  const BooleanProperty = require( 'AXON/BooleanProperty' );
  const circuitConstructionKitCommon = require( 'CIRCUIT_CONSTRUCTION_KIT_COMMON/circuitConstructionKitCommon' );
  const ConventionalCurrentArrowNode = require( 'CIRCUIT_CONSTRUCTION_KIT_COMMON/view/ConventionalCurrentArrowNode' );
  const ElectronChargeNode = require( 'SCENERY_PHET/ElectronChargeNode' );
  const Image = require( 'SCENERY/nodes/Image' );
  const Matrix3 = require( 'DOT/Matrix3' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Util = require( 'DOT/Util' );

  // constants
  const ELECTRON_CHARGE_NODE = new ElectronChargeNode( {

    // Electrons are transparent to convey they are a representation rather than physical objects
    // Workaround for https://github.com/phetsims/circuit-construction-kit-dc/issues/160
    sphereOpacity: 0.75,
    minusSignOpacity: 0.75,

    // selected so an electron will exactly fit the width of a wire
    scale: 0.78
  } ).toDataURLImageSynchronous();

  const ARROW_NODE = new ConventionalCurrentArrowNode( Tandem.rootTandem.createTandem( 'arrowNode' ) )
    .toDataURLImageSynchronous();

  const ARROW_OFFSET = Matrix3.translation( -ARROW_NODE.width / 2, -ARROW_NODE.height / 2 );
  const HALF_ROTATION = Matrix3.rotation2( Math.PI );

  // scratch matrix that is used to set values to scenery
  const NODE_MATRIX = new Matrix3();

  // Below this amperage, no conventional current will be rendered.
  const CONVENTIONAL_CHARGE_THRESHOLD = 1E-6;

  // position the electron--note the offsets that were used to make it look exactly centered, see
  // https://github.com/phetsims/circuit-construction-kit-dc/issues/104
  const ELECTRON_OFFSET = Matrix3.translation( -ELECTRON_CHARGE_NODE.width / 2 - 0.5, -ELECTRON_CHARGE_NODE.height / 2 - 0.5 );

  class ChargeNode extends Image {

    /**
     * @param {Charge} charge - the model element
     */
    constructor( charge ) {

      const child = charge.charge > 0 ? ARROW_NODE : ELECTRON_CHARGE_NODE;

      super( child.image, {
        pickable: false
      } );

      // @public (read-only) {Charge} - the model depicted by this node
      this.charge = charge;

      this.outsideOfBlackBoxProperty = new BooleanProperty( false );

      // Update the visibility accordingly.  A multilink will not work because the charge circuitElement changes.
      this.updateVisibleListener = this.updateVisible.bind( this );

      // When the model position changes, update the node position
      this.updateTransformListener = this.updateTransform.bind( this );

      charge.changedEmitter.addListener( this.updateTransformListener );
      charge.visibleProperty.link( this.updateVisibleListener );
      this.outsideOfBlackBoxProperty.link( this.updateVisibleListener );

      charge.disposeEmitterCharge.addListener( this.dispose.bind( this ) );

      this.updateTransformListener();
    }

    /**
     * Dispose resources when no longer used.
     * @public
     */
    dispose() {
      this.charge.changedEmitter.removeListener( this.updateTransformListener );
      this.charge.visibleProperty.unlink( this.updateVisibleListener );
      this.outsideOfBlackBoxProperty.unlink( this.updateVisibleListener );
      super.dispose();
    }

    /**
     * @private - update the transform of the charge node
     */
    updateTransform() {
      const charge = this.charge;
      const current = charge.circuitElement.currentProperty.get();

      NODE_MATRIX.set( charge.matrix );

      if ( charge.charge > 0 ) {

        // Rotate if current is running backwards
        ( current < 0 ) && NODE_MATRIX.multiplyMatrix( HALF_ROTATION );

        // Center
        NODE_MATRIX.multiplyMatrix( ARROW_OFFSET );

        // Apply the transform
        this.matrix = NODE_MATRIX;

        let opacity = Util.linear( 0.015, CONVENTIONAL_CHARGE_THRESHOLD, 1, 0, Math.abs( charge.circuitElement.currentProperty.get() ) );
        opacity = Util.clamp( opacity, 0, 1 );
        this.setImageOpacity( opacity );
      }
      else {

        // Set rotation to 0 since electrons should always be upside-up
        NODE_MATRIX.set00( 1 );
        NODE_MATRIX.set01( 0 );
        NODE_MATRIX.set10( 0 );
        NODE_MATRIX.set11( 1 );

        // Center the electrons
        NODE_MATRIX.multiplyMatrix( ELECTRON_OFFSET );

        // Apply the transform
        this.matrix = NODE_MATRIX;
      }
      this.updateVisible();
      this.outsideOfBlackBoxProperty.set( !charge.circuitElement.insideTrueBlackBoxProperty.get() );
    }

    /**
     * @private - update the visibility
     */
    updateVisible() {
      this.visible = this.charge.visibleProperty.get() &&
                     this.outsideOfBlackBoxProperty.get();
    }
  }

  /**
   * Identifies the images used to render this node so they can be prepopulated in the WebGL sprite sheet.
   * @public {Array.<Image>}
   */
  ChargeNode.webglSpriteNodes = [ ELECTRON_CHARGE_NODE, ARROW_NODE ];

  return circuitConstructionKitCommon.register( 'ChargeNode', ChargeNode );
} );
示例#9
0
    intersection: function( ray ) {
      var self = this;
      var result = [];

      // find the rotation that will put our ray in the direction of the x-axis so we can only solve for y=0 for intersections
      var inverseMatrix = Matrix3.rotation2( -ray.direction.angle() ).timesMatrix( Matrix3.translation( -ray.position.x, -ray.position.y ) );

      var p0 = inverseMatrix.timesVector2( this._start );
      var p1 = inverseMatrix.timesVector2( this._control );
      var p2 = inverseMatrix.timesVector2( this._end );

      //(1-t)^2 start + 2(1-t)t control + t^2 end
      var a = p0.y - 2 * p1.y + p2.y;
      var b = -2 * p0.y + 2 * p1.y;
      var c = p0.y;

      var ts = solveQuadraticRootsReal( a, b, c );

      _.each( ts, function( t ) {
        if ( t >= 0 && t <= 1 ) {
          var hitPoint = self.positionAt( t );
          var unitTangent = self.tangentAt( t ).normalized();
          var perp = unitTangent.perpendicular();
          var toHit = hitPoint.minus( ray.position );

          // make sure it's not behind the ray
          if ( toHit.dot( ray.direction ) > 0 ) {
            result.push( {
              distance: toHit.magnitude(),
              point: hitPoint,
              normal: perp.dot( ray.direction ) > 0 ? perp.negated() : perp,
              wind: ray.direction.perpendicular().dot( unitTangent ) < 0 ? 1 : -1
            } );
          }
        }
      } );
      return result;
    },
示例#10
0
    updateDOM: function() {
      var node = this.node;
      var fillElement = this.fillElement;
      var strokeElement = this.strokeElement;

      if ( this.paintDirty ) {
        var borderRadius = Math.min( node._cornerXRadius, node._cornerYRadius );
        var borderRadiusDirty = this.dirtyCornerXRadius || this.dirtyCornerYRadius;

        if ( this.dirtyWidth ) {
          fillElement.style.width = node._rectWidth + 'px';
        }
        if ( this.dirtyHeight ) {
          fillElement.style.height = node._rectHeight + 'px';
        }
        if ( borderRadiusDirty ) {
          fillElement.style[ Features.borderRadius ] = borderRadius + 'px'; // if one is zero, we are not rounded, so we do the min here
        }
        if ( this.dirtyFill ) {
          fillElement.style.backgroundColor = node.getCSSFill();
        }

        if ( this.dirtyStroke ) {
          // update stroke presence
          if ( node.hasStroke() ) {
            strokeElement.style.borderStyle = 'solid';
          }
          else {
            strokeElement.style.borderStyle = 'none';
          }
        }

        if ( node.hasStroke() ) {
          // since we only execute these if we have a stroke, we need to redo everything if there was no stroke previously.
          // the other option would be to update stroked information when there is no stroke (major performance loss for fill-only rectangles)
          var hadNoStrokeBefore = !this.hadStroke;

          if ( hadNoStrokeBefore || this.dirtyWidth || this.dirtyLineWidth ) {
            strokeElement.style.width = ( node._rectWidth - node.getLineWidth() ) + 'px';
          }
          if ( hadNoStrokeBefore || this.dirtyHeight || this.dirtyLineWidth ) {
            strokeElement.style.height = ( node._rectHeight - node.getLineWidth() ) + 'px';
          }
          if ( hadNoStrokeBefore || this.dirtyLineWidth ) {
            strokeElement.style.left = ( -node.getLineWidth() / 2 ) + 'px';
            strokeElement.style.top = ( -node.getLineWidth() / 2 ) + 'px';
            strokeElement.style.borderWidth = node.getLineWidth() + 'px';
          }

          if ( hadNoStrokeBefore || this.dirtyStroke ) {
            strokeElement.style.borderColor = node.getSimpleCSSStroke();
          }

          if ( hadNoStrokeBefore || borderRadiusDirty || this.dirtyLineWidth || this.dirtyLineOptions ) {
            strokeElement.style[ Features.borderRadius ] = ( node.isRounded() || node.getLineJoin() === 'round' ) ? ( borderRadius + node.getLineWidth() / 2 ) + 'px' : '0';
          }
        }
      }

      // shift the element vertically, postmultiplied with the entire transform.
      if ( this.transformDirty || this.dirtyX || this.dirtyY ) {
        scratchMatrix.set( this.getTransformMatrix() );
        var translation = Matrix3.translation( node._rectX, node._rectY );
        scratchMatrix.multiplyMatrix( translation );
        translation.freeToPool();
        scenery.Util.applyPreparedTransform( scratchMatrix, this.fillElement, this.forceAcceleration );
      }

      // clear all of the dirty flags
      this.setToCleanState();
      this.cleanPaintableState();
      this.transformDirty = false;
    },