diff --git a/js/common/ProjectileMotionConstants.js b/js/common/ProjectileMotionConstants.js index 42a7bcf9..d472f3d9 100644 --- a/js/common/ProjectileMotionConstants.js +++ b/js/common/ProjectileMotionConstants.js @@ -168,9 +168,9 @@ const ProjectileMotionConstants = { }, // zooming - MIN_ZOOM: 0.25, + MIN_ZOOM: 1 / 32, MAX_ZOOM: 2, - DEFAULT_ZOOM: 1.0, + DEFAULT_ZOOM: 1, // normal/slow/play/pause/step PLAY_CONTROLS_HORIZONTAL_INSET: 10, diff --git a/js/common/model/DataProbe.js b/js/common/model/DataProbe.js index 75c46787..b036eb09 100644 --- a/js/common/model/DataProbe.js +++ b/js/common/model/DataProbe.js @@ -26,9 +26,10 @@ class DataProbe { * @param {PhetioGroup.} trajectoryGroup * @param {number} dataProbeX - x initial position of the dataProbe * @param {number} dataProbeY - y initial position of the dataProbe + * @param {NumberProperty} zoomProperty - current zoom of the play area * @param {Tandem} tandem */ - constructor( trajectoryGroup, dataProbeX, dataProbeY, tandem ) { + constructor( trajectoryGroup, dataProbeX, dataProbeY, zoomProperty, tandem ) { // @public this.positionProperty = new Vector2Property( new Vector2( dataProbeX, dataProbeY ), { @@ -51,6 +52,9 @@ class DataProbe { phetioDocumentation: 'Whether the dataProbe is out in the play area (false when in toolbox)' } ); + // @private - used to adjust the tolerance of the sensing radius when detecting data points on trajectories. + this.zoomProperty = zoomProperty; + // @public {PhetioGroup.} group of trajectories in the model this.trajectoryGroup = trajectoryGroup; @@ -69,6 +73,15 @@ class DataProbe { this.isActiveProperty.reset(); } + /** + * @private + * @param {Vector2} dataPointPosition + * @returns {boolean} - if the position provided is close enough to the dataProbe position to be sensed. + */ + pointWithinTolerance( dataPointPosition ) { + return dataPointPosition.distance( this.positionProperty.get() ) <= ( SENSING_RADIUS / this.zoomProperty.value ); + } + /** * Checks for if there is a point the dataProbe is close to. If so, updates dataPointProperty * @public @@ -76,14 +89,14 @@ class DataProbe { updateData() { for ( let i = this.trajectoryGroup.count - 1; i >= 0; i-- ) { const currentTrajectory = this.trajectoryGroup.getElement( i ); - if ( currentTrajectory.apexPoint && currentTrajectory.apexPoint.position.distance( this.positionProperty.get() ) <= SENSING_RADIUS ) { // current point shown is apex and it is still within sensing radius + if ( currentTrajectory.apexPoint && this.pointWithinTolerance( currentTrajectory.apexPoint.position ) ) { // current point shown is apex and it is still within sensing radius this.dataPointProperty.set( currentTrajectory.apexPoint ); return; } const point = currentTrajectory.getNearestPoint( this.positionProperty.get().x, this.positionProperty.get().y ); const pointIsReadable = point && ( point.apex || point.position.y === 0 || Utils.toFixedNumber( point.time * 1000, 0 ) % TIME_PER_MINOR_DOT === 0 ); - if ( pointIsReadable && point.position.distance( this.positionProperty.get() ) <= SENSING_RADIUS ) { + if ( pointIsReadable && this.pointWithinTolerance( point.position ) ) { this.dataPointProperty.set( point ); return; } @@ -102,7 +115,7 @@ class DataProbe { // point can be read by dataProbe if it exists, it is on the ground, or it is the right timestep const pointIsReadable = point && ( point.apex || point.position.y === 0 || Utils.toFixedNumber( point.time * 1000, 0 ) % TIME_PER_MINOR_DOT === 0 ); - if ( pointIsReadable && point.position.distance( this.positionProperty.get() ) <= SENSING_RADIUS ) { + if ( pointIsReadable && this.pointWithinTolerance( point.position ) ) { this.dataPointProperty.set( point ); } } diff --git a/js/common/model/ProjectileMotionModel.js b/js/common/model/ProjectileMotionModel.js index ca60ba89..ab5d11e8 100644 --- a/js/common/model/ProjectileMotionModel.js +++ b/js/common/model/ProjectileMotionModel.js @@ -12,6 +12,7 @@ import Emitter from '../../../../axon/js/Emitter.js'; import EnumerationProperty from '../../../../axon/js/EnumerationProperty.js'; import NumberProperty from '../../../../axon/js/NumberProperty.js'; import Property from '../../../../axon/js/Property.js'; +import Range from '../../../../dot/js/Range.js'; import Vector2 from '../../../../dot/js/Vector2.js'; import EventTimer from '../../../../phet-core/js/EventTimer.js'; import merge from '../../../../phet-core/js/merge.js'; @@ -30,6 +31,10 @@ import Target from './Target.js'; import Trajectory from './Trajectory.js'; // constants +const MIN_ZOOM = ProjectileMotionConstants.MIN_ZOOM; +const MAX_ZOOM = ProjectileMotionConstants.MAX_ZOOM; +const DEFAULT_ZOOM = ProjectileMotionConstants.DEFAULT_ZOOM; + const TIME_PER_DATA_POINT = ProjectileMotionConstants.TIME_PER_DATA_POINT; // ms class ProjectileMotionModel { @@ -203,12 +208,20 @@ class ProjectileMotionModel { // @public {Emitter} emits when cannon needs to update its muzzle flash animation this.muzzleFlashStepper = new Emitter(); + // zoom Property + this.zoomProperty = new NumberProperty( DEFAULT_ZOOM, { + tandem: tandem.createTandem( 'zoomProperty' ), + range: new Range( MIN_ZOOM, MAX_ZOOM ), + phetioDocumentation: 'Used to adjust to visual zoom for this screen. Each new zoom level increases the value by a factor of 2.', + phetioStudioControl: false // see https://github.com/phetsims/projectile-motion/issues/219 + } ); + // @public {PhetioGroup.} a group of trajectories, limited to MAX_NUMBER_OF_TRAJECTORIES // Create this after model properties to support the PhetioGroup creating the prototype immediately this.trajectoryGroup = Trajectory.createGroup( this, tandem.createTandem( 'trajectoryGroup' ) ); // @public {DataProbe} model for the dataProbe probe - this.dataProbe = new DataProbe( this.trajectoryGroup, 10, 10, tandem.createTandem( 'dataProbe' ) ); // position arbitrary + this.dataProbe = new DataProbe( this.trajectoryGroup, 10, 10, this.zoomProperty, tandem.createTandem( 'dataProbe' ) ); // position arbitrary // Links in this constructor last for the life time of the sim, so no need to dispose @@ -243,6 +256,7 @@ class ProjectileMotionModel { this.target.reset(); this.measuringTape.reset(); this.dataProbe.reset(); + this.zoomProperty.reset(); this.cannonHeightProperty.reset(); this.cannonAngleProperty.reset(); diff --git a/js/common/view/ProjectileMotionScreenView.js b/js/common/view/ProjectileMotionScreenView.js index aedd888e..663c6057 100644 --- a/js/common/view/ProjectileMotionScreenView.js +++ b/js/common/view/ProjectileMotionScreenView.js @@ -225,7 +225,7 @@ class ProjectileMotionScreenView extends ScreenView { phetioDocumentation: 'the Node for the dataProbe tool' } ); - const zoomButtonGroup = new MagnifyingGlassZoomButtonGroup( viewProperties.zoomProperty, { + const zoomButtonGroup = new MagnifyingGlassZoomButtonGroup( model.zoomProperty, { applyZoomIn: currentZoom => currentZoom * 2, applyZoomOut: currentZoom => currentZoom / 2, spacing: X_MARGIN, @@ -246,8 +246,8 @@ class ProjectileMotionScreenView extends ScreenView { phetioDocumentation: 'Container for the zoom in and out buttons' } ); - // Watch the zoom Property and update transform Property accordingly - viewProperties.zoomProperty.link( zoomFactor => { + // Watch the zoomProperty and update transform Property accordingly + model.zoomProperty.link( zoomFactor => { transformProperty.set( ModelViewTransform2.createSinglePointScaleInvertedYMapping( Vector2.ZERO, ProjectileMotionConstants.VIEW_ORIGIN, @@ -261,14 +261,14 @@ class ProjectileMotionScreenView extends ScreenView { phetioDocumentation: 'the panel that holds the tools when not in the play area' } ); - // reset all button, also a closure for zoomProperty and measuringTape + // reset all button, also a closure for and measuringTape const resetAllButton = new ResetAllButton( { listener: () => { + + // reset zoom (in model) before the target is reset, so that the transform is correct model.reset(); - // reset zoom (in viewProperties) before the target is reset, so that the transform is correct viewProperties.reset(); - targetNode.reset(); cannonNode.reset(); }, diff --git a/js/common/view/ProjectileMotionViewProperties.js b/js/common/view/ProjectileMotionViewProperties.js index add2350d..6f9a1994 100644 --- a/js/common/view/ProjectileMotionViewProperties.js +++ b/js/common/view/ProjectileMotionViewProperties.js @@ -7,16 +7,9 @@ */ import BooleanProperty from '../../../../axon/js/BooleanProperty.js'; -import NumberProperty from '../../../../axon/js/NumberProperty.js'; -import Range from '../../../../dot/js/Range.js'; import merge from '../../../../phet-core/js/merge.js'; import Tandem from '../../../../tandem/js/Tandem.js'; import projectileMotion from '../../projectileMotion.js'; -import ProjectileMotionConstants from '../ProjectileMotionConstants.js'; - -const MIN_ZOOM = ProjectileMotionConstants.MIN_ZOOM; -const MAX_ZOOM = ProjectileMotionConstants.MAX_ZOOM; -const DEFAULT_ZOOM = ProjectileMotionConstants.DEFAULT_ZOOM; class ProjectileMotionViewProperties { /** @@ -75,15 +68,6 @@ class ProjectileMotionViewProperties { } ); } - - // zoom Property - this.zoomProperty = new NumberProperty( DEFAULT_ZOOM, { - tandem: options.tandem.createTandem( 'zoomProperty' ), - range: new Range( MIN_ZOOM, MAX_ZOOM ), - phetioDocumentation: 'Used to adjust to visual zoom for this screen. Each new zoom level increases the value by a factor of 2.', - phetioStudioControl: false // see https://github.com/phetsims/projectile-motion/issues/219 - } ); - } /** @@ -103,7 +87,6 @@ class ProjectileMotionViewProperties { this.totalForceVectorOnProperty.reset(); this.componentsForceVectorsOnProperty.reset(); } - this.zoomProperty.reset(); } }