Skip to content

Commit

Permalink
Create launcher and connect launcher type, height and angle - see #7
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew-blackman committed Nov 19, 2023
1 parent 420a55f commit 7d1a80f
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 11 deletions.
11 changes: 9 additions & 2 deletions js/common/ProjectileDataLabConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,32 @@
import projectileDataLab from '../projectileDataLab.js';
import PhetFont from '../../../scenery-phet/js/PhetFont.js';

const maxFieldDistance = 100;
const fieldWidth = 880;
const pixelsToDistance = fieldWidth / maxFieldDistance;

const ProjectileDataLabConstants = {

SCREEN_VIEW_X_MARGIN: 15,
SCREEN_VIEW_Y_MARGIN: 15,

// In field units
MAX_FIELD_DISTANCE: 100,
MAX_FIELD_DISTANCE: maxFieldDistance,
FIELD_LINE_NUMBER_INCREMENT: 10,
RAISED_LAUNCHER_HEIGHT: 15,

// In view units
FIELD_CENTER_OFFSET_X: 50,
FIELD_CENTER_Y: 500,
FIELD_HEIGHT: 50,
FIELD_WIDTH: 880,
FIELD_WIDTH: fieldWidth,
FIELD_BORDER_LINE_WIDTH: 3,
FIELD_CENTER_LINE_WIDTH: 3,
FIELD_LINE_WIDTH: 2,
FIELD_LABEL_TOP_MARGIN: 5,

PIXELS_TO_DISTANCE: pixelsToDistance,

PRIMARY_FONT: new PhetFont( 16 ),
FIELD_LABEL_FONT: new PhetFont( 17 )
} as const;
Expand Down
26 changes: 21 additions & 5 deletions js/common/model/ProjectileDataLabModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,34 @@ import { EmptySelfOptions } from '../../../../phet-core/js/optionize.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import Property from '../../../../axon/js/Property.js';
import NumberIO from '../../../../tandem/js/types/NumberIO.js';
import ProjectileDataLabConstants from '../ProjectileDataLabConstants.js';

type SelfOptions = EmptySelfOptions;
export type ProjectileDataLabModelOptions = SelfOptions & { tandem: Tandem };

export default class ProjectileDataLabModel implements TModel {

public readonly launcherProperty: Property<number>;
public readonly launcherAngleProperty: Property<number>;
public readonly launcherHeightProperty: Property<number>;

public readonly launcherTypeProperty: Property<number>;
public readonly binWidthProperty: Property<number>;

public constructor( providedOptions: ProjectileDataLabModelOptions ) {

this.launcherProperty = new Property<number>( 1, {
this.launcherAngleProperty = new Property<number>( 30, {
validValues: [ 0, 30, 45, 60 ],
tandem: Tandem.OPT_OUT
} );

this.launcherHeightProperty = new Property<number>( 0, {
validValues: [ 0, ProjectileDataLabConstants.RAISED_LAUNCHER_HEIGHT ],
tandem: Tandem.OPT_OUT
} );

this.launcherTypeProperty = new Property<number>( 1, {
validValues: [ 1, 2, 3, 4, 5, 6 ],
tandem: providedOptions.tandem.createTandem( 'launcherProperty' ),
tandem: providedOptions.tandem.createTandem( 'launcherTypeProperty' ),
phetioDocumentation: 'This property configures the active launcher by number.',
phetioValueType: NumberIO
} );
Expand All @@ -33,11 +47,13 @@ export default class ProjectileDataLabModel implements TModel {
phetioDocumentation: 'This property configures the bin width of the field and histogram.',
phetioValueType: NumberIO
} );

}

public reset(): void {
// implement me
this.launcherAngleProperty.reset();
this.launcherHeightProperty.reset();
this.launcherTypeProperty.reset();
this.binWidthProperty.reset();
}
}
projectileDataLab.register( 'ProjectileDataLabModel', ProjectileDataLabModel );
25 changes: 25 additions & 0 deletions js/common/model/VSMModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { LauncherConfiguration, LauncherConfigurationValues } from './LauncherCo
import { EmptySelfOptions } from '../../../../phet-core/js/optionize.js';
import StringUnionIO from '../../../../tandem/js/types/StringUnionIO.js';
import { ProjectileType, ProjectileTypeValues } from './ProjectileType.js';
import ProjectileDataLabConstants from '../ProjectileDataLabConstants.js';

type SelfOptions = EmptySelfOptions;
export type VSMModelOptions = SelfOptions & ProjectileDataLabModelOptions;
Expand All @@ -35,6 +36,30 @@ export default class VSMModel extends ProjectileDataLabModel {
phetioDocumentation: 'This property configures the type of projectile.',
phetioValueType: StringUnionIO( ProjectileTypeValues )
} );

