Skip to content

Commit

Permalink
Add CASObject and SoccerBall/Node, see #6
Browse files Browse the repository at this point in the history
  • Loading branch information
samreid committed Feb 4, 2022
1 parent 36fc668 commit 72ef1a1
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 2 deletions.
8 changes: 8 additions & 0 deletions images/ball_png.js

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions js/common/model/CASObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2022, University of Colorado Boulder

/**
* Base class for a manipulable data point which could be a soccer ball or, in the lab screen, a colored sphere.
*
* @author Chris Klusendorf (PhET Interactive Simulations)
* @author Sam Reid (PhET Interactive Simulations)
*/

import centerAndSpread from '../../centerAndSpread.js';
import Vector2Property from '../../../../dot/js/Vector2Property.js';
import PhetioObject, { PhetioObjectOptions } from '../../../../tandem/js/PhetioObject.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import optionize from '../../../../phet-core/js/optionize.js';

type CASObjectSelfOptions = {
initialPosition?: Vector2,
radius?: number
};
export type CASObjectOptions =
CASObjectSelfOptions
& PhetioObjectOptions
& Required<Pick<PhetioObjectOptions, 'tandem'>>;

class CASObject extends PhetioObject {
readonly positionProperty: Vector2Property; // in model coordinates
readonly radius: number;

constructor( providedOptions: CASObjectOptions ) {

const options = optionize<CASObjectOptions, CASObjectSelfOptions, PhetioObjectOptions>( {
initialPosition: Vector2.ZERO,
radius: 15,

// phet-io options
tandem: Tandem.REQUIRED
}, providedOptions );

super( options );

this.radius = options.radius;

this.positionProperty = new Vector2Property( options.initialPosition, {
tandem: options.tandem.createTandem( 'positionProperty' )
} );
}

reset() {
this.positionProperty.reset();
}
}

centerAndSpread.register( 'CASObject', CASObject );
export default CASObject;
47 changes: 47 additions & 0 deletions js/common/model/SoccerBall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2022, University of Colorado Boulder

/**
* Soccer balls are animated and hence track their velocity.
*
* @author Chris Klusendorf (PhET Interactive Simulations)
* @author Sam Reid (PhET Interactive Simulations)
*/

import centerAndSpread from '../../centerAndSpread.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import optionize from '../../../../phet-core/js/optionize.js';
import NumberProperty from '../../../../axon/js/NumberProperty.js';
import CASObject, { CASObjectOptions } from './CASObject.js';

type SoccerBallSelfOptions = {};
export type SoccerBallOptions = SoccerBallSelfOptions & CASObjectOptions;

class SoccerBall extends CASObject {
private readonly velocityProperty: NumberProperty;

constructor( providedOptions: SoccerBallOptions ) {

const options = optionize<SoccerBallOptions, SoccerBallSelfOptions>( {

// phet-io options
tandem: Tandem.REQUIRED
}, providedOptions );

super( options );

this.velocityProperty = new NumberProperty( 0, {
tandem: options.tandem.createTandem( 'velocityProperty' )
} );
}

/**
* Resets the model.
*/
reset() {
super.reset();
this.velocityProperty.reset();
}
}

centerAndSpread.register( 'SoccerBall', SoccerBall );
export default SoccerBall;
38 changes: 38 additions & 0 deletions js/common/view/CASObjectNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2022, University of Colorado Boulder

/**
* Base class which renders a Node for the CASObject.
*
* @author Chris Klusendorf (PhET Interactive Simulations)
* @author Sam Reid (PhET Interactive Simulations)
*/

import optionize from '../../../../phet-core/js/optionize.js';
import centerAndSpread from '../../centerAndSpread.js';
import { Node, NodeOptions } from '../../../../scenery/js/imports.js';
import CASObject from '../model/CASObject.js';
import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js';

type CASObjectNodeSelfOptions = {
contentNode: Node
};
export type CASObjectNodeOptions = CASObjectNodeSelfOptions & NodeOptions;

class CASObjectNode extends Node {

constructor( casObject: CASObject, modelViewTransform: ModelViewTransform2, providedOptions: CASObjectNodeOptions ) {
const options = optionize<CASObjectNodeOptions>( {}, providedOptions );
super( options );

this.maxWidth = modelViewTransform.modelToViewDeltaX( casObject.radius * 2 );
this.maxHeight = this.maxWidth;
this.addChild( options.contentNode );

casObject.positionProperty.link( position => {
this.center = modelViewTransform.modelToViewPosition( position );
} );
}
}

