import React = require('react'); import "./PhysicsSimulationBox.scss"; import Weight, { IForce } from "./PhysicsSimulationWeight"; import Wall, { IWallProps } from "./PhysicsSimulationWall" import Wedge from "./PhysicsSimulationWedge" import { props, any } from 'bluebird'; import { render } from 'react-dom'; 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 = window.innerWidth * 0.7; yMax = window.innerHeight * 0.8; color = `rgba(0,0,0,0.5)`; radius = 50 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: false, 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) * 400, wedgeWidth: 400, 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 = 300; let angle = 50; 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 - 50; let yPos = y - 50 - 5; this.addPendulum(); this.setState({startPosX: xPos}) this.setState({startPosY: yPos}) let mag = 9.81 * Math.cos((50 * 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: 50}) this.setState({pendulumLength: 300}) this.setState({adjustPendulumAngle: {angle: 50, length: 300}}) 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 = 400; height = Math.tan((angle * Math.PI) / 180) * 400; this.setState({wedgeWidth: width}) this.setState({wedgeHeight: height}) } else if (angle < 70) { width = 200; height = Math.tan((angle * Math.PI) / 180) * 200; this.setState({wedgeWidth: width}) this.setState({wedgeHeight: height}) } else { width = 100; height = Math.tan((angle * Math.PI) / 180) * 100; this.setState({wedgeWidth: width}) this.setState({wedgeHeight: height}) } // update weight position based on updated wedge width/height let yPos = (width - 50) * Math.tan((angle * Math.PI) / 180); if (angle < 40) { yPos += Math.sqrt(angle); } else if (angle < 58) { yPos += angle / 2; } else if (angle < 68) { yPos += angle; } else if (angle < 70) { yPos += angle * 1.3; } else if (angle < 75) { yPos += angle * 1.5; } else if (angle < 78) { yPos += angle * 2; } else if (angle < 79) { yPos += angle * 2.25; } else if (angle < 80) { yPos += angle * 2.6; } else { yPos += angle * 3; } this.setState({startPosX: Math.round((this.xMax * 0.5 - 200) * 10) / 10}); 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: 70, xPos: 0, yPos: 80, angleInDegrees: 0 }); walls.push({ length: 80, xPos: 0, yPos: 0, angleInDegrees: 90 }); walls.push({ length: 80, xPos: 69.5, yPos: 0, angleInDegrees: 90 }); this.setState({wallPositions: walls}) } }; componentDidMount() { // 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 (