From 0cda1a8f8b4e3d79c30291685456c7cb62fd7e8e Mon Sep 17 00:00:00 2001 From: brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> Date: Mon, 24 Apr 2023 13:39:14 -0400 Subject: add folder for physics box --- .../nodes/PhysicsBox/PhysicsSimulationBox.scss | 72 ++ .../nodes/PhysicsBox/PhysicsSimulationBox.tsx | 494 ++++++++++++ .../nodes/PhysicsBox/PhysicsSimulationWall.tsx | 34 + .../nodes/PhysicsBox/PhysicsSimulationWedge.tsx | 83 ++ .../nodes/PhysicsBox/PhysicsSimulationWeight.tsx | 860 +++++++++++++++++++++ 5 files changed, 1543 insertions(+) create mode 100644 src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss create mode 100644 src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx create mode 100644 src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx create mode 100644 src/client/views/nodes/PhysicsBox/PhysicsSimulationWedge.tsx create mode 100644 src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx (limited to 'src/client/views/nodes/PhysicsBox') diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss new file mode 100644 index 000000000..0f05010b4 --- /dev/null +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss @@ -0,0 +1,72 @@ +* { + box-sizing: border-box; + font-size: 14px; +} + +.mechanicsSimulationContainer { + background-color: white; + height: 100%; + width: 100%; + display: flex; + + .mechanicsSimulationEquationContainer { + position: fixed; + left: 70%; + padding: 1em; + + .mechanicsSimulationControls { + display: flex; + justify-content: space-between; + } + } +} + +.coordinateSystem { + z-index: -100; +} + +th, +td { + border-collapse: collapse; + padding: 1em; +} + +table { + min-width: 300px; +} + +tr:nth-child(even) { + background-color: #d6eeee; +} + +button { + z-index: 50; +} + +.angleLabel { + font-weight: bold; + font-size: 20px; + user-select: none; + pointer-events: none; +} + +.mechanicsSimulationSettingsMenu { + width: 100%; + height: 100%; + font-size: 12px; + background-color: rgb(224, 224, 224); + border-radius: 2px; + border-color: black; + border-style: solid; + padding: 10px; + position: fixed; + z-index: 1000; +} + +.mechanicsSimulationSettingsMenuRow { + display: flex; +} + +.mechanicsSimulationSettingsMenuRowDescription { + width: 50%; +} \ No newline at end of file diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx new file mode 100644 index 000000000..d0e854263 --- /dev/null +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx @@ -0,0 +1,494 @@ +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 Weight from "./PhysicsSimulationWeight"; +import Wall from "./PhysicsSimulationWall" +import Wedge from "./PhysicsSimulationWedge" +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { CheckBox } from "../search/CheckBox"; +export interface IForce { + description: string; + magnitude: number; + directionInDegrees: number; +} +export interface IWallProps { + length: number; + xPos: number; + yPos: number; + angleInDegrees: number; +} + +interface PhysicsVectorTemplate { + top: number; + left: number; + width: number; + height: number; + x1: number; + y1: number; + x2: number; + y2: number; + weightX: number; + weightY: number; +} + +@observer +export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent() { + + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PhysicsSimulationBox, fieldKey); } + + // Constants + gravityMagnitude = 9.81; + forceOfGravity: IForce = { + description: "Gravity", + magnitude: this.gravityMagnitude, + directionInDegrees: 270, + }; + xMin = 0; + yMin = 0; + xMax = 300; + yMax = 300; + color = `rgba(0,0,0,0.5)`; + radius = 0.1*this.yMax + update = true + menuIsOpen = false + + constructor(props: any) { + super(props); + } + + // Add one weight to the simulation + addWeight () { + this.dataDoc.weight = true; + this.dataDoc.wedge = false; + this.dataDoc.pendulum = false; + this.addWalls(); + }; + + // Set weight defaults + setToWeightDefault () { + this.dataDoc.startPosY = this.yMin+this.radius; + this.dataDoc.startPosX = (this.xMax+this.xMin-this.radius)/2; + this.dataDoc.updatedForces = [this.forceOfGravity]; + this.dataDoc.startForces = [this.forceOfGravity]; + } + + // Add a wedge with a One Weight to the simulation + addWedge () { + this.dataDoc.weight = true; + this.dataDoc.wedge = true; + this.dataDoc.pendulum = false; + this.addWalls(); + }; + + // Set wedge defaults + setToWedgeDefault () { + this.changeWedgeBasedOnNewAngle(26); + this.updateForcesWithFriction(this.dataDoc.coefficientOfStaticFriction); + } + + // Add a simple pendulum to the simulation + addPendulum = () => { + this.dataDoc.weight = true; + this.dataDoc.wedge = false; + this.dataDoc.pendulum = true; + this.removeWalls(); + let angle = this.dataDoc.pendulumAngle; + let mag = 9.81 * Math.cos((angle * Math.PI) / 180); + let forceOfTension: IForce = { + description: "Tension", + magnitude: mag, + directionInDegrees: 90 - angle, + }; + this.dataDoc.updatedForces = [this.forceOfGravity, forceOfTension]; + this.dataDoc.startForces = [this.forceOfGravity, forceOfTension]; + }; + + // Set pendulum defaults + setToPendulumDefault () { + let length = this.xMax*0.7; + let angle = 35; + let x = length * Math.cos(((90 - angle) * Math.PI) / 180); + let y = length * Math.sin(((90 - angle) * Math.PI) / 180); + let xPos = this.xMax / 2 - x - this.radius; + let yPos = y - this.radius; + this.dataDoc.startPosX = xPos; + this.dataDoc.startPosY = yPos; + let mag = 9.81 * Math.cos((angle * Math.PI) / 180); + this.dataDoc.pendulumAngle = angle; + this.dataDoc.pendulumLength = length; + this.dataDoc.startPendulumAngle = angle; + this.dataDoc.adjustPendulumAngle = !this.dataDoc.adjustPendulumAngle; + } + + // Update forces when coefficient of static friction changes in freeform mode + updateForcesWithFriction ( + coefficient: number, + width: number = this.dataDoc.wedgeWidth, + height: number = this.dataDoc.wedgeHeight + ) { + let normalForce = { + description: "Normal Force", + magnitude: this.forceOfGravity.magnitude * Math.cos(Math.atan(height / width)), + directionInDegrees: + 180 - 90 - (Math.atan(height / width) * 180) / Math.PI, + }; + let frictionForce: IForce = { + description: "Static Friction Force", + magnitude: + coefficient * + this.forceOfGravity.magnitude * + Math.cos(Math.atan(height / width)), + directionInDegrees: 180 - (Math.atan(height / width) * 180) / Math.PI, + }; + // reduce magnitude of friction force if necessary such that block cannot slide up plane + let yForce = -this.forceOfGravity.magnitude; + 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) + + this.forceOfGravity.magnitude) / + Math.sin((frictionForce.directionInDegrees * Math.PI) / 180); + } + if (coefficient != 0) { + this.dataDoc.startForces = [this.forceOfGravity, normalForce, frictionForce]; + this.dataDoc.updatedForces = [this.forceOfGravity, normalForce, frictionForce]; + } else { + this.dataDoc.startForces = [this.forceOfGravity, normalForce]; + this.dataDoc.updatedForces = [this.forceOfGravity, normalForce]; + } + }; + + // Change wedge height and width and weight position to match new wedge angle + changeWedgeBasedOnNewAngle = (angle: number) => { + let width = 0; + let height = 0; + if (angle < 50) { + width = this.xMax*0.6; + height = Math.tan((angle * Math.PI) / 180) * width; + this.dataDoc.wedgeWidth = width; + this.dataDoc.wedgeHeight = height; + } else if (angle < 70) { + width = this.xMax*0.3; + height = Math.tan((angle * Math.PI) / 180) * width; + this.dataDoc.wedgeWidth = width; + this.dataDoc.wedgeHeight = height; + } else { + width = this.xMax*0.15; + height = Math.tan((angle * Math.PI) / 180) * width; + this.dataDoc.wedgeWidth = width; + this.dataDoc.wedgeHeight = height; + } + + // update weight position based on updated wedge width/height + let xPos = (this.xMax * 0.2)-this.radius; + let yPos = width * Math.tan((angle * Math.PI) / 180) - this.radius; + + this.dataDoc.startPosX = xPos; + this.dataDoc.startPosY = this.getDisplayYPos(yPos); + this.updateForcesWithFriction( + Number(this.dataDoc.coefficientOfStaticFriction), + width, + height + ); + this.dataDoc['updateDisplay'] = !this.dataDoc['updateDisplay'] + }; + + // Helper function to go between display and real values + getDisplayYPos = (yPos: number) => { + return this.yMax - yPos - 2 * 50 + 5; + }; + + // In review mode, edit force arrow sketch on mouse movement + editForce = (element: PhysicsVectorTemplate) => { + if (!this.dataDoc.sketching) { + let sketches = this.dataDoc.forceSketches.filter((sketch: PhysicsVectorTemplate) => sketch != element); + this.dataDoc.forceSketches = sketches; + this.dataDoc.currentForceSketch = element; + this.dataDoc.sketching = true; + } + }; + + // In review mode, used to delete force arrow sketch on SHIFT+click + deleteForce = (element: PhysicsVectorTemplate) => { + if (!this.dataDoc.sketching) { + let sketches = this.dataDoc.forceSketches.filter((sketch: PhysicsVectorTemplate) => sketch != element); + this.dataDoc.forceSketches = sketches; + } + }; + + // Remove floor and walls from simulation + removeWalls = () => { + this.dataDoc.wallPositions = [] + }; + + // Add floor and walls to simulation + addWalls = () => { + let walls = []; + walls.push({ length: 100, xPos: 0, yPos: 97, angleInDegrees: 0 }); + walls.push({ length: 100, xPos: 0, yPos: 0, angleInDegrees: 90 }); + walls.push({ length: 100, xPos: 97, yPos: 0, angleInDegrees: 90 }); + this.dataDoc.wallPositions = walls + }; + + + componentDidMount() { + this.xMax = this.layoutDoc._width; + this.yMax = this.layoutDoc._height; + this.radius = 0.1*this.layoutDoc._height; + + // Add weight + if (this.dataDoc.simulationType == "Inclined Plane") { + this.addWedge() + } else if (this.dataDoc.simulationType == "Pendulum") { + this.addPendulum() + } else { + this.dataDoc.simulationType = "Free Weight" + this.addWeight() + } + this.dataDoc.accelerationXDisplay = this.dataDoc.accelerationXDisplay ?? 0; + this.dataDoc.accelerationYDisplay = this.dataDoc.accelerationYDisplay ?? 0; + this.dataDoc.coefficientOfKineticFriction = this.dataDoc.coefficientOfKineticFriction ?? 0; + this.dataDoc.coefficientOfStaticFriction = this.dataDoc.coefficientOfStaticFriction ?? 0; + this.dataDoc.currentForceSketch = this.dataDoc.currentForceSketch ?? []; + this.dataDoc.elasticCollisions = this.dataDoc.elasticCollisions ?? false; + this.dataDoc.forceSketches = this.dataDoc.forceSketches ?? []; + this.dataDoc.pendulumAngle = this.dataDoc.pendulumAngle ?? 26; + this.dataDoc.pendulumLength = this.dataDoc.pendulumLength ?? 300; + this.dataDoc.positionXDisplay = this.dataDoc.positionXDisplay ?? 0; + this.dataDoc.positionYDisplay = this.dataDoc.positionYDisplay ?? 0; + this.dataDoc.showAcceleration = this.dataDoc.showAcceleration ?? false; + this.dataDoc.showForceMagnitudes = this.dataDoc.showForceMagnitudes ?? false; + this.dataDoc.showForces = this.dataDoc.showForces ?? false; + this.dataDoc.showVelocity = this.dataDoc.showVelocity ?? false; + this.dataDoc.startForces = this.dataDoc.startForces ?? [this.forceOfGravity]; + this.dataDoc.startPendulumAngle = this.dataDoc.startPendulumAngle ?? 0; + this.dataDoc.startPosX = this.dataDoc.startPosX ?? 50; + this.dataDoc.startPosY = this.dataDoc.startPosY ?? 50; + this.dataDoc.stepNumber = this.dataDoc.stepNumber ?? 0; + this.dataDoc.updateDisplay = this.dataDoc.updateDisplay ?? false; + this.dataDoc.updatedForces = this.dataDoc.updatedForces ?? [this.forceOfGravity]; + this.dataDoc.velocityXDisplay = this.dataDoc.velocityXDisplay ?? 0; + this.dataDoc.velocityYDisplay = this.dataDoc.velocityYDisplay ?? 0; + this.dataDoc.wallPositions = this.dataDoc.wallPositions ?? []; + this.dataDoc.wedgeAngle = this.dataDoc.wedgeAngle ?? 26; + this.dataDoc.wedgeHeight = this.dataDoc.wedgeHeight ?? Math.tan((26 * Math.PI) / 180) * this.xMax*0.6; + this.dataDoc.wedgeWidth = this.dataDoc.wedgeWidth ?? this.xMax*0.6; + + this.dataDoc.adjustPendulumAngle = true; + this.dataDoc.simulationPaused = true; + this.dataDoc.simulationReset = false; + + // Add listener for SHIFT key, which determines if sketch force arrow will be edited or deleted on click + document.addEventListener("keydown", (e) => { + if (e.shiftKey) { + this.dataDoc.deleteMode = true; + } + }); + document.addEventListener("keyup", (e) => { + if (e.shiftKey) { + this.dataDoc.deleteMode = false; + } + }); + } + + componentDidUpdate() { + this.xMax = this.layoutDoc._width; + this.yMax = this.layoutDoc._height; + this.radius = 0.1*this.layoutDoc._height; + } + + render () { + return ( +
+
+
+
+
+ {this.dataDoc.weight && ( + + )} + {this.dataDoc.wedge && ( + + )} +
+
+ {(this.dataDoc.wallPositions ?? []).map((element: { length: number; xPos: number; yPos: number; angleInDegrees: number; }, index: React.Key | null | undefined) => { + return ( +
+ +
+ ); + })} +
+
+
+
+ {this.menuIsOpen && ( +
+
{this.menuIsOpen = false; this.dataDoc.simulationReset = !this.dataDoc.simulationReset;}}> + +
+

Simulation Settings

+
+

Show forces

+
{this.dataDoc.showForces = !this.dataDoc.showForces}}/>
+
+
+

Show acceleration

+
{this.dataDoc.showAcceleration = !this.dataDoc.showAcceleration}}/>
+
+
+
+

