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 QuestionMarkIcon from '@mui/icons-material/QuestionMark'; import ReplayIcon from '@mui/icons-material/Replay'; import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, FormControl, FormControlLabel, FormGroup, IconButton, LinearProgress, Stack } from '@mui/material'; import Typography from '@mui/material/Typography'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { NumListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { BoolCast, NumCast, StrCast } from '../../../../fields/Types'; 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 React = require('react'); interface IWallProps { length: number; xPos: number; yPos: number; angleInDegrees: number; } interface IForce { description: string; magnitude: number; directionInDegrees: number; } interface VectorTemplate { top: number; left: number; width: number; height: number; x1: number; y1: number; x2: number; y2: number; weightX: number; weightY: number; } interface QuestionTemplate { questionSetup: string[]; variablesForQuestionSetup: string[]; question: string; answerParts: string[]; answerSolutionDescriptions: string[]; goal: string; hints: { description: string; content: string }[]; } interface TutorialTemplate { question: string; steps: { description: string; content: string; forces: { description: string; magnitude: number; directionInDegrees: number; component: boolean; }[]; showMagnitude: boolean; }[]; } @observer export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PhysicsSimulationBox, fieldKey); } _widthDisposer: IReactionDisposer | undefined; @observable _simReset = 0; // semi-Constants xMin = 0; yMin = 0; xMax = this.props.PanelWidth() * 0.6; yMax = this.props.PanelHeight(); color = `rgba(0,0,0,0.5)`; radius = 50; wallPositions: IWallProps[] = []; @computed get circularMotionRadius() { return (NumCast(this.dataDoc.circularMotionRadius, 150) * this.props.PanelWidth()) / 1000; } @computed get gravity() { return NumCast(this.dataDoc.simulation_gravity, -9.81); } @computed get simulationType() { return StrCast(this.dataDoc.simulation_type, 'Inclined Plane'); } @computed get simulationMode() { return StrCast(this.dataDoc.simulation_mode, 'Freeform'); } // Used for spring simulation @computed get springConstant() { return NumCast(this.dataDoc.spring_constant, 0.5); } @computed get springLengthRest() { return NumCast(this.dataDoc.spring_lengthRest, 200); } @computed get springLengthStart() { return NumCast(this.dataDoc.spring_lengthStart, 200); } @computed get pendulumAngle() { return NumCast(this.dataDoc.pendulum_angle); } @computed get pendulumAngleStart() { return NumCast(this.dataDoc.pendulum_angleStart); } @computed get pendulumLength() { return NumCast(this.dataDoc.pendulum_length); } @computed get pendulumLengthStart() { return NumCast(this.dataDoc.pendulum_lengthStart); } // Used for wedge simulation @computed get wedgeAngle() { return NumCast(this.dataDoc.wedge_angle, 26); } @computed get wedgeHeight() { return NumCast(this.dataDoc.wedge_height, Math.tan((26 * Math.PI) / 180) * this.xMax * 0.5); } @computed get wedgeWidth() { return NumCast(this.dataDoc.wedge_width, this.xMax * 0.5); } @computed get mass1() { return NumCast(this.dataDoc.mass1, 1); } @computed get mass2() { return NumCast(this.dataDoc.mass2, 1); } @computed get mass1Radius() { return NumCast(this.dataDoc.mass1_radius, 30); } @computed get mass1PosXStart() { return NumCast(this.dataDoc.mass1_positionXstart); } @computed get mass1PosYStart() { return NumCast(this.dataDoc.mass1_positionYstart); } @computed get mass1VelXStart() { return NumCast(this.dataDoc.mass1_velocityXstart); } @computed get mass1VelYStart() { return NumCast(this.dataDoc.mass1_velocityYstart); } @computed get mass2PosXStart() { return NumCast(this.dataDoc.mass2_positionXstart); } @computed get mass2PosYStart() { return NumCast(this.dataDoc.mass2_positionYstart); } @computed get mass2VelXStart() { return NumCast(this.dataDoc.mass2_velocityXstart); } @computed get mass2VelYStart() { return NumCast(this.dataDoc.mass2_velocityYstart); } @computed get selectedQuestion() { return this.dataDoc.selectedQuestion ? (JSON.parse(StrCast(this.dataDoc.selectedQuestion)) as QuestionTemplate) : questions.inclinePlane[0]; } @computed get tutorial() { return this.dataDoc.tutorial ? (JSON.parse(StrCast(this.dataDoc.tutorial)) as TutorialTemplate) : tutorials.inclinePlane; } @computed get selectedSolutions() { return NumListCast(this.dataDoc.selectedSolutions); } @computed get questionPartOne() { return StrCast(this.dataDoc.questionPartOne); } @computed get questionPartTwo() { return StrCast(this.dataDoc.questionPartTwo); } componentWillUnmount() { this._widthDisposer?.(); } componentDidMount() { // Setup and update simulation this._widthDisposer = reaction(() => [this.props.PanelWidth(), this.props.PanelHeight()], this.setupSimulation, { fireImmediately: true }); // Create walls this.wallPositions = [ { length: 100, xPos: 0, yPos: 0, angleInDegrees: 0 }, { length: 100, xPos: 0, yPos: 100, angleInDegrees: 0 }, { length: 100, xPos: 0, yPos: 0, angleInDegrees: 90 }, { length: 100, xPos: (this.xMax / this.props.PanelWidth()) * 100, yPos: 0, angleInDegrees: 90 }, ]; } componentDidUpdate() { if (this.xMax !== this.props.PanelWidth() * 0.6 || this.yMax != this.props.PanelHeight()) { this.xMax = this.props.PanelWidth() * 0.6; this.yMax = this.props.PanelHeight(); this.setupSimulation(); } } gravityForce = (mass: number): IForce => ({ description: 'Gravity', magnitude: mass * Math.abs(this.gravity), directionInDegrees: 270, }); @action setupSimulation = () => { const simulationType = this.simulationType; const mode = this.simulationMode; this.dataDoc.simulation_paused = true; if (simulationType != 'Circular Motion') { this.dataDoc.mass1_velocityXstart = 0; this.dataDoc.mass1_velocityYstart = 0; this.dataDoc.mass1_velocityX = 0; this.dataDoc.mass1_velocityY = 0; } if (mode == 'Freeform') { this.dataDoc.simulation_showForceMagnitudes = true; // prettier-ignore switch (simulationType) { case 'One Weight': this.dataDoc.simulation_showComponentForces = false; this.dataDoc.mass1_positionYstart = this.yMin + this.mass1Radius; this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius; this.dataDoc.mass1_positionY = this.getDisplayYPos(this.yMin + this.mass1Radius); this.dataDoc.mass1_positionX = (this.xMax + this.xMin) / 2 - this.mass1Radius; this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]); this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1)]); break; case 'Inclined Plane': this.setupInclinedPlane(); break; case 'Pendulum': this.setupPendulum(); break; case 'Spring': this.setupSpring(); break; case 'Circular Motion': this.setupCircular(20); break; case 'Pulley': this.setupPulley(); break; case 'Suspension': this.setupSuspension();break; } this._simReset++; } else if (mode == 'Review') { this.dataDoc.simulation_showComponentForces = false; this.dataDoc.simulation_showForceMagnitudes = true; this.dataDoc.simulation_showAcceleration = false; this.dataDoc.simulation_showVelocity = false; this.dataDoc.simulation_showForces = true; this.generateNewQuestion(); // prettier-ignore switch (simulationType) { case 'One Weight' : break;// TODO - one weight review problems case 'Spring': this.setupSpring(); break; // TODO - spring review problems case 'Inclined Plane': this.dataDoc.mass1_forcesUpdated = this.dataDoc.mass1_forcesStart = ''; break; case 'Pendulum': this.setupPendulum(); break; // TODO - pendulum review problems case 'Circular Motion': this.setupCircular(0); break; // TODO - circular motion review problems case 'Pulley': this.setupPulley(); break; // TODO - pulley tutorial review problems case 'Suspension': this.setupSuspension(); break; // TODO - suspension tutorial review problems } } else if (mode == 'Tutorial') { this.dataDoc.simulation_showComponentForces = false; this.dataDoc.tutorial_stepNumber = 0; this.dataDoc.simulation_showAcceleration = false; if (this.simulationType != 'Circular Motion') { this.dataDoc.mass1_velocityX = 0; this.dataDoc.mass1_velocityY = 0; this.dataDoc.simulation_showVelocity = false; } else { this.dataDoc.mass1_velocityX = 20; this.dataDoc.mass1_velocityY = 0; this.dataDoc.simulation_showVelocity = true; } switch (this.simulationType) { case 'One Weight': this.dataDoc.simulation_showForces = true; this.dataDoc.mass1_positionYstart = this.yMax - 100; this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius; this.dataDoc.tutorial = JSON.stringify(tutorials.freeWeight); this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.freeWeight.steps[0].forces); this.dataDoc.simulation_showForceMagnitudes = tutorials.freeWeight.steps[0].showMagnitude; break; case 'Spring': this.dataDoc.simulation_showForces = true; this.setupSpring(); this.dataDoc.mass1_positionYstart = this.yMin + 200 + 19.62; this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius; this.dataDoc.tutorial = JSON.stringify(tutorials.spring); this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.spring.steps[0].forces); this.dataDoc.simulation_showForceMagnitudes = tutorials.spring.steps[0].showMagnitude; break; case 'Pendulum': this.setupPendulum(); this.dataDoc.tutorial = JSON.stringify(tutorials.pendulum); this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.pendulum.steps[0].forces); this.dataDoc.simulation_showForceMagnitudes = tutorials.pendulum.steps[0].showMagnitude; break; case 'Inclined Plane': this.dataDoc.wedge_angle = 26; this.setupInclinedPlane(); this.dataDoc.simulation_showForces = true; this.dataDoc.tutorial = JSON.stringify(tutorials.inclinePlane); this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.inclinePlane.steps[0].forces); this.dataDoc.simulation_showForceMagnitudes = tutorials.inclinePlane.steps[0].showMagnitude; break; case 'Circular Motion': this.dataDoc.simulation_showForces = true; this.setupCircular(40); this.dataDoc.tutorial = JSON.stringify(tutorials.circular); this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.circular.steps[0].forces); this.dataDoc.simulation_showForceMagnitudes = tutorials.circular.steps[0].showMagnitude; break; case 'Pulley': this.dataDoc.simulation_showForces = true; this.setupPulley(); this.dataDoc.tutorial = JSON.stringify(tutorials.pulley); this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.pulley.steps[0].forces); this.dataDoc.simulation_showForceMagnitudes = tutorials.pulley.steps[0].showMagnitude; break; case 'Suspension': this.dataDoc.simulation_showForces = true; this.setupSuspension(); this.dataDoc.tutorial = JSON.stringify(tutorials.suspension); this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.suspension.steps[0].forces); this.dataDoc.simulation_showForceMagnitudes = tutorials.suspension.steps[0].showMagnitude; break; } this._simReset++; } }; // Helper function to go between display and real values getDisplayYPos = (yPos: number) => this.yMax - yPos - 2 * this.mass1Radius + 5; getYPosFromDisplay = (yDisplay: number) => this.yMax - yDisplay - 2 * this.mass1Radius + 5; // Update forces when coefficient of static friction changes in freeform mode updateForcesWithFriction = (coefficient: number, width = this.wedgeWidth, height = this.wedgeHeight) => { const normalForce: IForce = { description: 'Normal Force', magnitude: Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1, directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI, }; let frictionForce: IForce = { description: 'Static Friction Force', magnitude: coefficient * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1, directionInDegrees: 180 - (Math.atan(height / width) * 180) / Math.PI, }; // reduce magnitude or friction force if necessary such that block cannot slide up plane let yForce = -Math.abs(this.gravity) * this.mass1; 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.gravity) * this.mass1) / Math.sin((frictionForce.directionInDegrees * Math.PI) / 180); } const normalForceComponent: IForce = { description: 'Normal Force', magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)), directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI, }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin(Math.PI / 2 - Math.atan(height / width)), directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI + 180, }; const gravityPerpendicular: IForce = { description: 'Gravity Perpendicular Component', magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(Math.PI / 2 - Math.atan(height / width)), directionInDegrees: 360 - (Math.atan(height / width) * 180) / Math.PI, }; const gravityForce = this.gravityForce(this.mass1); if (coefficient != 0) { this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce, normalForce, frictionForce]); this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce, normalForce, frictionForce]); this.dataDoc.mass1_componentForces = JSON.stringify([frictionForce, normalForceComponent, gravityParallel, gravityPerpendicular]); } else { this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce, normalForce]); this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce, normalForce]); this.dataDoc.mass1_componentForces = JSON.stringify([normalForceComponent, gravityParallel, gravityPerpendicular]); } }; // Change wedge height and width and weight position to match new wedge angle changeWedgeBasedOnNewAngle = (angle: number) => { const radAng = (angle * Math.PI) / 180; this.dataDoc.wedge_width = this.xMax * 0.5; this.dataDoc.wedge_height = Math.tan(radAng) * this.dataDoc.wedge_width; // update weight position based on updated wedge width/height let yPos = this.yMax - this.dataDoc.wedge_height - this.mass1Radius * Math.cos(radAng) - this.mass1Radius; let xPos = this.xMax * 0.25 + this.mass1Radius * Math.sin(radAng) - this.mass1Radius; this.dataDoc.mass1_positionXstart = xPos; this.dataDoc.mass1_positionYstart = yPos; if (this.simulationMode == 'Freeform') { this.updateForcesWithFriction(NumCast(this.dataDoc.coefficientOfStaticFriction), this.dataDoc.wedge_width, Math.tan(radAng) * this.dataDoc.wedge_width); } }; // In review mode, update forces when coefficient of static friction changed updateReviewForcesBasedOnCoefficient = (coefficient: number) => { let theta = this.wedgeAngle; let index = this.selectedQuestion.variablesForQuestionSetup.indexOf('theta - max 45'); if (index >= 0) { theta = NumListCast(this.dataDoc.questionVariables)[index]; } if (isNaN(theta)) { return; } this.dataDoc.review_GravityMagnitude = Math.abs(this.gravity); this.dataDoc.review_GravityAngle = 270; this.dataDoc.review_NormalMagnitude = Math.abs(this.gravity) * Math.cos((theta * Math.PI) / 180); this.dataDoc.review_NormalAngle = 90 - theta; let yForce = -Math.abs(this.gravity); yForce += Math.abs(this.gravity) * Math.cos((theta * Math.PI) / 180) * Math.sin(((90 - theta) * Math.PI) / 180); yForce += coefficient * Math.abs(this.gravity) * Math.cos((theta * Math.PI) / 180) * Math.sin(((180 - theta) * Math.PI) / 180); let friction = coefficient * Math.abs(this.gravity) * Math.cos((theta * Math.PI) / 180); if (yForce > 0) { friction = (-(Math.abs(this.gravity) * Math.cos((theta * Math.PI) / 180)) * Math.sin(((90 - theta) * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin(((180 - theta) * Math.PI) / 180); } 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.review_GravityMagnitude = Math.abs(this.gravity); this.dataDoc.review_GravityAngle = 270; this.dataDoc.review_NormalMagnitude = Math.abs(this.gravity) * Math.cos((angle * Math.PI) / 180); this.dataDoc.review_NormalAngle = 90 - angle; let yForce = -Math.abs(this.gravity); yForce += Math.abs(this.gravity) * Math.cos((angle * Math.PI) / 180) * Math.sin(((90 - angle) * Math.PI) / 180); yForce += NumCast(this.dataDoc.review_Coefficient) * Math.abs(this.gravity) * Math.cos((angle * Math.PI) / 180) * Math.sin(((180 - angle) * Math.PI) / 180); let friction = NumCast(this.dataDoc.review_Coefficient) * Math.abs(this.gravity) * Math.cos((angle * Math.PI) / 180); if (yForce > 0) { friction = (-(Math.abs(this.gravity) * Math.cos((angle * Math.PI) / 180)) * Math.sin(((90 - angle) * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin(((180 - angle) * Math.PI) / 180); } 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 = this.wedgeAngle; let index = question.variablesForQuestionSetup.indexOf('theta - max 45'); if (index >= 0) { theta = questionVars[index]; } let muS: number = NumCast(this.dataDoc.coefficientOfStaticFriction); index = question.variablesForQuestionSetup.indexOf('coefficient of static friction'); if (index >= 0) { muS = questionVars[index]; } for (let i = 0; i < question.answerSolutionDescriptions.length; i++) { const description = question.answerSolutionDescriptions[i]; 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.gravity) * Math.cos((theta / 180) * Math.PI)); } else if (description == 'solve static force magnitude from wedge angle given equilibrium') { let normalForceMagnitude = Math.abs(this.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.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.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.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') { solutions.push((Math.atan(muS) * 180) / Math.PI); } } this.dataDoc.selectedSolutions = new List(solutions); return solutions; }; // In review mode, check if input answers match correct answers and optionally generate alert checkAnswers = (showAlert: boolean = true) => { let error: boolean = false; let epsilon: number = 0.01; if (this.selectedQuestion) { for (let i = 0; i < this.selectedQuestion.answerParts.length; i++) { if (this.selectedQuestion.answerParts[i] == 'force of gravity') { if (Math.abs(NumCast(this.dataDoc.review_GravityMagnitude) - this.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.selectedQuestion.answerParts[i] == 'angle of gravity') { if (Math.abs(NumCast(this.dataDoc.review_GravityAngle) - this.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.selectedQuestion.answerParts[i] == 'normal force') { if (Math.abs(NumCast(this.dataDoc.review_NormalMagnitude) - this.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.selectedQuestion.answerParts[i] == 'angle of normal force') { if (Math.abs(NumCast(this.dataDoc.review_NormalAngle) - this.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.selectedQuestion.answerParts[i] == 'force of static friction') { if (Math.abs(NumCast(this.dataDoc.review_StaticMagnitude) - this.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.selectedQuestion.answerParts[i] == 'angle of static friction') { if (Math.abs(NumCast(this.dataDoc.review_StaticAngle) - this.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.selectedQuestion.answerParts[i] == 'coefficient of static friction') { if (Math.abs(NumCast(this.dataDoc.coefficientOfStaticFriction) - this.selectedSolutions[i]) > epsilon) { error = true; } } else if (this.selectedQuestion.answerParts[i] == 'wedge angle') { if (Math.abs(this.wedgeAngle - this.selectedSolutions[i]) > epsilon) { error = true; } } } } if (showAlert) { this.dataDoc.simulation_paused = false; setTimeout(() => (this.dataDoc.simulation_paused = true), 3000); } if (this.selectedQuestion.goal == 'noMovement') { this.dataDoc.noMovement = !error; } }; // Reset all review values to default resetReviewValuesToDefault = () => { 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.simulation_paused = true; }; // In review mode, reset problem variables and generate a new question generateNewQuestion = () => { this.resetReviewValuesToDefault(); const vars: number[] = []; let question: QuestionTemplate = questions.inclinePlane[0]; if (this.simulationType === 'Inclined Plane') { this.dataDoc.questionNumber = (NumCast(this.dataDoc.questionNumber) + 1) % questions.inclinePlane.length; question = questions.inclinePlane[NumCast(this.dataDoc.questionNumber)]; let coefficient = 0; let wedge_angle = 0; for (let i = 0; i < question.variablesForQuestionSetup.length; i++) { if (question.variablesForQuestionSetup[i] == 'theta - max 45') { let randValue = Math.floor(Math.random() * 44 + 1); vars.push(randValue); wedge_angle = randValue; } else if (question.variablesForQuestionSetup[i] == 'coefficient of static friction') { let randValue = Math.round(Math.random() * 1000) / 1000; vars.push(randValue); coefficient = randValue; } } this.dataDoc.wedge_angle = wedge_angle; this.changeWedgeBasedOnNewAngle(wedge_angle); this.dataDoc.coefficientOfStaticFriction = coefficient; this.dataDoc.review_Coefficient = coefficient; } let q = ''; for (let i = 0; i < question.questionSetup.length; i++) { q += question.questionSetup[i]; if (i != question.questionSetup.length - 1) { q += vars[i]; if (question.variablesForQuestionSetup[i].includes('theta')) { q += ' degree (≈' + Math.round((1000 * (vars[i] * Math.PI)) / 180) / 1000 + ' rad)'; } } } this.dataDoc.questionVariables = new List(vars); this.dataDoc.selectedQuestion = JSON.stringify(question); this.dataDoc.questionPartOne = q; this.dataDoc.questionPartTwo = question.question; this.dataDoc.answers = new List(this.getAnswersToQuestion(question, vars)); //this.dataDoc.simulation_reset = (!this.dataDoc.simulation_reset); }; // Default setup for uniform circular motion simulation @action setupCircular = (value: number) => { this.dataDoc.simulation_showComponentForces = false; this.dataDoc.mass1_velocityYstart = 0; this.dataDoc.mass1_velocityXstart = value; let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius; let yPos = (this.yMax + this.yMin) / 2 + this.circularMotionRadius - this.mass1Radius; this.dataDoc.mass1_positionYstart = yPos; this.dataDoc.mass1_positionXstart = xPos; const tensionForce: IForce = { description: 'Centripetal Force', magnitude: (this.dataDoc.mass1_velocityXstart ** 2 * this.mass1) / this.circularMotionRadius, directionInDegrees: 90, }; this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce]); this.dataDoc.mass1_forcesStart = JSON.stringify([tensionForce]); this._simReset++; }; setupInclinedPlane = () => { this.changeWedgeBasedOnNewAngle(this.wedgeAngle); this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1)]); this.updateForcesWithFriction(NumCast(this.dataDoc.coefficientOfStaticFriction)); }; // Default setup for pendulum simulation setupPendulum = () => { const length = (300 * this.props.PanelWidth()) / 1000; 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 - this.mass1Radius; const yPos = y - this.mass1Radius - 5; this.dataDoc.mass1_positionXstart = xPos; this.dataDoc.mass1_positionYstart = yPos; const forceOfTension: IForce = { description: 'Tension', magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin((60 * Math.PI) / 180), directionInDegrees: 90 - angle, }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin(((90 - angle) * Math.PI) / 180), directionInDegrees: -angle - 90, }; const gravityPerpendicular: IForce = { description: 'Gravity Perpendicular Component', magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(((90 - angle) * Math.PI) / 180), directionInDegrees: -angle, }; this.dataDoc.mass1_componentForces = JSON.stringify([forceOfTension, gravityParallel, gravityPerpendicular]); this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]); this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]); this.dataDoc.pendulum_angle = this.dataDoc.pendulum_angleStart = 30; this.dataDoc.pendulum_length = this.dataDoc.pendulum_lengthStart = 300; }; // Default setup for spring simulation @action setupSpring = () => { this.dataDoc.simulation_showComponentForces = false; this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]); this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1)]); this.dataDoc.mass1_positionXstart = this.xMax / 2 - this.mass1Radius; this.dataDoc.mass1_positionYstart = 200; this.dataDoc.spring_constant = 0.5; this.dataDoc.spring_lengthRest = 200; this.dataDoc.spring_lengthStart = 200; this._simReset++; }; // Default setup for suspension simulation @action setupSuspension = () => { let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius; let yPos = this.yMin + 200; this.dataDoc.mass1_positionYstart = yPos; this.dataDoc.mass1_positionXstart = xPos; this.dataDoc.mass1_positionY = this.getDisplayYPos(yPos); this.dataDoc.mass1_positionX = xPos; let tensionMag = (this.mass1 * Math.abs(this.gravity)) / (2 * Math.sin(Math.PI / 4)); const tensionForce1: IForce = { description: 'Tension', magnitude: tensionMag, directionInDegrees: 45, }; const tensionForce2: IForce = { description: 'Tension', magnitude: tensionMag, directionInDegrees: 135, }; const gravity = this.gravityForce(this.mass1); this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]); this.dataDoc.mass1_forcesStart = JSON.stringify([tensionForce1, tensionForce2, gravity]); this._simReset++; }; // Default setup for pulley simulation @action setupPulley = () => { this.dataDoc.simulation_showComponentForces = false; this.dataDoc.mass1_positionYstart = (this.yMax + this.yMin) / 2; this.dataDoc.mass1_positionXstart = (this.xMin + this.xMax) / 2 - 2 * this.mass1Radius - 5; this.dataDoc.mass1_positionY = this.getDisplayYPos((this.yMax + this.yMin) / 2); this.dataDoc.mass1_positionX = (this.xMin + this.xMax) / 2 - 2 * this.mass1Radius - 5; const a = (-1 * ((this.mass1 - this.mass2) * Math.abs(this.gravity))) / (this.mass1 + this.mass2); const gravityForce1 = this.gravityForce(this.mass1); const tensionForce1: IForce = { description: 'Tension', magnitude: this.mass1 * a + this.mass1 * Math.abs(this.gravity), directionInDegrees: 90, }; this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce1, tensionForce1]); this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce1, tensionForce1]); const gravityForce2 = this.gravityForce(this.mass2); const tensionForce2: IForce = { description: 'Tension', magnitude: -this.mass2 * a + this.mass2 * Math.abs(this.gravity), directionInDegrees: 90, }; this.dataDoc.mass2_positionYstart = (this.yMax + this.yMin) / 2; this.dataDoc.mass2_positionXstart = (this.xMin + this.xMax) / 2 + 5; this.dataDoc.mass2_positionY = this.getDisplayYPos((this.yMax + this.yMin) / 2); this.dataDoc.mass2_positionX = (this.xMin + this.xMax) / 2 + 5; this.dataDoc.mass2_forcesUpdated = JSON.stringify([gravityForce2, tensionForce2]); this.dataDoc.mass2_forcesStart = JSON.stringify([gravityForce2, tensionForce2]); this._simReset++; }; public static parseJSON(json: string) { return !json ? [] : (JSON.parse(json) as IForce[]); } // Handle force change in review mode updateReviewModeValues = () => { const forceOfGravityReview: IForce = { description: 'Gravity', magnitude: NumCast(this.dataDoc.review_GravityMagnitude), directionInDegrees: NumCast(this.dataDoc.review_GravityAngle), }; const normalForceReview: IForce = { description: 'Normal Force', magnitude: NumCast(this.dataDoc.review_NormalMagnitude), directionInDegrees: NumCast(this.dataDoc.review_NormalAngle), }; const staticFrictionForceReview: IForce = { description: 'Static Friction Force', magnitude: NumCast(this.dataDoc.review_StaticMagnitude), directionInDegrees: NumCast(this.dataDoc.review_StaticAngle), }; this.dataDoc.mass1_forcesStart = JSON.stringify([forceOfGravityReview, normalForceReview, staticFrictionForceReview]); this.dataDoc.mass1_forcesUpdated = JSON.stringify([forceOfGravityReview, normalForceReview, staticFrictionForceReview]); }; pause = () => (this.dataDoc.simulation_paused = true); componentForces1 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass1_componentForces)); setComponentForces1 = (forces: IForce[]) => (this.dataDoc.mass1_componentForces = JSON.stringify(forces)); componentForces2 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass2_componentForces)); setComponentForces2 = (forces: IForce[]) => (this.dataDoc.mass2_componentForces = JSON.stringify(forces)); startForces1 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass1_forcesStart)); startForces2 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass2_forcesStart)); forcesUpdated1 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass1_forcesUpdated)); setForcesUpdated1 = (forces: IForce[]) => (this.dataDoc.mass1_forcesUpdated = JSON.stringify(forces)); forcesUpdated2 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass2_forcesUpdated)); setForcesUpdated2 = (forces: IForce[]) => (this.dataDoc.mass2_forcesUpdated = JSON.stringify(forces)); setPosition1 = (xPos: number | undefined, yPos: number | undefined) => { yPos !== undefined && (this.dataDoc.mass1_positionY = Math.round(yPos * 100) / 100); xPos !== undefined && (this.dataDoc.mass1_positionX = Math.round(xPos * 100) / 100); }; setPosition2 = (xPos: number | undefined, yPos: number | undefined) => { yPos !== undefined && (this.dataDoc.mass2_positionY = Math.round(yPos * 100) / 100); xPos !== undefined && (this.dataDoc.mass2_positionX = Math.round(xPos * 100) / 100); }; setVelocity1 = (xVel: number | undefined, yVel: number | undefined) => { yVel !== undefined && (this.dataDoc.mass1_velocityY = (-1 * Math.round(yVel * 100)) / 100); xVel !== undefined && (this.dataDoc.mass1_velocityX = Math.round(xVel * 100) / 100); }; setVelocity2 = (xVel: number | undefined, yVel: number | undefined) => { yVel !== undefined && (this.dataDoc.mass2_velocityY = (-1 * Math.round(yVel * 100)) / 100); xVel !== undefined && (this.dataDoc.mass2_velocityX = Math.round(xVel * 100) / 100); }; setAcceleration1 = (xAccel: number, yAccel: number) => { this.dataDoc.mass1_accelerationY = yAccel; this.dataDoc.mass1_accelerationX = xAccel; }; setAcceleration2 = (xAccel: number, yAccel: number) => { this.dataDoc.mass2_accelerationY = yAccel; this.dataDoc.mass2_accelerationX = xAccel; }; setPendulumAngle = (angle: number | undefined, length: number | undefined) => { angle !== undefined && (this.dataDoc.pendulum_angle = angle); length !== undefined && (this.dataDoc.pendulum_length = length); }; setSpringLength = (length: number) => { this.dataDoc.spring_lengthStart = length; }; resetRequest = () => this._simReset; render() { const commonWeightProps = { pause: this.pause, paused: BoolCast(this.dataDoc.simulation_paused), panelWidth: this.props.PanelWidth, panelHeight: this.props.PanelHeight, resetRequest: this.resetRequest, xMax: this.xMax, xMin: this.xMin, yMax: this.yMax, yMin: this.yMin, wallPositions: this.wallPositions, gravity: Math.abs(this.gravity), timestepSize: 0.05, showComponentForces: BoolCast(this.dataDoc.simulation_showComponentForces), coefficientOfKineticFriction: NumCast(this.dataDoc.coefficientOfKineticFriction), elasticCollisions: BoolCast(this.dataDoc.elasticCollisions), simulationMode: this.simulationMode, noMovement: BoolCast(this.dataDoc.noMovement), circularMotionRadius: this.circularMotionRadius, wedgeHeight: this.wedgeHeight, wedgeWidth: this.wedgeWidth, springConstant: this.springConstant, springStartLength: this.springLengthStart, springRestLength: this.springLengthRest, setSpringLength: this.setSpringLength, setPendulumAngle: this.setPendulumAngle, pendulumAngle: this.pendulumAngle, pendulumLength: this.pendulumLength, startPendulumAngle: this.pendulumAngleStart, startPendulumLength: this.pendulumLengthStart, radius: this.mass1Radius, simulationSpeed: NumCast(this.dataDoc.simulation_speed, 2), showAcceleration: BoolCast(this.dataDoc.simulation_showAcceleration), showForceMagnitudes: BoolCast(this.dataDoc.simulation_showForceMagnitudes), showForces: BoolCast(this.dataDoc.simulation_showForces), showVelocity: BoolCast(this.dataDoc.simulation_showVelocity), simulationType: this.simulationType, }; return (
{!this.dataDoc.simulation_paused && (
)}
{this.simulationType == 'Pulley' && ( )}
{(this.simulationType == 'One Weight' || this.simulationType == 'Inclined Plane') && this.wallPositions?.map((element, index) => )}
this.props.isContentActive() && e.stopPropagation()} style={{ overflow: 'auto', height: `${Math.max(1, 800 / this.props.PanelWidth()) * 100}%`, transform: `scale(${Math.min(1, this.props.PanelWidth() / 850)})` }}>
{this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && ( (this.dataDoc.simulation_paused = false)}> )} {!this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && ( (this.dataDoc.simulation_paused = true)}> )} {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && ( this._simReset++)}> )}
{this.simulationMode == 'Review' && this.simulationType != 'Inclined Plane' && (

<>{this.simulationType} review problems in progress!


)} {this.simulationMode == 'Review' && this.simulationType == 'Inclined Plane' && (
{!this.dataDoc.hintDialogueOpen && ( (this.dataDoc.hintDialogueOpen = true)} sx={{ position: 'fixed', left: this.xMax - 50 + 'px', top: this.yMin + 14 + 'px', }}> )} (this.dataDoc.hintDialogueOpen = false)}> Hints {this.selectedQuestion.hints?.map((hint: any, index: number) => (
Hint {index + 1}: {hint.description} {hint.content}
))}

