diff options
-rw-r--r-- | src/client/documents/Documents.ts | 2 | ||||
-rw-r--r-- | src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx | 508 |
2 files changed, 205 insertions, 305 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 13c27d182..515a870b5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -690,7 +690,7 @@ export namespace Docs { { data: '', layout: { view: PhysicsSimulationBox, dataField: defaultDataKey, _width: 1000, _height: 800 }, - options: { _height: 100, layout_forceReflow: true, nativeHeightUnfrozen: true, nativeDimModifiable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '' }, + options: { _height: 100, layout_forceReflow: true, nativeHeightUnfrozen: true, mass1: '', mass2: '', nativeDimModifiable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '' }, }, ], ]); diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx index c036f1041..7175daf12 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx @@ -1,7 +1,8 @@ -import { computed, trace } from 'mobx'; +import { computed } from 'mobx'; import { observer } from 'mobx-react'; import './PhysicsSimulationBox.scss'; import React = require('react'); +import _ = require('lodash'); interface IWallProps { length: number; @@ -118,9 +119,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { componentDidMount() { // Timer for animating the simulation - setInterval(() => { - this.setState({ timer: this.state.timer + 1 }); - }, 50); + setInterval(() => this.setState({ timer: this.state.timer + 1 }), 50); } // Constants @@ -158,57 +157,46 @@ export default class Weight extends React.Component<IWeightProps, IState> { const xAccel = Math.round(this.getNewAccelerationX(this.props.forcesUpdated()) * 100) / 100; const yAccel = (-1 * Math.round(this.getNewAccelerationY(this.props.forcesUpdated()) * 100)) / 100; this.props.setAcceleration(xAccel, yAccel); - this.setState({ xAccel }); - this.setState({ yAccel }); + this.setState({ xAccel, yAccel }); }; componentDidUpdate(prevProps: Readonly<IWeightProps>, prevState: Readonly<IState>, snapshot?: any): void { if (prevProps.simulationType != this.props.simulationType) { - this.setState({ xVelocity: this.props.startVelX }); - this.setState({ yVelocity: this.props.startVelY }); + this.setState({ xVelocity: this.props.startVelX, yVelocity: this.props.startVelY }); this.setDisplayValues(); } // Change pendulum angle from input field if (prevProps.startPendulumAngle != this.props.startPendulumAngle || prevProps.startPendulumLength !== this.props.startPendulumLength) { - let length = this.props.startPendulumLength; + const length = this.props.startPendulumLength; const x = length * Math.cos(((90 - this.props.startPendulumAngle) * Math.PI) / 180); const y = length * Math.sin(((90 - this.props.startPendulumAngle) * Math.PI) / 180); - const xPos = this.props.xMax / 2 - x - this.props.radius; - const yPos = y - this.props.radius - 5; - this.setState({ xPosition: xPos }); - this.setState({ yPosition: yPos }); - this.setState({ updatedStartPosX: xPos }); - this.setState({ updatedStartPosY: yPos }); + const xPosition = this.props.xMax / 2 - x - this.props.radius; + const yPosition = y - this.props.radius - 5; + this.setState({ xPosition, yPosition, updatedStartPosX: xPosition, updatedStartPosY: yPosition }); this.props.setPendulumAngle(this.props.startPendulumAngle, this.props.startPendulumLength); } // When display values updated by user, update real value if (prevProps.updateMassPosX !== this.props.updateMassPosX) { - let x = this.props.updateMassPosX; - x = Math.max(0, x); - x = Math.min(x, this.props.xMax - 2 * this.props.radius); - this.setState({ updatedStartPosX: x }); - this.setState({ xPosition: x }); + const x = Math.min(Math.max(0, this.props.updateMassPosX), this.props.xMax - 2 * this.props.radius); + this.setState({ updatedStartPosX: x, xPosition: x }); this.props.setPosition(x, undefined); } if (prevProps.updateMassPosY != this.props.updateMassPosY) { - let y = this.props.updateMassPosY; - y = Math.max(0, y); - y = Math.min(y, this.props.yMax - 2 * this.props.radius); - let coordinatePosition = this.getDisplayYPos(y); - this.setState({ updatedStartPosY: coordinatePosition }); - this.setState({ yPosition: coordinatePosition }); + const y = Math.min(Math.max(0, this.props.updateMassPosY), this.props.yMax - 2 * this.props.radius); + const coordinatePosition = this.getDisplayYPos(y); + this.setState({ yPosition: coordinatePosition, updatedStartPosY: coordinatePosition }); this.props.setPosition(undefined, this.getDisplayYPos(y)); if (this.props.displayXVelocity != this.state.xVelocity) { - let x = this.props.displayXVelocity; + const x = this.props.displayXVelocity; this.setState({ xVelocity: x }); this.props.setVelocity(x, undefined); } if (this.props.displayYVelocity != -this.state.yVelocity) { - let y = this.props.displayYVelocity; + const y = this.props.displayYVelocity; this.setState({ yVelocity: -y }); this.props.setVelocity(undefined, y); } @@ -221,34 +209,28 @@ export default class Weight extends React.Component<IWeightProps, IState> { if (this.props.startVelY != 0) { maxYPos -= (this.props.startVelY * this.props.startVelY) / (2 * Math.abs(this.props.gravity)); } - if (maxYPos < 0) { - maxYPos = 0; - } + if (maxYPos < 0) maxYPos = 0; + this.setState({ maxPosYConservation: maxYPos }); } } // Check for collisions and update - if (!this.props.paused) { - if (prevState.timer != this.state.timer) { - if (!this.props.noMovement) { - let collisions = false; - if (this.props.simulationType == 'One Weight' || this.props.simulationType == 'Inclined Plane') { - const collisionsWithGround = this.checkForCollisionsWithGround(); - const collisionsWithWalls = this.checkForCollisionsWithWall(); - collisions = collisionsWithGround || collisionsWithWalls; - } - if (this.props.simulationType == 'Pulley') { - if (this.state.yPosition <= this.props.yMin + 100 || this.state.yPosition >= this.props.yMax - 100) { - collisions = true; - } - } - if (!collisions) { - this.update(); - } - this.setDisplayValues(); + if (!this.props.paused && !this.props.noMovement && prevState.timer != this.state.timer) { + let collisions = false; + if (this.props.simulationType == 'One Weight' || this.props.simulationType == 'Inclined Plane') { + const collisionsWithGround = this.checkForCollisionsWithGround(); + const collisionsWithWalls = this.checkForCollisionsWithWall(); + collisions = collisionsWithGround || collisionsWithWalls; + } + if (this.props.simulationType == 'Pulley') { + if (this.state.yPosition <= this.props.yMin + 100 || this.state.yPosition >= this.props.yMax - 100) { + collisions = true; } } + if (!collisions) this.update(); + + this.setDisplayValues(); } // Reset everything on reset button click @@ -266,23 +248,23 @@ export default class Weight extends React.Component<IWeightProps, IState> { directionInDegrees: 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI, component: false, }; - let frictionForce: IForce = { + const frictionForce: IForce = { description: 'Kinetic Friction Force', magnitude: this.props.mass * this.props.coefficientOfKineticFriction * Math.abs(this.props.gravity) * Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)), directionInDegrees: 180 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI, component: false, }; // reduce magnitude of friction force if necessary such that block cannot slide up plane - let yForce = -Math.abs(this.props.gravity); - yForce += normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180); - yForce += frictionForce.magnitude * Math.sin((frictionForce.directionInDegrees * Math.PI) / 180); + // prettier-ignore + const yForce = -Math.abs(this.props.gravity) + + normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180) + + frictionForce.magnitude * Math.sin((frictionForce.directionInDegrees * Math.PI) / 180); if (yForce > 0) { frictionForce.magnitude = (-normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180) + Math.abs(this.props.gravity)) / Math.sin((frictionForce.directionInDegrees * Math.PI) / 180); } const frictionForceComponent: IForce = { description: 'Kinetic Friction Force', - magnitude: this.props.mass * this.props.coefficientOfKineticFriction * Math.abs(this.props.gravity) * Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)), directionInDegrees: 180 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI, component: true, @@ -323,18 +305,13 @@ export default class Weight extends React.Component<IWeightProps, IState> { // Add/remove walls when simulation type changes if (prevProps.simulationType != this.props.simulationType) { - let w: IWallProps[] = []; - if (this.props.simulationType == 'One Weight' || this.props.simulationType == 'Inclined Plane') { - w = this.props.wallPositions; - } - this.setState({ walls: w }); + this.setState({ walls: ['One Weight', 'Inclined Plane'].includes(this.props.simulationType) ? this.props.wallPositions : [] }); } // Update x position when start pos x changes if (prevProps.startPosX != this.props.startPosX) { if (this.props.paused && !isNaN(this.props.startPosX)) { - this.setState({ xPosition: this.props.startPosX }); - this.setState({ updatedStartPosX: this.props.startPosX }); + this.setState({ xPosition: this.props.startPosX, updatedStartPosX: this.props.startPosX }); this.props.setPosition(this.props.startPosX, undefined); } } @@ -342,8 +319,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { // Update y position when start pos y changes TODO debug if (prevProps.startPosY != this.props.startPosY) { if (this.props.paused && !isNaN(this.props.startPosY)) { - this.setState({ yPosition: this.props.startPosY }); - this.setState({ updatedStartPosY: this.props.startPosY ?? 0 }); + this.setState({ yPosition: this.props.startPosY, updatedStartPosY: this.props.startPosY ?? 0 }); this.props.setPosition(undefined, this.getDisplayYPos(this.props.startPosY)); } } @@ -380,78 +356,53 @@ export default class Weight extends React.Component<IWeightProps, IState> { // Reset simulation on reset button click resetEverything = () => { - this.setState({ kineticFriction: false }); - this.setState({ xPosition: this.state.updatedStartPosX }); - this.setState({ yPosition: this.state.updatedStartPosY }); - this.setState({ xVelocity: this.props.startVelX }); - this.setState({ yVelocity: this.props.startVelY }); + this.setState({ + kineticFriction: false, + xPosition: this.state.updatedStartPosX, + yPosition: this.state.updatedStartPosY, + xVelocity: this.props.startVelX, + yVelocity: this.props.startVelY, + angleLabel: Math.round(this.props.pendulumAngle * 100) / 100, + }); this.props.setPendulumAngle(this.props.startPendulumAngle, undefined); this.props.setForcesUpdated(this.props.startForces()); this.props.setPosition(this.state.updatedStartPosX, this.state.updatedStartPosY); this.props.setVelocity(this.props.startVelX, this.props.startVelY); this.props.setAcceleration(0, 0); - this.setState({ angleLabel: Math.round(this.props.pendulumAngle * 100) / 100 }); }; // Compute x acceleration from forces, F=ma getNewAccelerationX = (forceList: IForce[]) => { - let newXAcc = 0; - if (forceList) { - forceList.forEach(force => { - newXAcc += (force.magnitude * Math.cos((force.directionInDegrees * Math.PI) / 180)) / this.props.mass; - }); - } - return newXAcc; + // prettier-ignore + return forceList.reduce((newXacc, force) => + newXacc + (force.magnitude * Math.cos((force.directionInDegrees * Math.PI) / 180)) / this.props.mass, 0); }; // Compute y acceleration from forces, F=ma getNewAccelerationY = (forceList: IForce[]) => { - let newYAcc = 0; - if (forceList) { - forceList.forEach(force => { - newYAcc += (-1 * (force.magnitude * Math.sin((force.directionInDegrees * Math.PI) / 180))) / this.props.mass; - }); - } - return newYAcc; + // prettier-ignore + return forceList.reduce((newYacc, force) => + newYacc + (-1 * (force.magnitude * Math.sin((force.directionInDegrees * Math.PI) / 180))) / this.props.mass, 0); }; // Compute uniform circular motion forces given x, y positions - getNewCircularMotionForces = (xPos: number, yPos: number) => { - let deltaX = (this.props.xMin + this.props.xMax) / 2 - (xPos + this.props.radius); - let deltaY = yPos + this.props.radius - (this.props.yMin + this.props.yMax) / 2; - let dir = (Math.atan2(deltaY, deltaX) * 180) / Math.PI; - const tensionForce: IForce = { - description: 'Centripetal Force', - magnitude: (this.props.startVelX ** 2 * this.props.mass) / this.props.circularMotionRadius, - directionInDegrees: dir, - component: false, - }; - return [tensionForce]; + getNewCircularMotionForces = (xPos: number, yPos: number): IForce[] => { + const deltaX = (this.props.xMin + this.props.xMax) / 2 - (xPos + this.props.radius); + const deltaY = yPos + this.props.radius - (this.props.yMin + this.props.yMax) / 2; + return [ + { + description: 'Centripetal Force', + magnitude: (this.props.startVelX ** 2 * this.props.mass) / this.props.circularMotionRadius, + directionInDegrees: (Math.atan2(deltaY, deltaX) * 180) / Math.PI, + component: false, + }, + ]; }; // Compute spring forces given y position - getNewSpringForces = (yPos: number) => { - let springForce: IForce = { - description: 'Spring Force', - magnitude: 0, - directionInDegrees: 90, - component: false, - }; - if (yPos - this.props.springRestLength > 0) { - springForce = { - description: 'Spring Force', - magnitude: this.props.springConstant * (yPos - this.props.springRestLength), - directionInDegrees: 90, - component: false, - }; - } else if (yPos - this.props.springRestLength < 0) { - springForce = { - description: 'Spring Force', - magnitude: this.props.springConstant * (this.props.springRestLength - yPos), - directionInDegrees: 270, - component: false, - }; - } + getNewSpringForces = (yPos: number): IForce[] => { + const yPosPlus = yPos - this.props.springRestLength > 0; + const yPosMinus = yPos - this.props.springRestLength < 0; return [ { description: 'Gravity', @@ -459,12 +410,17 @@ export default class Weight extends React.Component<IWeightProps, IState> { directionInDegrees: 270, component: false, }, - springForce, + { + description: 'Spring Force', + magnitude: this.props.springConstant * (yPos - this.props.springRestLength) * (yPosPlus ? 1 : yPosMinus ? -1 : 0), + directionInDegrees: yPosPlus ? 90 : 270, + component: false, + }, ]; }; // Compute pendulum forces given position, velocity - getNewPendulumForces = (xPos: number, yPos: number, xVel: number, yVel: number) => { + getNewPendulumForces = (xPos: number, yPos: number, xVel: number, yVel: number): IForce[] => { const x = this.props.xMax / 2 - xPos - this.props.radius; const y = yPos + this.props.radius + 5; let angle = (Math.atan(y / x) * 180) / Math.PI; @@ -481,13 +437,6 @@ export default class Weight extends React.Component<IWeightProps, IState> { const mag = this.props.mass * Math.abs(this.props.gravity) * Math.cos((oppositeAngle * Math.PI) / 180) + (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength; - const forceOfTension: IForce = { - description: 'Tension', - magnitude: mag, - directionInDegrees: angle, - component: false, - }; - return [ { description: 'Gravity', @@ -495,42 +444,33 @@ export default class Weight extends React.Component<IWeightProps, IState> { directionInDegrees: 270, component: false, }, - forceOfTension, + { + description: 'Tension', + magnitude: mag, + directionInDegrees: angle, + component: false, + }, ]; }; // Check for collisions in x direction checkForCollisionsWithWall = () => { let collision = false; - const minX = this.state.xPosition; - const maxX = this.state.xPosition + 2 * this.props.radius; - if (this.state.xVelocity != 0) { - this.state.walls.forEach(wall => { - if (wall.angleInDegrees == 90) { + if (this.state.xVelocity !== 0) { + this.state.walls + .filter(wall => wall.angleInDegrees === 90) + .forEach(wall => { const wallX = (wall.xPos / 100) * this.props.panelWidth(); - if (wall.xPos < 0.35) { - if (minX <= wallX) { - this.setState({ xPosition: wallX + 0.01 }); - if (this.props.elasticCollisions) { - this.setState({ xVelocity: -this.state.xVelocity }); - } else { - this.setState({ xVelocity: 0 }); - } - collision = true; - } - } else { - if (maxX >= wallX) { - this.setState({ xPosition: wallX - 2 * this.props.radius - 0.01 }); - if (this.props.elasticCollisions) { - this.setState({ xVelocity: -this.state.xVelocity }); - } else { - this.setState({ xVelocity: 0 }); - } - collision = true; - } + const minX = this.state.xPosition < wallX && wall.xPos < 0.35; + const maxX = this.state.xPosition + 2 * this.props.radius >= wallX && wall.xPos > 0.35; + if (minX || maxX) { + this.setState({ + xPosition: minX ? wallX + 0.01 : wallX - 2 * this.props.radius - 0.01, + xVelocity: this.props.elasticCollisions ? -this.state.xVelocity : 0, + }); + collision = true; } - } - }); + }); } return collision; }; @@ -544,41 +484,27 @@ export default class Weight extends React.Component<IWeightProps, IState> { this.state.walls.forEach(wall => { if (wall.angleInDegrees == 0 && wall.yPos > 0.4) { const groundY = (wall.yPos / 100) * this.props.panelHeight(); + const gravity: IForce = { + description: 'Gravity', + magnitude: Math.abs(this.props.gravity) * this.props.mass, + directionInDegrees: 270, + component: false, + }; if (maxY > groundY) { this.setState({ yPosition: groundY - 2 * this.props.radius - 0.01 }); if (this.props.elasticCollisions) { this.setState({ yVelocity: -this.state.yVelocity }); } else { this.setState({ yVelocity: 0 }); - if (this.props.simulationType != 'Two Weights') { - const forceOfGravity: IForce = { - description: 'Gravity', - magnitude: Math.abs(this.props.gravity) * this.props.mass, - directionInDegrees: 270, - component: false, - }; - const normalForce: IForce = { - description: 'Normal force', - magnitude: Math.abs(this.props.gravity) * this.props.mass, - directionInDegrees: wall.angleInDegrees + 90, - component: false, - }; - this.props.setForcesUpdated([forceOfGravity, normalForce]); - if (this.props.simulationType == 'Inclined Plane') { - const forceOfGravityC: IForce = { - description: 'Gravity', - magnitude: Math.abs(this.props.gravity) * this.props.mass, - directionInDegrees: 270, - component: true, - }; - const normalForceC: IForce = { - description: 'Normal force', - magnitude: Math.abs(this.props.gravity) * this.props.mass, - directionInDegrees: wall.angleInDegrees + 90, - component: true, - }; - this.props.setComponentForces([forceOfGravityC, normalForceC]); - } + const normalForce: IForce = { + description: 'Normal force', + magnitude: gravity.magnitude, + directionInDegrees: -gravity.directionInDegrees, + component: false, + }; + this.props.setForcesUpdated([gravity, normalForce]); + if (this.props.simulationType === 'Inclined Plane') { + this.props.setComponentForces([gravity, { ...normalForce, component: true }]); } } collision = true; @@ -591,12 +517,10 @@ export default class Weight extends React.Component<IWeightProps, IState> { if (wall.angleInDegrees == 0 && wall.yPos < 0.4) { const groundY = (wall.yPos / 100) * this.props.panelHeight(); if (minY < groundY) { - this.setState({ yPosition: groundY + 0.01 }); - if (this.props.elasticCollisions) { - this.setState({ yVelocity: -this.state.yVelocity }); - } else { - this.setState({ yVelocity: 0 }); - } + this.setState({ + yPosition: groundY + 0.01, + yVelocity: this.props.elasticCollisions ? -this.state.yVelocity : 0, + }); collision = true; } } @@ -606,51 +530,43 @@ export default class Weight extends React.Component<IWeightProps, IState> { }; // Called at each RK4 step - evaluate = (currentXPos: number, currentYPos: number, currentXVel: number, currentYVel: number, deltaXPos: number, deltaYPos: number, deltaXVel: number, deltaYVel: number, dt: number) => { - const newXPos = currentXPos + deltaXPos * dt; - const newYPos = currentYPos + deltaYPos * dt; - const newXVel = currentXVel + deltaXVel * dt; - const newYVel = currentYVel + deltaYVel * dt; - const newDeltaXPos = newXVel; - const newDeltaYPos = newYVel; - let forces = this.props.forcesUpdated(); - if (this.props.simulationType == 'Pendulum') { - forces = this.getNewPendulumForces(newXPos, newYPos, newXVel, newYVel); - } else if (this.props.simulationType == 'Spring') { - forces = this.getNewSpringForces(newYPos); - } else if (this.props.simulationType == 'Circular Motion') { - forces = this.getNewCircularMotionForces(newXPos, newYPos); - } - const newDeltaXVel = this.getNewAccelerationX(forces); - const newDeltaYVel = this.getNewAccelerationY(forces); + evaluate = (currentXPos: number, currentYPos: number, currentXVel: number, currentYVel: number, currdeltaXPos: number, currdeltaYPos: number, currdeltaXVel: number, currdeltaYVel: number, dt: number) => { + const xPos = currentXPos + currdeltaXPos * dt; + const yPos = currentYPos + currdeltaYPos * dt; + const xVel = currentXVel + currdeltaXVel * dt; + const yVel = currentYVel + currdeltaYVel * dt; + const forces = this.getForces(xPos, yPos, xVel, yVel); return { - xPos: newXPos, - yPos: newYPos, - xVel: newXVel, - yVel: newYVel, - deltaXPos: newDeltaXPos, - deltaYPos: newDeltaYPos, - deltaXVel: newDeltaXVel, - deltaYVel: newDeltaYVel, + xPos, + yPos, + xVel, + yVel, + deltaXPos: xVel, + deltaYPos: yVel, + deltaXVel: this.getNewAccelerationX(forces), + deltaYVel: this.getNewAccelerationY(forces), }; }; + getForces = (xPos: number, yPos: number, xVel: number, yVel: number) => { + // prettier-ignore + switch (this.props.simulationType) { + case 'Pendulum': return this.getNewPendulumForces(xPos, yPos, xVel, yVel); + case 'Spring' : return this.getNewSpringForces(yPos); + case 'Circular Motion': return this.getNewCircularMotionForces(xPos, yPos); + default: return this.props.forcesUpdated(); + } + }; + // Update position, velocity using RK4 method update = () => { - let startXVel = this.state.xVelocity; - let startYVel = this.state.yVelocity; + const startXVel = this.state.xVelocity; + const startYVel = this.state.yVelocity; let xPos = this.state.xPosition; let yPos = this.state.yPosition; let xVel = this.state.xVelocity; let yVel = this.state.yVelocity; - let forces = this.props.forcesUpdated(); - if (this.props.simulationType == 'Pendulum') { - forces = this.getNewPendulumForces(xPos, yPos, xVel, yVel); - } else if (this.props.simulationType == 'Spring') { - forces = this.getNewSpringForces(yPos); - } else if (this.props.simulationType == 'Circular Motion') { - forces = this.getNewCircularMotionForces(xPos, yPos); - } + const forces = this.getForces(xPos, yPos, xVel, yVel); const xAcc = this.getNewAccelerationX(forces); const yAcc = this.getNewAccelerationY(forces); for (let i = 0; i < this.props.simulationSpeed; i++) { @@ -665,56 +581,48 @@ export default class Weight extends React.Component<IWeightProps, IState> { yPos += ((this.props.timestepSize * 1.0) / 6.0) * (k1.deltaYPos + 2 * (k2.deltaYPos + k3.deltaYPos) + k4.deltaYPos); } // make sure harmonic motion maintained and errors don't propagate - if (this.props.simulationType == 'Spring') { - if (startYVel < 0 && yVel > 0 && yPos < this.props.springRestLength) { - let equilibriumPos = this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant; - let amplitude = Math.abs(equilibriumPos - this.props.springStartLength); - yPos = equilibriumPos - amplitude; - } else if (startYVel > 0 && yVel < 0 && yPos > this.props.springRestLength) { - let equilibriumPos = this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant; - let amplitude = Math.abs(equilibriumPos - this.props.springStartLength); - yPos = equilibriumPos + amplitude; - } - } - if (this.props.simulationType == 'Pendulum') { - let startX = this.state.updatedStartPosX; - if (startXVel <= 0 && xVel > 0) { - xPos = this.state.updatedStartPosX; - if (this.state.updatedStartPosX > this.props.xMax / 2) { - xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius; + switch (this.props.simulationType) { + case 'Spring': + if (startYVel < 0 && yVel > 0 && yPos < this.props.springRestLength) { + const equilibriumPos = this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant; + const amplitude = Math.abs(equilibriumPos - this.props.springStartLength); + yPos = equilibriumPos - amplitude; + } else if (startYVel > 0 && yVel < 0 && yPos > this.props.springRestLength) { + const equilibriumPos = this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant; + const amplitude = Math.abs(equilibriumPos - this.props.springStartLength); + yPos = equilibriumPos + amplitude; } - yPos = this.props.startPosY; - } else if (startXVel >= 0 && xVel < 0) { - xPos = this.state.updatedStartPosX; - if (this.state.updatedStartPosX < this.props.xMax / 2) { - xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius; + break; + case 'Pendulum': + const startX = this.state.updatedStartPosX; + if (startXVel <= 0 && xVel > 0) { + xPos = this.state.updatedStartPosX; + if (this.state.updatedStartPosX > this.props.xMax / 2) { + xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius; + } + yPos = this.props.startPosY; + } else if (startXVel >= 0 && xVel < 0) { + xPos = this.state.updatedStartPosX; + if (this.state.updatedStartPosX < this.props.xMax / 2) { + xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius; + } + yPos = this.props.startPosY; + } + break; + case 'One Weight': + if (yPos < this.state.maxPosYConservation) { + yPos = this.state.maxPosYConservation; } - yPos = this.props.startPosY; - } - } - if (this.props.simulationType == 'One Weight') { - if (yPos < this.state.maxPosYConservation) { - yPos = this.state.maxPosYConservation; - } - } - this.setState({ xVelocity: xVel }); - this.setState({ yVelocity: yVel }); - this.setState({ xPosition: xPos }); - this.setState({ yPosition: yPos }); - let forcesn = this.props.forcesUpdated(); - if (this.props.simulationType == 'Pendulum') { - forcesn = this.getNewPendulumForces(xPos, yPos, xVel, yVel); - } else if (this.props.simulationType == 'Spring') { - forcesn = this.getNewSpringForces(yPos); - } else if (this.props.simulationType == 'Circular Motion') { - forcesn = this.getNewCircularMotionForces(xPos, yPos); } + this.setState({ xVelocity: xVel, yVelocity: yVel, xPosition: xPos, yPosition: yPos }); + + const forcesn = this.getForces(xPos, yPos, xVel, yVel); this.props.setForcesUpdated(forcesn); // set component forces if they change if (this.props.simulationType == 'Pendulum') { - let x = this.props.xMax / 2 - xPos - this.props.radius; - let y = yPos + this.props.radius + 5; + const x = this.props.xMax / 2 - xPos - this.props.radius; + const y = yPos + this.props.radius + 5; let angle = (Math.atan(y / x) * 180) / Math.PI; if (angle < 0) { angle += 180; @@ -756,7 +664,6 @@ export default class Weight extends React.Component<IWeightProps, IState> { // Render weight, spring, rod(s), vectors render() { - trace(); return ( <div> <div @@ -764,9 +671,11 @@ export default class Weight extends React.Component<IWeightProps, IState> { onPointerDown={e => { if (this.draggable) { this.props.pause(); - this.setState({ dragging: true }); - this.setState({ clickPositionX: e.clientX }); - this.setState({ clickPositionY: e.clientY }); + this.setState({ + dragging: true, + clickPositionX: e.clientX, + clickPositionY: e.clientY, + }); } }} onPointerMove={e => { @@ -804,8 +713,10 @@ export default class Weight extends React.Component<IWeightProps, IState> { } this.setState({ updatedStartPosY: newY }); } - this.setState({ clickPositionX: e.clientX }); - this.setState({ clickPositionY: e.clientY }); + this.setState({ + clickPositionX: e.clientX, + clickPositionY: e.clientY, + }); this.setDisplayValues(); } }} @@ -832,27 +743,25 @@ export default class Weight extends React.Component<IWeightProps, IState> { this.props.setSpringLength(newY); } if (this.props.simulationType == 'Suspension') { - let x1rod = (this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200; - let x2rod = (this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius; - let deltaX1 = this.state.xPosition + this.props.radius - x1rod; - let deltaX2 = x2rod - (this.state.xPosition + this.props.radius); - let deltaY = this.state.yPosition + this.props.radius; - let dir1T = Math.PI - Math.atan(deltaY / deltaX1); - let dir2T = Math.atan(deltaY / deltaX2); - let tensionMag2 = (this.props.mass * Math.abs(this.props.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T)); - let tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T); - dir1T = (dir1T * 180) / Math.PI; - dir2T = (dir2T * 180) / Math.PI; + const x1rod = (this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200; + const x2rod = (this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius; + const deltaX1 = this.state.xPosition + this.props.radius - x1rod; + const deltaX2 = x2rod - (this.state.xPosition + this.props.radius); + const deltaY = this.state.yPosition + this.props.radius; + const dir1T = Math.PI - Math.atan(deltaY / deltaX1); + const dir2T = Math.atan(deltaY / deltaX2); + const tensionMag2 = (this.props.mass * Math.abs(this.props.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T)); + const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T); const tensionForce1: IForce = { description: 'Tension', magnitude: tensionMag1, - directionInDegrees: dir1T, + directionInDegrees: (dir1T * 180) / Math.PI, component: false, }; const tensionForce2: IForce = { description: 'Tension', magnitude: tensionMag2, - directionInDegrees: dir2T, + directionInDegrees: (dir2T * 180) / Math.PI, component: false, }; const grav: IForce = { @@ -881,19 +790,10 @@ export default class Weight extends React.Component<IWeightProps, IState> { <svg width={this.props.xMax + 'px'} height={this.props.panelHeight() + 'px'}> {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(val => { const count = 10; - let xPos1; - let yPos1; - let xPos2; - let yPos2; - if (val % 2 == 0) { - xPos1 = this.state.xPosition + this.props.radius - 20; - xPos2 = this.state.xPosition + this.props.radius + 20; - } else { - xPos1 = this.state.xPosition + this.props.radius + 20; - xPos2 = this.state.xPosition + this.props.radius - 20; - } - yPos1 = (val * this.state.yPosition) / count; - yPos2 = ((val + 1) * this.state.yPosition) / count; + const xPos1 = this.state.xPosition + this.props.radius + (val % 2 === 0 ? -20 : 20); + const yPos1 = (val * this.state.yPosition) / count; + const xPos2 = this.state.xPosition + this.props.radius + (val % 2 === 0 ? 20 : -20); + const yPos2 = ((val + 1) * this.state.yPosition) / count; return <line key={val} x1={xPos1} y1={yPos1} x2={xPos2} y2={yPos2} stroke={'#808080'} strokeWidth="10" />; })} </svg> @@ -1154,12 +1054,12 @@ export default class Weight extends React.Component<IWeightProps, IState> { if (force.magnitude < this.epsilon) { return; } - let arrowStartY: number = this.state.yPosition + this.props.radius; + const arrowStartY: number = this.state.yPosition + this.props.radius; const arrowStartX: number = this.state.xPosition + this.props.radius; - let arrowEndY: number = arrowStartY - Math.abs(force.magnitude) * 20 * Math.sin((force.directionInDegrees * Math.PI) / 180); + const arrowEndY: number = arrowStartY - Math.abs(force.magnitude) * 20 * Math.sin((force.directionInDegrees * Math.PI) / 180); const arrowEndX: number = arrowStartX + Math.abs(force.magnitude) * 20 * Math.cos((force.directionInDegrees * Math.PI) / 180); - let color = '#0d0d0d'; + const color = '#0d0d0d'; let labelTop = arrowEndY; let labelLeft = arrowEndX; @@ -1219,12 +1119,12 @@ export default class Weight extends React.Component<IWeightProps, IState> { if (force.magnitude < this.epsilon) { return; } - let arrowStartY: number = this.state.yPosition + this.props.radius; + const arrowStartY: number = this.state.yPosition + this.props.radius; const arrowStartX: number = this.state.xPosition + this.props.radius; - let arrowEndY: number = arrowStartY - Math.abs(force.magnitude) * 20 * Math.sin((force.directionInDegrees * Math.PI) / 180); + const arrowEndY: number = arrowStartY - Math.abs(force.magnitude) * 20 * Math.sin((force.directionInDegrees * Math.PI) / 180); const arrowEndX: number = arrowStartX + Math.abs(force.magnitude) * 20 * Math.cos((force.directionInDegrees * Math.PI) / 180); - let color = '#0d0d0d'; + const color = '#0d0d0d'; let labelTop = arrowEndY; let labelLeft = arrowEndX; |