Show velocity

+
{this.dataDoc.showVelocity = !this.dataDoc.showVelocity}}/>
+
+
+ {this.dataDoc.simulationType == "Free Weight" &&
+

Elastic collisions

+
{this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions}}/>
+
} + {this.dataDoc.simulationType == "Pendulum" &&
+

Pendulum start angle

+
+ { + let angle = e.target.value; + if (angle > 35) { + angle = 35 + } + if (angle < 0) { + angle = 0 + } + let length = this.xMax*0.7; + let x = length * Math.cos(((90 - angle) * Math.PI) / 180); + let y = length * Math.sin(((90 - angle) * Math.PI) / 180); + let xPos = this.xMax / 2 - x - this.radius; + let yPos = y - this.radius; + this.dataDoc.startPosX = xPos; + this.dataDoc.startPosY = yPos; + let mag = 9.81 * Math.cos((angle * Math.PI) / 180); + this.dataDoc.pendulumAngle = angle; + this.dataDoc.pendulumLength = length; + this.dataDoc.startPendulumAngle = angle; + this.dataDoc.adjustPendulumAngle = !this.dataDoc.adjustPendulumAngle; + }} + /> +
+
} + {this.dataDoc.simulationType == "Inclined Plane" &&
+

Inclined plane angle

+
+ { + let angle = e.target.value ?? 0 + if (angle > 70) { + angle = 70 + } + if (angle < 0) { + angle = 0 + } + this.dataDoc.wedgeAngle = angle + this.changeWedgeBasedOnNewAngle(angle) + }} + /> +
+
} +
+ )} +
+
+
+
+ {this.dataDoc.simulationPaused && ( + + )} + {!this.dataDoc.simulationPaused && ( + + )} + {this.dataDoc.simulationPaused && ( + + )} + {this.dataDoc.simulationPaused && ( )} + +
+
+
+
+
+ ); + } + } \ No newline at end of file diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx new file mode 100644 index 000000000..9283e8d46 --- /dev/null +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx @@ -0,0 +1,34 @@ +import React = require('react'); + +export interface Force { + magnitude: number; + directionInDegrees: number; +} +export interface IWallProps { + length: number; + xPos: number; + yPos: number; + angleInDegrees: number; +} + +export default class App extends React.Component { + + constructor(props: any) { + super(props) + } + + wallStyle = { + width: this.props.angleInDegrees == 0 ? this.props.length + "%" : "3%", + height: this.props.angleInDegrees == 0 ? "3%" : this.props.length + "%", + position: "absolute" as "absolute", + left: this.props.xPos + "%", + top: this.props.yPos + "%", + backgroundColor: "#6c7b8b", + margin: 0, + padding: 0, + }; + + render () { + return (
); + } +}; diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWedge.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWedge.tsx new file mode 100644 index 000000000..6134a6bc0 --- /dev/null +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWedge.tsx @@ -0,0 +1,83 @@ +import React = require('react'); +import "./PhysicsSimulationBox.scss"; + +export interface IWedgeProps { + startHeight: number; + startWidth: number; + startLeft: number; + xMax: number; + yMax: number; +} + +interface IState { + angleInRadians: number, + left: number, + coordinates: string, +} + +export default class Wedge extends React.Component { + + constructor(props: any) { + super(props) + this.state = { + angleInRadians: Math.atan(this.props.startHeight / this.props.startWidth), + left: this.props.startLeft, + coordinates: "", + } + } + + color = "#deb887"; + + updateCoordinates() { + const coordinatePair1 = + Math.round(this.state.left) + "," + Math.round(this.props.yMax) + " "; + const coordinatePair2 = + Math.round(this.state.left + this.props.startWidth) + + "," + + Math.round(this.props.yMax) + + " "; + const coordinatePair3 = + Math.round(this.state.left) + + "," + + Math.round(this.props.yMax - this.props.startHeight); + const coord = coordinatePair1 + coordinatePair2 + coordinatePair3; + this.setState({coordinates: coord}); + } + + componentDidMount() { + this.updateCoordinates() + } + + componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { + if (prevProps.startHeight != this.props.startHeight || prevProps.startWidth != this.props.startWidth) { + this.setState({angleInRadians: Math.atan(this.props.startHeight / this.props.startWidth)}); + this.updateCoordinates(); + } + } + + render() { + return ( +
+
+ + + +
+ +

+ {Math.round(((this.state.angleInRadians * 180) / Math.PI) * 100) / 100}° +

+
+ ); + } +}; diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx new file mode 100644 index 000000000..39b3249e8 --- /dev/null +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx @@ -0,0 +1,860 @@ +import React = require('react'); +import { Doc } from '../../../fields/Doc'; +import { IWallProps } from "./PhysicsSimulationWall"; + +export interface IForce { + description: string; + magnitude: number; + directionInDegrees: number; +} +export interface IWeightProps { + adjustPendulumAngle: boolean; + color: string; + dataDoc: Doc; + mass: number; + radius: number; + simulationReset: boolean; + startPosX: number; + startPosY: number; + startVelX?: number; + startVelY?: number; + timestepSize: number; + updateDisplay: boolean, + walls: IWallProps[]; + wedge: boolean; + wedgeHeight: number; + wedgeWidth: number; + xMax: number; + xMin: number; + yMax: number; + yMin: number; +} + +interface IState { + angleLabel: number, + clickPositionX: number, + clickPositionY: number, + dragging: boolean, + kineticFriction: boolean, + timer: number; + update: boolean, + updatedStartPosX: number, + updatedStartPosY: number, + xPosition: number, + xVelocity: number, + yPosition: number, + yVelocity: number, +} +export default class Weight extends React.Component { + + constructor(props: any) { + super(props) + this.state = { + clickPositionX: 0, + clickPositionY: 0, + dragging: false, + kineticFriction: false, + timer: 0, + angleLabel: 0, + updatedStartPosX: this.props.dataDoc['startPosX'], + updatedStartPosY: this.props.dataDoc['startPosY'], + xPosition: this.props.dataDoc['startPosX'], + xVelocity: this.props.startVelX ? this.props.startVelX: 0, + yPosition: this.props.dataDoc['startPosY'], + yVelocity: this.props.startVelY ? this.props.startVelY: 0, + } + } + + // Constants + draggable = !this.props.dataDoc['wedge'] ; + epsilon = 0.0001; + forceOfGravity: IForce = { + description: "Gravity", + magnitude: this.props.mass * 9.81, + directionInDegrees: 270, + }; + + // Var + weightStyle = { + alignItems: "center", + backgroundColor: this.props.color, + borderColor: "black", + borderRadius: 50 + "%", + borderStyle: "solid", + display: "flex", + height: 2 * this.props.radius + "px", + justifyContent: "center", + left: this.props.dataDoc['startPosX'] + "px", + position: "absolute" as "absolute", + top: this.props.dataDoc['startPosY'] + "px", + touchAction: "none", + width: 2 * this.props.radius + "px", + zIndex: 5, + }; + + // Helper function to go between display and real values + getDisplayYPos = (yPos: number) => { + return this.props.yMax - yPos - 2 * this.props.radius + 5; + }; + getYPosFromDisplay = (yDisplay: number) => { + return this.props.yMax - yDisplay - 2 * this.props.radius + 5; + }; + + // Set display values based on real values + setYPosDisplay = (yPos: number) => { + const displayPos = this.getDisplayYPos(yPos); + this.props.dataDoc['positionYDisplay'] = Math.round(displayPos * 100) / 100 + }; + setXPosDisplay = (xPos: number) => { + this.props.dataDoc['positionXDisplay'] = Math.round(xPos * 100) / 100; + }; + setYVelDisplay = (yVel: number) => { + this.props.dataDoc['velocityYDisplay'] = (-1 * Math.round(yVel * 100)) / 100; + }; + setXVelDisplay = (xVel: number) => { + this.props.dataDoc['velocityXDisplay'] = Math.round(xVel * 100) / 100; + }; + + setDisplayValues = ( + xPos: number = this.state.xPosition, + yPos: number = this.state.yPosition, + xVel: number = this.state.xVelocity, + yVel: number = this.state.yVelocity + ) => { + this.setYPosDisplay(yPos); + this.setXPosDisplay(xPos); + this.setYVelDisplay(yVel); + this.setXVelDisplay(xVel); + this.props.dataDoc['accelerationYDisplay'] = + (-1 * Math.round(this.getNewAccelerationY(this.props.dataDoc['updatedForces']) * 100)) / 100 + ; + this.props.dataDoc['accelerationXDisplay'] = + Math.round(this.getNewAccelerationX(this.props.dataDoc['updatedForces']) * 100) / 100 + ; + }; + + componentDidMount() { + // Timer for animating the simulation + setInterval(() => { + this.setState({timer: this.state.timer + 1}); + }, 60); + } + + componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { + + // When display values updated by user, update real values + if (this.props.updateDisplay != prevProps.updateDisplay) { + if (this.props.dataDoc['positionXDisplay'] != this.state.xPosition) { + let x = this.props.dataDoc['positionXDisplay']; + x = Math.max(0, x); + x = Math.min(x, this.props.xMax - 2 * this.props.radius); + this.setState({updatedStartPosX: x}) + this.setState({xPosition: x}) + this.props.dataDoc['positionXDisplay'] = x; + } + + if (this.props.dataDoc['positionYDisplay'] != this.getDisplayYPos(this.state.yPosition)) { + let y = this.props.dataDoc['positionYDisplay']; + y = Math.max(0, y); + y = Math.min(y, this.props.yMax - 2 * this.props.radius); + this.props.dataDoc['positionYDisplay'] = y; + let coordinatePosition = this.getYPosFromDisplay(y); + this.setState({updatedStartPosY: coordinatePosition}) + this.setState({yPosition: coordinatePosition}) + } + + if (this.props.dataDoc['velocityXDisplay'] != this.state.xVelocity) { + let x = this.props.dataDoc['velocityXDisplay']; + this.setState({xVelocity: x}) + this.props.dataDoc['velocityXDisplay'] = x; + } + + if (this.props.dataDoc['velocityYDisplay'] != this.state.yVelocity) { + let y = this.props.dataDoc['velocityYDisplay']; + this.setState({yVelocity: -y}) + this.props.dataDoc['velocityYDisplay'] + } + } + // Update sim + if (this.state.timer != prevState.timer) { + if (!this.props.dataDoc['simulationPaused']) { + let collisions = false; + if (!this.props.dataDoc['pendulum']) { + const collisionsWithGround = this.checkForCollisionsWithGround(); + const collisionsWithWalls = this.checkForCollisionsWithWall(); + collisions = collisionsWithGround || collisionsWithWalls; + } + if (!collisions) { + this.update(); + } + this.setDisplayValues(); + } + } + + if (this.props.simulationReset != prevProps.simulationReset) { + this.resetEverything(); + } + if (this.props.adjustPendulumAngle != prevProps.adjustPendulumAngle) { + console.log('update angle') + // Change pendulum angle based on input field + let length = this.props.dataDoc['pendulumLength'] ?? 0; + const x = + length * Math.cos(((90 - this.props.dataDoc['pendulumAngle']) * Math.PI) / 180); + const y = + length * Math.sin(((90 - this.props.dataDoc['pendulumAngle']) * Math.PI) / 180); + const xPos = this.props.xMax / 2 - x - this.props.radius; + const yPos = y - this.props.radius - 5; + this.setState({xPosition: xPos}) + this.setState({yPosition: yPos}) + this.setState({updatedStartPosX: xPos}) + this.setState({updatedStartPosY: yPos}) + this.setState({angleLabel: Math.round(this.props.dataDoc['pendulumAngle'] * 100) / 100}) + } + // Update x start position + if (this.props.startPosX != prevProps.startPosX) { + this.setState({updatedStartPosX: this.props.dataDoc['startPosX']}) + this.setState({xPosition: this.props.dataDoc['startPosX']}) + this.setXPosDisplay(this.props.dataDoc['startPosX']); + } + // Update y start position + if (this.props.startPosY != prevProps.startPosY) { + this.setState({updatedStartPosY: this.props.dataDoc['startPosY']}) + this.setState({yPosition: this.props.dataDoc['startPosY']}) + this.setYPosDisplay(this.props.dataDoc['startPosY']); + } + if (!this.props.dataDoc['simulationPaused']) { + if (this.state.xVelocity != prevState.xVelocity) { + if (this.props.dataDoc['wedge'] && this.state.xVelocity != 0 && !this.state.kineticFriction) { + this.setState({kineticFriction: true}); + //switch from static to kinetic friction + const normalForce: IForce = { + description: "Normal Force", + magnitude: + this.forceOfGravity.magnitude * + Math.cos(Math.atan(this.props.dataDoc['wedgeHeight'] / this.props.dataDoc['wedgeWidth'] )), + directionInDegrees: + 180 - 90 - (Math.atan(this.props.dataDoc['wedgeHeight'] / this.props.dataDoc['wedgeWidth'] ) * 180) / Math.PI, + }; + let frictionForce: IForce = { + description: "Kinetic Friction Force", + magnitude: + this.props.dataDoc['coefficientOfKineticFriction'] * + this.forceOfGravity.magnitude * + Math.cos(Math.atan(this.props.dataDoc['wedgeHeight'] / this.props.dataDoc['wedgeWidth'] )), + directionInDegrees: + 180 - (Math.atan(this.props.dataDoc['wedgeHeight'] / this.props.dataDoc['wedgeWidth'] ) * 180) / Math.PI, + }; + // reduce magnitude of friction force if necessary such that block cannot slide up plane + let yForce = -this.forceOfGravity.magnitude; + 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) + + this.forceOfGravity.magnitude) / + Math.sin((frictionForce.directionInDegrees * Math.PI) / 180); + } + if (this.props.dataDoc['coefficientOfKineticFriction'] != 0) { + this.props.dataDoc['updatedForces'] = [this.forceOfGravity, normalForce, frictionForce]; + } else { + this.props.dataDoc['updatedForces'] = ([this.forceOfGravity, normalForce]); + } + } + } + } + + this.weightStyle = { + alignItems: "center", + backgroundColor: this.props.color, + borderColor: this.state.dragging ? "lightblue" : "black", + borderRadius: 50 + "%", + borderStyle: "solid", + display: "flex", + height: 2 * this.props.radius + "px", + justifyContent: "center", + left: this.state.xPosition + "px", + position: "absolute" as "absolute", + top: this.state.yPosition + "px", + touchAction: "none", + width: 2 * this.props.radius + "px", + zIndex: 5, + }; + } + + resetEverything = () => { + this.setState({kineticFriction: false}) + this.setState({xPosition: this.state.updatedStartPosX}) + this.setState({yPosition: this.state.updatedStartPosY}) + this.setState({xVelocity: this.props.startVelX ?? 0}) + this.setState({yVelocity: this.props.startVelY ?? 0}) + this.props.dataDoc['updatedForces'] = (this.props.dataDoc['startForces']) + this.setState({angleLabel: Math.round(this.props.dataDoc['pendulumAngle']* 100) / 100}) + this.setDisplayValues(); + }; + + getNewAccelerationX = (forceList: IForce[]) => { + let newXAcc = 0; + if (forceList) { + forceList.forEach((force) => { + newXAcc += + (force.magnitude * + Math.cos((force.directionInDegrees * Math.PI) / 180)) / + this.props.mass; + }); + } + return newXAcc; + }; + + getNewAccelerationY = (forceList: IForce[]) => { + let newYAcc = 0; + if (forceList) { + forceList.forEach((force) => { + newYAcc += + (-1 * + (force.magnitude * + Math.sin((force.directionInDegrees * Math.PI) / 180))) / + this.props.mass; + }); + } + return newYAcc; + }; + + getNewForces = ( + xPos: number, + yPos: number, + xVel: number, + yVel: number + ) => { + if (!this.props.dataDoc['pendulum']) { + return this.props.dataDoc['updatedForces']; + } + const x = this.props.xMax / 2 - xPos - this.props.radius; + const y = yPos + this.props.radius + 5; + let angle = (Math.atan(y / x) * 180) / Math.PI; + if (angle < 0) { + angle += 180; + } + let oppositeAngle = 90 - angle; + if (oppositeAngle < 0) { + oppositeAngle = 90 - (180 - angle); + } + + const pendulumLength = Math.sqrt(x * x + y * y); + this.props.dataDoc['pendulumAngle'] = oppositeAngle; + this.props.dataDoc['pendulumLength'] = Math.sqrt(x * x + y * y); + + const mag = + this.props.mass * 9.81 * Math.cos((oppositeAngle * Math.PI) / 180) + + (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength; + + const forceOfTension: IForce = { + description: "Tension", + magnitude: mag, + directionInDegrees: angle, + }; + this.setState({angleLabel: Math.round(this.props.dataDoc['pendulumAngle']* 100) / 100}) + + return [this.forceOfGravity, forceOfTension]; + }; + + getNewPosition = (pos: number, vel: number) => { + return pos + vel * this.props.timestepSize; + }; + + getNewVelocity = (vel: number, acc: number) => { + return vel + acc * this.props.timestepSize; + }; + + checkForCollisionsWithWall = () => { + let collision = false; + const minX = this.state.xPosition; + const maxX = this.state.xPosition + 2 * this.props.radius; + const containerWidth = 300; + if (this.state.xVelocity != 0) { + if (this.props.dataDoc.wallPositions) { + this.props.dataDoc['wallPositions'].forEach((wall) => { + if (wall.angleInDegrees == 90) { + const wallX = (wall.xPos / 100) * 300; + if (wall.xPos < 0.35) { + if (minX <= wallX) { + if (this.props.dataDoc['elasticCollisions']) { + this.setState({xVelocity: -this.state.xVelocity}); + } else { + this.setState({xVelocity: 0}); + this.setState({xPosition: wallX+5}); + } + collision = true; + } + } else { + if (maxX >= wallX) { + if (this.props.dataDoc['elasticCollisions']) { + this.setState({xVelocity: -this.state.xVelocity}); + } else { + this.setState({xVelocity: 0}); + this.setState({xPosition: wallX - 2 * this.props.radius + 5}); + } + collision = true; + } + } + } + }); + } + } + return collision; + }; + + checkForCollisionsWithGround = () => { + let collision = false; + const maxY = this.state.yPosition + 2 * this.props.radius; + if (this.state.yVelocity > 0) { + if (this.props.dataDoc.wallPositions) { + this.props.dataDoc['wallPositions'].forEach((wall) => { + if (wall.angleInDegrees == 0) { + const groundY = (wall.yPos / 100) * this.props.yMax; + if (maxY >= groundY) { + if (this.props.dataDoc['elasticCollisions']) { + this.setState({yVelocity: -this.state.yVelocity}) + } else { + this.setState({yVelocity: 0}) + this.setState({yPosition: groundY - 2 * this.props.radius + 5}) + const forceOfGravity: IForce = { + description: "Gravity", + magnitude: 9.81 * this.props.mass, + directionInDegrees: 270, + }; + const normalForce: IForce = { + description: "Normal force", + magnitude: 9.81 * this.props.mass, + directionInDegrees: wall.angleInDegrees + 90, + }; + this.props.dataDoc['updatedForces'] = ([forceOfGravity, normalForce]); + } + collision = true; + } + } + }); + } + } + return collision; + }; + + update = () => { + // RK4 update + let xPos = this.state.xPosition; + let yPos = this.state.yPosition; + let xVel = this.state.xVelocity; + let yVel = this.state.yVelocity; + for (let i = 0; i < 60; i++) { + let forces1 = this.getNewForces(xPos, yPos, xVel, yVel); + const xAcc1 = this.getNewAccelerationX(forces1); + const yAcc1 = this.getNewAccelerationY(forces1); + const xVel1 = this.getNewVelocity(xVel, xAcc1); + const yVel1 = this.getNewVelocity(yVel, yAcc1); + + let xVel2 = this.getNewVelocity(xVel, xAcc1 / 2); + let yVel2 = this.getNewVelocity(yVel, yAcc1 / 2); + let xPos2 = this.getNewPosition(xPos, xVel1 / 2); + let yPos2 = this.getNewPosition(yPos, yVel1 / 2); + const forces2 = this.getNewForces(xPos2, yPos2, xVel2, yVel2); + const xAcc2 = this.getNewAccelerationX(forces2); + const yAcc2 = this.getNewAccelerationY(forces2); + xVel2 = this.getNewVelocity(xVel2, xAcc2); + yVel2 = this.getNewVelocity(yVel2, yAcc2); + xPos2 = this.getNewPosition(xPos2, xVel2); + yPos2 = this.getNewPosition(yPos2, yVel2); + + let xVel3 = this.getNewVelocity(xVel, xAcc2 / 2); + let yVel3 = this.getNewVelocity(yVel, yAcc2 / 2); + let xPos3 = this.getNewPosition(xPos, xVel2 / 2); + let yPos3 = this.getNewPosition(yPos, yVel2 / 2); + const forces3 = this.getNewForces(xPos3, yPos3, xVel3, yVel3); + const xAcc3 = this.getNewAccelerationX(forces3); + const yAcc3 = this.getNewAccelerationY(forces3); + xVel3 = this.getNewVelocity(xVel3, xAcc3); + yVel3 = this.getNewVelocity(yVel3, yAcc3); + xPos3 = this.getNewPosition(xPos3, xVel3); + yPos3 = this.getNewPosition(yPos3, yVel3); + + let xVel4 = this.getNewVelocity(xVel, xAcc3); + let yVel4 = this.getNewVelocity(yVel, yAcc3); + let xPos4 = this.getNewPosition(xPos, xVel3); + let yPos4 = this.getNewPosition(yPos, yVel3); + const forces4 = this.getNewForces(xPos4, yPos4, xVel4, yVel4); + const xAcc4 = this.getNewAccelerationX(forces4); + const yAcc4 = this.getNewAccelerationY(forces4); + xVel4 = this.getNewVelocity(xVel4, xAcc4); + yVel4 = this.getNewVelocity(yVel4, yAcc4); + xPos4 = this.getNewPosition(xPos4, xVel4); + yPos4 = this.getNewPosition(yPos4, yVel4); + + xVel += + this.props.timestepSize * (xAcc1 / 6.0 + xAcc2 / 3.0 + xAcc3 / 3.0 + xAcc4 / 6.0); + yVel += + this.props.timestepSize * (yAcc1 / 6.0 + yAcc2 / 3.0 + yAcc3 / 3.0 + yAcc4 / 6.0); + xPos += + this.props.timestepSize * (xVel1 / 6.0 + xVel2 / 3.0 + xVel3 / 3.0 + xVel4 / 6.0); + yPos += + this.props.timestepSize * (yVel1 / 6.0 + yVel2 / 3.0 + yVel3 / 3.0 + yVel4 / 6.0); + } + + this.setState({xVelocity: xVel}); + this.setState({yVelocity: yVel}); + this.setState({xPosition: xPos}); + this.setState({yPosition: yPos}); + + this.props.dataDoc['updatedForces'] = (this.getNewForces(xPos, yPos, xVel, yVel)); + }; + + + labelBackgroundColor = `rgba(255,255,255,0.5)`; + + render () { + return ( +
+
{ + // if (this.draggable) { + // e.preventDefault(); + // this.props.dataDoc['simulationPaused'] = true; + // this.setState({dragging: true}); + // this.setState({clickPositionX: e.clientX}) + // this.setState({clickPositionY: e.clientY}) + // } + // }} + // onPointerMove={(e) => { + // e.preventDefault(); + // if (this.state.dragging) { + // let newY = this.state.yPosition + e.clientY - this.state.clickPositionY; + // if (newY > this.props.yMax - 2 * this.props.radius) { + // newY = this.props.yMax - 2 * this.props.radius; + // } + + // let newX = this.state.xPosition + e.clientX - this.state.clickPositionX; + // if (newX > this.props.xMax - 2 * this.props.radius) { + // newX = this.props.xMax - 2 * this.props.radius; + // } else if (newX < 0) { + // newX = 0; + // } + // this.setState({xPosition: newX}) + // this.setState({yPosition: newY}) + // this.setState({updatedStartPosX: newX}) + // this.setState({updatedStartPosY: newY}) + // this.props.dataDoc['positionYDisplay'] = Math.round((this.props.yMax - 2 * this.props.radius - newY + 5) * 100) / 100; + // this.setState({clickPositionX: e.clientX}) + // this.setState({clickPositionY: e.clientY}) + // this.setDisplayValues(); + // } + // }} + // onPointerUp={(e) => { + // if (this.state.dragging) { + // e.preventDefault(); + // if (!this.props.dataDoc['pendulum']) { + // this.resetEverything(); + // } + // this.setState({dragging: false}); + // let newY = this.state.yPosition + e.clientY - this.state.clickPositionY; + // if (newY > this.props.yMax - 2 * this.props.radius) { + // newY = this.props.yMax - 2 * this.props.radius; + // } + + // let newX = this.state.xPosition + e.clientX - this.state.clickPositionX; + // if (newX > this.props.xMax - 2 * this.props.radius) { + // newX = this.props.xMax - 2 * this.props.radius; + // } else if (newX < 0) { + // newX = 0; + // } + // if (this.props.dataDoc['pendulum']) { + // const x = this.props.xMax / 2 - newX - this.props.radius; + // const y = newY + this.props.radius + 5; + // let angle = (Math.atan(y / x) * 180) / Math.PI; + // if (angle < 0) { + // angle += 180; + // } + // let oppositeAngle = 90 - angle; + // if (oppositeAngle < 0) { + // oppositeAngle = 90 - (180 - angle); + // } + + // const pendulumLength = Math.sqrt(x * x + y * y); + // this.props.dataDoc['pendulumAngle'] = oppositeAngle; + // this.props.dataDoc['pendulumLength'] = Math.sqrt(x * x + y * y); + // const mag = 9.81 * Math.cos((oppositeAngle * Math.PI) / 180); + // const forceOfTension: IForce = { + // description: "Tension", + // magnitude: mag, + // directionInDegrees: angle, + // }; + // this.setState({kineticFriction: false}) + // this.setState({xVelocity: this.props.startVelX ?? 0}) + // this.setState({yVelocity: this.props.startVelY ?? 0}) + // this.setDisplayValues(); + // this.props.dataDoc['updatedForces'] = ([this.forceOfGravity, forceOfTension]); + // } + // } + // }} + > +
+

{this.props.mass} kg

+
+
+ {this.props.dataDoc['pendulum'] && ( +
+ + + + {!this.state.dragging && ( +
+

+ {this.state.angleLabel}° +

+
+ )} +
+ )} + {!this.state.dragging && this.props.dataDoc['showAcceleration'] && ( +
+
+ + + + + + + + +
+

+ {Math.round( + 100 * + Math.sqrt( + Math.pow(this.getNewAccelerationX(this.props.dataDoc['updatedForces']) * 3, 2) + + Math.pow(this.getNewAccelerationY(this.props.dataDoc['updatedForces']) * 3, 2) + ) + ) / 100}{" "} + m/s2 +

+
+
+
+ )} + {!this.state.dragging && this.props.dataDoc['showVelocity'] && ( +
+
+ + + + + + + + +
+

+ {Math.round( + 100 * Math.sqrt(this.state.xVelocity**2 + this.state.yVelocity**2) + ) / 100}{" "} + m/s +

+
+
+
+ )} + {!this.state.dragging && + this.props.dataDoc['showForces'] && + this.props.dataDoc['updatedForces'].map((force, index) => { + if (force.magnitude < this.epsilon) { + return; + } + let arrowStartY: number = this.state.yPosition + this.props.radius; + const arrowStartX: number = this.state.xPosition + this.props.radius; + let arrowEndY: number = + arrowStartY - + Math.abs(force.magnitude) * + 10 * + Math.sin((force.directionInDegrees * Math.PI) / 180); + const arrowEndX: number = + arrowStartX + + Math.abs(force.magnitude) * + 10 * + Math.cos((force.directionInDegrees * Math.PI) / 180); + + let color = "#0d0d0d"; + + let labelTop = arrowEndY; + let labelLeft = arrowEndX; + if (force.directionInDegrees > 90 && force.directionInDegrees < 270) { + labelLeft -= 120; + } else { + labelLeft += 30; + } + if (force.directionInDegrees >= 0 && force.directionInDegrees < 180) { + labelTop += 40; + } else { + labelTop -= 40; + } + labelTop = Math.min(labelTop, this.props.yMax + 50); + labelTop = Math.max(labelTop, this.props.yMin); + labelLeft = Math.min(labelLeft, this.props.xMax - 60); + labelLeft = Math.max(labelLeft, this.props.xMin); + + return ( +
+
+ + + + + + + + +
+
+ {force.description &&

{force.description}

} + {!force.description &&

Force

} + {this.props.dataDoc['showForceMagnitudes'] && ( +

{Math.round(100 * force.magnitude) / 100} N

+ )} +
+
+ ); + })} +
+ ); + } +}; -- cgit v1.2.3-70-g09d2 From f327a27d664f45c10a90a4464cd0fa5edec59b68 Mon Sep 17 00:00:00 2001 From: brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> Date: Mon, 1 May 2023 12:51:12 -0400 Subject: start adding box code --- .../nodes/PhysicsBox/PhysicsSimulationBox.tsx | 745 ++++++++++----------- .../nodes/PhysicsBox/PhysicsSimulationWedge.tsx | 83 --- 2 files changed, 346 insertions(+), 482 deletions(-) delete mode 100644 src/client/views/nodes/PhysicsBox/PhysicsSimulationWedge.tsx (limited to 'src/client/views/nodes/PhysicsBox') diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx index d0e854263..f27843ab0 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx @@ -6,22 +6,41 @@ import { observer } from 'mobx-react'; import "./PhysicsSimulationBox.scss"; import Weight from "./PhysicsSimulationWeight"; import Wall from "./PhysicsSimulationWall" -import Wedge from "./PhysicsSimulationWedge" import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { CheckBox } from "../search/CheckBox"; -export interface IForce { - description: string; - magnitude: number; - directionInDegrees: number; -} -export interface IWallProps { - length: number; - xPos: number; - yPos: number; - angleInDegrees: number; -} - -interface PhysicsVectorTemplate { +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 React, { useEffect, useState } from "react"; +import "./App.scss"; +import { InputField } from "./InputField"; +import questions from "./PhysicsSimulationQuestions.json"; +import tutorials from "./PhysicsSimulationTutorial.json"; +import { IWallProps, Wall } from "./Wall"; +import { IForce, Weight } from "./Weight"; +interface VectorTemplate { top: number; left: number; width: number; @@ -33,118 +52,191 @@ interface PhysicsVectorTemplate { 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 default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PhysicsSimulationBox, fieldKey); } - // Constants - gravityMagnitude = 9.81; - forceOfGravity: IForce = { - description: "Gravity", - magnitude: this.gravityMagnitude, - directionInDegrees: 270, - }; - xMin = 0; - yMin = 0; - xMax = 300; - yMax = 300; - color = `rgba(0,0,0,0.5)`; - radius = 0.1*this.yMax - update = true - menuIsOpen = false - constructor(props: any) { super(props); } - // Add one weight to the simulation - addWeight () { - this.dataDoc.weight = true; - this.dataDoc.wedge = false; - this.dataDoc.pendulum = false; - this.addWalls(); - }; + // Constants + const xMin = 0; + const yMin = 0; + const xMax = window.innerWidth * 0.7; + const yMax = window.innerHeight * 0.8; + const color = `rgba(0,0,0,0.5)`; + const radius = 50; + const wallPositions: IWallProps[] = []; - // Set weight defaults - setToWeightDefault () { - this.dataDoc.startPosY = this.yMin+this.radius; - this.dataDoc.startPosX = (this.xMax+this.xMin-this.radius)/2; - this.dataDoc.updatedForces = [this.forceOfGravity]; - this.dataDoc.startForces = [this.forceOfGravity]; - } - // Add a wedge with a One Weight to the simulation - addWedge () { - this.dataDoc.weight = true; - this.dataDoc.wedge = true; - this.dataDoc.pendulum = false; - this.addWalls(); - }; + componentDidMount() { + this.wallPositions.push({ length: 70, xPos: 0, yPos: 0, angleInDegrees: 0 }); + this.wallPositions.push({ length: 70, xPos: 0, yPos: 80, angleInDegrees: 0 }); + this.wallPositions.push({ length: 80, xPos: 0, yPos: 0, angleInDegrees: 90 }); + this.wallPositions.push({ length: 80, xPos: 69.5, yPos: 0, angleInDegrees: 90 }); - // Set wedge defaults - setToWedgeDefault () { - this.changeWedgeBasedOnNewAngle(26); - this.updateForcesWithFriction(this.dataDoc.coefficientOfStaticFriction); - } + // Used throughout sims + this.xMax = this.layoutDoc._width; + this.yMax = this.layoutDoc._height; + this.radius = 0.1*this.layoutDoc._height; + 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 = this.dataDoc.mode ?? "Freeform"; + this.dataDoc.positionXDisplay = this.dataDoc.positionXDisplay ?? 0; + this.dataDoc.positionYDisplay = this.dataDoc.positionYDisplay ?? 0; + this.dataDoc.resetAll = this.dataDoc.resetAll ?? true; + 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 ?? 0; + this.dataDoc.startPosY = this.dataDoc.startPosY ?? 0; + this.dataDoc.startVelX = this.dataDoc.startVelX ?? 0; + this.dataDoc.startVelY = this.dataDoc.startVelY ?? 0; + this.dataDoc.stepNumber = this.dataDoc.stepNumber ?? 0; + this.dataDoc.timer = this.dataDoc.timer ?? 0; + this.dataDoc.updatedForces = this.dataDoc.updatedForces ?? []; + this.dataDoc.velocityXDisplay = this.dataDoc.velocityXDisplay ?? 0; + this.dataDoc.velocityYDisplay = this.dataDoc.velocityYDisplay ?? 0; - // Add a simple pendulum to the simulation - addPendulum = () => { - this.dataDoc.weight = true; - this.dataDoc.wedge = false; - this.dataDoc.pendulum = true; - this.removeWalls(); - let angle = this.dataDoc.pendulumAngle; - let mag = 9.81 * Math.cos((angle * Math.PI) / 180); - let forceOfTension: IForce = { - description: "Tension", - magnitude: mag, - directionInDegrees: 90 - angle, - }; - this.dataDoc.updatedForces = [this.forceOfGravity, forceOfTension]; - this.dataDoc.startForces = [this.forceOfGravity, forceOfTension]; - }; + // Used for review mode + this.dataDoc.answerInputFields = this.dataDoc.answerInputFields ??
; + this.dataDoc.currentForceSketch = this.dataDoc.currentForceSketch ?? null; + this.dataDoc.deleteMode = this.dataDoc.deleteMode ?? false; + this.dataDoc.forceSketches = this.dataDoc.forceSketches ?? []; + this.dataDoc.hintDialogueOpen = this.dataDoc.hintDialogueOpen ?? false; + this.dataDoc.noMovement = this.dataDoc.noMovement ?? false; + this.dataDoc.questionNumber = this.dataDoc.questionNumber ?? 0; + this.dataDoc.questionPartOne = this.dataDoc.questionPartOne ?? ""; + this.dataDoc.questionPartTwo = this.dataDoc.questionPartTwo ?? ""; + this.dataDoc.reviewGravityAngle = this.dataDoc.reviewGravityAngle ?? 0; + this.dataDoc.reviewGravityMagnitude = this.dataDoc.reviewGravityMagnitude ?? 0; + this.dataDoc.reviewNormalAngle = this.dataDoc.reviewNormalAngle ?? 0; + this.dataDoc.reviewNormalMagnitude = this.dataDoc.reviewNormalMagnitude ?? 0; + this.dataDoc.reviewStaticAngle = this.dataDoc.reviewStaticAngle ?? 0; + this.dataDoc.reviewStaticMagnitude = this.dataDoc.reviewStaticMagnitude ?? 0; + this.dataDoc.selectedSolutions = 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; - // Set pendulum defaults - setToPendulumDefault () { - let length = this.xMax*0.7; - let angle = 35; - let x = length * Math.cos(((90 - angle) * Math.PI) / 180); - let y = length * Math.sin(((90 - angle) * Math.PI) / 180); - let xPos = this.xMax / 2 - x - this.radius; - let yPos = y - this.radius; - this.dataDoc.startPosX = xPos; - this.dataDoc.startPosY = yPos; - let mag = 9.81 * Math.cos((angle * Math.PI) / 180); - this.dataDoc.pendulumAngle = angle; - this.dataDoc.pendulumLength = length; - this.dataDoc.startPendulumAngle = angle; - this.dataDoc.adjustPendulumAngle = !this.dataDoc.adjustPendulumAngle; + // 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) * 400; + this.dataDoc.wedgeWidth = this.dataDoc.wedgeWidth ?? 400; + + // 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; + } + + componentDidUpdate() { + this.xMax = this.layoutDoc._width; + this.yMax = this.layoutDoc._height; + this.radius = 0.1*this.layoutDoc._height; } + // Helper function to go between display and real values + getDisplayYPos = (yPos: number) => { + return this.yMax - this.dataDoc.yPos - 2 * this.radius + 5; + }; + getYPosFromDisplay = (yDisplay: number) => { + return this.yMax - this.dataDoc.yDisplay - 2 * this.radius + 5; + }; + // Update forces when coefficient of static friction changes in freeform mode - updateForcesWithFriction ( + updateForcesWithFriction = ( coefficient: number, - width: number = this.dataDoc.wedgeWidth, - height: number = this.dataDoc.wedgeHeight - ) { - let normalForce = { + width: number = this.wedgeWidth, + height: number = this.wedgeHeight + ) => { + const normalForce: IForce = { description: "Normal Force", - magnitude: this.forceOfGravity.magnitude * Math.cos(Math.atan(height / width)), + magnitude: Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass, directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI, + component: false, }; let frictionForce: IForce = { description: "Static Friction Force", magnitude: coefficient * - this.forceOfGravity.magnitude * - Math.cos(Math.atan(height / width)), + Math.abs(this.gravity) * + Math.cos(Math.atan(height / width)) * + this.mass, directionInDegrees: 180 - (Math.atan(height / width) * 180) / Math.PI, + component: false, }; - // reduce magnitude of friction force if necessary such that block cannot slide up plane - let yForce = -this.forceOfGravity.magnitude; + // reduce magnitude or friction force if necessary such that block cannot slide up plane + let yForce = -Math.abs(this.gravity) * this.mass; yForce += normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180); @@ -155,15 +247,65 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { - return this.yMax - yPos - 2 * 50 + 5; - }; - - // In review mode, edit force arrow sketch on mouse movement - editForce = (element: PhysicsVectorTemplate) => { - if (!this.dataDoc.sketching) { - let sketches = this.dataDoc.forceSketches.filter((sketch: PhysicsVectorTemplate) => sketch != element); - this.dataDoc.forceSketches = sketches; - this.dataDoc.currentForceSketch = element; - this.dataDoc.sketching = true; + if (this.dataDoc.mode == "Freeform") { + this.updateForcesWithFriction( + Number(this.dataDoc.coefficientOfStaticFriction), + width, + height + ); } }; - // In review mode, used to delete force arrow sketch on SHIFT+click - deleteForce = (element: PhysicsVectorTemplate) => { - if (!this.dataDoc.sketching) { - let sketches = this.dataDoc.forceSketches.filter((sketch: PhysicsVectorTemplate) => sketch != element); - this.dataDoc.forceSketches = sketches; + // 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); }; - // Remove floor and walls from simulation - removeWalls = () => { - this.dataDoc.wallPositions = [] - }; - - // Add floor and walls to simulation - addWalls = () => { - let walls = []; - walls.push({ length: 100, xPos: 0, yPos: 97, angleInDegrees: 0 }); - walls.push({ length: 100, xPos: 0, yPos: 0, angleInDegrees: 90 }); - walls.push({ length: 100, xPos: 97, yPos: 0, angleInDegrees: 90 }); - this.dataDoc.wallPositions = walls - }; - - - componentDidMount() { - this.xMax = this.layoutDoc._width; - this.yMax = this.layoutDoc._height; - this.radius = 0.1*this.layoutDoc._height; - - // Add weight - if (this.dataDoc.simulationType == "Inclined Plane") { - this.addWedge() - } else if (this.dataDoc.simulationType == "Pendulum") { - this.addPendulum() - } else { - this.dataDoc.simulationType = "Free Weight" - this.addWeight() + // 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.accelerationXDisplay = this.dataDoc.accelerationXDisplay ?? 0; - this.dataDoc.accelerationYDisplay = this.dataDoc.accelerationYDisplay ?? 0; - this.dataDoc.coefficientOfKineticFriction = this.dataDoc.coefficientOfKineticFriction ?? 0; - this.dataDoc.coefficientOfStaticFriction = this.dataDoc.coefficientOfStaticFriction ?? 0; - this.dataDoc.currentForceSketch = this.dataDoc.currentForceSketch ?? []; - this.dataDoc.elasticCollisions = this.dataDoc.elasticCollisions ?? false; - this.dataDoc.forceSketches = this.dataDoc.forceSketches ?? []; - this.dataDoc.pendulumAngle = this.dataDoc.pendulumAngle ?? 26; - this.dataDoc.pendulumLength = this.dataDoc.pendulumLength ?? 300; - this.dataDoc.positionXDisplay = this.dataDoc.positionXDisplay ?? 0; - this.dataDoc.positionYDisplay = this.dataDoc.positionYDisplay ?? 0; - this.dataDoc.showAcceleration = this.dataDoc.showAcceleration ?? false; - this.dataDoc.showForceMagnitudes = this.dataDoc.showForceMagnitudes ?? false; - this.dataDoc.showForces = this.dataDoc.showForces ?? false; - this.dataDoc.showVelocity = this.dataDoc.showVelocity ?? false; - this.dataDoc.startForces = this.dataDoc.startForces ?? [this.forceOfGravity]; - this.dataDoc.startPendulumAngle = this.dataDoc.startPendulumAngle ?? 0; - this.dataDoc.startPosX = this.dataDoc.startPosX ?? 50; - this.dataDoc.startPosY = this.dataDoc.startPosY ?? 50; - this.dataDoc.stepNumber = this.dataDoc.stepNumber ?? 0; - this.dataDoc.updateDisplay = this.dataDoc.updateDisplay ?? false; - this.dataDoc.updatedForces = this.dataDoc.updatedForces ?? [this.forceOfGravity]; - this.dataDoc.velocityXDisplay = this.dataDoc.velocityXDisplay ?? 0; - this.dataDoc.velocityYDisplay = this.dataDoc.velocityYDisplay ?? 0; - this.dataDoc.wallPositions = this.dataDoc.wallPositions ?? []; - this.dataDoc.wedgeAngle = this.dataDoc.wedgeAngle ?? 26; - this.dataDoc.wedgeHeight = this.dataDoc.wedgeHeight ?? Math.tan((26 * Math.PI) / 180) * this.xMax*0.6; - this.dataDoc.wedgeWidth = this.dataDoc.wedgeWidth ?? this.xMax*0.6; - - this.dataDoc.adjustPendulumAngle = true; - this.dataDoc.simulationPaused = true; - this.dataDoc.simulationReset = false; + this.dataDoc.reviewStaticMagnitude = (friction); + this.dataDoc.reviewStaticAngle = (180 - angle); + }; - // Add listener for SHIFT key, which determines if sketch force arrow will be edited or deleted on click - document.addEventListener("keydown", (e) => { - if (e.shiftKey) { - this.dataDoc.deleteMode = true; - } - }); - document.addEventListener("keyup", (e) => { - if (e.shiftKey) { - this.dataDoc.deleteMode = false; - } - }); - } - - componentDidUpdate() { - this.xMax = this.layoutDoc._width; - this.yMax = this.layoutDoc._height; - this.radius = 0.1*this.layoutDoc._height; - } render () { - return ( -
-
-
-
-
- {this.dataDoc.weight && ( - - )} - {this.dataDoc.wedge && ( - - )} -
-
- {(this.dataDoc.wallPositions ?? []).map((element: { length: number; xPos: number; yPos: number; angleInDegrees: number; }, index: React.Key | null | undefined) => { - return ( -
- -
- ); - })} -
-
-
-
- {this.menuIsOpen && ( -
-
{this.menuIsOpen = false; this.dataDoc.simulationReset = !this.dataDoc.simulationReset;}}> - -
-

Simulation Settings

-
-

Show forces

-
{this.dataDoc.showForces = !this.dataDoc.showForces}}/>
-
-
-

Show acceleration

-
{this.dataDoc.showAcceleration = !this.dataDoc.showAcceleration}}/>
-
-
-
-

