diff options
author | brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> | 2023-02-12 15:34:16 -0500 |
---|---|---|
committer | brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> | 2023-02-12 15:34:16 -0500 |
commit | cb19032c67d810d3683e101896e20df7eecec77c (patch) | |
tree | c7dc6ffab467ae33346e91fd2f8dc87f6b2d7fae /src/client/views/nodes/PhysicsSimulationBox.tsx | |
parent | 456d91f480ed96ac963c9f17164eb23e76fb4320 (diff) |
start making everything persistent
Diffstat (limited to 'src/client/views/nodes/PhysicsSimulationBox.tsx')
-rw-r--r-- | src/client/views/nodes/PhysicsSimulationBox.tsx | 573 |
1 files changed, 556 insertions, 17 deletions
diff --git a/src/client/views/nodes/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsSimulationBox.tsx index 4a68ba5aa..3ea4fba9f 100644 --- a/src/client/views/nodes/PhysicsSimulationBox.tsx +++ b/src/client/views/nodes/PhysicsSimulationBox.tsx @@ -3,33 +3,572 @@ import { FieldView, FieldViewProps } from './FieldView'; import React = require('react'); import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { observer } from 'mobx-react'; -import App from './PhysicsSimulationApp'; +import "./PhysicsSimulationBox.scss"; +import Weight from "./PhysicsSimulationWeight"; +import Wall from "./PhysicsSimulationWall" +import Wedge from "./PhysicsSimulationWedge" export interface IForce { - description: string; - magnitude: number; - directionInDegrees: number; + description: string; + magnitude: number; + directionInDegrees: number; } export interface IWallProps { - length: number; - xPos: number; - yPos: number; - angleInDegrees: number; + 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; +} + +interface IState { + accelerationXDisplay: number, + accelerationYDisplay: number, + adjustPendulumAngle: {angle: number, length: number}, + coefficientOfKineticFriction: number, + coefficientOfStaticFriction: number, + currentForceSketch: PhysicsVectorTemplate | null, + deleteMode: boolean, + displayChange: {xDisplay: number, yDisplay: number}, + elasticCollisions: boolean, + forceSketches: PhysicsVectorTemplate[], + pendulum: boolean, + pendulumAngle: number, + pendulumLength: number, + positionXDisplay: number, + positionYDisplay: number, + showAcceleration: boolean, + showForceMagnitudes: boolean, + showForces: boolean, + showVelocity: boolean, + simulationPaused: boolean, + simulationReset: boolean, + simulationType: "Inclined Plane", + sketching: boolean, + startForces: IForce[], + startPendulumAngle: number, + startPosX: number, + startPosY: number, + stepNumber: number, + timer: number, + updatedForces: IForce[], + velocityXDisplay: number, + velocityYDisplay: number, + wallPositions: IWallProps[], + wedge: boolean, + wedgeAngle: number, + wedgeHeight: number, + wedgeWidth: number, + weight: boolean, } @observer export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PhysicsSimulationBox, fieldKey); } + 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 + + constructor(props: any) { + super(props); + this.dataDoc.accelerationXDisplay = 0; + this.dataDoc.accelerationYDisplay = 0; + this.dataDoc.adjustPendulumAngle = {angle: 0, length: 0}; + this.dataDoc.coefficientOfKineticFriction = 0; + this.dataDoc.coefficientOfStaticFriction = 0; + this.dataDoc.currentForceSketch = []; + this.dataDoc.deleteMode = false; + this.dataDoc.displayChange = {xDisplay: 0, yDisplay: 0}; + this.dataDoc.elasticCollisions = false; + this.dataDoc.forceSketches = []; + this.dataDoc.pendulum = false; + this.dataDoc.pendulumAngle = 0; + this.dataDoc.pendulumLength = 300; + this.dataDoc.positionXDisplay = 0; + this.dataDoc.positionYDisplay = 0; + this.dataDoc.showAcceleration = false; + this.dataDoc.showForceMagnitudes = false; + this.dataDoc.showForces = false; + this.dataDoc.showVelocity = false; + this.dataDoc.simulationPaused = true; + this.dataDoc.simulationReset = false; + this.dataDoc.simulationType = "Inclined Plane"; + this.dataDoc.sketching = false; + this.dataDoc.startForces = [this.forceOfGravity]; + this.dataDoc.startPendulumAngle = 0; + this.dataDoc.startPosX = 0; + this.dataDoc.startPosY = 0; + this.dataDoc.stepNumber = 0; + this.dataDoc.timer = 0; + this.dataDoc.updatedForces = [this.forceOfGravity]; + this.dataDoc.velocityXDisplay = 0; + this.dataDoc.velocityYDisplay = 0; + this.dataDoc.wallPositions = []; + this.dataDoc.wedge = false; + this.dataDoc.wedgeAngle = 26; + this.dataDoc.wedgeHeight = Math.tan((26 * Math.PI) / 180) * this.xMax*0.6; + this.dataDoc.wedgeWidth = this.xMax*0.6; + this.dataDoc.weight = false; + } + + // Add one weight to the simulation + addWeight () { + this.dataDoc.weight = true; + this.dataDoc.wedge = false; + this.dataDoc.pendulum = false; + 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]; + this.addWalls(); + this.dataDoc.simulationReset = !this.dataDoc.simulationReset; + }; + + // Add a wedge with a One Weight to the simulation + addWedge () { + this.dataDoc.weight = true; + this.dataDoc.wedge = true; + this.dataDoc.pendulum = false; + this.changeWedgeBasedOnNewAngle(26); + this.addWalls(); + this.dataDoc.startForces = [this.forceOfGravity]; + 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; + 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); + let forceOfTension: IForce = { + description: "Tension", + magnitude: mag, + directionInDegrees: 90 - angle, + }; + this.dataDoc.updatedForces = [this.forceOfGravity, forceOfTension]; + this.dataDoc.startForces = [this.forceOfGravity, forceOfTension]; + this.dataDoc.pendulumAngle = angle; + this.dataDoc.pendulumLength = length; + this.dataDoc.adjustPendulumAngle = {angle: angle, length: length}; + this.removeWalls(); + }; + + // 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 + ); + }; + + // 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 = () => { + if (this.dataDoc.wallPositions.length == 0) { + 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() { + // Add weight + this.addPendulum() + + // 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; + } + }); - constructor(props: any) { - super(props); - } + // Timer for animating the simulation + setInterval(() => { + this.dataDoc.timer = this.dataDoc.timer+1; + }, 60); + } render () { return ( - <div className = "physicsSimulationContainer"> - <App/> - </div> - ); - } + <div> + <div className="mechanicsSimulationContainer"> + <div + className="mechanicsSimulationContentContainer" + onPointerMove={(e) => { + // if (sketching) { + // x1 = positionXDisplay + 50; + // y1 = yMax - positionYDisplay - 2 * 50 + 5 + 50; + // x2 = e.clientX; + // y2 = e.clientY; + // height = Math.abs(y1 - y2) + 120; + // width = Math.abs(x1 - x2) + 120; + // top = Math.min(y1, y2) - 60; + // left = Math.min(x1, x2) - 60; + // x1Updated = x1 - left; + // x2Updated = x2 - left; + // y1Updated = y1 - top; + // y2Updated = y2 - top; + // setCurrentForceSketch({ + // top: top, + // left: left, + // width: width, + // height: height, + // x1: x1Updated, + // y1: y1Updated, + // x2: x2Updated, + // y2: y2Updated, + // weightX: positionXDisplay, + // weightY: positionYDisplay, + // }); + // } + }} + onPointerDown={(e) => { + // if (sketching && currentForceSketch) { + // setSketching(false); + // sketches = forceSketches; + // sketches.push(currentForceSketch); + // setForceSketches(sketches); + // setCurrentForceSketch(null); + // } + }} + > + <div className="mechanicsSimulationButtonsAndElements"> + <div className="mechanicsSimulationElements"> + {/* {showForces && currentForceSketch && simulationPaused && ( + <div + style={{ + position: "fixed", + top: currentForceSketch.top, + left: currentForceSketch.left, + }} + > + <svg + width={currentForceSketch.width + "px"} + height={currentForceSketch.height + "px"} + > + <defs> + <marker + id="sketchArrow" + markerWidth="10" + markerHeight="10" + refX="0" + refY="2" + orient="auto" + markerUnits="strokeWidth" + > + <path d="M0,0 L0,4 L6,2 z" fill={color} /> + </marker> + </defs> + <line + x1={currentForceSketch.x1} + y1={currentForceSketch.y1} + x2={currentForceSketch.x2} + y2={currentForceSketch.y2} + stroke={color} + strokeWidth="10" + markerEnd="url(#sketchArrow)" + /> + </svg> + </div> + )} */} + {/* {showForces && + forceSketches.length > 0 && + simulationPaused && + forceSketches.map((element: PhysicsVectorTemplate, index) => { + return ( + <div + key={index} + style={{ + position: "fixed", + top: element.top + (positionYDisplay - element.weightY), + left: + element.left + (positionXDisplay - element.weightX), + }} + > + <svg + width={element.width + "px"} + height={element.height + "px"} + > + <defs> + <marker + id="sketchArrow" + markerWidth="10" + markerHeight="10" + refX="0" + refY="2" + orient="auto" + markerUnits="strokeWidth" + > + <path d="M0,0 L0,4 L6,2 z" fill={color} /> + </marker> + </defs> + <line + x1={element.x1} + y1={element.y1} + x2={element.x2} + y2={element.y2} + stroke={color} + strokeWidth="10" + markerEnd="url(#sketchArrow)" + onClick={() => { + if (deleteMode) { + deleteForce(element); + } else { + editForce(element); + } + }} + /> + </svg> + </div> + ); + })} */} + {this.dataDoc.weight && ( + <Weight + adjustPendulumAngle={this.dataDoc.adjustPendulumAngle} + color={"red"} + displayXPosition={this.dataDoc.positionXDisplay} + displayXVelocity={this.dataDoc.velocityXDisplay} + displayYPosition={this.dataDoc.positionYDisplay} + displayYVelocity={this.dataDoc.velocityYDisplay} + elasticCollisions={this.dataDoc.elasticCollisions} + incrementTime={this.dataDoc.timer} + mass={1} + paused={this.dataDoc.simulationPaused} + pendulum={this.dataDoc.pendulum} + pendulumAngle={this.dataDoc.pendulumAngle} + pendulumLength={this.dataDoc.pendulumLength} + radius={this.radius} + reset={this.dataDoc.simulationReset} + showForceMagnitudes={this.dataDoc.showForceMagnitudes} + setSketching={(val: boolean) => {this.dataDoc.sketching = val}} + setDisplayXAcceleration={(val: number) => {this.dataDoc.accelerationXDisplay = val}} + setDisplayXPosition={(val: number) => {this.dataDoc.positionXDisplay = val}} + setDisplayXVelocity={(val: number) => {this.dataDoc.velocityXDisplay = val}} + setDisplayYAcceleration={(val: number) => {this.dataDoc.accelerationYDisplay = val}} + setDisplayYPosition={(val: number) => {this.dataDoc.positionYDisplay = val}} + setDisplayYVelocity={(val: number) => {this.dataDoc.velocityYDisplay = val}} + setPaused={(val: boolean) => {this.dataDoc.simulationPaused = val}} + setPendulumAngle={(val: number) => {this.dataDoc.pendulumAngle = val}} + setPendulumLength={(val: number) => {this.dataDoc.pendulumLength = val}} + setStartPendulumAngle={(val: number) => {this.dataDoc.startPendulumAngle = val}} + setUpdatedForces={(val: IForce[]) => {this.dataDoc.updatedForces = val}} + showAcceleration={this.dataDoc.showAcceleration} + showForces={this.dataDoc.showForces} + showVelocity={this.dataDoc.showVelocity} + startForces={this.dataDoc.startForces} + startPosX={this.dataDoc.startPosX} + startPosY={this.dataDoc.startPosY} + timestepSize={0.002} + updateDisplay={this.dataDoc.displayChange} + updatedForces={this.dataDoc.updatedForces} + walls={this.dataDoc.wallPositions} + wedge={this.dataDoc.wedge} + wedgeHeight={this.dataDoc.wedgeHeight} + wedgeWidth={this.dataDoc.wedgeWidth} + coefficientOfKineticFriction={this.dataDoc.coefficientOfKineticFriction} + xMax={this.xMax} + yMax={this.yMax} + xMin={this.xMin} + 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 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> + )} + <button onClick={() => { + if (!this.dataDoc.pendulum && !this.dataDoc.wedge) { + this.addWedge() + } + else if (!this.dataDoc.pendulum && this.dataDoc.wedge) { + this.addPendulum() + } + else { + this.addWeight() + } + }} >TYPE</button> + </div> + </div> + </div> + </div> + </div> + ); + } }
\ No newline at end of file |