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.tsx | 494 +++++++++++++++++++++ 1 file changed, 494 insertions(+) create mode 100644 src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx (limited to 'src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx') 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 -- cgit v1.2.3-70-g09d2