this.launcherConfigurationProperty.link( launcherAngle => {
let angle = 0;
let height = 0;
switch( launcherAngle ) {
case 'ANGLE_45':
angle = 45;
break;
case 'ANGLE_60':
angle = 60;
break;
case 'ANGLE_30':
angle = 30;
break;
case 'ANGLE_0':
height = ProjectileDataLabConstants.RAISED_LAUNCHER_HEIGHT;
break;
default:
break;
}

this.launcherAngleProperty.value = angle;
this.launcherHeightProperty.value = height;
} );
}

public override reset(): void {
Expand Down
101 changes: 101 additions & 0 deletions js/common/view/LauncherNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2023, University of Colorado Boulder

import { Node, NodeOptions, Rectangle } from '../../../../scenery/js/imports.js';
import optionize, { EmptySelfOptions } from '../../../../phet-core/js/optionize.js';
import projectileDataLab from '../../projectileDataLab.js';
import ProjectileDataLabColors from '../ProjectileDataLabColors.js';
import Property from '../../../../axon/js/Property.js';
import ProjectileDataLabConstants from '../ProjectileDataLabConstants.js';

/**
* The LauncherNode is the visual representation of the projectile launcher. It contains a launcher, frame and a stand.
*
* @author Matthew Blackman (PhET Interactive Simulations)
* @author Sam Reid (PhET Interactive Simulations)
*/

type SelfOptions = EmptySelfOptions;
type LauncherNodeOptions = SelfOptions & NodeOptions;

export default class LauncherNode extends Node {

private readonly originY: number;
private readonly launcher: Node;

public constructor( x: number,
originY: number,
launcherAngleProperty: Property<number>,
launcherHeightProperty: Property<number>,
launcherTypeProperty: Property<number>,
providedOptions: LauncherNodeOptions ) {

const launcher = new Node();
const defaultOptions = { x: x, children: [ launcher ] };
const options = optionize<LauncherNodeOptions, SelfOptions, NodeOptions>()( defaultOptions, providedOptions );
super( options );

this.originY = originY;
this.launcher = launcher;

launcherAngleProperty.link( launcherAngle => {
this.updateLauncherAngle( launcherAngle );
} );

launcherHeightProperty.link( launcherHeight => {
this.updateLauncherHeight( launcherHeight );
} );

launcherTypeProperty.link( launcherType => {
this.launcher.removeAllChildren();
this.launcherGraphicsForType( launcherType ).forEach( launcherGraphics => {
this.launcher.addChild( launcherGraphics );
} );
} );
}

private launcherGraphicsForType( launcherType: number ): Rectangle[] {
const launcherLengthBeforeOrigin = 100;
const launcherLengthAfterOrigin = 15;
const launcherLength = launcherLengthBeforeOrigin + launcherLengthAfterOrigin;
const launcherWidth = 32;
const launcherFillColorProperty = ProjectileDataLabColors.launcherFillColorProperties[ launcherType - 1 ];

const launcherRect = new Rectangle(
-launcherLengthBeforeOrigin,
-0.5 * launcherWidth,
launcherLength,
launcherWidth, {
fill: launcherFillColorProperty,
stroke: ProjectileDataLabColors.launcherStrokeColorProperty,
lineWidth: 1,
cornerRadius: 0.2 * launcherWidth
}
);

const launcherEndRectWidth = 1.2 * launcherWidth;
const launcherEndRectLength = 0.12 * launcherEndRectWidth;

const launcherEndRect = new Rectangle(
launcherLengthAfterOrigin - 0.5 * launcherEndRectLength,
-0.5 * launcherEndRectWidth,
launcherEndRectLength,
launcherEndRectWidth, {
fill: launcherFillColorProperty.value.darkerColor( 0.8 ),
stroke: ProjectileDataLabColors.launcherStrokeColorProperty,
lineWidth: 1,
cornerRadius: 0.1 * launcherEndRectLength
}
);

return [ launcherRect, launcherEndRect ];
}

private updateLauncherAngle( angle: number ): void {
this.launcher.setRotation( -angle * Math.PI / 180 );
}

private updateLauncherHeight( height: number ): void {
this.y = this.originY - height * ProjectileDataLabConstants.PIXELS_TO_DISTANCE;
}
}
projectileDataLab.register( 'LauncherNode', LauncherNode );
41 changes: 41 additions & 0 deletions js/common/view/LauncherTypeSection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2023, University of Colorado Boulder

import PDLPanelSection, { PDLPanelSectionOptions } from './PDLPanelSection.js';
import { EmptySelfOptions } from '../../../../phet-core/js/optionize.js';
import Property from '../../../../axon/js/Property.js';
import RectangularRadioButtonGroup, { RectangularRadioButtonGroupItem } from '../../../../sun/js/buttons/RectangularRadioButtonGroup.js';
import { Text } from '../../../../scenery/js/imports.js';
import ProjectileDataLabStrings from '../../ProjectileDataLabStrings.js';
import projectileDataLab from '../../projectileDataLab.js';

/**
* @author Matthew Blackman (PhET Interactive Simulations)
* @author Sam Reid (PhET Interactive Simulations)
*/

type SelfOptions = EmptySelfOptions;
type ProjectileTypeSectionOptions = SelfOptions & PDLPanelSectionOptions;

export default class LauncherTypeSection extends PDLPanelSection {

public constructor( launcherTypeProperty: Property<number>, providedOptions: ProjectileTypeSectionOptions ) {
// TODO: Try to use Array.map for this without type errors - see https://github.com/phetsims/projectile-data-lab/issues/5
const launcherTypeRadioButtonGroupItems: RectangularRadioButtonGroupItem<number>[] = [];

launcherTypeProperty.validValues?.forEach( launcherType => {
launcherTypeRadioButtonGroupItems.push( {
value: launcherType,
tandemName: `launcherType${launcherType}RadioButton`,
createNode: () => new Text( launcherType.toString() )
} );
} );

const launcherTypeRadioButtonGroup = new RectangularRadioButtonGroup( launcherTypeProperty, launcherTypeRadioButtonGroupItems, {
tandem: providedOptions.tandem.createTandem( 'launcherTypeRadioButtonGroup' ),
orientation: 'horizontal'
} );
super( ProjectileDataLabStrings.launcherStringProperty, launcherTypeRadioButtonGroup, providedOptions );
}
}

projectileDataLab.register( 'LauncherTypeSection', LauncherTypeSection );
2 changes: 1 addition & 1 deletion js/measures/view/MeasuresScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default class MeasuresScreenView extends PDLScreenView {
const options = optionize<ProjectileDataLabScreenViewOptions, SelfOptions, ScreenViewOptions>()( {}, providedOptions );
super( model, options );

const measuresLaunchPanel = new SourcesLaunchPanel( model.launcherConfigurationProperty, model.projectileTypeProperty, {
const measuresLaunchPanel = new SourcesLaunchPanel( model.launcherConfigurationProperty, model.projectileTypeProperty, model.launcherTypeProperty, {
tandem: options.tandem.createTandem( 'measuresLaunchPanel' )
} );
this.addChild( measuresLaunchPanel );
Expand Down
2 changes: 1 addition & 1 deletion js/sampling/view/SamplingScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default class SamplingScreenView extends PDLScreenView {
const options = optionize<SamplingScreenViewOptions, SelfOptions, ScreenViewOptions>()( {}, providedOptions );
super( model, options );

const samplingLaunchPanel = new SamplingLaunchPanel( model.launcherProperty, model.sampleSizeProperty, {
const samplingLaunchPanel = new SamplingLaunchPanel( model.launcherTypeProperty, model.sampleSizeProperty, {
tandem: options.tandem.createTandem( 'samplingLaunchPanel' )
} );
this.addChild( samplingLaunchPanel );
Expand Down
2 changes: 1 addition & 1 deletion js/sources/view/SourcesScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default class SourcesScreenView extends PDLScreenView {
const options = optionize<ProjectileDataLabScreenViewOptions, SelfOptions, ScreenViewOptions>()( {}, providedOptions );
super( model, options );

const sourcesLaunchPanel = new SourcesLaunchPanel( model.launcherConfigurationProperty, model.projectileTypeProperty, {
const sourcesLaunchPanel = new SourcesLaunchPanel( model.launcherConfigurationProperty, model.projectileTypeProperty, model.launcherTypeProperty, {
tandem: options.tandem.createTandem( 'sourcesLaunchPanel' )
} );
this.addChild( sourcesLaunchPanel );
Expand Down
2 changes: 1 addition & 1 deletion js/variability/view/VariabilityScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default class VariabilityScreenView extends PDLScreenView {
const options = optionize<ProjectileDataLabScreenViewOptions, SelfOptions, ScreenViewOptions>()( {}, providedOptions );
super( model, options );

const variabilityLaunchPanel = new VariabilityLaunchPanel( model.launcherConfigurationProperty, model.projectileTypeProperty, {
const variabilityLaunchPanel = new VariabilityLaunchPanel( model.launcherConfigurationProperty, model.projectileTypeProperty, model.launcherTypeProperty, {
tandem: options.tandem.createTandem( 'variabilityLaunchPanel' )
} );
this.addChild( variabilityLaunchPanel );
Expand Down

0 comments on commit 7d1a80f

Please sign in to comment.