Skip to content

Commit

Permalink
make optical axis look like it passes through things, #283
Browse files Browse the repository at this point in the history
  • Loading branch information
pixelzoom committed Dec 22, 2021
1 parent 02c6b64 commit d169709
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 15 deletions.
5 changes: 5 additions & 0 deletions js/common/model/GOModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class GOModel {
// target/ image associated with secondPoint
readonly secondTarget: Target;

// optional barrier that may block rays
readonly barrier: Barrier | null;

// elapsed time of light rays animation
readonly lightRaysTimeProperty: NumberProperty;

Expand Down Expand Up @@ -105,6 +108,8 @@ class GOModel {

this.secondTarget = new Target( this.secondPoint.positionProperty, this.optic, this.representationProperty );

this.barrier = options.barrier;

this.lightRaysTimeProperty = new NumberProperty( 0, {
units: 's',
range: new Range( 0, RAYS_ANIMATION_TIME ),
Expand Down
28 changes: 22 additions & 6 deletions js/common/view/GOScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import FocalPointNode from './FocalPointNode.js';
import GOControlPanel from './GOControlPanel.js';
import LabelsNode from './LabelsNode.js';
import LightRaysNode from './LightRaysNode.js';
import OpticalHorizontalAxisNode from './OpticalHorizontalAxisNode.js';
import OpticalAxisNode from './OpticalAxisNode.js';
import OpticShapeRadioButtonGroup from './OpticShapeRadioButtonGroup.js';
import OpticVerticalAxisNode from './OpticVerticalAxisNode.js';
import RepresentationComboBox from './RepresentationComboBox.js';
Expand All @@ -48,6 +48,7 @@ import Optic from '../model/Optic.js';
import TwoFPointNode from './TwoFPointNode.js';
import BooleanProperty from '../../../../axon/js/BooleanProperty.js';
import DragLockedButton from './DragLockedButton.js';
import OpticalAxisForegroundNode from './OpticalAxisForegroundNode.js';

// constants
const ZOOM_RANGE = new RangeWithValue( 1, 3, 3 );
Expand All @@ -72,6 +73,7 @@ class GOScreenView extends ScreenView {
protected readonly visibleProperties: VisibleProperties;
protected readonly modelBoundsProperty: IReadOnlyProperty<Bounds2>;
protected readonly experimentAreaNode: Node;
protected readonly additionalNodesParent: Node;
protected readonly zoomButtonGroup: Node;

private readonly model: GOModel;
Expand Down Expand Up @@ -248,11 +250,19 @@ class GOScreenView extends ScreenView {
visibleProperty: visibleProperties.secondPointVisibleProperty
} );

const opticalHorizontalAxisNode = new OpticalHorizontalAxisNode( model.optic.positionProperty, modelBoundsProperty, modelViewTransform, {
visibleProperty: model.optic.opticalAxisVisibleProperty,
tandem: options.tandem.createTandem( 'opticalHorizontalAxisNodeNode' )
// The complete optical axis, to be put in the background
const opticalAxisNode = new OpticalAxisNode( model.optic.positionProperty, modelBoundsProperty, modelViewTransform, {
visibleProperty: model.optic.opticalAxisVisibleProperty
} );

// The part of the optical axis that appears to be in front of the things. This makes it looks like
// the axis is going through the source object, real/virtual image, etc.
const opticalAxisForegroundNode = new OpticalAxisForegroundNode( model.optic.positionProperty,
model.representationProperty, model.sourceObject.positionProperty, model.firstTarget.positionProperty,
model.barrier, modelBoundsProperty, modelViewTransform, {
visibleProperty: model.optic.opticalAxisVisibleProperty
} );

const opticNode = options.createOpticNode( model.optic, modelBoundsProperty, modelViewTransform, options.tandem );

const opticVerticalAxisNode = new OpticVerticalAxisNode( model.optic, model.raysModeProperty, modelBoundsProperty, modelViewTransform, {
Expand Down Expand Up @@ -304,15 +314,20 @@ class GOScreenView extends ScreenView {
tandem: options.tandem.createTandem( 'twoFPointsNode' )
} );

//TODO this is a hack to allow LensScreenView to add the projection screen etc. in the correct layering order
const additionalNodesParent = new Node();

// Layer for all the Nodes within the "experiment area".
// The experiment area is subject to zoom in/out, so include add all Nodes that need to be zoomed.
const experimentAreaNode = new Node( {
children: [
opticalHorizontalAxisNode,
opticalAxisNode,
sourceObjectNode,
targetNode,
additionalNodesParent,
opticalAxisForegroundNode,
opticNode,
opticVerticalAxisNode,
targetNode,
focalPointsNode,
twoFPointsNode,
lightRays1Node,
Expand Down Expand Up @@ -435,6 +450,7 @@ class GOScreenView extends ScreenView {
this.visibleProperties = visibleProperties; // {VisibleProperties}
this.modelBoundsProperty = modelBoundsProperty; // {DerivedProperty.<Bounds2>}
this.experimentAreaNode = experimentAreaNode; // {Node}
this.additionalNodesParent = additionalNodesParent; // {Node}
this.zoomButtonGroup = zoomButtonGroup; // {Node}
}

Expand Down
108 changes: 108 additions & 0 deletions js/common/view/OpticalAxisForegroundNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright 2021, University of Colorado Boulder

/**
* TODO
*
* @author Chris Malley (PixelZoom, Inc.)
*/

import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
import IProperty from '../../../../axon/js/IProperty.js';
import IReadOnlyProperty from '../../../../axon/js/IReadOnlyProperty.js';
import Property from '../../../../axon/js/Property.js';
import Bounds2 from '../../../../dot/js/Bounds2.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import Shape from '../../../../kite/js/Shape.js';
import merge from '../../../../phet-core/js/merge.js';
import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js';
import { Line } from '../../../../scenery/js/imports.js';
import geometricOptics from '../../geometricOptics.js';
import GOColors from '../GOColors.js';
import GOConstants from '../GOConstants.js';
import Barrier from '../model/Barrier.js';
import Representation from '../model/Representation.js';

type Options = {
visibleProperty: IProperty<boolean>
};

class OpticalAxisForegroundNode extends Line {

/**
* @param opticPositionProperty
* @param representationProperty
* @param sourceObjectPositionProperty
* @param targetPositionProperty
* @param barrier
* @param modelBoundsProperty
* @param modelViewTransform
* @param options
*/
constructor( opticPositionProperty: IReadOnlyProperty<Vector2>,
representationProperty: IReadOnlyProperty<Representation>,
sourceObjectPositionProperty: IReadOnlyProperty<Vector2>,
targetPositionProperty: IReadOnlyProperty<Vector2>,
barrier: Barrier | null,
modelBoundsProperty: IReadOnlyProperty<Bounds2>,
modelViewTransform: ModelViewTransform2,
options?: Options ) {

// create optical axis line, with arbitrary length values.
super( 0, 0, 1, 0, merge( {
stroke: GOColors.opticalAxisStrokeProperty,
lineWidth: GOConstants.AXIS_LINE_WIDTH,
lineDash: GOConstants.AXIS_LINE_DASH
}, options ) );

// set the horizontal extent of the optical axis line
modelBoundsProperty.link( bounds => {
this.setX1( modelViewTransform.modelToViewX( bounds.left ) );
this.setX2( modelViewTransform.modelToViewX( bounds.right ) );
} );

// update y-position of line based on position of optic
opticPositionProperty.link( position => {
const yView = modelViewTransform.modelToViewY( position.y );
this.setY1( yView );
this.setY2( yView );
} );

//TODO this is a hack for dealing with the fact that the Mirror screen doesn't have a ProjectionScreen
let barrierPositionProperty;
if ( barrier ) {
barrierPositionProperty = barrier.positionProperty;
}
else {
barrierPositionProperty = new DerivedProperty( [ modelBoundsProperty ],
( modelBounds: Bounds2 ) => new Vector2( modelBounds.right - 100, modelBounds.centerX ) );
}

// Clip area
Property.multilink(
[ representationProperty, sourceObjectPositionProperty, targetPositionProperty, barrierPositionProperty, modelBoundsProperty ],
( representation: Representation, sourceObjectPosition: Vector2, targetPosition: Vector2, barrierPosition: Vector2, modelBounds: Bounds2 ) => {
let minX;
let maxX;
const minY = modelViewTransform.modelToViewY( modelBounds.minY );
const maxY = modelViewTransform.modelToViewY( modelBounds.maxY );
if ( representation.isObject ) {
minX = modelViewTransform.modelToViewX( sourceObjectPosition.x );
maxX = modelViewTransform.modelToViewX( targetPosition.x );
}
else {
minX = modelViewTransform.modelToViewX( modelBoundsProperty.value.left );
maxX = modelViewTransform.modelToViewX( barrierPosition.x );
}
this.clipArea = Shape.rectangle( minX, minY, maxX - minX, maxY - minY );
} );
}

public dispose(): void {
assert && assert( false, 'dispose is not supported, exists for the lifetime of the sim' );
super.dispose();
}
}

geometricOptics.register( 'OpticalAxisForegroundNode', OpticalAxisForegroundNode );

export default OpticalAxisForegroundNode;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2021, University of Colorado Boulder

/**
* OpticalHorizontalAxisNode is the horizontal line that passes through the geometric center of a lens or mirror.
* OpticalAxisNode is the horizontal line that passes through the geometric center of a lens or mirror.
* It is referred to as 'optical axis', or sometimes as 'principal axis'.
*
* @author Sarah Chang (Swarthmore College)
Expand All @@ -15,17 +15,15 @@ import Vector2 from '../../../../dot/js/Vector2.js';
import merge from '../../../../phet-core/js/merge.js';
import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js';
import { Line } from '../../../../scenery/js/imports.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import geometricOptics from '../../geometricOptics.js';
import GOColors from '../GOColors.js';
import GOConstants from '../GOConstants.js';

type Options = {
visibleProperty: IProperty<boolean>,
tandem: Tandem
visibleProperty: IProperty<boolean>
};

class OpticalHorizontalAxisNode extends Line {
class OpticalAxisNode extends Line {

/**
* @param opticPositionProperty
Expand Down Expand Up @@ -63,6 +61,6 @@ class OpticalHorizontalAxisNode extends Line {
}
}

geometricOptics.register( 'OpticalHorizontalAxisNode', OpticalHorizontalAxisNode );
geometricOptics.register( 'OpticalAxisNode', OpticalAxisNode );

export default OpticalHorizontalAxisNode;
export default OpticalAxisNode;
4 changes: 2 additions & 2 deletions js/lens/view/LensScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ class LensScreenView extends GOScreenView {
// DO NOT instrument for PhET-iO, see https://github.com/phetsims/geometric-optics/issues/269
} );

// Add projection screen and light spots at the bottom of the z-layer.
// Add projection screen and light spots in front of the optical axis.
const lightSourceNodes = new Node( {
children: [ this.projectionScreenNode, lightSpot1Node, lightSpot2Node ],
visibleProperty: new DerivedProperty( [ model.representationProperty ],
( representation: Representation ) => !representation.isObject )
} );
this.experimentAreaNode.insertChild( 0, lightSourceNodes );
this.additionalNodesParent.addChild( lightSourceNodes );

// pdom -traversal order
// Insert projectionScreenNode after zoomButtonGroup.
Expand Down

0 comments on commit d169709

Please sign in to comment.