Skip to content

Commit

Permalink
Particle: convert Vector2 fields to number fields, #231
Browse files Browse the repository at this point in the history
  • Loading branch information
pixelzoom committed May 3, 2024
1 parent 016d019 commit c63b761
Show file tree
Hide file tree
Showing 12 changed files with 143 additions and 73 deletions.
18 changes: 14 additions & 4 deletions js/common/GasPropertiesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,29 @@ const GasPropertiesUtils = {
/**
* Determines the position of a point that is the reflection of a specified point across a line.
* Used in collision response.
* @param p - the point to reflect
* @param x - the point to reflect
* @param y - the point to reflect
* @param pointOnLine - point on the line
* @param lineAngle - angle of the line, in radians
* @param reflectedPoint - the point to be mutated with the return value
* @returns reflectedPoint mutated
*/
reflectPointAcrossLine( p: Vector2, pointOnLine: Vector2, lineAngle: number, reflectedPoint: Vector2 ): Vector2 {
reflectPointAcrossLineXY( x: number, y: number, pointOnLine: Vector2, lineAngle: number, reflectedPoint: Vector2 ): Vector2 {
const alpha = lineAngle % ( Math.PI * 2 );
const gamma = Math.atan2( ( p.y - pointOnLine.y ), ( p.x - pointOnLine.x ) ) % ( Math.PI * 2 );
const gamma = Math.atan2( ( y - pointOnLine.y ), ( x - pointOnLine.x ) ) % ( Math.PI * 2 );
const theta = ( 2 * alpha - gamma ) % ( Math.PI * 2 );
const d = p.distance( pointOnLine );
const d = GasPropertiesUtils.distanceXY( x, y, pointOnLine.x, pointOnLine.y );
reflectedPoint.setXY( pointOnLine.x + d * Math.cos( theta ), pointOnLine.y + d * Math.sin( theta ) );
return reflectedPoint;
},

/**
* Gets the distance between 2 points.
*/
distanceXY( x1: number, y1: number, x2: number, y2: number ): number {
const dx = x1 - x2;
const dy = y1 - y2;
return Math.sqrt( dx * dx + dy * dy );
}
};

Expand Down
36 changes: 18 additions & 18 deletions js/common/model/CollisionDetector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,24 +175,24 @@ export default class CollisionDetector {
particle.left = containerBounds.minX;

// If the left wall is moving, it will do work.
particle.setVelocityXY( -( particle.velocity.x - leftWallVelocity.x ), particle.velocity.y );
particle.setVelocityXY( -( particle.vx - leftWallVelocity.x ), particle.vy );
collided = true;
}
else if ( particle.right >= containerBounds.maxX ) {
particle.right = containerBounds.maxX;
particle.setVelocityXY( -particle.velocity.x, particle.velocity.y );
particle.setVelocityXY( -particle.vx, particle.vy );
collided = true;
}

// adjust y
if ( particle.top >= containerBounds.maxY ) {
particle.top = containerBounds.maxY;
particle.setVelocityXY( particle.velocity.x, -particle.velocity.y );
particle.setVelocityXY( particle.vx, -particle.vy );
collided = true;
}
else if ( particle.bottom <= containerBounds.minY ) {
particle.bottom = containerBounds.minY;
particle.setVelocityXY( particle.velocity.x, -particle.velocity.y );
particle.setVelocityXY( particle.vx, -particle.vy );
collided = true;
}

Expand Down Expand Up @@ -278,11 +278,11 @@ function doParticleParticleCollisions( particles: Particle[], mutableVectors: Mu
// Determine where the particles made contact.
//-----------------------------------------------------------------------------------------

const dx = particle1.position.x - particle2.position.x;
const dy = particle1.position.y - particle2.position.y;
const contactRatio = particle1.radius / particle1.position.distance( particle2.position );
const contactPointX = particle1.position.x - dx * contactRatio;
const contactPointY = particle1.position.y - dy * contactRatio;
const dx = particle1.x - particle2.x;
const dy = particle1.y - particle2.y;
const contactRatio = particle1.radius / particle1.distance( particle2 );
const contactPointX = particle1.x - dx * contactRatio;
const contactPointY = particle1.y - dy * contactRatio;

//-----------------------------------------------------------------------------------------
// Adjust particle positions by reflecting across the line of impact.
Expand Down Expand Up @@ -310,7 +310,7 @@ function doParticleParticleCollisions( particles: Particle[], mutableVectors: Mu

// Compute the impulse, j.
// There is no angular velocity in our model, so the denominator involves only mass.
mutableVectors.relativeVelocity.set( particle1.velocity ).subtract( particle2.velocity );
mutableVectors.relativeVelocity.setXY( particle1.vx, particle1.vy ).subtractXY( particle2.vx, particle2.vy );
const vr = mutableVectors.relativeVelocity.dot( mutableVectors.normal );
const numerator = -vr * ( 1 + e );
const denominator = ( 1 / particle1.mass + 1 / particle2.mass );
Expand All @@ -335,23 +335,23 @@ function doParticleParticleCollisions( particles: Particle[], mutableVectors: Mu
function adjustParticlePosition( particle: Particle, contactPointX: number, contactPointY: number,
lineAngle: number, pointOnLine: Vector2, reflectedPoint: Vector2 ): void {

const previousDistance = particle.previousPosition.distanceXY( contactPointX, contactPointY );
const previousDistance = GasPropertiesUtils.distanceXY( particle.previousX, particle.previousY, contactPointX, contactPointY );
const positionRatio = particle.radius / previousDistance;
pointOnLine.setXY(
contactPointX - ( contactPointX - particle.previousPosition.x ) * positionRatio,
contactPointY - ( contactPointY - particle.previousPosition.y ) * positionRatio
contactPointX - ( contactPointX - particle.previousX ) * positionRatio,
contactPointY - ( contactPointY - particle.previousY ) * positionRatio
);
GasPropertiesUtils.reflectPointAcrossLine( particle.position, pointOnLine, lineAngle, reflectedPoint );
particle.setPositionXY( reflectedPoint.x, reflectedPoint.y );
GasPropertiesUtils.reflectPointAcrossLineXY( particle.x, particle.y, pointOnLine, lineAngle, reflectedPoint );
particle.setXY( reflectedPoint.x, reflectedPoint.y );
}

/**
* Adjusts the speed of a particle in response to a collision with another particle.
*/
function adjustParticleSpeed( particle: Particle, scale: number, normalVector: Vector2 ): void {
const vx = normalVector.x * scale;
const vy = normalVector.y * scale;
particle.setVelocityXY( particle.velocity.x + vx, particle.velocity.y + vy );
const vxDelta = normalVector.x * scale;
const vyDelta = normalVector.y * scale;
particle.setVelocityXY( particle.vx + vxDelta, particle.vy + vyDelta );
}

gasProperties.register( 'CollisionDetector', CollisionDetector );
128 changes: 94 additions & 34 deletions js/common/model/Particle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
*/

import Bounds2 from '../../../../dot/js/Bounds2.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import { ProfileColorProperty } from '../../../../scenery/js/imports.js';
import gasProperties from '../../gasProperties.js';
import GasPropertiesUtils from '../GasPropertiesUtils.js';

type SelfOptions = {
mass: number; // AMU
Expand All @@ -35,9 +35,17 @@ export default class Particle {
public readonly colorProperty: ProfileColorProperty;
public readonly highlightColorProperty: ProfileColorProperty;

public readonly position: Vector2; // center of the particle, pm, MUTATED!
public readonly previousPosition: Vector2; // position on previous time step, MUTATED!
public readonly velocity: Vector2; // pm/ps, initially at rest, MUTATED!
// (x,y) position of the particle, at the center of the particle
private _x: number;
private _y: number;

// Position on the previous time step
private _previousX: number;
private _previousY: number;

// Velocity vector components, pm/ps, initially at rest
private _vx;
private _vy;

private _isDisposed: boolean;

Expand All @@ -49,9 +57,14 @@ export default class Particle {
this.colorProperty = providedOptions.colorProperty;
this.highlightColorProperty = providedOptions.highlightColorProperty;

this.position = new Vector2( 0, 0 );
this.previousPosition = this.position.copy();
this.velocity = new Vector2( 0, 0 );
this._x = 0;
this._y = 0;

this._previousX = 0;
this._previousY = 0;

this._vx = 0;
this._vy = 0;

this._isDisposed = false;
}
Expand All @@ -68,36 +81,59 @@ export default class Particle {
/**
* ES5 getters and setters for particle position.
*/
public get left(): number { return this.position.x - this.radius; }

public get x(): number { return this._x; }

public get y(): number { return this._y; }

public get previousX(): number { return this._previousX; }

public get previousY(): number { return this._previousY; }

public get left(): number { return this._x - this.radius; }

public set left( value: number ) {
this.setPositionXY( value + this.radius, this.position.y );
this.setXY( value + this.radius, this._y );
}

public get right(): number { return this.position.x + this.radius; }
public get right(): number { return this._x + this.radius; }

public set right( value: number ) {
this.setPositionXY( value - this.radius, this.position.y );
this.setXY( value - this.radius, this._y );
}

public get top(): number { return this.position.y + this.radius; }
public get top(): number { return this._y + this.radius; }

public set top( value: number ) {
this.setPositionXY( this.position.x, value - this.radius );
this.setXY( this._x, value - this.radius );
}

public get bottom(): number { return this.position.y - this.radius; }
public get bottom(): number { return this._y - this.radius; }

public set bottom( value: number ) {
this.setPositionXY( this.position.x, value + this.radius );
this.setXY( this._x, value + this.radius );
}

/**
* Gets the kinetic energy of this particle.
* @returns AMU * pm^2 / ps^2
* ES5 getters for particle velocity.
*/

public get vx(): number { return this._vx; }

public get vy(): number { return this._vy; }

/**
* Gets the particle's speed, the velocity magnitude, in pm/ps.
*/
public get speed(): number {
return Math.sqrt( this._vx * this._vx + this._vy * this._vy );
}

/**
* Gets the kinetic energy of this particle, in AMU * pm^2 / ps^2.
*/
public getKineticEnergy(): number {
return 0.5 * this.mass * this.velocity.magnitudeSquared; // KE = (1/2) * m * |v|^2
return 0.5 * this.mass * this.speed * this.speed; // KE = (1/2) * m * |v|^2
}

/**
Expand All @@ -108,22 +144,32 @@ export default class Particle {
assert && assert( dt > 0, `invalid dt: ${dt}` );
assert && assert( !this._isDisposed, 'attempted to step a disposed Particle' );

this.setPositionXY( this.position.x + dt * this.velocity.x, this.position.y + dt * this.velocity.y );
this.setXY( this._x + dt * this._vx, this._y + dt * this._vy );
}

/**
* Sets this particle's xy position and remembers the previous xy position.
*/
public setXY( x: number, y: number ): void {
this._previousX = this._x;
this._previousY = this._y;
this._x = x;
this._y = y;
}

/**
* Sets this particle's position and remembers the previous position.
* Sets this particle's x position and remembers the previous x position.
*/
public setPositionXY( x: number, y: number ): void {
this.previousPosition.setXY( this.position.x, this.position.y );
this.position.setXY( x, y );
public setX( x: number ): void {
this.setXY( x, this._y );
}

/**
* Sets this particle's velocity in Cartesian coordinates.
*/
public setVelocityXY( x: number, y: number ): void {
this.velocity.setXY( x, y );
public setVelocityXY( vx: number, vy: number ): void {
this._vx = vx;
this._vy = vy;
}

/**
Expand All @@ -137,27 +183,27 @@ export default class Particle {
}

/**
* Sets this particle's velocity magnitude (speed).
* @param magnitude - pm/ps
* Sets this particle's speed (velocity magnitude).
*/
public setVelocityMagnitude( magnitude: number ): void {
assert && assert( magnitude >= 0, `invalid magnitude: ${magnitude}` );
this.velocity.setMagnitude( magnitude );
public setSpeed( speed: number ): void {
assert && assert( speed >= 0, `invalid magnitude: ${speed}` );
this.scaleVelocity( speed / this.speed );
}

/**
* Scales this particle's velocity. Used when heat/cool is applied.
*/
public scaleVelocity( scale: number ): void {
assert && assert( scale > 0, `invalid scale: ${scale}` );
this.velocity.multiply( scale );
this._vx *= scale;
this._vy *= scale;
}

/**
* Does this particle contact another particle now?
*/
public contactsParticle( particle: Particle ): boolean {
return this.position.distance( particle.position ) <= ( this.radius + particle.radius );
return this.distance( particle ) <= ( this.radius + particle.radius );
}

/**
Expand All @@ -166,7 +212,21 @@ export default class Particle {
* implementation, and makes the collision behavior more natural looking.
*/
public contactedParticle( particle: Particle ): boolean {
return this.previousPosition.distance( particle.previousPosition ) <= ( this.radius + particle.radius );
return this.previousDistance( particle ) <= ( this.radius + particle.radius );
}

/**
* Distance to some other particle, in pm.
*/
public distance( particle: Particle ): number {
return GasPropertiesUtils.distanceXY( this._x, this._y, particle.x, particle.y );
}

/**
* Previous distance to some other particle, in pm.
*/
private previousDistance( particle: Particle ): number {
return GasPropertiesUtils.distanceXY( this._previousX, this._previousY, particle.previousX, particle.previousY );
}

/**
Expand All @@ -186,7 +246,7 @@ export default class Particle {
* String representation of this particle. For debugging only, do not rely on format.
*/
public toString(): string {
return `Particle[position:(${this.position.x},${this.position.y}) mass:${this.mass} radius:${this.radius}]`;
return `Particle[position:(${this._x},${this._y}) mass:${this.mass} radius:${this.radius}]`;
}
}

Expand Down
4 changes: 2 additions & 2 deletions js/common/model/ParticleSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ export default class ParticleSystem extends PhetioObject {
const particle = createParticle();

// Position the particle just inside the container, accounting for radius.
particle.setPositionXY( this.particleEntryPosition.x - particle.radius, this.particleEntryPosition.y );
particle.setXY( this.particleEntryPosition.x - particle.radius, this.particleEntryPosition.y );

// Initial speed, |v| = sqrt( 3kT / m )
const speed = Math.sqrt( 3 * GasPropertiesConstants.BOLTZMANN * temperatures[ i ] / particle.mass );
Expand Down Expand Up @@ -353,7 +353,7 @@ export default class ParticleSystem extends PhetioObject {
const actualParticleKE = particle.getKineticEnergy();
const desiredParticleKE = ratio * actualParticleKE;
const desiredSpeed = Math.sqrt( 2 * desiredParticleKE / particle.mass ); // |v| = Math.sqrt( 2 * KE / m )
particle.setVelocityMagnitude( desiredSpeed );
particle.setSpeed( desiredSpeed );
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions js/common/model/ParticleUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const ParticleUtils = {
assert && assert( scaleX > 0, `invalid scaleX: ${scaleX}` );

for ( let i = particles.length - 1; i >= 0; i-- ) {
particles[ i ].position.setX( scaleX * particles[ i ].position.x );
particles[ i ].setX( scaleX * particles[ i ].x );
}
},

Expand Down Expand Up @@ -147,7 +147,7 @@ const ParticleUtils = {
let totalMass = 0;
for ( let i = particles.length - 1; i >= 0; i-- ) {
const particle = particles[ i ];
numerator += ( particle.mass * particle.position.x );
numerator += ( particle.mass * particle.x );
totalMass += particle.mass;
}
centerXOfMass = numerator / totalMass;
Expand Down
4 changes: 2 additions & 2 deletions js/common/view/ParticlePositionsNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export default class ParticlePositionsNode extends Path {
public update(): void {
const shape = new Shape();
this.particleArrays.forEach( particleArray => particleArray.forEach( particle => {
const x = this.modelViewTransform.modelToViewX( particle.position.x );
const y = this.modelViewTransform.modelToViewY( particle.position.y );
const x = this.modelViewTransform.modelToViewX( particle.x );
const y = this.modelViewTransform.modelToViewY( particle.y );
shape.circle( x, y, 1 );
} ) );
this.shape = shape;
Expand Down
Loading

0 comments on commit c63b761

Please sign in to comment.