aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-05-22 15:32:15 -0400
committerbobzel <zzzman@gmail.com>2023-05-22 15:32:15 -0400
commitc43b5f144eef86bd07fbb447b7c8a7087f4d0cac (patch)
treedddeacafbf2d419daca6f82864f0600a5aeb9211 /src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
parentbed3309e1fda6597b2a8fea10ad82cd3a0402051 (diff)
start of physics cleanup
Diffstat (limited to 'src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx')
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx2840
1 files changed, 1259 insertions, 1581 deletions
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
index 88af37791..a2883e825 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
@@ -1,1674 +1,1352 @@
-import { Doc } from '../../../../fields/Doc';
+import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc';
import React = require('react');
-import "./PhysicsSimulationBox.scss";
+import './PhysicsSimulationBox.scss';
+import { NumCast } from '../../../../fields/Types';
interface IWallProps {
- length: number;
- xPos: number;
- yPos: number;
- angleInDegrees: number;
+ length: number;
+ xPos: number;
+ yPos: number;
+ angleInDegrees: number;
}
interface IForce {
- description: string;
- magnitude: number;
- directionInDegrees: number;
- component: boolean;
+ description: string;
+ magnitude: number;
+ directionInDegrees: number;
+ component: boolean;
}
export interface IWeightProps {
- dataDoc: Doc;
- layoutDoc: Doc;
- adjustPendulumAngle: { angle: number; length: number };
- circularMotionRadius: number;
- coefficientOfKineticFriction: number;
- color: string;
- componentForces: IForce[];
- displayXVelocity: number;
- displayYVelocity: number;
- elasticCollisions: boolean;
- gravity: number;
- mass: number;
- mode: string;
- noMovement: boolean;
- paused: boolean;
- pendulumAngle: number;
- pendulumLength: number;
- radius: number;
- reset: boolean;
- showAcceleration: boolean;
- showComponentForces: boolean;
- showForceMagnitudes: boolean;
- showForces: boolean;
- showVelocity: boolean;
- simulationSpeed: number;
- simulationType: string;
- springConstant: number;
- springRestLength: number;
- springStartLength: number;
- startForces: IForce[];
- startPendulumAngle: number;
- startPosX: number;
- startPosY: number;
- startVelX: number;
- startVelY: number;
- timestepSize: number;
- updateDisplay: { xDisplay: number; yDisplay: number };
- updatedForces: IForce[];
- wallPositions: IWallProps[];
- wedgeHeight: number;
- wedgeWidth: number;
- xMax: number;
- xMin: number;
- yMax: number;
- yMin: number;
+ dataDoc: Doc;
+ layoutDoc: Doc;
+ adjustPendulumAngle: number;
+ adjustPendulumLength: number;
+ circularMotionRadius: number;
+ coefficientOfKineticFriction: number;
+ color: string;
+ componentForces: IForce[];
+ displayXVelocity: number;
+ displayYVelocity: number;
+ elasticCollisions: boolean;
+ gravity: number;
+ mass: number;
+ mode: string;
+ noMovement: boolean;
+ paused: boolean;
+ pendulumAngle: number;
+ pendulumLength: number;
+ radius: number;
+ reset: boolean;
+ showAcceleration: boolean;
+ showComponentForces: boolean;
+ showForceMagnitudes: boolean;
+ showForces: boolean;
+ showVelocity: boolean;
+ simulationSpeed: number;
+ simulationType: string;
+ springConstant: number;
+ springRestLength: number;
+ springStartLength: number;
+ startForces: IForce[];
+ startPendulumAngle: number;
+ startPosX: number;
+ startPosY: number;
+ startVelX: number;
+ startVelY: number;
+ timestepSize: number;
+ updateXDisplay: number;
+ updateYDisplay: number;
+ updatedForces: IForce[];
+ wallPositions: IWallProps[];
+ wedgeHeight: number;
+ wedgeWidth: number;
+ xMax: number;
+ xMin: number;
+ yMax: number;
+ yMin: number;
}
interface IState {
- angleLabel: number,
- clickPositionX: number,
- clickPositionY: number,
- coordinates: string,
- dragging: boolean,
- kineticFriction: boolean,
- maxPosYConservation: number,
- timer: number,
- updatedStartPosX: any,
- updatedStartPosY: any,
- walls: IWallProps[],
- xPosition: any,
- xVelocity: number,
- yPosition: any,
- yVelocity: number,
- xAccel: number,
- yAccel: number,
+ angleLabel: number;
+ clickPositionX: number;
+ clickPositionY: number;
+ coordinates: string;
+ dragging: boolean;
+ kineticFriction: boolean;
+ maxPosYConservation: number;
+ timer: number;
+ updatedStartPosX: any;
+ updatedStartPosY: any;
+ walls: IWallProps[];
+ xPosition: any;
+ xVelocity: number;
+ yPosition: any;
+ yVelocity: number;
+ xAccel: number;
+ yAccel: number;
}
export default class Weight extends React.Component<IWeightProps, IState> {
-
- constructor(props: any) {
- super(props)
- this.state = {
- angleLabel: 0,
- clickPositionX: 0,
- clickPositionY: 0,
- coordinates: "",
- dragging: false,
- kineticFriction: false,
- maxPosYConservation: 0,
- timer: 0,
- updatedStartPosX: this.props.startPosX ?? 0,
- updatedStartPosY: this.props.startPosY ?? 0,
- walls: [],
- xPosition: this.props.startPosX ?? 0,
- xVelocity: this.props.startVelX ? this.props.startVelX: 0,
- yPosition: this.props.startPosY ?? 0,
- yVelocity: this.props.startVelY ? this.props.startVelY: 0,
- xAccel: 0,
- yAccel: 0,
+ constructor(props: any) {
+ super(props);
+ this.state = {
+ angleLabel: 0,
+ clickPositionX: 0,
+ clickPositionY: 0,
+ coordinates: '',
+ dragging: false,
+ kineticFriction: false,
+ maxPosYConservation: 0,
+ timer: 0,
+ updatedStartPosX: this.props.startPosX ?? 0,
+ updatedStartPosY: this.props.startPosY ?? 0,
+ walls: [],
+ xPosition: this.props.startPosX ?? 0,
+ xVelocity: this.props.startVelX ? this.props.startVelX : 0,
+ yPosition: this.props.startPosY ?? 0,
+ yVelocity: this.props.startVelY ? this.props.startVelY : 0,
+ xAccel: 0,
+ yAccel: 0,
+ };
}
- }
-
- componentDidMount() {
- // Timer for animating the simulation
- setInterval(() => {
- this.setState({timer: this.state.timer + 1});
- }, 50);
- }
-
- // Constants
- draggable =
- this.props.dataDoc['simulationType'] != "Inclined Plane" &&
- this.props.dataDoc['simulationType'] != "Pendulum" &&
- this.props.dataDoc['mode'] == "Freeform";
- epsilon = 0.0001;
- labelBackgroundColor = `rgba(255,255,255,0.5)`;
-
- // Variables
- weightStyle = {
- alignItems: "center",
- backgroundColor: this.props.color,
- borderColor: "black",
- borderRadius: 50 + "%",
- borderStyle: "solid",
- display: "flex",
- height: 2 * this.props.radius + "px",
- justifyContent: "center",
- left: this.props.startPosX + "px",
- position: "absolute" as "absolute",
- top: this.props.startPosY + "px",
- touchAction: "none",
- width: 2 * this.props.radius + "px",
- zIndex: 5,
- };
-
- // Helper function to go between display and real values
- getDisplayYPos = (yPos: number) => {
- return this.props.yMax - yPos - 2 * this.props.radius + 5;
- };
- getYPosFromDisplay = (yDisplay: number) => {
- return this.props.yMax - yDisplay - 2 * this.props.radius + 5;
- };
- // Set display values based on real values
- setYPosDisplay = (yPos: number) => {
- const displayPos = this.getDisplayYPos(yPos);
- if (this.props.color == 'red') {
- this.props.dataDoc['positionYDisplay'] = Math.round(displayPos * 100) / 100
- } else {
- this.props.dataDoc['positionYDisplay2'] = Math.round(displayPos * 100) / 100
+ componentDidMount() {
+ // Timer for animating the simulation
+ setInterval(() => {
+ this.setState({ timer: this.state.timer + 1 });
+ }, 50);
}
- };
- setXPosDisplay = (xPos: number) => {
- if (this.props.color == 'red') {
- this.props.dataDoc['positionXDisplay'] = Math.round(xPos * 100) / 100;
- } else {
- this.props.dataDoc['positionXDisplay2'] = Math.round(xPos * 100) / 100;}
- };
- setYVelDisplay = (yVel: number) => {
- if (this.props.color == 'red') {
- this.props.dataDoc['velocityYDisplay'] = (-1 * Math.round(yVel * 100)) / 100;
- } else {
- this.props.dataDoc['velocityYDisplay2'] = (-1 * Math.round(yVel * 100)) / 100;}
- };
- setXVelDisplay = (xVel: number) => {
- if (this.props.color == 'red') {
- this.props.dataDoc['velocityXDisplay'] = Math.round(xVel * 100) / 100;
- } else {
- this.props.dataDoc['velocityXDisplay2'] = Math.round(xVel * 100) / 100;}
- };
- // Update display values when simulation updates
- setDisplayValues = (
- xPos: number = this.state.xPosition,
- yPos: number = this.state.yPosition,
- xVel: number = this.state.xVelocity,
- yVel: number = this.state.yVelocity
- ) => {
- this.setYPosDisplay(yPos);
- this.setXPosDisplay(xPos);
- this.setYVelDisplay(yVel);
- this.setXVelDisplay(xVel);
- if (this.props.color == 'red') {
- this.props.dataDoc['accelerationYDisplay'] =
- (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100
- ;
- this.props.dataDoc['accelerationXDisplay'] =
- Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100
- ;
- } else {
- this.props.dataDoc['accelerationYDisplay2'] =
- (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100
- ;
- this.props.dataDoc['accelerationXDisplay2'] =
- Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100
- ;}
+ // Constants
+ draggable = this.props.dataDoc.simulation_Type != 'Inclined Plane' && this.props.dataDoc.simulation_Type != 'Pendulum' && this.props.dataDoc.mode == 'Freeform';
+ epsilon = 0.0001;
+ labelBackgroundColor = `rgba(255,255,255,0.5)`;
- this.setState({xAccel : (Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100)})
- this.setState({yAccel :
- (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100
- });
- };
+ // Variables
+ weightStyle = {
+ alignItems: 'center',
+ backgroundColor: this.props.color,
+ borderColor: 'black',
+ borderRadius: 50 + '%',
+ borderStyle: 'solid',
+ display: 'flex',
+ height: 2 * this.props.radius + 'px',
+ justifyContent: 'center',
+ left: this.props.startPosX + 'px',
+ position: 'absolute' as 'absolute',
+ top: this.props.startPosY + 'px',
+ touchAction: 'none',
+ width: 2 * this.props.radius + 'px',
+ zIndex: 5,
+ };
- componentDidUpdate(prevProps: Readonly<IWeightProps>, prevState: Readonly<IState>, snapshot?: any): void {
- // Change pendulum angle from input field
- if (prevProps.adjustPendulumAngle != this.props.adjustPendulumAngle) {
- let length = this.props.adjustPendulumAngle.length;
- const x =
- length * Math.cos(((90 - this.props.adjustPendulumAngle.angle) * Math.PI) / 180);
- const y =
- length * Math.sin(((90 - this.props.adjustPendulumAngle.angle) * Math.PI) / 180);
- const xPos = this.props.xMax / 2 - x - this.props.radius;
- const yPos = y - this.props.radius - 5;
- this.setState({xPosition: xPos})
- this.setState({yPosition: yPos})
- this.setState({updatedStartPosX: xPos})
- this.setState({updatedStartPosY: yPos})
- this.props.dataDoc['pendulumAngle'] = this.props.adjustPendulumAngle.angle
- this.props.dataDoc['pendulumLength'] = this.props.adjustPendulumAngle.length
- }
+ // Helper function to go between display and real values
+ getDisplayYPos = (yPos: number) => {
+ return this.props.yMax - yPos - 2 * this.props.radius + 5;
+ };
+ getYPosFromDisplay = (yDisplay: number) => {
+ return this.props.yMax - yDisplay - 2 * this.props.radius + 5;
+ };
- // When display values updated by user, update real values
- if (prevProps.updateDisplay != this.props.updateDisplay) {
- if (this.props.updateDisplay.xDisplay != this.state.xPosition) {
- let x = this.props.updateDisplay.xDisplay;
- x = Math.max(0, x);
- x = Math.min(x, this.props.xMax - 2 * this.props.radius);
- this.setState({updatedStartPosX: x})
- this.setState({xPosition: x})
+ // Set display values based on real values
+ setYPosDisplay = (yPos: number) => {
+ const displayPos = this.getDisplayYPos(yPos);
if (this.props.color == 'red') {
- this.props.dataDoc['positionXDisplay'] = x
+ this.props.dataDoc.position_YDisplay = Math.round(displayPos * 100) / 100;
} else {
- this.props.dataDoc['positionXDisplay2'] = x
+ this.props.dataDoc.position_YDisplay2 = Math.round(displayPos * 100) / 100;
}
- }
-
- if (this.props.updateDisplay.yDisplay != this.getDisplayYPos(this.state.yPosition)) {
- let y = this.props.updateDisplay.yDisplay;
- y = Math.max(0, y);
- y = Math.min(y, this.props.yMax - 2 * this.props.radius);
- let coordinatePosition = this.getYPosFromDisplay(y);
- this.setState({updatedStartPosY: coordinatePosition})
- this.setState({yPosition: coordinatePosition})
+ };
+ setXPosDisplay = (xPos: number) => {
if (this.props.color == 'red') {
- this.props.dataDoc['positionYDisplay'] = y
+ this.props.dataDoc.position_XDisplay = Math.round(xPos * 100) / 100;
} else {
- this.props.dataDoc['positionYDisplay2'] = y
+ this.props.dataDoc.position_XDisplay2 = Math.round(xPos * 100) / 100;
}
- }
-
- if (this.props.displayXVelocity != this.state.xVelocity) {
- let x = this.props.displayXVelocity;
- this.setState({xVelocity: x})
+ };
+ setYVelDisplay = (yVel: number) => {
if (this.props.color == 'red') {
- this.props.dataDoc['velocityXDisplay'] = x
+ this.props.dataDoc.velocity_YDisplay = (-1 * Math.round(yVel * 100)) / 100;
} else {
- this.props.dataDoc['velocityXDisplay2'] = x
+ this.props.dataDoc.velocity_YDisplay2 = (-1 * Math.round(yVel * 100)) / 100;
}
- }
-
- if (this.props.displayYVelocity != -this.state.yVelocity) {
- let y = this.props.displayYVelocity;
- this.setState({yVelocity: -y})
+ };
+ setXVelDisplay = (xVel: number) => {
if (this.props.color == 'red') {
- this.props.dataDoc['velocityYDisplay'] = y
+ this.props.dataDoc.velocity_XDisplay = Math.round(xVel * 100) / 100;
} else {
- this.props.dataDoc['velocityYDisplay2'] = y
+ this.props.dataDoc.velocity_XDisplay2 = Math.round(xVel * 100) / 100;
}
- }
- }
-
- // Prevent bug when switching between sims
- if (prevProps.startForces != this.props.startForces) {
- this.setState({xVelocity: this.props.startVelX})
- this.setState({yVelocity: this.props.startVelY})
- this.setDisplayValues();
- }
+ };
- // Make sure weight doesn't go above max height
- if ((prevState.updatedStartPosY != this.state.updatedStartPosY || prevProps.startVelY != this.props.startVelY) && !isNaN(this.state.updatedStartPosY) && !isNaN(this.props.startVelY)){
- if (this.props.dataDoc['simulationType'] == "One Weight") {
- let maxYPos = this.state.updatedStartPosY;
- if (this.props.startVelY != 0) {
- maxYPos -= (this.props.startVelY * this.props.startVelY) / (2 * Math.abs(this.props.gravity));
+ // Update display values when simulation updates
+ setDisplayValues = (xPos: number = this.state.xPosition, yPos: number = this.state.yPosition, xVel: number = this.state.xVelocity, yVel: number = this.state.yVelocity) => {
+ this.setYPosDisplay(yPos);
+ this.setXPosDisplay(xPos);
+ this.setYVelDisplay(yVel);
+ this.setXVelDisplay(xVel);
+ if (this.props.color == 'red') {
+ this.props.dataDoc.acceleration_YDisplay = (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100;
+ this.props.dataDoc.acceleration_XDisplay = Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100;
+ } else {
+ this.props.dataDoc.acceleration_YDisplay2 = (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100;
+ this.props.dataDoc.acceleration_XDisplay2 = Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100;
}
- if (maxYPos < 0) {
- maxYPos = 0;
+
+ this.setState({ xAccel: Math.round(this.getNewAccelerationX(this.props.updatedForces) * 100) / 100 });
+ this.setState({ yAccel: (-1 * Math.round(this.getNewAccelerationY(this.props.updatedForces) * 100)) / 100 });
+ };
+
+ componentDidUpdate(prevProps: Readonly<IWeightProps>, prevState: Readonly<IState>, snapshot?: any): void {
+ // Change pendulum angle from input field
+ if (prevProps.adjustPendulumAngle != this.props.adjustPendulumAngle || prevProps.adjustPendulumLength !== this.props.adjustPendulumLength) {
+ let length = this.props.adjustPendulumLength;
+ const x = length * Math.cos(((90 - this.props.adjustPendulumAngle) * Math.PI) / 180);
+ const y = length * Math.sin(((90 - this.props.adjustPendulumAngle) * Math.PI) / 180);
+ const xPos = this.props.xMax / 2 - x - this.props.radius;
+ const yPos = y - this.props.radius - 5;
+ this.setState({ xPosition: xPos });
+ this.setState({ yPosition: yPos });
+ this.setState({ updatedStartPosX: xPos });
+ this.setState({ updatedStartPosY: yPos });
+ this.props.dataDoc.pendulum_angle = this.props.adjustPendulumAngle;
+ this.props.dataDoc.pendulum_length = this.props.adjustPendulumLength;
}
- this.setState({maxPosYConservation: maxYPos})
- }
- }
- // Check for collisions and update
- if (!this.props.paused) {
- if (prevState.timer != this.state.timer) {
- if (!this.props.noMovement) {
- let collisions = false;
- if (
- this.props.dataDoc['simulationType'] == "One Weight" ||
- this.props.dataDoc['simulationType'] == "Inclined Plane"
- ) {
- const collisionsWithGround = this.checkForCollisionsWithGround();
- const collisionsWithWalls = this.checkForCollisionsWithWall();
- collisions = collisionsWithGround || collisionsWithWalls;
- }
- if (this.props.dataDoc['simulationType'] == "Pulley") {
- if (this.state.yPosition <= this.props.yMin + 100 || this.state.yPosition >= this.props.yMax - 100) {
- collisions = true;
+ // When display values updated by user, update real value
+ if (prevProps.updateYDisplay != this.props.updateYDisplay || prevProps.updateXDisplay !== this.props.updateXDisplay) {
+ if (this.props.updateXDisplay != this.state.xPosition) {
+ let x = this.props.updateXDisplay;
+ x = Math.max(0, x);
+ x = Math.min(x, this.props.xMax - 2 * this.props.radius);
+ this.setState({ updatedStartPosX: x });
+ this.setState({ xPosition: x });
+ if (this.props.color == 'red') {
+ this.props.dataDoc.position_XDisplay = x;
+ } else {
+ this.props.dataDoc.position_XDisplay2 = x;
+ }
+ }
+
+ if (this.props.updateYDisplay != this.getDisplayYPos(this.state.yPosition)) {
+ let y = this.props.updateYDisplay;
+ y = Math.max(0, y);
+ y = Math.min(y, this.props.yMax - 2 * this.props.radius);
+ let coordinatePosition = this.getYPosFromDisplay(y);
+ this.setState({ updatedStartPosY: coordinatePosition });
+ this.setState({ yPosition: coordinatePosition });
+ if (this.props.color == 'red') {
+ this.props.dataDoc.position_YDisplay = y;
+ } else {
+ this.props.dataDoc.position_YDisplay2 = y;
+ }
+ }
+
+ if (this.props.displayXVelocity != this.state.xVelocity) {
+ let x = this.props.displayXVelocity;
+ this.setState({ xVelocity: x });
+ if (this.props.color == 'red') {
+ this.props.dataDoc.velocity_XDisplay = x;
+ } else {
+ this.props.dataDoc.velocity_XDisplay2 = x;
+ }
+ }
+
+ if (this.props.displayYVelocity != -this.state.yVelocity) {
+ let y = this.props.displayYVelocity;
+ this.setState({ yVelocity: -y });
+ if (this.props.color == 'red') {
+ this.props.dataDoc.velocity_YDisplay = y;
+ } else {
+ this.props.dataDoc.velocity_YDisplay2 = y;
+ }
}
- }
- if (!collisions) {
- this.update();
- }
- this.setDisplayValues();
}
- }
- }
- // Reset everything on reset button click
- if (prevProps.reset != this.props.reset) {
- this.resetEverything();
- }
-
- // Convert from static to kinetic friction if/when weight slips on inclined plane
- if (prevState.xVelocity != this.state.xVelocity) {
- if (
- this.props.dataDoc['simulationType'] == "Inclined Plane" &&
- Math.abs(this.state.xVelocity) > 0.1 &&
- this.props.dataDoc['mode'] != "Review" &&
- !this.state.kineticFriction
- ) {
- this.setState({kineticFriction: true})
- const normalForce: IForce = {
- description: "Normal Force",
- magnitude:
- this.props.mass *
- Math.abs(this.props.gravity) *
- Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
- directionInDegrees:
- 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
- component: false,
- };
- let frictionForce: IForce = {
- description: "Kinetic Friction Force",
- magnitude:
- this.props.mass *
- this.props.coefficientOfKineticFriction *
- Math.abs(this.props.gravity) *
- Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
- directionInDegrees:
- 180 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
- component: false,
- };
- // reduce magnitude of friction force if necessary such that block cannot slide up plane
- let yForce = -Math.abs(this.props.gravity);
- 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) +
- Math.abs(this.props.gravity)) /
- Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
+ // Prevent bug when switching between sims
+ if (prevProps.startForces != this.props.startForces) {
+ this.setState({ xVelocity: this.props.startVelX });
+ this.setState({ yVelocity: this.props.startVelY });
+ this.setDisplayValues();
}
- const frictionForceComponent: IForce = {
- description: "Kinetic Friction Force",
+ // Make sure weight doesn't go above max height
+ if ((prevState.updatedStartPosY != this.state.updatedStartPosY || prevProps.startVelY != this.props.startVelY) && !isNaN(this.state.updatedStartPosY) && !isNaN(this.props.startVelY)) {
+ if (this.props.dataDoc.simulation_Type == 'One Weight') {
+ let maxYPos = this.state.updatedStartPosY;
+ if (this.props.startVelY != 0) {
+ maxYPos -= (this.props.startVelY * this.props.startVelY) / (2 * Math.abs(this.props.gravity));
+ }
+ if (maxYPos < 0) {
+ maxYPos = 0;
+ }
+ this.setState({ maxPosYConservation: maxYPos });
+ }
+ }
- magnitude:
- this.props.mass *
- this.props.coefficientOfKineticFriction *
- Math.abs(this.props.gravity) *
- Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
- directionInDegrees:
- 180 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
- component: true,
- };
- const normalForceComponent: IForce = {
- description: "Normal Force",
- magnitude:
- this.props.mass *
- Math.abs(this.props.gravity) *
- Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
- directionInDegrees:
- 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
- component: true,
- };
- const gravityParallel: IForce = {
- description: "Gravity Parallel Component",
- magnitude:
- this.props.mass *
- Math.abs(this.props.gravity) *
- Math.sin(Math.PI / 2 - Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
- directionInDegrees:
- 180 -
- 90 -
- (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI +
- 180,
- component: true,
- };
- const gravityPerpendicular: IForce = {
- description: "Gravity Perpendicular Component",
- magnitude:
- this.props.mass *
- Math.abs(this.props.gravity) *
- Math.cos(Math.PI / 2 - Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
- directionInDegrees:
- 360 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
- component: true,
- };
- const gravityForce: IForce = {
- description: "Gravity",
- magnitude: this.props.mass * Math.abs(this.props.gravity),
- directionInDegrees: 270,
- component: false,
- };
- if (this.props.coefficientOfKineticFriction != 0) {
- this.props.dataDoc['updatedForces'] = ([gravityForce, normalForce, frictionForce]);
- this.props.dataDoc['componentForces'] = ([
- frictionForceComponent,
- normalForceComponent,
- gravityParallel,
- gravityPerpendicular,
- ]);
- } else {
- this.props.dataDoc['updatedForces'] = ([gravityForce, normalForce]);
- this.props.dataDoc['componentForces'] = ([
- normalForceComponent,
- gravityParallel,
- gravityPerpendicular,
- ]);
+ // Check for collisions and update
+ if (!this.props.paused) {
+ if (prevState.timer != this.state.timer) {
+ if (!this.props.noMovement) {
+ let collisions = false;
+ if (this.props.dataDoc.simulation_Type == 'One Weight' || this.props.dataDoc.simulation_Type == 'Inclined Plane') {
+ const collisionsWithGround = this.checkForCollisionsWithGround();
+ const collisionsWithWalls = this.checkForCollisionsWithWall();
+ collisions = collisionsWithGround || collisionsWithWalls;
+ }
+ if (this.props.dataDoc.simulation_Type == 'Pulley') {
+ if (this.state.yPosition <= this.props.yMin + 100 || this.state.yPosition >= this.props.yMax - 100) {
+ collisions = true;
+ }
+ }
+ if (!collisions) {
+ this.update();
+ }
+ this.setDisplayValues();
+ }
+ }
}
- }
- }
- // Add/remove walls when simulation type changes
- if (prevProps.simulationType != this.props.simulationType) {
- let w: IWallProps[] = [];
- if (this.props.dataDoc['simulationType'] == "One Weight" || this.props.dataDoc['simulationType'] == "Inclined Plane") {
- w = this.props.wallPositions
- }
- this.setState({walls: w})
- }
+ // Reset everything on reset button click
+ if (prevProps.reset != this.props.reset) {
+ this.resetEverything();
+ }
- // Update x position when start pos x changes
- if (prevProps.startPosX != this.props.startPosX) {
- if (this.props.paused && !isNaN(this.props.startPosX)) {
- this.setState({xPosition: this.props.startPosX})
- this.setState({updatedStartPosX: this.props.startPosX})
- this.setXPosDisplay(this.props.startPosX)
- }
- }
+ // Convert from static to kinetic friction if/when weight slips on inclined plane
+ if (prevState.xVelocity != this.state.xVelocity) {
+ if (this.props.dataDoc.simulation_Type == 'Inclined Plane' && Math.abs(this.state.xVelocity) > 0.1 && this.props.dataDoc.mode != 'Review' && !this.state.kineticFriction) {
+ this.setState({ kineticFriction: true });
+ const normalForce: IForce = {
+ description: 'Normal Force',
+ magnitude: this.props.mass * Math.abs(this.props.gravity) * Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
+ directionInDegrees: 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
+ component: false,
+ };
+ let frictionForce: IForce = {
+ description: 'Kinetic Friction Force',
+ magnitude: this.props.mass * this.props.coefficientOfKineticFriction * Math.abs(this.props.gravity) * Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
+ directionInDegrees: 180 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
+ component: false,
+ };
+ // reduce magnitude of friction force if necessary such that block cannot slide up plane
+ let yForce = -Math.abs(this.props.gravity);
+ 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) + Math.abs(this.props.gravity)) / Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
+ }
- // Update y position when start pos y changes TODO debug
- if (prevProps.startPosY != this.props.startPosY) {
- if (this.props.paused && !isNaN(this.props.startPosY)) {
- this.setState({yPosition: this.props.startPosY})
- this.setState({updatedStartPosY: this.props.startPosY ?? 0})
- this.setYPosDisplay(this.props.startPosY ?? 0)
- }
- }
+ const frictionForceComponent: IForce = {
+ description: 'Kinetic Friction Force',
- // Update wedge coordinates
- if (prevProps.wedgeWidth != this.props.wedgeWidth || prevProps.wedgeHeight != this.props.wedgeHeight) {
- const left = this.props.xMax * 0.25;
- const coordinatePair1 = Math.round(left) + "," + this.props.yMax + " ";
- const coordinatePair2 = Math.round(left + this.props.wedgeWidth) + "," + this.props.yMax + " ";
- const coordinatePair3 = Math.round(left) + "," + (this.props.yMax - this.props.wedgeHeight);
- const coord = coordinatePair1 + coordinatePair2 + coordinatePair3;
- this.setState({coordinates: coord})
- }
+ magnitude: this.props.mass * this.props.coefficientOfKineticFriction * Math.abs(this.props.gravity) * Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
+ directionInDegrees: 180 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
+ component: true,
+ };
+ const normalForceComponent: IForce = {
+ description: 'Normal Force',
+ magnitude: this.props.mass * Math.abs(this.props.gravity) * Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
+ directionInDegrees: 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
+ component: true,
+ };
+ const gravityParallel: IForce = {
+ description: 'Gravity Parallel Component',
+ magnitude: this.props.mass * Math.abs(this.props.gravity) * Math.sin(Math.PI / 2 - Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
+ directionInDegrees: 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI + 180,
+ component: true,
+ };
+ const gravityPerpendicular: IForce = {
+ description: 'Gravity Perpendicular Component',
+ magnitude: this.props.mass * Math.abs(this.props.gravity) * Math.cos(Math.PI / 2 - Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
+ directionInDegrees: 360 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
+ component: true,
+ };
+ const gravityForce: IForce = {
+ description: 'Gravity',
+ magnitude: this.props.mass * Math.abs(this.props.gravity),
+ directionInDegrees: 270,
+ component: false,
+ };
+ if (this.props.coefficientOfKineticFriction != 0) {
+ this.props.dataDoc.updatedForces = [gravityForce, normalForce, frictionForce];
+ this.props.dataDoc.componentForces = [frictionForceComponent, normalForceComponent, gravityParallel, gravityPerpendicular];
+ } else {
+ this.props.dataDoc.updatedForces = [gravityForce, normalForce];
+ this.props.dataDoc.componentForces = [normalForceComponent, gravityParallel, gravityPerpendicular];
+ }
+ }
+ }
- if (this.state.xPosition != prevState.xPosition || this.state.yPosition != prevState.yPosition) {
- this.weightStyle = {
- alignItems: "center",
- backgroundColor: this.props.color,
- borderColor: "black",
- borderRadius: 50 + "%",
- borderStyle: "solid",
- display: "flex",
- height: 2 * this.props.radius + "px",
- justifyContent: "center",
- left: this.state.xPosition + "px",
- position: "absolute" as "absolute",
- top: this.state.yPosition + "px",
- touchAction: "none",
- width: 2 * this.props.radius + "px",
- zIndex: 5,
- };
- }
- }
+ // Add/remove walls when simulation type changes
+ if (prevProps.simulationType != this.props.simulationType) {
+ let w: IWallProps[] = [];
+ if (this.props.dataDoc.simulation_Type == 'One Weight' || this.props.dataDoc.simulation_Type == 'Inclined Plane') {
+ w = this.props.wallPositions;
+ }
+ this.setState({ walls: w });
+ }
- // Reset simulation on reset button click
- resetEverything = () => {
- this.setState({kineticFriction: false})
- this.setState({xPosition: this.state.updatedStartPosX})
- this.setState({yPosition: this.state.updatedStartPosY})
- this.setState({xVelocity: this.props.startVelX ?? 0})
- this.setState({yVelocity: this.props.startVelY ?? 0})
- this.props.dataDoc['pendulumAngle'] = this.props.dataDoc['startPendulumAngle']
- this.props.dataDoc['updatedForces'] = (this.props.dataDoc['startForces'])
- this.props.dataDoc['updatedForces2'] = (this.props.dataDoc['startForces2'])
- if (this.props.color == 'red') {
- this.props.dataDoc['positionXDisplay'] = this.state.updatedStartPosX
- this.props.dataDoc['positionYDisplay'] = this.state.updatedStartPosY
- this.props.dataDoc['velocityXDisplay'] = this.props.startVelX ?? 0
- this.props.dataDoc['velocityYDisplay'] = this.props.startVelY ?? 0
- this.props.dataDoc['accelerationXDisplay'] = 0
- this.props.dataDoc['accelerationYDisplay'] = 0
- } else {
- this.props.dataDoc['positionXDisplay2'] = this.state.updatedStartPosX
- this.props.dataDoc['positionYDisplay2'] = this.state.updatedStartPosY
- this.props.dataDoc['velocityXDisplay2'] = this.props.startVelX ?? 0
- this.props.dataDoc['velocityYDisplay2'] = this.props.startVelY ?? 0
- this.props.dataDoc['accelerationXDisplay2'] = 0
- this.props.dataDoc['accelerationYDisplay2'] = 0
- }
- this.setState({angleLabel: Math.round(this.props.dataDoc['pendulumAngle'] ?? 0 * 100) / 100})
- };
+ // Update x position when start pos x changes
+ if (prevProps.startPosX != this.props.startPosX) {
+ if (this.props.paused && !isNaN(this.props.startPosX)) {
+ this.setState({ xPosition: this.props.startPosX });
+ this.setState({ updatedStartPosX: this.props.startPosX });
+ this.setXPosDisplay(this.props.startPosX);
+ }
+ }
- // Compute x acceleration from forces, F=ma
- getNewAccelerationX = (forceList: IForce[]) => {
- let newXAcc = 0;
- if (forceList) {
- forceList.forEach((force) => {
- newXAcc +=
- (force.magnitude *
- Math.cos((force.directionInDegrees * Math.PI) / 180)) /
- this.props.mass;
- });
- }
- return newXAcc;
- };
+ // Update y position when start pos y changes TODO debug
+ if (prevProps.startPosY != this.props.startPosY) {
+ if (this.props.paused && !isNaN(this.props.startPosY)) {
+ this.setState({ yPosition: this.props.startPosY });
+ this.setState({ updatedStartPosY: this.props.startPosY ?? 0 });
+ this.setYPosDisplay(this.props.startPosY ?? 0);
+ }
+ }
+ // Update wedge coordinates
+ if (this.state.coordinates === '' || prevProps.wedgeWidth != this.props.wedgeWidth || prevProps.wedgeHeight != this.props.wedgeHeight) {
+ const left = this.props.xMax * 0.25;
+ const coordinatePair1 = Math.round(left) + ',' + this.props.yMax + ' ';
+ const coordinatePair2 = Math.round(left + this.props.wedgeWidth) + ',' + this.props.yMax + ' ';
+ const coordinatePair3 = Math.round(left) + ',' + (this.props.yMax - this.props.wedgeHeight);
+ const coord = coordinatePair1 + coordinatePair2 + coordinatePair3;
+ this.setState({ coordinates: coord });
+ }
- // Compute y acceleration from forces, F=ma
- getNewAccelerationY = (forceList: IForce[]) => {
- let newYAcc = 0;
- if (forceList) {
- forceList.forEach((force) => {
- newYAcc +=
- (-1 *
- (force.magnitude *
- Math.sin((force.directionInDegrees * Math.PI) / 180))) /
- this.props.mass;
- });
+ if (this.state.xPosition != prevState.xPosition || this.state.yPosition != prevState.yPosition) {
+ this.weightStyle = {
+ alignItems: 'center',
+ backgroundColor: this.props.color,
+ borderColor: 'black',
+ borderRadius: 50 + '%',
+ borderStyle: 'solid',
+ display: 'flex',
+ height: 2 * this.props.radius + 'px',
+ justifyContent: 'center',
+ left: this.state.xPosition + 'px',
+ position: 'absolute' as 'absolute',
+ top: this.state.yPosition + 'px',
+ touchAction: 'none',
+ width: 2 * this.props.radius + 'px',
+ zIndex: 5,
+ };
+ }
}
- return newYAcc;
- };
- // Compute uniform circular motion forces given x, y positions
- getNewCircularMotionForces = (xPos: number, yPos: number) => {
- let deltaX = (this.props.xMin + this.props.xMax) / 2 - (xPos + this.props.radius);
- let deltaY = yPos + this.props.radius - (this.props.yMin + this.props.yMax) / 2;
- let dir = (Math.atan2(deltaY, deltaX) * 180) / Math.PI;
- const tensionForce: IForce = {
- description: "Centripetal Force",
- magnitude: (this.props.startVelX ** 2 * this.props.mass) / this.props.circularMotionRadius,
- directionInDegrees: dir,
- component: false,
+ // Reset simulation on reset button click
+ resetEverything = () => {
+ this.setState({ kineticFriction: false });
+ this.setState({ xPosition: this.state.updatedStartPosX });
+ this.setState({ yPosition: this.state.updatedStartPosY });
+ this.setState({ xVelocity: this.props.startVelX ?? 0 });
+ this.setState({ yVelocity: this.props.startVelY ?? 0 });
+ this.props.dataDoc.pendulum_angle = this.props.dataDoc.start_PendulumAngle;
+ this.props.dataDoc.updatedForces = this.props.dataDoc.start_Forces;
+ this.props.dataDoc.updatedForces2 = this.props.dataDoc.start_Forces2;
+ if (this.props.color == 'red') {
+ this.props.dataDoc.position_XDisplay = this.state.updatedStartPosX;
+ this.props.dataDoc.position_YDisplay = this.state.updatedStartPosY;
+ this.props.dataDoc.velocity_XDisplay = this.props.startVelX ?? 0;
+ this.props.dataDoc.velocity_YDisplay = this.props.startVelY ?? 0;
+ this.props.dataDoc.acceleration_XDisplay = 0;
+ this.props.dataDoc.acceleration_YDisplay = 0;
+ } else {
+ this.props.dataDoc.position_XDisplay2 = this.state.updatedStartPosX;
+ this.props.dataDoc.position_YDisplay2 = this.state.updatedStartPosY;
+ this.props.dataDoc.velocity_XDisplay2 = this.props.startVelX ?? 0;
+ this.props.dataDoc.velocity_YDisplay2 = this.props.startVelY ?? 0;
+ this.props.dataDoc.acceleration_XDisplay2 = 0;
+ this.props.dataDoc.acceleration_YDisplay2 = 0;
+ }
+ this.setState({ angleLabel: Math.round(NumCast(this.props.dataDoc.pendulum_angle) ?? 0 * 100) / 100 });
};
- return [tensionForce];
- };
- // Compute spring forces given y position
- getNewSpringForces = (yPos: number) => {
- let springForce: IForce = {
- description: "Spring Force",
- magnitude: 0,
- directionInDegrees: 90,
- component: false,
+ // Compute x acceleration from forces, F=ma
+ getNewAccelerationX = (forceList: IForce[]) => {
+ let newXAcc = 0;
+ if (forceList) {
+ forceList.forEach(force => {
+ newXAcc += (force.magnitude * Math.cos((force.directionInDegrees * Math.PI) / 180)) / this.props.mass;
+ });
+ }
+ return newXAcc;
};
- if (yPos - this.props.springRestLength > 0) {
- springForce = {
- description: "Spring Force",
- magnitude: this.props.springConstant * (yPos - this.props.springRestLength),
- directionInDegrees: 90,
- component: false,
- };
- } else if (yPos - this.props.springRestLength < 0) {
- springForce = {
- description: "Spring Force",
- magnitude: this.props.springConstant * (this.props.springRestLength - yPos),
- directionInDegrees: 270,
- component: false,
- };
- }
- return [
- {
- description: "Gravity",
- magnitude: Math.abs(this.props.gravity) * this.props.mass,
- directionInDegrees: 270,
- component: false,
- },
- springForce,
- ];
- };
-
- // Compute pendulum forces given position, velocity
- getNewPendulumForces = (
- xPos: number,
- yPos: number,
- xVel: number,
- yVel: number
- ) => {
- const x = this.props.xMax / 2 - xPos - this.props.radius;
- const y = yPos + this.props.radius + 5;
- let angle = (Math.atan(y / x) * 180) / Math.PI;
- if (angle < 0) {
- angle += 180;
- }
- let oppositeAngle = 90 - angle;
- if (oppositeAngle < 0) {
- oppositeAngle = 90 - (180 - angle);
- }
-
- const pendulumLength = Math.sqrt(x * x + y * y);
- this.props.dataDoc['pendulumAngle'] = oppositeAngle;
- const mag =
- this.props.mass * Math.abs(this.props.gravity) * Math.cos((oppositeAngle * Math.PI) / 180) +
- (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength;
-
- const forceOfTension: IForce = {
- description: "Tension",
- magnitude: mag,
- directionInDegrees: angle,
- component: false,
+ // Compute y acceleration from forces, F=ma
+ getNewAccelerationY = (forceList: IForce[]) => {
+ let newYAcc = 0;
+ if (forceList) {
+ forceList.forEach(force => {
+ newYAcc += (-1 * (force.magnitude * Math.sin((force.directionInDegrees * Math.PI) / 180))) / this.props.mass;
+ });
+ }
+ return newYAcc;
};
- return [
- {
- description: "Gravity",
- magnitude: Math.abs(this.props.gravity) * this.props.mass,
- directionInDegrees: 270,
- component: false,
- },
- forceOfTension,
- ];
- };
+ // Compute uniform circular motion forces given x, y positions
+ getNewCircularMotionForces = (xPos: number, yPos: number) => {
+ let deltaX = (this.props.xMin + this.props.xMax) / 2 - (xPos + this.props.radius);
+ let deltaY = yPos + this.props.radius - (this.props.yMin + this.props.yMax) / 2;
+ let dir = (Math.atan2(deltaY, deltaX) * 180) / Math.PI;
+ const tensionForce: IForce = {
+ description: 'Centripetal Force',
+ magnitude: (this.props.startVelX ** 2 * this.props.mass) / this.props.circularMotionRadius,
+ directionInDegrees: dir,
+ component: false,
+ };
+ return [tensionForce];
+ };
- // Check for collisions in x direction
- checkForCollisionsWithWall = () => {
- let collision = false;
- const minX = this.state.xPosition;
- const maxX = this.state.xPosition + 2 * this.props.radius;
- if (this.state.xVelocity != 0) {
- this.state.walls.forEach((wall) => {
- if (wall.angleInDegrees == 90) {
- const wallX = (wall.xPos / 100) * this.props.layoutDoc._width;
- if (wall.xPos < 0.35) {
- if (minX <= wallX) {
- this.setState({xPosition: wallX+0.01});
- if (this.props.elasticCollisions) {
- this.setState({xVelocity: -this.state.xVelocity});
- } else {
- this.setState({xVelocity: 0});
- }
- collision = true;
- }
- } else {
- if (maxX >= wallX) {
- this.setState({xPosition: wallX- 2 * this.props.radius-0.01});
- if (this.props.elasticCollisions) {
- this.setState({xVelocity: -this.state.xVelocity});
- } else {
- this.setState({xVelocity: 0});
- }
- collision = true;
- }
- }
+ // Compute spring forces given y position
+ getNewSpringForces = (yPos: number) => {
+ let springForce: IForce = {
+ description: 'Spring Force',
+ magnitude: 0,
+ directionInDegrees: 90,
+ component: false,
+ };
+ if (yPos - this.props.springRestLength > 0) {
+ springForce = {
+ description: 'Spring Force',
+ magnitude: this.props.springConstant * (yPos - this.props.springRestLength),
+ directionInDegrees: 90,
+ component: false,
+ };
+ } else if (yPos - this.props.springRestLength < 0) {
+ springForce = {
+ description: 'Spring Force',
+ magnitude: this.props.springConstant * (this.props.springRestLength - yPos),
+ directionInDegrees: 270,
+ component: false,
+ };
}
- });
- }
- return collision;
- };
+ return [
+ {
+ description: 'Gravity',
+ magnitude: Math.abs(this.props.gravity) * this.props.mass,
+ directionInDegrees: 270,
+ component: false,
+ },
+ springForce,
+ ];
+ };
- // Check for collisions in y direction
- checkForCollisionsWithGround = () => {
- let collision = false;
- const minY = this.state.yPosition;
- const maxY = this.state.yPosition + 2 * this.props.radius;
- if (this.state.yVelocity > 0) {
- this.state.walls.forEach((wall) => {
- if (wall.angleInDegrees == 0 && wall.yPos > 0.4) {
- const groundY = (wall.yPos / 100) * this.props.layoutDoc._height;
- if (maxY > groundY) {
- this.setState({yPosition: groundY- 2 * this.props.radius-0.01});
- if (this.props.elasticCollisions) {
- this.setState({yVelocity: -this.state.yVelocity});
- } else {
- this.setState({yVelocity: 0});
- if (this.props.dataDoc['simulationType'] != "Two Weights") {
- const forceOfGravity: IForce = {
- description: "Gravity",
- magnitude: Math.abs(this.props.gravity) * this.props.mass,
- directionInDegrees: 270,
- component: false,
- };
- const normalForce: IForce = {
- description: "Normal force",
- magnitude: Math.abs(this.props.gravity) * this.props.mass,
- directionInDegrees: wall.angleInDegrees + 90,
- component: false,
- };
- this.props.dataDoc['updatedForces'] = ([forceOfGravity, normalForce]);
- if (this.props.dataDoc['simulationType'] == "Inclined Plane") {
- const forceOfGravityC: IForce = {
- description: "Gravity",
- magnitude: Math.abs(this.props.gravity) * this.props.mass,
- directionInDegrees: 270,
- component: true,
- };
- const normalForceC: IForce = {
- description: "Normal force",
- magnitude: Math.abs(this.props.gravity) * this.props.mass,
- directionInDegrees: wall.angleInDegrees + 90,
- component: true,
- };
- this.props.dataDoc['componentForces'] = ([forceOfGravityC, normalForceC]);
- }
- }
- }
- collision = true;
- }
+ // Compute pendulum forces given position, velocity
+ getNewPendulumForces = (xPos: number, yPos: number, xVel: number, yVel: number) => {
+ const x = this.props.xMax / 2 - xPos - this.props.radius;
+ const y = yPos + this.props.radius + 5;
+ let angle = (Math.atan(y / x) * 180) / Math.PI;
+ if (angle < 0) {
+ angle += 180;
}
- });
- }
- if (this.state.yVelocity < 0) {
- this.state.walls.forEach((wall) => {
- if (wall.angleInDegrees == 0 && wall.yPos < 0.4) {
- const groundY = (wall.yPos / 100) * this.props.layoutDoc._height;
- if (minY < groundY) {
- this.setState({yPosition: groundY + 0.01});
- if (this.props.elasticCollisions) {
- this.setState({yVelocity: -this.state.yVelocity});
- } else {
- this.setState({yVelocity: 0});
- }
- collision = true;
- }
+ let oppositeAngle = 90 - angle;
+ if (oppositeAngle < 0) {
+ oppositeAngle = 90 - (180 - angle);
}
- });
- }
- return collision;
- };
- // Called at each RK4 step
- evaluate = (
- currentXPos: number,
- currentYPos: number,
- currentXVel: number,
- currentYVel: number,
- deltaXPos: number,
- deltaYPos: number,
- deltaXVel: number,
- deltaYVel: number,
- dt: number
- ) => {
- const newXPos = currentXPos + deltaXPos * dt;
- const newYPos = currentYPos + deltaYPos * dt;
- const newXVel = currentXVel + deltaXVel * dt;
- const newYVel = currentYVel + deltaYVel * dt;
- const newDeltaXPos = newXVel;
- const newDeltaYPos = newYVel;
- let forces = this.props.updatedForces;
- if (this.props.dataDoc['simulationType'] == "Pendulum") {
- forces = this.getNewPendulumForces(newXPos, newYPos, newXVel, newYVel);
- } else if (this.props.dataDoc['simulationType'] == "Spring") {
- forces = this.getNewSpringForces(newYPos);
- } else if (this.props.dataDoc['simulationType'] == "Circular Motion") {
- forces = this.getNewCircularMotionForces(newXPos, newYPos);
- }
- const newDeltaXVel = this.getNewAccelerationX(forces);
- const newDeltaYVel = this.getNewAccelerationY(forces);
- return {
- xPos: newXPos,
- yPos: newYPos,
- xVel: newXVel,
- yVel: newYVel,
- deltaXPos: newDeltaXPos,
- deltaYPos: newDeltaYPos,
- deltaXVel: newDeltaXVel,
- deltaYVel: newDeltaYVel,
+ const pendulumLength = Math.sqrt(x * x + y * y);
+ this.props.dataDoc.pendulum_angle = oppositeAngle;
+
+ const mag = this.props.mass * Math.abs(this.props.gravity) * Math.cos((oppositeAngle * Math.PI) / 180) + (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength;
+
+ const forceOfTension: IForce = {
+ description: 'Tension',
+ magnitude: mag,
+ directionInDegrees: angle,
+ component: false,
+ };
+
+ return [
+ {
+ description: 'Gravity',
+ magnitude: Math.abs(this.props.gravity) * this.props.mass,
+ directionInDegrees: 270,
+ component: false,
+ },
+ forceOfTension,
+ ];
};
- };
- // Update position, velocity using RK4 method
- update = () => {
- let startXVel = this.state.xVelocity;
- let startYVel = this.state.yVelocity;
- let xPos = this.state.xPosition;
- let yPos = this.state.yPosition;
- let xVel = this.state.xVelocity;
- let yVel = this.state.yVelocity;
- let forces: IForce[] = this.props.dataDoc['updatedForces'];
- if (this.props.dataDoc['simulationType'] == "Pendulum") {
- forces = this.getNewPendulumForces(xPos, yPos, xVel, yVel);
- } else if (this.props.dataDoc['simulationType'] == "Spring") {
- forces = this.getNewSpringForces(yPos);
- } else if (this.props.dataDoc['simulationType'] == "Circular Motion") {
- forces = this.getNewCircularMotionForces(xPos, yPos);
- }
- const xAcc = this.getNewAccelerationX(forces);
- const yAcc = this.getNewAccelerationY(forces);
- for (let i = 0; i < this.props.simulationSpeed; i++) {
- const k1 = this.evaluate(xPos, yPos, xVel, yVel, xVel, yVel, xAcc, yAcc, 0);
- const k2 = this.evaluate(
- xPos,
- yPos,
- xVel,
- yVel,
- k1.deltaXPos,
- k1.deltaYPos,
- k1.deltaXVel,
- k1.deltaYVel,
- this.props.timestepSize * 0.5
- );
- const k3 = this.evaluate(
- xPos,
- yPos,
- xVel,
- yVel,
- k2.deltaXPos,
- k2.deltaYPos,
- k2.deltaXVel,
- k2.deltaYVel,
- this.props.timestepSize * 0.5
- );
- const k4 = this.evaluate(
- xPos,
- yPos,
- xVel,
- yVel,
- k3.deltaXPos,
- k3.deltaYPos,
- k3.deltaXVel,
- k3.deltaYVel,
- this.props.timestepSize
- );
+ // Check for collisions in x direction
+ checkForCollisionsWithWall = () => {
+ let collision = false;
+ const minX = this.state.xPosition;
+ const maxX = this.state.xPosition + 2 * this.props.radius;
+ if (this.state.xVelocity != 0) {
+ this.state.walls.forEach(wall => {
+ if (wall.angleInDegrees == 90) {
+ const wallX = (wall.xPos / 100) * this.props.layoutDoc[WidthSym]();
+ if (wall.xPos < 0.35) {
+ if (minX <= wallX) {
+ this.setState({ xPosition: wallX + 0.01 });
+ if (this.props.elasticCollisions) {
+ this.setState({ xVelocity: -this.state.xVelocity });
+ } else {
+ this.setState({ xVelocity: 0 });
+ }
+ collision = true;
+ }
+ } else {
+ if (maxX >= wallX) {
+ this.setState({ xPosition: wallX - 2 * this.props.radius - 0.01 });
+ if (this.props.elasticCollisions) {
+ this.setState({ xVelocity: -this.state.xVelocity });
+ } else {
+ this.setState({ xVelocity: 0 });
+ }
+ collision = true;
+ }
+ }
+ }
+ });
+ }
+ return collision;
+ };
- xVel +=
- ((this.props.timestepSize * 1.0) / 6.0) *
- (k1.deltaXVel + 2 * (k2.deltaXVel + k3.deltaXVel) + k4.deltaXVel);
- yVel +=
- ((this.props.timestepSize * 1.0) / 6.0) *
- (k1.deltaYVel + 2 * (k2.deltaYVel + k3.deltaYVel) + k4.deltaYVel);
- xPos +=
- ((this.props.timestepSize * 1.0) / 6.0) *
- (k1.deltaXPos + 2 * (k2.deltaXPos + k3.deltaXPos) + k4.deltaXPos);
- yPos +=
- ((this.props.timestepSize * 1.0) / 6.0) *
- (k1.deltaYPos + 2 * (k2.deltaYPos + k3.deltaYPos) + k4.deltaYPos);
- }
- // make sure harmonic motion maintained and errors don't propagate
- if (this.props.dataDoc['simulationType'] == "Spring") {
- if (startYVel < 0 && yVel > 0 && yPos < this.props.springRestLength) {
- let equilibriumPos =
- this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant;
- let amplitude = Math.abs(equilibriumPos - this.props.springStartLength);
- yPos = equilibriumPos - amplitude;
- } else if (startYVel > 0 && yVel < 0 && yPos > this.props.springRestLength) {
- let equilibriumPos =
- this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant;
- let amplitude = Math.abs(equilibriumPos - this.props.springStartLength);
- yPos = equilibriumPos + amplitude;
- }
- }
- if (this.props.dataDoc['simulationType'] == "Pendulum") {
- let startX = this.state.updatedStartPosX;
- if (startXVel <= 0 && xVel > 0) {
- xPos = this.state.updatedStartPosX;
- if (this.state.updatedStartPosX > this.props.xMax / 2) {
- xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius;
+ // Check for collisions in y direction
+ checkForCollisionsWithGround = () => {
+ let collision = false;
+ const minY = this.state.yPosition;
+ const maxY = this.state.yPosition + 2 * this.props.radius;
+ if (this.state.yVelocity > 0) {
+ this.state.walls.forEach(wall => {
+ if (wall.angleInDegrees == 0 && wall.yPos > 0.4) {
+ const groundY = (wall.yPos / 100) * this.props.layoutDoc[HeightSym]();
+ if (maxY > groundY) {
+ this.setState({ yPosition: groundY - 2 * this.props.radius - 0.01 });
+ if (this.props.elasticCollisions) {
+ this.setState({ yVelocity: -this.state.yVelocity });
+ } else {
+ this.setState({ yVelocity: 0 });
+ if (this.props.dataDoc.simulation_Type != 'Two Weights') {
+ const forceOfGravity: IForce = {
+ description: 'Gravity',
+ magnitude: Math.abs(this.props.gravity) * this.props.mass,
+ directionInDegrees: 270,
+ component: false,
+ };
+ const normalForce: IForce = {
+ description: 'Normal force',
+ magnitude: Math.abs(this.props.gravity) * this.props.mass,
+ directionInDegrees: wall.angleInDegrees + 90,
+ component: false,
+ };
+ this.props.dataDoc.updatedForces = [forceOfGravity, normalForce];
+ if (this.props.dataDoc.simulation_Type == 'Inclined Plane') {
+ const forceOfGravityC: IForce = {
+ description: 'Gravity',
+ magnitude: Math.abs(this.props.gravity) * this.props.mass,
+ directionInDegrees: 270,
+ component: true,
+ };
+ const normalForceC: IForce = {
+ description: 'Normal force',
+ magnitude: Math.abs(this.props.gravity) * this.props.mass,
+ directionInDegrees: wall.angleInDegrees + 90,
+ component: true,
+ };
+ this.props.dataDoc.componentForces = [forceOfGravityC, normalForceC];
+ }
+ }
+ }
+ collision = true;
+ }
+ }
+ });
}
- yPos = this.props.startPosY;
- } else if (startXVel >= 0 && xVel < 0) {
- xPos = this.state.updatedStartPosX;
- if (this.state.updatedStartPosX < this.props.xMax / 2) {
- xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius;
+ if (this.state.yVelocity < 0) {
+ this.state.walls.forEach(wall => {
+ if (wall.angleInDegrees == 0 && wall.yPos < 0.4) {
+ const groundY = (wall.yPos / 100) * this.props.layoutDoc[HeightSym]();
+ if (minY < groundY) {
+ this.setState({ yPosition: groundY + 0.01 });
+ if (this.props.elasticCollisions) {
+ this.setState({ yVelocity: -this.state.yVelocity });
+ } else {
+ this.setState({ yVelocity: 0 });
+ }
+ collision = true;
+ }
+ }
+ });
}
- yPos = this.props.startPosY;
- }
- }
- if (this.props.dataDoc['simulationType'] == "One Weight") {
- if (yPos < this.state.maxPosYConservation) {
- yPos = this.state.maxPosYConservation;
- }
- }
- this.setState({xVelocity: xVel});
- this.setState({yVelocity: yVel});
- this.setState({xPosition: xPos});
- this.setState({yPosition: yPos});
- let forcesn = this.props.dataDoc['updatedForces']
- if (this.props.dataDoc['simulationType'] == "Pendulum") {
- forcesn = this.getNewPendulumForces(xPos, yPos, xVel, yVel);
- } else if (this.props.dataDoc['simulationType'] == "Spring") {
- forcesn = this.getNewSpringForces(yPos);
- } else if (this.props.dataDoc['simulationType'] == "Circular Motion") {
- forcesn = this.getNewCircularMotionForces(xPos, yPos);
- }
- this.props.dataDoc['updatedForces'] = (forcesn);
-
- // set component forces if they change
- if (this.props.dataDoc['simulationType'] == "Pendulum") {
- let x = this.props.xMax / 2 - xPos - this.props.radius;
- let y = yPos + this.props.radius + 5;
- let angle = (Math.atan(y / x) * 180) / Math.PI;
- if (angle < 0) {
- angle += 180;
- }
- let oppositeAngle = 90 - angle;
- if (oppositeAngle < 0) {
- oppositeAngle = 90 - (180 - angle);
- }
-
- const pendulumLength = Math.sqrt(x * x + y * y);
+ return collision;
+ };
- const mag =
- this.props.mass * Math.abs(this.props.gravity) * Math.cos((oppositeAngle * Math.PI) / 180) +
- (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength;
+ // Called at each RK4 step
+ evaluate = (currentXPos: number, currentYPos: number, currentXVel: number, currentYVel: number, deltaXPos: number, deltaYPos: number, deltaXVel: number, deltaYVel: number, dt: number) => {
+ const newXPos = currentXPos + deltaXPos * dt;
+ const newYPos = currentYPos + deltaYPos * dt;
+ const newXVel = currentXVel + deltaXVel * dt;
+ const newYVel = currentYVel + deltaYVel * dt;
+ const newDeltaXPos = newXVel;
+ const newDeltaYPos = newYVel;
+ let forces = this.props.updatedForces;
+ if (this.props.dataDoc.simulation_Type == 'Pendulum') {
+ forces = this.getNewPendulumForces(newXPos, newYPos, newXVel, newYVel);
+ } else if (this.props.dataDoc.simulation_Type == 'Spring') {
+ forces = this.getNewSpringForces(newYPos);
+ } else if (this.props.dataDoc.simulation_Type == 'Circular Motion') {
+ forces = this.getNewCircularMotionForces(newXPos, newYPos);
+ }
+ const newDeltaXVel = this.getNewAccelerationX(forces);
+ const newDeltaYVel = this.getNewAccelerationY(forces);
+ return {
+ xPos: newXPos,
+ yPos: newYPos,
+ xVel: newXVel,
+ yVel: newYVel,
+ deltaXPos: newDeltaXPos,
+ deltaYPos: newDeltaYPos,
+ deltaXVel: newDeltaXVel,
+ deltaYVel: newDeltaYVel,
+ };
+ };
- const tensionComponent: IForce = {
- description: "Tension",
- magnitude: mag,
- directionInDegrees: angle,
- component: true,
- };
- const gravityParallel: IForce = {
- description: "Gravity Parallel Component",
- magnitude: Math.abs(this.props.gravity) * Math.cos(((90 - angle) * Math.PI) / 180),
- directionInDegrees: 270 - (90 - angle),
- component: true,
- };
- const gravityPerpendicular: IForce = {
- description: "Gravity Perpendicular Component",
- magnitude: Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180),
- directionInDegrees: -(90 - angle),
- component: true,
- };
- if (Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180) < 0) {
- gravityPerpendicular.magnitude = Math.abs(
- Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180)
- );
- gravityPerpendicular.directionInDegrees = 180 - (90 - angle);
- }
- this.props.dataDoc['componentForces'] = ([
- tensionComponent,
- gravityParallel,
- gravityPerpendicular,
- ]);
- }
- };
+ // Update position, velocity using RK4 method
+ update = () => {
+ let startXVel = this.state.xVelocity;
+ let startYVel = this.state.yVelocity;
+ let xPos = this.state.xPosition;
+ let yPos = this.state.yPosition;
+ let xVel = this.state.xVelocity;
+ let yVel = this.state.yVelocity;
+ let forces: IForce[] = this.props.dataDoc.updatedForces;
+ if (this.props.dataDoc.simulation_Type == 'Pendulum') {
+ forces = this.getNewPendulumForces(xPos, yPos, xVel, yVel);
+ } else if (this.props.dataDoc.simulation_Type == 'Spring') {
+ forces = this.getNewSpringForces(yPos);
+ } else if (this.props.dataDoc.simulation_Type == 'Circular Motion') {
+ forces = this.getNewCircularMotionForces(xPos, yPos);
+ }
+ const xAcc = this.getNewAccelerationX(forces);
+ const yAcc = this.getNewAccelerationY(forces);
+ for (let i = 0; i < this.props.simulationSpeed; i++) {
+ const k1 = this.evaluate(xPos, yPos, xVel, yVel, xVel, yVel, xAcc, yAcc, 0);
+ const k2 = this.evaluate(xPos, yPos, xVel, yVel, k1.deltaXPos, k1.deltaYPos, k1.deltaXVel, k1.deltaYVel, this.props.timestepSize * 0.5);
+ const k3 = this.evaluate(xPos, yPos, xVel, yVel, k2.deltaXPos, k2.deltaYPos, k2.deltaXVel, k2.deltaYVel, this.props.timestepSize * 0.5);
+ const k4 = this.evaluate(xPos, yPos, xVel, yVel, k3.deltaXPos, k3.deltaYPos, k3.deltaXVel, k3.deltaYVel, this.props.timestepSize);
- // Render weight, spring, rod(s), vectors
- render () {
- return (
- <div>
- <div
- className="weightContainer"
- onPointerDown={(e) => {
- if (this.draggable) {
- this.props.dataDoc['paused'] = true;
- this.setState({dragging: true});
- this.setState({clickPositionX: e.clientX});
- this.setState({clickPositionY: e.clientY});
- }
- }}
- onPointerMove={(e) => {
- if (this.state.dragging) {
- let newY = this.state.yPosition + e.clientY - this.state.clickPositionY;
- if (newY > this.props.yMax - 2 * this.props.radius - 10) {
- newY = this.props.yMax - 2 * this.props.radius - 10;
- } else if (newY < 10) {
- newY = 10;
+ xVel += ((this.props.timestepSize * 1.0) / 6.0) * (k1.deltaXVel + 2 * (k2.deltaXVel + k3.deltaXVel) + k4.deltaXVel);
+ yVel += ((this.props.timestepSize * 1.0) / 6.0) * (k1.deltaYVel + 2 * (k2.deltaYVel + k3.deltaYVel) + k4.deltaYVel);
+ xPos += ((this.props.timestepSize * 1.0) / 6.0) * (k1.deltaXPos + 2 * (k2.deltaXPos + k3.deltaXPos) + k4.deltaXPos);
+ yPos += ((this.props.timestepSize * 1.0) / 6.0) * (k1.deltaYPos + 2 * (k2.deltaYPos + k3.deltaYPos) + k4.deltaYPos);
+ }
+ // make sure harmonic motion maintained and errors don't propagate
+ if (this.props.dataDoc.simulation_Type == 'Spring') {
+ if (startYVel < 0 && yVel > 0 && yPos < this.props.springRestLength) {
+ let equilibriumPos = this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant;
+ let amplitude = Math.abs(equilibriumPos - this.props.springStartLength);
+ yPos = equilibriumPos - amplitude;
+ } else if (startYVel > 0 && yVel < 0 && yPos > this.props.springRestLength) {
+ let equilibriumPos = this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant;
+ let amplitude = Math.abs(equilibriumPos - this.props.springStartLength);
+ yPos = equilibriumPos + amplitude;
}
-
- let newX = this.state.xPosition + e.clientX - this.state.clickPositionX;
- if (newX > this.props.xMax - 2 * this.props.radius - 10) {
- newX = this.props.xMax - 2 * this.props.radius - 10;
- } else if (newX < 10) {
- newX = 10;
+ }
+ if (this.props.dataDoc.simulation_Type == 'Pendulum') {
+ let startX = this.state.updatedStartPosX;
+ if (startXVel <= 0 && xVel > 0) {
+ xPos = this.state.updatedStartPosX;
+ if (this.state.updatedStartPosX > this.props.xMax / 2) {
+ xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius;
+ }
+ yPos = this.props.startPosY;
+ } else if (startXVel >= 0 && xVel < 0) {
+ xPos = this.state.updatedStartPosX;
+ if (this.state.updatedStartPosX < this.props.xMax / 2) {
+ xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius;
+ }
+ yPos = this.props.startPosY;
}
- if (this.props.dataDoc['simulationType'] == "Suspension") {
- if (newX < (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15) {
- newX = (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15;
- } else if (newX > (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15) {
- newX = (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15;
- }
+ }
+ if (this.props.dataDoc.simulation_Type == 'One Weight') {
+ if (yPos < this.state.maxPosYConservation) {
+ yPos = this.state.maxPosYConservation;
}
+ }
+ this.setState({ xVelocity: xVel });
+ this.setState({ yVelocity: yVel });
+ this.setState({ xPosition: xPos });
+ this.setState({ yPosition: yPos });
+ let forcesn = this.props.dataDoc.updatedForces;
+ if (this.props.dataDoc.simulation_Type == 'Pendulum') {
+ forcesn = this.getNewPendulumForces(xPos, yPos, xVel, yVel);
+ } else if (this.props.dataDoc.simulation_Type == 'Spring') {
+ forcesn = this.getNewSpringForces(yPos);
+ } else if (this.props.dataDoc.simulation_Type == 'Circular Motion') {
+ forcesn = this.getNewCircularMotionForces(xPos, yPos);
+ }
+ this.props.dataDoc.updatedForces = forcesn;
- this.setState({yPosition: newY});
- this.props.dataDoc['positionYDisplay'] =
- Math.round((this.props.yMax - 2 * this.props.radius - newY + 5) * 100) / 100
- if (this.props.dataDoc['simulationType'] != "Pulley") {
- this.setState({xPosition: newX});
- this.props.dataDoc['positionXDisplay'] = newX
- }
- if (this.props.dataDoc['simulationType'] != "Suspension") {
- if (this.props.dataDoc['simulationType'] != "Pulley") {
- this.setState({updatedStartPosX: newX});
- }
- this.setState({updatedStartPosY: newY});
- }
- this.setState({clickPositionX: e.clientX});
- this.setState({clickPositionY: e.clientY});
- this.setDisplayValues();
- }
- }}
- onPointerUp={(e) => {
- if (this.state.dragging) {
- if (
- this.props.dataDoc['simulationType'] != "Pendulum" &&
- this.props.dataDoc['simulationType'] != "Suspension"
- ) {
- this.resetEverything();
+ // set component forces if they change
+ if (this.props.dataDoc.simulation_Type == 'Pendulum') {
+ let x = this.props.xMax / 2 - xPos - this.props.radius;
+ let y = yPos + this.props.radius + 5;
+ let angle = (Math.atan(y / x) * 180) / Math.PI;
+ if (angle < 0) {
+ angle += 180;
}
- this.setState({dragging: false});
- let newY = this.state.yPosition + e.clientY - this.state.clickPositionY;
- if (newY > this.props.yMax - 2 * this.props.radius - 10) {
- newY = this.props.yMax - 2 * this.props.radius - 10;
- } else if (newY < 10) {
- newY = 10;
+ let oppositeAngle = 90 - angle;
+ if (oppositeAngle < 0) {
+ oppositeAngle = 90 - (180 - angle);
}
- let newX = this.state.xPosition + e.clientX - this.state.clickPositionX;
- if (newX > this.props.xMax - 2 * this.props.radius - 10) {
- newX = this.props.xMax - 2 * this.props.radius - 10;
- } else if (newX < 10) {
- newX = 10;
- }
- if (this.props.dataDoc['simulationType'] == "Spring") {
- this.props.dataDoc.springStartLength = newY
- }
- if (this.props.dataDoc['simulationType'] == "Suspension") {
- let x1rod = (this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200;
- let x2rod = (this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius;
- let deltaX1 = this.state.xPosition + this.props.radius - x1rod;
- let deltaX2 = x2rod - (this.state.xPosition + this.props.radius);
- let deltaY = this.state.yPosition + this.props.radius;
- let dir1T = Math.PI - Math.atan(deltaY / deltaX1);
- let dir2T = Math.atan(deltaY / deltaX2);
- let tensionMag2 =
- (this.props.mass * Math.abs(this.props.gravity)) /
- ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) +
- Math.sin(dir2T));
- let tensionMag1 =
- (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
- dir1T = (dir1T * 180) / Math.PI;
- dir2T = (dir2T * 180) / Math.PI;
- const tensionForce1: IForce = {
- description: "Tension",
- magnitude: tensionMag1,
- directionInDegrees: dir1T,
- component: false,
- };
- const tensionForce2: IForce = {
- description: "Tension",
- magnitude: tensionMag2,
- directionInDegrees: dir2T,
- component: false,
- };
- const grav: IForce = {
- description: "Gravity",
- magnitude: this.props.mass * Math.abs(this.props.gravity),
- directionInDegrees: 270,
- component: false,
- };
- this.props.dataDoc['updatedForces'] = ([tensionForce1, tensionForce2, grav]);
+ const pendulumLength = Math.sqrt(x * x + y * y);
+
+ const mag = this.props.mass * Math.abs(this.props.gravity) * Math.cos((oppositeAngle * Math.PI) / 180) + (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength;
+
+ const tensionComponent: IForce = {
+ description: 'Tension',
+ magnitude: mag,
+ directionInDegrees: angle,
+ component: true,
+ };
+ const gravityParallel: IForce = {
+ description: 'Gravity Parallel Component',
+ magnitude: Math.abs(this.props.gravity) * Math.cos(((90 - angle) * Math.PI) / 180),
+ directionInDegrees: 270 - (90 - angle),
+ component: true,
+ };
+ const gravityPerpendicular: IForce = {
+ description: 'Gravity Perpendicular Component',
+ magnitude: Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180),
+ directionInDegrees: -(90 - angle),
+ component: true,
+ };
+ if (Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180) < 0) {
+ gravityPerpendicular.magnitude = Math.abs(Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180));
+ gravityPerpendicular.directionInDegrees = 180 - (90 - angle);
}
- }
- }}
- >
- <div className="weight" style={this.weightStyle}>
- <p className="weightLabel">{this.props.mass} kg</p>
- </div>
- </div>
- {this.props.dataDoc['simulationType'] == "Spring" && (
- <div
- className="spring"
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: 0,
- top: 0,
- }}
- >
- <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
- {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((val) => {
- const count = 10;
- let xPos1;
- let yPos1;
- let xPos2;
- let yPos2;
- if (val % 2 == 0) {
- xPos1 = this.state.xPosition + this.props.radius - 20;
- xPos2 = this.state.xPosition + this.props.radius + 20;
- } else {
- xPos1 = this.state.xPosition + this.props.radius + 20;
- xPos2 = this.state.xPosition + this.props.radius - 20;
- }
- yPos1 = (val * this.state.yPosition) / count;
- yPos2 = ((val + 1) * this.state.yPosition) / count;
- return (
- <line
- key={val}
- x1={xPos1}
- y1={yPos1}
- x2={xPos2}
- y2={yPos2}
- stroke={"#808080"}
- strokeWidth="10"
- />
- );
- })}
- </svg>
- </div>
- )}
-
- {this.props.dataDoc['simulationType'] == "Pulley" && (
- <div
- className="rod"
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: 0,
- top: 0,
- }}
- >
- <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
- <line
- x1={this.state.xPosition + this.props.radius}
- y1={this.state.yPosition + this.props.radius}
- x2={this.state.xPosition + this.props.radius}
- y2={this.props.yMin}
- stroke={"#deb887"}
- strokeWidth="10"
- />
- </svg>
- </div>
- )}
- {this.props.dataDoc['simulationType'] == "Pulley" && (
- <div
- className="wheel"
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: 0,
- top: 0,
- }}
- >
- <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
- <circle
- cx={(this.props.xMax + this.props.xMin) / 2}
- cy={this.props.radius}
- r={this.props.radius * 1.5}
- fill={"#808080"}
- />
- </svg>
- </div>
- )}
- {this.props.dataDoc['simulationType'] == "Suspension" && (
- <div
- className="rod"
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: 0,
- top: 0,
- }}
- >
- <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
- <line
- x1={this.state.xPosition + this.props.radius}
- y1={this.state.yPosition + this.props.radius}
- x2={(this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200}
- y2={this.props.yMin}
- stroke={"#deb887"}
- strokeWidth="10"
- />
- </svg>
- <p
- style={{
- position: "absolute",
- left: (this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200 + 80 + "px",
- top: 10 + "px",
- backgroundColor: this.labelBackgroundColor,
- }}
- >
- {Math.round(
- ((Math.atan(
- (this.state.yPosition + this.props.radius) /
- (this.state.xPosition +
- this.props.radius -
- ((this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200))
- ) *
- 180) /
- Math.PI) *
- 100
- ) / 100}
- °
- </p>
- <svg width={this.props.layoutDoc._width + "px"} height={this.props.layoutDoc._height + "px"}>
- <line
- x1={this.state.xPosition + this.props.radius}
- y1={this.state.yPosition + this.props.radius}
- x2={(this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius}
- y2={this.props.yMin}
- stroke={"#deb887"}
- strokeWidth="10"
- />
- </svg>
+ this.props.dataDoc.componentForces = [tensionComponent, gravityParallel, gravityPerpendicular];
+ }
+ };
- <p
- style={{
- position: "absolute",
- left: (this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius - 80 + "px",
- top: 10 + "px",
- backgroundColor: this.labelBackgroundColor,
- }}
- >
- {Math.round(
- ((Math.atan(
- (this.state.yPosition + this.props.radius) /
- ((this.props.xMax + this.props.xMin) / 2 +
- this.props.yMin +
- 200 +
- this.props.radius -
- (this.state.xPosition + this.props.radius))
- ) *
- 180) /
- Math.PI) *
- 100
- ) / 100}
- °
- </p>
- </div>
- )}
- {this.props.dataDoc['simulationType'] == "Circular Motion" && (
- <div
- className="rod"
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: 0,
- top: 0,
- }}
- >
- <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
- <line
- x1={this.state.xPosition + this.props.radius}
- y1={this.state.yPosition + this.props.radius}
- x2={(this.props.xMin + this.props.xMax) / 2}
- y2={(this.props.yMin + this.props.yMax) / 2}
- stroke={"#deb887"}
- strokeWidth="10"
- />
- </svg>
- </div>
- )}
- {this.props.dataDoc['simulationType'] == "Pendulum" && (
- <div
- className="rod"
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: 0,
- top: 0,
- }}
- >
- <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
- <line
- x1={this.state.xPosition + this.props.radius}
- y1={this.state.yPosition + this.props.radius}
- x2={this.props.xMax / 2}
- y2={-5}
- stroke={"#deb887"}
- strokeWidth="10"
- />
- </svg>
- {!this.state.dragging && (
+ // Render weight, spring, rod(s), vectors
+ render() {
+ return (
<div>
- <p
- style={{
- position: "absolute",
- zIndex: 5,
- left: this.state.xPosition + "px",
- top: this.state.yPosition - 70 + "px",
- backgroundColor: this.labelBackgroundColor,
- }}
- >
- {Math.round(this.props.pendulumLength)} m
- </p>
- <p
- style={{
- position: "absolute",
- left: this.props.xMax / 2 + "px",
- top: 30 + "px",
- backgroundColor: this.labelBackgroundColor,
- }}
- >
- {Math.round(this.props.pendulumAngle * 100) / 100}°
- </p>
- </div>
- )}
- </div>
- )}
- {this.props.dataDoc['simulationType'] == "Inclined Plane" && (
- <div>
- <div
- style={{ position: "absolute", left: "0", top: "0" }}
- >
- <svg width={this.props.xMax + "px"} height={this.props.yMax + "px"}>
- <polygon points={this.state.coordinates} style={{ fill: "burlywood" }} />
- </svg>
- </div>
-
- <p
- style={{
- position: "absolute",
- left: Math.round(this.props.xMax * 0.5 - 200 + this.props.wedgeWidth - 80) + "px",
- top: Math.round(this.props.yMax - 40) + "px",
- }}
- >
- {Math.round(
- ((Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI) * 100
- ) / 100}
- °
- </p>
- </div>
- )}
- {!this.state.dragging && this.props.showAcceleration && (
- <div>
- <div
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: 0,
- top: 0,
- }}
- >
- <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
- <defs>
- <marker
- id="accArrow"
- markerWidth="10"
- markerHeight="10"
- refX="0"
- refY="3"
- orient="auto"
- markerUnits="strokeWidth"
- >
- <path d="M0,0 L0,6 L9,3 z" fill="green" />
- </marker>
- </defs>
- <line
- x1={this.state.xPosition + this.props.radius}
- y1={this.state.yPosition + this.props.radius}
- x2={this.state.xPosition + this.props.radius + this.getNewAccelerationX(this.props.updatedForces) * 15}
- y2={this.state.yPosition + this.props.radius + this.getNewAccelerationY(this.props.updatedForces) * 15}
- stroke={"green"}
- strokeWidth="5"
- markerEnd="url(#accArrow)"
- />
- </svg>
- <div
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: this.state.xPosition + this.props.radius + this.state.xAccel * 15 + 25 + "px",
- top: this.state.yPosition + this.props.radius + this.state.yAccel * 15 + 70 + "px",
- lineHeight: 1,
- }}
- >
- <p>
- {Math.round(
- 100 * Math.sqrt(this.state.xAccel * this.state.xAccel + this.state.yAccel * this.state.yAccel)
- ) / 100}{" "}
- m/s
- <sup>2</sup>
- </p>
- </div>
- </div>
- </div>
- )}
- {!this.state.dragging && this.props.showVelocity && (
- <div>
- <div
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: 0,
- top: 0,
- }}
- >
- <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
- <defs>
- <marker
- id="velArrow"
- markerWidth="10"
- markerHeight="10"
- refX="0"
- refY="3"
- orient="auto"
- markerUnits="strokeWidth"
- >
- <path d="M0,0 L0,6 L9,3 z" fill="blue" />
- </marker>
- </defs>
- <line
- x1={this.state.xPosition + this.props.radius}
- y1={this.state.yPosition + this.props.radius}
- x2={this.state.xPosition + this.props.radius + this.state.xVelocity * 7}
- y2={this.state.yPosition + this.props.radius + this.state.yVelocity * 7}
- stroke={"blue"}
- strokeWidth="5"
- markerEnd="url(#velArrow)"
- />
- </svg>
- <div
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: this.state.xPosition + this.props.radius + this.state.xVelocity * 7 + 25 + "px",
- top: this.state.yPosition + this.props.radius + this.state.yVelocity * 7 + "px",
- lineHeight: 1,
- }}
- >
- <p>
- {Math.round(
- 100 *
- Math.sqrt(
- this.props.displayXVelocity * this.props.displayXVelocity +
- this.props.displayYVelocity * this.props.displayYVelocity
- )
- ) / 100}{" "}
- m/s
- </p>
- </div>
- </div>
- </div>
- )}
- {!this.state.dragging &&
- this.props.showComponentForces &&
- this.props.componentForces.map((force, index) => {
- if (force.magnitude < this.epsilon) {
- return;
- }
- let arrowStartY: number = this.state.yPosition + this.props.radius;
- const arrowStartX: number = this.state.xPosition + this.props.radius;
- let arrowEndY: number =
- arrowStartY -
- Math.abs(force.magnitude) *
- 20 *
- Math.sin((force.directionInDegrees * Math.PI) / 180);
- const arrowEndX: number =
- arrowStartX +
- Math.abs(force.magnitude) *
- 20 *
- Math.cos((force.directionInDegrees * Math.PI) / 180);
+ <div
+ className="weightContainer"
+ onPointerDown={e => {
+ if (this.draggable) {
+ this.props.dataDoc.paused = true;
+ this.setState({ dragging: true });
+ this.setState({ clickPositionX: e.clientX });
+ this.setState({ clickPositionY: e.clientY });
+ }
+ }}
+ onPointerMove={e => {
+ if (this.state.dragging) {
+ let newY = this.state.yPosition + e.clientY - this.state.clickPositionY;
+ if (newY > this.props.yMax - 2 * this.props.radius - 10) {
+ newY = this.props.yMax - 2 * this.props.radius - 10;
+ } else if (newY < 10) {
+ newY = 10;
+ }
- let color = "#0d0d0d";
+ let newX = this.state.xPosition + e.clientX - this.state.clickPositionX;
+ if (newX > this.props.xMax - 2 * this.props.radius - 10) {
+ newX = this.props.xMax - 2 * this.props.radius - 10;
+ } else if (newX < 10) {
+ newX = 10;
+ }
+ if (this.props.dataDoc.simulation_Type == 'Suspension') {
+ if (newX < (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15) {
+ newX = (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15;
+ } else if (newX > (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15) {
+ newX = (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15;
+ }
+ }
- let labelTop = arrowEndY;
- let labelLeft = arrowEndX;
- if (force.directionInDegrees > 90 && force.directionInDegrees < 270) {
- labelLeft -= 120;
- } else {
- labelLeft += 30;
- }
- if (force.directionInDegrees >= 0 && force.directionInDegrees < 180) {
- labelTop += 40;
- } else {
- labelTop -= 40;
- }
- labelTop = Math.min(labelTop, this.props.yMax + 50);
- labelTop = Math.max(labelTop, this.props.yMin);
- labelLeft = Math.min(labelLeft, this.props.xMax - 60);
- labelLeft = Math.max(labelLeft, this.props.xMin);
+ this.setState({ yPosition: newY });
+ this.props.dataDoc.position_YDisplay = Math.round((this.props.yMax - 2 * this.props.radius - newY + 5) * 100) / 100;
+ if (this.props.dataDoc.simulation_Type != 'Pulley') {
+ this.setState({ xPosition: newX });
+ this.props.dataDoc.position_XDisplay = newX;
+ }
+ if (this.props.dataDoc.simulation_Type != 'Suspension') {
+ if (this.props.dataDoc.simulation_Type != 'Pulley') {
+ this.setState({ updatedStartPosX: newX });
+ }
+ this.setState({ updatedStartPosY: newY });
+ }
+ this.setState({ clickPositionX: e.clientX });
+ this.setState({ clickPositionY: e.clientY });
+ this.setDisplayValues();
+ }
+ }}
+ onPointerUp={e => {
+ if (this.state.dragging) {
+ if (this.props.dataDoc.simulation_Type != 'Pendulum' && this.props.dataDoc.simulation_Type != 'Suspension') {
+ this.resetEverything();
+ }
+ this.setState({ dragging: false });
+ let newY = this.state.yPosition + e.clientY - this.state.clickPositionY;
+ if (newY > this.props.yMax - 2 * this.props.radius - 10) {
+ newY = this.props.yMax - 2 * this.props.radius - 10;
+ } else if (newY < 10) {
+ newY = 10;
+ }
- return (
- <div key={index}>
- <div
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: this.props.xMin,
- top: this.props.yMin,
- }}
- >
- <svg
- width={this.props.xMax - this.props.xMin + "px"}
- height={this.props.layoutDoc._height + "px"}
- >
- <defs>
- <marker
- id="forceArrow"
- markerWidth="10"
- markerHeight="10"
- refX="0"
- refY="3"
- orient="auto"
- markerUnits="strokeWidth"
- >
- <path d="M0,0 L0,6 L9,3 z" fill={color} />
- </marker>
- </defs>
- {force.component == true && (
- <line
- x1={arrowStartX}
- y1={arrowStartY}
- x2={arrowEndX}
- y2={arrowEndY}
- stroke={color}
- strokeWidth="5"
- strokeDasharray="10,10"
- markerEnd="url(#forceArrow)"
- />
- )}
- {force.component == false && (
- <line
- x1={arrowStartX}
- y1={arrowStartY}
- x2={arrowEndX}
- y2={arrowEndY}
- stroke={color}
- strokeWidth="5"
- markerEnd="url(#forceArrow)"
- />
- )}
- </svg>
- </div>
- <div
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: labelLeft + "px",
- top: labelTop + "px",
- lineHeight: 1,
- backgroundColor: this.labelBackgroundColor,
- }}
- >
- {force.description && <p>{force.description}</p>}
- {!force.description && <p>Force</p>}
- {this.props.showForceMagnitudes && (
- <p>{Math.round(100 * force.magnitude) / 100} N</p>
+ let newX = this.state.xPosition + e.clientX - this.state.clickPositionX;
+ if (newX > this.props.xMax - 2 * this.props.radius - 10) {
+ newX = this.props.xMax - 2 * this.props.radius - 10;
+ } else if (newX < 10) {
+ newX = 10;
+ }
+ if (this.props.dataDoc.simulation_Type == 'Spring') {
+ this.props.dataDoc.springStartLength = newY;
+ }
+ if (this.props.dataDoc.simulation_Type == 'Suspension') {
+ let x1rod = (this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200;
+ let x2rod = (this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius;
+ let deltaX1 = this.state.xPosition + this.props.radius - x1rod;
+ let deltaX2 = x2rod - (this.state.xPosition + this.props.radius);
+ let deltaY = this.state.yPosition + this.props.radius;
+ let dir1T = Math.PI - Math.atan(deltaY / deltaX1);
+ let dir2T = Math.atan(deltaY / deltaX2);
+ let tensionMag2 = (this.props.mass * Math.abs(this.props.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
+ let tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
+ dir1T = (dir1T * 180) / Math.PI;
+ dir2T = (dir2T * 180) / Math.PI;
+ const tensionForce1: IForce = {
+ description: 'Tension',
+ magnitude: tensionMag1,
+ directionInDegrees: dir1T,
+ component: false,
+ };
+ const tensionForce2: IForce = {
+ description: 'Tension',
+ magnitude: tensionMag2,
+ directionInDegrees: dir2T,
+ component: false,
+ };
+ const grav: IForce = {
+ description: 'Gravity',
+ magnitude: this.props.mass * Math.abs(this.props.gravity),
+ directionInDegrees: 270,
+ component: false,
+ };
+ this.props.dataDoc.updatedForces = [tensionForce1, tensionForce2, grav];
+ }
+ }
+ }}>
+ <div className="weight" style={this.weightStyle}>
+ <p className="weightLabel">{this.props.mass} kg</p>
+ </div>
+ </div>
+ {this.props.dataDoc.simulation_Type == 'Spring' && (
+ <div
+ className="spring"
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ }}>
+ <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}>
+ {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(val => {
+ const count = 10;
+ let xPos1;
+ let yPos1;
+ let xPos2;
+ let yPos2;
+ if (val % 2 == 0) {
+ xPos1 = this.state.xPosition + this.props.radius - 20;
+ xPos2 = this.state.xPosition + this.props.radius + 20;
+ } else {
+ xPos1 = this.state.xPosition + this.props.radius + 20;
+ xPos2 = this.state.xPosition + this.props.radius - 20;
+ }
+ yPos1 = (val * this.state.yPosition) / count;
+ yPos2 = ((val + 1) * this.state.yPosition) / count;
+ return <line key={val} x1={xPos1} y1={yPos1} x2={xPos2} y2={yPos2} stroke={'#808080'} strokeWidth="10" />;
+ })}
+ </svg>
+ </div>
)}
- </div>
- </div>
- );
- })}
- {!this.state.dragging &&
- this.props.showForces && this.props.updatedForces &&
- this.props.updatedForces.map((force, index) => {
- if (force.magnitude < this.epsilon) {
- return;
- }
- let arrowStartY: number = this.state.yPosition + this.props.radius;
- const arrowStartX: number = this.state.xPosition + this.props.radius;
- let arrowEndY: number =
- arrowStartY -
- Math.abs(force.magnitude) *
- 20 *
- Math.sin((force.directionInDegrees * Math.PI) / 180);
- const arrowEndX: number =
- arrowStartX +
- Math.abs(force.magnitude) *
- 20 *
- Math.cos((force.directionInDegrees * Math.PI) / 180);
- let color = "#0d0d0d";
+ {this.props.dataDoc.simulation_Type == 'Pulley' && (
+ <div
+ className="rod"
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ }}>
+ <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}>
+ <line x1={this.state.xPosition + this.props.radius} y1={this.state.yPosition + this.props.radius} x2={this.state.xPosition + this.props.radius} y2={this.props.yMin} stroke={'#deb887'} strokeWidth="10" />
+ </svg>
+ </div>
+ )}
+ {this.props.dataDoc.simulation_Type == 'Pulley' && (
+ <div
+ className="wheel"
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ }}>
+ <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}>
+ <circle cx={(this.props.xMax + this.props.xMin) / 2} cy={this.props.radius} r={this.props.radius * 1.5} fill={'#808080'} />
+ </svg>
+ </div>
+ )}
+ {this.props.dataDoc.simulation_Type == 'Suspension' && (
+ <div
+ className="rod"
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ }}>
+ <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}>
+ <line
+ x1={this.state.xPosition + this.props.radius}
+ y1={this.state.yPosition + this.props.radius}
+ x2={(this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200}
+ y2={this.props.yMin}
+ stroke={'#deb887'}
+ strokeWidth="10"
+ />
+ </svg>
+ <p
+ style={{
+ position: 'absolute',
+ left: (this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200 + 80 + 'px',
+ top: 10 + 'px',
+ backgroundColor: this.labelBackgroundColor,
+ }}>
+ {Math.round(
+ ((Math.atan((this.state.yPosition + this.props.radius) / (this.state.xPosition + this.props.radius - ((this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200))) * 180) / Math.PI) * 100
+ ) / 100}
+ °
+ </p>
+ <div
+ className="rod"
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ }}>
+ <svg width={this.props.layoutDoc._width + 'px'} height={this.props.layoutDoc._height + 'px'}>
+ <line
+ x1={this.state.xPosition + this.props.radius}
+ y1={this.state.yPosition + this.props.radius}
+ x2={(this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius}
+ y2={this.props.yMin}
+ stroke={'#deb887'}
+ strokeWidth="10"
+ />
+ </svg>
+ </div>
- let labelTop = arrowEndY;
- let labelLeft = arrowEndX;
- if (force.directionInDegrees > 90 && force.directionInDegrees < 270) {
- labelLeft -= 120;
- } else {
- labelLeft += 30;
- }
- if (force.directionInDegrees >= 0 && force.directionInDegrees < 180) {
- labelTop += 40;
- } else {
- labelTop -= 40;
- }
- labelTop = Math.min(labelTop, this.props.yMax + 50);
- labelTop = Math.max(labelTop, this.props.yMin);
- labelLeft = Math.min(labelLeft, this.props.xMax - 60);
- labelLeft = Math.max(labelLeft, this.props.xMin);
+ <p
+ style={{
+ position: 'absolute',
+ left: (this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius - 80 + 'px',
+ top: 10 + 'px',
+ backgroundColor: this.labelBackgroundColor,
+ }}>
+ {Math.round(
+ ((Math.atan((this.state.yPosition + this.props.radius) / ((this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius - (this.state.xPosition + this.props.radius))) * 180) / Math.PI) * 100
+ ) / 100}
+ °
+ </p>
+ </div>
+ )}
+ {this.props.dataDoc.simulation_Type == 'Circular Motion' && (
+ <div
+ className="rod"
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ }}>
+ <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}>
+ <line
+ x1={this.state.xPosition + this.props.radius}
+ y1={this.state.yPosition + this.props.radius}
+ x2={(this.props.xMin + this.props.xMax) / 2}
+ y2={(this.props.yMin + this.props.yMax) / 2}
+ stroke={'#deb887'}
+ strokeWidth="10"
+ />
+ </svg>
+ </div>
+ )}
+ {this.props.dataDoc.simulation_Type == 'Pendulum' && (
+ <div
+ className="rod"
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ }}>
+ <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}>
+ <line x1={this.state.xPosition + this.props.radius} y1={this.state.yPosition + this.props.radius} x2={this.props.xMax / 2} y2={-5} stroke={'#deb887'} strokeWidth="10" />
+ </svg>
+ {!this.state.dragging && (
+ <div>
+ <p
+ style={{
+ position: 'absolute',
+ zIndex: 5,
+ left: this.state.xPosition + 'px',
+ top: this.state.yPosition - 70 + 'px',
+ backgroundColor: this.labelBackgroundColor,
+ }}>
+ {Math.round(this.props.pendulumLength)} m
+ </p>
+ <p
+ style={{
+ position: 'absolute',
+ left: this.props.xMax / 2 + 'px',
+ top: 30 + 'px',
+ backgroundColor: this.labelBackgroundColor,
+ }}>
+ {Math.round(this.props.pendulum_angle * 100) / 100}°
+ </p>
+ </div>
+ )}
+ </div>
+ )}
+ {this.props.dataDoc.simulation_Type == 'Inclined Plane' && (
+ <div>
+ <div style={{ position: 'absolute', left: '0', top: '0' }}>
+ <svg width={this.props.xMax + 'px'} height={this.props.yMax + 'px'}>
+ <polygon points={this.state.coordinates} style={{ fill: 'burlywood' }} />
+ </svg>
+ </div>
- return (
- <div key={index}>
- <div
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: this.props.xMin,
- top: this.props.yMin,
- }}
- >
- <svg
- width={this.props.xMax - this.props.xMin + "px"}
- height={this.props.layoutDoc._height + "px"}
- >
- <defs>
- <marker
- id="forceArrow"
- markerWidth="10"
- markerHeight="10"
- refX="0"
- refY="3"
- orient="auto"
- markerUnits="strokeWidth"
- >
- <path d="M0,0 L0,6 L9,3 z" fill={color} />
- </marker>
- </defs>
- {force.component == true && (
- <line
- x1={arrowStartX}
- y1={arrowStartY}
- x2={arrowEndX}
- y2={arrowEndY}
- stroke={color}
- strokeWidth="5"
- strokeDasharray="10,10"
- markerEnd="url(#forceArrow)"
- />
- )}
- {force.component == false && (
- <line
- x1={arrowStartX}
- y1={arrowStartY}
- x2={arrowEndX}
- y2={arrowEndY}
- stroke={color}
- strokeWidth="5"
- markerEnd="url(#forceArrow)"
- />
- )}
- </svg>
- </div>
- <div
- style={{
- pointerEvents: "none",
- position: "absolute",
- left: labelLeft + "px",
- top: labelTop + "px",
- lineHeight: 1,
- backgroundColor: this.labelBackgroundColor,
- }}
- >
- {force.description && <p>{force.description}</p>}
- {!force.description && <p>Force</p>}
- {this.props.showForceMagnitudes && (
- <p>{Math.round(100 * force.magnitude) / 100} N</p>
+ <p
+ style={{
+ position: 'absolute',
+ left: Math.round(this.props.xMax * 0.5 - 200 + this.props.wedgeWidth - 80) + 'px',
+ top: Math.round(this.props.yMax - 40) + 'px',
+ }}>
+ {Math.round(((Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI) * 100) / 100}°
+ </p>
+ </div>
)}
- </div>
+ {!this.state.dragging && this.props.showAcceleration && (
+ <div>
+ <div
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ }}>
+ <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}>
+ <defs>
+ <marker id="accArrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
+ <path d="M0,0 L0,6 L9,3 z" fill="green" />
+ </marker>
+ </defs>
+ <line
+ x1={this.state.xPosition + this.props.radius}
+ y1={this.state.yPosition + this.props.radius}
+ x2={this.state.xPosition + this.props.radius + this.getNewAccelerationX(this.props.updatedForces) * 15}
+ y2={this.state.yPosition + this.props.radius + this.getNewAccelerationY(this.props.updatedForces) * 15}
+ stroke={'green'}
+ strokeWidth="5"
+ markerEnd="url(#accArrow)"
+ />
+ </svg>
+ <div
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: this.state.xPosition + this.props.radius + this.state.xAccel * 15 + 25 + 'px',
+ top: this.state.yPosition + this.props.radius + this.state.yAccel * 15 + 70 + 'px',
+ lineHeight: 1,
+ }}>
+ <p>
+ {Math.round(100 * Math.sqrt(this.state.xAccel * this.state.xAccel + this.state.yAccel * this.state.yAccel)) / 100} m/s
+ <sup>2</sup>
+ </p>
+ </div>
+ </div>
+ </div>
+ )}
+ {!this.state.dragging && this.props.showVelocity && (
+ <div>
+ <div
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ }}>
+ <svg width={this.props.xMax + 'px'} height={this.props.layoutDoc._height + 'px'}>
+ <defs>
+ <marker id="velArrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
+ <path d="M0,0 L0,6 L9,3 z" fill="blue" />
+ </marker>
+ </defs>
+ <line
+ x1={this.state.xPosition + this.props.radius}
+ y1={this.state.yPosition + this.props.radius}
+ x2={this.state.xPosition + this.props.radius + this.state.xVelocity * 7}
+ y2={this.state.yPosition + this.props.radius + this.state.yVelocity * 7}
+ stroke={'blue'}
+ strokeWidth="5"
+ markerEnd="url(#velArrow)"
+ />
+ </svg>
+ <div
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: this.state.xPosition + this.props.radius + this.state.xVelocity * 7 + 25 + 'px',
+ top: this.state.yPosition + this.props.radius + this.state.yVelocity * 7 + 'px',
+ lineHeight: 1,
+ }}>
+ <p>{Math.round(100 * Math.sqrt(this.props.displayXVelocity * this.props.displayXVelocity + this.props.displayYVelocity * this.props.displayYVelocity)) / 100} m/s</p>
+ </div>
+ </div>
+ </div>
+ )}
+ {!this.state.dragging &&
+ this.props.showComponentForces &&
+ this.props.componentForces.map((force, index) => {
+ if (force.magnitude < this.epsilon) {
+ return;
+ }
+ let arrowStartY: number = this.state.yPosition + this.props.radius;
+ const arrowStartX: number = this.state.xPosition + this.props.radius;
+ let arrowEndY: number = arrowStartY - Math.abs(force.magnitude) * 20 * Math.sin((force.directionInDegrees * Math.PI) / 180);
+ const arrowEndX: number = arrowStartX + Math.abs(force.magnitude) * 20 * Math.cos((force.directionInDegrees * Math.PI) / 180);
+
+ let color = '#0d0d0d';
+
+ let labelTop = arrowEndY;
+ let labelLeft = arrowEndX;
+ if (force.directionInDegrees > 90 && force.directionInDegrees < 270) {
+ labelLeft -= 120;
+ } else {
+ labelLeft += 30;
+ }
+ if (force.directionInDegrees >= 0 && force.directionInDegrees < 180) {
+ labelTop += 40;
+ } else {
+ labelTop -= 40;
+ }
+ labelTop = Math.min(labelTop, this.props.yMax + 50);
+ labelTop = Math.max(labelTop, this.props.yMin);
+ labelLeft = Math.min(labelLeft, this.props.xMax - 60);
+ labelLeft = Math.max(labelLeft, this.props.xMin);
+
+ return (
+ <div key={index}>
+ <div
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: this.props.xMin,
+ top: this.props.yMin,
+ }}>
+ <svg width={this.props.xMax - this.props.xMin + 'px'} height={this.props.layoutDoc._height + 'px'}>
+ <defs>
+ <marker id="forceArrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
+ <path d="M0,0 L0,6 L9,3 z" fill={color} />
+ </marker>
+ </defs>
+ {force.component == true && <line x1={arrowStartX} y1={arrowStartY} x2={arrowEndX} y2={arrowEndY} stroke={color} strokeWidth="5" strokeDasharray="10,10" markerEnd="url(#forceArrow)" />}
+ {force.component == false && <line x1={arrowStartX} y1={arrowStartY} x2={arrowEndX} y2={arrowEndY} stroke={color} strokeWidth="5" markerEnd="url(#forceArrow)" />}
+ </svg>
+ </div>
+ <div
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: labelLeft + 'px',
+ top: labelTop + 'px',
+ lineHeight: 1,
+ backgroundColor: this.labelBackgroundColor,
+ }}>
+ {force.description && <p>{force.description}</p>}
+ {!force.description && <p>Force</p>}
+ {this.props.showForceMagnitudes && <p>{Math.round(100 * force.magnitude) / 100} N</p>}
+ </div>
+ </div>
+ );
+ })}
+ {!this.state.dragging &&
+ this.props.showForces &&
+ this.props.updatedForces &&
+ this.props.updatedForces.map((force, index) => {
+ if (force.magnitude < this.epsilon) {
+ return;
+ }
+ let arrowStartY: number = this.state.yPosition + this.props.radius;
+ const arrowStartX: number = this.state.xPosition + this.props.radius;
+ let arrowEndY: number = arrowStartY - Math.abs(force.magnitude) * 20 * Math.sin((force.directionInDegrees * Math.PI) / 180);
+ const arrowEndX: number = arrowStartX + Math.abs(force.magnitude) * 20 * Math.cos((force.directionInDegrees * Math.PI) / 180);
+
+ let color = '#0d0d0d';
+
+ let labelTop = arrowEndY;
+ let labelLeft = arrowEndX;
+ if (force.directionInDegrees > 90 && force.directionInDegrees < 270) {
+ labelLeft -= 120;
+ } else {
+ labelLeft += 30;
+ }
+ if (force.directionInDegrees >= 0 && force.directionInDegrees < 180) {
+ labelTop += 40;
+ } else {
+ labelTop -= 40;
+ }
+ labelTop = Math.min(labelTop, this.props.yMax + 50);
+ labelTop = Math.max(labelTop, this.props.yMin);
+ labelLeft = Math.min(labelLeft, this.props.xMax - 60);
+ labelLeft = Math.max(labelLeft, this.props.xMin);
+
+ return (
+ <div key={index}>
+ <div
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: this.props.xMin,
+ top: this.props.yMin,
+ }}>
+ <svg width={this.props.xMax - this.props.xMin + 'px'} height={this.props.layoutDoc._height + 'px'}>
+ <defs>
+ <marker id="forceArrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
+ <path d="M0,0 L0,6 L9,3 z" fill={color} />
+ </marker>
+ </defs>
+ {force.component == true && <line x1={arrowStartX} y1={arrowStartY} x2={arrowEndX} y2={arrowEndY} stroke={color} strokeWidth="5" strokeDasharray="10,10" markerEnd="url(#forceArrow)" />}
+ {force.component == false && <line x1={arrowStartX} y1={arrowStartY} x2={arrowEndX} y2={arrowEndY} stroke={color} strokeWidth="5" markerEnd="url(#forceArrow)" />}
+ </svg>
+ </div>
+ <div
+ style={{
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: labelLeft + 'px',
+ top: labelTop + 'px',
+ lineHeight: 1,
+ backgroundColor: this.labelBackgroundColor,
+ }}>
+ {force.description && <p>{force.description}</p>}
+ {!force.description && <p>Force</p>}
+ {this.props.showForceMagnitudes && <p>{Math.round(100 * force.magnitude) / 100} N</p>}
+ </div>
+ </div>
+ );
+ })}
</div>
- );
- })}
- </div>
- )}
-};
+ );
+ }
+}