Show velocity

-
{this.dataDoc.showVelocity = !this.dataDoc.showVelocity}}/>
-
-
- {this.dataDoc.simulationType == "Free Weight" &&
-

Elastic collisions

-
{this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions}}/>
-
} - {this.dataDoc.simulationType == "Pendulum" &&
-

Pendulum start angle

-
- { - let angle = e.target.value; - if (angle > 35) { - angle = 35 - } - if (angle < 0) { - angle = 0 - } - let length = this.xMax*0.7; - let x = length * Math.cos(((90 - angle) * Math.PI) / 180); - let y = length * Math.sin(((90 - angle) * Math.PI) / 180); - let xPos = this.xMax / 2 - x - this.radius; - let yPos = y - this.radius; - this.dataDoc.startPosX = xPos; - this.dataDoc.startPosY = yPos; - let mag = 9.81 * Math.cos((angle * Math.PI) / 180); - this.dataDoc.pendulumAngle = angle; - this.dataDoc.pendulumLength = length; - this.dataDoc.startPendulumAngle = angle; - this.dataDoc.adjustPendulumAngle = !this.dataDoc.adjustPendulumAngle; - }} - /> -
-
} - {this.dataDoc.simulationType == "Inclined Plane" &&
-

Inclined plane angle

-
- { - let angle = e.target.value ?? 0 - if (angle > 70) { - angle = 70 - } - if (angle < 0) { - angle = 0 - } - this.dataDoc.wedgeAngle = angle - this.changeWedgeBasedOnNewAngle(angle) - }} - /> -
-
} -
- )} -
-
-
-
- {this.dataDoc.simulationPaused && ( - - )} - {!this.dataDoc.simulationPaused && ( - - )} - {this.dataDoc.simulationPaused && ( - - )} - {this.dataDoc.simulationPaused && ( )} - -
-
-
-
-
- ); - } } \ No newline at end of file diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWedge.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWedge.tsx deleted file mode 100644 index 6134a6bc0..000000000 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWedge.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React = require('react'); -import "./PhysicsSimulationBox.scss"; - -export interface IWedgeProps { - startHeight: number; - startWidth: number; - startLeft: number; - xMax: number; - yMax: number; -} - -interface IState { - angleInRadians: number, - left: number, - coordinates: string, -} - -export default class Wedge extends React.Component { - - constructor(props: any) { - super(props) - this.state = { - angleInRadians: Math.atan(this.props.startHeight / this.props.startWidth), - left: this.props.startLeft, - coordinates: "", - } - } - - color = "#deb887"; - - updateCoordinates() { - const coordinatePair1 = - Math.round(this.state.left) + "," + Math.round(this.props.yMax) + " "; - const coordinatePair2 = - Math.round(this.state.left + this.props.startWidth) + - "," + - Math.round(this.props.yMax) + - " "; - const coordinatePair3 = - Math.round(this.state.left) + - "," + - Math.round(this.props.yMax - this.props.startHeight); - const coord = coordinatePair1 + coordinatePair2 + coordinatePair3; - this.setState({coordinates: coord}); - } - - componentDidMount() { - this.updateCoordinates() - } - - componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { - if (prevProps.startHeight != this.props.startHeight || prevProps.startWidth != this.props.startWidth) { - this.setState({angleInRadians: Math.atan(this.props.startHeight / this.props.startWidth)}); - this.updateCoordinates(); - } - } - - render() { - return ( -
-
- - - -
- -

- {Math.round(((this.state.angleInRadians * 180) / Math.PI) * 100) / 100}° -

-
- ); - } -}; -- cgit v1.2.3-70-g09d2 From 1eb8bee423564970c15ea0fbe9898d9e8c6b0491 Mon Sep 17 00:00:00 2001 From: brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> Date: Mon, 1 May 2023 13:02:18 -0400 Subject: start adding box code --- .../nodes/PhysicsBox/PhysicsSimulationBox.tsx | 406 ++++++++++++++++++++- 1 file changed, 405 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes/PhysicsBox') diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx index f27843ab0..3ae281177 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx @@ -198,7 +198,7 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { + 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); + this.dataDoc.answerInputFields = (
); + }; + + // 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 =(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); + const answers = this.getAnswersToQuestion(question, vars); + this.generateInputFieldsForQuestion(false, question, answers); + this.dataDoc.simulationReset = (!this.dataDoc.simulationReset); + }; + + // Generate answerInputFields for new review question + generateInputFieldsForQuestion = ( + showIcon: boolean = false, + question: QuestionTemplate = this.dataDoc.selectedQuestion, + answers: number[] = this.dataDoc.selectedSolutions + ) => { + let answerInput = []; + const d = new Date(); + for (let i = 0; i < question.answerParts.length; i++) { + if (question.answerParts[i] == "force of gravity") { + this.dataDoc.reviewGravityMagnitude = (0); + answerInput.push( +
+ Gravity magnitude

} + lowerBound={0} + changeValue={this.dataDoc.reviewGravityMagnitude} + step={0.1} + unit={"N"} + upperBound={50} + value={this.dataDoc.reviewGravityMagnitude} + showIcon={showIcon} + correctValue={answers[i]} + labelWidth={"7em"} + /> +
+ ); + } else if (question.answerParts[i] == "angle of gravity") { + this.dataDoc.reviewGravityAngle = (0); + answerInput.push( +
+ Gravity angle

} + lowerBound={0} + changeValue={this.dataDoc.reviewGravityAngle} + step={1} + unit={"°"} + upperBound={360} + value={this.dataDoc.reviewGravityAngle} + radianEquivalent={true} + showIcon={showIcon} + correctValue={answers[i]} + labelWidth={"7em"} + /> +
+ ); + } else if (question.answerParts[i] == "normal force") { + this.dataDoc.reviewNormalMagnitude = (0); + answerInput.push( +
+ Normal force magnitude

} + lowerBound={0} + changeValue={this.dataDoc.reviewNormalMagnitude} + step={0.1} + unit={"N"} + upperBound={50} + value={this.dataDoc.reviewNormalMagnitude} + showIcon={showIcon} + correctValue={answers[i]} + labelWidth={"7em"} + /> +
+ ); + } else if (question.answerParts[i] == "angle of normal force") { + this.dataDoc.reviewNormalAngle = (0); + answerInput.push( +
+ Normal force angle

} + lowerBound={0} + changeValue={this.dataDoc.reviewNormalAngle} + step={1} + unit={"°"} + upperBound={360} + value={this.dataDoc.reviewNormalAngle} + radianEquivalent={true} + showIcon={showIcon} + correctValue={answers[i]} + labelWidth={"7em"} + /> +
+ ); + } else if (question.answerParts[i] == "force of static friction") { + this.dataDoc.reviewStaticMagnitude = (0); + answerInput.push( +
+ Static friction magnitude

} + lowerBound={0} + changeValue={this.dataDoc.reviewStaticMagnitude} + step={0.1} + unit={"N"} + upperBound={50} + value={this.dataDoc.reviewStaticMagnitude} + showIcon={showIcon} + correctValue={answers[i]} + labelWidth={"7em"} + /> +
+ ); + } else if (question.answerParts[i] == "angle of static friction") { + this.dataDoc.reviewStaticAngle = (0); + answerInput.push( +
+ Static friction angle

} + lowerBound={0} + changeValue={this.dataDoc.reviewStaticAngle} + step={1} + unit={"°"} + upperBound={360} + value={this.dataDoc.reviewStaticAngle} + radianEquivalent={true} + showIcon={showIcon} + correctValue={answers[i]} + labelWidth={"7em"} + /> +
+ ); + } else if (question.answerParts[i] == "coefficient of static friction") { + this.updateReviewForcesBasedOnCoefficient(0); + answerInput.push( +
+ + μs + + } + lowerBound={0} + changeValue={this.dataDoc.coefficientOfStaticFriction} + step={0.1} + unit={""} + upperBound={1} + value={this.dataDoc.coefficientOfStaticFriction} + effect={this.updateReviewForcesBasedOnCoefficient} + showIcon={showIcon} + correctValue={answers[i]} + /> +
+ ); + } else if (question.answerParts[i] == "wedge angle") { + this.updateReviewForcesBasedOnAngle(0); + answerInput.push( +
+ θ} + lowerBound={0} + changeValue={this.dataDoc.qedgeAngle} + step={1} + unit={"°"} + upperBound={49} + value={this.dataDoc.wedgeAngle} + effect={(val: number) => { + this.changeWedgeBasedOnNewAngle(val); + this.updateReviewForcesBasedOnAngle(val); + }} + radianEquivalent={true} + showIcon={showIcon} + correctValue={answers[i]} + /> +
+ ); + } + } + + this.dataDoc.answerInputFields = ( +
+ {answerInput} +
+ ); + }; + + + render () { } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From a936f7c60c56e4a92aed99cae8e4194fa7fdeda5 Mon Sep 17 00:00:00 2001 From: brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> Date: Mon, 1 May 2023 14:19:49 -0400 Subject: add to box --- .../nodes/PhysicsBox/PhysicsSimulationBox.tsx | 343 ++++++++++++++++++++- 1 file changed, 341 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes/PhysicsBox') diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx index 3ae281177..88338d9b8 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx @@ -95,7 +95,6 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { + this.dataDoc.showComponentForces = (false); + this.dataDoc.startVelY = (0); + this.dataDoc.startVelX = (value); + let xPos = (this.xMax + this.xMin) / 2 - this.radius; + let yPos = (this.yMax + this.yMin) / 2 + this.dataDoc.circularMotionRadius - this.radius; + 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 - this.radius; + const yPos = y - this.radius - 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 - this.radius); + 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 - this.radius; + let yPos = this.yMin + 200; + this.dataDoc.startPosY = (yPos); + this.dataDoc.startPosX = (xPos); + this.dataDoc.positionYDisplay = (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 - 105); + this.dataDoc.positionYDisplay = (getDisplayYPos((this.yMax + this.yMin) / 2)); + this.dataDoc.positionXDisplay = ((this.xMin + this.xMax) / 2 - 105); + let a = (-1 * ((mass - mass2) * Math.abs(gravity))) / (mass + 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 = ((yMax + yMin) / 2); + this.dataDoc.startPosX2 = ((xMin + xMax) / 2 + 5); + this.dataDoc.positionYDisplay2 = (getDisplayYPos((yMax + yMin) / 2)); + this.dataDoc.positionXDisplay2 = ((xMin + xMax) / 2 + 5); + this.dataDoc.updatedForces2 = ([gravityForce2, tensionForce2]); + this.dataDoc.startForces2 = ([gravityForce2, tensionForce2]); + this.dataDoc.simulationReset = (!this.dataDoc.simulationReset); + }; -- cgit v1.2.3-70-g09d2 From abeb2b59b46e2c6de224d53b341495c162c75c7a Mon Sep 17 00:00:00 2001 From: brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> Date: Mon, 1 May 2023 14:34:04 -0400 Subject: add box --- .../nodes/PhysicsBox/PhysicsSimulationBox.tsx | 1302 ++++++++++++++++++++ 1 file changed, 1302 insertions(+) (limited to 'src/client/views/nodes/PhysicsBox') diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx index 88338d9b8..101cf1d4a 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx @@ -1178,7 +1178,1309 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { + 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, + ]); + } + + // Timer for animating the simulation, update every 0.05 seconds + setInterval(() => { + setTimer(timer + 1); + }, 50); + render () { +
+
+
+
+
+ {!this.dataDoc.simulationPaused && ( +
+ +
+ )} +
+
+ +
+
+
+
+ + {this.dataDoc.simulationType == "Pulley" && ( + + )} +
+
+ {(this.dataDoc.simulationType == "One Weight" || + this.dataDoc.simulationType == "Inclined Plane") && + 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: xMax - 50 + "px", + top: yMin + 14 + "px", + }} + > + + + )} + this.dataDoc.hintDialogueOpen = (false)} + > + Hints + + {this.dataDoc.selectedQuestion.hints.map((hint, index) => { + return ( +
+ +
+ + + Hint {index + 1}: {hint.description} + + + {hint.content} +
+
+
+ ); + })} +
+ + + +
+
+
+