centerAndSpread.register( 'CASObjectNode', CASObjectNode );
export default CASObjectNode;
33 changes: 33 additions & 0 deletions js/common/view/SoccerBallNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2022, University of Colorado Boulder

/**
* Renders a soccer ball using a raster image.
*
* @author Chris Klusendorf (PhET Interactive Simulations)
* @author Sam Reid (PhET Interactive Simulations)
*/

import optionize from '../../../../phet-core/js/optionize.js';
import centerAndSpread from '../../centerAndSpread.js';
import { Image } from '../../../../scenery/js/imports.js';
import CASObject from '../model/CASObject.js';
import CASObjectNode, { CASObjectNodeOptions } from './CASObjectNode.js';
import ball_png from '../../../images/ball_png.js';
import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js';

type SoccerBallNodeSelfOptions = {};
export type SoccerBallNodeOptions = SoccerBallNodeSelfOptions & Omit<CASObjectNodeOptions, 'contentNode'>;

class SoccerBallNode extends CASObjectNode {
constructor( casObject: CASObject, modelViewTransform: ModelViewTransform2, providedOptions: SoccerBallNodeOptions ) {

// TODO: Why do we have to specify 'contentNode'?
const options = optionize<SoccerBallNodeOptions, SoccerBallNodeSelfOptions, CASObjectNodeOptions, 'contentNode'>( {
contentNode: new Image( ball_png )
}, providedOptions );
super( casObject, modelViewTransform, options );
}
}

centerAndSpread.register( 'SoccerBallNode', SoccerBallNode );
export default SoccerBallNode;
27 changes: 25 additions & 2 deletions js/common/view/SoccerScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import Vector2 from '../../../../dot/js/Vector2.js';
import { Color, Node, Text } from '../../../../scenery/js/imports.js';
import Range from '../../../../dot/js/Range.js';
import Utils from '../../../../dot/js/Utils.js';
import SoccerBall from '../model/SoccerBall.js';
import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js';
import SoccerBallNode from './SoccerBallNode.js';
import Bounds2 from '../../../../dot/js/Bounds2.js';

type SoccerScreenViewSelfOptions = {
questionBarOptions: QuestionBarOptions
Expand All @@ -47,9 +51,11 @@ class SoccerScreenView extends CASScreenView {

this.addChild( new BackgroundNode( GROUND_POSITION_Y, this.visibleBoundsProperty ) );

// TODO: instrument number line, maybe the whole node or just the tick labels?
const numberLineNode = new Node();
const chartViewWidth = this.layoutBounds.width - NUMBER_LINE_MARGIN_X * 2;
const chartTransform = new ChartTransform( {
viewWidth: this.layoutBounds.width - NUMBER_LINE_MARGIN_X * 2,
viewWidth: chartViewWidth,
modelXRange: new Range( 1, 16 )
} );
const tickMarkSet = new TickMarkSet( chartTransform, Orientation.HORIZONTAL, 1, {
Expand All @@ -63,7 +69,8 @@ class SoccerScreenView extends CASScreenView {
createLabel: ( value: number ) => new Text( Utils.toFixed( value, 0 ), { fontSize: 16 } )
} );
numberLineNode.addChild( tickLabelSet );
numberLineNode.centerTop = new Vector2( this.layoutBounds.centerX, GROUND_POSITION_Y );
numberLineNode.top = GROUND_POSITION_Y;
numberLineNode.x = NUMBER_LINE_MARGIN_X;
this.addChild( numberLineNode );

this.addChild( new QuestionBar( this.layoutBounds, this.visibleBoundsProperty, options.questionBarOptions ) );
Expand All @@ -74,6 +81,22 @@ class SoccerScreenView extends CASScreenView {
} ) );

this.addChild( this.resetAllButton );

const ballRadius = 0.2;

const soccerBall = new SoccerBall( {
initialPosition: new Vector2( 1, ballRadius ),
radius: ballRadius,
tandem: options.tandem.createTandem( 'soccerBall' )
} );

// The ground is at y=0
const modelViewTransform2 = ModelViewTransform2.createRectangleInvertedYMapping(
new Bounds2( 1, 0, 16, 15 ),
new Bounds2( NUMBER_LINE_MARGIN_X, GROUND_POSITION_Y - chartViewWidth, NUMBER_LINE_MARGIN_X + chartViewWidth, GROUND_POSITION_Y )
);

this.addChild( new SoccerBallNode( soccerBall, modelViewTransform2, {} ) );
}

/**
Expand Down

0 comments on commit 72ef1a1

Please sign in to comment.