Skip to content

Commit

Permalink
Adding particle ray view, #53
Browse files Browse the repository at this point in the history
  • Loading branch information
AgustinVallejo committed Oct 15, 2024
1 parent cdd89c0 commit 3087d54
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 20 deletions.
5 changes: 5 additions & 0 deletions js/spin/model/SpinExperiment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,26 @@ export default class SpinExperiment extends EnumerationValue {
] );
public static readonly EXPERIMENT_3 = new SpinExperiment( 'Experiment 3 [Sz, Sx]', [
{ isZOriented: true, active: true },
{ isZOriented: false, active: true },
{ isZOriented: false, active: true }
] );
public static readonly EXPERIMENT_4 = new SpinExperiment( 'Experiment 4 [Sz, Sz]', [
{ isZOriented: true, active: true },
{ isZOriented: true, active: true },
{ isZOriented: true, active: true }
] );
public static readonly EXPERIMENT_5 = new SpinExperiment( 'Experiment 5 [Sx, Sz]', [
{ isZOriented: false, active: true },
{ isZOriented: true, active: true },
{ isZOriented: true, active: true }
] );
public static readonly EXPERIMENT_6 = new SpinExperiment( 'Experiment 6 [Sx, Sx]', [
{ isZOriented: false, active: true },
{ isZOriented: false, active: true },
{ isZOriented: false, active: true }
] );
public static readonly CUSTOM = new SpinExperiment( 'Custom', [
{ isZOriented: false, active: false },
{ isZOriented: false, active: false },
{ isZOriented: false, active: false }
] );
Expand Down
83 changes: 83 additions & 0 deletions js/spin/view/ParticleRayPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2024, University of Colorado Boulder

/**
* ParticleRayPath is the visual representation of a particle ray path in the UI.
* Coordinates for the three possible rays are provided, and the paths are drawn accordingly.
*
* @author Agustín Vallejo
*/

import Multilink from '../../../../axon/js/Multilink.js';
import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import { Shape } from '../../../../kite/js/imports.js';
import { Node, Path, PathOptions } from '../../../../scenery/js/imports.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import QuantumMeasurementColors from '../../common/QuantumMeasurementColors.js';
import quantumMeasurement from '../../quantumMeasurement.js';

// Constants

export default class ParticleRayPath extends Node {

public readonly updatePaths: (
primaryRayPoints: Vector2[],
secondaryRayPoints: Vector2[],
tertiaryRayPoints: Vector2[]
) => void;

public constructor(
particleAmmountProperty: TReadOnlyProperty<number>,
tandem: Tandem ) {

const rayPathOptions: PathOptions = {
lineWidth: 10,
stroke: QuantumMeasurementColors.downColorProperty,
lineCap: 'round'
};

const primaryRayPath = new Path( null, rayPathOptions );
const secondaryRayPath = new Path( null, rayPathOptions );
const tertiaryRayPath = new Path( null, rayPathOptions );

// TODO: Multilink for when we have another value in the model for the opacity of secondary and tertiary rays https://github.com/phetsims/quantum-measurement/issues/53
Multilink.multilink(
[ particleAmmountProperty ],
particleAmmount => {
primaryRayPath.opacity = particleAmmount;
secondaryRayPath.opacity = particleAmmount;
tertiaryRayPath.opacity = particleAmmount;
}
);

super( {
tandem: tandem,
localBounds: null,
children: [
primaryRayPath,
secondaryRayPath,
tertiaryRayPath
]
} );

this.updatePaths = (
primaryRayPoints: Vector2[],
secondaryRayPoints: Vector2[],
tertiaryRayPoints: Vector2[]
) => {
const mappedPrimaryRayPoints = primaryRayPoints.map( point => this.globalToLocalPoint( point ) );
const mappedSecondaryRayPoints = secondaryRayPoints.map( point => this.globalToLocalPoint( point ) );
const mappedTertiaryRayPoints = tertiaryRayPoints.map( point => this.globalToLocalPoint( point ) );

primaryRayPath.shape = new Shape().moveTo( mappedPrimaryRayPoints[ 0 ].x, mappedPrimaryRayPoints[ 0 ].y )
.lineTo( mappedPrimaryRayPoints[ 1 ].x, mappedPrimaryRayPoints[ 1 ].y );
secondaryRayPath.shape = new Shape().moveTo( mappedSecondaryRayPoints[ 0 ].x, mappedSecondaryRayPoints[ 0 ].y )
.lineTo( mappedSecondaryRayPoints[ 1 ].x, mappedSecondaryRayPoints[ 1 ].y );
tertiaryRayPath.shape = new Shape().moveTo( mappedTertiaryRayPoints[ 0 ].x, mappedTertiaryRayPoints[ 0 ].y )
.lineTo( mappedTertiaryRayPoints[ 1 ].x, mappedTertiaryRayPoints[ 1 ].y );
};

}
}

