aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
diff options
context:
space:
mode:
authorbrynnchernosky <56202540+brynnchernosky@users.noreply.github.com>2023-04-24 13:39:14 -0400
committerbrynnchernosky <56202540+brynnchernosky@users.noreply.github.com>2023-04-24 13:39:14 -0400
commit0cda1a8f8b4e3d79c30291685456c7cb62fd7e8e (patch)
treeb751e75a3f1c7a60b440c75834b723e5299ed0ff /src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
parentdffb5bdfdcffc7e1f85888b7222468bfb95a05ac (diff)
add folder for physics box
Diffstat (limited to 'src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx')
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx860
1 files changed, 860 insertions, 0 deletions
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
new file mode 100644
index 000000000..39b3249e8
--- /dev/null
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
@@ -0,0 +1,860 @@
+import React = require('react');
+import { Doc } from '../../../fields/Doc';
+import { IWallProps } from "./PhysicsSimulationWall";
+
+export interface IForce {
+ description: string;
+ magnitude: number;
+ directionInDegrees: number;
+}
+export interface IWeightProps {
+ adjustPendulumAngle: boolean;
+ color: string;
+ dataDoc: Doc;
+ mass: number;
+ radius: number;
+ simulationReset: boolean;
+ startPosX: number;
+ startPosY: number;
+ startVelX?: number;
+ startVelY?: number;
+ timestepSize: number;
+ updateDisplay: boolean,
+ walls: IWallProps[];
+ wedge: boolean;
+ wedgeHeight: number;
+ wedgeWidth: number;
+ xMax: number;
+ xMin: number;
+ yMax: number;
+ yMin: number;
+}
+
+interface IState {
+ angleLabel: number,
+ clickPositionX: number,
+ clickPositionY: number,
+ dragging: boolean,
+ kineticFriction: boolean,
+ timer: number;
+ update: boolean,
+ updatedStartPosX: number,
+ updatedStartPosY: number,
+ xPosition: number,
+ xVelocity: number,
+ yPosition: number,
+ yVelocity: number,
+}
+export default class Weight extends React.Component<IWeightProps, IState> {
+
+ constructor(props: any) {
+ super(props)
+ this.state = {
+ clickPositionX: 0,
+ clickPositionY: 0,
+ dragging: false,
+ kineticFriction: false,
+ timer: 0,
+ angleLabel: 0,
+ updatedStartPosX: this.props.dataDoc['startPosX'],
+ updatedStartPosY: this.props.dataDoc['startPosY'],
+ xPosition: this.props.dataDoc['startPosX'],
+ xVelocity: this.props.startVelX ? this.props.startVelX: 0,
+ yPosition: this.props.dataDoc['startPosY'],
+ yVelocity: this.props.startVelY ? this.props.startVelY: 0,
+ }
+ }
+
+ // Constants
+ draggable = !this.props.dataDoc['wedge'] ;
+ epsilon = 0.0001;
+ forceOfGravity: IForce = {
+ description: "Gravity",
+ magnitude: this.props.mass * 9.81,
+ directionInDegrees: 270,
+ };
+
+ // Var
+ 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.dataDoc['startPosX'] + "px",
+ position: "absolute" as "absolute",
+ top: this.props.dataDoc['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);
+ this.props.dataDoc['positionYDisplay'] = Math.round(displayPos * 100) / 100
+ };
+ setXPosDisplay = (xPos: number) => {
+ this.props.dataDoc['positionXDisplay'] = Math.round(xPos * 100) / 100;
+ };
+ setYVelDisplay = (yVel: number) => {
+ this.props.dataDoc['velocityYDisplay'] = (-1 * Math.round(yVel * 100)) / 100;
+ };
+ setXVelDisplay = (xVel: number) => {
+ this.props.dataDoc['velocityXDisplay'] = Math.round(xVel * 100) / 100;
+ };
+
+ 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);
+ this.props.dataDoc['accelerationYDisplay'] =
+ (-1 * Math.round(this.getNewAccelerationY(this.props.dataDoc['updatedForces']) * 100)) / 100
+ ;
+ this.props.dataDoc['accelerationXDisplay'] =
+ Math.round(this.getNewAccelerationX(this.props.dataDoc['updatedForces']) * 100) / 100
+ ;
+ };
+
+ componentDidMount() {
+ // Timer for animating the simulation
+ setInterval(() => {
+ this.setState({timer: this.state.timer + 1});
+ }, 60);
+ }
+
+ componentDidUpdate(prevProps: Readonly<IWeightProps>, prevState: Readonly<IState>, snapshot?: any): void {
+
+ // When display values updated by user, update real values
+ if (this.props.updateDisplay != prevProps.updateDisplay) {
+ if (this.props.dataDoc['positionXDisplay'] != this.state.xPosition) {
+ let x = this.props.dataDoc['positionXDisplay'];
+ x = Math.max(0, x);
+ x = Math.min(x, this.props.xMax - 2 * this.props.radius);
+ this.setState({updatedStartPosX: x})
+ this.setState({xPosition: x})
+ this.props.dataDoc['positionXDisplay'] = x;
+ }
+
+ if (this.props.dataDoc['positionYDisplay'] != this.getDisplayYPos(this.state.yPosition)) {
+ let y = this.props.dataDoc['positionYDisplay'];
+ y = Math.max(0, y);
+ y = Math.min(y, this.props.yMax - 2 * this.props.radius);
+ this.props.dataDoc['positionYDisplay'] = y;
+ let coordinatePosition = this.getYPosFromDisplay(y);
+ this.setState({updatedStartPosY: coordinatePosition})
+ this.setState({yPosition: coordinatePosition})
+ }
+
+ if (this.props.dataDoc['velocityXDisplay'] != this.state.xVelocity) {
+ let x = this.props.dataDoc['velocityXDisplay'];
+ this.setState({xVelocity: x})
+ this.props.dataDoc['velocityXDisplay'] = x;
+ }
+
+ if (this.props.dataDoc['velocityYDisplay'] != this.state.yVelocity) {
+ let y = this.props.dataDoc['velocityYDisplay'];
+ this.setState({yVelocity: -y})
+ this.props.dataDoc['velocityYDisplay']
+ }
+ }
+ // Update sim
+ if (this.state.timer != prevState.timer) {
+ if (!this.props.dataDoc['simulationPaused']) {
+ let collisions = false;
+ if (!this.props.dataDoc['pendulum']) {
+ const collisionsWithGround = this.checkForCollisionsWithGround();
+ const collisionsWithWalls = this.checkForCollisionsWithWall();
+ collisions = collisionsWithGround || collisionsWithWalls;
+ }
+ if (!collisions) {
+ this.update();
+ }
+ this.setDisplayValues();
+ }
+ }
+
+ if (this.props.simulationReset != prevProps.simulationReset) {
+ this.resetEverything();
+ }
+ if (this.props.adjustPendulumAngle != prevProps.adjustPendulumAngle) {
+ console.log('update angle')
+ // Change pendulum angle based on input field
+ let length = this.props.dataDoc['pendulumLength'] ?? 0;
+ const x =
+ length * Math.cos(((90 - this.props.dataDoc['pendulumAngle']) * Math.PI) / 180);
+ const y =
+ length * Math.sin(((90 - this.props.dataDoc['pendulumAngle']) * 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.setState({angleLabel: Math.round(this.props.dataDoc['pendulumAngle'] * 100) / 100})
+ }
+ // Update x start position
+ if (this.props.startPosX != prevProps.startPosX) {
+ this.setState({updatedStartPosX: this.props.dataDoc['startPosX']})
+ this.setState({xPosition: this.props.dataDoc['startPosX']})
+ this.setXPosDisplay(this.props.dataDoc['startPosX']);
+ }
+ // Update y start position
+ if (this.props.startPosY != prevProps.startPosY) {
+ this.setState({updatedStartPosY: this.props.dataDoc['startPosY']})
+ this.setState({yPosition: this.props.dataDoc['startPosY']})
+ this.setYPosDisplay(this.props.dataDoc['startPosY']);
+ }
+ if (!this.props.dataDoc['simulationPaused']) {
+ if (this.state.xVelocity != prevState.xVelocity) {
+ if (this.props.dataDoc['wedge'] && this.state.xVelocity != 0 && !this.state.kineticFriction) {
+ this.setState({kineticFriction: true});
+ //switch from static to kinetic friction
+ const normalForce: IForce = {
+ description: "Normal Force",
+ magnitude:
+ this.forceOfGravity.magnitude *
+ Math.cos(Math.atan(this.props.dataDoc['wedgeHeight'] / this.props.dataDoc['wedgeWidth'] )),
+ directionInDegrees:
+ 180 - 90 - (Math.atan(this.props.dataDoc['wedgeHeight'] / this.props.dataDoc['wedgeWidth'] ) * 180) / Math.PI,
+ };
+ let frictionForce: IForce = {
+ description: "Kinetic Friction Force",
+ magnitude:
+ this.props.dataDoc['coefficientOfKineticFriction'] *
+ this.forceOfGravity.magnitude *
+ Math.cos(Math.atan(this.props.dataDoc['wedgeHeight'] / this.props.dataDoc['wedgeWidth'] )),
+ directionInDegrees:
+ 180 - (Math.atan(this.props.dataDoc['wedgeHeight'] / this.props.dataDoc['wedgeWidth'] ) * 180) / Math.PI,
+ };
+ // reduce magnitude of friction force if necessary such that block cannot slide up plane
+ let yForce = -this.forceOfGravity.magnitude;
+ yForce +=
+ normalForce.magnitude *
+ Math.sin((normalForce.directionInDegrees * Math.PI) / 180);
+ yForce +=
+ frictionForce.magnitude *
+ Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
+ if (yForce > 0) {
+ frictionForce.magnitude =
+ (-normalForce.magnitude *
+ Math.sin((normalForce.directionInDegrees * Math.PI) / 180) +
+ this.forceOfGravity.magnitude) /
+ Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
+ }
+ if (this.props.dataDoc['coefficientOfKineticFriction'] != 0) {
+ this.props.dataDoc['updatedForces'] = [this.forceOfGravity, normalForce, frictionForce];
+ } else {
+ this.props.dataDoc['updatedForces'] = ([this.forceOfGravity, normalForce]);
+ }
+ }
+ }
+ }
+
+ this.weightStyle = {
+ alignItems: "center",
+ backgroundColor: this.props.color,
+ borderColor: this.state.dragging ? "lightblue" : "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,
+ };
+ }
+
+ 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['updatedForces'] = (this.props.dataDoc['startForces'])
+ this.setState({angleLabel: Math.round(this.props.dataDoc['pendulumAngle']* 100) / 100})
+ this.setDisplayValues();
+ };
+
+ 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;
+ };
+
+ 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;
+ };
+
+ getNewForces = (
+ xPos: number,
+ yPos: number,
+ xVel: number,
+ yVel: number
+ ) => {
+ if (!this.props.dataDoc['pendulum']) {
+ return this.props.dataDoc['updatedForces'];
+ }
+ 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;
+ this.props.dataDoc['pendulumLength'] = Math.sqrt(x * x + y * y);
+
+ const mag =
+ this.props.mass * 9.81 * Math.cos((oppositeAngle * Math.PI) / 180) +
+ (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength;
+
+ const forceOfTension: IForce = {
+ description: "Tension",
+ magnitude: mag,
+ directionInDegrees: angle,
+ };
+ this.setState({angleLabel: Math.round(this.props.dataDoc['pendulumAngle']* 100) / 100})
+
+ return [this.forceOfGravity, forceOfTension];
+ };
+
+ getNewPosition = (pos: number, vel: number) => {
+ return pos + vel * this.props.timestepSize;
+ };
+
+ getNewVelocity = (vel: number, acc: number) => {
+ return vel + acc * this.props.timestepSize;
+ };
+
+ checkForCollisionsWithWall = () => {
+ let collision = false;
+ const minX = this.state.xPosition;
+ const maxX = this.state.xPosition + 2 * this.props.radius;
+ const containerWidth = 300;
+ if (this.state.xVelocity != 0) {
+ if (this.props.dataDoc.wallPositions) {
+ this.props.dataDoc['wallPositions'].forEach((wall) => {
+ if (wall.angleInDegrees == 90) {
+ const wallX = (wall.xPos / 100) * 300;
+ if (wall.xPos < 0.35) {
+ if (minX <= wallX) {
+ if (this.props.dataDoc['elasticCollisions']) {
+ this.setState({xVelocity: -this.state.xVelocity});
+ } else {
+ this.setState({xVelocity: 0});
+ this.setState({xPosition: wallX+5});
+ }
+ collision = true;
+ }
+ } else {
+ if (maxX >= wallX) {
+ if (this.props.dataDoc['elasticCollisions']) {
+ this.setState({xVelocity: -this.state.xVelocity});
+ } else {
+ this.setState({xVelocity: 0});
+ this.setState({xPosition: wallX - 2 * this.props.radius + 5});
+ }
+ collision = true;
+ }
+ }
+ }
+ });
+ }
+ }
+ return collision;
+ };
+
+ checkForCollisionsWithGround = () => {
+ let collision = false;
+ const maxY = this.state.yPosition + 2 * this.props.radius;
+ if (this.state.yVelocity > 0) {
+ if (this.props.dataDoc.wallPositions) {
+ this.props.dataDoc['wallPositions'].forEach((wall) => {
+ if (wall.angleInDegrees == 0) {
+ const groundY = (wall.yPos / 100) * this.props.yMax;
+ if (maxY >= groundY) {
+ if (this.props.dataDoc['elasticCollisions']) {
+ this.setState({yVelocity: -this.state.yVelocity})
+ } else {
+ this.setState({yVelocity: 0})
+ this.setState({yPosition: groundY - 2 * this.props.radius + 5})
+ const forceOfGravity: IForce = {
+ description: "Gravity",
+ magnitude: 9.81 * this.props.mass,
+ directionInDegrees: 270,
+ };
+ const normalForce: IForce = {
+ description: "Normal force",
+ magnitude: 9.81 * this.props.mass,
+ directionInDegrees: wall.angleInDegrees + 90,
+ };
+ this.props.dataDoc['updatedForces'] = ([forceOfGravity, normalForce]);
+ }
+ collision = true;
+ }
+ }
+ });
+ }
+ }
+ return collision;
+ };
+
+ update = () => {
+ // RK4 update
+ let xPos = this.state.xPosition;
+ let yPos = this.state.yPosition;
+ let xVel = this.state.xVelocity;
+ let yVel = this.state.yVelocity;
+ for (let i = 0; i < 60; i++) {
+ let forces1 = this.getNewForces(xPos, yPos, xVel, yVel);
+ const xAcc1 = this.getNewAccelerationX(forces1);
+ const yAcc1 = this.getNewAccelerationY(forces1);
+ const xVel1 = this.getNewVelocity(xVel, xAcc1);
+ const yVel1 = this.getNewVelocity(yVel, yAcc1);
+
+ let xVel2 = this.getNewVelocity(xVel, xAcc1 / 2);
+ let yVel2 = this.getNewVelocity(yVel, yAcc1 / 2);
+ let xPos2 = this.getNewPosition(xPos, xVel1 / 2);
+ let yPos2 = this.getNewPosition(yPos, yVel1 / 2);
+ const forces2 = this.getNewForces(xPos2, yPos2, xVel2, yVel2);
+ const xAcc2 = this.getNewAccelerationX(forces2);
+ const yAcc2 = this.getNewAccelerationY(forces2);
+ xVel2 = this.getNewVelocity(xVel2, xAcc2);
+ yVel2 = this.getNewVelocity(yVel2, yAcc2);
+ xPos2 = this.getNewPosition(xPos2, xVel2);
+ yPos2 = this.getNewPosition(yPos2, yVel2);
+
+ let xVel3 = this.getNewVelocity(xVel, xAcc2 / 2);
+ let yVel3 = this.getNewVelocity(yVel, yAcc2 / 2);
+ let xPos3 = this.getNewPosition(xPos, xVel2 / 2);
+ let yPos3 = this.getNewPosition(yPos, yVel2 / 2);
+ const forces3 = this.getNewForces(xPos3, yPos3, xVel3, yVel3);
+ const xAcc3 = this.getNewAccelerationX(forces3);
+ const yAcc3 = this.getNewAccelerationY(forces3);
+ xVel3 = this.getNewVelocity(xVel3, xAcc3);
+ yVel3 = this.getNewVelocity(yVel3, yAcc3);
+ xPos3 = this.getNewPosition(xPos3, xVel3);
+ yPos3 = this.getNewPosition(yPos3, yVel3);
+
+ let xVel4 = this.getNewVelocity(xVel, xAcc3);
+ let yVel4 = this.getNewVelocity(yVel, yAcc3);
+ let xPos4 = this.getNewPosition(xPos, xVel3);
+ let yPos4 = this.getNewPosition(yPos, yVel3);
+ const forces4 = this.getNewForces(xPos4, yPos4, xVel4, yVel4);
+ const xAcc4 = this.getNewAccelerationX(forces4);
+ const yAcc4 = this.getNewAccelerationY(forces4);
+ xVel4 = this.getNewVelocity(xVel4, xAcc4);
+ yVel4 = this.getNewVelocity(yVel4, yAcc4);
+ xPos4 = this.getNewPosition(xPos4, xVel4);
+ yPos4 = this.getNewPosition(yPos4, yVel4);
+
+ xVel +=
+ this.props.timestepSize * (xAcc1 / 6.0 + xAcc2 / 3.0 + xAcc3 / 3.0 + xAcc4 / 6.0);
+ yVel +=
+ this.props.timestepSize * (yAcc1 / 6.0 + yAcc2 / 3.0 + yAcc3 / 3.0 + yAcc4 / 6.0);
+ xPos +=
+ this.props.timestepSize * (xVel1 / 6.0 + xVel2 / 3.0 + xVel3 / 3.0 + xVel4 / 6.0);
+ yPos +=
+ this.props.timestepSize * (yVel1 / 6.0 + yVel2 / 3.0 + yVel3 / 3.0 + yVel4 / 6.0);
+ }
+
+ this.setState({xVelocity: xVel});
+ this.setState({yVelocity: yVel});
+ this.setState({xPosition: xPos});
+ this.setState({yPosition: yPos});
+
+ this.props.dataDoc['updatedForces'] = (this.getNewForces(xPos, yPos, xVel, yVel));
+ };
+
+
+ labelBackgroundColor = `rgba(255,255,255,0.5)`;
+
+ render () {
+ return (
+ <div>
+ <div
+ className="weightContainer"
+ // onPointerDown={(e) => {
+ // if (this.draggable) {
+ // e.preventDefault();
+ // this.props.dataDoc['simulationPaused'] = true;
+ // this.setState({dragging: true});
+ // this.setState({clickPositionX: e.clientX})
+ // this.setState({clickPositionY: e.clientY})
+ // }
+ // }}
+ // onPointerMove={(e) => {
+ // e.preventDefault();
+ // if (this.state.dragging) {
+ // let newY = this.state.yPosition + e.clientY - this.state.clickPositionY;
+ // if (newY > this.props.yMax - 2 * this.props.radius) {
+ // newY = this.props.yMax - 2 * this.props.radius;
+ // }
+
+ // let newX = this.state.xPosition + e.clientX - this.state.clickPositionX;
+ // if (newX > this.props.xMax - 2 * this.props.radius) {
+ // newX = this.props.xMax - 2 * this.props.radius;
+ // } else if (newX < 0) {
+ // newX = 0;
+ // }
+ // this.setState({xPosition: newX})
+ // this.setState({yPosition: newY})
+ // this.setState({updatedStartPosX: newX})
+ // this.setState({updatedStartPosY: newY})
+ // this.props.dataDoc['positionYDisplay'] = Math.round((this.props.yMax - 2 * this.props.radius - newY + 5) * 100) / 100;
+ // this.setState({clickPositionX: e.clientX})
+ // this.setState({clickPositionY: e.clientY})
+ // this.setDisplayValues();
+ // }
+ // }}
+ // onPointerUp={(e) => {
+ // if (this.state.dragging) {
+ // e.preventDefault();
+ // if (!this.props.dataDoc['pendulum']) {
+ // 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) {
+ // newY = this.props.yMax - 2 * this.props.radius;
+ // }
+
+ // let newX = this.state.xPosition + e.clientX - this.state.clickPositionX;
+ // if (newX > this.props.xMax - 2 * this.props.radius) {
+ // newX = this.props.xMax - 2 * this.props.radius;
+ // } else if (newX < 0) {
+ // newX = 0;
+ // }
+ // if (this.props.dataDoc['pendulum']) {
+ // const x = this.props.xMax / 2 - newX - this.props.radius;
+ // const y = newY + 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;
+ // this.props.dataDoc['pendulumLength'] = Math.sqrt(x * x + y * y);
+ // const mag = 9.81 * Math.cos((oppositeAngle * Math.PI) / 180);
+ // const forceOfTension: IForce = {
+ // description: "Tension",
+ // magnitude: mag,
+ // directionInDegrees: angle,
+ // };
+ // this.setState({kineticFriction: false})
+ // this.setState({xVelocity: this.props.startVelX ?? 0})
+ // this.setState({yVelocity: this.props.startVelY ?? 0})
+ // this.setDisplayValues();
+ // this.props.dataDoc['updatedForces'] = ([this.forceOfGravity, forceOfTension]);
+ // }
+ // }
+ // }}
+ >
+ <div className="weight" style={this.weightStyle}>
+ <p className="weightLabel">{this.props.mass} kg</p>
+ </div>
+ </div>
+ {this.props.dataDoc['pendulum'] && (
+ <div
+ className="rod"
+ style={{
+ pointerEvents: "none",
+ position: "absolute",
+ left: 0,
+ top: 0,
+ }}
+ >
+ <svg width={this.props.xMax + "px"} height={300 + "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",
+ left: this.props.xMax / 2 + "px",
+ top: 30 + "px",
+ backgroundColor: this.labelBackgroundColor,
+ }}
+ >
+ {this.state.angleLabel}°
+ </p>
+ </div>
+ )}
+ </div>
+ )}
+ {!this.state.dragging && this.props.dataDoc['showAcceleration'] && (
+ <div>
+ <div
+ style={{
+ pointerEvents: "none",
+ position: "absolute",
+ left: 0,
+ top: 0,
+ }}
+ >
+ <svg width={this.props.xMax + "px"} height={300 + "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.dataDoc['updatedForces']) * 5}
+ y2={this.state.yPosition + this.props.radius + this.getNewAccelerationY(this.props.dataDoc['updatedForces']) * 5}
+ stroke={"green"}
+ strokeWidth="5"
+ markerEnd="url(#accArrow)"
+ />
+ </svg>
+ <div
+ style={{
+ pointerEvents: "none",
+ position: "absolute",
+ left:
+ this.state.xPosition +
+ this.props.radius +
+ this.getNewAccelerationX(this.props.dataDoc['updatedForces']) * 5 +
+ 25 +
+ "px",
+ top:
+ this.state.yPosition +
+ this.props.radius +
+ this.getNewAccelerationY(this.props.dataDoc['updatedForces']) * 5 +
+ 25 +
+ "px",
+ lineHeight: 0.5,
+ }}
+ >
+ <p>
+ {Math.round(
+ 100 *
+ Math.sqrt(
+ Math.pow(this.getNewAccelerationX(this.props.dataDoc['updatedForces']) * 3, 2) +
+ Math.pow(this.getNewAccelerationY(this.props.dataDoc['updatedForces']) * 3, 2)
+ )
+ ) / 100}{" "}
+ m/s<sup>2</sup>
+ </p>
+ </div>
+ </div>
+ </div>
+ )}
+ {!this.state.dragging && this.props.dataDoc['showVelocity'] && (
+ <div>
+ <div
+ style={{
+ pointerEvents: "none",
+ position: "absolute",
+ left: 0,
+ top: 0,
+ }}
+ >
+ <svg width={this.props.xMax + "px"} height={300 + "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 * 3}
+ y2={this.state.yPosition + this.props.radius + this.state.yVelocity * 3}
+ stroke={"blue"}
+ strokeWidth="5"
+ markerEnd="url(#velArrow)"
+ />
+ </svg>
+ <div
+ style={{
+ pointerEvents: "none",
+ position: "absolute",
+ left: this.state.xPosition + this.props.radius + this.state.xVelocity * 3 + 25 + "px",
+ top: this.state.yPosition + this.props.radius + this.state.yVelocity * 3 + "px",
+ lineHeight: 0.5,
+ }}
+ >
+ <p>
+ {Math.round(
+ 100 * Math.sqrt(this.state.xVelocity**2 + this.state.yVelocity**2)
+ ) / 100}{" "}
+ m/s
+ </p>
+ </div>
+ </div>
+ </div>
+ )}
+ {!this.state.dragging &&
+ this.props.dataDoc['showForces'] &&
+ this.props.dataDoc['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) *
+ 10 *
+ Math.sin((force.directionInDegrees * Math.PI) / 180);
+ const arrowEndX: number =
+ arrowStartX +
+ Math.abs(force.magnitude) *
+ 10 *
+ 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={300 + "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>
+ <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: 0.5,
+ backgroundColor: this.labelBackgroundColor,
+ }}
+ >
+ {force.description && <p>{force.description}</p>}
+ {!force.description && <p>Force</p>}
+ {this.props.dataDoc['showForceMagnitudes'] && (
+ <p>{Math.round(100 * force.magnitude) / 100} N</p>
+ )}
+ </div>
+ </div>
+ );
+ })}
+ </div>
+ );
+ }
+};