{this.dataDoc.questionPartOne}

+

{this.dataDoc.questionPartTwo}

+
+
{this.dataDoc.answerInputFields}
+
+
+ )} + {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={stepNumber == 0} + > + + +
+

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

+

{this.dataDoc.selectedTutorial.steps[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} + changeValue={setSimulationSpeed} + step={1} + unit={"x"} + upperBound={10} + value={simulationSpeed} + labelWidth={"5em"} + /> + {simulationPaused && simulationType != "Circular Motion" && ( + Gravity} + lowerBound={-30} + changeValue={setGravity} + step={0.01} + unit={"m/s2"} + upperBound={0} + value={gravity} + effect={(val: number) => { + setResetAll(!resetAll); + }} + labelWidth={"5em"} + /> + )} + {simulationPaused && simulationType != "Pulley" && ( + Mass} + lowerBound={1} + changeValue={setMass} + step={0.1} + unit={"kg"} + upperBound={5} + value={mass} + effect={(val: number) => { + setResetAll(!resetAll); + }} + labelWidth={"5em"} + /> + )} + {simulationPaused && simulationType == "Pulley" && ( + Red mass} + lowerBound={1} + changeValue={setMass} + step={0.1} + unit={"kg"} + upperBound={5} + value={mass} + effect={(val: number) => { + setResetAll(!resetAll); + }} + labelWidth={"5em"} + /> + )} + {simulationPaused && simulationType == "Pulley" && ( + Blue mass} + lowerBound={1} + changeValue={setMass2} + step={0.1} + unit={"kg"} + upperBound={5} + value={mass2} + effect={(val: number) => { + setResetAll(!resetAll); + }} + labelWidth={"5em"} + /> + )} + {simulationPaused && simulationType == "Circular Motion" && ( + Rod length} + lowerBound={100} + changeValue={setCircularMotionRadius} + step={5} + unit={"kg"} + upperBound={250} + value={circularMotionRadius} + effect={(val: number) => { + setResetAll(!resetAll); + }} + labelWidth={"5em"} + /> + )} + + + {simulationType == "Spring" && simulationPaused && ( +
+ Spring stiffness + } + lowerBound={0.1} + changeValue={setSpringConstant} + step={1} + unit={"N/m"} + upperBound={500} + value={springConstant} + effect={(val: number) => { + setSimulationReset(!simulationReset); + }} + radianEquivalent={false} + mode={"Freeform"} + labelWidth={"7em"} + /> + Rest length} + lowerBound={10} + changeValue={setSpringRestLength} + step={100} + unit={""} + upperBound={500} + value={springRestLength} + effect={(val: number) => { + setSimulationReset(!simulationReset); + }} + radianEquivalent={false} + mode={"Freeform"} + labelWidth={"7em"} + /> + + Starting displacement + + } + lowerBound={-(springRestLength - 10)} + changeValue={(val: number) => {}} + step={10} + unit={""} + upperBound={springRestLength} + value={springStartLength - springRestLength} + effect={(val: number) => { + setStartPosY(springRestLength + val); + setSpringStartLength(springRestLength + val); + setSimulationReset(!simulationReset); + }} + radianEquivalent={false} + mode={"Freeform"} + labelWidth={"7em"} + /> +
+ )} + {simulationType == "Inclined Plane" && simulationPaused && ( +
+ θ} + lowerBound={0} + changeValue={setWedgeAngle} + step={1} + unit={"°"} + upperBound={49} + value={wedgeAngle} + effect={(val: number) => { + changeWedgeBasedOnNewAngle(val); + setSimulationReset(!simulationReset); + }} + radianEquivalent={true} + mode={"Freeform"} + labelWidth={"2em"} + /> + + μs + + } + lowerBound={0} + changeValue={setCoefficientOfStaticFriction} + step={0.1} + unit={""} + upperBound={1} + value={coefficientOfStaticFriction} + effect={(val: number) => { + updateForcesWithFriction(val); + if (val < Number(coefficientOfKineticFriction)) { + setCoefficientOfKineticFriction(val); + } + setSimulationReset(!simulationReset); + }} + mode={"Freeform"} + labelWidth={"2em"} + /> + + μk + + } + lowerBound={0} + changeValue={setCoefficientOfKineticFriction} + step={0.1} + unit={""} + upperBound={Number(coefficientOfStaticFriction)} + value={coefficientOfKineticFriction} + effect={(val: number) => { + setSimulationReset(!simulationReset); + }} + mode={"Freeform"} + labelWidth={"2em"} + /> +
+ )} + {simulationType == "Inclined Plane" && !simulationPaused && ( + + θ: {Math.round(Number(wedgeAngle) * 100) / 100}° ≈{" "} + {Math.round(((Number(wedgeAngle) * Math.PI) / 180) * 100) / + 100}{" "} + rad +
+ μ s: {coefficientOfStaticFriction} +
+ μ k: {coefficientOfKineticFriction} +
+ )} + {simulationType == "Pendulum" && !simulationPaused && ( + + θ: {Math.round(pendulumAngle * 100) / 100}° ≈{" "} + {Math.round(((pendulumAngle * Math.PI) / 180) * 100) / 100}{" "} + rad + + )} + {simulationType == "Pendulum" && simulationPaused && ( +
+ Angle} + lowerBound={0} + changeValue={setPendulumAngle} + step={1} + unit={"°"} + upperBound={59} + value={pendulumAngle} + effect={(value) => { + setStartPendulumAngle(value); + if (simulationType == "Pendulum") { + const mag = + mass * + Math.abs(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(gravity) * + Math.cos((value * Math.PI) / 180), + directionInDegrees: 270 - value, + component: true, + }; + const gravityPerpendicular: IForce = { + description: "Gravity Perpendicular Component", + magnitude: + Math.abs(gravity) * + Math.sin((value * Math.PI) / 180), + directionInDegrees: -value, + component: true, + }; + + const length = pendulumLength; + const x = + length * Math.cos(((90 - value) * Math.PI) / 180); + const y = + length * Math.sin(((90 - value) * Math.PI) / 180); + const xPos = xMax / 2 - x - radius; + const yPos = y - radius - 5; + setStartPosX(xPos); + setStartPosY(yPos); + + setStartForces([ + { + description: "Gravity", + magnitude: Math.abs(gravity) * mass, + directionInDegrees: 270, + component: false, + }, + forceOfTension, + ]); + setUpdatedForces([ + { + description: "Gravity", + magnitude: Math.abs(gravity) * mass, + directionInDegrees: 270, + component: false, + }, + forceOfTension, + ]); + setComponentForces([ + tensionComponent, + gravityParallel, + gravityPerpendicular, + ]); + setAdjustPendulumAngle({ + angle: value, + length: pendulumLength, + }); + setSimulationReset(!simulationReset); + } + }} + radianEquivalent={true} + mode={"Freeform"} + labelWidth={"5em"} + /> + Rod length} + lowerBound={0} + changeValue={setPendulumLength} + step={1} + unit={"m"} + upperBound={400} + value={Math.round(pendulumLength)} + effect={(value) => { + if (simulationType == "Pendulum") { + setAdjustPendulumAngle({ + angle: pendulumAngle, + length: value, + }); + setSimulationReset(!simulationReset); + } + }} + radianEquivalent={false} + mode={"Freeform"} + labelWidth={"5em"} + /> +
+ )} +
+ )} +
+ {mode == "Freeform" && ( + + + + + + + + + + {(!simulationPaused || + simulationType == "Inclined Plane" || + simulationType == "Suspension" || + simulationType == "Circular Motion" || + simulationType == "Pulley") && ( + + )}{" "} + {simulationPaused && + simulationType != "Inclined Plane" && + simulationType != "Suspension" && + simulationType != "Circular Motion" && + simulationType != "Pulley" && ( + + )}{" "} + {(!simulationPaused || + simulationType == "Inclined Plane" || + simulationType == "Suspension" || + simulationType == "Circular Motion" || + simulationType == "Pulley") && ( + + )}{" "} + {simulationPaused && + simulationType != "Inclined Plane" && + simulationType != "Suspension" && + simulationType != "Circular Motion" && + simulationType != "Pulley" && ( + + )}{" "} + + + + {(!simulationPaused || + (simulationType != "One Weight" && + simulationType != "Circular Motion")) && ( + + )}{" "} + {simulationPaused && + (simulationType == "One Weight" || + simulationType == "Circular Motion") && ( + + )}{" "} + {(!simulationPaused || simulationType != "One Weight") && ( + + )}{" "} + {simulationPaused && simulationType == "One Weight" && ( + + )}{" "} + + + + + + + + + + + + +
{simulationType == "Pulley" ? "Red Weight" : ""}XY
{ + window.open( + "https://www.khanacademy.org/science/physics/two-dimensional-motion" + ); + }} + > + Position + + {positionXDisplay} m + + { + setDisplayChange({ + xDisplay: value, + yDisplay: positionYDisplay, + }); + }} + small={true} + mode={"Freeform"} + /> + + {positionYDisplay} m + + { + setDisplayChange({ + xDisplay: positionXDisplay, + yDisplay: value, + }); + }} + small={true} + mode={"Freeform"} + /> +
{ + window.open( + "https://www.khanacademy.org/science/physics/two-dimensional-motion" + ); + }} + > + Velocity + + {velocityXDisplay} m/s + + { + setStartVelX(value); + setSimulationReset(!simulationReset); + }} + small={true} + mode={"Freeform"} + /> + + {velocityYDisplay} m/s + + { + setStartVelY(-value); + setDisplayChange({ + xDisplay: positionXDisplay, + yDisplay: positionYDisplay, + }); + }} + small={true} + mode={"Freeform"} + /> +
{ + window.open( + "https://www.khanacademy.org/science/physics/two-dimensional-motion" + ); + }} + > + Acceleration + + {accelerationXDisplay} m/s2 + + {accelerationYDisplay} m/s2 +
+ Momentum + + {Math.round(velocityXDisplay * mass * 10) / 10} kg*m/s + + {Math.round(velocityYDisplay * mass * 10) / 10} kg*m/s +
+ )} + {mode == "Freeform" && simulationType == "Pulley" && ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Blue WeightXY
+ Position + {positionXDisplay2} m{positionYDisplay2} m
+ Velocity + + {velocityXDisplay2} m/s + + {velocityYDisplay2} m/s +
+ Acceleration + + {accelerationXDisplay2} m/s2 + + {accelerationYDisplay2} m/s2 +
+ Momentum + + {Math.round(velocityXDisplay2 * mass * 10) / 10} kg*m/s + + {Math.round(velocityYDisplay2 * mass * 10) / 10} kg*m/s +
+ )} +
+ {simulationType != "Pendulum" && simulationType != "Spring" && ( +
+

Kinematic Equations

+
    +
  • + Position: x1=x0+v0t+ + 1⁄ + 2at + 2 +
  • +
  • + Velocity: v1=v0+at +
  • +
  • Acceleration: a = F/m
  • +
+
+ )} + {simulationType == "Spring" && ( +
+

Harmonic Motion Equations: Spring

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

Harmonic Motion Equations: Pendulum

+
    +
  • + Pendulum period: Tp=2π√l⁄ + g +
  • +
+
+ )} +
+
+
+ + + + + + + + + +

