import React = require('react'); import "./PhysicsSimulationBox.scss"; import Weight, { IForce } from "./PhysicsSimulationWeight"; import Wall, { IWallProps } from "./PhysicsSimulationWall" import Wedge from "./PhysicsSimulationWedge" 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, } export default class App extends React.Component<{}, IState> { // 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.state = { accelerationXDisplay: 0, accelerationYDisplay: 0, adjustPendulumAngle: {angle: 0, length: 0}, coefficientOfKineticFriction: 0, coefficientOfStaticFriction: 0, currentForceSketch: null, deleteMode: false, displayChange: {xDisplay: 0, yDisplay: 0}, elasticCollisions: false, forceSketches: [], pendulum: false, pendulumAngle: 0, pendulumLength: 300, positionXDisplay: 0, positionYDisplay: 0, showAcceleration: false, showForceMagnitudes: false, showForces: false, showVelocity: false, simulationPaused: true, simulationReset: false, simulationType: "Inclined Plane", sketching: false, startForces: [this.forceOfGravity], startPendulumAngle: 0, startPosX: 0, startPosY: 0, stepNumber: 0, timer: 0, updatedForces: [this.forceOfGravity], velocityXDisplay: 0, velocityYDisplay: 0, wallPositions: [], wedge: false, wedgeAngle: 26, wedgeHeight: Math.tan((26 * Math.PI) / 180) * this.xMax*0.6, wedgeWidth: this.xMax*0.6, weight: false, } } // Add one weight to the simulation addWeight () { this.setState({weight: true}) this.setState({wedge: false}) this.setState({pendulum: false}) this.setState({startPosY: this.yMin+this.radius}) this.setState({startPosX: (this.xMax+this.xMin-this.radius)/2}) this.setState({updatedForces: [this.forceOfGravity]}) this.setState({startForces: [this.forceOfGravity]}) this.addWalls(); this.setState({simulationReset: !this.state.simulationReset}) }; // Add a wedge with a One Weight to the simulation addWedge () { this.setState({weight: true}) this.setState({wedge: true}) this.setState({pendulum: false}) this.changeWedgeBasedOnNewAngle(26); this.addWalls(); this.setState({startForces: [this.forceOfGravity]}) this.updateForcesWithFriction(this.state.coefficientOfStaticFriction); }; // Add a simple pendulum to the simulation addPendulum = () => { this.setState({weight: true}) this.setState({wedge: false}) this.setState({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.setState({startPosX: xPos}) this.setState({startPosY: yPos}) let mag = 9.81 * Math.cos((angle * Math.PI) / 180); let forceOfTension: IForce = { description: "Tension", magnitude: mag, directionInDegrees: 90 - angle, }; this.setState({updatedForces: [this.forceOfGravity, forceOfTension]}) this.setState({startForces: [this.forceOfGravity, forceOfTension]}) this.setState({pendulumAngle: angle}) this.setState({pendulumLength: length}) this.setState({adjustPendulumAngle: {angle: angle, length: length}}) this.removeWalls(); }; // Update forces when coefficient of static friction changes in freeform mode updateForcesWithFriction ( coefficient: number, width: number = this.state.wedgeWidth, height: number = this.state.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.setState({startForces: [this.forceOfGravity, normalForce, frictionForce]}) this.setState({updatedForces: [this.forceOfGravity, normalForce, frictionForce]}); } else { this.setState({startForces: [this.forceOfGravity, normalForce]}) this.setState({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.setState({wedgeWidth: width}) this.setState({wedgeHeight: height}) } else if (angle < 70) { width = this.xMax*0.3; height = Math.tan((angle * Math.PI) / 180) * width; this.setState({wedgeWidth: width}) this.setState({wedgeHeight: height}) } else { width = this.xMax*0.15; height = Math.tan((angle * Math.PI) / 180) * width; this.setState({wedgeWidth: width}) this.setState({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.setState({startPosX: xPos}); this.setState({startPosY: this.getDisplayYPos(yPos)}); this.updateForcesWithFriction( Number(this.state.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.state.sketching) { let sketches = this.state.forceSketches.filter((sketch) => sketch != element); this.setState({forceSketches: sketches}) this.setState({currentForceSketch: element}) this.setState({sketching: true}) } }; // In review mode, used to delete force arrow sketch on SHIFT+click deleteForce = (element: PhysicsVectorTemplate) => { if (!this.state.sketching) { let sketches = this.state.forceSketches.filter((sketch) => sketch != element); this.setState({forceSketches: sketches}) } }; // Remove floor and walls from simulation removeWalls = () => { this.setState({wallPositions: []}) }; // Add floor and walls to simulation addWalls = () => { if (this.state.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.setState({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.setState({deleteMode: true}) } }); document.addEventListener("keyup", (e) => { if (e.shiftKey) { this.setState({deleteMode: false}) } }); // Timer for animating the simulation setInterval(() => { this.setState({timer: this.state.timer+1}) }, 60); } render () { return (
{ // 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); // } }} >
{/* {showForces && currentForceSketch && simulationPaused && (
)} */} {/* {showForces && forceSketches.length > 0 && simulationPaused && forceSketches.map((element: PhysicsVectorTemplate, index) => { return (
{ if (deleteMode) { deleteForce(element); } else { editForce(element); } }} />
); })} */} {this.state.weight && ( {this.setState({sketching: val})}} setDisplayXAcceleration={(val: number) => {this.setState({accelerationXDisplay: val})}} setDisplayXPosition={(val: number) => {this.setState({positionXDisplay: val})}} setDisplayXVelocity={(val: number) => {this.setState({velocityXDisplay: val})}} setDisplayYAcceleration={(val: number) => {this.setState({accelerationYDisplay: val})}} setDisplayYPosition={(val: number) => {this.setState({positionYDisplay: val})}} setDisplayYVelocity={(val: number) => {this.setState({velocityYDisplay: val})}} setPaused={(val: boolean) => {this.setState({simulationPaused: val})}} setPendulumAngle={(val: number) => {this.setState({pendulumAngle: val})}} setPendulumLength={(val: number) => {this.setState({pendulumLength: val})}} setStartPendulumAngle={(val: number) => {this.setState({startPendulumAngle: val})}} setUpdatedForces={(val: IForce[]) => {this.setState({updatedForces: val})}} showAcceleration={this.state.showAcceleration} showForces={this.state.showForces} showVelocity={this.state.showVelocity} startForces={this.state.startForces} startPosX={this.state.startPosX} startPosY={this.state.startPosY} timestepSize={0.002} updateDisplay={this.state.displayChange} updatedForces={this.state.updatedForces} walls={this.state.wallPositions} wedge={this.state.wedge} wedgeHeight={this.state.wedgeHeight} wedgeWidth={this.state.wedgeWidth} coefficientOfKineticFriction={this.state.coefficientOfKineticFriction} xMax={this.xMax} yMax={this.yMax} xMin={this.xMin} yMin={this.yMin} /> )} {this.state.wedge && ( )}
{this.state.wallPositions.map((element, index) => { return (
); })}
{this.state.simulationPaused && ( )} {!this.state.simulationPaused && ( )} {this.state.simulationPaused && ( )}
); } }