diff options
Diffstat (limited to 'src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx')
-rw-r--r-- | src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx | 860 |
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> + ); + } +}; |