diff options
author | bobzel <zzzman@gmail.com> | 2023-05-24 15:04:13 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2023-05-24 15:04:13 -0400 |
commit | 4c96bbd25d84964811838d005ff4e40487e1ec41 (patch) | |
tree | 01e7b2e33cd1e8010b9e545130c166303e11354f /src | |
parent | f0dad2a437ac339e2d6126bdadfd7a110d9ff999 (diff) |
more phys code streamlining
Diffstat (limited to 'src')
3 files changed, 117 insertions, 308 deletions
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss index b498296bf..2e6ad413f 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss @@ -22,6 +22,12 @@ justify-content: space-between; } } + .rod { + pointer-events: none; + position: absolute; + left: 0; + top: 0; + } } .coordinateSystem { diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx index a0b47f13f..d3e1c6fd2 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx @@ -6,7 +6,7 @@ import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; import ReplayIcon from '@mui/icons-material/Replay'; import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, FormControl, FormControlLabel, FormGroup, IconButton, LinearProgress, Stack } from '@mui/material'; import Typography from '@mui/material/Typography'; -import { computed, reaction } from 'mobx'; +import { computed, IReactionDisposer, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { NumListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; @@ -31,7 +31,6 @@ interface IForce { description: string; magnitude: number; directionInDegrees: number; - component: boolean; } interface VectorTemplate { top: number; @@ -76,20 +75,23 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP return FieldView.LayoutString(PhysicsSimulationBox, fieldKey); } + _widthDisposer: IReactionDisposer | undefined; + + // semi-Constants + xMin = 0; + yMin = 0; + xMax = this.props.PanelWidth() * 0.6; + yMax = this.props.PanelHeight(); + color = `rgba(0,0,0,0.5)`; + radius = 50; + wallPositions: IWallProps[] = []; + @computed get circularMotionRadius() { return NumCast(this.dataDoc.circularMotionRadius, 150); } @computed get gravity() { return NumCast(this.dataDoc.simulation_gravity, -9.81); } - gravityForce = (mass: number): IForce => { - return { - description: 'Gravity', - magnitude: mass * Math.abs(this.gravity), - directionInDegrees: 270, - component: false, - }; - }; @computed get simulationType() { return StrCast(this.dataDoc.simulation_type, 'Inclined Plane'); } @@ -138,13 +140,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP } @computed get mass1Radius() { - return NumCast(this.dataDoc.mass1_radius, 60); + return NumCast(this.dataDoc.mass1_radius, 30); } @computed get mass1PosXStart() { - return NumCast(this.dataDoc.mass1_positionXstart, Math.round((this.xMax * 0.5 - 200) * 10) / 10); + return NumCast(this.dataDoc.mass1_positionXstart); } @computed get mass1PosYStart() { - return NumCast(this.dataDoc.mass1_positionYstart, this.getDisplayYPos((400 - 0.08 * this.props.PanelHeight()) * Math.tan((26 * Math.PI) / 180) + Math.sqrt(26))); + return NumCast(this.dataDoc.mass1_positionYstart); } @computed get mass1VelXStart() { return NumCast(this.dataDoc.mass1_velocityXstart); @@ -157,7 +159,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP return NumCast(this.dataDoc.mass2_positionXstart); } @computed get mass2PosYStart() { - return NumCast(this.dataDoc.mass2_positionYstart, this.getDisplayYPos((400 - 0.08 * this.props.PanelHeight()) * Math.tan((26 * Math.PI) / 180) + Math.sqrt(26))); + return NumCast(this.dataDoc.mass2_positionYstart); } @computed get mass2VelXStart() { return NumCast(this.dataDoc.mass2_velocityXstart); @@ -182,18 +184,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP return StrCast(this.dataDoc.questionPartTwo); } - // Constants - xMin = 0; - yMin = 0; - xMax = this.props.PanelWidth() * 0.6; - yMax = this.props.PanelHeight(); - color = `rgba(0,0,0,0.5)`; - radius = 50; - wallPositions: IWallProps[] = []; + componentWillUnmount() { + this._widthDisposer?.(); + } componentDidMount() { // Setup and update simulation - reaction(() => [this.props.PanelWidth(), this.props.PanelHeight()], this.setupSimulation, { fireImmediately: true }); + this._widthDisposer = reaction(() => [this.props.PanelWidth(), this.props.PanelHeight()], this.setupSimulation, { fireImmediately: true }); // Create walls this.wallPositions = [ @@ -212,6 +209,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP } } + gravityForce = (mass: number): IForce => ({ + description: 'Gravity', + magnitude: mass * Math.abs(this.gravity), + directionInDegrees: 270, + }); + setupSimulation = () => { const simulationType = this.simulationType; const mode = this.simulationMode; @@ -228,10 +231,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP switch (simulationType) { case 'One Weight': this.dataDoc.simulation_showComponentForces = false; - this.dataDoc.mass1_positionYstart = this.yMin + 0.08 * this.props.PanelHeight(); - this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight(); - this.dataDoc.mass1_positionY = this.getDisplayYPos(this.yMin + 0.08 * this.props.PanelHeight()); - this.dataDoc.mass1_positionX = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight(); + this.dataDoc.mass1_positionYstart = this.yMin + this.mass1Radius; + this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius; + this.dataDoc.mass1_positionY = this.getDisplayYPos(this.yMin + this.mass1Radius); + this.dataDoc.mass1_positionX = (this.xMax + this.xMin) / 2 - this.mass1Radius; this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]); this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1)]); this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; @@ -256,10 +259,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP switch (simulationType) { case 'One Weight' : break;// TODO - one weight review problems case 'Spring': this.setupSpring(); break; // TODO - spring review problems - case 'Inclined Plane': - this.dataDoc.mass1_forcesUpdated = ''; - this.dataDoc.mass1_forcesStart = ''; - break; + case 'Inclined Plane': this.dataDoc.mass1_forcesUpdated = this.dataDoc.mass1_forcesStart = ''; break; case 'Pendulum': this.setupPendulum(); break; // TODO - pendulum review problems case 'Circular Motion': this.setupCircular(0); break; // TODO - circular motion review problems case 'Pulley': this.setupPulley(); break; // TODO - pulley tutorial review problems @@ -283,7 +283,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP case 'One Weight': this.dataDoc.simulation_showForces = true; this.dataDoc.mass1_positionYstart = this.yMax - 100; - this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight(); + this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius; this.dataDoc.tutorial = JSON.stringify(tutorials.freeWeight); this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.freeWeight.steps[0].forces); this.dataDoc.simulation_showForceMagnitudes = tutorials.freeWeight.steps[0].showMagnitude; @@ -292,7 +292,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.simulation_showForces = true; this.setupSpring(); this.dataDoc.mass1_positionYstart = this.yMin + 200 + 19.62; - this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight(); + this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius; this.dataDoc.tutorial = JSON.stringify(tutorials.spring); this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.spring.steps[0].forces); this.dataDoc.simulation_showForceMagnitudes = tutorials.spring.steps[0].showMagnitude; @@ -338,12 +338,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP }; // Helper function to go between display and real values - getDisplayYPos = (yPos: number) => { - return this.yMax - yPos - 2 * (0.08 * this.props.PanelHeight()) + 5; - }; - getYPosFromDisplay = (yDisplay: number) => { - return this.yMax - yDisplay - 2 * (0.08 * this.props.PanelHeight()) + 5; - }; + getDisplayYPos = (yPos: number) => this.yMax - yPos - 2 * this.mass1Radius + 5; + getYPosFromDisplay = (yDisplay: number) => this.yMax - yDisplay - 2 * this.mass1Radius + 5; // Update forces when coefficient of static friction changes in freeform mode updateForcesWithFriction = (coefficient: number, width = this.wedgeWidth, height = this.wedgeHeight) => { @@ -351,13 +347,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP description: 'Normal Force', magnitude: Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1, directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI, - component: false, }; let frictionForce: IForce = { description: 'Static Friction Force', magnitude: coefficient * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1, directionInDegrees: 180 - (Math.atan(height / width) * 180) / Math.PI, - component: false, }; // reduce magnitude or friction force if necessary such that block cannot slide up plane let yForce = -Math.abs(this.gravity) * this.mass1; @@ -366,35 +360,27 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP if (yForce > 0) { frictionForce.magnitude = (-normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180) + Math.abs(this.gravity) * this.mass1) / Math.sin((frictionForce.directionInDegrees * Math.PI) / 180); } - const frictionForceComponent: IForce = { - description: 'Static Friction Force', - magnitude: coefficient * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)), - directionInDegrees: 180 - (Math.atan(height / width) * 180) / Math.PI, - component: true, - }; + const normalForceComponent: IForce = { description: 'Normal Force', - magnitude: Math.abs(this.gravity) * Math.cos(Math.atan(height / width)), + magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)), directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI, - component: true, }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin(Math.PI / 2 - Math.atan(height / width)), directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI + 180, - component: true, }; const gravityPerpendicular: IForce = { description: 'Gravity Perpendicular Component', magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(Math.PI / 2 - Math.atan(height / width)), directionInDegrees: 360 - (Math.atan(height / width) * 180) / Math.PI, - component: true, }; const gravityForce = this.gravityForce(this.mass1); if (coefficient != 0) { this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce, normalForce, frictionForce]); this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce, normalForce, frictionForce]); - this.dataDoc.mass1_componentForces = JSON.stringify([frictionForceComponent, normalForceComponent, gravityParallel, gravityPerpendicular]); + this.dataDoc.mass1_componentForces = JSON.stringify([frictionForce, normalForceComponent, gravityParallel, gravityPerpendicular]); } else { this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce, normalForce]); this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce, normalForce]); @@ -549,24 +535,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP } } if (showAlert) { - if (!error) { - this.dataDoc.simulation_paused = false; - setTimeout(() => { - this.dataDoc.simulation_paused = true; - }, 3000); - } else { - this.dataDoc.simulation_paused = false; - setTimeout(() => { - this.dataDoc.simulation_paused = true; - }, 3000); - } + this.dataDoc.simulation_paused = false; + setTimeout(() => (this.dataDoc.simulation_paused = true), 3000); } if (this.selectedQuestion.goal == 'noMovement') { - if (!error) { - this.dataDoc.noMovement = true; - } else { - this.dataDoc.roMovement = false; - } + this.dataDoc.noMovement = !error; } }; @@ -590,11 +563,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP let question: QuestionTemplate = questions.inclinePlane[0]; if (this.simulationType === 'Inclined Plane') { - if (this.dataDoc.questionNumber === questions.inclinePlane.length - 1) { - this.dataDoc.questionNumber = 0; - } else { - this.dataDoc.questionNumber = NumCast(this.dataDoc.questionNumber) + 1; - } + this.dataDoc.questionNumber = (NumCast(this.dataDoc.questionNumber) + 1) % questions.inclinePlane.length; question = questions.inclinePlane[NumCast(this.dataDoc.questionNumber)]; let coefficient = 0; @@ -639,15 +608,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.simulation_showComponentForces = false; this.dataDoc.mass1_velocityYstart = 0; this.dataDoc.mass1_velocityXstart = value; - let xPos = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight(); - let yPos = (this.yMax + this.yMin) / 2 + this.circularMotionRadius - 0.08 * this.props.PanelHeight(); + let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius; + let yPos = (this.yMax + this.yMin) / 2 + this.circularMotionRadius - this.mass1Radius; this.dataDoc.mass1_positionYstart = yPos; this.dataDoc.mass1_positionXstart = xPos; const tensionForce: IForce = { description: 'Centripetal Force', magnitude: (this.dataDoc.mass1_velocityXstart ** 2 * this.mass1) / this.circularMotionRadius, directionInDegrees: 90, - component: false, }; this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce]); this.dataDoc.mass1_forcesStart = JSON.stringify([tensionForce]); @@ -666,38 +634,27 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP const angle = 30; const x = length * Math.cos(((90 - angle) * Math.PI) / 180); const y = length * Math.sin(((90 - angle) * Math.PI) / 180); - const xPos = this.xMax / 2 - x - 0.08 * this.props.PanelHeight(); - const yPos = y - 0.08 * this.props.PanelHeight() - 5; + const xPos = this.xMax / 2 - x - this.mass1Radius; + const yPos = y - this.mass1Radius - 5; this.dataDoc.mass1_positionXstart = xPos; this.dataDoc.mass1_positionYstart = yPos; - const mag = this.mass1 * Math.abs(this.gravity) * Math.sin((60 * Math.PI) / 180); const forceOfTension: IForce = { description: 'Tension', - magnitude: mag, - directionInDegrees: 90 - angle, - component: false, - }; - - const tensionComponent: IForce = { - description: 'Tension', - magnitude: mag, + magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin((60 * Math.PI) / 180), directionInDegrees: 90 - angle, - component: true, }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin(((90 - angle) * Math.PI) / 180), directionInDegrees: -angle - 90, - component: true, }; const gravityPerpendicular: IForce = { description: 'Gravity Perpendicular Component', magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(((90 - angle) * Math.PI) / 180), directionInDegrees: -angle, - component: true, }; - this.dataDoc.mass1_componentForces = JSON.stringify([tensionComponent, gravityParallel, gravityPerpendicular]); + this.dataDoc.mass1_componentForces = JSON.stringify([forceOfTension, gravityParallel, gravityPerpendicular]); this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]); this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]); this.dataDoc.pendulum_angle = this.dataDoc.pendulum_angleStart = 30; @@ -709,7 +666,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.simulation_showComponentForces = false; this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]); this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1)]); - this.dataDoc.mass1_positionXstart = this.xMax / 2 - 0.08 * this.props.PanelHeight(); + this.dataDoc.mass1_positionXstart = this.xMax / 2 - this.mass1Radius; this.dataDoc.mass1_positionYstart = 200; this.dataDoc.spring_constant = 0.5; this.dataDoc.spring_lengthRest = 200; @@ -719,7 +676,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP // Default setup for suspension simulation setupSuspension = () => { - let xPos = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight(); + let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius; let yPos = this.yMin + 200; this.dataDoc.mass1_positionYstart = yPos; this.dataDoc.mass1_positionXstart = xPos; @@ -730,13 +687,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP description: 'Tension', magnitude: tensionMag, directionInDegrees: 45, - component: false, }; const tensionForce2: IForce = { description: 'Tension', magnitude: tensionMag, directionInDegrees: 135, - component: false, }; const gravity = this.gravityForce(this.mass1); this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]); @@ -757,7 +712,6 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP description: 'Tension', magnitude: this.mass1 * a + this.mass1 * Math.abs(this.gravity), directionInDegrees: 90, - component: false, }; this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce1, tensionForce1]); this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce1, tensionForce1]); @@ -767,7 +721,6 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP description: 'Tension', magnitude: -this.mass2 * a + this.mass2 * Math.abs(this.gravity), directionInDegrees: 90, - component: false, }; this.dataDoc.mass2_positionYstart = (this.yMax + this.yMin) / 2; this.dataDoc.mass2_positionXstart = (this.xMin + this.xMax) / 2 + 5; @@ -789,19 +742,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP description: 'Gravity', magnitude: NumCast(this.dataDoc.review_GravityMagnitude), directionInDegrees: NumCast(this.dataDoc.review_GravityAngle), - component: false, }; const normalForceReview: IForce = { description: 'Normal Force', magnitude: NumCast(this.dataDoc.review_NormalMagnitude), directionInDegrees: NumCast(this.dataDoc.review_NormalAngle), - component: false, }; const staticFrictionForceReview: IForce = { description: 'Static Friction Force', magnitude: NumCast(this.dataDoc.review_StaticMagnitude), directionInDegrees: NumCast(this.dataDoc.review_StaticAngle), - component: false, }; this.dataDoc.mass1_forcesStart = JSON.stringify([forceOfGravityReview, normalForceReview, staticFrictionForceReview]); this.dataDoc.mass1_forcesUpdated = JSON.stringify([forceOfGravityReview, normalForceReview, staticFrictionForceReview]); @@ -1378,7 +1328,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP /> {(this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') && ( <FormControlLabel - control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showForces)} onChange={() => (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />} + control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showComponentForces)} onChange={() => (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />} label="Show component force vectors" labelPlacement="start" /> @@ -1616,26 +1566,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP description: 'Tension', magnitude: mag, directionInDegrees: 90 - value, - component: false, - }; - - const tensionComponent: IForce = { - description: 'Tension', - magnitude: mag, - directionInDegrees: 90 - value, - component: true, }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', magnitude: Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180), directionInDegrees: 270 - value, - component: true, }; const gravityPerpendicular: IForce = { description: 'Gravity Perpendicular Component', magnitude: Math.abs(this.gravity) * Math.sin((value * Math.PI) / 180), directionInDegrees: -value, - component: true, }; const length = this.pendulumLength; @@ -1648,7 +1588,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]); this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]); - this.dataDoc.mass1_componentForces = JSON.stringify([tensionComponent, gravityParallel, gravityPerpendicular]); + this.dataDoc.mass1_componentForces = JSON.stringify([forceOfTension, gravityParallel, gravityPerpendicular]); this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; } }} @@ -1736,13 +1676,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP description: 'Tension', magnitude: tensionMag1, directionInDegrees: dir1T, - component: false, }; const tensionForce2: IForce = { description: 'Tension', magnitude: tensionMag2, directionInDegrees: dir2T, - component: false, }; const gravity = this.gravityForce(this.mass1); this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]); @@ -1787,13 +1725,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP description: 'Tension', magnitude: tensionMag1, directionInDegrees: dir1T, - component: false, }; const tensionForce2: IForce = { description: 'Tension', magnitude: tensionMag2, directionInDegrees: dir2T, - component: false, }; const gravity = this.gravityForce(this.mass1); this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]); diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx index d062cc8c5..98b0e3f04 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx @@ -14,7 +14,6 @@ interface IForce { description: string; magnitude: number; directionInDegrees: number; - component: boolean; } export interface IWeightProps { pause: () => void; @@ -250,13 +249,11 @@ export default class Weight extends React.Component<IWeightProps, IState> { description: 'Normal Force', magnitude: this.props.mass * Math.abs(this.props.gravity) * Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)), directionInDegrees: 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI, - component: false, }; 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 // prettier-ignore @@ -271,31 +268,26 @@ export default class Weight extends React.Component<IWeightProps, IState> { 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, }; const normalForceComponent: IForce = { description: 'Normal Force', magnitude: this.props.mass * Math.abs(this.props.gravity) * Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)), directionInDegrees: 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI, - component: true, }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', magnitude: this.props.mass * Math.abs(this.props.gravity) * Math.sin(Math.PI / 2 - Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)), directionInDegrees: 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI + 180, - component: true, }; const gravityPerpendicular: IForce = { description: 'Gravity Perpendicular Component', magnitude: this.props.mass * Math.abs(this.props.gravity) * Math.cos(Math.PI / 2 - Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)), directionInDegrees: 360 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI, - component: true, }; const gravityForce: IForce = { description: 'Gravity', magnitude: this.props.mass * Math.abs(this.props.gravity), directionInDegrees: 270, - component: false, }; const kineticFriction = this.props.coefficientOfKineticFriction != 0; this.props.setForcesUpdated([gravityForce, normalForce, ...(kineticFriction ? [frictionForce] : [])]); @@ -394,7 +386,6 @@ export default class Weight extends React.Component<IWeightProps, IState> { description: 'Centripetal Force', magnitude: (this.props.startVelX ** 2 * this.props.mass) / this.props.circularMotionRadius, directionInDegrees: (Math.atan2(deltaY, deltaX) * 180) / Math.PI, - component: false, }, ]; }; @@ -408,13 +399,11 @@ export default class Weight extends React.Component<IWeightProps, IState> { description: 'Gravity', magnitude: Math.abs(this.props.gravity) * this.props.mass, directionInDegrees: 270, - component: false, }, { description: 'Spring Force', magnitude: this.props.springConstant * (yPos - this.props.springRestLength) * (yPosPlus ? 1 : yPosMinus ? -1 : 0), directionInDegrees: yPosPlus ? 90 : 270, - component: false, }, ]; }; @@ -442,13 +431,11 @@ export default class Weight extends React.Component<IWeightProps, IState> { description: 'Gravity', magnitude: Math.abs(this.props.gravity) * this.props.mass, directionInDegrees: 270, - component: false, }, { description: 'Tension', magnitude: mag, directionInDegrees: angle, - component: false, }, ]; }; @@ -488,7 +475,6 @@ export default class Weight extends React.Component<IWeightProps, IState> { 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 }); @@ -500,11 +486,10 @@ export default class Weight extends React.Component<IWeightProps, IState> { 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 }]); + this.props.setComponentForces([gravity, normalForce]); } } collision = true; @@ -641,19 +626,16 @@ export default class Weight extends React.Component<IWeightProps, IState> { description: 'Tension', magnitude: mag, directionInDegrees: angle, - component: true, }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', magnitude: Math.abs(this.props.gravity) * Math.cos(((90 - angle) * Math.PI) / 180), directionInDegrees: 270 - (90 - angle), - component: true, }; const gravityPerpendicular: IForce = { description: 'Gravity Perpendicular Component', magnitude: Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180), directionInDegrees: -(90 - angle), - component: true, }; if (Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180) < 0) { gravityPerpendicular.magnitude = Math.abs(Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180)); @@ -663,6 +645,57 @@ export default class Weight extends React.Component<IWeightProps, IState> { } }; + renderForce = (force: IForce, index: number, asComponent: boolean) => { + if (force.magnitude < this.epsilon) return; + + const angle = (force.directionInDegrees * Math.PI) / 180; + const arrowStartY = this.state.yPosition + this.props.radius - this.props.radius * Math.sin(angle); + const arrowStartX = this.state.xPosition + this.props.radius + this.props.radius * Math.cos(angle); + const arrowEndY = arrowStartY - Math.abs(force.magnitude) * Math.sin(angle) - this.props.radius * Math.sin(angle); + const arrowEndX = arrowStartX + Math.abs(force.magnitude) * Math.cos(angle) + this.props.radius * Math.cos(angle); + + const color = '#0d0d0d'; + + let labelTop = arrowEndY + (force.directionInDegrees >= 0 && force.directionInDegrees < 180 ? 40 : -40); + let labelLeft = arrowEndX + (force.directionInDegrees > 90 && force.directionInDegrees < 270 ? -120 : 30); + + labelTop = Math.max(Math.min(labelTop, this.props.yMax + 50), this.props.yMin); + labelLeft = Math.max(Math.min(labelLeft, this.props.xMax - 60), this.props.xMin); + + return ( + <div key={index} style={{ zIndex: 6, position: 'absolute' }}> + <div + style={{ + pointerEvents: 'none', + position: 'absolute', + left: this.props.xMin, + top: this.props.yMin, + }}> + <svg width={this.props.xMax - this.props.xMin + 'px'} height={this.panelHeight}> + <defs> + <marker id="forceArrow" markerWidth="4" markerHeight="4" refX="0" refY="2" orient="auto" markerUnits="strokeWidth"> + <path d="M0,0 L0,4 L4,2 z" fill={color} /> + </marker> + </defs> + <line strokeDasharray={asComponent ? '10,10' : undefined} x1={arrowStartX} y1={arrowStartY} x2={arrowEndX} y2={arrowEndY} stroke={color} strokeWidth="5" markerEnd="url(#forceArrow)" /> + </svg> + </div> + <div + style={{ + pointerEvents: 'none', + position: 'absolute', + left: labelLeft + 'px', + top: labelTop + 'px', + lineHeight: 1, + backgroundColor: this.labelBackgroundColor, + }}> + <p>{force.description || 'Force'}</p> + {this.props.showForceMagnitudes && <p>{Math.round(100 * force.magnitude) / 100} N</p>} + </div> + </div> + ); + }; + // Render weight, spring, rod(s), vectors render() { return ( @@ -757,19 +790,16 @@ export default class Weight extends React.Component<IWeightProps, IState> { description: 'Tension', magnitude: tensionMag1, directionInDegrees: (dir1T * 180) / Math.PI, - component: false, }; const tensionForce2: IForce = { description: 'Tension', magnitude: tensionMag2, directionInDegrees: (dir2T * 180) / Math.PI, - component: false, }; const grav: IForce = { description: 'Gravity', magnitude: this.props.mass * Math.abs(this.props.gravity), directionInDegrees: 270, - component: false, }; this.props.setForcesUpdated([tensionForce1, tensionForce2, grav]); } @@ -802,14 +832,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { )} {this.props.simulationType == 'Pulley' && ( - <div - className="rod" - style={{ - pointerEvents: 'none', - position: 'absolute', - left: 0, - top: 0, - }}> + <div className="rod"> <svg width={this.panelWidth} height={this.panelHeight}> <line // x1={this.state.xPosition + this.props.radius} @@ -837,14 +860,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { </div> )} {this.props.simulationType == 'Suspension' && ( - <div - className="rod" - style={{ - pointerEvents: 'none', - position: 'absolute', - left: 0, - top: 0, - }}> + <div className="rod"> <svg width={this.panelWidth} height={this.panelHeight}> <line x1={this.state.xPosition + this.props.radius} @@ -867,14 +883,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { ) / 100} ° </p> - <div - className="rod" - style={{ - pointerEvents: 'none', - position: 'absolute', - left: 0, - top: 0, - }}> + <div className="rod"> <svg width={this.props.panelWidth() + 'px'} height={this.panelHeight}> <line x1={this.state.xPosition + this.props.radius} @@ -902,14 +911,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { </div> )} {this.props.simulationType == 'Circular Motion' && ( - <div - className="rod" - style={{ - pointerEvents: 'none', - position: 'absolute', - left: 0, - top: 0, - }}> + <div className="rod"> <svg width={this.panelWidth} height={this.panelHeight}> <line x1={this.state.xPosition + this.props.radius} @@ -923,14 +925,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { </div> )} {this.props.simulationType == 'Pendulum' && ( - <div - className="rod" - style={{ - pointerEvents: 'none', - position: 'absolute', - left: 0, - top: 0, - }}> + <div className="rod"> <svg width={this.panelWidth} height={this.panelHeight}> <line x1={this.state.xPosition + this.props.radius} y1={this.state.yPosition + this.props.radius} x2={this.props.xMax / 2} y2={-5} stroke={'#deb887'} strokeWidth="10" /> </svg> @@ -1056,136 +1051,8 @@ export default class Weight extends React.Component<IWeightProps, IState> { </div> </div> )} - {!this.state.dragging && - this.props.showComponentForces && - this.props.componentForces().map((force, index) => { - if (force.magnitude < this.epsilon) { - return; - } - const arrowStartY = this.state.yPosition + this.props.radius; - const arrowStartX = this.state.xPosition + this.props.radius; - const arrowEndY = arrowStartY - Math.abs(force.magnitude) * 20 * Math.sin((force.directionInDegrees * Math.PI) / 180); - const arrowEndX = arrowStartX + Math.abs(force.magnitude) * 20 * Math.cos((force.directionInDegrees * Math.PI) / 180); - - const color = '#0d0d0d'; - - let labelTop = arrowEndY; - let labelLeft = arrowEndX; - if (force.directionInDegrees > 90 && force.directionInDegrees < 270) { - labelLeft -= 120; - } else { - labelLeft += 30; - } - if (force.directionInDegrees >= 0 && force.directionInDegrees < 180) { - labelTop += 40; - } else { - labelTop -= 40; - } - labelTop = Math.min(labelTop, this.props.yMax + 50); - labelTop = Math.max(labelTop, this.props.yMin); - labelLeft = Math.min(labelLeft, this.props.xMax - 60); - labelLeft = Math.max(labelLeft, this.props.xMin); - - return ( - <div key={index}> - <div - style={{ - pointerEvents: 'none', - position: 'absolute', - left: this.props.xMin, - top: this.props.yMin, - }}> - <svg width={this.props.xMax - this.props.xMin + 'px'} height={this.panelHeight}> - <defs> - <marker id="forceArrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth"> - <path d="M0,0 L0,6 L9,3 z" fill={color} /> - </marker> - </defs> - {force.component == true && <line x1={arrowStartX} y1={arrowStartY} x2={arrowEndX} y2={arrowEndY} stroke={color} strokeWidth="5" strokeDasharray="10,10" markerEnd="url(#forceArrow)" />} - {force.component == false && <line x1={arrowStartX} y1={arrowStartY} x2={arrowEndX} y2={arrowEndY} stroke={color} strokeWidth="5" markerEnd="url(#forceArrow)" />} - </svg> - </div> - <div - style={{ - pointerEvents: 'none', - position: 'absolute', - left: labelLeft + 'px', - top: labelTop + 'px', - lineHeight: 1, - backgroundColor: this.labelBackgroundColor, - }}> - {force.description && <p>{force.description}</p>} - {!force.description && <p>Force</p>} - {this.props.showForceMagnitudes && <p>{Math.round(100 * force.magnitude) / 100} N</p>} - </div> - </div> - ); - })} - {!this.state.dragging && - this.props.showForces && - this.props.forcesUpdated().map((force, index) => { - if (force.magnitude < this.epsilon) { - return; - } - const arrowStartY = this.state.yPosition + this.props.radius; - const arrowStartX = this.state.xPosition + this.props.radius; - const arrowEndY = arrowStartY - Math.abs(force.magnitude) * 20 * Math.sin((force.directionInDegrees * Math.PI) / 180); - const arrowEndX = arrowStartX + Math.abs(force.magnitude) * 20 * Math.cos((force.directionInDegrees * Math.PI) / 180); - - const color = '#0d0d0d'; - - let labelTop = arrowEndY; - let labelLeft = arrowEndX; - if (force.directionInDegrees > 90 && force.directionInDegrees < 270) { - labelLeft -= 120; - } else { - labelLeft += 30; - } - if (force.directionInDegrees >= 0 && force.directionInDegrees < 180) { - labelTop += 40; - } else { - labelTop -= 40; - } - labelTop = Math.min(labelTop, this.props.yMax + 50); - labelTop = Math.max(labelTop, this.props.yMin); - labelLeft = Math.min(labelLeft, this.props.xMax - 60); - labelLeft = Math.max(labelLeft, this.props.xMin); - - return ( - <div key={index}> - <div - style={{ - pointerEvents: 'none', - position: 'absolute', - left: this.props.xMin, - top: this.props.yMin, - }}> - <svg width={this.props.xMax - this.props.xMin + 'px'} height={this.panelHeight}> - <defs> - <marker id="forceArrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth"> - <path d="M0,0 L0,6 L9,3 z" fill={color} /> - </marker> - </defs> - {force.component == true && <line x1={arrowStartX} y1={arrowStartY} x2={arrowEndX} y2={arrowEndY} stroke={color} strokeWidth="5" strokeDasharray="10,10" markerEnd="url(#forceArrow)" />} - {force.component == false && <line x1={arrowStartX} y1={arrowStartY} x2={arrowEndX} y2={arrowEndY} stroke={color} strokeWidth="5" markerEnd="url(#forceArrow)" />} - </svg> - </div> - <div - style={{ - pointerEvents: 'none', - position: 'absolute', - left: labelLeft + 'px', - top: labelTop + 'px', - lineHeight: 1, - backgroundColor: this.labelBackgroundColor, - }}> - {force.description && <p>{force.description}</p>} - {!force.description && <p>Force</p>} - {this.props.showForceMagnitudes && <p>{Math.round(100 * force.magnitude) / 100} N</p>} - </div> - </div> - ); - })} + {!this.state.dragging && this.props.showComponentForces && this.props.componentForces().map((force, index) => this.renderForce(force, index, true))} + {!this.state.dragging && this.props.showForces && this.props.forcesUpdated().map((force, index) => this.renderForce(force, index, false))} </div> ); } |