diff options
Diffstat (limited to 'src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx')
-rw-r--r-- | src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx | 494 |
1 files changed, 494 insertions, 0 deletions
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<FieldViewProps>() { + + 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 ( + <div> + <div className="mechanicsSimulationContainer"> + <div className="mechanicsSimulationContentContainer"> + <div className="mechanicsSimulationButtonsAndElements"> + <div className="mechanicsSimulationElements"> + {this.dataDoc.weight && ( + <Weight + adjustPendulumAngle={this.dataDoc.adjustPendulumAngle} + color={"red"} + dataDoc={this.dataDoc} + mass={1} + radius={this.radius} + simulationReset={this.dataDoc.simulationReset} + startPosX={this.dataDoc.startPosX} + startPosY={this.dataDoc.startPosY} + timestepSize={0.002} + updateDisplay={this.dataDoc.updateDisplay} + walls={this.dataDoc.wallPositions} + wedge={this.dataDoc.wedge} + wedgeWidth={this.dataDoc.wedgeWidth} + wedgeHeight={this.dataDoc.wedgeHeight} + xMax={this.xMax} + xMin={this.xMin} + yMax={this.yMax} + yMin={this.yMin} + /> + )} + {this.dataDoc.wedge && ( + <Wedge + startWidth={this.dataDoc.wedgeWidth} + startHeight={this.dataDoc.wedgeHeight} + startLeft={this.xMax * 0.2} + xMax={this.xMax} + yMax={this.yMax} + /> + )} + </div> + <div> + {(this.dataDoc.wallPositions ?? []).map((element: { length: number; xPos: number; yPos: number; angleInDegrees: number; }, index: React.Key | null | undefined) => { + return ( + <div key={index}> + <Wall + length={element.length} + xPos={element.xPos} + yPos={element.yPos} + angleInDegrees={element.angleInDegrees} + /> + </div> + ); + })} + </div> + </div> + </div> + <div style = {{width: this.layoutDoc._width+'px', height: this.layoutDoc._height+'px'}}> + {this.menuIsOpen && ( + <div className="mechanicsSimulationSettingsMenu"> + <div className="close-button" onClick={() => {this.menuIsOpen = false; this.dataDoc.simulationReset = !this.dataDoc.simulationReset;}}> + <FontAwesomeIcon icon={'times'} color="black" size={'lg'} /> + </div> + <h4>Simulation Settings</h4> + <div className="mechanicsSimulationSettingsMenuRow"> + <div className="mechanicsSimulationSettingsMenuRowDescription"><p>Show forces</p></div> + <div><input type="checkbox" checked={this.dataDoc.showForces} onClick={() => {this.dataDoc.showForces = !this.dataDoc.showForces}}/></div> + </div> + <div className="mechanicsSimulationSettingsMenuRow"> + <div className="mechanicsSimulationSettingsMenuRowDescription"><p>Show acceleration</p></div> + <div><input type="checkbox" checked={this.dataDoc.showAcceleration} onClick={() => {this.dataDoc.showAcceleration = !this.dataDoc.showAcceleration}}/></div> + </div> + <div className="mechanicsSimulationSettingsMenuRow"> + <div className="mechanicsSimulationSettingsMenuRowDescription"> + <p>Show velocity</p></div> + <div><input type="checkbox" checked={this.dataDoc.showVelocity} onClick={() => {this.dataDoc.showVelocity = !this.dataDoc.showVelocity}}/></div> + </div> + <hr/> + {this.dataDoc.simulationType == "Free Weight" && <div className="mechanicsSimulationSettingsMenuRow"> + <div className="mechanicsSimulationSettingsMenuRowDescription"><p>Elastic collisions </p></div> + <div><input type="checkbox" checked={this.dataDoc.elasticCollisions} onClick={() => {this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions}}/></div> + </div>} + {this.dataDoc.simulationType == "Pendulum" && <div className="mechanicsSimulationSettingsMenuRow"> + <div className="mechanicsSimulationSettingsMenuRowDescription"><p>Pendulum start angle</p></div> + <div> + <input + type="number" + value={this.dataDoc.startPendulumAngle} + max={35} + min={0} + step={1} + onInput={(e) => { + 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; + }} + /> + </div> + </div>} + {this.dataDoc.simulationType == "Inclined Plane" && <div className="mechanicsSimulationSettingsMenuRow"> + <div className="mechanicsSimulationSettingsMenuRowDescription"><p>Inclined plane angle</p></div> + <div> + <input + type="number" + value={this.dataDoc.wedgeAngle} + max={70} + min={0} + step={1} + onInput={(e) => { + let angle = e.target.value ?? 0 + if (angle > 70) { + angle = 70 + } + if (angle < 0) { + angle = 0 + } + this.dataDoc.wedgeAngle = angle + this.changeWedgeBasedOnNewAngle(angle) + }} + /> + </div> + </div>} + </div> + )} + </div> + <div className="mechanicsSimulationEquationContainer"> + <div className="mechanicsSimulationControls"> + <div> + {this.dataDoc.simulationPaused && ( + <button onClick={() => { + this.dataDoc.simulationPaused = false} + } >START</button> + )} + {!this.dataDoc.simulationPaused && ( + <button onClick={() => { + this.dataDoc.simulationPaused = true} + } >PAUSE</button> + )} + {this.dataDoc.simulationPaused && ( + <button onClick={() => { + this.dataDoc.simulationReset = !this.dataDoc.simulationReset} + } >RESET</button> + )} + {this.dataDoc.simulationPaused && ( <button onClick={() => { + if (!this.dataDoc.pendulum && !this.dataDoc.wedge) { + this.addWedge() + this.setToWedgeDefault() + this.dataDoc.simulationType = "Inclined Plane" + this.dataDoc.elasticCollisions = false + } + else if (!this.dataDoc.pendulum && this.dataDoc.wedge) { + this.setToPendulumDefault() + this.addPendulum() + this.dataDoc.simulationType = "Pendulum" + this.dataDoc.elasticCollisions = false + } + else { + this.setToWeightDefault() + this.addWeight() + this.dataDoc.simulationType = "Free Weight" + } + this.dataDoc.simulationReset = !this.dataDoc.simulationReset + }} >TYPE</button>)} + <button onClick={() => {this.menuIsOpen=true; this.dataDoc.simulationReset = !this.dataDoc.simulationReset;}}>MENU</button> + </div> + </div> + </div> + </div> + </div> + ); + } + }
\ No newline at end of file |