quantumMeasurement.register( 'ParticleRayPath', ParticleRayPath );
15 changes: 11 additions & 4 deletions js/spin/view/ParticleSourceNode.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Copyright 2024, University of Colorado Boulder

/**
* ParticleSourceNode contains the UI elements around the particle source, including the particle-shooting apparatus,
* and other UI elements to control the source mode.
* ParticleSourceNode contains the UI elements of the particle source, including the particle-shooter,
* and other UI elements to control the source mode between 'single particle' and 'continuous' ray.
*
* @author Agustín Vallejo
*/

import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
import Dimension2 from '../../../../dot/js/Dimension2.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import { Shape } from '../../../../kite/js/imports.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import { LinearGradient, Node, Path, RichText, Text, VBox } from '../../../../scenery/js/imports.js';
Expand All @@ -21,14 +22,18 @@ import quantumMeasurement from '../../quantumMeasurement.js';
import SpinModel, { SourceMode } from '../model/SpinModel.js';

// Constants
const PARTICLE_SOURCE_WIDTH = 100;
const PARTICLE_SOURCE_HEIGHT = 100;
export const PARTICLE_SOURCE_WIDTH = 120;
export const PARTICLE_SOURCE_HEIGHT = 120;
const PARTICLE_SOURCE_CORNER_RADIUS = 10;

