diff --git a/bending-light_en.html b/bending-light_en.html index 2f994c2b..cd4824ab 100644 --- a/bending-light_en.html +++ b/bending-light_en.html @@ -45,7 +45,9 @@ "simulation": true, "phet-io": { "validation": false - } + }, + "supportsOutputJS": true, + "typescript": true }, "eslintConfig": { "extends": "../chipper/eslint/sim_eslintrc.js" @@ -130,7 +132,7 @@ // Module loading in compilation-free (development) mode will be kicked off once strings are loaded. // This is done in load-unbuilt-strings.js - window.phet.chipper.loadModules = () => loadURL( 'js/bending-light-main.js', 'module' ); + window.phet.chipper.loadModules = () => loadURL( '../chipper/dist/bending-light/js/bending-light-main.js', 'module' ); \ No newline at end of file diff --git a/js/common/BendingLightConstants.js b/js/common/BendingLightConstants.ts similarity index 97% rename from js/common/BendingLightConstants.js rename to js/common/BendingLightConstants.ts index b02cccb5..dee6bec4 100644 --- a/js/common/BendingLightConstants.js +++ b/js/common/BendingLightConstants.ts @@ -27,7 +27,7 @@ const MAX_ANGLE_IN_WAVE_MODE = 3.0194; // CIE 1931 2-angle tristimulus values, from http://cvrl.ioo.ucl.ac.uk/cmfs.htm. Maps wavelength (in nm) to an // X,Y,Z value in the XYZ color space. -const XYZ = { +const XYZ: { [ key: number ]: { x: number, y: number, z: number } } = { 360: { x: 0.0001299, y: 0.000003917, z: 0.0006061 }, 365: { x: 0.0002321, y: 0.000006965, z: 0.001086 }, 370: { x: 0.0004149, y: 0.00001239, z: 0.001946 }, @@ -128,7 +128,7 @@ const XYZ = { // CIE Standard Illuminant D65 relative spectral power distribution (e.g. white light wavelength distribution) // From http://www.cie.co.at/publ/abst/datatables15_2004/sid65.txt, with wavelength < 360 removed to match our XYZ // table. The relative values between wavelengths are what is important here. -const D65 = { +const D65: { [ key: number ]: number } = { 360: 46.6383, 365: 49.3637, 370: 52.0891, @@ -228,10 +228,10 @@ const D65 = { // {Object} - Maps wavelength (nm) to {Vector3} XYZ colorspace values multiplied times the D65 intensity. Combines // the D65 and XYZ responses, so it contains "how bright in XYZ" each wavelength will be for white light. -const XYZ_INTENSITIES = {}; +const XYZ_INTENSITIES: { [ key: number ]: Vector3 } = {}; // Cache the magnitudes as well so they don't need to be computed many times during each draw -const XYZ_INTENSITIES_MAGNITUDE = {}; +const XYZ_INTENSITIES_MAGNITUDE: { [ key: number ]: number } = {}; for ( const wavelength in XYZ ) { const intensity = D65[ wavelength ]; @@ -261,7 +261,7 @@ const MAX_XYZ_INTENSITY = ( () => { // that each XYZ value is in the range [0,1]. Multiplying any entry componentwise with MAX_XYZ_INTENSITY // will result in the original XYZ_INTENSITIES value. const NORMALIZED_XYZ_INTENSITIES = ( () => { - const result = {}; + const result: { [ key: number ]: Vector3 } = {}; for ( const wavelength in XYZ_INTENSITIES ) { const xyz = XYZ_INTENSITIES[ wavelength ]; diff --git a/js/common/model/BendingLightModel.js b/js/common/model/BendingLightModel.ts similarity index 77% rename from js/common/model/BendingLightModel.js rename to js/common/model/BendingLightModel.ts index ec8100a5..2f677b3b 100644 --- a/js/common/model/BendingLightModel.js +++ b/js/common/model/BendingLightModel.ts @@ -15,6 +15,7 @@ import Utils from '../../../../scenery/js/util/Utils.js'; import bendingLight from '../../bendingLight.js'; import BendingLightConstants from '../BendingLightConstants.js'; import Laser from './Laser.js'; +import LightRay from './LightRay.js'; import MediumColorFactory from './MediumColorFactory.js'; // constants @@ -23,19 +24,36 @@ const DEFAULT_LASER_DISTANCE_FROM_PIVOT = 9.225E-6; // a good size for the units being used in the sim; used to determine the dimensions of various model objects const CHARACTERISTIC_LENGTH = BendingLightConstants.WAVELENGTH_RED; -class BendingLightModel { +abstract class BendingLightModel { + readonly rays: LightRay[]; // Comes from ObservableArray + readonly mediumColorFactory: MediumColorFactory; + readonly modelWidth: number; + readonly modelHeight: number; + readonly allowWebGL: boolean; + readonly laserViewProperty: Property; + readonly wavelengthProperty: Property; + readonly speedProperty: Property; + private readonly indexOfRefractionProperty: Property; + readonly showNormalProperty: Property; + readonly isPlayingProperty: Property; + readonly showAnglesProperty: Property; + readonly laser: Laser; + static DEFAULT_LASER_DISTANCE_FROM_PIVOT: number; + rotationArrowAngleOffset: number | null; /** * @param {number} laserAngle - laser angle in radians * @param {boolean} topLeftQuadrant - specifies whether laser in topLeftQuadrant * @param {number} laserDistanceFromPivot - distance of laser from pivot point - * @param {Object} [properties] - additional properties to add to the property set */ - constructor( laserAngle, topLeftQuadrant, laserDistanceFromPivot, properties ) { + constructor( laserAngle: number, topLeftQuadrant: boolean, laserDistanceFromPivot: number ) { // @public (read-only)- list of rays in the model this.rays = createObservableArray(); + // overriden in subtypes + this.rotationArrowAngleOffset = null; + this.mediumColorFactory = new MediumColorFactory(); // dimensions of the model, guaranteed to be shown in entirety on the stage @@ -49,7 +67,7 @@ class BendingLightModel { this.laserViewProperty = new Property( 'ray' ); // @public, Whether the laser is Ray or Wave mode // TODO: Enumeration this.wavelengthProperty = new Property( BendingLightConstants.WAVELENGTH_RED ); this.isPlayingProperty = new Property( true ); - this.speedProperty = new Property( TimeSpeed.NORMAL ); + this.speedProperty = new Property( 'normal' ); this.indexOfRefractionProperty = new Property( 1 ); this.showNormalProperty = new Property( true ); this.showAnglesProperty = new Property( false ); @@ -63,8 +81,8 @@ class BendingLightModel { * @public * @param {LightRay} ray - model of light ray */ - addRay( ray ) { - this.rays.add( ray ); + addRay( ray: LightRay ) { + this.rays.push( ray ); } /** @@ -73,9 +91,9 @@ class BendingLightModel { */ clearModel() { for ( let i = 0; i < this.rays.length; i++ ) { - this.rays.get( i ).particles.clear(); + this.rays[ i ].particles.clear(); } - this.rays.clear(); + this.rays.length = 0; } /** @@ -87,6 +105,8 @@ class BendingLightModel { this.propagateRays(); } + abstract propagateRays(): void; + /** * @public * @override @@ -111,7 +131,7 @@ class BendingLightModel { * @param {number} cosTheta2 - cosine of reflected angle * @returns {number} */ - static getReflectedPower( n1, n2, cosTheta1, cosTheta2 ) { + static getReflectedPower( n1: number, n2: number, cosTheta1: number, cosTheta2: number ) { return Math.pow( ( n1 * cosTheta1 - n2 * cosTheta2 ) / ( n1 * cosTheta1 + n2 * cosTheta2 ), 2 ); } @@ -124,7 +144,7 @@ class BendingLightModel { * @param {number} cosTheta2 - cosine of transmitted angle * @returns {number} */ - static getTransmittedPower( n1, n2, cosTheta1, cosTheta2 ) { + static getTransmittedPower( n1: number, n2: number, cosTheta1: number, cosTheta2: number ) { return 4 * n1 * n2 * cosTheta1 * cosTheta2 / ( Math.pow( n1 * cosTheta1 + n2 * cosTheta2, 2 ) ); } } diff --git a/js/common/model/DispersionFunction.js b/js/common/model/DispersionFunction.ts similarity index 91% rename from js/common/model/DispersionFunction.js rename to js/common/model/DispersionFunction.ts index 7c48e877..a024c5e7 100644 --- a/js/common/model/DispersionFunction.js +++ b/js/common/model/DispersionFunction.ts @@ -15,12 +15,14 @@ import bendingLight from '../../bendingLight.js'; import BendingLightConstants from '../BendingLightConstants.js'; class DispersionFunction { + private readonly referenceIndexOfRefraction: number; + private readonly referenceWavelength: number; /** * @param {number} referenceIndexOfRefraction - IndexOfRefraction of medium * @param {number} wavelength - wavelength in meters */ - constructor( referenceIndexOfRefraction, wavelength ) { + constructor( referenceIndexOfRefraction: number, wavelength: number ) { this.referenceIndexOfRefraction = referenceIndexOfRefraction; // @public (read-only) this.referenceWavelength = wavelength; // @public (read-only) } @@ -31,7 +33,7 @@ class DispersionFunction { * @param {number} wavelength - wavelength in meters * @returns {number} */ - getSellmeierValue( wavelength ) { + getSellmeierValue( wavelength: number ) { const L2 = wavelength * wavelength; const B1 = 1.03961212; const B2 = 0.231792344; @@ -59,7 +61,7 @@ class DispersionFunction { * @returns {number} * @public */ - getIndexOfRefraction( wavelength ) { + getIndexOfRefraction( wavelength: number ) { // get the reference values const nAirReference = this.getAirIndex( this.referenceWavelength ); @@ -82,7 +84,7 @@ class DispersionFunction { * @returns {number} * @private */ - getAirIndex( wavelength ) { + getAirIndex( wavelength: number ) { return 1 + 5792105E-8 / ( 238.0185 - Math.pow( wavelength * 1E6, -2 ) ) + 167917E-8 / ( 57.362 - Math.pow( wavelength * 1E6, -2 ) ); diff --git a/js/common/model/IntensityMeter.js b/js/common/model/IntensityMeter.ts similarity index 84% rename from js/common/model/IntensityMeter.js rename to js/common/model/IntensityMeter.ts index aac4349d..6c5dc112 100644 --- a/js/common/model/IntensityMeter.js +++ b/js/common/model/IntensityMeter.ts @@ -16,6 +16,11 @@ import bendingLight from '../../bendingLight.js'; import Reading from './Reading.js'; class IntensityMeter { + readonly readingProperty: Property; + readonly sensorPositionProperty: Vector2Property; + readonly bodyPositionProperty: Vector2Property; + private rayReadings: Reading[]; + readonly enabledProperty: Property; /** * @param {number} sensorX - sensor x position in model coordinates @@ -23,9 +28,9 @@ class IntensityMeter { * @param {number} bodyX - body x position in model coordinates * @param {number} bodyY - body y position in model coordinates */ - constructor( sensorX, sensorY, bodyX, bodyY ) { + constructor( sensorX: number, sensorY: number, bodyX: number, bodyY: number ) { - this.readingProperty = new Property( Reading.MISS ); // @public, value to show on the body + this.readingProperty = new Property( 'miss' ); // @public, value to show on the body this.sensorPositionProperty = new Vector2Property( new Vector2( sensorX, sensorY ) ); // @public this.bodyPositionProperty = new Vector2Property( new Vector2( bodyX, bodyY ) ); // @public this.enabledProperty = new Property( false ); // @public, True if it is in the play area @@ -74,7 +79,7 @@ class IntensityMeter { */ clearRayReadings() { this.rayReadings = []; - this.readingProperty.set( Reading.MISS ); + this.readingProperty.set( 'miss' ); } /** @@ -82,7 +87,7 @@ class IntensityMeter { * @public * @param {Reading} r - intensity of the wave or MISS */ - addRayReading( r ) { + addRayReading( r: Reading ) { this.rayReadings.push( r ); this.updateReading(); } @@ -94,7 +99,7 @@ class IntensityMeter { updateReading() { // enumerate the hits - const hits = []; + const hits: Reading[] = []; this.rayReadings.forEach( rayReading => { if ( rayReading.isHit() ) { hits.push( rayReading ); @@ -103,13 +108,13 @@ class IntensityMeter { // if not hits, say "MISS" if ( hits.length === 0 ) { - this.readingProperty.set( Reading.MISS ); + this.readingProperty.set( 'miss' ); } else { // otherwise, sum the intensities let total = 0.0; - hits.forEach( hit => { + hits.forEach( ( hit: Reading ) => { total += hit.value; } ); this.readingProperty.set( new Reading( total ) ); diff --git a/js/common/model/Laser.js b/js/common/model/Laser.ts similarity index 88% rename from js/common/model/Laser.js rename to js/common/model/Laser.ts index a761a38c..f42e0b89 100644 --- a/js/common/model/Laser.js +++ b/js/common/model/Laser.ts @@ -16,6 +16,15 @@ import BendingLightConstants from '../BendingLightConstants.js'; import LaserColor from '../view/LaserColor.js'; class Laser { + readonly topLeftQuadrant: boolean; + readonly onProperty: Property; + readonly waveProperty: Property; + readonly colorModeProperty: Property; + readonly emissionPointProperty: Vector2Property; + readonly colorProperty: DerivedProperty; + private readonly wavelengthProperty: Property; + private readonly directionUnitVector: Vector2; + readonly pivotProperty: Vector2Property; /** * @param {Property.} wavelengthProperty - wavelength of light @@ -23,7 +32,7 @@ class Laser { * @param {number} angle - laser angle * @param {boolean} topLeftQuadrant - specifies whether laser in topLeftQuadrant */ - constructor( wavelengthProperty, distanceFromPivot, angle, topLeftQuadrant ) { + constructor( wavelengthProperty: Property, distanceFromPivot: number, angle: number, topLeftQuadrant: boolean ) { this.topLeftQuadrant = topLeftQuadrant; this.pivotProperty = new Vector2Property( new Vector2( 0, 0 ) ); // @public, point to be pivoted about, and at which the laser points @@ -33,7 +42,7 @@ class Laser { this.emissionPointProperty = new Vector2Property( Vector2.createPolar( distanceFromPivot, angle ) ); // @public model the point where light comes out of the laser where the light comes from // @public (read-only) - this.colorProperty = new DerivedProperty( [ wavelengthProperty ], wavelength => new LaserColor( wavelength ) ); + this.colorProperty = new DerivedProperty( [ wavelengthProperty ], ( wavelength: number ) => new LaserColor( wavelength ) ); // @public (read-only) this.wavelengthProperty = wavelengthProperty; @@ -68,7 +77,7 @@ class Laser { * @param {number} deltaX - amount of space in x direction laser to be translated * @param {number} deltaY - amount of space in y direction laser to be translated */ - translate( deltaX, deltaY ) { + translate( deltaX: number, deltaY: number ) { // Caution -- For reasons unknown to @samreid, if the order of the following instructions is switched, the // laser will rotate while being dragged, see #221 @@ -93,7 +102,7 @@ class Laser { * @param {number} angle - angle to be rotated * @public */ - setAngle( angle ) { + setAngle( angle: number ) { const distFromPivot = this.pivotProperty.value.distance( this.emissionPointProperty.value ); this.emissionPointProperty.value = new Vector2( distFromPivot * Math.cos( angle ) + this.pivotProperty.value.x, diff --git a/js/common/model/LightRay.js b/js/common/model/LightRay.ts similarity index 90% rename from js/common/model/LightRay.js rename to js/common/model/LightRay.ts index b3ce81b0..7dd21864 100644 --- a/js/common/model/LightRay.js +++ b/js/common/model/LightRay.ts @@ -12,6 +12,7 @@ import Ray2 from '../../../../dot/js/Ray2.js'; import Vector2 from '../../../../dot/js/Vector2.js'; import Line from '../../../../kite/js/segments/Line.js'; import Shape from '../../../../kite/js/Shape.js'; +import Color from '../../../../scenery/js/util/Color.js'; import bendingLight from '../../bendingLight.js'; import BendingLightConstants from '../BendingLightConstants.js'; @@ -20,7 +21,7 @@ import BendingLightConstants from '../BendingLightConstants.js'; * If a vector is infinite, make it finite * @param vector */ -const makeFinite = vector => { +const makeFinite = ( vector: Vector2 ) => { if ( !isFinite( vector.x ) ) { vector.x = 0; } @@ -30,6 +31,26 @@ const makeFinite = vector => { }; class LightRay { + extendBackwards: boolean; + color: any; + waveWidth: number; + trapeziumWidth: number; + tip: Vector2; + tail: Vector2; + indexOfRefraction: number; + wavelength: number; + wavelengthInVacuum: number; + powerFraction: number; + numWavelengthsPhaseOffset: number; + extend: boolean; + vectorForm: Vector2; + particles: any; + time: number; + rayType: string; + unitVector: Vector2; + waveShape: Shape | null; + clipRegionCorners: Vector2[] | null; + static RAY_WIDTH: number; /** * @param {number} trapeziumWidth - width of wave at intersection of mediums @@ -47,10 +68,13 @@ class LightRay { * @param {string} laserView - specifies the laser view whether ray or wave mode * @param {string} rayType - for the intro model, 'incident' | 'reflected' | 'transmitted' | 'prism' */ - constructor( trapeziumWidth, tail, tip, indexOfRefraction, wavelength, wavelengthInVacuum, powerFraction, color, - waveWidth, numWavelengthsPhaseOffset, extend, extendBackwards, laserView, rayType ) { + constructor( trapeziumWidth: number, tail: Vector2, tip: Vector2, indexOfRefraction: number, wavelength: number, wavelengthInVacuum: number, powerFraction: number, color: Color, + waveWidth: number, numWavelengthsPhaseOffset: number, extend: boolean, extendBackwards: boolean, laserView: string, rayType: string ) { + this.waveShape = null; + this.clipRegionCorners = null; + // fill in the triangular chip near y=0 even for truncated beams, if it is the transmitted beam // Light must be extended backwards for the transmitted wave shape to be correct this.extendBackwards = extendBackwards; // @public (read-only) @@ -158,7 +182,7 @@ class LightRay { * @public * @param {number} time - simulation time */ - setTime( time ) { + setTime( time: number ) { this.time = time; } @@ -177,7 +201,7 @@ class LightRay { * @returns {Ray2} * @private */ - createParallelRay( distance, rayType ) { + createParallelRay( distance: number, rayType: string ) { const perpendicular = Vector2.createPolar( distance, this.getAngle() + Math.PI / 2 ); const t = rayType === 'incident' ? this.tip : this.tail; const tail = t.plus( perpendicular ); @@ -191,7 +215,7 @@ class LightRay { * @param {string} rayType - 'incident', 'transmitted' or 'reflected' * @returns {Array} */ - getIntersections( sensorRegion, rayType ) { + getIntersections( sensorRegion: Shape, rayType: string ) { if ( this.waveShape ) { @@ -286,11 +310,12 @@ class LightRay { * @param {boolean} waveMode - specifies whether ray or wave mode * @returns {boolean} */ - contains( position, waveMode ) { - if ( waveMode ) { + contains( position: Vector2, waveMode: boolean ) { + if ( waveMode && this.waveShape ) { return this.waveShape.containsPoint( position ); } else { + // @ts-ignore return this.toLine().explicitClosestToPoint( position )[ 0 ].distanceSquared < 1E-14; } } @@ -333,7 +358,7 @@ class LightRay { * @param {number} distanceAlongRay - distance of a specific point from the start of the ray * @returns {number} */ - getCosArg( distanceAlongRay ) { + getCosArg( distanceAlongRay: number ) { const w = this.getAngularFrequency(); const k = 2 * Math.PI / this.wavelength; const x = distanceAlongRay; diff --git a/js/common/model/Medium.js b/js/common/model/Medium.ts similarity index 79% rename from js/common/model/Medium.js rename to js/common/model/Medium.ts index 9e778527..b7b0396c 100644 --- a/js/common/model/Medium.js +++ b/js/common/model/Medium.ts @@ -7,19 +7,26 @@ * @author Chandrashekar Bemagoni (Actual Concepts) */ +import Shape from '../../../../kite/js/Shape.js'; +import Color from '../../../../scenery/js/util/Color.js'; import IOType from '../../../../tandem/js/types/IOType.js'; import StringIO from '../../../../tandem/js/types/StringIO.js'; import VoidIO from '../../../../tandem/js/types/VoidIO.js'; import bendingLight from '../../bendingLight.js'; +import Substance from './Substance.js'; class Medium { + shape: Shape; + substance: Substance; + color: Color; + static MediumIO: IOType; /** * @param {Shape} shape - shape of the medium * @param {Substance} substance - state of the medium * @param {Color} color - color of the medium */ - constructor( shape, substance, color ) { + constructor( shape: Shape, substance: Substance, color: Color ) { // immutable shape this.shape = shape; // @public (read-only) @@ -33,7 +40,7 @@ class Medium { * @param {number} wavelength - wavelength of the medium * @returns {number} */ - getIndexOfRefraction( wavelength ) { + getIndexOfRefraction( wavelength: number ) { return this.substance.dispersionFunction.getIndexOfRefraction( wavelength ); } @@ -55,7 +62,8 @@ Medium.MediumIO = new IOType( 'MediumIO', { setName: { returnType: VoidIO, parameterTypes: [ StringIO ], - implementation: text => { + implementation: ( text: string ) => { + // @ts-ignore this.name = text; }, documentation: 'Set the name of the solute', @@ -65,7 +73,8 @@ Medium.MediumIO = new IOType( 'MediumIO', { setFormula: { returnType: VoidIO, parameterTypes: [ StringIO ], - implementation: text => { + implementation: ( text: string ) => { + // @ts-ignore this.formula = text; }, documentation: 'Set the formula of the solute', @@ -74,7 +83,7 @@ Medium.MediumIO = new IOType( 'MediumIO', { }, // TODO: This needs to be implemented - toStateObject( medium ) { + toStateObject( medium: Medium ) { return {}; } } ); diff --git a/js/common/model/MediumColorFactory.js b/js/common/model/MediumColorFactory.ts similarity index 82% rename from js/common/model/MediumColorFactory.js rename to js/common/model/MediumColorFactory.ts index b2118f40..9662261e 100644 --- a/js/common/model/MediumColorFactory.js +++ b/js/common/model/MediumColorFactory.ts @@ -15,6 +15,9 @@ import bendingLight from '../../bendingLight.js'; import Substance from './Substance.js'; class MediumColorFactory { + lightTypeProperty: Property; + getColorAgainstWhite: ( indexForRed: number ) => Color; + getColorAgainstBlack: ( indexForRed: number ) => Color; constructor() { this.lightTypeProperty = new Property( 'singleColor' ); // could also be 'white' @@ -47,7 +50,7 @@ class MediumColorFactory { * @returns {Color} * @public */ - getColor( indexForRed ) { + getColor( indexForRed: number ) { return this.lightTypeProperty.value === 'singleColor' ? this.getColorAgainstWhite( indexForRed ) : this.getColorAgainstBlack( indexForRed ); @@ -60,7 +63,7 @@ class MediumColorFactory { * @param {number} value * @returns {number} */ -const clamp = value => Utils.clamp( value, 0, 255 ); +const clamp = ( value: number ) => Utils.clamp( value, 0, 255 ); /** * Blend colors a and b with the specified amount of "b" to use between 0 and 1 @@ -70,7 +73,7 @@ const clamp = value => Utils.clamp( value, 0, 255 ); * @param {number} ratio * @returns {Color} */ -const colorBlend = ( a, b, ratio ) => { +const colorBlend = ( a: Color, b: Color, ratio: number ): Color => { const reduction = ( 1 - ratio ); return new Color( clamp( a.getRed() * reduction + b.getRed() * ratio ), @@ -80,7 +83,7 @@ const colorBlend = ( a, b, ratio ) => { ); }; -const createProfile = ( AIR_COLOR, WATER_COLOR, GLASS_COLOR, DIAMOND_COLOR ) => indexForRed => { +const createProfile = ( AIR_COLOR:Color, WATER_COLOR:Color, GLASS_COLOR:Color, DIAMOND_COLOR:Color ) => ( indexForRed: number) => { // precompute to improve readability below const waterIndexForRed = Substance.WATER.indexOfRefractionForRedLight; @@ -88,35 +91,25 @@ const createProfile = ( AIR_COLOR, WATER_COLOR, GLASS_COLOR, DIAMOND_COLOR ) => const diamondIndexForRed = Substance.DIAMOND.indexOfRefractionForRedLight; // find out what region the index of refraction lies in, and linearly interpolate between adjacent medium colors - let linearFunction; let ratio; if ( indexForRed < waterIndexForRed ) { // Map the values between 1 and waterIndexForRed to (0,1) linearly - linearFunction = new LinearFunction( 1.0, waterIndexForRed, 0, 1 ); - - // returns the value between 0 to 1. - ratio = linearFunction( indexForRed ); + ratio = Utils.linear( 1.0, waterIndexForRed, 0, 1, indexForRed ); return colorBlend( AIR_COLOR, WATER_COLOR, ratio ); } else { if ( indexForRed < glassIndexForRed ) { // Map the values between waterIndexForRed and glassIndexForRed to (0,1) linearly - linearFunction = new LinearFunction( waterIndexForRed, glassIndexForRed, 0, 1 ); - - // returns the value between 0 to 1. - ratio = linearFunction( indexForRed ); + ratio = Utils.linear( waterIndexForRed, glassIndexForRed, 0, 1, indexForRed ); return colorBlend( WATER_COLOR, GLASS_COLOR, ratio ); } else { if ( indexForRed < diamondIndexForRed ) { // Map the values between glassIndexForRed and diamondIndexForRed to (0,1) linearly - linearFunction = new LinearFunction( glassIndexForRed, diamondIndexForRed, 0, 1 ); - - // returns the value between 0 to 1. - ratio = linearFunction( indexForRed ); + ratio = Utils.linear( glassIndexForRed, diamondIndexForRed, 0, 1, indexForRed ); return colorBlend( GLASS_COLOR, DIAMOND_COLOR, ratio ); } else { diff --git a/js/common/model/Reading.js b/js/common/model/Reading.ts similarity index 90% rename from js/common/model/Reading.js rename to js/common/model/Reading.ts index cab4edef..950f30f9 100644 --- a/js/common/model/Reading.js +++ b/js/common/model/Reading.ts @@ -15,19 +15,22 @@ import bendingLight from '../../bendingLight.js'; // strings const missString = MathSymbols.NO_VALUE; +// @ts-ignore const pattern0ValuePercentString = bendingLightStrings.pattern_0value_percent; // constants const VALUE_DECIMALS = 2; class Reading { + readonly value: number; + static MISS: Reading; /** * A single reading for the intensity meter * * @param {string} value - the text to be shown on the intensity meter */ - constructor( value ) { + constructor( value: number ) { // @public (read-only) this.value = value; @@ -47,7 +50,7 @@ class Reading { * @param {number} value - value to be displayed on intensity meter * @returns {string} */ - format( value ) { + format( value: number ) { return StringUtils.format( pattern0ValuePercentString, Utils.toFixed( value, VALUE_DECIMALS ) ); } @@ -62,6 +65,8 @@ class Reading { } Reading.MISS = { + value: 0, + format( value: number ) {return '';}, /** * Get string to display on intensity sensor diff --git a/js/common/model/Substance.js b/js/common/model/Substance.ts similarity index 78% rename from js/common/model/Substance.js rename to js/common/model/Substance.ts index 02824b3f..f1c0292e 100644 --- a/js/common/model/Substance.js +++ b/js/common/model/Substance.ts @@ -12,17 +12,36 @@ import bendingLight from '../../bendingLight.js'; import BendingLightConstants from '../BendingLightConstants.js'; import DispersionFunction from './DispersionFunction.js'; +// @ts-ignore const airString = bendingLightStrings.air; +// @ts-ignore const diamondString = bendingLightStrings.diamond; +// @ts-ignore const glassString = bendingLightStrings.glass; +// @ts-ignore const mysteryAString = bendingLightStrings.mysteryA; +// @ts-ignore const mysteryBString = bendingLightStrings.mysteryB; +// @ts-ignore const waterString = bendingLightStrings.water; // constants const DIAMOND_INDEX_OF_REFRACTION_FOR_RED_LIGHT = 2.419; class Substance { + readonly dispersionFunction: DispersionFunction; + readonly mystery: boolean; + readonly indexOfRefractionForRedLight: number; + private readonly name: string; + readonly indexForRed: number; + private readonly custom: boolean; + static AIR: Substance; + static WATER: Substance; + static GLASS: Substance; + static DIAMOND: Substance; + static MYSTERY_A: Substance; + static MYSTERY_B: Substance; + static DIAMOND_INDEX_OF_REFRACTION_FOR_RED_LIGHT: number; /** * @param {string} name - name of the medium @@ -30,7 +49,7 @@ class Substance { * @param {boolean} mystery - true if medium state is mystery else other state * @param {boolean} custom - true if medium state is custom else other state */ - constructor( name, indexForRed, mystery, custom ) { + constructor( name: string, indexForRed: number, mystery: boolean, custom: boolean ) { this.name = name; // @public (read-only) this.dispersionFunction = new DispersionFunction( indexForRed, BendingLightConstants.WAVELENGTH_RED ); // @public (read-only) this.mystery = mystery; // @public (read-only) diff --git a/js/common/model/WaveParticle.js b/js/common/model/WaveParticle.ts similarity index 75% rename from js/common/model/WaveParticle.js rename to js/common/model/WaveParticle.ts index 13e9a26a..ae523381 100644 --- a/js/common/model/WaveParticle.js +++ b/js/common/model/WaveParticle.ts @@ -8,9 +8,17 @@ * @author Chandrashekar Bemagoni (Actual Concepts) */ +import Vector2 from '../../../../dot/js/Vector2.js'; +import Color from '../../../../scenery/js/util/Color.js'; import bendingLight from '../../bendingLight.js'; class WaveParticle { + private readonly position: Vector2; + private readonly width: number; + private readonly color: Color; + private readonly angle: number; + private readonly height: number; + private readonly particleGradientColor: Color; /** * @param {Vector2} position - position of wave particle @@ -20,7 +28,7 @@ class WaveParticle { * @param {number} angle - angle of wave particle * @param {number} waveHeight - height of wave particle */ - constructor( position, width, color, particleGradientColor, angle, waveHeight ) { + constructor( position: Vector2, width: number, color: Color, particleGradientColor: Color, angle: number, waveHeight: number ) { this.position = position; // @public this.width = width; // @public this.color = color; // @public @@ -52,7 +60,7 @@ class WaveParticle { * @public * @param {number} x - x position in model values */ - setX( x ) { + setX( x: number ) { this.position.x = x; } @@ -60,7 +68,7 @@ class WaveParticle { * @public * @param {number} y - y position in model values */ - setY( y ) { + setY( y: number ) { this.position.y = y; } } diff --git a/js/common/view/BendingLightScreenView.js b/js/common/view/BendingLightScreenView.ts similarity index 73% rename from js/common/view/BendingLightScreenView.js rename to js/common/view/BendingLightScreenView.ts index 5f10cdfa..d3ba1f66 100644 --- a/js/common/view/BendingLightScreenView.js +++ b/js/common/view/BendingLightScreenView.ts @@ -15,11 +15,25 @@ import merge from '../../../../phet-core/js/merge.js'; import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import Node from '../../../../scenery/js/nodes/Node.js'; import bendingLight from '../../bendingLight.js'; +import BendingLightModel from '../model/BendingLightModel.js'; import LaserNode from './LaserNode.js'; import RotationDragHandle from './RotationDragHandle.js'; import SingleColorLightCanvasNode from './SingleColorLightCanvasNode.js'; -class BendingLightScreenView extends ScreenView { +abstract class BendingLightScreenView extends ScreenView { + protected readonly showProtractorProperty: Property; + readonly bendingLightModel: BendingLightModel; + protected readonly beforeLightLayer: Node; + protected readonly beforeLightLayer2: Node; + private readonly afterLightLayer: Node; + protected readonly afterLightLayer2: Node; + protected readonly afterLightLayer3: Node; + protected readonly mediumNode: Node; + readonly incidentWaveLayer: Node; + private readonly singleColorLightNode: SingleColorLightCanvasNode; + readonly laserViewLayer: Node; + private readonly occlusionHandler: () => void; + protected readonly modelViewTransform: ModelViewTransform2; /** * @param {BendingLightModel} bendingLightModel - main model of the simulations @@ -28,14 +42,14 @@ class BendingLightScreenView extends ScreenView { * @param {boolean} laserHasKnob - laser image * @param {Object} [options] */ - constructor( bendingLightModel, laserTranslationRegion, laserRotationRegion, laserHasKnob, options ) { + constructor( bendingLightModel: BendingLightModel, laserTranslationRegion: any, laserRotationRegion: any, laserHasKnob: boolean, options: any ) { options = merge( { occlusionHandler: () => {}, // {function} moves objects out from behind a control panel if dropped there ccwArrowNotAtMax: () => true, // {function} shows whether laser at min angle clockwiseArrowNotAtMax: () => true, // {function} shows whether laser at max angle, In prisms tab // laser node can rotate 360 degrees.so arrows showing all the times when laser node rotate - clampDragAngle: angle => angle, // {function} function that limits the angle of laser to its bounds + clampDragAngle: ( angle: number ) => angle, // {function} function that limits the angle of laser to its bounds horizontalPlayAreaOffset: 0, // {number} in stage coordinates, how far to shift the play area horizontally verticalPlayAreaOffset: 0 // {number} in stage coordinates, how far to shift the play area vertically. In the // prisms screen, it is shifted up a bit to center the play area above the south control panel @@ -143,7 +157,7 @@ class BendingLightScreenView extends ScreenView { } ); Property.multilink( [ bendingLightModel.laser.colorModeProperty, bendingLightModel.laserViewProperty ], - ( colorMode, laserView ) => { + ( colorMode: 'white' | 'singleWavelength', laserView: 'ray' | 'wave' ) => { // TODO: enums this.singleColorLightNode.visible = laserView === 'ray' && colorMode !== 'white'; } ); @@ -170,35 +184,39 @@ class BendingLightScreenView extends ScreenView { * @param {BendingLightModel} bendingLightModel * @protected */ - addLightNodes( bendingLightModel ) { - } + abstract addLightNodes( bendingLightModel: BendingLightModel ): void; /** - * @param {boolean} showRotationDragHandlesProperty - * @param {boolean} showTranslationDragHandlesProperty + * @param {Property} showRotationDragHandlesProperty + * @param {Property} showTranslationDragHandlesProperty * @param {boolean}clockwiseArrowNotAtMax * @param {boolean} ccwArrowNotAtMax * @param {number} laserImageWidth * @protected */ - addLaserHandles( showRotationDragHandlesProperty, showTranslationDragHandlesProperty, - clockwiseArrowNotAtMax, ccwArrowNotAtMax, laserImageWidth ) { + addLaserHandles( showRotationDragHandlesProperty: Property, showTranslationDragHandlesProperty: Property, + clockwiseArrowNotAtMax: ( n: number ) => boolean, ccwArrowNotAtMax: ( n: number ) => boolean, laserImageWidth: number ) { const bendingLightModel = this.bendingLightModel; - // Shows the direction in which laser can be rotated - // for laser left rotation - const leftRotationDragHandle = new RotationDragHandle( this.modelViewTransform, bendingLightModel.laser, - Math.PI / 23, showRotationDragHandlesProperty, clockwiseArrowNotAtMax, laserImageWidth * 0.58, - bendingLightModel.rotationArrowAngleOffset ); - this.addChild( leftRotationDragHandle ); - - // for laser right rotation - const rightRotationDragHandle = new RotationDragHandle( this.modelViewTransform, bendingLightModel.laser, - -Math.PI / 23, - showRotationDragHandlesProperty, ccwArrowNotAtMax, laserImageWidth * 0.58, - bendingLightModel.rotationArrowAngleOffset - ); - this.addChild( rightRotationDragHandle ); + if ( typeof bendingLightModel.rotationArrowAngleOffset === 'number' ) { + // Shows the direction in which laser can be rotated + // for laser left rotation + const leftRotationDragHandle = new RotationDragHandle( this.modelViewTransform, bendingLightModel.laser, + Math.PI / 23, showRotationDragHandlesProperty, clockwiseArrowNotAtMax, laserImageWidth * 0.58, + bendingLightModel.rotationArrowAngleOffset ); + this.addChild( leftRotationDragHandle ); + + // for laser right rotation + const rightRotationDragHandle = new RotationDragHandle( this.modelViewTransform, bendingLightModel.laser, + -Math.PI / 23, + showRotationDragHandlesProperty, ccwArrowNotAtMax, laserImageWidth * 0.58, + bendingLightModel.rotationArrowAngleOffset + ); + this.addChild( rightRotationDragHandle ); + } + else { + assert && assert( false, 'should have been a number' ); + } } } diff --git a/js/common/view/FloatingLayout.js b/js/common/view/FloatingLayout.ts similarity index 87% rename from js/common/view/FloatingLayout.js rename to js/common/view/FloatingLayout.ts index 49872a76..5297b41b 100644 --- a/js/common/view/FloatingLayout.js +++ b/js/common/view/FloatingLayout.ts @@ -7,7 +7,9 @@ * @author Sam Reid (PhET Interactive Simulations) */ +import ScreenView from '../../../../joist/js/ScreenView.js'; import bendingLight from '../../bendingLight.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; // The fraction the objects can float out of the layout bounds const floatFraction = 0.3; @@ -24,7 +26,7 @@ const FloatingLayout = { * @param {ScreenView} screenView * @param {Node[]} nodes */ - floatRight: ( screenView, nodes ) => { + floatRight: ( screenView: ScreenView, nodes: Node[] ) => { screenView.visibleBoundsProperty.link( visibleBounds => { // Let the panels move to the right, but not too far @@ -42,8 +44,7 @@ const FloatingLayout = { * @param {Node[]} nodes * @param {number} delta */ - floatLeft: ( screenView, nodes, delta ) => { - delta = delta || 0; + floatLeft: ( screenView: ScreenView, nodes: Node[], delta: number = 0 ) => { screenView.visibleBoundsProperty.link( visibleBounds => { // Let the panels move to the left, but not too far @@ -60,7 +61,7 @@ const FloatingLayout = { * @param {ScreenView} screenView * @param {Node[]} nodes */ - floatTop: ( screenView, nodes ) => { + floatTop: ( screenView: ScreenView, nodes: Node[] ) => { screenView.visibleBoundsProperty.link( visibleBounds => { // Let the panels move to the top, but not too far @@ -77,7 +78,7 @@ const FloatingLayout = { * @param {ScreenView} screenView * @param {Node[]} nodes */ - floatBottom: ( screenView, nodes ) => { + floatBottom: ( screenView: ScreenView, nodes: Node[] ) => { screenView.visibleBoundsProperty.link( visibleBounds => { // Let the panels move to the bottom, but not too far diff --git a/js/common/view/IntensityMeterNode.js b/js/common/view/IntensityMeterNode.ts similarity index 90% rename from js/common/view/IntensityMeterNode.js rename to js/common/view/IntensityMeterNode.ts index 1b6f5242..bda3530b 100644 --- a/js/common/view/IntensityMeterNode.js +++ b/js/common/view/IntensityMeterNode.ts @@ -22,7 +22,10 @@ import LinearGradient from '../../../../scenery/js/util/LinearGradient.js'; import NodeProperty from '../../../../scenery/js/util/NodeProperty.js'; import bendingLightStrings from '../../bendingLightStrings.js'; import bendingLight from '../../bendingLight.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; +import IntensityMeter from '../model/IntensityMeter.js'; +// @ts-ignore const intensityString = bendingLightStrings.intensity; // constants @@ -31,12 +34,19 @@ const bodyNormalProperty = new Vector2Property( new Vector2( NORMAL_DISTANCE, 0 const sensorNormalProperty = new Vector2Property( new Vector2( 0, NORMAL_DISTANCE ) ); class IntensityMeterNode extends Node { + modelViewTransform: ModelViewTransform2; + probeNode: ProbeNode; + intensityMeter: IntensityMeter; + bodyNode: Node; + wireNode: WireNode; + syncModelFromView: () => void; + /** * @param {ModelViewTransform2} modelViewTransform - Transform between model and view coordinate frames * @param {IntensityMeter} intensityMeter - model for the intensity meter * @param {Object} [options] */ - constructor( modelViewTransform, intensityMeter, options ) { + constructor( modelViewTransform: ModelViewTransform2, intensityMeter: IntensityMeter, options?: any ) { super(); this.modelViewTransform = modelViewTransform; // @public (read-only) @@ -84,7 +94,7 @@ class IntensityMeterNode extends Node { } // Add the reading to the body node - const valueNode = new Text( intensityMeter.readingProperty.get().getString(), { + const valueNode = new Text( intensityMeter.readingProperty.get(), { font: new PhetFont( 25 ), fill: 'black', maxWidth: valueBackground.width * 0.85 @@ -101,18 +111,21 @@ class IntensityMeterNode extends Node { // displayed value intensityMeter.readingProperty.link( reading => { - valueNode.setText( reading.getString() ); + valueNode.setText( reading ); valueNode.center = valueBackground.center; } ); // Connect the sensor to the body with a gray wire - const above = amount => position => position.plusXY( 0, -amount ); + const above = ( amount: number ) => ( position: Vector2 ) => position.plusXY( 0, -amount ); + // @ts-ignore const rightBottomProperty = new NodeProperty( this.bodyNode, this.bodyNode.boundsProperty, 'rightBottom' ); // @private this.wireNode = new WireNode( new DerivedProperty( [ rightBottomProperty ], above( 12 ) ), bodyNormalProperty, + + // @ts-ignore new NodeProperty( this.probeNode, this.probeNode.boundsProperty, 'centerBottom' ), sensorNormalProperty, { lineWidth: 3, stroke: 'gray' diff --git a/js/common/view/LaserColor.js b/js/common/view/LaserColor.ts similarity index 91% rename from js/common/view/LaserColor.js rename to js/common/view/LaserColor.ts index c79855ab..9943502b 100644 --- a/js/common/view/LaserColor.js +++ b/js/common/view/LaserColor.ts @@ -11,11 +11,12 @@ import VisibleColor from '../../../../scenery-phet/js/VisibleColor.js'; import bendingLight from '../../bendingLight.js'; class LaserColor { + private readonly wavelength: number; /** * @param {number} wavelength - wavelength (in meters) of the light */ - constructor( wavelength ) { + constructor( wavelength: number ) { this.wavelength = wavelength; // @public } diff --git a/js/common/view/LaserNode.js b/js/common/view/LaserNode.ts similarity index 79% rename from js/common/view/LaserNode.js rename to js/common/view/LaserNode.ts index cf5fd55b..5d2d71c5 100644 --- a/js/common/view/LaserNode.js +++ b/js/common/view/LaserNode.ts @@ -19,8 +19,12 @@ import Image from '../../../../scenery/js/nodes/Image.js'; import bendingLight from '../../bendingLight.js'; import BendingLightConstants from '../BendingLightConstants.js'; import knobImageData from '../../../images/knob_png.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; +import Laser from '../model/Laser.js'; class LaserNode extends Node { + laserImageWidth: number; + translateViewXY: ( x: number, y: number ) => void; /** * @param {ModelViewTransform2} modelViewTransform - Transform between model and view coordinate frames @@ -38,9 +42,9 @@ class LaserNode extends Node { * there * @param {Object} [options] */ - constructor( modelViewTransform, laser, showRotationDragHandlesProperty, showTranslationDragHandlesProperty, - clampDragAngle, translationRegion, rotationRegion, hasKnob, dragBoundsProperty, - occlusionHandler, options ) { + constructor( modelViewTransform: ModelViewTransform2, laser: Laser, showRotationDragHandlesProperty: Property, showTranslationDragHandlesProperty: Property, + clampDragAngle: ( n: number ) => number, translationRegion: any, rotationRegion: any, hasKnob: boolean, dragBoundsProperty: Property, + occlusionHandler: any, options: any ) { const laserPointerNode = new LaserPointerNode( laser.onProperty, { bodySize: new Dimension2( 70, 30 ), @@ -50,10 +54,15 @@ class LaserNode extends Node { buttonYMargin: 2, cornerRadius: 2, buttonTouchAreaDilation: 4, - getButtonLocation: bodyNode => bodyNode.rightCenter.blend( bodyNode.center, 0.5 ) - } ); + getButtonLocation: ( bodyNode: Node ) => bodyNode.rightCenter.blend( bodyNode.center, 0.5 ) + } ) as Node; + + const knobImage = hasKnob ? ( new Image( knobImageData, { + scale: 0.58, + rightCenter: laserPointerNode.leftCenter - const knobImage = hasKnob ? new Image( knobImageData, { scale: 0.58, rightCenter: laserPointerNode.leftCenter } ) : null; + // This will be resolved when Image is TypeScript + } ) as unknown as Node ) : null; if ( knobImage ) { knobImage.touchArea = knobImage.localBounds.dilatedXY( 15, 27 ).shiftedX( -15 ); hasKnob && laserPointerNode.addChild( knobImage ); @@ -66,7 +75,7 @@ class LaserNode extends Node { // If there is a knob ("Prisms" screen), it is used to rotate the laser. // If there is no knob ("Intro" and "More Tools" screens), then the laser is rotated by dragging the laser itself. - const rotationTarget = hasKnob ? knobImage : laserPointerNode; + const rotationTarget = ( hasKnob ? knobImage : laserPointerNode ) as Node; // When mousing over or starting to drag the laser, increment the over count. If it is more than zero // then show the drag handles. This ensures they will be shown whenever dragging or over, and they won't flicker @@ -99,45 +108,47 @@ class LaserNode extends Node { } ); // add the drag region for translating the laser - let start; + let start: Vector2 | null; // On the "Prisms" screen, the laser can be translated by dragging the laser, and rotated by dragging the knob on // the back of the laser. This part of the code handles the translation. if ( hasKnob ) { const translationListener = new SimpleDragHandler( { - start: event => { + start: ( event: any ) => { start = this.globalToParentPoint( event.pointer.point ); showTranslationDragHandlesProperty.value = true; }, - drag: event => { + drag: ( event: any ) => { const laserNodeDragBounds = dragBoundsProperty.value.erodedXY( lightImageHeight / 2, lightImageHeight / 2 ); const laserDragBoundsInModelValues = modelViewTransform.viewToModelBounds( laserNodeDragBounds ); const endDrag = this.globalToParentPoint( event.pointer.point ); - const deltaX = modelViewTransform.viewToModelDeltaX( endDrag.x - start.x ); - const deltaY = modelViewTransform.viewToModelDeltaY( endDrag.y - start.y ); - - // position of final emission point with out constraining to bounds - emissionPointEndPosition.setXY( laser.emissionPointProperty.value.x + deltaX, laser.emissionPointProperty.value.y + deltaY ); - - // position of final emission point with constraining to bounds - const emissionPointEndPositionInBounds = laserDragBoundsInModelValues.closestPointTo( emissionPointEndPosition ); - - const translateX = emissionPointEndPositionInBounds.x - laser.emissionPointProperty.value.x; - const translateY = emissionPointEndPositionInBounds.y - laser.emissionPointProperty.value.y; - laser.translate( translateX, translateY ); - - // Store the position of caught point after translating. Can be obtained by adding distance between emission - // point and drag point (end - emissionPointEndPosition) to emission point (emissionPointEndPositionInBounds) - // after translating. - const boundsDx = emissionPointEndPositionInBounds.x - emissionPointEndPosition.x; - const boundsDY = emissionPointEndPositionInBounds.y - emissionPointEndPosition.y; - endDrag.x = endDrag.x + modelViewTransform.modelToViewDeltaX( boundsDx ); - endDrag.y = endDrag.y + modelViewTransform.modelToViewDeltaY( boundsDY ); - - start = endDrag; - showTranslationDragHandlesProperty.value = true; + if ( start ) { + const deltaX = modelViewTransform.viewToModelDeltaX( endDrag.x - start.x ); + const deltaY = modelViewTransform.viewToModelDeltaY( endDrag.y - start.y ); + + // position of final emission point with out constraining to bounds + emissionPointEndPosition.setXY( laser.emissionPointProperty.value.x + deltaX, laser.emissionPointProperty.value.y + deltaY ); + + // position of final emission point with constraining to bounds + const emissionPointEndPositionInBounds = laserDragBoundsInModelValues.closestPointTo( emissionPointEndPosition ); + + const translateX = emissionPointEndPositionInBounds.x - laser.emissionPointProperty.value.x; + const translateY = emissionPointEndPositionInBounds.y - laser.emissionPointProperty.value.y; + laser.translate( translateX, translateY ); + + // Store the position of caught point after translating. Can be obtained by adding distance between emission + // point and drag point (end - emissionPointEndPosition) to emission point (emissionPointEndPositionInBounds) + // after translating. + const boundsDx = emissionPointEndPositionInBounds.x - emissionPointEndPosition.x; + const boundsDY = emissionPointEndPositionInBounds.y - emissionPointEndPosition.y; + endDrag.x = endDrag.x + modelViewTransform.modelToViewDeltaX( boundsDx ); + endDrag.y = endDrag.y + modelViewTransform.modelToViewDeltaY( boundsDY ); + + start = endDrag; + showTranslationDragHandlesProperty.value = true; + } }, end: () => { showTranslationDragHandlesProperty.value = false; @@ -179,7 +190,7 @@ class LaserNode extends Node { showTranslationDragHandlesProperty.value = false; overCountProperty.value++; }, - drag: event => { + drag: ( event: any ) => { const coordinateFrame = this.parents[ 0 ]; const laserAnglebeforeRotate = laser.getAngle(); const localLaserPosition = coordinateFrame.globalToLocalPoint( event.pointer.point ); @@ -257,7 +268,7 @@ class LaserNode extends Node { * @param {number} y * @public */ - this.translateViewXY = ( x, y ) => { + this.translateViewXY = ( x: number, y: number ) => { const delta = modelViewTransform.viewToModelDeltaXY( x, y ); laser.translate( delta.x, delta.y ); }; diff --git a/js/common/view/MediumControlPanel.js b/js/common/view/MediumControlPanel.ts similarity index 92% rename from js/common/view/MediumControlPanel.js rename to js/common/view/MediumControlPanel.ts index d057e36d..0f8f3200 100644 --- a/js/common/view/MediumControlPanel.js +++ b/js/common/view/MediumControlPanel.ts @@ -29,12 +29,20 @@ import BendingLightConstants from '../BendingLightConstants.js'; import DispersionFunction from '../model/DispersionFunction.js'; import Medium from '../model/Medium.js'; import Substance from '../model/Substance.js'; +import BendingLightScreenView from './BendingLightScreenView.js'; +import MediumColorFactory from '../model/MediumColorFactory.js'; +// @ts-ignore const airString = bendingLightStrings.air; +// @ts-ignore const customString = bendingLightStrings.custom; +// @ts-ignore const glassString = bendingLightStrings.glass; +// @ts-ignore const indexOfRefractionString = bendingLightStrings.indexOfRefraction; +// @ts-ignore const unknownString = bendingLightStrings.unknown; +// @ts-ignore const waterString = bendingLightStrings.water; // constants @@ -44,6 +52,10 @@ const PLUS_MINUS_SPACING = 4; const INSET = 10; class MediumControlPanel extends Node { + mediumColorFactory: MediumColorFactory; + mediumProperty: Property; + laserWavelength: Property; // TODO: Rename to Property + mediumIndexProperty: Property; /** * @param {BendingLightScreenView} view - view of the simulation @@ -55,8 +67,8 @@ class MediumControlPanel extends Node { * @param {number} decimalPlaces - decimalPlaces to show for index of refraction * @param {Object} [options] - options that can be passed on to the underlying node */ - constructor( view, mediumColorFactory, mediumProperty, name, textFieldVisible, laserWavelength, - decimalPlaces, options ) { + constructor( view: BendingLightScreenView, mediumColorFactory: MediumColorFactory, mediumProperty: Property, name: string, textFieldVisible: boolean, laserWavelength: Property, + decimalPlaces: number, options: any ) { super(); this.mediumColorFactory = mediumColorFactory; @@ -95,7 +107,7 @@ class MediumControlPanel extends Node { const textOptionsOfComboBoxStrings = { font: new PhetFont( 10 ) }; - const createItem = item => { + const createItem = ( item: any ) => { const comboBoxTextWidth = textFieldVisible ? 130 : 75; const itemName = new Text( item.name, textOptionsOfComboBoxStrings ); if ( itemName.width > comboBoxTextWidth ) { @@ -146,6 +158,7 @@ class MediumControlPanel extends Node { items[ i ] = createItem( material ); } // add a combo box + // @ts-ignore const materialComboBox = new ComboBox( items, comboBoxSubstanceProperty, view, { labelNode: materialTitle, listPosition: options.comboBoxListPosition, @@ -333,12 +346,14 @@ class MediumControlPanel extends Node { } ); // disable the plus button when wavelength is at max and minus button at min wavelength - this.mediumIndexProperty.link( indexOfRefraction => { + this.mediumIndexProperty.link( ( indexOfRefraction: number ) => { if ( custom ) { this.setCustomIndexOfRefraction( indexOfRefraction ); } - plusButton.enabled = ( Utils.toFixed( indexOfRefraction, decimalPlaces ) < INDEX_OF_REFRACTION_MAX ); - minusButton.enabled = ( Utils.toFixed( indexOfRefraction, decimalPlaces ) > INDEX_OF_REFRACTION_MIN ); + + // TODO: This changed from string comparison, is it correct? + plusButton.enabled = ( Utils.roundToInterval( indexOfRefraction, decimalPlaces ) < INDEX_OF_REFRACTION_MAX ); + minusButton.enabled = ( Utils.roundToInterval( indexOfRefraction, decimalPlaces ) > INDEX_OF_REFRACTION_MIN ); } ); } @@ -355,7 +370,7 @@ class MediumControlPanel extends Node { * @public * @param {number} indexOfRefraction - indexOfRefraction of medium */ - setCustomIndexOfRefraction( indexOfRefraction ) { + setCustomIndexOfRefraction( indexOfRefraction: number ) { // have to pass the value through the dispersion function to account for the // current wavelength of the laser (since index of refraction is a function of wavelength) @@ -371,7 +386,7 @@ class MediumControlPanel extends Node { * @public * @param {Substance} substance - specifies state of the medium */ - setSubstance( substance ) { + setSubstance( substance: Substance ) { const color = this.mediumColorFactory.getColor( substance.indexOfRefractionForRedLight ); this.setMedium( new Medium( this.mediumProperty.get().shape, substance, color ) ); } @@ -381,7 +396,7 @@ class MediumControlPanel extends Node { * @private * @param {Medium} medium - specifies medium */ - setMedium( medium ) { + setMedium( medium: Medium ) { this.mediumProperty.set( medium ); } } diff --git a/js/common/view/MediumNode.js b/js/common/view/MediumNode.ts similarity index 82% rename from js/common/view/MediumNode.js rename to js/common/view/MediumNode.ts index 61c03188..9df801d8 100644 --- a/js/common/view/MediumNode.js +++ b/js/common/view/MediumNode.ts @@ -6,6 +6,8 @@ * @author Chandrashekar Bemagoni (Actual Concepts) */ +import Property from '../../../../axon/js/Property.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import Node from '../../../../scenery/js/nodes/Node.js'; import Path from '../../../../scenery/js/nodes/Path.js'; import bendingLight from '../../bendingLight.js'; @@ -16,7 +18,7 @@ class MediumNode extends Node { * @param {ModelViewTransform2} modelViewTransform - converts between model and view co-ordinates * @param {Property.} mediumProperty - specifies medium */ - constructor( modelViewTransform, mediumProperty ) { + constructor( modelViewTransform: ModelViewTransform2, mediumProperty: Property ) { super( { pickable: false } ); // user can't interact with the medium except through control panels. // add the shape that paints the medium @@ -28,6 +30,7 @@ class MediumNode extends Node { // Update whenever the medium changes mediumProperty.link( medium => { + // @ts-ignore mediumRectangleNode.fill = medium.color; } ); } diff --git a/js/common/view/RotationDragHandle.js b/js/common/view/RotationDragHandle.ts similarity index 86% rename from js/common/view/RotationDragHandle.js rename to js/common/view/RotationDragHandle.ts index 30dac7b7..ec28cea1 100644 --- a/js/common/view/RotationDragHandle.js +++ b/js/common/view/RotationDragHandle.ts @@ -10,10 +10,13 @@ import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; import Property from '../../../../axon/js/Property.js'; +import Vector2 from '../../../../dot/js/Vector2.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import CurvedArrowShape from '../../../../scenery-phet/js/CurvedArrowShape.js'; import Node from '../../../../scenery/js/nodes/Node.js'; import Path from '../../../../scenery/js/nodes/Path.js'; import bendingLight from '../../bendingLight.js'; +import Laser from '../model/Laser.js'; class RotationDragHandle extends Node { @@ -28,8 +31,8 @@ class RotationDragHandle extends Node { * @param {number} rotationArrowAngleOffset - for unknown reasons the rotation arrows are off by PI/4 on the * intro/more-tools screen, so account for that here. */ - constructor( modelViewTransform, laser, deltaAngle, showDragHandlesProperty, notAtMax, - laserImageWidth, rotationArrowAngleOffset ) { + constructor( modelViewTransform: ModelViewTransform2, laser: Laser, deltaAngle: number, showDragHandlesProperty: Property, notAtMax: ( n: number ) => boolean, + laserImageWidth: number, rotationArrowAngleOffset: number ) { super(); @@ -39,7 +42,7 @@ class RotationDragHandle extends Node { laser.pivotProperty, showDragHandlesProperty ], - ( emissionPoint, pivot, showDragHandles ) => notAtMax( laser.getAngle() ) && showDragHandles + ( emissionPoint: Vector2, pivot: Vector2, showDragHandles: boolean ) => notAtMax( laser.getAngle() ) && showDragHandles ); // Show the drag handle if the "show drag handles" is true and if the laser isn't already at the max angle. diff --git a/js/common/view/SingleColorLightCanvasNode.js b/js/common/view/SingleColorLightCanvasNode.ts similarity index 85% rename from js/common/view/SingleColorLightCanvasNode.js rename to js/common/view/SingleColorLightCanvasNode.ts index 65da40d5..19909ca5 100644 --- a/js/common/view/SingleColorLightCanvasNode.js +++ b/js/common/view/SingleColorLightCanvasNode.ts @@ -7,14 +7,18 @@ */ import Bounds2 from '../../../../dot/js/Bounds2.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import CanvasNode from '../../../../scenery/js/nodes/CanvasNode.js'; import bendingLight from '../../bendingLight.js'; import LightRay from '../model/LightRay.js'; // constants -const lineDash = []; +const lineDash: number[] = []; class SingleColorLightCanvasNode extends CanvasNode { + private readonly modelViewTransform: ModelViewTransform2; + private readonly rays: LightRay[]; + private readonly strokeWidth: number; /** * @param {ModelViewTransform2} modelViewTransform - converts between model and view co-ordinates @@ -22,7 +26,7 @@ class SingleColorLightCanvasNode extends CanvasNode { * @param {number} stageHeight - height of the dev area * @param {ObservableArrayDef.} rays - */ - constructor( modelViewTransform, stageWidth, stageHeight, rays ) { + constructor( modelViewTransform: ModelViewTransform2, stageWidth: number, stageHeight: number, rays: LightRay[] ) { super( { canvasBounds: new Bounds2( 0, 0, stageWidth, stageHeight ) @@ -40,7 +44,7 @@ class SingleColorLightCanvasNode extends CanvasNode { * @protected * @param {CanvasRenderingContext2D} context */ - paintCanvas( context ) { + paintCanvas( context: CanvasRenderingContext2D ) { context.save(); context.lineWidth = this.strokeWidth; @@ -51,7 +55,7 @@ class SingleColorLightCanvasNode extends CanvasNode { context.lineCap = 'round'; for ( let i = 0; i < this.rays.length; i++ ) { - const ray = this.rays.get( i ); + const ray = this.rays[ i ]; // iPad3 shows a opacity=0 ray as opacity=1 for unknown reasons, so we simply omit those rays if ( ray.powerFraction > 1E-6 ) { diff --git a/js/common/view/ToolIconListener.js b/js/common/view/ToolIconListener.ts similarity index 78% rename from js/common/view/ToolIconListener.js rename to js/common/view/ToolIconListener.ts index 875be283..b7b52744 100644 --- a/js/common/view/ToolIconListener.js +++ b/js/common/view/ToolIconListener.ts @@ -6,7 +6,9 @@ * @author Sam Reid (PhET Interactive Simulations) */ +import MovableDragHandler from '../../../../scenery-phet/js/input/MovableDragHandler.js'; import SimpleDragHandler from '../../../../scenery/js/input/SimpleDragHandler.js'; +import Trail from '../../../../scenery/js/util/Trail.js'; import bendingLight from '../../bendingLight.js'; class ToolIconListener extends SimpleDragHandler { @@ -15,9 +17,9 @@ class ToolIconListener extends SimpleDragHandler { * @param {MovableDragHandler[]} components - the individual listeners that events should be forwarded to. * @param {function} init - called with (event) when the start drag occurs */ - constructor( components, init ) { + constructor( components: MovableDragHandler[], init: ( event: any ) => void ) { super( { - start: ( event, trail ) => { + start: ( event: any, trail: Trail ) => { init( event ); // Forward the event to the components @@ -25,14 +27,14 @@ class ToolIconListener extends SimpleDragHandler { components[ i ].handleForwardedStartEvent( event, trail ); } }, - drag: ( event, trail ) => { + drag: ( event: any, trail: Trail ) => { // Forward the event to the components for ( let i = 0; i < components.length; i++ ) { components[ i ].handleForwardedDragEvent( event, trail ); } }, - end: ( event, trail ) => { + end: ( event: any, trail: Trail ) => { // Forward the event to the components for ( let i = 0; i < components.length; i++ ) { diff --git a/js/common/view/TranslationDragHandle.js b/js/common/view/TranslationDragHandle.ts similarity index 84% rename from js/common/view/TranslationDragHandle.js rename to js/common/view/TranslationDragHandle.ts index 1cd24b46..5044e782 100644 --- a/js/common/view/TranslationDragHandle.js +++ b/js/common/view/TranslationDragHandle.ts @@ -9,9 +9,12 @@ */ import Property from '../../../../axon/js/Property.js'; +import Vector2 from '../../../../dot/js/Vector2.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import ArrowNode from '../../../../scenery-phet/js/ArrowNode.js'; import Node from '../../../../scenery/js/nodes/Node.js'; import bendingLight from '../../bendingLight.js'; +import Laser from '../model/Laser.js'; class TranslationDragHandle extends Node { @@ -24,7 +27,7 @@ class TranslationDragHandle extends Node { * @param {number} laserImageWidth - width of the laser * @constructor */ - constructor( modelViewTransform, laser, dx, dy, showDragHandlesProperty, laserImageWidth ) { + constructor( modelViewTransform: ModelViewTransform2, laser: Laser, dx: number, dy: number, showDragHandlesProperty: Property, laserImageWidth: number ) { super(); @@ -43,7 +46,7 @@ class TranslationDragHandle extends Node { // update the position when laser pivot or emission point change Property.multilink( [ laser.pivotProperty, laser.emissionPointProperty, showDragHandlesProperty ], - ( laserPivot, laserEmission ) => { + ( laserPivot: Vector2, laserEmission: Vector2 ) => { if ( showDragHandlesProperty.get() ) { const laserAngle = -laser.getAngle(); const magnitude = laserImageWidth * 0.35; diff --git a/js/common/view/WavelengthControl.js b/js/common/view/WavelengthControl.ts similarity index 97% rename from js/common/view/WavelengthControl.js rename to js/common/view/WavelengthControl.ts index 080bfa19..597e8ea4 100644 --- a/js/common/view/WavelengthControl.js +++ b/js/common/view/WavelengthControl.ts @@ -20,7 +20,9 @@ import bendingLightStrings from '../../bendingLightStrings.js'; import bendingLight from '../../bendingLight.js'; import BendingLightConstants from '../BendingLightConstants.js'; +// @ts-ignore const unitsNmString = bendingLightStrings.units_nm; +// @ts-ignore const wavelengthPatternString = bendingLightStrings.wavelengthPattern; // constants @@ -33,7 +35,7 @@ class WavelengthControl extends Node { * @param {Property.} enabledProperty * @param {number} trackWidth */ - constructor( wavelengthProperty, enabledProperty, trackWidth ) { + constructor( wavelengthProperty: Property, enabledProperty: Property, trackWidth: number ) { const wavelengthPropertyNM = new Property( wavelengthProperty.value * 1E9, { reentrant: true } ); wavelengthProperty.link( wavelength => { diff --git a/js/intro/model/IntroModel.js b/js/intro/model/IntroModel.ts similarity index 94% rename from js/intro/model/IntroModel.js rename to js/intro/model/IntroModel.ts index c1c525af..220e1f78 100644 --- a/js/intro/model/IntroModel.js +++ b/js/intro/model/IntroModel.ts @@ -15,6 +15,7 @@ import Vector2 from '../../../../dot/js/Vector2.js'; import Shape from '../../../../kite/js/Shape.js'; import TimeSpeed from '../../../../scenery-phet/js/TimeSpeed.js'; import Color from '../../../../scenery/js/util/Color.js'; +import Tandem from '../../../../tandem/js/Tandem.js'; import NumberIO from '../../../../tandem/js/types/NumberIO.js'; import bendingLight from '../../bendingLight.js'; import BendingLightConstants from '../../common/BendingLightConstants.js'; @@ -33,13 +34,21 @@ const CHARACTERISTIC_LENGTH = BendingLightConstants.WAVELENGTH_RED; const BEAM_LENGTH = 1E-3; class IntroModel extends BendingLightModel { + topMediumProperty: Property; + bottomMediumProperty: Property; + time: number; + indexOfRefractionOfTopMediumProperty: DerivedProperty; + indexOfRefractionOfBottomMediumProperty: DerivedProperty; + intensityMeter: IntensityMeter; + tailVector: Vector2; + tipVector: Vector2; /** * @param {Substance} bottomSubstance - state of bottom medium * @param {boolean} horizontalPlayAreaOffset - specifies center alignment * @param {Tandem} tandem */ - constructor( bottomSubstance, horizontalPlayAreaOffset, tandem ) { + constructor( bottomSubstance: Substance, horizontalPlayAreaOffset: boolean, tandem: Tandem ) { super( Math.PI * 3 / 4, true, BendingLightModel.DEFAULT_LASER_DISTANCE_FROM_PIVOT ); @@ -69,7 +78,7 @@ class IntroModel extends BendingLightModel { this.topMediumProperty, this.laser.colorProperty ], - ( topMedium, color ) => topMedium.getIndexOfRefraction( color.wavelength ), { + ( topMedium: Medium, color: any ) => topMedium.getIndexOfRefraction( color.wavelength ), { tandem: tandem.createTandem( 'indexOfRefractionOfTopMediumProperty' ), phetioType: DerivedProperty.DerivedPropertyIO( NumberIO ) } ); @@ -80,7 +89,7 @@ class IntroModel extends BendingLightModel { this.bottomMediumProperty, this.laser.colorProperty ], - ( bottomMedium, color ) => bottomMedium.getIndexOfRefraction( color.wavelength ), { + ( bottomMedium: Medium, color: any ) => bottomMedium.getIndexOfRefraction( color.wavelength ), { tandem: tandem.createTandem( 'indexOfRefractionOfBottomMediumProperty' ), phetioType: DerivedProperty.DerivedPropertyIO( NumberIO ) } ); @@ -265,7 +274,7 @@ class IntroModel extends BendingLightModel { * @param {string} rayType - 'incident', 'transmitted' or 'reflected' * @returns {boolean} */ - addAndAbsorb( ray, rayType ) { + addAndAbsorb( ray: LightRay, rayType: 'incident' | 'transmitted' | 'reflected' ) { const angleOffset = rayType === 'incident' ? Math.PI : 0; // find intersection points with the intensity sensor @@ -346,11 +355,11 @@ class IntroModel extends BendingLightModel { * @param {Vector2} position - position where the velocity to be determined * @returns {Vector2} */ - getVelocity( position ) { + getVelocity( position: Vector2 ) { const laserView = this.laserViewProperty.value; for ( let i = 0; i < this.rays.length; i++ ) { - if ( this.rays.get( i ).contains( position, laserView === 'wave' ) ) { - return this.rays.get( i ).getVelocityVector(); + if ( this.rays[ i ].contains( position, laserView === 'wave' ) ) { + return this.rays[ i ].getVelocityVector(); } } return new Vector2( 0, 0 ); @@ -362,9 +371,9 @@ class IntroModel extends BendingLightModel { * @param {Vector2} position - position where the wave value to be determined * @returns {Object|null}- returns object of time and magnitude if point is on ray otherwise returns null */ - getWaveValue( position ) { + getWaveValue( position: Vector2 ) { for ( let i = 0; i < this.rays.length; i++ ) { - const ray = this.rays.get( i ); + const ray = this.rays[ i ]; if ( ray.contains( position, this.laserViewProperty.value === 'wave' ) ) { // map power to displayed amplitude @@ -399,9 +408,10 @@ class IntroModel extends BendingLightModel { * Update simulation time and wave propagation. * @public */ - updateSimulationTimeAndWaveShape( speed ) { + updateSimulationTimeAndWaveShape( speed: any ) { // Update the time + // @ts-ignore this.time = this.time + ( speed === TimeSpeed.NORMAL ? 1E-16 : 0.5E-16 ); // set time for each ray @@ -423,7 +433,7 @@ class IntroModel extends BendingLightModel { let particleGradientColor; let j; for ( let k = 0; k < this.rays.length; k++ ) { - const lightRay = this.rays.get( k ); + const lightRay = this.rays[ k ]; const directionVector = lightRay.getUnitVector(); const wavelength = lightRay.wavelength; const angle = lightRay.getAngle(); @@ -447,8 +457,8 @@ class IntroModel extends BendingLightModel { const distance = this.tipVector.distance( this.tailVector ); const gapBetweenSuccessiveParticles = wavelength; particleColor = new Color( lightRay.color.getRed(), lightRay.color.getGreen(), lightRay.color.getBlue(), - Math.sqrt( lightRay.powerFraction ) ).toCSS(); - particleGradientColor = new Color( 0, 0, 0, Math.sqrt( lightRay.powerFraction ) ).toCSS(); + Math.sqrt( lightRay.powerFraction ) ); + particleGradientColor = new Color( 0, 0, 0, Math.sqrt( lightRay.powerFraction ) ); // calculate the number of particles that can fit in the distance const numberOfParticles = Math.min( Math.ceil( distance / gapBetweenSuccessiveParticles ), 150 ) + 1; @@ -470,7 +480,7 @@ class IntroModel extends BendingLightModel { propagateParticles() { for ( let i = 0; i < this.rays.length; i++ ) { - const lightRay = this.rays.get( i ); + const lightRay = this.rays[ i ]; const wavelength = lightRay.wavelength; const directionVector = lightRay.getUnitVector(); const waveParticles = lightRay.particles; diff --git a/js/intro/view/AngleIcon.js b/js/intro/view/AngleIcon.ts similarity index 100% rename from js/intro/view/AngleIcon.js rename to js/intro/view/AngleIcon.ts diff --git a/js/intro/view/AngleNode.js b/js/intro/view/AngleNode.ts similarity index 87% rename from js/intro/view/AngleNode.js rename to js/intro/view/AngleNode.ts index 5234218e..42945e70 100644 --- a/js/intro/view/AngleNode.js +++ b/js/intro/view/AngleNode.ts @@ -11,12 +11,14 @@ import Property from '../../../../axon/js/Property.js'; import Utils from '../../../../dot/js/Utils.js'; import Vector2 from '../../../../dot/js/Vector2.js'; import Shape from '../../../../kite/js/Shape.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import Line from '../../../../scenery/js/nodes/Line.js'; import Node from '../../../../scenery/js/nodes/Node.js'; import Path from '../../../../scenery/js/nodes/Path.js'; import Text from '../../../../scenery/js/nodes/Text.js'; import Panel from '../../../../sun/js/Panel.js'; import bendingLight from '../../bendingLight.js'; +import LightRay from '../../common/model/LightRay.js'; // constants const CIRCLE_RADIUS = 50; // radius of the circular arc in stage coordinates @@ -43,12 +45,12 @@ class AngleNode extends Node { * @param {ModelViewTransform2} modelViewTransform * @param {function} addStepListener - */ - constructor( showAnglesProperty, laserOnProperty, showNormalProperty, rays, modelViewTransform, - addStepListener ) { + constructor( showAnglesProperty: Property, laserOnProperty: Property, showNormalProperty: Property, rays: LightRay[], modelViewTransform: ModelViewTransform2, + addStepListener: ( x: any ) => void ) { super(); // Only show the AngleNode when it is selected via a checkbox and the laser is on - Property.multilink( [ showAnglesProperty, laserOnProperty ], ( showAngles, laserOn ) => { + Property.multilink( [ showAnglesProperty, laserOnProperty ], ( showAngles: boolean, laserOn: boolean ) => { this.visible = showAngles && laserOn; } ); @@ -82,6 +84,7 @@ class AngleNode extends Node { } ); // defines ES5 getter/setter + // TODO: This should be improved Object.defineProperty( panel, 'text', { get: () => 'hello', // TODO: Is this correct? set: value => { text.text = value; }, @@ -107,7 +110,7 @@ class AngleNode extends Node { this.addChild( refractedReadout ); // Helper function used to create the vertical line marker above and below the origin - const createLine = y => new Line( + const createLine = ( y: number ) => new Line( getOriginX(), getOriginY() + y - LINE_HEIGHT / 2, getOriginX(), getOriginY() + y + LINE_HEIGHT / 2, { stroke: 'black', @@ -136,18 +139,21 @@ class AngleNode extends Node { const markDirty = () => { dirty = true; }; + + // @ts-ignore rays.addItemAddedListener( markDirty ); + // @ts-ignore rays.addItemRemovedListener( markDirty ); /** - * Select the ray of the given type 'incident' | 'reflected' | 'incident', or null if there isn't one of that type + * Select the ray of the given type 'incident' | 'reflected', or null if there isn't one of that type * @param type * @returns {LightRay} */ - const getRay = type => { + const getRay = ( type: 'incident' | 'reflected' | 'transmitted' | null ) => { // TODO: enum let selected = null; for ( let i = 0; i < rays.length; i++ ) { - const ray = rays.get( i ); + const ray = rays[ i ]; if ( ray.rayType === type ) { assert && assert( selected === null, 'multiple rays of the same type' ); selected = ray; @@ -174,16 +180,17 @@ class AngleNode extends Node { const incomingAngleFromNormal = incomingRay.getAngle() + Math.PI / 2; const refractedAngleFromNormal = refractedRay.getAngle() + Math.PI / 2; - const getShape = ( angle, startAngle, endAngle, anticlockwise ) => angle >= 1E-6 ? - Shape.arc( - getOriginX(), - getOriginY(), - CIRCLE_RADIUS, - startAngle, - endAngle, - anticlockwise - ) : - null; + const getShape = ( angle: number, startAngle: number, endAngle: number, anticlockwise: boolean ) => + angle >= 1E-6 ? + Shape.arc( + getOriginX(), + getOriginY(), + CIRCLE_RADIUS, + startAngle, + endAngle, + anticlockwise + ) : + null; // Only show the incident angle when the ray is coming in at a shallow angle, see #288 const isIncomingRayHorizontal = Math.abs( incomingRay.getAngle() ) < 1E-6; @@ -214,11 +221,12 @@ class AngleNode extends Node { ) / ROUNDING_FACTOR; const incomingReadoutText = `${Utils.toFixed( incomingRayDegreesFromNormal, NUM_DIGITS )}\u00B0`; - const createDirectionVector = angle => Vector2.createPolar( CIRCLE_RADIUS + LINE_HEIGHT + 5, angle ); + const createDirectionVector = ( angle: number ) => Vector2.createPolar( CIRCLE_RADIUS + LINE_HEIGHT + 5, angle ); const incomingReadoutDirection = createDirectionVector( -Math.PI / 2 - incomingAngleFromNormal / 2 ); const reflectedReadoutDirection = createDirectionVector( -Math.PI / 2 + incomingAngleFromNormal / 2 ); const refractedReadoutDirection = createDirectionVector( +Math.PI / 2 - refractedAngleFromNormal / 2 ); + // @ts-ignore incomingReadout.text = incomingReadoutText; // When the angle becomes too small, pop the text out so that it won't be obscured by the ray @@ -227,6 +235,7 @@ class AngleNode extends Node { incomingReadout.center = origin.plus( incomingReadoutDirection ) .plusXY( incomingRayDegreesFromNormal >= angleThresholdToBumpToSide ? 0 : -BUMP_TO_SIDE_DISTANCE, 0 ); + // @ts-ignore reflectedReadout.text = incomingReadoutText; // It's the same reflectedReadout.center = origin.plus( reflectedReadoutDirection ) .plusXY( incomingRayDegreesFromNormal >= angleThresholdToBumpToSide ? 0 : +BUMP_TO_SIDE_DISTANCE, 0 ); @@ -242,6 +251,7 @@ class AngleNode extends Node { lowerArcPath.visible = showLowerAngle; lowerMark.visible = !showNormalProperty.value && showLowerAngle; + // @ts-ignore refractedReadout.text = refractedReadoutText; const bumpBottomReadout = refractedRayDegreesFromNormal >= angleThresholdToBumpToSide; refractedReadout.center = origin.plus( refractedReadoutDirection ) diff --git a/js/intro/view/IntroScreenView.js b/js/intro/view/IntroScreenView.ts similarity index 93% rename from js/intro/view/IntroScreenView.js rename to js/intro/view/IntroScreenView.ts index d38bb1b9..a6edfe5c 100644 --- a/js/intro/view/IntroScreenView.js +++ b/js/intro/view/IntroScreenView.ts @@ -20,6 +20,7 @@ import MovableDragHandler from '../../../../scenery-phet/js/input/MovableDragHan import ProtractorNode from '../../../../scenery-phet/js/ProtractorNode.js'; import TimeControlNode from '../../../../scenery-phet/js/TimeControlNode.js'; import HBox from '../../../../scenery/js/nodes/HBox.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; import Path from '../../../../scenery/js/nodes/Path.js'; import Text from '../../../../scenery/js/nodes/Text.js'; import VBox from '../../../../scenery/js/nodes/VBox.js'; @@ -34,20 +35,33 @@ import IntensityMeterNode from '../../common/view/IntensityMeterNode.js'; import MediumControlPanel from '../../common/view/MediumControlPanel.js'; import MediumNode from '../../common/view/MediumNode.js'; import ToolIconListener from '../../common/view/ToolIconListener.js'; +import IntroModel from '../model/IntroModel.js'; import AngleIcon from './AngleIcon.js'; import AngleNode from './AngleNode.js'; import NormalLine from './NormalLine.js'; import WaveCanvasNode from './WaveCanvasNode.js'; import WaveWebGLNode from './WaveWebGLNode.js'; +import BendingLightModel from '../../common/model/BendingLightModel.js'; +// @ts-ignore const anglesString = bendingLightStrings.angles; +// @ts-ignore const materialString = bendingLightStrings.material; +// @ts-ignore const normalLineString = bendingLightStrings.normalLine; // constants const INSET = 10; class IntroScreenView extends BendingLightScreenView { + introModel: IntroModel; + stepEmitter: Emitter; + topMediumControlPanel: MediumControlPanel; + bottomMediumControlPanel: MediumControlPanel; + dropInToolbox: ( node: Node, enabledProperty: Property ) => void; + bumpLeft: ( node: Node, positionProperty: Property ) => void; + toolbox: Panel; + timeControlNode: TimeControlNode; /** * @param {IntroModel} introModel - model of intro screen @@ -56,7 +70,7 @@ class IntroScreenView extends BendingLightScreenView { * @param {function} createLaserControlPanel * @param {Object} [options] */ - constructor( introModel, hasMoreTools, indexOfRefractionDecimals, createLaserControlPanel, options ) { + constructor( introModel: IntroModel, hasMoreTools: boolean, indexOfRefractionDecimals: number, createLaserControlPanel: any, options: object ) { options = merge( { @@ -69,7 +83,7 @@ class IntroScreenView extends BendingLightScreenView { * @param {number} angle * @returns {number} */ - clampDragAngle: angle => { + clampDragAngle: ( angle: number ) => { while ( angle < 0 ) { angle += Math.PI * 2; } return Utils.clamp( angle, Math.PI / 2, Math.PI ); }, @@ -78,7 +92,7 @@ class IntroScreenView extends BendingLightScreenView { * @param {number} laserAngle * @returns {boolean} */ - clockwiseArrowNotAtMax: laserAngle => { + clockwiseArrowNotAtMax: ( laserAngle: number ) => { if ( introModel.laserViewProperty.value === 'ray' ) { return laserAngle < Math.PI; } @@ -91,7 +105,7 @@ class IntroScreenView extends BendingLightScreenView { * @param {number} laserAngle * @returns {boolean} */ - ccwArrowNotAtMax: laserAngle => laserAngle > Math.PI / 2 + ccwArrowNotAtMax: ( laserAngle: number ) => laserAngle > Math.PI / 2 }, options ); super( @@ -102,7 +116,7 @@ class IntroScreenView extends BendingLightScreenView { // laserRotationRegion - In this screen, clicking anywhere on the laser (i.e. on its 'full' bounds) // translates it, so always return the 'full' region. - full => full, + ( full: boolean ) => full, // laserHasKnob false, @@ -154,10 +168,10 @@ class IntroScreenView extends BendingLightScreenView { // be fine. this.beforeLightLayer.addChild( new Path( this.modelViewTransform.modelToViewShape( new Shape() .moveTo( -1, 0 ) - .lineTo( 1, 0 ), { + .lineTo( 1, 0 ) ), { stroke: 'gray', pickable: false - } ) ) ); + } ) ); // show the normal line where the laser strikes the interface between mediums const normalLineHeight = stageHeight / 2; @@ -176,7 +190,7 @@ class IntroScreenView extends BendingLightScreenView { this.modelViewTransform, // Method to add a step listener - stepCallback => this.stepEmitter.addListener( stepCallback ) + ( stepCallback: any ) => this.stepEmitter.addListener( stepCallback ) ) ); introModel.showNormalProperty.linkAttribute( normalLine, 'visible' ); @@ -191,6 +205,8 @@ class IntroScreenView extends BendingLightScreenView { introModel.laser.colorProperty ], () => { for ( let k = 0; k < this.incidentWaveLayer.getChildrenCount(); k++ ) { + + // @ts-ignore this.incidentWaveLayer.children[ k ].step(); } this.incidentWaveLayer.setVisible( introModel.laser.onProperty.value && introModel.laserViewProperty.value === 'wave' ); @@ -265,7 +281,7 @@ class IntroScreenView extends BendingLightScreenView { const protractorPositionProperty = new Property( protractorPosition ); // When a node is released, check if it is over the toolbox. If so, drop it in. - const dropInToolbox = ( node, enabledProperty ) => { + const dropInToolbox = ( node: Node, enabledProperty: Property ) => { if ( node.getGlobalBounds().intersectsBounds( this.toolbox.getGlobalBounds() ) ) { enabledProperty.value = false; } @@ -290,7 +306,7 @@ class IntroScreenView extends BendingLightScreenView { const modelViewTransform = this.modelViewTransform; // When a node is dropped behind a control panel, move it to the side so it won't be lost. - const bumpLeft = ( node, positionProperty ) => { + const bumpLeft = ( node: Node, positionProperty: Property ) => { while ( node.getGlobalBounds().intersectsBounds( topMediumControlPanel.getGlobalBounds() ) || node.getGlobalBounds().intersectsBounds( bottomMediumControlPanel.getGlobalBounds() ) ) { positionProperty.value = positionProperty.value.plusXY( modelViewTransform.viewToModelDeltaX( -20 ), 0 ); @@ -473,6 +489,8 @@ class IntroScreenView extends BendingLightScreenView { updateWaveShape() { if ( this.introModel.laserViewProperty.value === 'wave' ) { for ( let k = 0; k < this.incidentWaveLayer.getChildrenCount(); k++ ) { + + // @ts-ignore this.incidentWaveLayer.children[ k ].step(); } } @@ -483,8 +501,8 @@ class IntroScreenView extends BendingLightScreenView { * @param {BendingLightModel} bendingLightModel * @private */ - addLightNodes( bendingLightModel ) { - super.addLightNodes( bendingLightModel ); + addLightNodes( bendingLightModel: BendingLightModel ) { + // super.addLightNodes( bendingLightModel ); this.addChild( this.incidentWaveLayer ); diff --git a/package.json b/package.json index 5a8770b9..460da85d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "phet-io": { "validation": false }, - "supportsOutputJS": true + "supportsOutputJS": true, + "typescript": true }, "eslintConfig": { "extends": "../chipper/eslint/sim_eslintrc.js" diff --git a/tsconfig.json b/tsconfig.json index 8ca80a02..19ec4c3c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,17 @@ { "extends": "../chipper/tsconfig-core.json", - "references": [{"path":"../joist"}], + "references": [ + { + "path": "../joist" + } + ], "include": [ "js/**/*", "sounds/**/*", "mipmaps/**/*", - "images/**/*" + "images/**/*", + "../chipper/phet-types.d.ts", + "../chipper/node_modules/@types/lodash/index.d.ts", + "../chipper/node_modules/@types/qunit/index.d.ts" ] } \ No newline at end of file