+ {simulationType == "Circular Motion" ? "Z" : "Y"} +

+

+ X +

+
+
} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From f6ad7cf21b8763a0dd54275a8e433e473e382afe Mon Sep 17 00:00:00 2001 From: brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> Date: Mon, 1 May 2023 14:45:01 -0400 Subject: add box --- .../nodes/PhysicsBox/PhysicsSimulationBox.tsx | 355 ++++++++++----------- 1 file changed, 177 insertions(+), 178 deletions(-) (limited to 'src/client/views/nodes/PhysicsBox') diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx index 101cf1d4a..446c4933b 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx @@ -89,8 +89,8 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent - Speed} lowerBound={1} - changeValue={setSimulationSpeed} + changeValue={this.dataDoc.simulationSpeed} //TODO - deal with input field change value now that datadoc is being used! step={1} unit={"x"} upperBound={10} - value={simulationSpeed} + value={this.dataDoc.simulationSpeed} labelWidth={"5em"} /> - {simulationPaused && simulationType != "Circular Motion" && ( + {this.dataDoc.simulationPaused && this.dataDoc.simulationType != "Circular Motion" && ( Gravity} lowerBound={-30} - changeValue={setGravity} + changeValue={this.dataDoc.gravity} step={0.01} unit={"m/s2"} upperBound={0} - value={gravity} + value={this.dataDoc.gravity} effect={(val: number) => { - setResetAll(!resetAll); + this.dataDoc.resetAll = (!this.dataDoc.resetAll); }} labelWidth={"5em"} /> )} - {simulationPaused && simulationType != "Pulley" && ( + {this.dataDoc.simulationPaused && this.dataDoc.simulationType != "Pulley" && ( Mass} lowerBound={1} - changeValue={setMass} + changeValue={this.dataDoc.mass} step={0.1} unit={"kg"} upperBound={5} - value={mass} + value={this.dataDoc.mass} effect={(val: number) => { - setResetAll(!resetAll); + this.dataDoc.resetAll = (!this.dataDoc.resetAll); }} labelWidth={"5em"} /> )} - {simulationPaused && simulationType == "Pulley" && ( + {this.dataDoc.simulationPaused && this.dataDoc.simulationType == "Pulley" && ( Red mass} lowerBound={1} - changeValue={setMass} + changeValue={this.dataDoc.mass} step={0.1} unit={"kg"} upperBound={5} - value={mass} + value={this.dataDoc.mass} effect={(val: number) => { - setResetAll(!resetAll); + this.dataDoc.resetAll = (!this.dataDoc.resetAll); }} labelWidth={"5em"} /> )} - {simulationPaused && simulationType == "Pulley" && ( + {this.dataDoc.simulationPaused && this.dataDoc.simulationType == "Pulley" && ( Blue mass} lowerBound={1} - changeValue={setMass2} + changeValue={this.dataDoc.mass2} step={0.1} unit={"kg"} upperBound={5} - value={mass2} + value={this.dataDoc.mass2} effect={(val: number) => { - setResetAll(!resetAll); + this.dataDoc.resetAll = (!this.dataDoc.resetAll); }} labelWidth={"5em"} /> )} - {simulationPaused && simulationType == "Circular Motion" && ( + {this.dataDoc.simulationPaused && this.dataDoc.simulationType == "Circular Motion" && ( Rod length} lowerBound={100} - changeValue={setCircularMotionRadius} + changeValue={this.dataDoc.circularMotionRadius} step={5} unit={"kg"} upperBound={250} - value={circularMotionRadius} + value={this.dataDoc.circularMotionRadius} effect={(val: number) => { - setResetAll(!resetAll); + this.dataDoc.resetAll = (!this.dataDoc.resetAll); }} labelWidth={"5em"} /> )} - {simulationType == "Spring" && simulationPaused && ( + {this.dataDoc.simulationType == "Spring" && this.dataDoc.simulationPaused && (
Spring stiffness } lowerBound={0.1} - changeValue={setSpringConstant} + changeValue={this.dataDoc.springConstant} step={1} unit={"N/m"} upperBound={500} value={springConstant} effect={(val: number) => { - setSimulationReset(!simulationReset); + this.dataDoc.simulationReset(!this.dataDoc.simulationReset); }} radianEquivalent={false} mode={"Freeform"} @@ -1872,13 +1871,13 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponentRest length} lowerBound={10} - changeValue={setSpringRestLength} + changeValue={this.dataDoc.springRestLength} step={100} unit={""} upperBound={500} - value={springRestLength} + value={this.dataDoc.springRestLength} effect={(val: number) => { - setSimulationReset(!simulationReset); + this.dataDoc.simulationReset = (!this.dataDoc.simulationReset); }} radianEquivalent={false} mode={"Freeform"} @@ -1890,16 +1889,16 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent } - lowerBound={-(springRestLength - 10)} + lowerBound={-(this.dataDoc.springRestLength - 10)} changeValue={(val: number) => {}} step={10} unit={""} - upperBound={springRestLength} - value={springStartLength - springRestLength} + upperBound={this.dataDoc.springRestLength} + value={this.dataDoc.springStartLength - this.dataDoc.springRestLength} effect={(val: number) => { - setStartPosY(springRestLength + val); - setSpringStartLength(springRestLength + val); - setSimulationReset(!simulationReset); + this.dataDoc.startPosY = (this.dataDoc.springRestLength + val); + this.dataDoc.springStartLength = (this.dataDoc.springRestLength + val); + this.dataDoc.simulationReset = (!this.dataDoc.simulationReset); }} radianEquivalent={false} mode={"Freeform"} @@ -1907,19 +1906,19 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent
)} - {simulationType == "Inclined Plane" && simulationPaused && ( + {this.dataDoc.simulationType == "Inclined Plane" && this.dataDoc.simulationPaused && (
θ} lowerBound={0} - changeValue={setWedgeAngle} + changeValue={this.dataDoc.wedgeAngle} step={1} unit={"°"} upperBound={49} - value={wedgeAngle} + value={this.dataDoc.wedgeAngle} effect={(val: number) => { - changeWedgeBasedOnNewAngle(val); - setSimulationReset(!simulationReset); + this.changeWedgeBasedOnNewAngle(val); + this.dataDoc.simulationReset = (!this.dataDoc.simulationReset); }} radianEquivalent={true} mode={"Freeform"} @@ -1932,17 +1931,17 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent } lowerBound={0} - changeValue={setCoefficientOfStaticFriction} + changeValue={this.dataDoc.coefficientOfStaticFriction} step={0.1} unit={""} upperBound={1} - value={coefficientOfStaticFriction} + value={this.dataDoc.coefficientOfStaticFriction} effect={(val: number) => { - updateForcesWithFriction(val); - if (val < Number(coefficientOfKineticFriction)) { - setCoefficientOfKineticFriction(val); + this.updateForcesWithFriction(val); + if (val < Number(this.dataDoc.coefficientOfKineticFriction)) { + this.dataDoc.soefficientOfKineticFriction = (val); } - setSimulationReset(!simulationReset); + this.dataDoc.simulationReset = (!this.dataDoc.simulationReset); }} mode={"Freeform"} labelWidth={"2em"} @@ -1954,54 +1953,54 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent } lowerBound={0} - changeValue={setCoefficientOfKineticFriction} + changeValue={this.dataDoc.coefficientOfKineticFriction} step={0.1} unit={""} - upperBound={Number(coefficientOfStaticFriction)} - value={coefficientOfKineticFriction} + upperBound={Number(this.dataDoc.coefficientOfStaticFriction)} + value={this.dataDoc.coefficientOfKineticFriction} effect={(val: number) => { - setSimulationReset(!simulationReset); + this.dataDoc.simulationReset = (!this.dataDoc.simulationReset); }} mode={"Freeform"} labelWidth={"2em"} />
)} - {simulationType == "Inclined Plane" && !simulationPaused && ( + {this.dataDoc.simulationType == "Inclined Plane" && !this.dataDoc.simulationPaused && ( - θ: {Math.round(Number(wedgeAngle) * 100) / 100}° ≈{" "} - {Math.round(((Number(wedgeAngle) * Math.PI) / 180) * 100) / + θ: {Math.round(Number(this.dataDoc.wedgeAngle) * 100) / 100}° ≈{" "} + {Math.round(((Number(this.dataDoc.wedgeAngle) * Math.PI) / 180) * 100) / 100}{" "} rad
- μ s: {coefficientOfStaticFriction} + μ s: {this.dataDoc.coefficientOfStaticFriction}
- μ k: {coefficientOfKineticFriction} + μ k: {this.dataDoc.coefficientOfKineticFriction}
)} - {simulationType == "Pendulum" && !simulationPaused && ( + {this.dataDoc.simulationType == "Pendulum" && !this.dataDoc.simulationPaused && ( - θ: {Math.round(pendulumAngle * 100) / 100}° ≈{" "} - {Math.round(((pendulumAngle * Math.PI) / 180) * 100) / 100}{" "} + θ: {Math.round(this.dataDoc.pendulumAngle * 100) / 100}° ≈{" "} + {Math.round(((this.dataDoc.pendulumAngle * Math.PI) / 180) * 100) / 100}{" "} rad )} - {simulationType == "Pendulum" && simulationPaused && ( + {this.dataDoc.simulationType == "Pendulum" && this.dataDoc.simulationPaused && (
Angle} lowerBound={0} - changeValue={setPendulumAngle} + changeValue={this.dataDoc.pendulumAngle} step={1} unit={"°"} upperBound={59} - value={pendulumAngle} + value={this.dataDoc.pendulumAngle} effect={(value) => { - setStartPendulumAngle(value); + this.dataDoc.startPendulumAngle = (value); if (simulationType == "Pendulum") { const mag = mass * - Math.abs(gravity) * + Math.abs(this.dataDoc.gravity) * Math.cos((value * Math.PI) / 180); const forceOfTension: IForce = { @@ -2020,7 +2019,7 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponentRod length} lowerBound={0} - changeValue={setPendulumLength} + changeValue={this.dataDoc.pendulumLength} step={1} unit={"m"} upperBound={400} - value={Math.round(pendulumLength)} + value={Math.round(this.dataDoc.pendulumLength)} effect={(value) => { if (simulationType == "Pendulum") { - setAdjustPendulumAngle({ - angle: pendulumAngle, + this.dataDoc.adjustPendulumAngle = ({ + angle: this.dataDoc.pendulumAngle, length: value, }); - setSimulationReset(!simulationReset); + this.dataDoc.simulationReset = (!this.dataDoc.simulationReset); } }} radianEquivalent={false} @@ -2104,39 +2103,39 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent )}
- {mode == "Freeform" && ( + {this.dataDoc.mode == "Freeform" && ( - + - {(!simulationPaused || - simulationType == "Inclined Plane" || - simulationType == "Suspension" || - simulationType == "Circular Motion" || - simulationType == "Pulley") && ( + {(!this.dataDoc.simulationPaused || + this.dataDoc.simulationType == "Inclined Plane" || + this.dataDoc.simulationType == "Suspension" || + this.dataDoc.simulationType == "Circular Motion" || + this.dataDoc.simulationType == "Pulley") && ( )}{" "} - {simulationPaused && - simulationType != "Inclined Plane" && - simulationType != "Suspension" && - simulationType != "Circular Motion" && - simulationType != "Pulley" && ( + {this.dataDoc.simulationPaused && + this.dataDoc.simulationType != "Inclined Plane" && + this.dataDoc.simulationType != "Suspension" && + this.dataDoc.simulationType != "Circular Motion" && + this.dataDoc.simulationType != "Pulley" && ( )}{" "} - {(!simulationPaused || - simulationType == "Inclined Plane" || - simulationType == "Suspension" || - simulationType == "Circular Motion" || - simulationType == "Pulley") && ( + {(!this.dataDoc.simulationPaused || + this.dataDoc.simulationType == "Inclined Plane" || + this.dataDoc.simulationType == "Suspension" || + this.dataDoc.simulationType == "Circular Motion" || + this.dataDoc.simulationType == "Pulley") && ( )}{" "} - {simulationPaused && - simulationType != "Inclined Plane" && - simulationType != "Suspension" && - simulationType != "Circular Motion" && - simulationType != "Pulley" && ( + {this.dataDoc.simulationPaused && + this.dataDoc.simulationType != "Inclined Plane" && + this.dataDoc.simulationType != "Suspension" && + this.dataDoc.simulationType != "Circular Motion" && + this.dataDoc.simulationType != "Pulley" && ( - {(!simulationPaused || - (simulationType != "One Weight" && - simulationType != "Circular Motion")) && ( + {(!this.dataDoc.simulationPaused || + (this.dataDoc.simulationType != "One Weight" && + this.dataDoc.simulationType != "Circular Motion")) && ( )}{" "} - {simulationPaused && - (simulationType == "One Weight" || - simulationType == "Circular Motion") && ( + {this.dataDoc.simulationPaused && + (this.dataDoc.simulationType == "One Weight" || + this.dataDoc.simulationType == "Circular Motion") && ( )}{" "} - {(!simulationPaused || simulationType != "One Weight") && ( + {(!this.dataDoc.simulationPaused || this.dataDoc.simulationType != "One Weight") && ( )}{" "} - {simulationPaused && simulationType == "One Weight" && ( + {this.dataDoc.simulationPaused && this.dataDoc.simulationType == "One Weight" && ( @@ -2294,16 +2293,16 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponentMomentum
{simulationType == "Pulley" ? "Red Weight" : ""}{this.dataDoc.simulationType == "Pulley" ? "Red Weight" : ""} X Y
{ - window.open( - "https://www.khanacademy.org/science/physics/two-dimensional-motion" - ); - }} + // onClick={() => { + // window.open( + // "https://www.khanacademy.org/science/physics/two-dimensional-motion" + // ); + // }} > Position - {positionXDisplay} m + {this.dataDoc.positionXDisplay} m { - setDisplayChange({ + this.dataDoc.displayChange = ({ xDisplay: value, - yDisplay: positionYDisplay, + yDisplay: this.dataDoc.positionYDisplay, }); }} small={true} @@ -2160,20 +2159,20 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent - {positionYDisplay} m + {this.dataDoc.positionYDisplay} m { - setDisplayChange({ - xDisplay: positionXDisplay, + this.dataDoc.displayChange = ({ + xDisplay: this.dataDoc.positionXDisplay, yDisplay: value, }); }} @@ -2201,24 +2200,24 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { - window.open( - "https://www.khanacademy.org/science/physics/two-dimensional-motion" - ); - }} + // onClick={() => { + // window.open( + // "https://www.khanacademy.org/science/physics/two-dimensional-motion" + // ); + // }} > Velocity - {velocityXDisplay} m/s + {this.dataDoc.velocityXDisplay} m/s { - setStartVelX(value); - setSimulationReset(!simulationReset); + this.dataDoc.startVelX = (value); + this.dataDoc.simulationReset = (!this.dataDoc.simulationReset); }} small={true} mode={"Freeform"} /> - {velocityYDisplay} m/s + {this.dataDoc.velocityYDisplay} m/s { - setStartVelY(-value); - setDisplayChange({ - xDisplay: positionXDisplay, - yDisplay: positionYDisplay, + this.dataDoc.startVelY = (-value); + this.dataDoc.displayChange = ({ + xDisplay: this.dataDoc.positionXDisplay, + yDisplay: this.dataDoc.positionYDisplay, }); }} small={true} @@ -2274,19 +2273,19 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { - window.open( - "https://www.khanacademy.org/science/physics/two-dimensional-motion" - ); - }} + // onClick={() => { + // window.open( + // "https://www.khanacademy.org/science/physics/two-dimensional-motion" + // ); + // }} > Acceleration - {accelerationXDisplay} m/s2 + {this.dataDoc.accelerationXDisplay} m/s2 - {accelerationYDisplay} m/s2 + {this.dataDoc.accelerationYDisplay} m/s2
- {Math.round(velocityXDisplay * mass * 10) / 10} kg*m/s + {Math.round(this.dataDoc.velocityXDisplay * this.dataDoc.mass * 10) / 10} kg*m/s - {Math.round(velocityYDisplay * mass * 10) / 10} kg*m/s + {Math.round(this.dataDoc.velocityYDisplay * this.dataDoc.mass * 10) / 10} kg*m/s
)} - {mode == "Freeform" && simulationType == "Pulley" && ( + {this.dataDoc.mode == "Freeform" && this.dataDoc.simulationType == "Pulley" && ( @@ -2315,19 +2314,19 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent Position - - + + @@ -2335,10 +2334,10 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponentAcceleration @@ -2346,17 +2345,17 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponentMomentum
{positionXDisplay2} m{positionYDisplay2} m{this.dataDoc.positionXDisplay2} m{this.dataDoc.positionYDisplay2} m
Velocity - {velocityXDisplay2} m/s + {this.dataDoc.velocityXDisplay2} m/s - {velocityYDisplay2} m/s + {this.dataDoc.velocityYDisplay2} m/s
- {accelerationXDisplay2} m/s2 + {this.dataDoc.accelerationXDisplay2} m/s2 - {accelerationYDisplay2} m/s2 + {this.dataDoc.accelerationYDisplay2} m/s2
- {Math.round(velocityXDisplay2 * mass * 10) / 10} kg*m/s + {Math.round(this.dataDoc.velocityXDisplay2 * this.dataDoc.mass * 10) / 10} kg*m/s - {Math.round(velocityYDisplay2 * mass * 10) / 10} kg*m/s + {Math.round(this.dataDoc.velocityYDisplay2 * this.dataDoc.mass * 10) / 10} kg*m/s
)}
- {simulationType != "Pendulum" && simulationType != "Spring" && ( + {this.dataDoc.simulationType != "Pendulum" && this.dataDoc.simulationType != "Spring" && (

Kinematic Equations

    @@ -2373,7 +2372,7 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent
)} - {simulationType == "Spring" && ( + {this.dataDoc.simulationType == "Spring" && (

Harmonic Motion Equations: Spring

    @@ -2409,7 +2408,7 @@ export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent
)} - {simulationType == "Pendulum" && ( + {this.dataDoc.simulationType == "Pendulum" && (

Harmonic Motion Equations: Pendulum