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 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 Typography from '@mui/material/Typography'; 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'; interface IWallProps { length: number; xPos: number; yPos: number; angleInDegrees: number; } interface IForce { description: string; magnitude: number; directionInDegrees: number; component: boolean; } 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); } constructor(props: any) { super(props); } // Constants xMin = 0; yMin = 0; xMax = 100; yMax = 100; color = `rgba(0,0,0,0.5)`; radius = 50; wallPositions: IWallProps[] = []; componentDidMount() { // Used throughout sims this.layoutDoc._width = 1000; 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.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.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.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.selectedSolutions = []; this.dataDoc.selectedQuestion = this.dataDoc.selectedQuestion ?? questions.inclinePlane[0]; // this.dataDoc.sketching = this.dataDoc.sketching ?? false; // Used for tutorial mode this.dataDoc.selectedTutorial = this.dataDoc.selectedTutorial ?? tutorials.inclinePlane; // Used for uniform circular motion 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; // 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; // 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; // 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.updatedForces2 = this.dataDoc.updatedForces2 ?? []; this.dataDoc.mass2 = this.dataDoc.mass2 ?? 1; // Setup simulation this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); // Create walls let walls = []; walls.push({ length: (this.xMax / this.layoutDoc._width) * 100, xPos: 0, yPos: 0, angleInDegrees: 0 }); walls.push({ length: (this.xMax / this.layoutDoc._width) * 100, xPos: 0, yPos: (this.yMax / this.layoutDoc._height) * 100, angleInDegrees: 0 }); walls.push({ length: (this.yMax / this.layoutDoc._height) * 100, xPos: 0, yPos: 0, angleInDegrees: 90 }); walls.push({ length: (this.yMax / this.layoutDoc._height) * 100, xPos: (this.xMax / this.layoutDoc._width) * 100, yPos: 0, angleInDegrees: 90 }); this.wallPositions = walls; } componentDidUpdate() { if (this.xMax !== this.layoutDoc[WidthSym]() * 0.6 || this.yMax != this.layoutDoc[HeightSym]() * 0.9) { this.layoutDoc._width = Math.max(this.layoutDoc[WidthSym](), 800); 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); } } setupSimulation = (simulationType: string, mode: string) => { this.dataDoc.simulationPaused = true; if (simulationType != 'Circular Motion') { this.dataDoc.startVelX = 0; this.dataDoc.setStartVelY = 0; this.dataDoc.velocityXDisplay = 0; this.dataDoc.velocityYDisplay = 0; } if (mode == 'Freeform') { this.dataDoc.showForceMagnitudes = 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.updatedForces = [ { description: 'Gravity', magnitude: Math.abs(this.dataDoc.gravity) * this.dataDoc.mass, directionInDegrees: 270, component: false, }, ]; this.dataDoc.startForces = [ { description: 'Gravity', magnitude: Math.abs(this.dataDoc.gravity) * this.dataDoc.mass, directionInDegrees: 270, component: false, }, ]; this.dataDoc.simulationReset = !this.dataDoc.simulationReset; } else if (simulationType == 'Inclined Plane') { this.changeWedgeBasedOnNewAngle(26); this.dataDoc.startForces = [ { description: 'Gravity', magnitude: Math.abs(this.dataDoc.gravity) * this.dataDoc.mass, directionInDegrees: 270, component: false, }, ]; this.updateForcesWithFriction(Number(this.dataDoc.coefficientOfStaticFriction)); } else if (simulationType == 'Pendulum') { this.setupPendulum(); } else if (simulationType == 'Spring') { this.setupSpring(); } else if (simulationType == 'Circular Motion') { this.setupCircular(0); } else if (simulationType == 'Pulley') { this.setupPulley(); } else if (simulationType == 'Suspension') { 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.generateNewQuestion(); if (simulationType == 'One Weight') { // TODO - one weight review problems } else if (simulationType == 'Spring') { this.setupSpring(); // TODO - spring review problems } else if (simulationType == 'Inclined Plane') { this.dataDoc.updatedForces = []; this.dataDoc.startForces = []; } else if (simulationType == 'Pendulum') { this.setupPendulum(); // TODO - pendulum review problems } else if (simulationType == 'Circular Motion') { this.setupCircular(0); // TODO - circular motion review problems } else if (simulationType == 'Pulley') { this.setupPulley(); // TODO - pulley tutorial review problems } else if (simulationType == 'Suspension') { this.setupSuspension(); // TODO - suspension tutorial review problems } } else if (mode == 'Tutorial') { this.dataDoc.showComponentForces = 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; } else { this.dataDoc.velocityXDisplay = 20; this.dataDoc.velocityYDisplay = 0; this.dataDoc.showVelocity = 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](); 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.setupSpring(); this.dataDoc.startPosY = this.yMin + 200 + 19.62; this.dataDoc.startPosX = (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; 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.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.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.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.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.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.simulationReset = !this.dataDoc.simulationReset; } }; // Helper function to go between display and real values getDisplayYPos = (yPos: number) => { return this.yMax - yPos - 2 * (0.08 * this.layoutDoc[HeightSym]()) + 5; }; getYPosFromDisplay = (yDisplay: number) => { return this.yMax - yDisplay - 2 * (0.08 * this.layoutDoc[HeightSym]()) + 5; }; // Update forces when coefficient of static friction changes in freeform mode updateForcesWithFriction = (coefficient: number, width: number = this.dataDoc.wedgeWidth, height: number = this.dataDoc.wedgeHeight) => { const normalForce: IForce = { description: 'Normal Force', magnitude: Math.abs(this.dataDoc.gravity) * Math.cos(Math.atan(height / width)) * 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, 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; 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); } const frictionForceComponent: IForce = { description: 'Static Friction Force', magnitude: coefficient * Math.abs(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)), 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)), 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)), 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), directionInDegrees: 270, component: false, }; if (coefficient != 0) { this.dataDoc.startForces = [gravityForce, normalForce, frictionForce]; this.dataDoc.updatedForces = [gravityForce, normalForce, frictionForce]; this.dataDoc.componentForces = [frictionForceComponent, normalForceComponent, gravityParallel, gravityPerpendicular]; } else { this.dataDoc.startForces = [gravityForce, normalForce]; this.dataDoc.updatedForces = [gravityForce, normalForce]; this.dataDoc.componentForces = [normalForceComponent, gravityParallel, gravityPerpendicular]; } }; // Change wedge height and width and weight position to match new wedge angle changeWedgeBasedOnNewAngle = (angle: number) => { this.dataDoc.wedgeWidth = this.xMax * 0.5; this.dataDoc.wedgeHeight = Math.tan((angle * Math.PI) / 180) * this.xMax * 0.5; // update weight position based on updated wedge width/height let yPos = this.yMax - 0.08 * this.layoutDoc[HeightSym]() * 2 - Math.tan((angle * Math.PI) / 180) * this.xMax * 0.5; // adjust y position if (angle >= 5 && angle < 10) { yPos += 0.08 * this.layoutDoc[HeightSym]() * 0.1; } else if (angle >= 10 && angle < 15) { yPos += 0.08 * this.layoutDoc[HeightSym]() * 0.23; } else if (angle >= 15 && angle < 20) { yPos += 0.08 * this.layoutDoc[HeightSym]() * 0.26; } else if (angle >= 20 && angle < 25) { yPos += 0.08 * this.layoutDoc[HeightSym]() * 0.33; } else if (angle >= 25 && angle < 30) { yPos += 0.08 * this.layoutDoc[HeightSym]() * 0.35; } else if (angle >= 30 && angle < 35) { yPos += 0.08 * this.layoutDoc[HeightSym]() * 0.4; } else if (angle >= 35 && angle < 40) { yPos += 0.08 * this.layoutDoc[HeightSym]() * 0.45; } else if (angle >= 40 && angle < 45) { yPos += 0.08 * this.layoutDoc[HeightSym]() * 0.47; } else if (angle >= 45) { yPos += 0.08 * this.layoutDoc[HeightSym]() * 0.52; } this.dataDoc.startPosX = this.xMax * 0.25; this.dataDoc.startPosY = 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); } }; // In review mode, update forces when coefficient of static friction changed updateReviewForcesBasedOnCoefficient = (coefficient: number) => { let theta: number = Number(this.dataDoc.wedgeAngle); let index = this.dataDoc.selectedQuestion.variablesForQuestionSetup.indexOf('theta - max 45'); if (index >= 0) { theta = 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); 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); } this.dataDoc.reviewStaticMagnitude = friction; this.dataDoc.reviewStaticAngle = 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); 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); } this.dataDoc.reviewStaticMagnitude = friction; this.dataDoc.reviewStaticAngle = 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 index = question.variablesForQuestionSetup.indexOf('theta - max 45'); if (index >= 0) { theta = questionVars[index]; } let muS: number = Number(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(Number(description))) { solutions.push(Number(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)); } 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 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); 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 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 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 = 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.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) { error = true; } } else if (this.dataDoc.selectedQuestion.answerParts[i] == 'angle of gravity') { if (Math.abs(this.dataDoc.reviewGravityAngle - 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) { error = true; } } else if (this.dataDoc.selectedQuestion.answerParts[i] == 'angle of normal force') { if (Math.abs(this.dataDoc.reviewNormalAngle - 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) { error = true; } } else if (this.dataDoc.selectedQuestion.answerParts[i] == 'angle of static friction') { if (Math.abs(this.dataDoc.reviewStaticAngle - 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) { error = true; } } else if (this.dataDoc.selectedQuestion.answerParts[i] == 'wedge angle') { if (Math.abs(Number(this.dataDoc.wedgeAngle) - this.dataDoc.selectedSolutions[i]) > epsilon) { error = true; } } } } if (showAlert) { if (!error) { this.dataDoc.simulationPaused = false; setTimeout(() => { this.dataDoc.simulationPaused = true; }, 3000); } else { this.dataDoc.simulationPaused = false; setTimeout(() => { this.dataDoc.simulationPaused = true; }, 3000); } } if (this.dataDoc.selectedQuestion.goal == 'noMovement') { if (!error) { this.dataDoc.noMovement = true; } else { this.dataDoc.roMovement = false; } } }; // 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.coefficientOfKineticFriction = 0; this.dataDoc.simulationPaused = 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.dataDoc.simulationType == 'Inclined Plane') { if (this.dataDoc.questionNumber == questions.inclinePlane.length - 1) { this.dataDoc.questionNumber = 0; } else { this.dataDoc.questionNumber = this.dataDoc.questionNumber + 1; } question = questions.inclinePlane[this.dataDoc.questionNumber]; let coefficient = 0; let wedgeAngle = 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); wedgeAngle = 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.wedgeAngle = wedgeAngle; this.changeWedgeBasedOnNewAngle(wedgeAngle); this.dataDoc.coefficientOfStaticFriction = coefficient; this.dataDoc.reviewCoefficient = 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 = 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); }; // Default setup for uniform circular motion simulation setupCircular = (value: number) => { this.dataDoc.showComponentForces = false; this.dataDoc.startVelY = 0; this.dataDoc.startVelX = 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; const tensionForce: IForce = { description: 'Centripetal Force', magnitude: (this.dataDoc.startVelX ** 2 * this.dataDoc.mass) / this.dataDoc.circularMotionRadius, directionInDegrees: 90, component: false, }; this.dataDoc.updatedForces = [tensionForce]; this.dataDoc.startForces = [tensionForce]; this.dataDoc.simulationReset = !this.dataDoc.simulationReset; }; // Default setup for pendulum simulation setupPendulum = () => { 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; const mag = this.dataDoc.mass * Math.abs(this.dataDoc.gravity) * Math.sin((60 * Math.PI) / 180); const forceOfTension: IForce = { description: 'Tension', magnitude: mag, directionInDegrees: 90 - angle, component: false, }; const tensionComponent: IForce = { description: 'Tension', magnitude: mag, directionInDegrees: 90 - angle, component: true, }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', magnitude: this.dataDoc.mass * Math.abs(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), directionInDegrees: -angle, component: true, }; this.dataDoc.componentForces = [tensionComponent, gravityParallel, gravityPerpendicular]; this.dataDoc.updatedForces = [ { description: 'Gravity', magnitude: this.dataDoc.mass * Math.abs(this.dataDoc.gravity), directionInDegrees: 270, component: false, }, forceOfTension, ]; this.dataDoc.startForces = [ { description: 'Gravity', magnitude: this.dataDoc.mass * Math.abs(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 }; }; // Default setup for spring simulation setupSpring = () => { this.dataDoc.showComponentForces = false; const gravityForce: IForce = { description: 'Gravity', magnitude: Math.abs(this.dataDoc.gravity) * 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.springConstant = 0.5; this.dataDoc.springRestLength = 200; this.dataDoc.springStartLength = 200; this.dataDoc.simulationReset = !this.dataDoc.simulationReset; }; // 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)); const tensionForce1: IForce = { description: 'Tension', magnitude: tensionMag, directionInDegrees: 45, component: false, }; const tensionForce2: IForce = { description: 'Tension', magnitude: tensionMag, directionInDegrees: 135, component: false, }; const grav: IForce = { description: 'Gravity', magnitude: this.dataDoc.mass * Math.abs(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; }; // 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); const gravityForce1: IForce = { description: 'Gravity', magnitude: this.dataDoc.mass * Math.abs(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), directionInDegrees: 90, component: false, }; a *= -1; const gravityForce2: IForce = { description: 'Gravity', magnitude: this.dataDoc.mass2 * Math.abs(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), 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.updatedForces2 = [gravityForce2, tensionForce2]; this.dataDoc.startForces2 = [gravityForce2, tensionForce2]; this.dataDoc.simulationReset = !this.dataDoc.simulationReset; }; // Helper function used for tutorial and review mode getForceFromJSON = ( json: { description: string; magnitude: number; directionInDegrees: number; component: boolean; }[] ): IForce[] => { const forces: IForce[] = []; for (let i = 0; i < json.length; i++) { const force: IForce = { description: json[i].description, magnitude: json[i].magnitude, directionInDegrees: json[i].directionInDegrees, component: json[i].component, }; forces.push(force); } return forces; }; // Handle force change in review mode updateReviewModeValues = () => { const forceOfGravityReview: IForce = { description: 'Gravity', magnitude: this.dataDoc.reviewGravityMagnitude, directionInDegrees: this.dataDoc.reviewGravityAngle, component: false, }; const normalForceReview: IForce = { description: 'Normal Force', magnitude: this.dataDoc.reviewNormalMagnitude, directionInDegrees: this.dataDoc.reviewNormalAngle, component: false, }; const staticFrictionForceReview: IForce = { description: 'Static Friction Force', magnitude: this.dataDoc.reviewStaticMagnitude, directionInDegrees: this.dataDoc.reviewStaticAngle, component: false, }; this.dataDoc.startForces = [forceOfGravityReview, normalForceReview, staticFrictionForceReview]; this.dataDoc.updatedForces = [forceOfGravityReview, normalForceReview, staticFrictionForceReview]; }; render() { return (
{!this.dataDoc.simulationPaused && (
)}
{this.dataDoc.simulationType == 'Pulley' && ( )}
{(this.dataDoc.simulationType == 'One Weight' || this.dataDoc.simulationType == 'Inclined Plane') && this.wallPositions && this.wallPositions.map((element, index) => { return ; })}
{this.dataDoc.simulationPaused && this.dataDoc.mode != 'Tutorial' && ( { this.dataDoc.simulationPaused = false; }}> )} {!this.dataDoc.simulationPaused && this.dataDoc.mode != 'Tutorial' && ( { this.dataDoc.simulationPaused = true; }}> )} {this.dataDoc.simulationPaused && this.dataDoc.mode != 'Tutorial' && ( { this.dataDoc.simulationReset = !this.dataDoc.simulationReset; }}> )}
{this.dataDoc.mode == 'Review' && this.dataDoc.simulationType != 'Inclined Plane' && (

{this.dataDoc.simulationType} review problems in progress!


)} {this.dataDoc.mode == 'Review' && this.dataDoc.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.dataDoc.selectedQuestion.hints && this.dataDoc.selectedQuestion.hints.map((hint: any, index: number) => { return (
Hint {index + 1}: {hint.description} {hint.content}
); })}