export default class ParticleSourceNode extends VBox {

// Global position vector for the particle ray, it is updated outside of the constructor
public particleExitGlobalPosition = new Vector2( 0, 0 );

public constructor( model: SpinModel, tandem: Tandem ) {

// Main shape of the component
const particleSourceView = new Path( new Shape().roundRect( 0, 0, PARTICLE_SOURCE_WIDTH, PARTICLE_SOURCE_HEIGHT, PARTICLE_SOURCE_CORNER_RADIUS, PARTICLE_SOURCE_CORNER_RADIUS ),
{
fill: new LinearGradient( 0, 0, 0, 100 )
Expand All @@ -37,6 +42,7 @@ export default class ParticleSourceNode extends VBox {
.addColorStop( 1, 'blue' )
} );

// Button for 'single' mode
const shootParticleButton = new RoundMomentaryButton<boolean>(
model.currentlyShootingParticlesProperty, false, true, {
scale: 0.7,
Expand All @@ -46,6 +52,7 @@ export default class ParticleSourceNode extends VBox {
tandem: tandem.createTandem( 'shootParticleButton' )
} );

// Slider for 'continuous' mode
const sliderRange = model.particleAmmountProperty.range;
const particleAmmountSlider = new HSlider( model.particleAmmountProperty, sliderRange, {
thumbFill: QuantumMeasurementColors.downColorProperty,
Expand Down
64 changes: 57 additions & 7 deletions js/spin/view/SpinMeasurementArea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@
* @author Agustín Vallejo
*/

import Bounds2 from '../../../../dot/js/Bounds2.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import { HBox, Node, Text, VBox } from '../../../../scenery/js/imports.js';
import ComboBox, { ComboBoxItem } from '../../../../sun/js/ComboBox.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import quantumMeasurement from '../../quantumMeasurement.js';
import SpinExperiment from '../model/SpinExperiment.js';
import SpinModel from '../model/SpinModel.js';
import ParticleSourceNode from './ParticleSourceNode.js';
import SternGerlachNode from './SternGerlachNode.js';
import ParticleRayPath from './ParticleRayPath.js';
import ParticleSourceNode, { PARTICLE_SOURCE_HEIGHT, PARTICLE_SOURCE_WIDTH } from './ParticleSourceNode.js';
import SternGerlachNode, { PARTICLE_HOLE_WIDTH, STERN_GERLACH_HEIGHT, STERN_GERLACH_WIDTH } from './SternGerlachNode.js';

export default class SpinMeasurementArea extends VBox {

public constructor( model: SpinModel, parentNode: Node, tandem: Tandem ) {
public constructor( model: SpinModel, parentNode: Node, layoutBounds: Bounds2, tandem: Tandem ) {

const items: ComboBoxItem<SpinExperiment>[] = SpinExperiment.enumeration.values.map( experiment => {
return {
Expand All @@ -32,6 +35,19 @@ export default class SpinMeasurementArea extends VBox {
tandem: tandem.createTandem( 'experimentComboBox' )
} );

const particleSourceNode = new ParticleSourceNode( model, tandem.createTandem( 'particleSourceNode' ) );

const firstSternGerlachNode = new SternGerlachNode( model.firstSternGerlachModel, tandem.createTandem( 'firstSternGerlachNode' ) );
const secondSternGerlachNode = new SternGerlachNode( model.secondSternGerlachModel, tandem.createTandem( 'secondSternGerlachNode' ) );
const thirdSternGerlachNode = new SternGerlachNode( model.thirdSternGerlachModel, tandem.createTandem( 'thirdSternGerlachNode' ) );

const particleRayPath = new ParticleRayPath(
model.particleAmmountProperty,
tandem.createTandem( 'particleRayPath' )
);

parentNode.addChild( particleRayPath );

super( {
children: [
experimentComboBox,
Expand All @@ -41,13 +57,13 @@ export default class SpinMeasurementArea extends VBox {
new HBox( {
spacing: 50,
children: [
new ParticleSourceNode( model, tandem ),
new SternGerlachNode( model.firstSternGerlachModel, tandem.createTandem( 'firstSternGerlachNode' ) ),
particleSourceNode,
firstSternGerlachNode,
new VBox( {
spacing: 20,
children: [
new SternGerlachNode( model.secondSternGerlachModel, tandem.createTandem( 'secondSternGerlachNode' ) ),
new SternGerlachNode( model.secondSternGerlachModel, tandem.createTandem( 'thirdSternGerlachNode' ) )
secondSternGerlachNode,
thirdSternGerlachNode
]
} )
]
Expand All @@ -57,6 +73,40 @@ export default class SpinMeasurementArea extends VBox {
spacing: 20,
margin: 20
} );

model.currentExperimentProperty.link( experiment => {

// Adjust the global positions of the nodes
particleSourceNode.particleExitGlobalPosition = particleSourceNode.localToGlobalPoint( new Vector2( PARTICLE_SOURCE_WIDTH, PARTICLE_SOURCE_HEIGHT ) );

firstSternGerlachNode.entranceGlobalPosition = firstSternGerlachNode.localToGlobalPoint( new Vector2( 0, STERN_GERLACH_HEIGHT / 2 ) );
firstSternGerlachNode.topExitGlobalPosition = firstSternGerlachNode.localToGlobalPoint( new Vector2( STERN_GERLACH_WIDTH + PARTICLE_HOLE_WIDTH, STERN_GERLACH_HEIGHT / 4 ) );
firstSternGerlachNode.bottomExitGlobalPosition = firstSternGerlachNode.localToGlobalPoint( new Vector2( STERN_GERLACH_WIDTH + PARTICLE_HOLE_WIDTH, 3 * STERN_GERLACH_HEIGHT / 4 ) );

secondSternGerlachNode.entranceGlobalPosition = secondSternGerlachNode.localToGlobalPoint( new Vector2( -PARTICLE_HOLE_WIDTH, STERN_GERLACH_HEIGHT / 2 ) );

thirdSternGerlachNode.entranceGlobalPosition = thirdSternGerlachNode.localToGlobalPoint( new Vector2( -PARTICLE_HOLE_WIDTH, STERN_GERLACH_HEIGHT / 2 ) );

// Update the paths of the particle rays according to the current experiment (if 2nd and 3rd SG are not visible, the rays keep going on)
const primaryRayPoints = [ particleSourceNode.particleExitGlobalPosition, firstSternGerlachNode.entranceGlobalPosition ];

console.log( 'jiji', firstSternGerlachNode.topExitGlobalPosition );

if ( experiment.experimentSettings.length === 1 ) {
particleRayPath.updatePaths(
primaryRayPoints,
[ firstSternGerlachNode.topExitGlobalPosition, new Vector2( layoutBounds.maxX * 5, firstSternGerlachNode.topExitGlobalPosition.y ) ],
[ firstSternGerlachNode.bottomExitGlobalPosition, new Vector2( layoutBounds.maxX * 5, firstSternGerlachNode.bottomExitGlobalPosition.y ) ]
);
}
else {
particleRayPath.updatePaths(
primaryRayPoints,
[ firstSternGerlachNode.topExitGlobalPosition, secondSternGerlachNode.entranceGlobalPosition ],
[ firstSternGerlachNode.bottomExitGlobalPosition, thirdSternGerlachNode.entranceGlobalPosition ]
);
}
} );
}
}

Expand Down
4 changes: 3 additions & 1 deletion js/spin/view/SpinScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default class SpinScreenView extends QuantumMeasurementScreenView {
} );
this.addChild( dividingLine );

const spinMeasurementArea = new SpinMeasurementArea( model, this, tandem.createTandem( 'spinMeasurementArea' ) );
const spinMeasurementArea = new SpinMeasurementArea( model, this, this.layoutBounds, tandem.createTandem( 'spinMeasurementArea' ) );
spinMeasurementArea.centerX = 2 * dividingLineX;
this.addChild( spinMeasurementArea );

Expand All @@ -49,6 +49,8 @@ export default class SpinScreenView extends QuantumMeasurementScreenView {
spinStatePreparationArea.opacity = 1 - opacity;
spinMeasurementArea.opacity = 1 - opacity;
} );

model.currentExperimentProperty.notifyListenersStatic();
}

public override reset(): void {
Expand Down
17 changes: 9 additions & 8 deletions js/spin/view/SternGerlachNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ import quantumMeasurement from '../../quantumMeasurement.js';
import SternGerlachModel from '../model/SternGerlachModel.js';

// Constants
const STERN_GERLACH_WIDTH = 150;
const STERN_GERLACH_HEIGHT = 100;
const PARTICLE_HOLE_WIDTH = 5;
export const STERN_GERLACH_WIDTH = 150;
export const STERN_GERLACH_HEIGHT = 100;
export const PARTICLE_HOLE_WIDTH = 5;
const PARTICLE_HOLE_HEIGHT = 20;

export default class SternGerlachNode extends Node {

public readonly entranceGlobalPosition: Vector2;
public readonly boxWidth = STERN_GERLACH_WIDTH;
public readonly boxHeight = STERN_GERLACH_HEIGHT;
// Global position vectors, they are to be updated outside of the constructor
public entranceGlobalPosition = new Vector2( 0, 0 );
public topExitGlobalPosition = new Vector2( 0, 0 );
public bottomExitGlobalPosition = new Vector2( 0, 0 );

public constructor( experimentModel: SternGerlachModel, tandem: Tandem ) {

// Component for the entry and exit points of the SG apparatus
const createParticleHole = ( x: number, y: number ) => {
return new Path( new Shape().rect( x, y, PARTICLE_HOLE_WIDTH, PARTICLE_HOLE_HEIGHT ),
{
Expand All @@ -42,6 +44,7 @@ export default class SternGerlachNode extends Node {
return Math.pow( x, 2 );
};

// Decoration curves that go in the front of the main rectangle
const curveUpShape = new Shape().moveTo( 0, STERN_GERLACH_HEIGHT / 2 );
const curveDownShape = new Shape().moveTo( 0, STERN_GERLACH_HEIGHT / 2 );

Expand Down Expand Up @@ -76,8 +79,6 @@ export default class SternGerlachNode extends Node {
{ font: new PhetFont( 16 ), fill: 'white', center: new Vector2( 25, 80 ) } )
]
} );

this.entranceGlobalPosition = this.localToGlobalPoint( new Vector2( 0, STERN_GERLACH_HEIGHT / 2 ) );
}
}

Expand Down

0 comments on commit 3087d54

Please sign in to comment.