aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx')
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx494
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