{this.dataDoc.questionPartOne}

{this.dataDoc.questionPartTwo}

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

} lowerBound={0} dataDoc={this.dataDoc} prop={'reviewGravityMagnitude'} 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')]} labelWidth={'7em'} /> )} {this.dataDoc.selectedQuestion.answerParts.includes('angle of gravity') && ( Gravity angle

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

} lowerBound={0} dataDoc={this.dataDoc} prop={'reviewNormalMagnitude'} 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')]} labelWidth={'7em'} /> )} {this.dataDoc.selectedQuestion.answerParts.includes('angle of normal force') && ( Normal force angle

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

} lowerBound={0} dataDoc={this.dataDoc} prop={'reviewStaticMagnitude'} 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')]} labelWidth={'7em'} /> )} {this.dataDoc.selectedQuestion.answerParts.includes('angle of static friction') && ( Static friction angle

} lowerBound={0} dataDoc={this.dataDoc} prop={'reviewStaticAngle'} step={1} unit={'°'} upperBound={360} value={this.dataDoc.reviewStaticAngle} radianEquivalent={true} showIcon={this.dataDoc.showIcon} correctValue={this.dataDoc.answers[this.dataDoc.selectedQuestion.answerParts.indexOf('angle of static friction')]} labelWidth={'7em'} /> )} {this.dataDoc.selectedQuestion.answerParts.includes('coefficient of static friction') && ( μs } lowerBound={0} dataDoc={this.dataDoc} prop={'coefficientOfStaticFriction'} step={0.1} unit={''} upperBound={1} value={this.dataDoc.coefficientOfStaticFriction} effect={this.updateReviewForcesBasedOnCoefficient} showIcon={this.dataDoc.showIcon} correctValue={this.dataDoc.answers[this.dataDoc.selectedQuestion.answerParts.indexOf('coefficient of static friction')]} /> )} {this.dataDoc.selectedQuestion.answerParts.includes('wedge angle') && ( θ} lowerBound={0} dataDoc={this.dataDoc} prop={'wedgeAngle'} step={1} unit={'°'} upperBound={49} value={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')]} /> )}
)} {this.dataDoc.mode == 'Tutorial' && (

Problem

{this.dataDoc.selectedTutorial.question}

{ let step = 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.updatedForces = this.getForceFromJSON(this.dataDoc.selectedTutorial.steps[step].forces); this.dataDoc.showForceMagnitudes = this.dataDoc.selectedTutorial.steps[step].showMagnitude; }} disabled={this.dataDoc.stepNumber == 0}>

Step {this.dataDoc.stepNumber + 1}: {this.dataDoc.selectedTutorial.steps[this.dataDoc.stepNumber].description}

{this.dataDoc.selectedTutorial.steps[this.dataDoc.stepNumber].content}

{ let step = 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.updatedForces = this.getForceFromJSON(this.dataDoc.selectedTutorial.steps[step].forces); this.dataDoc.showForceMagnitudes = this.dataDoc.selectedTutorial.steps[step].showMagnitude; }} disabled={this.dataDoc.stepNumber == this.dataDoc.selectedTutorial.steps.length - 1}>
{(this.dataDoc.simulationType == 'One Weight' || this.dataDoc.simulationType == 'Inclined Plane' || this.dataDoc.simulationType == 'Pendulum') &&

Resources

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

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

)} {this.dataDoc.mode == 'Freeform' && (
{this.dataDoc.simulationType == 'One Weight' && ( (this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions)} />} label="Make collisions elastic" labelPlacement="start" /> )} (this.dataDoc.showForces = !this.dataDoc.showForces)} />} label="Show force vectors" labelPlacement="start" /> {(this.dataDoc.simulationType == 'Inclined Plane' || this.dataDoc.simulationType == 'Pendulum') && ( (this.dataDoc.showComponentForces = !this.dataDoc.showComponentForces)} />} label="Show component force vectors" labelPlacement="start" /> )} (this.dataDoc.showAcceleration = !this.dataDoc.showAcceleration)} />} label="Show acceleration vector" labelPlacement="start" /> (this.dataDoc.showVelocity = !this.dataDoc.showVelocity)} />} label="Show velocity vector" labelPlacement="start" /> Speed} 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' && ( Gravity} lowerBound={-30} dataDoc={this.dataDoc} prop={'gravity'} step={0.01} unit={'m/s2'} upperBound={0} value={this.dataDoc.gravity ?? -9.81} effect={(val: number) => { this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); }} labelWidth={'5em'} /> )} {this.dataDoc.simulationPaused && this.dataDoc.simulationType != 'Pulley' && ( Mass} lowerBound={1} dataDoc={this.dataDoc} prop={'mass'} step={0.1} unit={'kg'} upperBound={5} value={this.dataDoc.mass ?? 1} effect={(val: number) => { this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); }} labelWidth={'5em'} /> )} {this.dataDoc.simulationPaused && this.dataDoc.simulationType == 'Pulley' && ( Red mass} lowerBound={1} dataDoc={this.dataDoc} prop={'mass'} step={0.1} unit={'kg'} upperBound={5} value={this.dataDoc.mass ?? 1} effect={(val: number) => { this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); }} labelWidth={'5em'} /> )} {this.dataDoc.simulationPaused && this.dataDoc.simulationType == 'Pulley' && ( Blue mass} lowerBound={1} dataDoc={this.dataDoc} prop={'mass2'} step={0.1} unit={'kg'} upperBound={5} value={this.dataDoc.mass2 ?? 1} effect={(val: number) => { this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); }} labelWidth={'5em'} /> )} {this.dataDoc.simulationPaused && this.dataDoc.simulationType == 'Circular Motion' && ( Rod length} lowerBound={100} dataDoc={this.dataDoc} prop={'circularMotionRadius'} step={5} unit={'m'} upperBound={250} value={this.dataDoc.circularMotionRadius ?? 100} effect={(val: number) => { this.setupSimulation(this.dataDoc.simulationType, this.dataDoc.mode); }} labelWidth={'5em'} /> )} {this.dataDoc.simulationType == 'Spring' && this.dataDoc.simulationPaused && (
Spring stiffness} lowerBound={0.1} dataDoc={this.dataDoc} prop={'springConstant'} step={1} unit={'N/m'} upperBound={500} value={this.dataDoc.springConstant ?? 0.5} effect={(val: number) => { this.dataDoc.simulationReset(!this.dataDoc.simulationReset); }} radianEquivalent={false} mode={'Freeform'} labelWidth={'7em'} /> Rest length} lowerBound={10} dataDoc={this.dataDoc} prop={'springRestLength'} step={100} unit={''} upperBound={500} value={this.dataDoc.springRestLength ?? 200} effect={(val: number) => { this.dataDoc.simulationReset = !this.dataDoc.simulationReset; }} radianEquivalent={false} mode={'Freeform'} labelWidth={'7em'} /> Starting displacement} lowerBound={-(this.dataDoc.springRestLength - 10)} dataDoc={this.dataDoc} prop={''} step={10} unit={''} upperBound={this.dataDoc.springRestLength} value={this.dataDoc.springStartLength - this.dataDoc.springRestLength ?? 0} effect={(val: number) => { this.dataDoc.startPosY = this.dataDoc.springRestLength + val; this.dataDoc.springStartLength = this.dataDoc.springRestLength + val; this.dataDoc.simulationReset = !this.dataDoc.simulationReset; }} radianEquivalent={false} mode={'Freeform'} labelWidth={'7em'} />
)} {this.dataDoc.simulationType == 'Inclined Plane' && this.dataDoc.simulationPaused && (
θ} lowerBound={0} dataDoc={this.dataDoc} prop={'wedgeAngle'} step={1} unit={'°'} upperBound={49} value={this.dataDoc.wedgeAngle ?? 26} effect={(val: number) => { this.changeWedgeBasedOnNewAngle(val); this.dataDoc.simulationReset = !this.dataDoc.simulationReset; }} radianEquivalent={true} mode={'Freeform'} labelWidth={'2em'} /> μs } lowerBound={0} dataDoc={this.dataDoc} prop={'coefficientOfStaticFriction'} step={0.1} unit={''} upperBound={1} value={this.dataDoc.coefficientOfStaticFriction ?? 0} effect={(val: number) => { this.updateForcesWithFriction(val); if (val < Number(this.dataDoc.coefficientOfKineticFriction)) { this.dataDoc.soefficientOfKineticFriction = val; } this.dataDoc.simulationReset = !this.dataDoc.simulationReset; }} mode={'Freeform'} labelWidth={'2em'} /> μk } lowerBound={0} dataDoc={this.dataDoc} prop={'coefficientOfKineticFriction'} step={0.1} unit={''} upperBound={Number(this.dataDoc.coefficientOfStaticFriction)} value={this.dataDoc.coefficientOfKineticFriction ?? 0} effect={(val: number) => { this.dataDoc.simulationReset = !this.dataDoc.simulationReset; }} mode={'Freeform'} labelWidth={'2em'} />
)} {this.dataDoc.simulationType == 'Inclined Plane' && !this.dataDoc.simulationPaused && ( θ: {Math.round(Number(this.dataDoc.wedgeAngle) * 100) / 100}° ≈ {Math.round(((Number(this.dataDoc.wedgeAngle) * Math.PI) / 180) * 100) / 100} rad
μ s: {this.dataDoc.coefficientOfStaticFriction}
μ k: {this.dataDoc.coefficientOfKineticFriction}
)} {this.dataDoc.simulationType == 'Pendulum' && !this.dataDoc.simulationPaused && ( θ: {Math.round(this.dataDoc.pendulumAngle * 100) / 100}° ≈ {Math.round(((this.dataDoc.pendulumAngle * Math.PI) / 180) * 100) / 100} rad )} {this.dataDoc.simulationType == 'Pendulum' && this.dataDoc.simulationPaused && (
Angle} lowerBound={0} dataDoc={this.dataDoc} prop={'pendulumAngle'} step={1} unit={'°'} upperBound={59} value={this.dataDoc.pendulumAngle ?? 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); const forceOfTension: IForce = { description: 'Tension', magnitude: mag, directionInDegrees: 90 - value, component: false, }; const tensionComponent: IForce = { description: 'Tension', magnitude: mag, directionInDegrees: 90 - value, component: true, }; const gravityParallel: IForce = { description: 'Gravity Parallel Component', magnitude: Math.abs(this.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), directionInDegrees: -value, component: true, }; const length = this.dataDoc.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 - this.dataDoc.radius; const yPos = y - this.dataDoc.radius - 5; this.dataDoc.startPosX = xPos; this.dataDoc.startPosY = yPos; this.dataDoc.startForces = [ { description: 'Gravity', magnitude: Math.abs(this.dataDoc.gravity) * this.dataDoc.mass, directionInDegrees: 270, component: false, }, forceOfTension, ]; this.dataDoc.updatedForces = [ { description: 'Gravity', magnitude: Math.abs(this.dataDoc.gravity) * 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; } }} radianEquivalent={true} mode={'Freeform'} labelWidth={'5em'} /> Rod length} lowerBound={0} dataDoc={this.dataDoc} prop={'pendulumLength'} step={1} unit={'m'} upperBound={400} value={Math.round(this.dataDoc.pendulumLength)} effect={value => { if (this.dataDoc.simulationType == 'Pendulum') { this.dataDoc.adjustPendulumAngle = { angle: this.dataDoc.pendulumAngle, length: value, }; this.dataDoc.simulationReset = !this.dataDoc.simulationReset; } }} radianEquivalent={false} mode={'Freeform'} labelWidth={'5em'} />
)}
)}
{this.dataDoc.mode == 'Freeform' && ( {(!this.dataDoc.simulationPaused || this.dataDoc.simulationType == 'Inclined Plane' || this.dataDoc.simulationType == 'Circular Motion' || this.dataDoc.simulationType == 'Pulley') && ( )}{' '} {this.dataDoc.simulationPaused && this.dataDoc.simulationType != 'Inclined Plane' && this.dataDoc.simulationType != 'Circular Motion' && this.dataDoc.simulationType != 'Pulley' && ( )}{' '} {(!this.dataDoc.simulationPaused || this.dataDoc.simulationType == 'Inclined Plane' || this.dataDoc.simulationType == 'Circular Motion' || this.dataDoc.simulationType == 'Pulley') && ( )}{' '} {this.dataDoc.simulationPaused && this.dataDoc.simulationType != 'Inclined Plane' && this.dataDoc.simulationType != 'Circular Motion' && this.dataDoc.simulationType != 'Pulley' && ( )}{' '} {(!this.dataDoc.simulationPaused || (this.dataDoc.simulationType != 'One Weight' && this.dataDoc.simulationType != 'Circular Motion')) && ( )}{' '} {this.dataDoc.simulationPaused && (this.dataDoc.simulationType == 'One Weight' || this.dataDoc.simulationType == 'Circular Motion') && ( )}{' '} {(!this.dataDoc.simulationPaused || this.dataDoc.simulationType != 'One Weight') && }{' '} {this.dataDoc.simulationPaused && this.dataDoc.simulationType == 'One Weight' && ( )}{' '}
{this.dataDoc.simulationType == 'Pulley' ? 'Red Weight' : ''} X Y
{ // window.open( // "https://www.khanacademy.org/science/physics/two-dimensional-motion" // ); // }} > Position {this.dataDoc.positionXDisplay} m { this.dataDoc.displayChange = { xDisplay: value, yDisplay: this.dataDoc.positionYDisplay, }; if (this.dataDoc['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(this.dataDoc.positionYDisplay) + 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 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.dataDoc.mass * Math.abs(this.dataDoc.gravity), directionInDegrees: 270, component: false, }; this.dataDoc['updatedForces'] = [tensionForce1, tensionForce2, grav]; } }} small={true} mode={'Freeform'} /> {this.dataDoc.positionYDisplay} m { this.dataDoc.displayChange = { xDisplay: this.dataDoc.positionXDisplay, yDisplay: value, }; if (this.dataDoc['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 = this.dataDoc.positionXDisplay + this.radius - x1rod; let deltaX2 = x2rod - (this.dataDoc.positionXDisplay + 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 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.dataDoc.mass * Math.abs(this.dataDoc.gravity), directionInDegrees: 270, component: false, }; this.dataDoc['updatedForces'] = [tensionForce1, tensionForce2, grav]; } }} small={true} mode={'Freeform'} />
{ // window.open( // "https://www.khanacademy.org/science/physics/two-dimensional-motion" // ); // }} > Velocity {this.dataDoc.velocityXDisplay} m/s { this.dataDoc.startVelX = value; this.dataDoc.simulationReset = !this.dataDoc.simulationReset; }} small={true} mode={'Freeform'} /> {this.dataDoc.velocityYDisplay} m/s { this.dataDoc.startVelY = -value; this.dataDoc.displayChange = { xDisplay: this.dataDoc.positionXDisplay, yDisplay: this.dataDoc.positionYDisplay, }; }} small={true} mode={'Freeform'} />
{ // window.open( // "https://www.khanacademy.org/science/physics/two-dimensional-motion" // ); // }} > Acceleration {this.dataDoc.accelerationXDisplay} m/s2 {this.dataDoc.accelerationYDisplay} m/s2
Momentum {Math.round(this.dataDoc.velocityXDisplay * this.dataDoc.mass * 10) / 10} kg*m/s {Math.round(this.dataDoc.velocityYDisplay * this.dataDoc.mass * 10) / 10} kg*m/s
)} {this.dataDoc.mode == 'Freeform' && this.dataDoc.simulationType == 'Pulley' && (
Blue Weight X Y
Position {this.dataDoc.positionXDisplay2} m {this.dataDoc.positionYDisplay2} m
Velocity {this.dataDoc.velocityXDisplay2} m/s {this.dataDoc.velocityYDisplay2} m/s
Acceleration {this.dataDoc.accelerationXDisplay2} m/s2 {this.dataDoc.accelerationYDisplay2} m/s2
Momentum {Math.round(this.dataDoc.velocityXDisplay2 * this.dataDoc.mass * 10) / 10} kg*m/s {Math.round(this.dataDoc.velocityYDisplay2 * this.dataDoc.mass * 10) / 10} kg*m/s
)}
{this.dataDoc.simulationType != 'Pendulum' && this.dataDoc.simulationType != 'Spring' && (

Kinematic Equations

  • Position: x1=x0+v0t+ 12at 2
  • Velocity: v1=v0+at
  • Acceleration: a = F/m
)} {this.dataDoc.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.dataDoc.simulationType == 'Pendulum' && (

Harmonic Motion Equations: Pendulum

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

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

X

); } }