{this.questionPartOne}

{this.questionPartTwo}

{this.selectedQuestion.answerParts.includes('force of gravity') && ( Gravity magnitude

} lowerBound={0} dataDoc={this.dataDoc} prop="review_GravityMagnitude" step={0.1} unit={'N'} upperBound={50} value={NumCast(this.dataDoc.review_GravityMagnitude)} showIcon={BoolCast(this.dataDoc.simulation_showIcon)} correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('force of gravity')]} labelWidth={'7em'} /> )} {this.selectedQuestion.answerParts.includes('angle of gravity') && ( Gravity angle

} lowerBound={0} dataDoc={this.dataDoc} prop="review_GravityAngle" step={1} unit={'°'} upperBound={360} value={NumCast(this.dataDoc.review_GravityAngle)} radianEquivalent={true} showIcon={BoolCast(this.dataDoc.simulation_showIcon)} correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of gravity')]} labelWidth={'7em'} /> )} {this.selectedQuestion.answerParts.includes('normal force') && ( Normal force magnitude

} lowerBound={0} dataDoc={this.dataDoc} prop="review_NormalMagnitude" step={0.1} unit={'N'} upperBound={50} value={NumCast(this.dataDoc.review_NormalMagnitude)} showIcon={BoolCast(this.dataDoc.simulation_showIcon)} correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('normal force')]} labelWidth={'7em'} /> )} {this.selectedQuestion.answerParts.includes('angle of normal force') && ( Normal force angle

} lowerBound={0} dataDoc={this.dataDoc} prop="review_NormalAngle" step={1} unit={'°'} upperBound={360} value={NumCast(this.dataDoc.review_NormalAngle)} radianEquivalent={true} showIcon={BoolCast(this.dataDoc.simulation_showIcon)} correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of normal force')]} labelWidth={'7em'} /> )} {this.selectedQuestion.answerParts.includes('force of static friction') && ( Static friction magnitude

} lowerBound={0} dataDoc={this.dataDoc} prop="review_StaticMagnitude" step={0.1} unit={'N'} upperBound={50} value={NumCast(this.dataDoc.review_StaticMagnitude)} showIcon={BoolCast(this.dataDoc.simulation_showIcon)} correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('force of static friction')]} labelWidth={'7em'} /> )} {this.selectedQuestion.answerParts.includes('angle of static friction') && ( Static friction angle

} lowerBound={0} dataDoc={this.dataDoc} prop="review_StaticAngle" step={1} unit={'°'} upperBound={360} value={NumCast(this.dataDoc.review_StaticAngle)} radianEquivalent={true} showIcon={BoolCast(this.dataDoc.simulation_showIcon)} correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of static friction')]} labelWidth={'7em'} /> )} {this.selectedQuestion.answerParts.includes('coefficient of static friction') && ( μs } lowerBound={0} dataDoc={this.dataDoc} prop="coefficientOfStaticFriction" step={0.1} unit={''} upperBound={1} value={NumCast(this.dataDoc.coefficientOfStaticFriction)} effect={this.updateReviewForcesBasedOnCoefficient} showIcon={BoolCast(this.dataDoc.simulation_showIcon)} correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('coefficient of static friction')]} /> )} {this.selectedQuestion.answerParts.includes('wedge angle') && ( θ} lowerBound={0} dataDoc={this.dataDoc} prop="wedge_angle" step={1} unit={'°'} upperBound={49} value={this.wedgeAngle} effect={(val: number) => { this.changeWedgeBasedOnNewAngle(val); this.updateReviewForcesBasedOnAngle(val); }} radianEquivalent={true} showIcon={BoolCast(this.dataDoc.simulation_showIcon)} correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('wedge angle')]} /> )}
)} {this.simulationMode == 'Tutorial' && (

Problem

{this.tutorial.question}

{ let step = NumCast(this.dataDoc.tutorial_stepNumber) - 1; step = Math.max(step, 0); step = Math.min(step, this.tutorial.steps.length - 1); this.dataDoc.tutorial_stepNumber = step; this.dataDoc.mass1_forcesStart = JSON.stringify(this.tutorial.steps[step].forces); this.dataDoc.mass1_forcesUpdated = JSON.stringify(this.tutorial.steps[step].forces); this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude; }} disabled={this.dataDoc.tutorial_stepNumber == 0}>

Step {NumCast(this.dataDoc.tutorial_stepNumber) + 1}: {this.tutorial.steps[NumCast(this.dataDoc.tutorial_stepNumber)].description}

{this.tutorial.steps[NumCast(this.dataDoc.tutorial_stepNumber)].content}

{ let step = NumCast(this.dataDoc.tutorial_stepNumber) + 1; step = Math.max(step, 0); step = Math.min(step, this.tutorial.steps.length - 1); this.dataDoc.tutorial_stepNumber = step; this.dataDoc.mass1_forcesStart = JSON.stringify(this.tutorial.steps[step].forces); this.dataDoc.mass1_forcesUpdated = JSON.stringify(this.tutorial.steps[step].forces); this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude; }} disabled={this.dataDoc.tutorial_stepNumber === this.tutorial.steps.length - 1}>
{(this.simulationType == 'One Weight' || this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') &&

Resources

} {this.simulationType == 'One Weight' && ( )} {this.simulationType == 'Inclined Plane' && ( )} {this.simulationType == 'Pendulum' && ( )}
)} {this.simulationMode == 'Review' && this.simulationType == 'Inclined Plane' && (

(this.dataDoc.simulation_mode = 'Tutorial')}> {' '} Go to walkthrough{' '}

)} {this.simulationMode == 'Freeform' && (
{this.simulationType == 'One Weight' && ( (this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions)} />} label="Make collisions elastic" labelPlacement="start" /> )} (this.dataDoc.simulation_showForces = !this.dataDoc.simulation_showForces)} />} label="Show force vectors" labelPlacement="start" /> {(this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') && ( (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />} label="Show component force vectors" labelPlacement="start" /> )} (this.dataDoc.simulation_showAcceleration = !this.dataDoc.simulation_showAcceleration)} />} label="Show acceleration vector" labelPlacement="start" /> (this.dataDoc.simulation_showVelocity = !this.dataDoc.simulation_showVelocity)} />} label="Show velocity vector" labelPlacement="start" /> Speed} lowerBound={1} dataDoc={this.dataDoc} prop="simulation_speed" step={1} unit={'x'} upperBound={10} value={NumCast(this.dataDoc.simulation_speed, 2)} labelWidth={'5em'} /> {this.dataDoc.simulation_paused && this.simulationType != 'Circular Motion' && ( Gravity} lowerBound={-30} dataDoc={this.dataDoc} prop="gravity" step={0.01} unit={'m/s2'} upperBound={0} value={NumCast(this.dataDoc.simulation_gravity, -9.81)} effect={(val: number) => this.setupSimulation()} labelWidth={'5em'} /> )} {this.dataDoc.simulation_paused && this.simulationType != 'Pulley' && ( Mass} lowerBound={1} dataDoc={this.dataDoc} prop="mass1" step={0.1} unit={'kg'} upperBound={5} value={this.mass1 ?? 1} effect={(val: number) => this.setupSimulation()} labelWidth={'5em'} /> )} {this.dataDoc.simulation_paused && this.simulationType == 'Pulley' && ( Red mass} lowerBound={1} dataDoc={this.dataDoc} prop="mass1" step={0.1} unit={'kg'} upperBound={5} value={this.mass1 ?? 1} effect={(val: number) => this.setupSimulation()} labelWidth={'5em'} /> )} {this.dataDoc.simulation_paused && this.simulationType == 'Pulley' && ( Blue mass} lowerBound={1} dataDoc={this.dataDoc} prop="mass2" step={0.1} unit={'kg'} upperBound={5} value={this.mass2 ?? 1} effect={(val: number) => this.setupSimulation()} labelWidth={'5em'} /> )} {this.dataDoc.simulation_paused && this.simulationType == 'Circular Motion' && ( Rod length} lowerBound={100} dataDoc={this.dataDoc} prop="circularMotionRadius" step={5} unit={'m'} upperBound={250} value={this.circularMotionRadius} effect={(val: number) => this.setupSimulation()} labelWidth={'5em'} /> )} {this.simulationType == 'Spring' && this.dataDoc.simulation_paused && (
Spring stiffness} lowerBound={0.1} dataDoc={this.dataDoc} prop="spring_constant" step={1} unit={'N/m'} upperBound={500} value={this.springConstant} effect={action(() => this._simReset++)} radianEquivalent={false} mode={'Freeform'} labelWidth={'7em'} /> Rest length} lowerBound={10} dataDoc={this.dataDoc} prop="spring_lengthRest" step={100} unit="" upperBound={500} value={this.springLengthRest} effect={action(() => this._simReset++)} radianEquivalent={false} mode="Freeform" labelWidth={'7em'} /> Starting displacement} lowerBound={-(this.springLengthRest - 10)} dataDoc={this.dataDoc} prop="" step={10} unit="" upperBound={this.springLengthRest} value={this.springLengthStart - this.springLengthRest} effect={action((val: number) => { this.dataDoc.mass1_positionYstart = this.springLengthRest + val; this.dataDoc.spring_lengthStart = this.springLengthRest + val; this._simReset++; })} radianEquivalent={false} mode="Freeform" labelWidth={'7em'} />
)} {this.simulationType == 'Inclined Plane' && this.dataDoc.simulation_paused && (
θ} lowerBound={0} dataDoc={this.dataDoc} prop="wedge_angle" step={1} unit={'°'} upperBound={49} value={this.wedgeAngle} effect={action((val: number) => { this.changeWedgeBasedOnNewAngle(val); this._simReset++; })} radianEquivalent={true} mode={'Freeform'} labelWidth={'2em'} /> μs } lowerBound={0} dataDoc={this.dataDoc} prop="coefficientOfStaticFriction" step={0.1} unit={''} upperBound={1} value={NumCast(this.dataDoc.coefficientOfStaticFriction) ?? 0} effect={action((val: number) => { this.updateForcesWithFriction(val); if (val < NumCast(this.dataDoc.coefficientOfKineticFriction)) { this.dataDoc.soefficientOfKineticFriction = val; } this._simReset++; })} mode={'Freeform'} labelWidth={'2em'} /> μk } lowerBound={0} dataDoc={this.dataDoc} prop="coefficientOfKineticFriction" step={0.1} unit={''} upperBound={NumCast(this.dataDoc.coefficientOfStaticFriction)} value={NumCast(this.dataDoc.coefficientOfKineticFriction) ?? 0} effect={action(() => this._simReset++)} mode={'Freeform'} labelWidth={'2em'} />
)} {this.simulationType == 'Inclined Plane' && !this.dataDoc.simulation_paused && ( <> θ: {Math.round(this.wedgeAngle * 100) / 100}° ≈ {Math.round(((this.wedgeAngle * Math.PI) / 180) * 100) / 100} rad
μ s: {this.dataDoc.coefficientOfStaticFriction}
μ k: {this.dataDoc.coefficientOfKineticFriction}
)} {this.simulationType == 'Pendulum' && !this.dataDoc.simulation_paused && ( θ: {Math.round(this.pendulumAngle * 100) / 100}° ≈ {Math.round(((this.pendulumAngle * Math.PI) / 180) * 100) / 100} rad )} {this.simulationType == 'Pendulum' && this.dataDoc.simulation_paused && (
Angle} lowerBound={0} dataDoc={this.dataDoc} prop="pendulum_angle" step={1} unit={'°'} upperBound={59} value={NumCast(this.dataDoc.pendulum_angle, 30)} effect={action(value => { this.dataDoc.pendulum_angleStart = value; this.dataDoc.pendulum_lengthStart = this.dataDoc.pendulum_length; if (this.simulationType == 'Pendulum') { const mag = this.mass1 * Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180); const forceOfTension: IForce = { description: 'Tension', magnitude: mag, directionInDegrees: 90 - value, }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', magnitude: Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180), directionInDegrees: 270 - value, }; const gravityPerpendicular: IForce = { description: 'Gravity Perpendicular Component', magnitude: Math.abs(this.gravity) * Math.sin((value * Math.PI) / 180), directionInDegrees: -value, }; const length = this.pendulumLength; 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 - NumCast(this.dataDoc.radius); const yPos = y - NumCast(this.dataDoc.radius) - 5; this.dataDoc.mass1_positionXstart = xPos; this.dataDoc.mass1_positionYstart = yPos; this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]); this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]); this.dataDoc.mass1_componentForces = JSON.stringify([forceOfTension, gravityParallel, gravityPerpendicular]); this._simReset++; } })} radianEquivalent={true} mode="Freeform" labelWidth="5em" /> Rod length} lowerBound={0} dataDoc={this.dataDoc} prop="pendulum_length" step={1} unit="m" upperBound={400} value={Math.round(this.pendulumLength)} effect={action(value => { if (this.simulationType == 'Pendulum') { this.dataDoc.pendulum_angleStart = this.pendulumAngle; this.dataDoc.pendulum_lengthStart = value; this._simReset++; } })} radianEquivalent={false} mode="Freeform" labelWidth="5em" />
)}
)}
{this.simulationMode == 'Freeform' && ( {(!this.dataDoc.simulation_paused || this.simulationType == 'Inclined Plane' || this.simulationType == 'Circular Motion' || this.simulationType == 'Pulley') && ( )}{' '} {this.dataDoc.simulation_paused && this.simulationType != 'Inclined Plane' && this.simulationType != 'Circular Motion' && this.simulationType != 'Pulley' && ( )}{' '} {(!this.dataDoc.simulation_paused || this.simulationType == 'Inclined Plane' || this.simulationType == 'Circular Motion' || this.simulationType == 'Pulley') && ( )}{' '} {this.dataDoc.simulation_paused && this.simulationType != 'Inclined Plane' && this.simulationType != 'Circular Motion' && this.simulationType != 'Pulley' && ( )}{' '} {(!this.dataDoc.simulation_paused || (this.simulationType != 'One Weight' && this.simulationType != 'Circular Motion')) && ( )}{' '} {this.dataDoc.simulation_paused && (this.simulationType == 'One Weight' || this.simulationType == 'Circular Motion') && ( )}{' '} {(!this.dataDoc.simulation_paused || this.simulationType != 'One Weight') && ( )}{' '} {this.dataDoc.simulation_paused && this.simulationType == 'One Weight' && ( )}{' '}
{this.simulationType == 'Pulley' ? 'Red Weight' : ''} X Y
{ // window.open( // "https://www.khanacademy.org/science/physics/two-dimensional-motion" // ); // }} > Position <>{this.dataDoc.mass1_positionX} m { this.dataDoc.mass1_xChange = value; if (this.simulationType == '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(NumCast(this.dataDoc.mass1_positionY)) + this.radius; let dir1T = Math.PI - Math.atan(deltaY / deltaX1); let dir2T = Math.atan(deltaY / deltaX2); let tensionMag2 = (this.mass1 * Math.abs(this.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, }; const tensionForce2: IForce = { description: 'Tension', magnitude: tensionMag2, directionInDegrees: dir2T, }; const gravity = this.gravityForce(this.mass1); this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]); } }} small={true} mode="Freeform" /> {`${NumCast(this.dataDoc.mass1_positionY)} m`} { this.dataDoc.mass1_yChange = value; if (this.simulationType == '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 = NumCast(this.dataDoc.mass1_positionX) + this.radius - x1rod; let deltaX2 = x2rod - (NumCast(this.dataDoc.mass1_positionX) + 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.mass1 * Math.abs(this.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, }; const tensionForce2: IForce = { description: 'Tension', magnitude: tensionMag2, directionInDegrees: dir2T, }; const gravity = this.gravityForce(this.mass1); this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]); } }} small={true} mode="Freeform" />
{ // window.open( // "https://www.khanacademy.org/science/physics/two-dimensional-motion" // ); // }} > Velocity {`${NumCast(this.dataDoc.mass1_velocityX)} m/s`} { this.dataDoc.mass1_velocityXstart = value; this._simReset++; })} small={true} mode="Freeform" /> <>{this.dataDoc.mass1_velocityY} m/s { this.dataDoc.mass1_velocityYstart = -value; }} small={true} mode="Freeform" />
{ // window.open( // "https://www.khanacademy.org/science/physics/two-dimensional-motion" // ); // }} > Acceleration <> {this.dataDoc.mass1_accelerationX} m/s2 <> {this.dataDoc.mass1_accelerationY} m/s2
Momentum {Math.round(NumCast(this.dataDoc.mass1_velocityX) * this.mass1 * 10) / 10} kg*m/s {Math.round(NumCast(this.dataDoc.mass1_velocityY) * this.mass1 * 10) / 10} kg*m/s
)} {this.simulationMode == 'Freeform' && this.simulationType == 'Pulley' && (
Blue Weight X Y
Position {`${this.dataDoc.mass2_positionX} m`} {`${this.dataDoc.mass2_positionY} m`}
Velocity {`${this.dataDoc.mass2_positionX} m/s`} {`${this.dataDoc.mass2_positionY} m/s`}
Acceleration <> {this.dataDoc.mass2_accelerationX} m/s2 <> {this.dataDoc.mass2_accelerationY} m/s2
Momentum {Math.round(NumCast(this.dataDoc.mass2_velocityX) * this.mass1 * 10) / 10} kg*m/s {Math.round(NumCast(this.dataDoc.mass2_velocityY) * this.mass1 * 10) / 10} kg*m/s
)}
{this.simulationType != 'Pendulum' && this.simulationType != 'Spring' && (

Kinematic Equations

  • Position: x1=x0+v0t+ 12at 2
  • Velocity: v1=v0+at
  • Acceleration: a = F/m
)} {this.simulationType == 'Spring' && (

Harmonic Motion Equations: Spring

  • Spring force: Fs=kd
  • Spring period: Ts=2π√mk
  • Equilibrium displacement for vertical spring: d = mg/k
  • Elastic potential energy: Us=12kd2
    • Maximum when system is at maximum displacement, 0 when system is at 0 displacement
  • Translational kinetic energy: K=12mv2
    • Maximum when system is at maximum/minimum velocity (at 0 displacement), 0 when velocity is 0 (at maximum displacement)
)} {this.simulationType == 'Pendulum' && (

Harmonic Motion Equations: Pendulum

  • Pendulum period: Tp=2π√lg
)}

{this.simulationType == 'Circular Motion' ? 'Z' : 'Y'}

X

); } }