diff options
-rw-r--r-- | src/client/documents/Documents.ts | 3 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 2 | ||||
-rw-r--r-- | src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss | 128 | ||||
-rw-r--r-- | src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx | 1131 | ||||
-rw-r--r-- | src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx | 2840 | ||||
-rw-r--r-- | src/fields/Doc.ts | 2 |
6 files changed, 1874 insertions, 2232 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6f8582c2e..4ee3368ab 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -688,8 +688,9 @@ export namespace Docs { [ DocumentType.SIMULATION, { + data: '', layout: { view: PhysicsSimulationBox, dataField: defaultDataKey }, - options: { _height: 100 }, + options: { _height: 100, position: '', velocity: '', acceleration: '', pendulum: '', simulation: '', review: '', show: '', start: '' }, }, ], ]); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index fffb9c79b..4a702cef3 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -266,7 +266,7 @@ export class CurrentUserUtils { {key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_altContentUI: true}}, {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, - {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, _freeform_backgroundGrid: true, }}, + {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss index 7193e3157..c29e36a97 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss @@ -1,76 +1,78 @@ -* { - box-sizing: border-box; - font-size: 14px; -} +.physicsSimApp { + * { + box-sizing: border-box; + font-size: 14px; + } -.mechanicsSimulationContainer { - background-color: white; - height: 100%; - width: 100%; - display: flex; + .mechanicsSimulationContainer { + background-color: white; + height: 100%; + width: 100%; + display: flex; - .mechanicsSimulationEquationContainer { - position: fixed; - left: 60%; - padding: 1em; + .mechanicsSimulationEquationContainer { + position: fixed; + left: 60%; + padding: 1em; - .mechanicsSimulationControls { - display: flex; - justify-content: space-between; - } - } -} + .mechanicsSimulationControls { + display: flex; + justify-content: space-between; + } + } + } -.coordinateSystem { - z-index: -100; -} + .coordinateSystem { + z-index: -100; + } -th, -td { - border-collapse: collapse; - padding: 1em; -} + th, + td { + border-collapse: collapse; + padding: 1em; + } -table { - min-width: 300px; -} + table { + min-width: 300px; + } -tr:nth-child(even) { - background-color: #d6eeee; -} + tr:nth-child(even) { + background-color: #d6eeee; + } -button { - z-index: 50; -} + button { + z-index: 50; + } -.angleLabel { - font-weight: bold; - font-size: 20px; - user-select: none; - pointer-events: none; -} + .angleLabel { + font-weight: bold; + font-size: 20px; + user-select: none; + pointer-events: none; + } -.mechanicsSimulationSettingsMenu { - width: 100%; - height: 100%; - font-size: 12px; - background-color: rgb(224, 224, 224); - border-radius: 2px; - border-color: black; - border-style: solid; - padding: 10px; - position: fixed; - z-index: 1000; -} + .mechanicsSimulationSettingsMenu { + width: 100%; + height: 100%; + font-size: 12px; + background-color: rgb(224, 224, 224); + border-radius: 2px; + border-color: black; + border-style: solid; + padding: 10px; + position: fixed; + z-index: 1000; + } -.mechanicsSimulationSettingsMenuRow { - display: flex; -} + .mechanicsSimulationSettingsMenuRow { + display: flex; + } -.mechanicsSimulationSettingsMenuRowDescription { - width: 50%; -} + .mechanicsSimulationSettingsMenuRowDescription { + width: 50%; + } -.dropdownMenu { - z-index: 50; -}
\ No newline at end of file + .dropdownMenu { + z-index: 50; + } +} diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx index ab93583df..db15c9a5c 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx @@ -1,29 +1,24 @@ -import './PhysicsSimulationBox.scss'; -import { FieldView, FieldViewProps } from './../FieldView'; -import React = require('react'); -import { ViewBoxAnnotatableComponent } from '../../DocComponent'; -import { observer } from 'mobx-react'; -import './PhysicsSimulationBox.scss'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { CheckBox } from '../../search/CheckBox'; +import ArrowLeftIcon from '@mui/icons-material/ArrowLeft'; +import ArrowRightIcon from '@mui/icons-material/ArrowRight'; import PauseIcon from '@mui/icons-material/Pause'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; -import ReplayIcon from '@mui/icons-material/Replay'; import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; -import ArrowLeftIcon from '@mui/icons-material/ArrowLeft'; -import ArrowRightIcon from '@mui/icons-material/ArrowRight'; -import EditIcon from '@mui/icons-material/Edit'; -import EditOffIcon from '@mui/icons-material/EditOff'; -import { Box, Button, Checkbox, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, FormControl, FormControlLabel, FormGroup, IconButton, LinearProgress, Stack } from '@mui/material'; +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 { observer } from 'mobx-react'; +import { HeightSym, NumListCast, WidthSym } from '../../../../fields/Doc'; +import { ViewBoxAnnotatableComponent } from '../../DocComponent'; +import { FieldView, FieldViewProps } from './../FieldView'; import './PhysicsSimulationBox.scss'; import InputField from './PhysicsSimulationInputField'; import * as questions from './PhysicsSimulationQuestions.json'; import * as tutorials from './PhysicsSimulationTutorial.json'; import Wall from './PhysicsSimulationWall'; import Weight from './PhysicsSimulationWeight'; -import { NumCast } from '../../../../fields/Types'; -import { HeightSym, WidthSym } from '../../../../fields/Doc'; +import React = require('react'); +import { BoolCast, NumCast, StrCast } from '../../../../fields/Types'; +import { List } from '../../../../fields/List'; interface IWallProps { length: number; @@ -70,7 +65,7 @@ interface TutorialTemplate { directionInDegrees: number; component: boolean; }[]; - showMagnitude: boolean; + show_Magnitude: boolean; }[]; } @@ -99,54 +94,36 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.layoutDoc._height = 800; this.xMax = this.layoutDoc._width * 0.6; this.yMax = this.layoutDoc._height * 0.9; - this.dataDoc.reviewCoefficient = this.dataDoc.reviewCoefficient ?? 0; - this.dataDoc.questionVariables = this.dataDoc.questionVariables ?? []; - this.dataDoc.accelerationXDisplay = this.dataDoc.accelerationXDisplay ?? 0; - this.dataDoc.accelerationYDisplay = this.dataDoc.accelerationYDisplay ?? 0; this.dataDoc.componentForces = this.dataDoc.componentForces ?? []; - this.dataDoc.displayChange = this.dataDoc.displayChange ?? { xDisplay: 0, yDisplay: 0 }; - this.dataDoc.elasticCollisions = this.dataDoc.elasticCollisions ?? false; - this.dataDoc.gravity = this.dataDoc.gravity ?? -9.81; - this.dataDoc.mass = this.dataDoc.mass ?? 1; + this.dataDoc.gravity = NumCast(this.dataDoc.gravity, -9.81); + this.dataDoc.mass = NumCast(this.dataDoc.mass, 1); this.dataDoc.mode = 'Freeform'; - this.dataDoc.positionXDisplay = this.dataDoc.positionXDisplay ?? 0; - this.dataDoc.positionYDisplay = this.dataDoc.positionYDisplay ?? 0; - this.dataDoc.showAcceleration = this.dataDoc.showAcceleration ?? false; - this.dataDoc.showComponentForces = this.dataDoc.showComponentForces ?? false; - this.dataDoc.showForces = this.dataDoc.showForces ?? true; - this.dataDoc.showForceMagnitudes = this.dataDoc.showForceMagnitudes ?? true; - this.dataDoc.showVelocity = this.dataDoc.showVelocity ?? false; - this.dataDoc.simulationPaused = this.dataDoc.simulationPaused ?? true; - this.dataDoc.simulationReset = this.dataDoc.simulationReset ?? false; - this.dataDoc.simulationSpeed = this.dataDoc.simulationSpeed ?? 2; - this.dataDoc.simulationType = this.dataDoc.simulationType ?? 'Inclined Plane'; - this.dataDoc.startForces = this.dataDoc.startForces ?? []; - this.dataDoc.startPosX = this.dataDoc.startPosX ?? Math.round((this.xMax * 0.5 - 200) * 10) / 10; - this.dataDoc.startPosY = this.dataDoc.startPosY ?? this.getDisplayYPos((400 - 0.08 * this.layoutDoc._height) * Math.tan((26 * Math.PI) / 180) + Math.sqrt(26)); - this.dataDoc.startVelX = this.dataDoc.startVelX ?? 0; - this.dataDoc.startVelY = this.dataDoc.startVelY ?? 0; - this.dataDoc.stepNumber = this.dataDoc.stepNumber ?? 0; + this.dataDoc.show_ComponentForces = this.dataDoc.show_ComponentForces ?? false; + this.dataDoc.simulation_Speed = NumCast(this.dataDoc.simulation_Speed, 2); + this.dataDoc.simulation_Type = StrCast(this.dataDoc.simulation_Type, 'Inclined Plane'); + this.dataDoc.start_Forces = this.dataDoc.start_Forces ?? []; + this.dataDoc.start_PosX = this.dataDoc.start_PosX ?? Math.round((this.xMax * 0.5 - 200) * 10) / 10; + this.dataDoc.start_PosY = this.dataDoc.start_PosY ?? this.getDisplayYPos((400 - 0.08 * this.layoutDoc._height) * Math.tan((26 * Math.PI) / 180) + Math.sqrt(26)); + this.dataDoc.stepNumber = NumCast(this.dataDoc.stepNumber); this.dataDoc.updatedForces = this.dataDoc.updatedForces ?? []; - this.dataDoc.velocityXDisplay = this.dataDoc.velocityXDisplay ?? 0; - this.dataDoc.velocityYDisplay = this.dataDoc.velocityYDisplay ?? 0; // Used for review mode // this.dataDoc.currentForceSketch = this.dataDoc.currentForceSketch ?? null; // this.dataDoc.deleteMode = this.dataDoc.deleteMode ?? false; // this.dataDoc.forceSketches = this.dataDoc.forceSketches ?? []; - this.dataDoc.answers = []; - this.dataDoc.showIcon = false; + this.dataDoc.answers = new List<number>(); + this.dataDoc.show_Icon = false; this.dataDoc.hintDialogueOpen = false; this.dataDoc.noMovement = false; this.dataDoc.questionNumber = 0; this.dataDoc.questionPartOne = ''; this.dataDoc.questionPartTwo = ''; - this.dataDoc.reviewGravityAngle = 0; - this.dataDoc.reviewGravityMagnitude = 0; - this.dataDoc.reviewNormalAngle = 0; - this.dataDoc.reviewNormalMagnitude = 0; - this.dataDoc.reviewStaticAngle = 0; - this.dataDoc.reviewStaticMagnitude = 0; + this.dataDoc.review_GravityAngle = 0; + this.dataDoc.review_GravityMagnitude = 0; + this.dataDoc.review_NormalAngle = 0; + this.dataDoc.review_NormalMagnitude = 0; + this.dataDoc.review_StaticAngle = 0; + this.dataDoc.review_StaticMagnitude = 0; this.dataDoc.selectedSolutions = []; this.dataDoc.selectedQuestion = this.dataDoc.selectedQuestion ?? questions.inclinePlane[0]; // this.dataDoc.sketching = this.dataDoc.sketching ?? false; @@ -158,39 +135,25 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.circularMotionRadius = this.dataDoc.circularMotionRadius ?? 150; // Used for spring simulation - this.dataDoc.springConstant = this.dataDoc.springConstant ?? 0.5; - this.dataDoc.springRestLength = this.dataDoc.springRestLength ?? 200; - this.dataDoc.springStartLength = this.dataDoc.springStartLength ?? 200; + this.dataDoc.springConstant = NumCast(this.dataDoc.springConstant, 0.5); + this.dataDoc.springRestLength = NumCast(this.dataDoc.springRestLength, 200); + this.dataDoc.springStartLength = NumCast(this.dataDoc.springStartLength, 200); // Used for pendulum simulation - this.dataDoc.adjustPendulumAngle = this.dataDoc.adjustPendulumAngle ?? { angle: 0, length: 0 }; - this.dataDoc.pendulumAngle = this.dataDoc.pendulumAngle ?? 0; - this.dataDoc.pendulumLength = this.dataDoc.pendulumLength ?? 300; - this.dataDoc.startPendulumAngle = this.dataDoc.startPendulumAngle ?? 0; + this.dataDoc.pendulum_length = NumCast(this.dataDoc.pendulum_length, 300); // Used for wedge simulation - this.dataDoc.coefficientOfKineticFriction = this.dataDoc.coefficientOfKineticFriction ?? 0; - this.dataDoc.coefficientOfStaticFriction = this.dataDoc.coefficientOfStaticFriction ?? 0; - this.dataDoc.wedgeAngle = this.dataDoc.wedgeAngle ?? 26; - this.dataDoc.wedgeHeight = this.dataDoc.wedgeHeight ?? Math.tan((26 * Math.PI) / 180) * this.xMax * 0.5; - this.dataDoc.wedgeWidth = this.dataDoc.wedgeWidth ?? this.xMax * 0.5; + this.dataDoc.wedgeAngle = NumCast(this.dataDoc.wedgeAngle, 26); + this.dataDoc.wedgeHeight = NumCast(this.dataDoc.wedgeHeight, Math.tan((26 * Math.PI) / 180) * this.xMax * 0.5); + this.dataDoc.wedgeWidth = NumCast(this.dataDoc.wedgeWidth, this.xMax * 0.5); // Used for pulley simulation - this.dataDoc.positionXDisplay2 = this.dataDoc.positionXDisplay2 ?? 0; - this.dataDoc.velocityXDisplay2 = this.dataDoc.velocityXDisplay2 ?? 0; - this.dataDoc.accelerationXDisplay2 = this.dataDoc.accelerationXDisplay2 ?? 0; - this.dataDoc.positionYDisplay2 = this.dataDoc.positionYDisplay2 ?? 0; - this.dataDoc.velocityYDisplay2 = this.dataDoc.velocityYDisplay2 ?? 0; - this.dataDoc.accelerationYDisplay2 = this.dataDoc.accelerationYDisplay2 ?? 0; - this.dataDoc.startPosX2 = this.dataDoc.startPosX2 ?? 0; - this.dataDoc.startPosY2 = this.dataDoc.startPosY2 ?? 0; - this.dataDoc.displayChange2 = this.dataDoc.displayChange2 ?? { xDisplay: 0, yDisplay: 0 }; - this.dataDoc.startForces2 = this.dataDoc.startForces2 ?? []; + this.dataDoc.start_Forces2 = this.dataDoc.start_Forces2 ?? []; this.dataDoc.updatedForces2 = this.dataDoc.updatedForces2 ?? []; - this.dataDoc.mass2 = this.dataDoc.mass2 ?? 1; + this.dataDoc.mass2 = NumCast(this.dataDoc.mass2, 1); // Setup simulation - this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); + this.setupSimulation(StrCast(this.dataDoc.simulation_Type), StrCast(this.dataDoc.mode)); // Create walls let walls = []; @@ -207,54 +170,54 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.layoutDoc._height = Math.max(this.layoutDoc[HeightSym](), 600); this.xMax = this.layoutDoc._width * 0.6; this.yMax = this.layoutDoc._height * 0.9; - this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); + this.setupSimulation(StrCast(this.dataDoc.simulation_Type), StrCast(this.dataDoc.mode)); } } setupSimulation = (simulationType: string, mode: string) => { - this.dataDoc.simulationPaused = true; + this.dataDoc.simulation_Paused = true; if (simulationType != 'Circular Motion') { - this.dataDoc.startVelX = 0; + this.dataDoc.start_VelX = 0; this.dataDoc.setStartVelY = 0; - this.dataDoc.velocityXDisplay = 0; - this.dataDoc.velocityYDisplay = 0; + this.dataDoc.velocity_XDisplay = 0; + this.dataDoc.velocity_YDisplay = 0; } if (mode == 'Freeform') { - this.dataDoc.showForceMagnitudes = true; + this.dataDoc.show_ForceMagnitudes = true; if (simulationType == 'One Weight') { - this.dataDoc.showComponentForces = false; - this.dataDoc.startPosY = this.yMin + 0.08 * this.layoutDoc[HeightSym](); - this.dataDoc.startPosX = (this.xMax + this.xMin) / 2 - 0.08 * this.layoutDoc[HeightSym](); - this.dataDoc.positionYDisplay = this.getDisplayYPos(this.yMin + 0.08 * this.layoutDoc[HeightSym]()); - this.dataDoc.positionXDisplay = (this.xMax + this.xMin) / 2 - 0.08 * this.layoutDoc[HeightSym](); + this.dataDoc.show_ComponentForces = false; + this.dataDoc.start_PosY = this.yMin + 0.08 * this.layoutDoc[HeightSym](); + this.dataDoc.start_PosX = (this.xMax + this.xMin) / 2 - 0.08 * this.layoutDoc[HeightSym](); + this.dataDoc.position_YDisplay = this.getDisplayYPos(this.yMin + 0.08 * this.layoutDoc[HeightSym]()); + this.dataDoc.position_XDisplay = (this.xMax + this.xMin) / 2 - 0.08 * this.layoutDoc[HeightSym](); this.dataDoc.updatedForces = [ { description: 'Gravity', - magnitude: Math.abs(this.dataDoc.gravity) * this.dataDoc.mass, + magnitude: Math.abs(NumCast(this.dataDoc.gravity)) * NumCast(this.dataDoc.mass), directionInDegrees: 270, component: false, }, ]; - this.dataDoc.startForces = [ + this.dataDoc.start_Forces = [ { description: 'Gravity', - magnitude: Math.abs(this.dataDoc.gravity) * this.dataDoc.mass, + magnitude: Math.abs(NumCast(this.dataDoc.gravity)) * NumCast(this.dataDoc.mass), directionInDegrees: 270, component: false, }, ]; - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; } else if (simulationType == 'Inclined Plane') { this.changeWedgeBasedOnNewAngle(26); - this.dataDoc.startForces = [ + this.dataDoc.start_Forces = [ { description: 'Gravity', - magnitude: Math.abs(this.dataDoc.gravity) * this.dataDoc.mass, + magnitude: Math.abs(NumCast(this.dataDoc.gravity)) * NumCast(this.dataDoc.mass), directionInDegrees: 270, component: false, }, ]; - this.updateForcesWithFriction(Number(this.dataDoc.coefficientOfStaticFriction)); + this.updateForcesWithFriction(NumCast(this.dataDoc.coefficientOfStaticFriction)); } else if (simulationType == 'Pendulum') { this.setupPendulum(); } else if (simulationType == 'Spring') { @@ -267,11 +230,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.setupSuspension(); } } else if (mode == 'Review') { - this.dataDoc.showComponentForces = false; - this.dataDoc.showForceMagnitudes = true; - this.dataDoc.showAcceleration = false; - this.dataDoc.showVelocity = false; - this.dataDoc.showForces = true; + this.dataDoc.show_ComponentForces = false; + this.dataDoc.show_ForceMagnitudes = true; + this.dataDoc.show_Acceleration = false; + this.dataDoc.show_Velocity = false; + this.dataDoc.show_Forces = true; this.generateNewQuestion(); if (simulationType == 'One Weight') { // TODO - one weight review problems @@ -280,7 +243,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP // TODO - spring review problems } else if (simulationType == 'Inclined Plane') { this.dataDoc.updatedForces = []; - this.dataDoc.startForces = []; + this.dataDoc.start_Forces = []; } else if (simulationType == 'Pendulum') { this.setupPendulum(); // TODO - pendulum review problems @@ -295,77 +258,78 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP // TODO - suspension tutorial review problems } } else if (mode == 'Tutorial') { - this.dataDoc.showComponentForces = false; + this.dataDoc.show_ComponentForces = false; this.dataDoc.stepNumber = 0; - this.dataDoc.showAcceleration = false; - if (this.dataDoc.simulationType != 'Circular Motion') { - this.dataDoc.velocityXDisplay = 0; - this.dataDoc.velocityYDisplay = 0; - this.dataDoc.showVelocity = false; + this.dataDoc.show_Acceleration = false; + if (this.dataDoc.simulation_Type != 'Circular Motion') { + this.dataDoc.velocity_XDisplay = 0; + this.dataDoc.velocity_YDisplay = 0; + this.dataDoc.show_Velocity = false; } else { - this.dataDoc.velocityXDisplay = 20; - this.dataDoc.velocityYDisplay = 0; - this.dataDoc.showVelocity = true; + this.dataDoc.velocity_XDisplay = 20; + this.dataDoc.velocity_YDisplay = 0; + this.dataDoc.show_Velocity = true; } - if (this.dataDoc.simulationType == 'One Weight') { - this.dataDoc.showForces = true; - this.dataDoc.startPosY = this.yMax - 100; - this.dataDoc.startPosX = (this.xMax + this.xMin) / 2 - 0.08 * this.layoutDoc[HeightSym](); + if (this.dataDoc.simulation_Type == 'One Weight') { + this.dataDoc.show_Forces = true; + this.dataDoc.start_PosY = this.yMax - 100; + this.dataDoc.start_PosX = (this.xMax + this.xMin) / 2 - 0.08 * this.layoutDoc[HeightSym](); this.dataDoc.selectedTutorial = tutorials.freeWeight; - this.dataDoc.startForces = this.getForceFromJSON(tutorials.freeWeight.steps[0].forces); - this.dataDoc.showForceMagnitudes = tutorials.freeWeight.steps[0].showMagnitude; - } else if (this.dataDoc.simulationType == 'Spring') { - this.dataDoc.showForces = true; + this.dataDoc.start_Forces = this.getForceFromJSON(tutorials.freeWeight.steps[0].forces); + this.dataDoc.show_ForceMagnitudes = tutorials.freeWeight.steps[0].showMagnitude; + } else if (this.dataDoc.simulation_Type == 'Spring') { + this.dataDoc.show_Forces = true; this.setupSpring(); - this.dataDoc.startPosY = this.yMin + 200 + 19.62; - this.dataDoc.startPosX = (this.xMax + this.xMin) / 2 - 0.08 * this.layoutDoc[HeightSym](); + this.dataDoc.start_PosY = this.yMin + 200 + 19.62; + this.dataDoc.start_PosX = (this.xMax + this.xMin) / 2 - 0.08 * this.layoutDoc[HeightSym](); this.dataDoc.selectedTutorial = tutorials.spring; - this.dataDoc.startForces = this.getForceFromJSON(tutorials.spring.steps[0].forces); - this.dataDoc.showForceMagnitudes = tutorials.spring.steps[0].showMagnitude; - } else if (this.dataDoc.simulationType == 'Pendulum') { - this.dataDoc.showForces = true; + this.dataDoc.start_Forces = this.getForceFromJSON(tutorials.spring.steps[0].forces); + this.dataDoc.show_ForceMagnitudes = tutorials.spring.steps[0].showMagnitude; + } else if (this.dataDoc.simulation_Type == 'Pendulum') { + this.dataDoc.show_Forces = true; const length = 300; 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.layoutDoc[HeightSym](); const yPos = y - 0.08 * this.layoutDoc[HeightSym]() - 5; - this.dataDoc.startPosX = xPos; - this.dataDoc.startPosY = yPos; + this.dataDoc.start_PosX = xPos; + this.dataDoc.start_PosY = yPos; this.dataDoc.selectedTutorial = tutorials.pendulum; - this.dataDoc.startForces = this.getForceFromJSON(tutorials.pendulum.steps[0].forces); - this.dataDoc.showForceMagnitudes = tutorials.pendulum.steps[0].showMagnitude; - this.dataDoc.pendulumAngle = 30; - this.dataDoc.pendulumLength = 300; - this.dataDoc.adjustPendulumAngle = { angle: 30, length: 300 }; - } else if (this.dataDoc.simulationType == 'Inclined Plane') { - this.dataDoc.showForces = true; + this.dataDoc.start_Forces = this.getForceFromJSON(tutorials.pendulum.steps[0].forces); + this.dataDoc.show_ForceMagnitudes = tutorials.pendulum.steps[0].showMagnitude; + this.dataDoc.pendulum_angle = 30; + this.dataDoc.pendulum_length = 300; + this.dataDoc.pendulum_angle_adjust = 30; + this.dataDoc.pendulum_length_adjust = 300; + } else if (this.dataDoc.simulation_Type == 'Inclined Plane') { + this.dataDoc.show_Forces = true; this.dataDoc.wedgeAngle = 26; this.changeWedgeBasedOnNewAngle(26); this.dataDoc.selectedTutorial = tutorials.inclinePlane; - this.dataDoc.startForces = this.getForceFromJSON(tutorials.inclinePlane.steps[0].forces); - this.dataDoc.showForceMagnitudes = tutorials.inclinePlane.steps[0].showMagnitude; - } else if (this.dataDoc.simulationType == 'Circular Motion') { - this.dataDoc.showForces = true; + this.dataDoc.start_Forces = this.getForceFromJSON(tutorials.inclinePlane.steps[0].forces); + this.dataDoc.show_ForceMagnitudes = tutorials.inclinePlane.steps[0].showMagnitude; + } else if (this.dataDoc.simulation_Type == 'Circular Motion') { + this.dataDoc.show_Forces = true; this.setupCircular(40); this.dataDoc.selectedTutorial = tutorials.circular; - this.dataDoc.startForces = this.getForceFromJSON(tutorials.circular.steps[0].forces); - this.dataDoc.showForceMagnitudes = tutorials.circular.steps[0].showMagnitude; - } else if (this.dataDoc.simulationType == 'Pulley') { - this.dataDoc.showForces = true; + this.dataDoc.start_Forces = this.getForceFromJSON(tutorials.circular.steps[0].forces); + this.dataDoc.show_ForceMagnitudes = tutorials.circular.steps[0].showMagnitude; + } else if (this.dataDoc.simulation_Type == 'Pulley') { + this.dataDoc.show_Forces = true; this.setupPulley(); this.dataDoc.selectedTutorial = tutorials.pulley; - this.dataDoc.startForces = this.getForceFromJSON(tutorials.pulley.steps[0].forces); - this.dataDoc.showForceMagnitudes = tutorials.pulley.steps[0].showMagnitude; - } else if (this.dataDoc.simulationType == 'Suspension') { - this.dataDoc.showForces = true; + this.dataDoc.start_Forces = this.getForceFromJSON(tutorials.pulley.steps[0].forces); + this.dataDoc.show_ForceMagnitudes = tutorials.pulley.steps[0].showMagnitude; + } else if (this.dataDoc.simulation_Type == 'Suspension') { + this.dataDoc.show_Forces = true; this.setupSuspension(); this.dataDoc.selectedTutorial = tutorials.suspension; - this.dataDoc.startForces = this.getForceFromJSON(tutorials.suspension.steps[0].forces); - this.dataDoc.showForceMagnitudes = tutorials.suspension.steps[0].showMagnitude; + this.dataDoc.start_Forces = this.getForceFromJSON(tutorials.suspension.steps[0].forces); + this.dataDoc.show_ForceMagnitudes = tutorials.suspension.steps[0].showMagnitude; } - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; } }; @@ -378,62 +342,63 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP }; // Update forces when coefficient of static friction changes in freeform mode - updateForcesWithFriction = (coefficient: number, width: number = this.dataDoc.wedgeWidth, height: number = this.dataDoc.wedgeHeight) => { + updateForcesWithFriction = (coefficient: number, width: number = NumCast(this.dataDoc.wedgeWidth), height: number = NumCast(this.dataDoc.wedgeHeight)) => { const normalForce: IForce = { description: 'Normal Force', - magnitude: Math.abs(this.dataDoc.gravity) * Math.cos(Math.atan(height / width)) * this.dataDoc.mass, + magnitude: Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos(Math.atan(height / width)) * NumCast(this.dataDoc.mass), directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI, component: false, }; let frictionForce: IForce = { description: 'Static Friction Force', - magnitude: coefficient * Math.abs(this.dataDoc.gravity) * Math.cos(Math.atan(height / width)) * this.dataDoc.mass, + magnitude: coefficient * Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos(Math.atan(height / width)) * NumCast(this.dataDoc.mass), 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.dataDoc.gravity) * this.dataDoc.mass; + let yForce = -Math.abs(NumCast(this.dataDoc.gravity)) * NumCast(this.dataDoc.mass); yForce += normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180); yForce += 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.dataDoc.gravity) * this.dataDoc.mass) / Math.sin((frictionForce.directionInDegrees * Math.PI) / 180); + frictionForce.magnitude = + (-normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180) + Math.abs(NumCast(this.dataDoc.gravity)) * NumCast(this.dataDoc.mass)) / Math.sin((frictionForce.directionInDegrees * Math.PI) / 180); } const frictionForceComponent: IForce = { description: 'Static Friction Force', - magnitude: coefficient * Math.abs(this.dataDoc.gravity) * Math.cos(Math.atan(height / width)), + magnitude: coefficient * Math.abs(NumCast(this.dataDoc.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.dataDoc.gravity) * Math.cos(Math.atan(height / width)), + magnitude: Math.abs(NumCast(this.dataDoc.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.dataDoc.mass * Math.abs(this.dataDoc.gravity) * Math.sin(Math.PI / 2 - Math.atan(height / width)), + magnitude: NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.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.dataDoc.mass * Math.abs(this.dataDoc.gravity) * Math.cos(Math.PI / 2 - Math.atan(height / width)), + magnitude: NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos(Math.PI / 2 - Math.atan(height / width)), directionInDegrees: 360 - (Math.atan(height / width) * 180) / Math.PI, component: true, }; const gravityForce: IForce = { description: 'Gravity', - magnitude: this.dataDoc.mass * Math.abs(this.dataDoc.gravity), + magnitude: NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)), directionInDegrees: 270, component: false, }; if (coefficient != 0) { - this.dataDoc.startForces = [gravityForce, normalForce, frictionForce]; + this.dataDoc.start_Forces = [gravityForce, normalForce, frictionForce]; this.dataDoc.updatedForces = [gravityForce, normalForce, frictionForce]; this.dataDoc.componentForces = [frictionForceComponent, normalForceComponent, gravityParallel, gravityPerpendicular]; } else { - this.dataDoc.startForces = [gravityForce, normalForce]; + this.dataDoc.start_Forces = [gravityForce, normalForce]; this.dataDoc.updatedForces = [gravityForce, normalForce]; this.dataDoc.componentForces = [normalForceComponent, gravityParallel, gravityPerpendicular]; } @@ -468,65 +433,65 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP yPos += 0.08 * this.layoutDoc[HeightSym]() * 0.52; } - this.dataDoc.startPosX = this.xMax * 0.25; - this.dataDoc.startPosY = yPos; + this.dataDoc.start_PosX = this.xMax * 0.25; + this.dataDoc.start_PosY = yPos; if (this.dataDoc.mode == 'Freeform') { - this.updateForcesWithFriction(Number(this.dataDoc.coefficientOfStaticFriction), this.xMax * 0.5, Math.tan((angle * Math.PI) / 180) * this.xMax * 0.5); + this.updateForcesWithFriction(NumCast(this.dataDoc.coefficientOfStaticFriction), this.xMax * 0.5, Math.tan((angle * Math.PI) / 180) * this.xMax * 0.5); } }; // In review mode, update forces when coefficient of static friction changed updateReviewForcesBasedOnCoefficient = (coefficient: number) => { - let theta: number = Number(this.dataDoc.wedgeAngle); + let theta: number = NumCast(this.dataDoc.wedgeAngle); let index = this.dataDoc.selectedQuestion.variablesForQuestionSetup.indexOf('theta - max 45'); if (index >= 0) { - theta = this.dataDoc.questionVariables[index]; + theta = NumListCast(this.dataDoc.questionVariables)[index]; } if (isNaN(theta)) { return; } - this.dataDoc.reviewGravityMagnitude = Math.abs(this.dataDoc.gravity); - this.dataDoc.reviewGravityAngle = 270; - this.dataDoc.reviewNormalMagnitude = Math.abs(this.dataDoc.gravity) * Math.cos((theta * Math.PI) / 180); - this.dataDoc.reviewNormalAngle = 90 - theta; - let yForce = -Math.abs(this.dataDoc.gravity); - yForce += Math.abs(this.dataDoc.gravity) * Math.cos((theta * Math.PI) / 180) * Math.sin(((90 - theta) * Math.PI) / 180); - yForce += coefficient * Math.abs(this.dataDoc.gravity) * Math.cos((theta * Math.PI) / 180) * Math.sin(((180 - theta) * Math.PI) / 180); - let friction = coefficient * Math.abs(this.dataDoc.gravity) * Math.cos((theta * Math.PI) / 180); + this.dataDoc.review_GravityMagnitude = Math.abs(NumCast(this.dataDoc.gravity)); + this.dataDoc.review_GravityAngle = 270; + this.dataDoc.review_NormalMagnitude = Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((theta * Math.PI) / 180); + this.dataDoc.review_NormalAngle = 90 - theta; + let yForce = -Math.abs(NumCast(this.dataDoc.gravity)); + yForce += Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((theta * Math.PI) / 180) * Math.sin(((90 - theta) * Math.PI) / 180); + yForce += coefficient * Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((theta * Math.PI) / 180) * Math.sin(((180 - theta) * Math.PI) / 180); + let friction = coefficient * Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((theta * Math.PI) / 180); if (yForce > 0) { - friction = (-(Math.abs(this.dataDoc.gravity) * Math.cos((theta * Math.PI) / 180)) * Math.sin(((90 - theta) * Math.PI) / 180) + Math.abs(this.dataDoc.gravity)) / Math.sin(((180 - theta) * Math.PI) / 180); + friction = (-(Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((theta * Math.PI) / 180)) * Math.sin(((90 - theta) * Math.PI) / 180) + Math.abs(NumCast(this.dataDoc.gravity))) / Math.sin(((180 - theta) * Math.PI) / 180); } - this.dataDoc.reviewStaticMagnitude = friction; - this.dataDoc.reviewStaticAngle = 180 - theta; + this.dataDoc.review_StaticMagnitude = friction; + this.dataDoc.review_StaticAngle = 180 - theta; }; // In review mode, update forces when wedge angle changed updateReviewForcesBasedOnAngle = (angle: number) => { - this.dataDoc.reviewGravityMagnitude = Math.abs(this.dataDoc.gravity); - this.dataDoc.reviewGravityAngle = 270; - this.dataDoc.reviewNormalMagnitude = Math.abs(this.dataDoc.gravity) * Math.cos((Number(angle) * Math.PI) / 180); - this.dataDoc.reviewNormalAngle = 90 - angle; - let yForce = -Math.abs(this.dataDoc.gravity); - yForce += Math.abs(this.dataDoc.gravity) * Math.cos((Number(angle) * Math.PI) / 180) * Math.sin(((90 - Number(angle)) * Math.PI) / 180); - yForce += this.dataDoc.reviewCoefficient * Math.abs(this.dataDoc.gravity) * Math.cos((Number(angle) * Math.PI) / 180) * Math.sin(((180 - Number(angle)) * Math.PI) / 180); - let friction = this.dataDoc.reviewCoefficient * Math.abs(this.dataDoc.gravity) * Math.cos((Number(angle) * Math.PI) / 180); + this.dataDoc.review_GravityMagnitude = Math.abs(NumCast(this.dataDoc.gravity)); + this.dataDoc.review_GravityAngle = 270; + this.dataDoc.review_NormalMagnitude = Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((angle * Math.PI) / 180); + this.dataDoc.review_NormalAngle = 90 - angle; + let yForce = -Math.abs(NumCast(this.dataDoc.gravity)); + yForce += Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((angle * Math.PI) / 180) * Math.sin(((90 - angle) * Math.PI) / 180); + yForce += NumCast(this.dataDoc.review_Coefficient) * Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((angle * Math.PI) / 180) * Math.sin(((180 - angle) * Math.PI) / 180); + let friction = NumCast(this.dataDoc.review_Coefficient) * Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((angle * Math.PI) / 180); if (yForce > 0) { - friction = (-(Math.abs(this.dataDoc.gravity) * Math.cos((Number(angle) * Math.PI) / 180)) * Math.sin(((90 - Number(angle)) * Math.PI) / 180) + Math.abs(this.dataDoc.gravity)) / Math.sin(((180 - Number(angle)) * Math.PI) / 180); + friction = (-(Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((angle * Math.PI) / 180)) * Math.sin(((90 - angle) * Math.PI) / 180) + Math.abs(NumCast(this.dataDoc.gravity))) / Math.sin(((180 - angle) * Math.PI) / 180); } - this.dataDoc.reviewStaticMagnitude = friction; - this.dataDoc.reviewStaticAngle = 180 - angle; + this.dataDoc.review_StaticMagnitude = friction; + this.dataDoc.review_StaticAngle = 180 - angle; }; // Solve for the correct answers to the generated problem getAnswersToQuestion = (question: QuestionTemplate, questionVars: number[]) => { const solutions: number[] = []; - let theta: number = Number(this.dataDoc.wedgeAngle); + let theta: number = NumCast(this.dataDoc.wedgeAngle); let index = question.variablesForQuestionSetup.indexOf('theta - max 45'); if (index >= 0) { theta = questionVars[index]; } - let muS: number = Number(this.dataDoc.coefficientOfStaticFriction); + let muS: number = NumCast(this.dataDoc.coefficientOfStaticFriction); index = question.variablesForQuestionSetup.indexOf('coefficient of static friction'); if (index >= 0) { muS = questionVars[index]; @@ -534,25 +499,25 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP for (let i = 0; i < question.answerSolutionDescriptions.length; i++) { const description = question.answerSolutionDescriptions[i]; - if (!isNaN(Number(description))) { - solutions.push(Number(description)); + if (!isNaN(NumCast(description))) { + solutions.push(NumCast(description)); } else if (description == 'solve normal force angle from wedge angle') { solutions.push(90 - theta); } else if (description == 'solve normal force magnitude from wedge angle') { - solutions.push(Math.abs(this.dataDoc.gravity) * Math.cos((theta / 180) * Math.PI)); + solutions.push(Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((theta / 180) * Math.PI)); } else if (description == 'solve static force magnitude from wedge angle given equilibrium') { - let normalForceMagnitude = Math.abs(this.dataDoc.gravity) * Math.cos((theta / 180) * Math.PI); + let normalForceMagnitude = Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((theta / 180) * Math.PI); let normalForceAngle = 90 - theta; let frictionForceAngle = 180 - theta; - let frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.dataDoc.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180); + let frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(NumCast(this.dataDoc.gravity))) / Math.sin((frictionForceAngle * Math.PI) / 180); solutions.push(frictionForceMagnitude); } else if (description == 'solve static force angle from wedge angle given equilibrium') { solutions.push(180 - theta); } else if (description == 'solve minimum static coefficient from wedge angle given equilibrium') { - let normalForceMagnitude = Math.abs(this.dataDoc.gravity) * Math.cos((theta / 180) * Math.PI); + let normalForceMagnitude = Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((theta / 180) * Math.PI); let normalForceAngle = 90 - theta; let frictionForceAngle = 180 - theta; - let frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.dataDoc.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180); + let frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(NumCast(this.dataDoc.gravity))) / Math.sin((frictionForceAngle * Math.PI) / 180); let frictionCoefficient = frictionForceMagnitude / normalForceMagnitude; solutions.push(frictionCoefficient); } else if (description == 'solve maximum wedge angle from coefficient of static friction given equilibrium') { @@ -570,35 +535,35 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP if (this.dataDoc.selectedQuestion) { for (let i = 0; i < this.dataDoc.selectedQuestion.answerParts.length; i++) { if (this.dataDoc.selectedQuestion.answerParts[i] == 'force of gravity') { - if (Math.abs(this.dataDoc.reviewGravityMagnitude - this.dataDoc.selectedSolutions[i]) > epsilon) { + if (Math.abs(NumCast(this.dataDoc.review_GravityMagnitude) - this.dataDoc.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.dataDoc.selectedQuestion.answerParts[i] == 'angle of gravity') { - if (Math.abs(this.dataDoc.reviewGravityAngle - this.dataDoc.selectedSolutions[i]) > epsilon) { + if (Math.abs(NumCast(this.dataDoc.review_GravityAngle) - this.dataDoc.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.dataDoc.selectedQuestion.answerParts[i] == 'normal force') { - if (Math.abs(this.dataDoc.reviewNormalMagnitude - this.dataDoc.selectedSolutions[i]) > epsilon) { + if (Math.abs(NumCast(this.dataDoc.review_NormalMagnitude) - this.dataDoc.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.dataDoc.selectedQuestion.answerParts[i] == 'angle of normal force') { - if (Math.abs(this.dataDoc.reviewNormalAngle - this.dataDoc.selectedSolutions[i]) > epsilon) { + if (Math.abs(NumCast(this.dataDoc.review_NormalAngle) - this.dataDoc.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.dataDoc.selectedQuestion.answerParts[i] == 'force of static friction') { - if (Math.abs(this.dataDoc.reviewStaticMagnitude - this.dataDoc.selectedSolutions[i]) > epsilon) { + if (Math.abs(NumCast(this.dataDoc.review_StaticMagnitude) - this.dataDoc.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.dataDoc.selectedQuestion.answerParts[i] == 'angle of static friction') { - if (Math.abs(this.dataDoc.reviewStaticAngle - this.dataDoc.selectedSolutions[i]) > epsilon) { + if (Math.abs(NumCast(this.dataDoc.review_StaticAngle) - this.dataDoc.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.dataDoc.selectedQuestion.answerParts[i] == 'coefficient of static friction') { - if (Math.abs(Number(this.dataDoc.coefficientOfStaticFriction) - this.dataDoc.selectedSolutions[i]) > epsilon) { + if (Math.abs(NumCast(this.dataDoc.coefficientOfStaticFriction) - this.dataDoc.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.dataDoc.selectedQuestion.answerParts[i] == 'wedge angle') { - if (Math.abs(Number(this.dataDoc.wedgeAngle) - this.dataDoc.selectedSolutions[i]) > epsilon) { + if (Math.abs(NumCast(this.dataDoc.wedgeAngle) - this.dataDoc.selectedSolutions[i]) > epsilon) { error = true; } } @@ -606,14 +571,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP } if (showAlert) { if (!error) { - this.dataDoc.simulationPaused = false; + this.dataDoc.simulation_Paused = false; setTimeout(() => { - this.dataDoc.simulationPaused = true; + this.dataDoc.simulation_Paused = true; }, 3000); } else { - this.dataDoc.simulationPaused = false; + this.dataDoc.simulation_Paused = false; setTimeout(() => { - this.dataDoc.simulationPaused = true; + this.dataDoc.simulation_Paused = true; }, 3000); } } @@ -628,14 +593,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP // Reset all review values to default resetReviewValuesToDefault = () => { - this.dataDoc.reviewGravityMagnitude = 0; - this.dataDoc.reviewGravityAngle = 0; - this.dataDoc.reviewNormalMagnitude = 0; - this.dataDoc.reviewNormalAngle = 0; - this.dataDoc.reviewStaticMagnitude = 0; - this.dataDoc.reviewStaticAngle = 0; + this.dataDoc.review_GravityMagnitude = 0; + this.dataDoc.review_GravityAngle = 0; + this.dataDoc.review_NormalMagnitude = 0; + this.dataDoc.review_NormalAngle = 0; + this.dataDoc.review_StaticMagnitude = 0; + this.dataDoc.review_StaticAngle = 0; this.dataDoc.coefficientOfKineticFriction = 0; - this.dataDoc.simulationPaused = true; + this.dataDoc.simulation_Paused = true; }; // In review mode, reset problem variables and generate a new question @@ -645,13 +610,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP const vars: number[] = []; let question: QuestionTemplate = questions.inclinePlane[0]; - if (this.dataDoc.simulationType == 'Inclined Plane') { - if (this.dataDoc.questionNumber == questions.inclinePlane.length - 1) { + if (this.dataDoc.simulation_Type == 'Inclined Plane') { + if (this.dataDoc.questionNumber === questions.inclinePlane.length - 1) { this.dataDoc.questionNumber = 0; } else { - this.dataDoc.questionNumber = this.dataDoc.questionNumber + 1; + this.dataDoc.questionNumber = NumCast(this.dataDoc.questionNumber) + 1; } - question = questions.inclinePlane[this.dataDoc.questionNumber]; + question = questions.inclinePlane[NumCast(this.dataDoc.questionNumber)]; let coefficient = 0; let wedgeAngle = 0; @@ -670,7 +635,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.wedgeAngle = wedgeAngle; this.changeWedgeBasedOnNewAngle(wedgeAngle); this.dataDoc.coefficientOfStaticFriction = coefficient; - this.dataDoc.reviewCoefficient = coefficient; + this.dataDoc.review_Coefficient = coefficient; } let q = ''; for (let i = 0; i < question.questionSetup.length; i++) { @@ -682,32 +647,32 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP } } } - this.dataDoc.questionVariables = vars; + this.dataDoc.questionVariables = new List<number>(vars); this.dataDoc.selectedQuestion = question; this.dataDoc.questionPartOne = q; this.dataDoc.questionPartTwo = question.question; - this.dataDoc.answers = this.getAnswersToQuestion(question, vars); - //this.dataDoc.simulationReset = (!this.dataDoc.simulationReset); + this.dataDoc.answers = new List<number>(this.getAnswersToQuestion(question, vars)); + //this.dataDoc.simulation_Reset = (!this.dataDoc.simulation_Reset); }; // Default setup for uniform circular motion simulation setupCircular = (value: number) => { - this.dataDoc.showComponentForces = false; - this.dataDoc.startVelY = 0; - this.dataDoc.startVelX = value; + this.dataDoc.show_ComponentForces = false; + this.dataDoc.start_VelY = 0; + this.dataDoc.start_VelX = value; let xPos = (this.xMax + this.xMin) / 2 - 0.08 * this.layoutDoc[HeightSym](); - let yPos = (this.yMax + this.yMin) / 2 + this.dataDoc.circularMotionRadius - 0.08 * this.layoutDoc[HeightSym](); - this.dataDoc.startPosY = yPos; - this.dataDoc.startPosX = xPos; + let yPos = (this.yMax + this.yMin) / 2 + NumCast(this.dataDoc.circularMotionRadius) - 0.08 * this.layoutDoc[HeightSym](); + this.dataDoc.start_PosY = yPos; + this.dataDoc.start_PosX = xPos; const tensionForce: IForce = { description: 'Centripetal Force', - magnitude: (this.dataDoc.startVelX ** 2 * this.dataDoc.mass) / this.dataDoc.circularMotionRadius, + magnitude: (this.dataDoc.start_VelX ** 2 * NumCast(this.dataDoc.mass)) / NumCast(this.dataDoc.circularMotionRadius), directionInDegrees: 90, component: false, }; this.dataDoc.updatedForces = [tensionForce]; - this.dataDoc.startForces = [tensionForce]; - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.start_Forces = [tensionForce]; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }; // Default setup for pendulum simulation @@ -718,9 +683,9 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP const y = length * Math.sin(((90 - angle) * Math.PI) / 180); const xPos = this.xMax / 2 - x - 0.08 * this.layoutDoc[HeightSym](); const yPos = y - 0.08 * this.layoutDoc[HeightSym]() - 5; - this.dataDoc.startPosX = xPos; - this.dataDoc.startPosY = yPos; - const mag = this.dataDoc.mass * Math.abs(this.dataDoc.gravity) * Math.sin((60 * Math.PI) / 180); + this.dataDoc.start_PosX = xPos; + this.dataDoc.start_PosY = yPos; + const mag = NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)) * Math.sin((60 * Math.PI) / 180); const forceOfTension: IForce = { description: 'Tension', magnitude: mag, @@ -736,13 +701,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', - magnitude: this.dataDoc.mass * Math.abs(this.dataDoc.gravity) * Math.sin(((90 - angle) * Math.PI) / 180), + magnitude: NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)) * Math.sin(((90 - angle) * Math.PI) / 180), directionInDegrees: -angle - 90, component: true, }; const gravityPerpendicular: IForce = { description: 'Gravity Perpendicular Component', - magnitude: this.dataDoc.mass * Math.abs(this.dataDoc.gravity) * Math.cos(((90 - angle) * Math.PI) / 180), + magnitude: NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos(((90 - angle) * Math.PI) / 180), directionInDegrees: -angle, component: true, }; @@ -751,55 +716,56 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.updatedForces = [ { description: 'Gravity', - magnitude: this.dataDoc.mass * Math.abs(this.dataDoc.gravity), + magnitude: NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)), directionInDegrees: 270, component: false, }, forceOfTension, ]; - this.dataDoc.startForces = [ + this.dataDoc.start_Forces = [ { description: 'Gravity', - magnitude: this.dataDoc.mass * Math.abs(this.dataDoc.gravity), + magnitude: NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)), directionInDegrees: 270, component: false, }, forceOfTension, ]; - this.dataDoc.startPendulumAngle = 30; - this.dataDoc.pendulumAngle = 30; - this.dataDoc.pendulumLength = 300; - this.dataDoc.adjustPendulumAngle = { angle: 30, length: 300 }; + this.dataDoc.start_PendulumAngle = 30; + this.dataDoc.pendulum_angle = 30; + this.dataDoc.pendulum_length = 300; + this.dataDoc.pendulum_angle_adjust = 30; + this.dataDoc.pendulum_length_adjust = 300; }; // Default setup for spring simulation setupSpring = () => { - this.dataDoc.showComponentForces = false; + this.dataDoc.show_ComponentForces = false; const gravityForce: IForce = { description: 'Gravity', - magnitude: Math.abs(this.dataDoc.gravity) * this.dataDoc.mass, + magnitude: Math.abs(NumCast(this.dataDoc.gravity)) * NumCast(this.dataDoc.mass), directionInDegrees: 270, component: false, }; this.dataDoc.updatedForces = [gravityForce]; - this.dataDoc.startForces = [gravityForce]; - this.dataDoc.startPosX = this.xMax / 2 - 0.08 * this.layoutDoc[HeightSym](); - this.dataDoc.startPosY = 200; + this.dataDoc.start_Forces = [gravityForce]; + this.dataDoc.start_PosX = this.xMax / 2 - 0.08 * this.layoutDoc[HeightSym](); + this.dataDoc.start_PosY = 200; this.dataDoc.springConstant = 0.5; this.dataDoc.springRestLength = 200; this.dataDoc.springStartLength = 200; - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }; // Default setup for suspension simulation setupSuspension = () => { let xPos = (this.xMax + this.xMin) / 2 - 0.08 * this.layoutDoc[HeightSym](); let yPos = this.yMin + 200; - this.dataDoc.startPosY = yPos; - this.dataDoc.startPosX = xPos; - this.dataDoc.positionYDisplay = this.getDisplayYPos(yPos); - this.dataDoc.positionXDisplay = xPos; - let tensionMag = (this.dataDoc.mass * Math.abs(this.dataDoc.gravity)) / (2 * Math.sin(Math.PI / 4)); + this.dataDoc.start_PosY = yPos; + this.dataDoc.start_PosX = xPos; + this.dataDoc.position_YDisplay = this.getDisplayYPos(yPos); + this.dataDoc.position_XDisplay = xPos; + let tensionMag = (NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity))) / (2 * Math.sin(Math.PI / 4)); const tensionForce1: IForce = { description: 'Tension', magnitude: tensionMag, @@ -814,59 +780,59 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP }; const grav: IForce = { description: 'Gravity', - magnitude: this.dataDoc.mass * Math.abs(this.dataDoc.gravity), + magnitude: NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)), directionInDegrees: 270, component: false, }; this.dataDoc.updatedForces = [tensionForce1, tensionForce2, grav]; - this.dataDoc.startForces = [tensionForce1, tensionForce2, grav]; - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.start_Forces = [tensionForce1, tensionForce2, grav]; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }; // Default setup for pulley simulation setupPulley = () => { - this.dataDoc.showComponentForces = false; - this.dataDoc.startPosY = (this.yMax + this.yMin) / 2; - this.dataDoc.startPosX = (this.xMin + this.xMax) / 2 - 2 * (0.08 * this.layoutDoc[HeightSym]()) - 5; - this.dataDoc.positionYDisplay = this.getDisplayYPos((this.yMax + this.yMin) / 2); - this.dataDoc.positionXDisplay = (this.xMin + this.xMax) / 2 - 2 * (0.08 * this.layoutDoc[HeightSym]()) - 5; - let a = (-1 * ((this.dataDoc.mass - this.dataDoc.mass2) * Math.abs(this.dataDoc.gravity))) / (this.dataDoc.mass + this.dataDoc.mass2); + this.dataDoc.show_ComponentForces = false; + this.dataDoc.start_PosY = (this.yMax + this.yMin) / 2; + this.dataDoc.start_PosX = (this.xMin + this.xMax) / 2 - 2 * (0.08 * this.layoutDoc[HeightSym]()) - 5; + this.dataDoc.position_YDisplay = this.getDisplayYPos((this.yMax + this.yMin) / 2); + this.dataDoc.position_XDisplay = (this.xMin + this.xMax) / 2 - 2 * (0.08 * this.layoutDoc[HeightSym]()) - 5; + let a = (-1 * ((NumCast(this.dataDoc.mass) - NumCast(this.dataDoc.mass2)) * Math.abs(NumCast(this.dataDoc.gravity)))) / (NumCast(this.dataDoc.mass) + NumCast(this.dataDoc.mass2)); const gravityForce1: IForce = { description: 'Gravity', - magnitude: this.dataDoc.mass * Math.abs(this.dataDoc.gravity), + magnitude: NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)), directionInDegrees: 270, component: false, }; const tensionForce1: IForce = { description: 'Tension', - magnitude: this.dataDoc.mass * a + this.dataDoc.mass * Math.abs(this.dataDoc.gravity), + magnitude: NumCast(this.dataDoc.mass) * a + NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)), directionInDegrees: 90, component: false, }; a *= -1; const gravityForce2: IForce = { description: 'Gravity', - magnitude: this.dataDoc.mass2 * Math.abs(this.dataDoc.gravity), + magnitude: NumCast(this.dataDoc.mass2) * Math.abs(NumCast(this.dataDoc.gravity)), directionInDegrees: 270, component: false, }; const tensionForce2: IForce = { description: 'Tension', - magnitude: this.dataDoc.mass2 * a + this.dataDoc.mass2 * Math.abs(this.dataDoc.gravity), + magnitude: NumCast(this.dataDoc.mass2) * a + NumCast(this.dataDoc.mass2) * Math.abs(NumCast(this.dataDoc.gravity)), directionInDegrees: 90, component: false, }; this.dataDoc.updatedForces = [gravityForce1, tensionForce1]; - this.dataDoc.startForces = [gravityForce1, tensionForce1]; - this.dataDoc.startPosY2 = (this.yMax + this.yMin) / 2; - this.dataDoc.startPosX2 = (this.xMin + this.xMax) / 2 + 5; - this.dataDoc.positionYDisplay2 = this.getDisplayYPos((this.yMax + this.yMin) / 2); - this.dataDoc.positionXDisplay2 = (this.xMin + this.xMax) / 2 + 5; + this.dataDoc.start_Forces = [gravityForce1, tensionForce1]; + this.dataDoc.start_PosY2 = (this.yMax + this.yMin) / 2; + this.dataDoc.start_PosX2 = (this.xMin + this.xMax) / 2 + 5; + this.dataDoc.position_YDisplay2 = this.getDisplayYPos((this.yMax + this.yMin) / 2); + this.dataDoc.position_XDisplay2 = (this.xMin + this.xMax) / 2 + 5; this.dataDoc.updatedForces2 = [gravityForce2, tensionForce2]; - this.dataDoc.startForces2 = [gravityForce2, tensionForce2]; + this.dataDoc.start_Forces2 = [gravityForce2, tensionForce2]; - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }; // Helper function used for tutorial and review mode @@ -895,23 +861,23 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP updateReviewModeValues = () => { const forceOfGravityReview: IForce = { description: 'Gravity', - magnitude: this.dataDoc.reviewGravityMagnitude, - directionInDegrees: this.dataDoc.reviewGravityAngle, + magnitude: NumCast(this.dataDoc.review_GravityMagnitude), + directionInDegrees: NumCast(this.dataDoc.review_GravityAngle), component: false, }; const normalForceReview: IForce = { description: 'Normal Force', - magnitude: this.dataDoc.reviewNormalMagnitude, - directionInDegrees: this.dataDoc.reviewNormalAngle, + magnitude: NumCast(this.dataDoc.review_NormalMagnitude), + directionInDegrees: NumCast(this.dataDoc.review_NormalAngle), component: false, }; const staticFrictionForceReview: IForce = { description: 'Static Friction Force', - magnitude: this.dataDoc.reviewStaticMagnitude, - directionInDegrees: this.dataDoc.reviewStaticAngle, + magnitude: NumCast(this.dataDoc.review_StaticMagnitude), + directionInDegrees: NumCast(this.dataDoc.review_StaticAngle), component: false, }; - this.dataDoc.startForces = [forceOfGravityReview, normalForceReview, staticFrictionForceReview]; + this.dataDoc.start_Forces = [forceOfGravityReview, normalForceReview, staticFrictionForceReview]; this.dataDoc.updatedForces = [forceOfGravityReview, normalForceReview, staticFrictionForceReview]; }; @@ -922,7 +888,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <div className="mechanicsSimulationContentContainer"> <div className="mechanicsSimulationButtonsAndElements"> <div className="mechanicsSimulationButtons"> - {!this.dataDoc.simulationPaused && ( + {!this.dataDoc.simulation_Paused && ( <div style={{ position: 'fixed', @@ -939,92 +905,96 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP dataDoc={this.dataDoc} layoutDoc={this.layoutDoc} wallPositions={this.wallPositions} - adjustPendulumAngle={this.dataDoc.adjustPendulumAngle} - gravity={this.dataDoc.gravity} - circularMotionRadius={this.dataDoc.circularMotionRadius} + adjustPendulumAngle={NumCast(this.dataDoc.pendulum_angle_adjust)} + adjustPendulumLength={NumCast(this.dataDoc.pendulum_length_adjust)} + gravity={NumCast(this.dataDoc.gravity)} + circularMotionRadius={NumCast(this.dataDoc.circularMotionRadius)} componentForces={this.dataDoc.componentForces} - showComponentForces={this.dataDoc.showComponentForces} + showComponentForces={BoolCast(this.dataDoc.show_ComponentForces)} color={'red'} - coefficientOfKineticFriction={Number(this.dataDoc.coefficientOfKineticFriction)} - displayXVelocity={this.dataDoc.velocityXDisplay} - displayYVelocity={this.dataDoc.velocityYDisplay} - elasticCollisions={this.dataDoc.elasticCollisions} - mass={this.dataDoc.mass} - mode={this.dataDoc.mode} - noMovement={this.dataDoc.noMovement} - paused={this.dataDoc.simulationPaused} - pendulumAngle={this.dataDoc.pendulumAngle} - pendulumLength={this.dataDoc.pendulumLength} + coefficientOfKineticFriction={NumCast(this.dataDoc.coefficientOfKineticFriction)} + displayXVelocity={NumCast(this.dataDoc.velocity_XDisplay)} + displayYVelocity={NumCast(this.dataDoc.velocity_YDisplay)} + elasticCollisions={BoolCast(this.dataDoc.elasticCollisions)} + mass={NumCast(this.dataDoc.mass)} + mode={StrCast(this.dataDoc.mode)} + noMovement={BoolCast(this.dataDoc.noMovement)} + paused={BoolCast(this.dataDoc.simulation_Paused)} + pendulumAngle={NumCast(this.dataDoc.pendulum_angle)} + pendulumLength={NumCast(this.dataDoc.pendulum_length)} radius={0.08 * this.layoutDoc[HeightSym]()} - reset={this.dataDoc.simulationReset} - simulationSpeed={this.dataDoc.simulationSpeed} - startPendulumAngle={this.dataDoc.startPendulumAngle} - showAcceleration={this.dataDoc.showAcceleration} - showForceMagnitudes={this.dataDoc.showForceMagnitudes} - showForces={this.dataDoc.showForces} - showVelocity={this.dataDoc.showVelocity} - simulationType={this.dataDoc.simulationType} - springConstant={this.dataDoc.springConstant} - springStartLength={this.dataDoc.springStartLength} - springRestLength={this.dataDoc.springRestLength} - startForces={this.dataDoc.startForces} - startPosX={this.dataDoc.startPosX} - startPosY={this.dataDoc.startPosY ?? 0} - startVelX={this.dataDoc.startVelX} - startVelY={this.dataDoc.startVelY} + reset={BoolCast(this.dataDoc.simulation_Reset)} + simulationSpeed={NumCast(this.dataDoc.simulation_Speed)} + startPendulumAngle={NumCast(this.dataDoc.start_PendulumAngle)} + showAcceleration={BoolCast(this.dataDoc.show_Acceleration)} + showForceMagnitudes={BoolCast(this.dataDoc.show_ForceMagnitudes)} + showForces={BoolCast(this.dataDoc.show_Forces)} + showVelocity={BoolCast(this.dataDoc.show_Velocity)} + simulationType={StrCast(this.dataDoc.simulation_Type)} + springConstant={NumCast(this.dataDoc.springConstant)} + springStartLength={NumCast(this.dataDoc.springStartLength)} + springRestLength={NumCast(this.dataDoc.springRestLength)} + startForces={this.dataDoc.start_Forces} + startPosX={NumCast(this.dataDoc.start_PosX)} + startPosY={NumCast(this.dataDoc.start_PosY)} + startVelX={NumCast(this.dataDoc.start_VelX)} + startVelY={NumCast(this.dataDoc.start_VelY)} timestepSize={0.05} - updateDisplay={this.dataDoc.displayChange} + updateXDisplay={NumCast(this.dataDoc.display_xChange)} + updateYDisplay={NumCast(this.dataDoc.display_yChange)} updatedForces={this.dataDoc.updatedForces} - wedgeHeight={this.dataDoc.wedgeHeight} - wedgeWidth={this.dataDoc.wedgeWidth} + wedgeHeight={NumCast(this.dataDoc.wedgeHeight)} + wedgeWidth={NumCast(this.dataDoc.wedgeWidth)} xMax={this.xMax} xMin={this.xMin} yMax={this.yMax} yMin={this.yMin} /> - {this.dataDoc.simulationType == 'Pulley' && ( + {this.dataDoc.simulation_Type == 'Pulley' && ( <Weight dataDoc={this.dataDoc} layoutDoc={this.layoutDoc} wallPositions={this.wallPositions} - adjustPendulumAngle={this.dataDoc.adjustPendulumAngle} - circularMotionRadius={this.dataDoc.circularMotionRadius} - gravity={this.dataDoc.gravity} + adjustPendulumAngle={NumCast(this.dataDoc.pendulum_angle_adjust)} + adjustPendulumLength={NumCast(this.dataDoc.pendulum_length_adjust)} + circularMotionRadius={NumCast(this.dataDoc.circularMotionRadius)} + gravity={NumCast(this.dataDoc.gravity)} componentForces={this.dataDoc.componentForces} - showComponentForces={this.dataDoc.showComponentForces} + showComponentForces={BoolCast(this.dataDoc.show_ComponentForces)} color={'blue'} - coefficientOfKineticFriction={Number(this.dataDoc.coefficientOfKineticFriction)} - displayXVelocity={this.dataDoc.velocityXDisplay2} - displayYVelocity={this.dataDoc.velocityYDisplay2} - elasticCollisions={this.dataDoc.elasticCollisions} - mass={this.dataDoc.mass2} - mode={this.dataDoc.mode} - noMovement={this.dataDoc.noMovement} - paused={this.dataDoc.simulationPaused} - pendulumAngle={this.dataDoc.pendulumAngle} - pendulumLength={this.dataDoc.pendulumLength} + coefficientOfKineticFriction={NumCast(this.dataDoc.coefficientOfKineticFriction)} + displayXVelocity={NumCast(this.dataDoc.velocity_XDisplay)} + displayYVelocity={NumCast(this.dataDoc.velocity_YDisplay)} + elasticCollisions={BoolCast(this.dataDoc.elasticCollisions)} + mass={NumCast(this.dataDoc.mass2)} + mode={StrCast(this.dataDoc.mode)} + noMovement={BoolCast(this.dataDoc.noMovement)} + paused={BoolCast(this.dataDoc.simulation_Paused)} + pendulumAngle={NumCast(this.dataDoc.pendulum_angle)} + pendulumLength={NumCast(this.dataDoc.pendulum_length)} radius={0.08 * this.layoutDoc[HeightSym]()} - reset={this.dataDoc.simulationReset} - simulationSpeed={this.dataDoc.simulationSpeed} - startPendulumAngle={this.dataDoc.startPendulumAngle} - showAcceleration={this.dataDoc.showAcceleration} - showForceMagnitudes={this.dataDoc.showForceMagnitudes} - showForces={this.dataDoc.showForces} - showVelocity={this.dataDoc.showVelocity} - simulationType={this.dataDoc.simulationType} - springConstant={this.dataDoc.springConstant} - springStartLength={this.dataDoc.springStartLength} - springRestLength={this.dataDoc.springRestLength} - startForces={this.dataDoc.startForces2} - startPosX={this.dataDoc.startPosX2} - startPosY={this.dataDoc.startPosY2} - startVelX={this.dataDoc.startVelX} - startVelY={this.dataDoc.startVelY} + reset={BoolCast(this.dataDoc.simulation_Reset)} + simulationSpeed={NumCast(this.dataDoc.simulation_Speed)} + startPendulumAngle={NumCast(this.dataDoc.start_PendulumAngle)} + showAcceleration={BoolCast(this.dataDoc.show_Acceleration)} + showForceMagnitudes={BoolCast(this.dataDoc.show_ForceMagnitudes)} + showForces={BoolCast(this.dataDoc.show_Forces)} + showVelocity={BoolCast(this.dataDoc.show_Velocity)} + simulationType={this.dataDoc.simulation_Type} + springConstant={NumCast(this.dataDoc.springConstant)} + springStartLength={NumCast(this.dataDoc.springStartLength)} + springRestLength={NumCast(this.dataDoc.springRestLength)} + startForces={this.dataDoc.start_Forces2} + startPosX={NumCast(this.dataDoc.start_PosX2)} + startPosY={NumCast(this.dataDoc.start_PosY2)} + startVelX={NumCast(this.dataDoc.start_VelX)} + startVelY={NumCast(this.dataDoc.start_VelY)} timestepSize={0.05} - updateDisplay={this.dataDoc.displayChange2} + updateXDisplay={NumCast(this.dataDoc.display_xChange2)} + updateYDisplay={NumCast(this.dataDoc.display_yChange2)} updatedForces={this.dataDoc.updatedForces2} - wedgeHeight={this.dataDoc.wedgeHeight} - wedgeWidth={this.dataDoc.wedgeWidth} + wedgeHeight={NumCast(this.dataDoc.wedgeHeight)} + wedgeWidth={NumCast(this.dataDoc.wedgeWidth)} xMax={this.xMax} xMin={this.xMin} yMax={this.yMax} @@ -1033,7 +1003,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP )} </div> <div> - {(this.dataDoc.simulationType == 'One Weight' || this.dataDoc.simulationType == 'Inclined Plane') && + {(this.dataDoc.simulation_Type == 'One Weight' || this.dataDoc.simulation_Type == 'Inclined Plane') && this.wallPositions && this.wallPositions.map((element, index) => { return <Wall key={index} length={element.length} xPos={element.xPos} yPos={element.yPos} angleInDegrees={element.angleInDegrees} />; @@ -1044,26 +1014,26 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <div className="mechanicsSimulationEquationContainer"> <div className="mechanicsSimulationControls"> <Stack direction="row" spacing={1}> - {this.dataDoc.simulationPaused && this.dataDoc.mode != 'Tutorial' && ( + {this.dataDoc.simulation_Paused && this.dataDoc.mode != 'Tutorial' && ( <IconButton onClick={() => { - this.dataDoc.simulationPaused = false; + this.dataDoc.simulation_Paused = false; }}> <PlayArrowIcon /> </IconButton> )} - {!this.dataDoc.simulationPaused && this.dataDoc.mode != 'Tutorial' && ( + {!this.dataDoc.simulation_Paused && this.dataDoc.mode != 'Tutorial' && ( <IconButton onClick={() => { - this.dataDoc.simulationPaused = true; + this.dataDoc.simulation_Paused = true; }}> <PauseIcon /> </IconButton> )} - {this.dataDoc.simulationPaused && this.dataDoc.mode != 'Tutorial' && ( + {this.dataDoc.simulation_Paused && this.dataDoc.mode != 'Tutorial' && ( <IconButton onClick={() => { - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }}> <ReplayIcon /> </IconButton> @@ -1071,10 +1041,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </Stack> <div className="dropdownMenu"> <select - value={this.dataDoc.simulationType} + value={StrCast(this.dataDoc.simulation_Type)} onChange={event => { - this.dataDoc.simulationType = event.target.value; - this.setupSimulation(event.target.value, this.dataDoc.mode); + this.dataDoc.simulation_Type = event.target.value; + this.setupSimulation(event.target.value, StrCast(this.dataDoc.mode)); }} style={{ height: '2em', width: '100%', fontSize: '16px' }}> <option value="One Weight">Projectile</option> @@ -1088,10 +1058,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </div> <div className="dropdownMenu"> <select - value={this.dataDoc.mode} + value={StrCast(this.dataDoc.mode)} onChange={event => { this.dataDoc.mode = event.target.value; - this.setupSimulation(this.dataDoc.simulationType, event.target.value); + this.setupSimulation(StrCast(this.dataDoc.simulation_Type), event.target.value); }} style={{ height: '2em', width: '100%', fontSize: '16px' }}> <option value="Tutorial">Tutorial Mode</option> @@ -1100,13 +1070,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </select> </div> </div> - {this.dataDoc.mode == 'Review' && this.dataDoc.simulationType != 'Inclined Plane' && ( + {this.dataDoc.mode == 'Review' && this.dataDoc.simulation_Type != 'Inclined Plane' && ( <div className="wordProblemBox"> - <p>{this.dataDoc.simulationType} review problems in progress!</p> + <p>{this.dataDoc.simulation_Type} review problems in progress!</p> <hr /> </div> )} - {this.dataDoc.mode == 'Review' && this.dataDoc.simulationType == 'Inclined Plane' && ( + {this.dataDoc.mode == 'Review' && this.dataDoc.simulation_Type == 'Inclined Plane' && ( <div> {!this.dataDoc.hintDialogueOpen && ( <IconButton @@ -1121,26 +1091,25 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <QuestionMarkIcon /> </IconButton> )} - <Dialog maxWidth={'sm'} fullWidth={true} open={this.dataDoc.hintDialogueOpen} onClose={() => (this.dataDoc.hintDialogueOpen = false)}> + <Dialog maxWidth={'sm'} fullWidth={true} open={BoolCast(this.dataDoc.hintDialogueOpen)} onClose={() => (this.dataDoc.hintDialogueOpen = false)}> <DialogTitle>Hints</DialogTitle> <DialogContent> - {this.dataDoc.selectedQuestion.hints && - this.dataDoc.selectedQuestion.hints.map((hint: any, index: number) => { - return ( - <div key={index}> - <DialogContentText> - <details> - <summary> - <b> - Hint {index + 1}: {hint.description} - </b> - </summary> - {hint.content} - </details> - </DialogContentText> - </div> - ); - })} + {this.dataDoc.selectedQuestion.hints?.map((hint: any, index: number) => { + return ( + <div key={index}> + <DialogContentText> + <details> + <summary> + <b> + Hint {index + 1}: {hint.description} + </b> + </summary> + {hint.content} + </details> + </DialogContentText> + </div> + ); + })} </DialogContent> <DialogActions> <Button @@ -1153,8 +1122,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </Dialog> <div className="wordProblemBox"> <div className="question"> - <p>{this.dataDoc.questionPartOne}</p> - <p>{this.dataDoc.questionPartTwo}</p> + <p>{StrCast(this.dataDoc.questionPartOne)}</p> + <p>{StrCast(this.dataDoc.questionPartTwo)}</p> </div> <div className="answers"> {this.dataDoc.selectedQuestion.answerParts.includes('force of gravity') && ( @@ -1162,13 +1131,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP label={<p>Gravity magnitude</p>} lowerBound={0} dataDoc={this.dataDoc} - prop={'reviewGravityMagnitude'} + prop="review_GravityMagnitude" step={0.1} unit={'N'} upperBound={50} - value={this.dataDoc.reviewGravityMagnitude} - showIcon={this.dataDoc.showIcon} - correctValue={this.dataDoc.answers[this.dataDoc.selectedQuestion.answerParts.indexOf('force of gravity')]} + value={NumCast(this.dataDoc.review_GravityMagnitude)} + showIcon={BoolCast(this.dataDoc.show_Icon)} + correctValue={NumListCast(this.dataDoc.answers)[this.dataDoc.selectedQuestion.answerParts.indexOf('force of gravity')]} labelWidth={'7em'} /> )} @@ -1177,14 +1146,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP label={<p>Gravity angle</p>} lowerBound={0} dataDoc={this.dataDoc} - prop={'reviewGravityAngle'} + prop="review_GravityAngle" step={1} unit={'°'} upperBound={360} - value={this.dataDoc.reviewGravityAngle} + value={NumCast(this.dataDoc.review_GravityAngle)} radianEquivalent={true} - showIcon={this.dataDoc.showIcon} - correctValue={this.dataDoc.answers[this.dataDoc.selectedQuestion.answerParts.indexOf('angle of gravity')]} + showIcon={BoolCast(this.dataDoc.show_Icon)} + correctValue={NumListCast(this.dataDoc.answers)[this.dataDoc.selectedQuestion.answerParts.indexOf('angle of gravity')]} labelWidth={'7em'} /> )} @@ -1193,13 +1162,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP label={<p>Normal force magnitude</p>} lowerBound={0} dataDoc={this.dataDoc} - prop={'reviewNormalMagnitude'} + prop="review_NormalMagnitude" step={0.1} unit={'N'} upperBound={50} - value={this.dataDoc.reviewNormalMagnitude} - showIcon={this.dataDoc.showIcon} - correctValue={this.dataDoc.answers[this.dataDoc.selectedQuestion.answerParts.indexOf('normal force')]} + value={NumCast(this.dataDoc.review_NormalMagnitude)} + showIcon={BoolCast(this.dataDoc.show_Icon)} + correctValue={NumListCast(this.dataDoc.answers)[this.dataDoc.selectedQuestion.answerParts.indexOf('normal force')]} labelWidth={'7em'} /> )} @@ -1208,14 +1177,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP label={<p>Normal force angle</p>} lowerBound={0} dataDoc={this.dataDoc} - prop={'reviewNormalAngle'} + prop="review_NormalAngle" step={1} unit={'°'} upperBound={360} - value={this.dataDoc.reviewNormalAngle} + value={NumCast(this.dataDoc.review_NormalAngle)} radianEquivalent={true} - showIcon={this.dataDoc.showIcon} - correctValue={this.dataDoc.answers[this.dataDoc.selectedQuestion.answerParts.indexOf('angle of normal force')]} + showIcon={BoolCast(this.dataDoc.show_Icon)} + correctValue={NumListCast(this.dataDoc.answers)[this.dataDoc.selectedQuestion.answerParts.indexOf('angle of normal force')]} labelWidth={'7em'} /> )} @@ -1224,13 +1193,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP label={<p>Static friction magnitude</p>} lowerBound={0} dataDoc={this.dataDoc} - prop={'reviewStaticMagnitude'} + prop="review_StaticMagnitude" step={0.1} unit={'N'} upperBound={50} - value={this.dataDoc.reviewStaticMagnitude} - showIcon={this.dataDoc.showIcon} - correctValue={this.dataDoc.answers[this.dataDoc.selectedQuestion.answerParts.indexOf('force of static friction')]} + value={NumCast(this.dataDoc.review_StaticMagnitude)} + showIcon={BoolCast(this.dataDoc.show_Icon)} + correctValue={NumListCast(this.dataDoc.answers)[this.dataDoc.selectedQuestion.answerParts.indexOf('force of static friction')]} labelWidth={'7em'} /> )} @@ -1239,14 +1208,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP label={<p>Static friction angle</p>} lowerBound={0} dataDoc={this.dataDoc} - prop={'reviewStaticAngle'} + prop="review_StaticAngle" step={1} unit={'°'} upperBound={360} - value={this.dataDoc.reviewStaticAngle} + value={NumCast(this.dataDoc.review_StaticAngle)} radianEquivalent={true} - showIcon={this.dataDoc.showIcon} - correctValue={this.dataDoc.answers[this.dataDoc.selectedQuestion.answerParts.indexOf('angle of static friction')]} + showIcon={BoolCast(this.dataDoc.show_Icon)} + correctValue={NumListCast(this.dataDoc.answers)[this.dataDoc.selectedQuestion.answerParts.indexOf('angle of static friction')]} labelWidth={'7em'} /> )} @@ -1259,14 +1228,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP } lowerBound={0} dataDoc={this.dataDoc} - prop={'coefficientOfStaticFriction'} + prop="coefficientOfStaticFriction" step={0.1} unit={''} upperBound={1} - value={this.dataDoc.coefficientOfStaticFriction} + value={NumCast(this.dataDoc.coefficientOfStaticFriction)} effect={this.updateReviewForcesBasedOnCoefficient} - showIcon={this.dataDoc.showIcon} - correctValue={this.dataDoc.answers[this.dataDoc.selectedQuestion.answerParts.indexOf('coefficient of static friction')]} + showIcon={BoolCast(this.dataDoc.show_Icon)} + correctValue={NumListCast(this.dataDoc.answers)[this.dataDoc.selectedQuestion.answerParts.indexOf('coefficient of static friction')]} /> )} {this.dataDoc.selectedQuestion.answerParts.includes('wedge angle') && ( @@ -1274,18 +1243,18 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP label={<Box>θ</Box>} lowerBound={0} dataDoc={this.dataDoc} - prop={'wedgeAngle'} + prop="wedgeAngle" step={1} unit={'°'} upperBound={49} - value={this.dataDoc.wedgeAngle ?? 26} + value={NumCast(this.dataDoc.wedgeAngle, 26)} effect={(val: number) => { this.changeWedgeBasedOnNewAngle(val); this.updateReviewForcesBasedOnAngle(val); }} radianEquivalent={true} - showIcon={this.dataDoc.showIcon} - correctValue={this.dataDoc.answers[this.dataDoc.selectedQuestion.answerParts.indexOf('wedge angle')]} + showIcon={BoolCast(this.dataDoc.show_Icon)} + correctValue={NumListCast(this.dataDoc.answers)[this.dataDoc.selectedQuestion.answerParts.indexOf('wedge angle')]} /> )} </div> @@ -1306,40 +1275,40 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP }}> <IconButton onClick={() => { - let step = this.dataDoc.stepNumber - 1; + let step = NumCast(this.dataDoc.stepNumber) - 1; step = Math.max(step, 0); step = Math.min(step, this.dataDoc.selectedTutorial.steps.length - 1); this.dataDoc.stepNumber = step; - this.dataDoc.startForces = this.getForceFromJSON(this.dataDoc.selectedTutorial.steps[step].forces); + this.dataDoc.start_Forces = this.getForceFromJSON(this.dataDoc.selectedTutorial.steps[step].forces); this.dataDoc.updatedForces = this.getForceFromJSON(this.dataDoc.selectedTutorial.steps[step].forces); - this.dataDoc.showForceMagnitudes = this.dataDoc.selectedTutorial.steps[step].showMagnitude; + this.dataDoc.show_ForceMagnitudes = this.dataDoc.selectedTutorial.steps[step].show_Magnitude; }} disabled={this.dataDoc.stepNumber == 0}> <ArrowLeftIcon /> </IconButton> <div> <h3> - Step {this.dataDoc.stepNumber + 1}: {this.dataDoc.selectedTutorial.steps[this.dataDoc.stepNumber].description} + Step {NumCast(this.dataDoc.stepNumber) + 1}: {this.dataDoc.selectedTutorial.steps[this.dataDoc.stepNumber].description} </h3> - <p>{this.dataDoc.selectedTutorial.steps[this.dataDoc.stepNumber].content}</p> + <p>{this.dataDoc.selectedTutorial.steps[NumCast(this.dataDoc.stepNumber)].content}</p> </div> <IconButton onClick={() => { - let step = this.dataDoc.stepNumber + 1; + let step = NumCast(this.dataDoc.stepNumber) + 1; step = Math.max(step, 0); step = Math.min(step, this.dataDoc.selectedTutorial.steps.length - 1); this.dataDoc.stepNumber = step; - this.dataDoc.startForces = this.getForceFromJSON(this.dataDoc.selectedTutorial.steps[step].forces); + this.dataDoc.start_Forces = this.getForceFromJSON(this.dataDoc.selectedTutorial.steps[step].forces); this.dataDoc.updatedForces = this.getForceFromJSON(this.dataDoc.selectedTutorial.steps[step].forces); - this.dataDoc.showForceMagnitudes = this.dataDoc.selectedTutorial.steps[step].showMagnitude; + this.dataDoc.show_ForceMagnitudes = this.dataDoc.selectedTutorial.steps[step].show_Magnitude; }} disabled={this.dataDoc.stepNumber == this.dataDoc.selectedTutorial.steps.length - 1}> <ArrowRightIcon /> </IconButton> </div> <div> - {(this.dataDoc.simulationType == 'One Weight' || this.dataDoc.simulationType == 'Inclined Plane' || this.dataDoc.simulationType == 'Pendulum') && <p>Resources</p>} - {this.dataDoc.simulationType == 'One Weight' && ( + {(this.dataDoc.simulation_Type == 'One Weight' || this.dataDoc.simulation_Type == 'Inclined Plane' || this.dataDoc.simulation_Type == 'Pendulum') && <p>Resources</p>} + {this.dataDoc.simulation_Type == 'One Weight' && ( <ul> <li> <a @@ -1367,7 +1336,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </li> </ul> )} - {this.dataDoc.simulationType == 'Inclined Plane' && ( + {this.dataDoc.simulation_Type == 'Inclined Plane' && ( <ul> <li> <a @@ -1395,7 +1364,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </li> </ul> )} - {this.dataDoc.simulationType == 'Pendulum' && ( + {this.dataDoc.simulation_Type == 'Pendulum' && ( <ul> <li> <a @@ -1414,7 +1383,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </div> </div> )} - {this.dataDoc.mode == 'Review' && this.dataDoc.simulationType == 'Inclined Plane' && ( + {this.dataDoc.mode == 'Review' && this.dataDoc.simulation_Type == 'Inclined Plane' && ( <div style={{ display: 'flex', @@ -1434,9 +1403,9 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <div style={{ display: 'flex', flexDirection: 'column' }}> <Button onClick={() => { - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; this.checkAnswers(); - this.dataDoc.showIcon = true; + this.dataDoc.show_Icon = true; }} variant="outlined"> <p>Submit</p> @@ -1444,7 +1413,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <Button onClick={() => { this.generateNewQuestion(); - this.dataDoc.showIcon = false; + this.dataDoc.show_Icon = false; }} variant="outlined"> <p>New question</p> @@ -1456,123 +1425,127 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <div className="vars"> <FormControl component="fieldset"> <FormGroup> - {this.dataDoc.simulationType == 'One Weight' && ( + {this.dataDoc.simulation_Type == 'One Weight' && ( <FormControlLabel - control={<Checkbox checked={this.dataDoc.elasticCollisions} onChange={() => (this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions)} />} + control={<Checkbox checked={BoolCast(this.dataDoc.elasticCollisions)} onChange={() => (this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions)} />} label="Make collisions elastic" labelPlacement="start" /> )} - <FormControlLabel control={<Checkbox checked={this.dataDoc.showForces} onChange={() => (this.dataDoc.showForces = !this.dataDoc.showForces)} />} label="Show force vectors" labelPlacement="start" /> - {(this.dataDoc.simulationType == 'Inclined Plane' || this.dataDoc.simulationType == 'Pendulum') && ( + <FormControlLabel control={<Checkbox checked={BoolCast(this.dataDoc.show_Forces)} onChange={() => (this.dataDoc.show_Forces = !this.dataDoc.show_Forces)} />} label="Show force vectors" labelPlacement="start" /> + {(this.dataDoc.simulation_Type == 'Inclined Plane' || this.dataDoc.simulation_Type == 'Pendulum') && ( <FormControlLabel - control={<Checkbox checked={this.dataDoc.showForces} onChange={() => (this.dataDoc.showComponentForces = !this.dataDoc.showComponentForces)} />} + control={<Checkbox checked={BoolCast(this.dataDoc.show_Forces)} onChange={() => (this.dataDoc.show_ComponentForces = !this.dataDoc.show_ComponentForces)} />} label="Show component force vectors" labelPlacement="start" /> )} <FormControlLabel - control={<Checkbox checked={this.dataDoc.showAcceleration} onChange={() => (this.dataDoc.showAcceleration = !this.dataDoc.showAcceleration)} />} + control={<Checkbox checked={BoolCast(this.dataDoc.show_Acceleration)} onChange={() => (this.dataDoc.show_Acceleration = !this.dataDoc.show_Acceleration)} />} label="Show acceleration vector" labelPlacement="start" /> - <FormControlLabel control={<Checkbox checked={this.dataDoc.showVelocity} onChange={() => (this.dataDoc.showVelocity = !this.dataDoc.showVelocity)} />} label="Show velocity vector" labelPlacement="start" /> - <InputField label={<Box>Speed</Box>} lowerBound={1} dataDoc={this.dataDoc} prop={'simulationSpeed'} step={1} unit={'x'} upperBound={10} value={this.dataDoc.simulationSpeed ?? 2} labelWidth={'5em'} /> - {this.dataDoc.simulationPaused && this.dataDoc.simulationType != 'Circular Motion' && ( + <FormControlLabel + control={<Checkbox checked={BoolCast(this.dataDoc.show_Velocity)} onChange={() => (this.dataDoc.show_Velocity = !this.dataDoc.show_Velocity)} />} + label="Show velocity vector" + labelPlacement="start" + /> + <InputField label={<Box>Speed</Box>} lowerBound={1} dataDoc={this.dataDoc} prop="simulation_Speed" step={1} unit={'x'} upperBound={10} value={this.dataDoc.simulation_Speed ?? 2} labelWidth={'5em'} /> + {this.dataDoc.simulation_Paused && this.dataDoc.simulation_Type != 'Circular Motion' && ( <InputField label={<Box>Gravity</Box>} lowerBound={-30} dataDoc={this.dataDoc} - prop={'gravity'} + prop="gravity" step={0.01} unit={'m/s2'} upperBound={0} - value={this.dataDoc.gravity ?? -9.81} + value={NumCast(this.dataDoc.gravity, -9.81)} effect={(val: number) => { - this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); + this.setupSimulation(StrCast(this.dataDoc.simulation_Type), StrCast(this.dataDoc.mode)); }} labelWidth={'5em'} /> )} - {this.dataDoc.simulationPaused && this.dataDoc.simulationType != 'Pulley' && ( + {this.dataDoc.simulation_Paused && this.dataDoc.simulation_Type != 'Pulley' && ( <InputField label={<Box>Mass</Box>} lowerBound={1} dataDoc={this.dataDoc} - prop={'mass'} + prop="mass" step={0.1} unit={'kg'} upperBound={5} - value={this.dataDoc.mass ?? 1} + value={NumCast(this.dataDoc.mass) ?? 1} effect={(val: number) => { - this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); + this.setupSimulation(StrCast(this.dataDoc.simulation_Type), StrCast(this.dataDoc.mode)); }} labelWidth={'5em'} /> )} - {this.dataDoc.simulationPaused && this.dataDoc.simulationType == 'Pulley' && ( + {this.dataDoc.simulation_Paused && this.dataDoc.simulation_Type == 'Pulley' && ( <InputField label={<Box>Red mass</Box>} lowerBound={1} dataDoc={this.dataDoc} - prop={'mass'} + prop="mass" step={0.1} unit={'kg'} upperBound={5} - value={this.dataDoc.mass ?? 1} + value={NumCast(this.dataDoc.mass) ?? 1} effect={(val: number) => { - this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); + this.setupSimulation(StrCast(this.dataDoc.simulation_Type), StrCast(this.dataDoc.mode)); }} labelWidth={'5em'} /> )} - {this.dataDoc.simulationPaused && this.dataDoc.simulationType == 'Pulley' && ( + {this.dataDoc.simulation_Paused && this.dataDoc.simulation_Type == 'Pulley' && ( <InputField label={<Box>Blue mass</Box>} lowerBound={1} dataDoc={this.dataDoc} - prop={'mass2'} + prop="mass2" step={0.1} unit={'kg'} upperBound={5} - value={this.dataDoc.mass2 ?? 1} + value={NumCast(this.dataDoc.mass2) ?? 1} effect={(val: number) => { - this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); + this.setupSimulation(StrCast(this.dataDoc.simulation_Type), StrCast(this.dataDoc.mode)); }} labelWidth={'5em'} /> )} - {this.dataDoc.simulationPaused && this.dataDoc.simulationType == 'Circular Motion' && ( + {this.dataDoc.simulation_Paused && this.dataDoc.simulation_Type == 'Circular Motion' && ( <InputField label={<Box>Rod length</Box>} lowerBound={100} dataDoc={this.dataDoc} - prop={'circularMotionRadius'} + prop="circularMotionRadius" step={5} unit={'m'} upperBound={250} - value={this.dataDoc.circularMotionRadius ?? 100} + value={NumCast(this.dataDoc.circularMotionRadius) ?? 100} effect={(val: number) => { - this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); + this.setupSimulation(StrCast(this.dataDoc.simulation_Type), StrCast(this.dataDoc.mode)); }} labelWidth={'5em'} /> )} </FormGroup> </FormControl> - {this.dataDoc.simulationType == 'Spring' && this.dataDoc.simulationPaused && ( + {this.dataDoc.simulation_Type == 'Spring' && this.dataDoc.simulation_Paused && ( <div> <InputField label={<Typography color="inherit">Spring stiffness</Typography>} lowerBound={0.1} dataDoc={this.dataDoc} - prop={'springConstant'} + prop="springConstant" step={1} unit={'N/m'} upperBound={500} - value={this.dataDoc.springConstant ?? 0.5} + value={NumCast(this.dataDoc.springConstant) ?? 0.5} effect={(val: number) => { - this.dataDoc.simulationReset(!this.dataDoc.simulationReset); + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }} radianEquivalent={false} mode={'Freeform'} @@ -1582,13 +1555,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP label={<Typography color="inherit">Rest length</Typography>} lowerBound={10} dataDoc={this.dataDoc} - prop={'springRestLength'} + prop="springRestLength" step={100} unit={''} upperBound={500} - value={this.dataDoc.springRestLength ?? 200} + value={NumCast(this.dataDoc.springRestLength) ?? 200} effect={(val: number) => { - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }} radianEquivalent={false} mode={'Freeform'} @@ -1596,17 +1569,17 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP /> <InputField label={<Typography color="inherit">Starting displacement</Typography>} - lowerBound={-(this.dataDoc.springRestLength - 10)} + lowerBound={-(NumCast(this.dataDoc.springRestLength) - 10)} dataDoc={this.dataDoc} - prop={''} + prop="" step={10} unit={''} - upperBound={this.dataDoc.springRestLength} - value={this.dataDoc.springStartLength - this.dataDoc.springRestLength ?? 0} + upperBound={NumCast(this.dataDoc.springRestLength)} + value={NumCast(this.dataDoc.springStartLength) - NumCast(this.dataDoc.springRestLength)} effect={(val: number) => { - this.dataDoc.startPosY = this.dataDoc.springRestLength + val; - this.dataDoc.springStartLength = this.dataDoc.springRestLength + val; - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.start_PosY = NumCast(this.dataDoc.springRestLength) + val; + this.dataDoc.springStartLength = NumCast(this.dataDoc.springRestLength) + val; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }} radianEquivalent={false} mode={'Freeform'} @@ -1614,20 +1587,20 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP /> </div> )} - {this.dataDoc.simulationType == 'Inclined Plane' && this.dataDoc.simulationPaused && ( + {this.dataDoc.simulation_Type == 'Inclined Plane' && this.dataDoc.simulation_Paused && ( <div> <InputField label={<Box>θ</Box>} lowerBound={0} dataDoc={this.dataDoc} - prop={'wedgeAngle'} + prop="wedgeAngle" step={1} unit={'°'} upperBound={49} - value={this.dataDoc.wedgeAngle ?? 26} + value={NumCast(this.dataDoc.wedgeAngle, 26)} effect={(val: number) => { this.changeWedgeBasedOnNewAngle(val); - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }} radianEquivalent={true} mode={'Freeform'} @@ -1641,17 +1614,17 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP } lowerBound={0} dataDoc={this.dataDoc} - prop={'coefficientOfStaticFriction'} + prop="coefficientOfStaticFriction" step={0.1} unit={''} upperBound={1} - value={this.dataDoc.coefficientOfStaticFriction ?? 0} + value={NumCast(this.dataDoc.coefficientOfStaticFriction) ?? 0} effect={(val: number) => { this.updateForcesWithFriction(val); - if (val < Number(this.dataDoc.coefficientOfKineticFriction)) { + if (val < NumCast(this.dataDoc.coefficientOfKineticFriction)) { this.dataDoc.soefficientOfKineticFriction = val; } - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }} mode={'Freeform'} labelWidth={'2em'} @@ -1664,48 +1637,48 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP } lowerBound={0} dataDoc={this.dataDoc} - prop={'coefficientOfKineticFriction'} + prop="coefficientOfKineticFriction" step={0.1} unit={''} - upperBound={Number(this.dataDoc.coefficientOfStaticFriction)} - value={this.dataDoc.coefficientOfKineticFriction ?? 0} + upperBound={NumCast(this.dataDoc.coefficientOfStaticFriction)} + value={NumCast(this.dataDoc.coefficientOfKineticFriction) ?? 0} effect={(val: number) => { - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }} mode={'Freeform'} labelWidth={'2em'} /> </div> )} - {this.dataDoc.simulationType == 'Inclined Plane' && !this.dataDoc.simulationPaused && ( + {this.dataDoc.simulation_Type == 'Inclined Plane' && !this.dataDoc.simulation_Paused && ( <Typography> - θ: {Math.round(Number(this.dataDoc.wedgeAngle) * 100) / 100}° ≈ {Math.round(((Number(this.dataDoc.wedgeAngle) * Math.PI) / 180) * 100) / 100} rad + θ: {Math.round(NumCast(this.dataDoc.wedgeAngle) * 100) / 100}° ≈ {Math.round(((NumCast(this.dataDoc.wedgeAngle) * Math.PI) / 180) * 100) / 100} rad <br /> μ <sub>s</sub>: {this.dataDoc.coefficientOfStaticFriction} <br /> μ <sub>k</sub>: {this.dataDoc.coefficientOfKineticFriction} </Typography> )} - {this.dataDoc.simulationType == 'Pendulum' && !this.dataDoc.simulationPaused && ( + {this.dataDoc.simulation_Type == 'Pendulum' && !this.dataDoc.simulation_Paused && ( <Typography> - θ: {Math.round(this.dataDoc.pendulumAngle * 100) / 100}° ≈ {Math.round(((this.dataDoc.pendulumAngle * Math.PI) / 180) * 100) / 100} rad + θ: {Math.round(NumCast(this.dataDoc.pendulum_angle) * 100) / 100}° ≈ {Math.round(((NumCast(this.dataDoc.pendulum_angle) * Math.PI) / 180) * 100) / 100} rad </Typography> )} - {this.dataDoc.simulationType == 'Pendulum' && this.dataDoc.simulationPaused && ( + {this.dataDoc.simulation_Type == 'Pendulum' && this.dataDoc.simulation_Paused && ( <div> <InputField label={<Box>Angle</Box>} lowerBound={0} dataDoc={this.dataDoc} - prop={'pendulumAngle'} + prop="pendulum_angle" step={1} unit={'°'} upperBound={59} - value={this.dataDoc.pendulumAngle ?? 30} + value={NumCast(this.dataDoc.pendulum_angle, 30)} effect={value => { - this.dataDoc.startPendulumAngle = value; - if (this.dataDoc.simulationType == 'Pendulum') { - const mag = this.dataDoc.mass * Math.abs(this.dataDoc.gravity) * Math.cos((value * Math.PI) / 180); + this.dataDoc.start_PendulumAngle = value; + if (this.dataDoc.simulation_Type == 'Pendulum') { + const mag = NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((value * Math.PI) / 180); const forceOfTension: IForce = { description: 'Tension', @@ -1722,29 +1695,29 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', - magnitude: Math.abs(this.dataDoc.gravity) * Math.cos((value * Math.PI) / 180), + magnitude: Math.abs(NumCast(this.dataDoc.gravity)) * Math.cos((value * Math.PI) / 180), directionInDegrees: 270 - value, component: true, }; const gravityPerpendicular: IForce = { description: 'Gravity Perpendicular Component', - magnitude: Math.abs(this.dataDoc.gravity) * Math.sin((value * Math.PI) / 180), + magnitude: Math.abs(NumCast(this.dataDoc.gravity)) * Math.sin((value * Math.PI) / 180), directionInDegrees: -value, component: true, }; - const length = this.dataDoc.pendulumLength; + const length = NumCast(this.dataDoc.pendulum_length); const x = length * Math.cos(((90 - value) * Math.PI) / 180); const y = length * Math.sin(((90 - value) * Math.PI) / 180); - const xPos = this.xMax / 2 - x - this.dataDoc.radius; - const yPos = y - this.dataDoc.radius - 5; - this.dataDoc.startPosX = xPos; - this.dataDoc.startPosY = yPos; + const xPos = this.xMax / 2 - x - NumCast(this.dataDoc.radius); + const yPos = y - NumCast(this.dataDoc.radius) - 5; + this.dataDoc.start_PosX = xPos; + this.dataDoc.start_PosY = yPos; - this.dataDoc.startForces = [ + this.dataDoc.start_Forces = [ { description: 'Gravity', - magnitude: Math.abs(this.dataDoc.gravity) * this.dataDoc.mass, + magnitude: Math.abs(NumCast(this.dataDoc.gravity)) * NumCast(this.dataDoc.mass), directionInDegrees: 270, component: false, }, @@ -1753,18 +1726,15 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.updatedForces = [ { description: 'Gravity', - magnitude: Math.abs(this.dataDoc.gravity) * this.dataDoc.mass, + magnitude: Math.abs(NumCast(this.dataDoc.gravity)) * NumCast(this.dataDoc.mass), directionInDegrees: 270, component: false, }, forceOfTension, ]; this.dataDoc.componentForces = [tensionComponent, gravityParallel, gravityPerpendicular]; - this.dataDoc.adjustPendulumAngle = { - angle: value, - length: this.dataDoc.pendulumLength, - }; - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.pendulum_angle_adjust = value; + (this.dataDoc.pendulum_length_adjust = this.dataDoc.pendulum_length), (this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset); } }} radianEquivalent={true} @@ -1775,18 +1745,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP label={<Box>Rod length</Box>} lowerBound={0} dataDoc={this.dataDoc} - prop={'pendulumLength'} + prop="pendulum_length" step={1} unit={'m'} upperBound={400} - value={Math.round(this.dataDoc.pendulumLength)} + value={Math.round(NumCast(this.dataDoc.pendulum_length))} effect={value => { - if (this.dataDoc.simulationType == 'Pendulum') { - this.dataDoc.adjustPendulumAngle = { - angle: this.dataDoc.pendulumAngle, - length: value, - }; - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + if (this.dataDoc.simulation_Type == 'Pendulum') { + this.dataDoc.pendulum_angle_adjust = NumCast(this.dataDoc.pendulum_angle); + this.dataDoc.pendulum_length_adjust = value; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; } }} radianEquivalent={false} @@ -1802,7 +1770,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <table> <tbody> <tr> - <td>{this.dataDoc.simulationType == 'Pulley' ? 'Red Weight' : ''}</td> + <td>{this.dataDoc.simulation_Type == 'Pulley' ? 'Red Weight' : ''}</td> <td>X</td> <td>Y</td> </tr> @@ -1817,36 +1785,34 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP > <Box>Position</Box> </td> - {(!this.dataDoc.simulationPaused || this.dataDoc.simulationType == 'Inclined Plane' || this.dataDoc.simulationType == 'Circular Motion' || this.dataDoc.simulationType == 'Pulley') && ( - <td style={{ cursor: 'default' }}>{this.dataDoc.positionXDisplay} m</td> + {(!this.dataDoc.simulation_Paused || this.dataDoc.simulation_Type == 'Inclined Plane' || this.dataDoc.simulation_Type == 'Circular Motion' || this.dataDoc.simulation_Type == 'Pulley') && ( + <td style={{ cursor: 'default' }}>{this.dataDoc.position_XDisplay} m</td> )}{' '} - {this.dataDoc.simulationPaused && this.dataDoc.simulationType != 'Inclined Plane' && this.dataDoc.simulationType != 'Circular Motion' && this.dataDoc.simulationType != 'Pulley' && ( + {this.dataDoc.simulation_Paused && this.dataDoc.simulation_Type != 'Inclined Plane' && this.dataDoc.simulation_Type != 'Circular Motion' && this.dataDoc.simulation_Type != 'Pulley' && ( <td style={{ cursor: 'default', }}> <InputField - lowerBound={this.dataDoc.simulationType == 'Projectile' ? 1 : (this.xMax + this.xMin) / 4 - this.radius - 15} + lowerBound={this.dataDoc.simulation_Type == 'Projectile' ? 1 : (this.xMax + this.xMin) / 4 - this.radius - 15} dataDoc={this.dataDoc} - prop={'positionXDisplay'} + prop="position_XDisplay" step={1} unit={'m'} - upperBound={this.dataDoc.simulationType == 'Projectile' ? this.xMax - 110 : (3 * (this.xMax + this.xMin)) / 4 - this.radius / 2 - 15} - value={this.dataDoc.positionXDisplay} + upperBound={this.dataDoc.simulation_Type == 'Projectile' ? this.xMax - 110 : (3 * (this.xMax + this.xMin)) / 4 - this.radius / 2 - 15} + value={NumCast(this.dataDoc.position_XDisplay)} effect={value => { - this.dataDoc.displayChange = { - xDisplay: value, - yDisplay: this.dataDoc.positionYDisplay, - }; - if (this.dataDoc['simulationType'] == 'Suspension') { + this.dataDoc.display_xChange = value; + this.dataDoc.display_yChange = this.dataDoc.position_YDisplay; + if (this.dataDoc.simulation_Type == 'Suspension') { let x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200; let x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius; let deltaX1 = value + this.radius - x1rod; let deltaX2 = x2rod - (value + this.radius); - let deltaY = this.getYPosFromDisplay(this.dataDoc.positionYDisplay) + this.radius; + let deltaY = this.getYPosFromDisplay(NumCast(this.dataDoc.position_YDisplay)) + this.radius; let dir1T = Math.PI - Math.atan(deltaY / deltaX1); let dir2T = Math.atan(deltaY / deltaX2); - let tensionMag2 = (this.dataDoc.mass * Math.abs(this.dataDoc.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T)); + let tensionMag2 = (NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.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; @@ -1864,11 +1830,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP }; const grav: IForce = { description: 'Gravity', - magnitude: this.dataDoc.mass * Math.abs(this.dataDoc.gravity), + magnitude: NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)), directionInDegrees: 270, component: false, }; - this.dataDoc['updatedForces'] = [tensionForce1, tensionForce2, grav]; + this.dataDoc.updatedForces = [tensionForce1, tensionForce2, grav]; } }} small={true} @@ -1876,10 +1842,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP /> </td> )}{' '} - {(!this.dataDoc.simulationPaused || this.dataDoc.simulationType == 'Inclined Plane' || this.dataDoc.simulationType == 'Circular Motion' || this.dataDoc.simulationType == 'Pulley') && ( - <td style={{ cursor: 'default' }}>{this.dataDoc.positionYDisplay} m</td> + {(!this.dataDoc.simulation_Paused || this.dataDoc.simulation_Type == 'Inclined Plane' || this.dataDoc.simulation_Type == 'Circular Motion' || this.dataDoc.simulation_Type == 'Pulley') && ( + <td style={{ cursor: 'default' }}>{`${NumCast(this.dataDoc.position_YDisplay)} m`}</td> )}{' '} - {this.dataDoc.simulationPaused && this.dataDoc.simulationType != 'Inclined Plane' && this.dataDoc.simulationType != 'Circular Motion' && this.dataDoc.simulationType != 'Pulley' && ( + {this.dataDoc.simulation_Paused && this.dataDoc.simulation_Type != 'Inclined Plane' && this.dataDoc.simulation_Type != 'Circular Motion' && this.dataDoc.simulation_Type != 'Pulley' && ( <td style={{ cursor: 'default', @@ -1887,25 +1853,23 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <InputField lowerBound={1} dataDoc={this.dataDoc} - prop={'positionYDisplay'} + prop="position_YDisplay" step={1} unit={'m'} upperBound={this.yMax - 110} - value={this.dataDoc.positionYDisplay} + value={NumCast(this.dataDoc.position_YDisplay)} effect={value => { - this.dataDoc.displayChange = { - xDisplay: this.dataDoc.positionXDisplay, - yDisplay: value, - }; - if (this.dataDoc['simulationType'] == 'Suspension') { + this.dataDoc.display_xChange = NumCast(this.dataDoc.position_XDisplay); + this.dataDoc.display_yChange = value; + if (this.dataDoc.simulation_Type == 'Suspension') { let x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200; let x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius; - let deltaX1 = this.dataDoc.positionXDisplay + this.radius - x1rod; - let deltaX2 = x2rod - (this.dataDoc.positionXDisplay + this.radius); + let deltaX1 = NumCast(this.dataDoc.position_XDisplay) + this.radius - x1rod; + let deltaX2 = x2rod - (NumCast(this.dataDoc.position_XDisplay) + this.radius); let deltaY = this.getYPosFromDisplay(value) + this.radius; let dir1T = Math.PI - Math.atan(deltaY / deltaX1); let dir2T = Math.atan(deltaY / deltaX2); - let tensionMag2 = (this.dataDoc.mass * Math.abs(this.dataDoc.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T)); + let tensionMag2 = (NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.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; @@ -1923,11 +1887,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP }; const grav: IForce = { description: 'Gravity', - magnitude: this.dataDoc.mass * Math.abs(this.dataDoc.gravity), + magnitude: NumCast(this.dataDoc.mass) * Math.abs(NumCast(this.dataDoc.gravity)), directionInDegrees: 270, component: false, }; - this.dataDoc['updatedForces'] = [tensionForce1, tensionForce2, grav]; + this.dataDoc.updatedForces = [tensionForce1, tensionForce2, grav]; } }} small={true} @@ -1947,10 +1911,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP > <Box>Velocity</Box> </td> - {(!this.dataDoc.simulationPaused || (this.dataDoc.simulationType != 'One Weight' && this.dataDoc.simulationType != 'Circular Motion')) && ( - <td style={{ cursor: 'default' }}>{this.dataDoc.velocityXDisplay} m/s</td> + {(!this.dataDoc.simulation_Paused || (this.dataDoc.simulation_Type != 'One Weight' && this.dataDoc.simulation_Type != 'Circular Motion')) && ( + <td style={{ cursor: 'default' }}>{`${NumCast(this.dataDoc.velocity_XDisplay)} m/s`}</td> )}{' '} - {this.dataDoc.simulationPaused && (this.dataDoc.simulationType == 'One Weight' || this.dataDoc.simulationType == 'Circular Motion') && ( + {this.dataDoc.simulation_Paused && (this.dataDoc.simulation_Type == 'One Weight' || this.dataDoc.simulation_Type == 'Circular Motion') && ( <td style={{ cursor: 'default', @@ -1958,22 +1922,22 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <InputField lowerBound={-50} dataDoc={this.dataDoc} - prop={'velocityXDisplay'} + prop="velocity_XDisplay" step={1} unit={'m/s'} upperBound={50} - value={this.dataDoc.velocityXDisplay} + value={this.dataDoc.velocity_XDisplay} effect={value => { - this.dataDoc.startVelX = value; - this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + this.dataDoc.start_VelX = value; + this.dataDoc.simulation_Reset = !this.dataDoc.simulation_Reset; }} small={true} mode={'Freeform'} /> </td> )}{' '} - {(!this.dataDoc.simulationPaused || this.dataDoc.simulationType != 'One Weight') && <td style={{ cursor: 'default' }}>{this.dataDoc.velocityYDisplay} m/s</td>}{' '} - {this.dataDoc.simulationPaused && this.dataDoc.simulationType == 'One Weight' && ( + {(!this.dataDoc.simulation_Paused || this.dataDoc.simulation_Type != 'One Weight') && <td style={{ cursor: 'default' }}>{this.dataDoc.velocity_YDisplay} m/s</td>}{' '} + {this.dataDoc.simulation_Paused && this.dataDoc.simulation_Type == 'One Weight' && ( <td style={{ cursor: 'default', @@ -1981,17 +1945,15 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <InputField lowerBound={-50} dataDoc={this.dataDoc} - prop={'velocityYDisplay'} + prop="velocity_YDisplay" step={1} unit={'m/s'} upperBound={50} - value={this.dataDoc.velocityYDisplay} + value={this.dataDoc.velocity_YDisplay} effect={value => { - this.dataDoc.startVelY = -value; - this.dataDoc.displayChange = { - xDisplay: this.dataDoc.positionXDisplay, - yDisplay: this.dataDoc.positionYDisplay, - }; + this.dataDoc.start_VelY = -value; + this.dataDoc.display_xChange = this.dataDoc.position_XDisplay; + this.dataDoc.display_yChange = this.dataDoc.position_YDisplay; }} small={true} mode={'Freeform'} @@ -2011,23 +1973,23 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <Box>Acceleration</Box> </td> <td style={{ cursor: 'default' }}> - {this.dataDoc.accelerationXDisplay} m/s<sup>2</sup> + {this.dataDoc.acceleration_XDisplay} m/s<sup>2</sup> </td> <td style={{ cursor: 'default' }}> - {this.dataDoc.accelerationYDisplay} m/s<sup>2</sup> + {this.dataDoc.acceleration_YDisplay} m/s<sup>2</sup> </td> </tr> <tr> <td> <Box>Momentum</Box> </td> - <td>{Math.round(this.dataDoc.velocityXDisplay * this.dataDoc.mass * 10) / 10} kg*m/s</td> - <td>{Math.round(this.dataDoc.velocityYDisplay * this.dataDoc.mass * 10) / 10} kg*m/s</td> + <td>{Math.round(this.dataDoc.velocity_XDisplay * this.dataDoc.mass * 10) / 10} kg*m/s</td> + <td>{Math.round(this.dataDoc.velocity_YDisplay * this.dataDoc.mass * 10) / 10} kg*m/s</td> </tr> </tbody> </table> )} - {this.dataDoc.mode == 'Freeform' && this.dataDoc.simulationType == 'Pulley' && ( + {this.dataDoc.mode == 'Freeform' && this.dataDoc.simulation_Type == 'Pulley' && ( <table> <tbody> <tr> @@ -2039,40 +2001,39 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <td> <Box>Position</Box> </td> - <td style={{ cursor: 'default' }}>{this.dataDoc.positionXDisplay2} m</td> - <td style={{ cursor: 'default' }}>{this.dataDoc.positionYDisplay2} m</td> + <td style={{ cursor: 'default' }}>{`${this.dataDoc.position_XDisplay2} m`}</td> + <td style={{ cursor: 'default' }}>{`${this.dataDoc.position_YDisplay2} m`}</td> </tr> <tr> <td> <Box>Velocity</Box> </td> - <td style={{ cursor: 'default' }}>{this.dataDoc.velocityXDisplay2} m/s</td> - - <td style={{ cursor: 'default' }}>{this.dataDoc.velocityYDisplay2} m/s</td> + <td style={{ cursor: 'default' }}>{`${this.dataDoc.position_XDisplay2} m/s`}</td> + <td style={{ cursor: 'default' }}>{`${this.dataDoc.position_YDisplay2} m/s`}</td> </tr> <tr> <td> <Box>Acceleration</Box> </td> <td style={{ cursor: 'default' }}> - {this.dataDoc.accelerationXDisplay2} m/s<sup>2</sup> + {this.dataDoc.acceleration_XDisplay2} m/s<sup>2</sup> </td> <td style={{ cursor: 'default' }}> - {this.dataDoc.accelerationYDisplay2} m/s<sup>2</sup> + {this.dataDoc.acceleration_YDisplay2} m/s<sup>2</sup> </td> </tr> <tr> <td> <Box>Momentum</Box> </td> - <td>{Math.round(this.dataDoc.velocityXDisplay2 * this.dataDoc.mass * 10) / 10} kg*m/s</td> - <td>{Math.round(this.dataDoc.velocityYDisplay2 * this.dataDoc.mass * 10) / 10} kg*m/s</td> + <td>{Math.round(NumCast(this.dataDoc.velocity_XDisplay2) * NumCast(this.dataDoc.mass) * 10) / 10} kg*m/s</td> + <td>{Math.round(NumCast(this.dataDoc.velocity_YDisplay2) * NumCast(this.dataDoc.mass) * 10) / 10} kg*m/s</td> </tr> </tbody> </table> )} </div> - {this.dataDoc.simulationType != 'Pendulum' && this.dataDoc.simulationType != 'Spring' && ( + {this.dataDoc.simulation_Type != 'Pendulum' && this.dataDoc.simulation_Type != 'Spring' && ( <div> <p>Kinematic Equations</p> <ul> @@ -2089,7 +2050,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </ul> </div> )} - {this.dataDoc.simulationType == 'Spring' && ( + {this.dataDoc.simulation_Type == 'Spring' && ( <div> <p>Harmonic Motion Equations: Spring</p> <ul> @@ -2118,7 +2079,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </ul> </div> )} - {this.dataDoc.simulationType == 'Pendulum' && ( + {this.dataDoc.simulation_Type == 'Pendulum' && ( <div> <p>Harmonic Motion Equations: Pendulum</p> <ul> @@ -2153,7 +2114,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP top: this.yMax - 120 + 40 + 'px', left: this.xMin + 90 - 80 + 'px', }}> - {this.dataDoc.simulationType == 'Circular Motion' ? 'Z' : 'Y'} + {this.dataDoc.simulation_Type == 'Circular Motion' ? 'Z' : 'Y'} </p> <p style={{ diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx index 88af37791..a2883e825 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx @@ -1,1674 +1,1352 @@ -import { Doc } from '../../../../fields/Doc'; +import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc'; import React = require('react'); -import "./PhysicsSimulationBox.scss"; +import './PhysicsSimulationBox.scss'; +import { NumCast } from '../../../../fields/Types'; interface IWallProps { - length: number; - xPos: number; - yPos: number; - angleInDegrees: number; + length: number; + xPos: number; + yPos: number; + angleInDegrees: number; } interface IForce { - description: string; - magnitude: number; - directionInDegrees: number; - component: boolean; + description: string; + magnitude: number; + directionInDegrees: number; + component: boolean; } export interface IWeightProps { - dataDoc: Doc; - layoutDoc: Doc; - adjustPendulumAngle: { angle: number; length: number }; - circularMotionRadius: number; - coefficientOfKineticFriction: number; - color: string; - componentForces: IForce[]; - displayXVelocity: number; - displayYVelocity: number; - elasticCollisions: boolean; - gravity: number; - mass: number; - mode: string; - noMovement: boolean; - paused: boolean; - pendulumAngle: number; - pendulumLength: number; - radius: number; - reset: boolean; - showAcceleration: boolean; - showComponentForces: boolean; - showForceMagnitudes: boolean; - showForces: boolean; - showVelocity: boolean; - simulationSpeed: number; - simulationType: string; - springConstant: number; - springRestLength: number; - springStartLength: number; - startForces: IForce[]; - startPendulumAngle: number; - startPosX: number; - startPosY: number; - startVelX: number; - startVelY: number; - timestepSize: number; - updateDisplay: { xDisplay: number; yDisplay: number }; - updatedForces: IForce[]; - wallPositions: IWallProps[]; - wedgeHeight: number; - wedgeWidth: number; - xMax: number; - xMin: number; - yMax: number; - yMin: number; + dataDoc: Doc; + layoutDoc: Doc; + adjustPendulumAngle: number; + adjustPendulumLength: number; + circularMotionRadius: number; + coefficientOfKineticFriction: number; + color: string; + componentForces: IForce[]; + displayXVelocity: number; + displayYVelocity: number; + elasticCollisions: boolean; + gravity: number; + mass: number; + mode: string; + noMovement: boolean; + paused: boolean; + pendulumAngle: number; + pendulumLength: number; + radius: number; + reset: boolean; + showAcceleration: boolean; + showComponentForces: boolean; + showForceMagnitudes: boolean; + showForces: boolean; + showVelocity: boolean; + simulationSpeed: number; + simulationType: string; + springConstant: number; + springRestLength: number; + springStartLength: number; + startForces: IForce[]; + startPendulumAngle: number; + startPosX: number; + startPosY: number; + startVelX: number; + startVelY: number; + timestepSize: number; + updateXDisplay: number; + updateYDisplay: number; + updatedForces: IForce[]; + wallPositions: IWallProps[]; + wedgeHeight: number; + wedgeWidth: number; + xMax: number; + xMin: number; + yMax: number; + yMin: number; } interface IState { - angleLabel: number, - clickPositionX: number, - clickPositionY: number, - coordinates: string, - dragging: boolean, - kineticFriction: boolean, - maxPosYConservation: number, - timer: number, - updatedStartPosX: any, - updatedStartPosY: any, - walls: IWallProps[], - xPosition: any, - xVelocity: number, - yPosition: any, - yVelocity: number, - xAccel: number, - yAccel: number, + angleLabel: number; + clickPositionX: number; + clickPositionY: number; + coordinates: string; + dragging: boolean; + kineticFriction: boolean; + maxPosYConservation: number; + timer: number; + updatedStartPosX: any; + updatedStartPosY: any; + walls: IWallProps[]; + xPosition: any; + xVelocity: number; + yPosition: any; + yVelocity: number; + xAccel: number; + yAccel: number; } export default class Weight extends React.Component<IWeightProps, IState> { - - constructor(props: any) { - super(props) - this.state = { - angleLabel: 0, - clickPositionX: 0, - clickPositionY: 0, - coordinates: "", - dragging: false, - kineticFriction: false, - maxPosYConservation: 0, - timer: 0, - updatedStartPosX: this.props.startPosX ?? 0, - updatedStartPosY: this.props.startPosY ?? 0, - walls: [], - xPosition: this.props.startPosX ?? 0, - xVelocity: this.props.startVelX ? this.props.startVelX: 0, - yPosition: this.props.startPosY ?? 0, - yVelocity: this.props.startVelY ? this.props.startVelY: 0, - xAccel: 0, - yAccel: 0, + constructor(props: any) { + super(props); + this.state = { + angleLabel: 0, + clickPositionX: 0, + clickPositionY: 0, + coordinates: '', + dragging: false, + kineticFriction: false, + maxPosYConservation: 0, + timer: 0, + updatedStartPosX: this.props.startPosX ?? 0, + updatedStartPosY: this.props.startPosY ?? 0, + walls: [], + xPosition: this.props.startPosX ?? 0, + xVelocity: this.props.startVelX ? this.props.startVelX : 0, + yPosition: this.props.startPosY ?? 0, + yVelocity: this.props.startVelY ? this.props.startVelY : 0, + xAccel: 0, + yAccel: 0, + }; } - } - - componentDidMount() { - // Timer for animating the simulation - setInterval(() => { - this.setState({timer: this.state.timer + 1}); - }, 50); - } - - // Constants - draggable = - this.props.dataDoc['simulationType'] != "Inclined Plane" && - this.props.dataDoc['simulationType'] != "Pendulum" && - this.props.dataDoc['mode'] == "Freeform"; - epsilon = 0.0001; - labelBackgroundColor = `rgba(255,255,255,0.5)`; - - // Variables - weightStyle = { - alignItems: "center", - backgroundColor: this.props.color, - borderColor: "black", - borderRadius: 50 + "%", - borderStyle: "solid", - display: "flex", - height: 2 * this.props.radius + "px", - justifyContent: "center", - left: this.props.startPosX + "px", - position: "absolute" as "absolute", - top: this.props.startPosY + "px", - touchAction: "none", - width: 2 * this.props.radius + "px", - zIndex: 5, - }; - - // Helper function to go between display and real values - getDisplayYPos = (yPos: number) => { - return this.props.yMax - yPos - 2 * this.props.radius + 5; - }; - getYPosFromDisplay = (yDisplay: number) => { - return this.props.yMax - yDisplay - 2 * this.props.radius + 5; - }; - // Set display values based on real values - setYPosDisplay = (yPos: number) => { - const displayPos = this.getDisplayYPos(yPos); - if (this.props.color == 'red') { - this.props.dataDoc['positionYDisplay'] = Math.round(displayPos * 100) / 100 - } else { - this.props.dataDoc['positionYDisplay2'] = Math.round(displayPos * 100) / 100 + componentDidMount() { + // Timer for animating the simulation + setInterval(() => { + this.setState({ timer: this.state.timer + 1 }); + }, 50); } - }; - setXPosDisplay = (xPos: number) => { - if (this.props.color == 'red') { - this.props.dataDoc['positionXDisplay'] = Math.round(xPos * 100) / 100; - } else { - this.props.dataDoc['positionXDisplay2'] = Math.round(xPos * 100) / 100;} - }; - setYVelDisplay = (yVel: number) => { - if (this.props.color == 'red') { - this.props.dataDoc['velocityYDisplay'] = (-1 * Math.round(yVel * 100)) / 100; - } else { - this.props.dataDoc['velocityYDisplay2'] = (-1 * Math.round(yVel * 100)) / 100;} - }; - setXVelDisplay = (xVel: number) => { - if (this.props.color == 'red') { - this.props.dataDoc['velocityXDisplay'] = Math.round(xVel * 100) / 100; - } else { - this.props.dataDoc['velocityXDisplay2'] = Math.round(xVel * 100) / 100;} - }; - // Update display values when simulation updates - setDisplayValues = ( - xPos: number = this.state.xPosition, - yPos: number = this.state.yPosition, - xVel: number = this.state.xVelocity, - yVel: number = this.state.yVelocity - ) => { - this.setYPosDisplay(yPos); - this.setXPosDisplay(xPos); - this.setYVelDisplay(yVel); - this.setXVelDisplay(xVel); - if (this.props.color == 'red') { - this.props.dataDoc['accelerationYDisplay'] = - (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100 - ; - this.props.dataDoc['accelerationXDisplay'] = - Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100 - ; - } else { - this.props.dataDoc['accelerationYDisplay2'] = - (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100 - ; - this.props.dataDoc['accelerationXDisplay2'] = - Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100 - ;} + // Constants + draggable = this.props.dataDoc.simulation_Type != 'Inclined Plane' && this.props.dataDoc.simulation_Type != 'Pendulum' && this.props.dataDoc.mode == 'Freeform'; + epsilon = 0.0001; + labelBackgroundColor = `rgba(255,255,255,0.5)`; - this.setState({xAccel : (Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100)}) - this.setState({yAccel : - (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100 - }); - }; + // Variables + weightStyle = { + alignItems: 'center', + backgroundColor: this.props.color, + borderColor: 'black', + borderRadius: 50 + '%', + borderStyle: 'solid', + display: 'flex', + height: 2 * this.props.radius + 'px', + justifyContent: 'center', + left: this.props.startPosX + 'px', + position: 'absolute' as 'absolute', + top: this.props.startPosY + 'px', + touchAction: 'none', + width: 2 * this.props.radius + 'px', + zIndex: 5, + }; - componentDidUpdate(prevProps: Readonly<IWeightProps>, prevState: Readonly<IState>, snapshot?: any): void { - // Change pendulum angle from input field - if (prevProps.adjustPendulumAngle != this.props.adjustPendulumAngle) { - let length = this.props.adjustPendulumAngle.length; - const x = - length * Math.cos(((90 - this.props.adjustPendulumAngle.angle) * Math.PI) / 180); - const y = - length * Math.sin(((90 - this.props.adjustPendulumAngle.angle) * 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}) - this.props.dataDoc['pendulumAngle'] = this.props.adjustPendulumAngle.angle - this.props.dataDoc['pendulumLength'] = this.props.adjustPendulumAngle.length - } + // Helper function to go between display and real values + getDisplayYPos = (yPos: number) => { + return this.props.yMax - yPos - 2 * this.props.radius + 5; + }; + getYPosFromDisplay = (yDisplay: number) => { + return this.props.yMax - yDisplay - 2 * this.props.radius + 5; + }; - // When display values updated by user, update real values - if (prevProps.updateDisplay != this.props.updateDisplay) { - if (this.props.updateDisplay.xDisplay != this.state.xPosition) { - let x = this.props.updateDisplay.xDisplay; - x = Math.max(0, x); - x = Math.min(x, this.props.xMax - 2 * this.props.radius); - this.setState({updatedStartPosX: x}) - this.setState({xPosition: x}) + // Set display values based on real values + setYPosDisplay = (yPos: number) => { + const displayPos = this.getDisplayYPos(yPos); if (this.props.color == 'red') { - this.props.dataDoc['positionXDisplay'] = x + this.props.dataDoc.position_YDisplay = Math.round(displayPos * 100) / 100; } else { - this.props.dataDoc['positionXDisplay2'] = x + this.props.dataDoc.position_YDisplay2 = Math.round(displayPos * 100) / 100; } - } - - if (this.props.updateDisplay.yDisplay != this.getDisplayYPos(this.state.yPosition)) { - let y = this.props.updateDisplay.yDisplay; - y = Math.max(0, y); - y = Math.min(y, this.props.yMax - 2 * this.props.radius); - let coordinatePosition = this.getYPosFromDisplay(y); - this.setState({updatedStartPosY: coordinatePosition}) - this.setState({yPosition: coordinatePosition}) + }; + setXPosDisplay = (xPos: number) => { if (this.props.color == 'red') { - this.props.dataDoc['positionYDisplay'] = y + this.props.dataDoc.position_XDisplay = Math.round(xPos * 100) / 100; } else { - this.props.dataDoc['positionYDisplay2'] = y + this.props.dataDoc.position_XDisplay2 = Math.round(xPos * 100) / 100; } - } - - if (this.props.displayXVelocity != this.state.xVelocity) { - let x = this.props.displayXVelocity; - this.setState({xVelocity: x}) + }; + setYVelDisplay = (yVel: number) => { if (this.props.color == 'red') { - this.props.dataDoc['velocityXDisplay'] = x + this.props.dataDoc.velocity_YDisplay = (-1 * Math.round(yVel * 100)) / 100; } else { - this.props.dataDoc['velocityXDisplay2'] = x + this.props.dataDoc.velocity_YDisplay2 = (-1 * Math.round(yVel * 100)) / 100; } - } - - if (this.props.displayYVelocity != -this.state.yVelocity) { - let y = this.props.displayYVelocity; - this.setState({yVelocity: -y}) + }; + setXVelDisplay = (xVel: number) => { if (this.props.color == 'red') { - this.props.dataDoc['velocityYDisplay'] = y + this.props.dataDoc.velocity_XDisplay = Math.round(xVel * 100) / 100; } else { - this.props.dataDoc['velocityYDisplay2'] = y + this.props.dataDoc.velocity_XDisplay2 = Math.round(xVel * 100) / 100; } - } - } - - // Prevent bug when switching between sims - if (prevProps.startForces != this.props.startForces) { - this.setState({xVelocity: this.props.startVelX}) - this.setState({yVelocity: this.props.startVelY}) - this.setDisplayValues(); - } + }; - // Make sure weight doesn't go above max height - if ((prevState.updatedStartPosY != this.state.updatedStartPosY || prevProps.startVelY != this.props.startVelY) && !isNaN(this.state.updatedStartPosY) && !isNaN(this.props.startVelY)){ - if (this.props.dataDoc['simulationType'] == "One Weight") { - let maxYPos = this.state.updatedStartPosY; - if (this.props.startVelY != 0) { - maxYPos -= (this.props.startVelY * this.props.startVelY) / (2 * Math.abs(this.props.gravity)); + // Update display values when simulation updates + setDisplayValues = (xPos: number = this.state.xPosition, yPos: number = this.state.yPosition, xVel: number = this.state.xVelocity, yVel: number = this.state.yVelocity) => { + this.setYPosDisplay(yPos); + this.setXPosDisplay(xPos); + this.setYVelDisplay(yVel); + this.setXVelDisplay(xVel); + if (this.props.color == 'red') { + this.props.dataDoc.acceleration_YDisplay = (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100; + this.props.dataDoc.acceleration_XDisplay = Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100; + } else { + this.props.dataDoc.acceleration_YDisplay2 = (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100; + this.props.dataDoc.acceleration_XDisplay2 = Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100; } - if (maxYPos < 0) { - maxYPos = 0; + + this.setState({ xAccel: Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100 }); + this.setState({ yAccel: (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100 }); + }; + + componentDidUpdate(prevProps: Readonly<IWeightProps>, prevState: Readonly<IState>, snapshot?: any): void { + // Change pendulum angle from input field + if (prevProps.adjustPendulumAngle != this.props.adjustPendulumAngle || prevProps.adjustPendulumLength !== this.props.adjustPendulumLength) { + let length = this.props.adjustPendulumLength; + const x = length * Math.cos(((90 - this.props.adjustPendulumAngle) * Math.PI) / 180); + const y = length * Math.sin(((90 - this.props.adjustPendulumAngle) * 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 }); + this.props.dataDoc.pendulum_angle = this.props.adjustPendulumAngle; + this.props.dataDoc.pendulum_length = this.props.adjustPendulumLength; } - 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.dataDoc['simulationType'] == "One Weight" || - this.props.dataDoc['simulationType'] == "Inclined Plane" - ) { - const collisionsWithGround = this.checkForCollisionsWithGround(); - const collisionsWithWalls = this.checkForCollisionsWithWall(); - collisions = collisionsWithGround || collisionsWithWalls; - } - if (this.props.dataDoc['simulationType'] == "Pulley") { - if (this.state.yPosition <= this.props.yMin + 100 || this.state.yPosition >= this.props.yMax - 100) { - collisions = true; + // When display values updated by user, update real value + if (prevProps.updateYDisplay != this.props.updateYDisplay || prevProps.updateXDisplay !== this.props.updateXDisplay) { + if (this.props.updateXDisplay != this.state.xPosition) { + let x = this.props.updateXDisplay; + x = Math.max(0, x); + x = Math.min(x, this.props.xMax - 2 * this.props.radius); + this.setState({ updatedStartPosX: x }); + this.setState({ xPosition: x }); + if (this.props.color == 'red') { + this.props.dataDoc.position_XDisplay = x; + } else { + this.props.dataDoc.position_XDisplay2 = x; + } + } + + if (this.props.updateYDisplay != this.getDisplayYPos(this.state.yPosition)) { + let y = this.props.updateYDisplay; + y = Math.max(0, y); + y = Math.min(y, this.props.yMax - 2 * this.props.radius); + let coordinatePosition = this.getYPosFromDisplay(y); + this.setState({ updatedStartPosY: coordinatePosition }); + this.setState({ yPosition: coordinatePosition }); + if (this.props.color == 'red') { + this.props.dataDoc.position_YDisplay = y; + } else { + this.props.dataDoc.position_YDisplay2 = y; + } + } + + if (this.props.displayXVelocity != this.state.xVelocity) { + let x = this.props.displayXVelocity; + this.setState({ xVelocity: x }); + if (this.props.color == 'red') { + this.props.dataDoc.velocity_XDisplay = x; + } else { + this.props.dataDoc.velocity_XDisplay2 = x; + } + } + + if (this.props.displayYVelocity != -this.state.yVelocity) { + let y = this.props.displayYVelocity; + this.setState({ yVelocity: -y }); + if (this.props.color == 'red') { + this.props.dataDoc.velocity_YDisplay = y; + } else { + this.props.dataDoc.velocity_YDisplay2 = y; + } } - } - if (!collisions) { - this.update(); - } - this.setDisplayValues(); } - } - } - // Reset everything on reset button click - if (prevProps.reset != this.props.reset) { - this.resetEverything(); - } - - // Convert from static to kinetic friction if/when weight slips on inclined plane - if (prevState.xVelocity != this.state.xVelocity) { - if ( - this.props.dataDoc['simulationType'] == "Inclined Plane" && - Math.abs(this.state.xVelocity) > 0.1 && - this.props.dataDoc['mode'] != "Review" && - !this.state.kineticFriction - ) { - this.setState({kineticFriction: true}) - const normalForce: 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: false, - }; - let 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); - 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); + // Prevent bug when switching between sims + if (prevProps.startForces != this.props.startForces) { + this.setState({ xVelocity: this.props.startVelX }); + this.setState({ yVelocity: this.props.startVelY }); + this.setDisplayValues(); } - const frictionForceComponent: IForce = { - description: "Kinetic Friction Force", + // Make sure weight doesn't go above max height + if ((prevState.updatedStartPosY != this.state.updatedStartPosY || prevProps.startVelY != this.props.startVelY) && !isNaN(this.state.updatedStartPosY) && !isNaN(this.props.startVelY)) { + if (this.props.dataDoc.simulation_Type == 'One Weight') { + let maxYPos = this.state.updatedStartPosY; + if (this.props.startVelY != 0) { + maxYPos -= (this.props.startVelY * this.props.startVelY) / (2 * Math.abs(this.props.gravity)); + } + if (maxYPos < 0) { + maxYPos = 0; + } + this.setState({ maxPosYConservation: maxYPos }); + } + } - 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, - }; - if (this.props.coefficientOfKineticFriction != 0) { - this.props.dataDoc['updatedForces'] = ([gravityForce, normalForce, frictionForce]); - this.props.dataDoc['componentForces'] = ([ - frictionForceComponent, - normalForceComponent, - gravityParallel, - gravityPerpendicular, - ]); - } else { - this.props.dataDoc['updatedForces'] = ([gravityForce, normalForce]); - this.props.dataDoc['componentForces'] = ([ - normalForceComponent, - gravityParallel, - gravityPerpendicular, - ]); + // 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.dataDoc.simulation_Type == 'One Weight' || this.props.dataDoc.simulation_Type == 'Inclined Plane') { + const collisionsWithGround = this.checkForCollisionsWithGround(); + const collisionsWithWalls = this.checkForCollisionsWithWall(); + collisions = collisionsWithGround || collisionsWithWalls; + } + if (this.props.dataDoc.simulation_Type == 'Pulley') { + if (this.state.yPosition <= this.props.yMin + 100 || this.state.yPosition >= this.props.yMax - 100) { + collisions = true; + } + } + if (!collisions) { + this.update(); + } + this.setDisplayValues(); + } + } } - } - } - // Add/remove walls when simulation type changes - if (prevProps.simulationType != this.props.simulationType) { - let w: IWallProps[] = []; - if (this.props.dataDoc['simulationType'] == "One Weight" || this.props.dataDoc['simulationType'] == "Inclined Plane") { - w = this.props.wallPositions - } - this.setState({walls: w}) - } + // Reset everything on reset button click + if (prevProps.reset != this.props.reset) { + this.resetEverything(); + } - // 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.setXPosDisplay(this.props.startPosX) - } - } + // Convert from static to kinetic friction if/when weight slips on inclined plane + if (prevState.xVelocity != this.state.xVelocity) { + if (this.props.dataDoc.simulation_Type == 'Inclined Plane' && Math.abs(this.state.xVelocity) > 0.1 && this.props.dataDoc.mode != 'Review' && !this.state.kineticFriction) { + this.setState({ kineticFriction: true }); + const normalForce: 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: false, + }; + let 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); + 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); + } - // 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.setYPosDisplay(this.props.startPosY ?? 0) - } - } + const frictionForceComponent: IForce = { + description: 'Kinetic Friction Force', - // Update wedge coordinates - if (prevProps.wedgeWidth != this.props.wedgeWidth || prevProps.wedgeHeight != this.props.wedgeHeight) { - const left = this.props.xMax * 0.25; - const coordinatePair1 = Math.round(left) + "," + this.props.yMax + " "; - const coordinatePair2 = Math.round(left + this.props.wedgeWidth) + "," + this.props.yMax + " "; - const coordinatePair3 = Math.round(left) + "," + (this.props.yMax - this.props.wedgeHeight); - const coord = coordinatePair1 + coordinatePair2 + coordinatePair3; - this.setState({coordinates: coord}) - } + 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, + }; + if (this.props.coefficientOfKineticFriction != 0) { + this.props.dataDoc.updatedForces = [gravityForce, normalForce, frictionForce]; + this.props.dataDoc.componentForces = [frictionForceComponent, normalForceComponent, gravityParallel, gravityPerpendicular]; + } else { + this.props.dataDoc.updatedForces = [gravityForce, normalForce]; + this.props.dataDoc.componentForces = [normalForceComponent, gravityParallel, gravityPerpendicular]; + } + } + } - if (this.state.xPosition != prevState.xPosition || this.state.yPosition != prevState.yPosition) { - this.weightStyle = { - alignItems: "center", - backgroundColor: this.props.color, - borderColor: "black", - borderRadius: 50 + "%", - borderStyle: "solid", - display: "flex", - height: 2 * this.props.radius + "px", - justifyContent: "center", - left: this.state.xPosition + "px", - position: "absolute" as "absolute", - top: this.state.yPosition + "px", - touchAction: "none", - width: 2 * this.props.radius + "px", - zIndex: 5, - }; - } - } + // Add/remove walls when simulation type changes + if (prevProps.simulationType != this.props.simulationType) { + let w: IWallProps[] = []; + if (this.props.dataDoc.simulation_Type == 'One Weight' || this.props.dataDoc.simulation_Type == 'Inclined Plane') { + w = this.props.wallPositions; + } + this.setState({ walls: w }); + } - // 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 ?? 0}) - this.setState({yVelocity: this.props.startVelY ?? 0}) - this.props.dataDoc['pendulumAngle'] = this.props.dataDoc['startPendulumAngle'] - this.props.dataDoc['updatedForces'] = (this.props.dataDoc['startForces']) - this.props.dataDoc['updatedForces2'] = (this.props.dataDoc['startForces2']) - if (this.props.color == 'red') { - this.props.dataDoc['positionXDisplay'] = this.state.updatedStartPosX - this.props.dataDoc['positionYDisplay'] = this.state.updatedStartPosY - this.props.dataDoc['velocityXDisplay'] = this.props.startVelX ?? 0 - this.props.dataDoc['velocityYDisplay'] = this.props.startVelY ?? 0 - this.props.dataDoc['accelerationXDisplay'] = 0 - this.props.dataDoc['accelerationYDisplay'] = 0 - } else { - this.props.dataDoc['positionXDisplay2'] = this.state.updatedStartPosX - this.props.dataDoc['positionYDisplay2'] = this.state.updatedStartPosY - this.props.dataDoc['velocityXDisplay2'] = this.props.startVelX ?? 0 - this.props.dataDoc['velocityYDisplay2'] = this.props.startVelY ?? 0 - this.props.dataDoc['accelerationXDisplay2'] = 0 - this.props.dataDoc['accelerationYDisplay2'] = 0 - } - this.setState({angleLabel: Math.round(this.props.dataDoc['pendulumAngle'] ?? 0 * 100) / 100}) - }; + // 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.setXPosDisplay(this.props.startPosX); + } + } - // 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; - }; + // 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.setYPosDisplay(this.props.startPosY ?? 0); + } + } + // Update wedge coordinates + if (this.state.coordinates === '' || prevProps.wedgeWidth != this.props.wedgeWidth || prevProps.wedgeHeight != this.props.wedgeHeight) { + const left = this.props.xMax * 0.25; + const coordinatePair1 = Math.round(left) + ',' + this.props.yMax + ' '; + const coordinatePair2 = Math.round(left + this.props.wedgeWidth) + ',' + this.props.yMax + ' '; + const coordinatePair3 = Math.round(left) + ',' + (this.props.yMax - this.props.wedgeHeight); + const coord = coordinatePair1 + coordinatePair2 + coordinatePair3; + this.setState({ coordinates: coord }); + } - // 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; - }); + if (this.state.xPosition != prevState.xPosition || this.state.yPosition != prevState.yPosition) { + this.weightStyle = { + alignItems: 'center', + backgroundColor: this.props.color, + borderColor: 'black', + borderRadius: 50 + '%', + borderStyle: 'solid', + display: 'flex', + height: 2 * this.props.radius + 'px', + justifyContent: 'center', + left: this.state.xPosition + 'px', + position: 'absolute' as 'absolute', + top: this.state.yPosition + 'px', + touchAction: 'none', + width: 2 * this.props.radius + 'px', + zIndex: 5, + }; + } } - return newYAcc; - }; - // 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, + // 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 ?? 0 }); + this.setState({ yVelocity: this.props.startVelY ?? 0 }); + this.props.dataDoc.pendulum_angle = this.props.dataDoc.start_PendulumAngle; + this.props.dataDoc.updatedForces = this.props.dataDoc.start_Forces; + this.props.dataDoc.updatedForces2 = this.props.dataDoc.start_Forces2; + if (this.props.color == 'red') { + this.props.dataDoc.position_XDisplay = this.state.updatedStartPosX; + this.props.dataDoc.position_YDisplay = this.state.updatedStartPosY; + this.props.dataDoc.velocity_XDisplay = this.props.startVelX ?? 0; + this.props.dataDoc.velocity_YDisplay = this.props.startVelY ?? 0; + this.props.dataDoc.acceleration_XDisplay = 0; + this.props.dataDoc.acceleration_YDisplay = 0; + } else { + this.props.dataDoc.position_XDisplay2 = this.state.updatedStartPosX; + this.props.dataDoc.position_YDisplay2 = this.state.updatedStartPosY; + this.props.dataDoc.velocity_XDisplay2 = this.props.startVelX ?? 0; + this.props.dataDoc.velocity_YDisplay2 = this.props.startVelY ?? 0; + this.props.dataDoc.acceleration_XDisplay2 = 0; + this.props.dataDoc.acceleration_YDisplay2 = 0; + } + this.setState({ angleLabel: Math.round(NumCast(this.props.dataDoc.pendulum_angle) ?? 0 * 100) / 100 }); }; - return [tensionForce]; - }; - // Compute spring forces given y position - getNewSpringForces = (yPos: number) => { - let springForce: IForce = { - description: "Spring Force", - magnitude: 0, - directionInDegrees: 90, - component: false, + // 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; }; - 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, - }; - } - return [ - { - description: "Gravity", - magnitude: Math.abs(this.props.gravity) * this.props.mass, - directionInDegrees: 270, - component: false, - }, - springForce, - ]; - }; - - // Compute pendulum forces given position, velocity - getNewPendulumForces = ( - xPos: number, - yPos: number, - xVel: number, - yVel: number - ) => { - 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; - } - let oppositeAngle = 90 - angle; - if (oppositeAngle < 0) { - oppositeAngle = 90 - (180 - angle); - } - - const pendulumLength = Math.sqrt(x * x + y * y); - this.props.dataDoc['pendulumAngle'] = oppositeAngle; - 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, + // 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; }; - return [ - { - description: "Gravity", - magnitude: Math.abs(this.props.gravity) * this.props.mass, - directionInDegrees: 270, - component: false, - }, - forceOfTension, - ]; - }; + // 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]; + }; - // 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) { - const wallX = (wall.xPos / 100) * this.props.layoutDoc._width; - 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; - } - } + // 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, + }; } - }); - } - return collision; - }; + return [ + { + description: 'Gravity', + magnitude: Math.abs(this.props.gravity) * this.props.mass, + directionInDegrees: 270, + component: false, + }, + springForce, + ]; + }; - // Check for collisions in y direction - checkForCollisionsWithGround = () => { - let collision = false; - const minY = this.state.yPosition; - const maxY = this.state.yPosition + 2 * this.props.radius; - if (this.state.yVelocity > 0) { - this.state.walls.forEach((wall) => { - if (wall.angleInDegrees == 0 && wall.yPos > 0.4) { - const groundY = (wall.yPos / 100) * this.props.layoutDoc._height; - 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.dataDoc['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.dataDoc['updatedForces'] = ([forceOfGravity, normalForce]); - if (this.props.dataDoc['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.dataDoc['componentForces'] = ([forceOfGravityC, normalForceC]); - } - } - } - collision = true; - } + // Compute pendulum forces given position, velocity + getNewPendulumForces = (xPos: number, yPos: number, xVel: number, yVel: number) => { + 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; } - }); - } - if (this.state.yVelocity < 0) { - this.state.walls.forEach((wall) => { - if (wall.angleInDegrees == 0 && wall.yPos < 0.4) { - const groundY = (wall.yPos / 100) * this.props.layoutDoc._height; - if (minY < groundY) { - this.setState({yPosition: groundY + 0.01}); - if (this.props.elasticCollisions) { - this.setState({yVelocity: -this.state.yVelocity}); - } else { - this.setState({yVelocity: 0}); - } - collision = true; - } + let oppositeAngle = 90 - angle; + if (oppositeAngle < 0) { + oppositeAngle = 90 - (180 - angle); } - }); - } - return collision; - }; - // 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.updatedForces; - if (this.props.dataDoc['simulationType'] == "Pendulum") { - forces = this.getNewPendulumForces(newXPos, newYPos, newXVel, newYVel); - } else if (this.props.dataDoc['simulationType'] == "Spring") { - forces = this.getNewSpringForces(newYPos); - } else if (this.props.dataDoc['simulationType'] == "Circular Motion") { - forces = this.getNewCircularMotionForces(newXPos, newYPos); - } - const newDeltaXVel = this.getNewAccelerationX(forces); - const newDeltaYVel = this.getNewAccelerationY(forces); - return { - xPos: newXPos, - yPos: newYPos, - xVel: newXVel, - yVel: newYVel, - deltaXPos: newDeltaXPos, - deltaYPos: newDeltaYPos, - deltaXVel: newDeltaXVel, - deltaYVel: newDeltaYVel, + const pendulumLength = Math.sqrt(x * x + y * y); + this.props.dataDoc.pendulum_angle = oppositeAngle; + + 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', + magnitude: Math.abs(this.props.gravity) * this.props.mass, + directionInDegrees: 270, + component: false, + }, + forceOfTension, + ]; }; - }; - // Update position, velocity using RK4 method - update = () => { - let startXVel = this.state.xVelocity; - let 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: IForce[] = this.props.dataDoc['updatedForces']; - if (this.props.dataDoc['simulationType'] == "Pendulum") { - forces = this.getNewPendulumForces(xPos, yPos, xVel, yVel); - } else if (this.props.dataDoc['simulationType'] == "Spring") { - forces = this.getNewSpringForces(yPos); - } else if (this.props.dataDoc['simulationType'] == "Circular Motion") { - forces = this.getNewCircularMotionForces(xPos, yPos); - } - const xAcc = this.getNewAccelerationX(forces); - const yAcc = this.getNewAccelerationY(forces); - for (let i = 0; i < this.props.simulationSpeed; i++) { - const k1 = this.evaluate(xPos, yPos, xVel, yVel, xVel, yVel, xAcc, yAcc, 0); - const k2 = this.evaluate( - xPos, - yPos, - xVel, - yVel, - k1.deltaXPos, - k1.deltaYPos, - k1.deltaXVel, - k1.deltaYVel, - this.props.timestepSize * 0.5 - ); - const k3 = this.evaluate( - xPos, - yPos, - xVel, - yVel, - k2.deltaXPos, - k2.deltaYPos, - k2.deltaXVel, - k2.deltaYVel, - this.props.timestepSize * 0.5 - ); - const k4 = this.evaluate( - xPos, - yPos, - xVel, - yVel, - k3.deltaXPos, - k3.deltaYPos, - k3.deltaXVel, - k3.deltaYVel, - this.props.timestepSize - ); + // 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) { + const wallX = (wall.xPos / 100) * this.props.layoutDoc[WidthSym](); + 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; + } + } + } + }); + } + return collision; + }; - xVel += - ((this.props.timestepSize * 1.0) / 6.0) * - (k1.deltaXVel + 2 * (k2.deltaXVel + k3.deltaXVel) + k4.deltaXVel); - yVel += - ((this.props.timestepSize * 1.0) / 6.0) * - (k1.deltaYVel + 2 * (k2.deltaYVel + k3.deltaYVel) + k4.deltaYVel); - xPos += - ((this.props.timestepSize * 1.0) / 6.0) * - (k1.deltaXPos + 2 * (k2.deltaXPos + k3.deltaXPos) + k4.deltaXPos); - 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.dataDoc['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.dataDoc['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; + // Check for collisions in y direction + checkForCollisionsWithGround = () => { + let collision = false; + const minY = this.state.yPosition; + const maxY = this.state.yPosition + 2 * this.props.radius; + if (this.state.yVelocity > 0) { + this.state.walls.forEach(wall => { + if (wall.angleInDegrees == 0 && wall.yPos > 0.4) { + const groundY = (wall.yPos / 100) * this.props.layoutDoc[HeightSym](); + 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.dataDoc.simulation_Type != '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.dataDoc.updatedForces = [forceOfGravity, normalForce]; + if (this.props.dataDoc.simulation_Type == '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.dataDoc.componentForces = [forceOfGravityC, normalForceC]; + } + } + } + collision = true; + } + } + }); } - 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; + if (this.state.yVelocity < 0) { + this.state.walls.forEach(wall => { + if (wall.angleInDegrees == 0 && wall.yPos < 0.4) { + const groundY = (wall.yPos / 100) * this.props.layoutDoc[HeightSym](); + if (minY < groundY) { + this.setState({ yPosition: groundY + 0.01 }); + if (this.props.elasticCollisions) { + this.setState({ yVelocity: -this.state.yVelocity }); + } else { + this.setState({ yVelocity: 0 }); + } + collision = true; + } + } + }); } - yPos = this.props.startPosY; - } - } - if (this.props.dataDoc['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.dataDoc['updatedForces'] - if (this.props.dataDoc['simulationType'] == "Pendulum") { - forcesn = this.getNewPendulumForces(xPos, yPos, xVel, yVel); - } else if (this.props.dataDoc['simulationType'] == "Spring") { - forcesn = this.getNewSpringForces(yPos); - } else if (this.props.dataDoc['simulationType'] == "Circular Motion") { - forcesn = this.getNewCircularMotionForces(xPos, yPos); - } - this.props.dataDoc['updatedForces'] = (forcesn); - - // set component forces if they change - if (this.props.dataDoc['simulationType'] == "Pendulum") { - let x = this.props.xMax / 2 - xPos - this.props.radius; - let y = yPos + this.props.radius + 5; - let angle = (Math.atan(y / x) * 180) / Math.PI; - if (angle < 0) { - angle += 180; - } - let oppositeAngle = 90 - angle; - if (oppositeAngle < 0) { - oppositeAngle = 90 - (180 - angle); - } - - const pendulumLength = Math.sqrt(x * x + y * y); + return collision; + }; - const mag = - this.props.mass * Math.abs(this.props.gravity) * Math.cos((oppositeAngle * Math.PI) / 180) + - (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength; + // 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.updatedForces; + if (this.props.dataDoc.simulation_Type == 'Pendulum') { + forces = this.getNewPendulumForces(newXPos, newYPos, newXVel, newYVel); + } else if (this.props.dataDoc.simulation_Type == 'Spring') { + forces = this.getNewSpringForces(newYPos); + } else if (this.props.dataDoc.simulation_Type == 'Circular Motion') { + forces = this.getNewCircularMotionForces(newXPos, newYPos); + } + const newDeltaXVel = this.getNewAccelerationX(forces); + const newDeltaYVel = this.getNewAccelerationY(forces); + return { + xPos: newXPos, + yPos: newYPos, + xVel: newXVel, + yVel: newYVel, + deltaXPos: newDeltaXPos, + deltaYPos: newDeltaYPos, + deltaXVel: newDeltaXVel, + deltaYVel: newDeltaYVel, + }; + }; - const tensionComponent: IForce = { - 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) - ); - gravityPerpendicular.directionInDegrees = 180 - (90 - angle); - } - this.props.dataDoc['componentForces'] = ([ - tensionComponent, - gravityParallel, - gravityPerpendicular, - ]); - } - }; + // Update position, velocity using RK4 method + update = () => { + let startXVel = this.state.xVelocity; + let 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: IForce[] = this.props.dataDoc.updatedForces; + if (this.props.dataDoc.simulation_Type == 'Pendulum') { + forces = this.getNewPendulumForces(xPos, yPos, xVel, yVel); + } else if (this.props.dataDoc.simulation_Type == 'Spring') { + forces = this.getNewSpringForces(yPos); + } else if (this.props.dataDoc.simulation_Type == 'Circular Motion') { + forces = this.getNewCircularMotionForces(xPos, yPos); + } + const xAcc = this.getNewAccelerationX(forces); + const yAcc = this.getNewAccelerationY(forces); + for (let i = 0; i < this.props.simulationSpeed; i++) { + const k1 = this.evaluate(xPos, yPos, xVel, yVel, xVel, yVel, xAcc, yAcc, 0); + const k2 = this.evaluate(xPos, yPos, xVel, yVel, k1.deltaXPos, k1.deltaYPos, k1.deltaXVel, k1.deltaYVel, this.props.timestepSize * 0.5); + const k3 = this.evaluate(xPos, yPos, xVel, yVel, k2.deltaXPos, k2.deltaYPos, k2.deltaXVel, k2.deltaYVel, this.props.timestepSize * 0.5); + const k4 = this.evaluate(xPos, yPos, xVel, yVel, k3.deltaXPos, k3.deltaYPos, k3.deltaXVel, k3.deltaYVel, this.props.timestepSize); - // Render weight, spring, rod(s), vectors - render () { - return ( - <div> - <div - className="weightContainer" - onPointerDown={(e) => { - if (this.draggable) { - this.props.dataDoc['paused'] = true; - this.setState({dragging: true}); - this.setState({clickPositionX: e.clientX}); - this.setState({clickPositionY: e.clientY}); - } - }} - onPointerMove={(e) => { - if (this.state.dragging) { - let newY = this.state.yPosition + e.clientY - this.state.clickPositionY; - if (newY > this.props.yMax - 2 * this.props.radius - 10) { - newY = this.props.yMax - 2 * this.props.radius - 10; - } else if (newY < 10) { - newY = 10; + xVel += ((this.props.timestepSize * 1.0) / 6.0) * (k1.deltaXVel + 2 * (k2.deltaXVel + k3.deltaXVel) + k4.deltaXVel); + yVel += ((this.props.timestepSize * 1.0) / 6.0) * (k1.deltaYVel + 2 * (k2.deltaYVel + k3.deltaYVel) + k4.deltaYVel); + xPos += ((this.props.timestepSize * 1.0) / 6.0) * (k1.deltaXPos + 2 * (k2.deltaXPos + k3.deltaXPos) + k4.deltaXPos); + 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.dataDoc.simulation_Type == '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; } - - let newX = this.state.xPosition + e.clientX - this.state.clickPositionX; - if (newX > this.props.xMax - 2 * this.props.radius - 10) { - newX = this.props.xMax - 2 * this.props.radius - 10; - } else if (newX < 10) { - newX = 10; + } + if (this.props.dataDoc.simulation_Type == '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; + } + 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; } - if (this.props.dataDoc['simulationType'] == "Suspension") { - if (newX < (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15) { - newX = (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15; - } else if (newX > (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15) { - newX = (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15; - } + } + if (this.props.dataDoc.simulation_Type == '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.dataDoc.updatedForces; + if (this.props.dataDoc.simulation_Type == 'Pendulum') { + forcesn = this.getNewPendulumForces(xPos, yPos, xVel, yVel); + } else if (this.props.dataDoc.simulation_Type == 'Spring') { + forcesn = this.getNewSpringForces(yPos); + } else if (this.props.dataDoc.simulation_Type == 'Circular Motion') { + forcesn = this.getNewCircularMotionForces(xPos, yPos); + } + this.props.dataDoc.updatedForces = forcesn; - this.setState({yPosition: newY}); - this.props.dataDoc['positionYDisplay'] = - Math.round((this.props.yMax - 2 * this.props.radius - newY + 5) * 100) / 100 - if (this.props.dataDoc['simulationType'] != "Pulley") { - this.setState({xPosition: newX}); - this.props.dataDoc['positionXDisplay'] = newX - } - if (this.props.dataDoc['simulationType'] != "Suspension") { - if (this.props.dataDoc['simulationType'] != "Pulley") { - this.setState({updatedStartPosX: newX}); - } - this.setState({updatedStartPosY: newY}); - } - this.setState({clickPositionX: e.clientX}); - this.setState({clickPositionY: e.clientY}); - this.setDisplayValues(); - } - }} - onPointerUp={(e) => { - if (this.state.dragging) { - if ( - this.props.dataDoc['simulationType'] != "Pendulum" && - this.props.dataDoc['simulationType'] != "Suspension" - ) { - this.resetEverything(); + // set component forces if they change + if (this.props.dataDoc.simulation_Type == 'Pendulum') { + let x = this.props.xMax / 2 - xPos - this.props.radius; + let y = yPos + this.props.radius + 5; + let angle = (Math.atan(y / x) * 180) / Math.PI; + if (angle < 0) { + angle += 180; } - this.setState({dragging: false}); - let newY = this.state.yPosition + e.clientY - this.state.clickPositionY; - if (newY > this.props.yMax - 2 * this.props.radius - 10) { - newY = this.props.yMax - 2 * this.props.radius - 10; - } else if (newY < 10) { - newY = 10; + let oppositeAngle = 90 - angle; + if (oppositeAngle < 0) { + oppositeAngle = 90 - (180 - angle); } - let newX = this.state.xPosition + e.clientX - this.state.clickPositionX; - if (newX > this.props.xMax - 2 * this.props.radius - 10) { - newX = this.props.xMax - 2 * this.props.radius - 10; - } else if (newX < 10) { - newX = 10; - } - if (this.props.dataDoc['simulationType'] == "Spring") { - this.props.dataDoc.springStartLength = newY - } - if (this.props.dataDoc['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 tensionForce1: IForce = { - description: "Tension", - magnitude: tensionMag1, - directionInDegrees: dir1T, - component: false, - }; - const tensionForce2: IForce = { - description: "Tension", - magnitude: tensionMag2, - directionInDegrees: dir2T, - component: false, - }; - const grav: IForce = { - description: "Gravity", - magnitude: this.props.mass * Math.abs(this.props.gravity), - directionInDegrees: 270, - component: false, - }; - this.props.dataDoc['updatedForces'] = ([tensionForce1, tensionForce2, grav]); + const pendulumLength = Math.sqrt(x * x + y * y); + + 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 tensionComponent: IForce = { + 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)); + gravityPerpendicular.directionInDegrees = 180 - (90 - angle); } - } - }} - > - <div className="weight" style={this.weightStyle}> - <p className="weightLabel">{this.props.mass} kg</p> - </div> - </div> - {this.props.dataDoc['simulationType'] == "Spring" && ( - <div - className="spring" - style={{ - pointerEvents: "none", - position: "absolute", - left: 0, - top: 0, - }} - > - <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "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; - return ( - <line - key={val} - x1={xPos1} - y1={yPos1} - x2={xPos2} - y2={yPos2} - stroke={"#808080"} - strokeWidth="10" - /> - ); - })} - </svg> - </div> - )} - - {this.props.dataDoc['simulationType'] == "Pulley" && ( - <div - className="rod" - style={{ - pointerEvents: "none", - position: "absolute", - left: 0, - top: 0, - }} - > - <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}> - <line - x1={this.state.xPosition + this.props.radius} - y1={this.state.yPosition + this.props.radius} - x2={this.state.xPosition + this.props.radius} - y2={this.props.yMin} - stroke={"#deb887"} - strokeWidth="10" - /> - </svg> - </div> - )} - {this.props.dataDoc['simulationType'] == "Pulley" && ( - <div - className="wheel" - style={{ - pointerEvents: "none", - position: "absolute", - left: 0, - top: 0, - }} - > - <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}> - <circle - cx={(this.props.xMax + this.props.xMin) / 2} - cy={this.props.radius} - r={this.props.radius * 1.5} - fill={"#808080"} - /> - </svg> - </div> - )} - {this.props.dataDoc['simulationType'] == "Suspension" && ( - <div - className="rod" - style={{ - pointerEvents: "none", - position: "absolute", - left: 0, - top: 0, - }} - > - <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}> - <line - x1={this.state.xPosition + this.props.radius} - y1={this.state.yPosition + this.props.radius} - x2={(this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200} - y2={this.props.yMin} - stroke={"#deb887"} - strokeWidth="10" - /> - </svg> - <p - style={{ - position: "absolute", - left: (this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200 + 80 + "px", - top: 10 + "px", - backgroundColor: this.labelBackgroundColor, - }} - > - {Math.round( - ((Math.atan( - (this.state.yPosition + this.props.radius) / - (this.state.xPosition + - this.props.radius - - ((this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200)) - ) * - 180) / - Math.PI) * - 100 - ) / 100} - ° - </p> - <svg width={this.props.layoutDoc._width + "px"} height={this.props.layoutDoc._height + "px"}> - <line - x1={this.state.xPosition + this.props.radius} - y1={this.state.yPosition + this.props.radius} - x2={(this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius} - y2={this.props.yMin} - stroke={"#deb887"} - strokeWidth="10" - /> - </svg> + this.props.dataDoc.componentForces = [tensionComponent, gravityParallel, gravityPerpendicular]; + } + }; - <p - style={{ - position: "absolute", - left: (this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius - 80 + "px", - top: 10 + "px", - backgroundColor: this.labelBackgroundColor, - }} - > - {Math.round( - ((Math.atan( - (this.state.yPosition + this.props.radius) / - ((this.props.xMax + this.props.xMin) / 2 + - this.props.yMin + - 200 + - this.props.radius - - (this.state.xPosition + this.props.radius)) - ) * - 180) / - Math.PI) * - 100 - ) / 100} - ° - </p> - </div> - )} - {this.props.dataDoc['simulationType'] == "Circular Motion" && ( - <div - className="rod" - style={{ - pointerEvents: "none", - position: "absolute", - left: 0, - top: 0, - }} - > - <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}> - <line - x1={this.state.xPosition + this.props.radius} - y1={this.state.yPosition + this.props.radius} - x2={(this.props.xMin + this.props.xMax) / 2} - y2={(this.props.yMin + this.props.yMax) / 2} - stroke={"#deb887"} - strokeWidth="10" - /> - </svg> - </div> - )} - {this.props.dataDoc['simulationType'] == "Pendulum" && ( - <div - className="rod" - style={{ - pointerEvents: "none", - position: "absolute", - left: 0, - top: 0, - }} - > - <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}> - <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> - {!this.state.dragging && ( + // Render weight, spring, rod(s), vectors + render() { + return ( <div> - <p - style={{ - position: "absolute", - zIndex: 5, - left: this.state.xPosition + "px", - top: this.state.yPosition - 70 + "px", - backgroundColor: this.labelBackgroundColor, - }} - > - {Math.round(this.props.pendulumLength)} m - </p> - <p - style={{ - position: "absolute", - left: this.props.xMax / 2 + "px", - top: 30 + "px", - backgroundColor: this.labelBackgroundColor, - }} - > - {Math.round(this.props.pendulumAngle * 100) / 100}° - </p> - </div> - )} - </div> - )} - {this.props.dataDoc['simulationType'] == "Inclined Plane" && ( - <div> - <div - style={{ position: "absolute", left: "0", top: "0" }} - > - <svg width={this.props.xMax + "px"} height={this.props.yMax + "px"}> - <polygon points={this.state.coordinates} style={{ fill: "burlywood" }} /> - </svg> - </div> - - <p - style={{ - position: "absolute", - left: Math.round(this.props.xMax * 0.5 - 200 + this.props.wedgeWidth - 80) + "px", - top: Math.round(this.props.yMax - 40) + "px", - }} - > - {Math.round( - ((Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI) * 100 - ) / 100} - ° - </p> - </div> - )} - {!this.state.dragging && this.props.showAcceleration && ( - <div> - <div - style={{ - pointerEvents: "none", - position: "absolute", - left: 0, - top: 0, - }} - > - <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}> - <defs> - <marker - id="accArrow" - markerWidth="10" - markerHeight="10" - refX="0" - refY="3" - orient="auto" - markerUnits="strokeWidth" - > - <path d="M0,0 L0,6 L9,3 z" fill="green" /> - </marker> - </defs> - <line - x1={this.state.xPosition + this.props.radius} - y1={this.state.yPosition + this.props.radius} - x2={this.state.xPosition + this.props.radius + this.getNewAccelerationX(this.props.updatedForces) * 15} - y2={this.state.yPosition + this.props.radius + this.getNewAccelerationY(this.props.updatedForces) * 15} - stroke={"green"} - strokeWidth="5" - markerEnd="url(#accArrow)" - /> - </svg> - <div - style={{ - pointerEvents: "none", - position: "absolute", - left: this.state.xPosition + this.props.radius + this.state.xAccel * 15 + 25 + "px", - top: this.state.yPosition + this.props.radius + this.state.yAccel * 15 + 70 + "px", - lineHeight: 1, - }} - > - <p> - {Math.round( - 100 * Math.sqrt(this.state.xAccel * this.state.xAccel + this.state.yAccel * this.state.yAccel) - ) / 100}{" "} - m/s - <sup>2</sup> - </p> - </div> - </div> - </div> - )} - {!this.state.dragging && this.props.showVelocity && ( - <div> - <div - style={{ - pointerEvents: "none", - position: "absolute", - left: 0, - top: 0, - }} - > - <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}> - <defs> - <marker - id="velArrow" - markerWidth="10" - markerHeight="10" - refX="0" - refY="3" - orient="auto" - markerUnits="strokeWidth" - > - <path d="M0,0 L0,6 L9,3 z" fill="blue" /> - </marker> - </defs> - <line - x1={this.state.xPosition + this.props.radius} - y1={this.state.yPosition + this.props.radius} - x2={this.state.xPosition + this.props.radius + this.state.xVelocity * 7} - y2={this.state.yPosition + this.props.radius + this.state.yVelocity * 7} - stroke={"blue"} - strokeWidth="5" - markerEnd="url(#velArrow)" - /> - </svg> - <div - style={{ - pointerEvents: "none", - position: "absolute", - left: this.state.xPosition + this.props.radius + this.state.xVelocity * 7 + 25 + "px", - top: this.state.yPosition + this.props.radius + this.state.yVelocity * 7 + "px", - lineHeight: 1, - }} - > - <p> - {Math.round( - 100 * - Math.sqrt( - this.props.displayXVelocity * this.props.displayXVelocity + - this.props.displayYVelocity * this.props.displayYVelocity - ) - ) / 100}{" "} - m/s - </p> - </div> - </div> - </div> - )} - {!this.state.dragging && - this.props.showComponentForces && - this.props.componentForces.map((force, index) => { - if (force.magnitude < this.epsilon) { - return; - } - let 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 arrowEndX: number = - arrowStartX + - Math.abs(force.magnitude) * - 20 * - Math.cos((force.directionInDegrees * Math.PI) / 180); + <div + className="weightContainer" + onPointerDown={e => { + if (this.draggable) { + this.props.dataDoc.paused = true; + this.setState({ dragging: true }); + this.setState({ clickPositionX: e.clientX }); + this.setState({ clickPositionY: e.clientY }); + } + }} + onPointerMove={e => { + if (this.state.dragging) { + let newY = this.state.yPosition + e.clientY - this.state.clickPositionY; + if (newY > this.props.yMax - 2 * this.props.radius - 10) { + newY = this.props.yMax - 2 * this.props.radius - 10; + } else if (newY < 10) { + newY = 10; + } - let color = "#0d0d0d"; + let newX = this.state.xPosition + e.clientX - this.state.clickPositionX; + if (newX > this.props.xMax - 2 * this.props.radius - 10) { + newX = this.props.xMax - 2 * this.props.radius - 10; + } else if (newX < 10) { + newX = 10; + } + if (this.props.dataDoc.simulation_Type == 'Suspension') { + if (newX < (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15) { + newX = (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15; + } else if (newX > (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15) { + newX = (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15; + } + } - 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); + this.setState({ yPosition: newY }); + this.props.dataDoc.position_YDisplay = Math.round((this.props.yMax - 2 * this.props.radius - newY + 5) * 100) / 100; + if (this.props.dataDoc.simulation_Type != 'Pulley') { + this.setState({ xPosition: newX }); + this.props.dataDoc.position_XDisplay = newX; + } + if (this.props.dataDoc.simulation_Type != 'Suspension') { + if (this.props.dataDoc.simulation_Type != 'Pulley') { + this.setState({ updatedStartPosX: newX }); + } + this.setState({ updatedStartPosY: newY }); + } + this.setState({ clickPositionX: e.clientX }); + this.setState({ clickPositionY: e.clientY }); + this.setDisplayValues(); + } + }} + onPointerUp={e => { + if (this.state.dragging) { + if (this.props.dataDoc.simulation_Type != 'Pendulum' && this.props.dataDoc.simulation_Type != 'Suspension') { + this.resetEverything(); + } + this.setState({ dragging: false }); + let newY = this.state.yPosition + e.clientY - this.state.clickPositionY; + if (newY > this.props.yMax - 2 * this.props.radius - 10) { + newY = this.props.yMax - 2 * this.props.radius - 10; + } else if (newY < 10) { + newY = 10; + } - 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.props.layoutDoc._height + "px"} - > - <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> + let newX = this.state.xPosition + e.clientX - this.state.clickPositionX; + if (newX > this.props.xMax - 2 * this.props.radius - 10) { + newX = this.props.xMax - 2 * this.props.radius - 10; + } else if (newX < 10) { + newX = 10; + } + if (this.props.dataDoc.simulation_Type == 'Spring') { + this.props.dataDoc.springStartLength = newY; + } + if (this.props.dataDoc.simulation_Type == '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 tensionForce1: IForce = { + description: 'Tension', + magnitude: tensionMag1, + directionInDegrees: dir1T, + component: false, + }; + const tensionForce2: IForce = { + description: 'Tension', + magnitude: tensionMag2, + directionInDegrees: dir2T, + component: false, + }; + const grav: IForce = { + description: 'Gravity', + magnitude: this.props.mass * Math.abs(this.props.gravity), + directionInDegrees: 270, + component: false, + }; + this.props.dataDoc.updatedForces = [tensionForce1, tensionForce2, grav]; + } + } + }}> + <div className="weight" style={this.weightStyle}> + <p className="weightLabel">{this.props.mass} kg</p> + </div> + </div> + {this.props.dataDoc.simulation_Type == 'Spring' && ( + <div + className="spring" + style={{ + pointerEvents: 'none', + position: 'absolute', + left: 0, + top: 0, + }}> + <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + '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; + return <line key={val} x1={xPos1} y1={yPos1} x2={xPos2} y2={yPos2} stroke={'#808080'} strokeWidth="10" />; + })} + </svg> + </div> )} - </div> - </div> - ); - })} - {!this.state.dragging && - this.props.showForces && this.props.updatedForces && - this.props.updatedForces.map((force, index) => { - if (force.magnitude < this.epsilon) { - return; - } - let 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 arrowEndX: number = - arrowStartX + - Math.abs(force.magnitude) * - 20 * - Math.cos((force.directionInDegrees * Math.PI) / 180); - let color = "#0d0d0d"; + {this.props.dataDoc.simulation_Type == 'Pulley' && ( + <div + className="rod" + style={{ + pointerEvents: 'none', + position: 'absolute', + left: 0, + top: 0, + }}> + <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}> + <line x1={this.state.xPosition + this.props.radius} y1={this.state.yPosition + this.props.radius} x2={this.state.xPosition + this.props.radius} y2={this.props.yMin} stroke={'#deb887'} strokeWidth="10" /> + </svg> + </div> + )} + {this.props.dataDoc.simulation_Type == 'Pulley' && ( + <div + className="wheel" + style={{ + pointerEvents: 'none', + position: 'absolute', + left: 0, + top: 0, + }}> + <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}> + <circle cx={(this.props.xMax + this.props.xMin) / 2} cy={this.props.radius} r={this.props.radius * 1.5} fill={'#808080'} /> + </svg> + </div> + )} + {this.props.dataDoc.simulation_Type == 'Suspension' && ( + <div + className="rod" + style={{ + pointerEvents: 'none', + position: 'absolute', + left: 0, + top: 0, + }}> + <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}> + <line + x1={this.state.xPosition + this.props.radius} + y1={this.state.yPosition + this.props.radius} + x2={(this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200} + y2={this.props.yMin} + stroke={'#deb887'} + strokeWidth="10" + /> + </svg> + <p + style={{ + position: 'absolute', + left: (this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200 + 80 + 'px', + top: 10 + 'px', + backgroundColor: this.labelBackgroundColor, + }}> + {Math.round( + ((Math.atan((this.state.yPosition + this.props.radius) / (this.state.xPosition + this.props.radius - ((this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200))) * 180) / Math.PI) * 100 + ) / 100} + ° + </p> + <div + className="rod" + style={{ + pointerEvents: 'none', + position: 'absolute', + left: 0, + top: 0, + }}> + <svg width={this.props.layoutDoc._width + 'px'} height={this.props.layoutDoc._height + 'px'}> + <line + x1={this.state.xPosition + this.props.radius} + y1={this.state.yPosition + this.props.radius} + x2={(this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius} + y2={this.props.yMin} + stroke={'#deb887'} + strokeWidth="10" + /> + </svg> + </div> - 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); + <p + style={{ + position: 'absolute', + left: (this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius - 80 + 'px', + top: 10 + 'px', + backgroundColor: this.labelBackgroundColor, + }}> + {Math.round( + ((Math.atan((this.state.yPosition + this.props.radius) / ((this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius - (this.state.xPosition + this.props.radius))) * 180) / Math.PI) * 100 + ) / 100} + ° + </p> + </div> + )} + {this.props.dataDoc.simulation_Type == 'Circular Motion' && ( + <div + className="rod" + style={{ + pointerEvents: 'none', + position: 'absolute', + left: 0, + top: 0, + }}> + <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}> + <line + x1={this.state.xPosition + this.props.radius} + y1={this.state.yPosition + this.props.radius} + x2={(this.props.xMin + this.props.xMax) / 2} + y2={(this.props.yMin + this.props.yMax) / 2} + stroke={'#deb887'} + strokeWidth="10" + /> + </svg> + </div> + )} + {this.props.dataDoc.simulation_Type == 'Pendulum' && ( + <div + className="rod" + style={{ + pointerEvents: 'none', + position: 'absolute', + left: 0, + top: 0, + }}> + <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}> + <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> + {!this.state.dragging && ( + <div> + <p + style={{ + position: 'absolute', + zIndex: 5, + left: this.state.xPosition + 'px', + top: this.state.yPosition - 70 + 'px', + backgroundColor: this.labelBackgroundColor, + }}> + {Math.round(this.props.pendulumLength)} m + </p> + <p + style={{ + position: 'absolute', + left: this.props.xMax / 2 + 'px', + top: 30 + 'px', + backgroundColor: this.labelBackgroundColor, + }}> + {Math.round(this.props.pendulum_angle * 100) / 100}° + </p> + </div> + )} + </div> + )} + {this.props.dataDoc.simulation_Type == 'Inclined Plane' && ( + <div> + <div style={{ position: 'absolute', left: '0', top: '0' }}> + <svg width={this.props.xMax + 'px'} height={this.props.yMax + 'px'}> + <polygon points={this.state.coordinates} style={{ fill: 'burlywood' }} /> + </svg> + </div> - 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.props.layoutDoc._height + "px"} - > - <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> + <p + style={{ + position: 'absolute', + left: Math.round(this.props.xMax * 0.5 - 200 + this.props.wedgeWidth - 80) + 'px', + top: Math.round(this.props.yMax - 40) + 'px', + }}> + {Math.round(((Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI) * 100) / 100}° + </p> + </div> )} - </div> + {!this.state.dragging && this.props.showAcceleration && ( + <div> + <div + style={{ + pointerEvents: 'none', + position: 'absolute', + left: 0, + top: 0, + }}> + <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}> + <defs> + <marker id="accArrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth"> + <path d="M0,0 L0,6 L9,3 z" fill="green" /> + </marker> + </defs> + <line + x1={this.state.xPosition + this.props.radius} + y1={this.state.yPosition + this.props.radius} + x2={this.state.xPosition + this.props.radius + this.getNewAccelerationX(this.props.updatedForces) * 15} + y2={this.state.yPosition + this.props.radius + this.getNewAccelerationY(this.props.updatedForces) * 15} + stroke={'green'} + strokeWidth="5" + markerEnd="url(#accArrow)" + /> + </svg> + <div + style={{ + pointerEvents: 'none', + position: 'absolute', + left: this.state.xPosition + this.props.radius + this.state.xAccel * 15 + 25 + 'px', + top: this.state.yPosition + this.props.radius + this.state.yAccel * 15 + 70 + 'px', + lineHeight: 1, + }}> + <p> + {Math.round(100 * Math.sqrt(this.state.xAccel * this.state.xAccel + this.state.yAccel * this.state.yAccel)) / 100} m/s + <sup>2</sup> + </p> + </div> + </div> + </div> + )} + {!this.state.dragging && this.props.showVelocity && ( + <div> + <div + style={{ + pointerEvents: 'none', + position: 'absolute', + left: 0, + top: 0, + }}> + <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}> + <defs> + <marker id="velArrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth"> + <path d="M0,0 L0,6 L9,3 z" fill="blue" /> + </marker> + </defs> + <line + x1={this.state.xPosition + this.props.radius} + y1={this.state.yPosition + this.props.radius} + x2={this.state.xPosition + this.props.radius + this.state.xVelocity * 7} + y2={this.state.yPosition + this.props.radius + this.state.yVelocity * 7} + stroke={'blue'} + strokeWidth="5" + markerEnd="url(#velArrow)" + /> + </svg> + <div + style={{ + pointerEvents: 'none', + position: 'absolute', + left: this.state.xPosition + this.props.radius + this.state.xVelocity * 7 + 25 + 'px', + top: this.state.yPosition + this.props.radius + this.state.yVelocity * 7 + 'px', + lineHeight: 1, + }}> + <p>{Math.round(100 * Math.sqrt(this.props.displayXVelocity * this.props.displayXVelocity + this.props.displayYVelocity * this.props.displayYVelocity)) / 100} m/s</p> + </div> + </div> + </div> + )} + {!this.state.dragging && + this.props.showComponentForces && + this.props.componentForces.map((force, index) => { + if (force.magnitude < this.epsilon) { + return; + } + let 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 arrowEndX: number = arrowStartX + Math.abs(force.magnitude) * 20 * Math.cos((force.directionInDegrees * Math.PI) / 180); + + let 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.props.layoutDoc._height + 'px'}> + <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.updatedForces && + this.props.updatedForces.map((force, index) => { + if (force.magnitude < this.epsilon) { + return; + } + let 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 arrowEndX: number = arrowStartX + Math.abs(force.magnitude) * 20 * Math.cos((force.directionInDegrees * Math.PI) / 180); + + let 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.props.layoutDoc._height + 'px'}> + <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> + ); + })} </div> - ); - })} - </div> - )} -}; + ); + } +} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 8dd322e05..d0e5d03b3 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -382,7 +382,7 @@ export class Doc extends RefField { public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; } - public get [DataSym]() { + public get [DataSym](): Doc { const self = this[SelfProxy]; return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); } |