aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss6
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx168
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx251
3 files changed, 117 insertions, 308 deletions
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss
index b498296bf..2e6ad413f 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss
@@ -22,6 +22,12 @@
justify-content: space-between;
}
}
+ .rod {
+ pointer-events: none;
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
}
.coordinateSystem {
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
index a0b47f13f..d3e1c6fd2 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
@@ -6,7 +6,7 @@ import QuestionMarkIcon from '@mui/icons-material/QuestionMark';
import ReplayIcon from '@mui/icons-material/Replay';
import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, FormControl, FormControlLabel, FormGroup, IconButton, LinearProgress, Stack } from '@mui/material';
import Typography from '@mui/material/Typography';
-import { computed, reaction } from 'mobx';
+import { computed, IReactionDisposer, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { NumListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
@@ -31,7 +31,6 @@ interface IForce {
description: string;
magnitude: number;
directionInDegrees: number;
- component: boolean;
}
interface VectorTemplate {
top: number;
@@ -76,20 +75,23 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
return FieldView.LayoutString(PhysicsSimulationBox, fieldKey);
}
+ _widthDisposer: IReactionDisposer | undefined;
+
+ // semi-Constants
+ xMin = 0;
+ yMin = 0;
+ xMax = this.props.PanelWidth() * 0.6;
+ yMax = this.props.PanelHeight();
+ color = `rgba(0,0,0,0.5)`;
+ radius = 50;
+ wallPositions: IWallProps[] = [];
+
@computed get circularMotionRadius() {
return NumCast(this.dataDoc.circularMotionRadius, 150);
}
@computed get gravity() {
return NumCast(this.dataDoc.simulation_gravity, -9.81);
}
- gravityForce = (mass: number): IForce => {
- return {
- description: 'Gravity',
- magnitude: mass * Math.abs(this.gravity),
- directionInDegrees: 270,
- component: false,
- };
- };
@computed get simulationType() {
return StrCast(this.dataDoc.simulation_type, 'Inclined Plane');
}
@@ -138,13 +140,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
}
@computed get mass1Radius() {
- return NumCast(this.dataDoc.mass1_radius, 60);
+ return NumCast(this.dataDoc.mass1_radius, 30);
}
@computed get mass1PosXStart() {
- return NumCast(this.dataDoc.mass1_positionXstart, Math.round((this.xMax * 0.5 - 200) * 10) / 10);
+ return NumCast(this.dataDoc.mass1_positionXstart);
}
@computed get mass1PosYStart() {
- return NumCast(this.dataDoc.mass1_positionYstart, this.getDisplayYPos((400 - 0.08 * this.props.PanelHeight()) * Math.tan((26 * Math.PI) / 180) + Math.sqrt(26)));
+ return NumCast(this.dataDoc.mass1_positionYstart);
}
@computed get mass1VelXStart() {
return NumCast(this.dataDoc.mass1_velocityXstart);
@@ -157,7 +159,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
return NumCast(this.dataDoc.mass2_positionXstart);
}
@computed get mass2PosYStart() {
- return NumCast(this.dataDoc.mass2_positionYstart, this.getDisplayYPos((400 - 0.08 * this.props.PanelHeight()) * Math.tan((26 * Math.PI) / 180) + Math.sqrt(26)));
+ return NumCast(this.dataDoc.mass2_positionYstart);
}
@computed get mass2VelXStart() {
return NumCast(this.dataDoc.mass2_velocityXstart);
@@ -182,18 +184,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
return StrCast(this.dataDoc.questionPartTwo);
}
- // Constants
- xMin = 0;
- yMin = 0;
- xMax = this.props.PanelWidth() * 0.6;
- yMax = this.props.PanelHeight();
- color = `rgba(0,0,0,0.5)`;
- radius = 50;
- wallPositions: IWallProps[] = [];
+ componentWillUnmount() {
+ this._widthDisposer?.();
+ }
componentDidMount() {
// Setup and update simulation
- reaction(() => [this.props.PanelWidth(), this.props.PanelHeight()], this.setupSimulation, { fireImmediately: true });
+ this._widthDisposer = reaction(() => [this.props.PanelWidth(), this.props.PanelHeight()], this.setupSimulation, { fireImmediately: true });
// Create walls
this.wallPositions = [
@@ -212,6 +209,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
}
}
+ gravityForce = (mass: number): IForce => ({
+ description: 'Gravity',
+ magnitude: mass * Math.abs(this.gravity),
+ directionInDegrees: 270,
+ });
+
setupSimulation = () => {
const simulationType = this.simulationType;
const mode = this.simulationMode;
@@ -228,10 +231,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
switch (simulationType) {
case 'One Weight':
this.dataDoc.simulation_showComponentForces = false;
- this.dataDoc.mass1_positionYstart = this.yMin + 0.08 * this.props.PanelHeight();
- this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight();
- this.dataDoc.mass1_positionY = this.getDisplayYPos(this.yMin + 0.08 * this.props.PanelHeight());
- this.dataDoc.mass1_positionX = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight();
+ this.dataDoc.mass1_positionYstart = this.yMin + this.mass1Radius;
+ this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius;
+ this.dataDoc.mass1_positionY = this.getDisplayYPos(this.yMin + this.mass1Radius);
+ this.dataDoc.mass1_positionX = (this.xMax + this.xMin) / 2 - this.mass1Radius;
this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]);
this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1)]);
this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset;
@@ -256,10 +259,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
switch (simulationType) {
case 'One Weight' : break;// TODO - one weight review problems
case 'Spring': this.setupSpring(); break; // TODO - spring review problems
- case 'Inclined Plane':
- this.dataDoc.mass1_forcesUpdated = '';
- this.dataDoc.mass1_forcesStart = '';
- break;
+ case 'Inclined Plane': this.dataDoc.mass1_forcesUpdated = this.dataDoc.mass1_forcesStart = ''; break;
case 'Pendulum': this.setupPendulum(); break; // TODO - pendulum review problems
case 'Circular Motion': this.setupCircular(0); break; // TODO - circular motion review problems
case 'Pulley': this.setupPulley(); break; // TODO - pulley tutorial review problems
@@ -283,7 +283,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
case 'One Weight':
this.dataDoc.simulation_showForces = true;
this.dataDoc.mass1_positionYstart = this.yMax - 100;
- this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight();
+ this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius;
this.dataDoc.tutorial = JSON.stringify(tutorials.freeWeight);
this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.freeWeight.steps[0].forces);
this.dataDoc.simulation_showForceMagnitudes = tutorials.freeWeight.steps[0].showMagnitude;
@@ -292,7 +292,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.simulation_showForces = true;
this.setupSpring();
this.dataDoc.mass1_positionYstart = this.yMin + 200 + 19.62;
- this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight();
+ this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius;
this.dataDoc.tutorial = JSON.stringify(tutorials.spring);
this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.spring.steps[0].forces);
this.dataDoc.simulation_showForceMagnitudes = tutorials.spring.steps[0].showMagnitude;
@@ -338,12 +338,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
};
// Helper function to go between display and real values
- getDisplayYPos = (yPos: number) => {
- return this.yMax - yPos - 2 * (0.08 * this.props.PanelHeight()) + 5;
- };
- getYPosFromDisplay = (yDisplay: number) => {
- return this.yMax - yDisplay - 2 * (0.08 * this.props.PanelHeight()) + 5;
- };
+ getDisplayYPos = (yPos: number) => this.yMax - yPos - 2 * this.mass1Radius + 5;
+ getYPosFromDisplay = (yDisplay: number) => this.yMax - yDisplay - 2 * this.mass1Radius + 5;
// Update forces when coefficient of static friction changes in freeform mode
updateForcesWithFriction = (coefficient: number, width = this.wedgeWidth, height = this.wedgeHeight) => {
@@ -351,13 +347,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
description: 'Normal Force',
magnitude: Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1,
directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI,
- component: false,
};
let frictionForce: IForce = {
description: 'Static Friction Force',
magnitude: coefficient * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1,
directionInDegrees: 180 - (Math.atan(height / width) * 180) / Math.PI,
- component: false,
};
// reduce magnitude or friction force if necessary such that block cannot slide up plane
let yForce = -Math.abs(this.gravity) * this.mass1;
@@ -366,35 +360,27 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
if (yForce > 0) {
frictionForce.magnitude = (-normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180) + Math.abs(this.gravity) * this.mass1) / Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
}
- const frictionForceComponent: IForce = {
- description: 'Static Friction Force',
- magnitude: coefficient * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)),
- directionInDegrees: 180 - (Math.atan(height / width) * 180) / Math.PI,
- component: true,
- };
+
const normalForceComponent: IForce = {
description: 'Normal Force',
- magnitude: Math.abs(this.gravity) * Math.cos(Math.atan(height / width)),
+ magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)),
directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI,
- component: true,
};
const gravityParallel: IForce = {
description: 'Gravity Parallel Component',
magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin(Math.PI / 2 - Math.atan(height / width)),
directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI + 180,
- component: true,
};
const gravityPerpendicular: IForce = {
description: 'Gravity Perpendicular Component',
magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(Math.PI / 2 - Math.atan(height / width)),
directionInDegrees: 360 - (Math.atan(height / width) * 180) / Math.PI,
- component: true,
};
const gravityForce = this.gravityForce(this.mass1);
if (coefficient != 0) {
this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce, normalForce, frictionForce]);
this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce, normalForce, frictionForce]);
- this.dataDoc.mass1_componentForces = JSON.stringify([frictionForceComponent, normalForceComponent, gravityParallel, gravityPerpendicular]);
+ this.dataDoc.mass1_componentForces = JSON.stringify([frictionForce, normalForceComponent, gravityParallel, gravityPerpendicular]);
} else {
this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce, normalForce]);
this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce, normalForce]);
@@ -549,24 +535,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
}
}
if (showAlert) {
- if (!error) {
- this.dataDoc.simulation_paused = false;
- setTimeout(() => {
- this.dataDoc.simulation_paused = true;
- }, 3000);
- } else {
- this.dataDoc.simulation_paused = false;
- setTimeout(() => {
- this.dataDoc.simulation_paused = true;
- }, 3000);
- }
+ this.dataDoc.simulation_paused = false;
+ setTimeout(() => (this.dataDoc.simulation_paused = true), 3000);
}
if (this.selectedQuestion.goal == 'noMovement') {
- if (!error) {
- this.dataDoc.noMovement = true;
- } else {
- this.dataDoc.roMovement = false;
- }
+ this.dataDoc.noMovement = !error;
}
};
@@ -590,11 +563,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
let question: QuestionTemplate = questions.inclinePlane[0];
if (this.simulationType === 'Inclined Plane') {
- if (this.dataDoc.questionNumber === questions.inclinePlane.length - 1) {
- this.dataDoc.questionNumber = 0;
- } else {
- this.dataDoc.questionNumber = NumCast(this.dataDoc.questionNumber) + 1;
- }
+ this.dataDoc.questionNumber = (NumCast(this.dataDoc.questionNumber) + 1) % questions.inclinePlane.length;
question = questions.inclinePlane[NumCast(this.dataDoc.questionNumber)];
let coefficient = 0;
@@ -639,15 +608,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.simulation_showComponentForces = false;
this.dataDoc.mass1_velocityYstart = 0;
this.dataDoc.mass1_velocityXstart = value;
- let xPos = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight();
- let yPos = (this.yMax + this.yMin) / 2 + this.circularMotionRadius - 0.08 * this.props.PanelHeight();
+ let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
+ let yPos = (this.yMax + this.yMin) / 2 + this.circularMotionRadius - this.mass1Radius;
this.dataDoc.mass1_positionYstart = yPos;
this.dataDoc.mass1_positionXstart = xPos;
const tensionForce: IForce = {
description: 'Centripetal Force',
magnitude: (this.dataDoc.mass1_velocityXstart ** 2 * this.mass1) / this.circularMotionRadius,
directionInDegrees: 90,
- component: false,
};
this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce]);
this.dataDoc.mass1_forcesStart = JSON.stringify([tensionForce]);
@@ -666,38 +634,27 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
const angle = 30;
const x = length * Math.cos(((90 - angle) * Math.PI) / 180);
const y = length * Math.sin(((90 - angle) * Math.PI) / 180);
- const xPos = this.xMax / 2 - x - 0.08 * this.props.PanelHeight();
- const yPos = y - 0.08 * this.props.PanelHeight() - 5;
+ const xPos = this.xMax / 2 - x - this.mass1Radius;
+ const yPos = y - this.mass1Radius - 5;
this.dataDoc.mass1_positionXstart = xPos;
this.dataDoc.mass1_positionYstart = yPos;
- const mag = this.mass1 * Math.abs(this.gravity) * Math.sin((60 * Math.PI) / 180);
const forceOfTension: IForce = {
description: 'Tension',
- magnitude: mag,
- directionInDegrees: 90 - angle,
- component: false,
- };
-
- const tensionComponent: IForce = {
- description: 'Tension',
- magnitude: mag,
+ magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin((60 * Math.PI) / 180),
directionInDegrees: 90 - angle,
- component: true,
};
const gravityParallel: IForce = {
description: 'Gravity Parallel Component',
magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin(((90 - angle) * Math.PI) / 180),
directionInDegrees: -angle - 90,
- component: true,
};
const gravityPerpendicular: IForce = {
description: 'Gravity Perpendicular Component',
magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(((90 - angle) * Math.PI) / 180),
directionInDegrees: -angle,
- component: true,
};
- this.dataDoc.mass1_componentForces = JSON.stringify([tensionComponent, gravityParallel, gravityPerpendicular]);
+ this.dataDoc.mass1_componentForces = JSON.stringify([forceOfTension, gravityParallel, gravityPerpendicular]);
this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]);
this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]);
this.dataDoc.pendulum_angle = this.dataDoc.pendulum_angleStart = 30;
@@ -709,7 +666,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.simulation_showComponentForces = false;
this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]);
this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1)]);
- this.dataDoc.mass1_positionXstart = this.xMax / 2 - 0.08 * this.props.PanelHeight();
+ this.dataDoc.mass1_positionXstart = this.xMax / 2 - this.mass1Radius;
this.dataDoc.mass1_positionYstart = 200;
this.dataDoc.spring_constant = 0.5;
this.dataDoc.spring_lengthRest = 200;
@@ -719,7 +676,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
// Default setup for suspension simulation
setupSuspension = () => {
- let xPos = (this.xMax + this.xMin) / 2 - 0.08 * this.props.PanelHeight();
+ let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
let yPos = this.yMin + 200;
this.dataDoc.mass1_positionYstart = yPos;
this.dataDoc.mass1_positionXstart = xPos;
@@ -730,13 +687,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
description: 'Tension',
magnitude: tensionMag,
directionInDegrees: 45,
- component: false,
};
const tensionForce2: IForce = {
description: 'Tension',
magnitude: tensionMag,
directionInDegrees: 135,
- component: false,
};
const gravity = this.gravityForce(this.mass1);
this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
@@ -757,7 +712,6 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
description: 'Tension',
magnitude: this.mass1 * a + this.mass1 * Math.abs(this.gravity),
directionInDegrees: 90,
- component: false,
};
this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce1, tensionForce1]);
this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce1, tensionForce1]);
@@ -767,7 +721,6 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
description: 'Tension',
magnitude: -this.mass2 * a + this.mass2 * Math.abs(this.gravity),
directionInDegrees: 90,
- component: false,
};
this.dataDoc.mass2_positionYstart = (this.yMax + this.yMin) / 2;
this.dataDoc.mass2_positionXstart = (this.xMin + this.xMax) / 2 + 5;
@@ -789,19 +742,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
description: 'Gravity',
magnitude: NumCast(this.dataDoc.review_GravityMagnitude),
directionInDegrees: NumCast(this.dataDoc.review_GravityAngle),
- component: false,
};
const normalForceReview: IForce = {
description: 'Normal Force',
magnitude: NumCast(this.dataDoc.review_NormalMagnitude),
directionInDegrees: NumCast(this.dataDoc.review_NormalAngle),
- component: false,
};
const staticFrictionForceReview: IForce = {
description: 'Static Friction Force',
magnitude: NumCast(this.dataDoc.review_StaticMagnitude),
directionInDegrees: NumCast(this.dataDoc.review_StaticAngle),
- component: false,
};
this.dataDoc.mass1_forcesStart = JSON.stringify([forceOfGravityReview, normalForceReview, staticFrictionForceReview]);
this.dataDoc.mass1_forcesUpdated = JSON.stringify([forceOfGravityReview, normalForceReview, staticFrictionForceReview]);
@@ -1378,7 +1328,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
/>
{(this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') && (
<FormControlLabel
- control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showForces)} onChange={() => (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />}
+ control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showComponentForces)} onChange={() => (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />}
label="Show component force vectors"
labelPlacement="start"
/>
@@ -1616,26 +1566,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
description: 'Tension',
magnitude: mag,
directionInDegrees: 90 - value,
- component: false,
- };
-
- const tensionComponent: IForce = {
- description: 'Tension',
- magnitude: mag,
- directionInDegrees: 90 - value,
- component: true,
};
const gravityParallel: IForce = {
description: 'Gravity Parallel Component',
magnitude: Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180),
directionInDegrees: 270 - value,
- component: true,
};
const gravityPerpendicular: IForce = {
description: 'Gravity Perpendicular Component',
magnitude: Math.abs(this.gravity) * Math.sin((value * Math.PI) / 180),
directionInDegrees: -value,
- component: true,
};
const length = this.pendulumLength;
@@ -1648,7 +1588,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]);
this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]);
- this.dataDoc.mass1_componentForces = JSON.stringify([tensionComponent, gravityParallel, gravityPerpendicular]);
+ this.dataDoc.mass1_componentForces = JSON.stringify([forceOfTension, gravityParallel, gravityPerpendicular]);
this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset;
}
}}
@@ -1736,13 +1676,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
description: 'Tension',
magnitude: tensionMag1,
directionInDegrees: dir1T,
- component: false,
};
const tensionForce2: IForce = {
description: 'Tension',
magnitude: tensionMag2,
directionInDegrees: dir2T,
- component: false,
};
const gravity = this.gravityForce(this.mass1);
this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
@@ -1787,13 +1725,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
description: 'Tension',
magnitude: tensionMag1,
directionInDegrees: dir1T,
- component: false,
};
const tensionForce2: IForce = {
description: 'Tension',
magnitude: tensionMag2,
directionInDegrees: dir2T,
- component: false,
};
const gravity = this.gravityForce(this.mass1);
this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
index d062cc8c5..98b0e3f04 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
@@ -14,7 +14,6 @@ interface IForce {
description: string;
magnitude: number;
directionInDegrees: number;
- component: boolean;
}
export interface IWeightProps {
pause: () => void;
@@ -250,13 +249,11 @@ export default class Weight extends React.Component<IWeightProps, IState> {
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,
};
const 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
// prettier-ignore
@@ -271,31 +268,26 @@ export default class Weight extends React.Component<IWeightProps, IState> {
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: 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,
};
const kineticFriction = this.props.coefficientOfKineticFriction != 0;
this.props.setForcesUpdated([gravityForce, normalForce, ...(kineticFriction ? [frictionForce] : [])]);
@@ -394,7 +386,6 @@ export default class Weight extends React.Component<IWeightProps, IState> {
description: 'Centripetal Force',
magnitude: (this.props.startVelX ** 2 * this.props.mass) / this.props.circularMotionRadius,
directionInDegrees: (Math.atan2(deltaY, deltaX) * 180) / Math.PI,
- component: false,
},
];
};
@@ -408,13 +399,11 @@ export default class Weight extends React.Component<IWeightProps, IState> {
description: 'Gravity',
magnitude: Math.abs(this.props.gravity) * this.props.mass,
directionInDegrees: 270,
- component: false,
},
{
description: 'Spring Force',
magnitude: this.props.springConstant * (yPos - this.props.springRestLength) * (yPosPlus ? 1 : yPosMinus ? -1 : 0),
directionInDegrees: yPosPlus ? 90 : 270,
- component: false,
},
];
};
@@ -442,13 +431,11 @@ export default class Weight extends React.Component<IWeightProps, IState> {
description: 'Gravity',
magnitude: Math.abs(this.props.gravity) * this.props.mass,
directionInDegrees: 270,
- component: false,
},
{
description: 'Tension',
magnitude: mag,
directionInDegrees: angle,
- component: false,
},
];
};
@@ -488,7 +475,6 @@ export default class Weight extends React.Component<IWeightProps, IState> {
description: 'Gravity',
magnitude: Math.abs(this.props.gravity) * this.props.mass,
directionInDegrees: 270,
- component: false,
};
if (maxY > groundY) {
this.setState({ yPosition: groundY - 2 * this.props.radius - 0.01 });
@@ -500,11 +486,10 @@ export default class Weight extends React.Component<IWeightProps, IState> {
description: 'Normal force',
magnitude: gravity.magnitude,
directionInDegrees: -gravity.directionInDegrees,
- component: false,
};
this.props.setForcesUpdated([gravity, normalForce]);
if (this.props.simulationType === 'Inclined Plane') {
- this.props.setComponentForces([gravity, { ...normalForce, component: true }]);
+ this.props.setComponentForces([gravity, normalForce]);
}
}
collision = true;
@@ -641,19 +626,16 @@ export default class Weight extends React.Component<IWeightProps, IState> {
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));
@@ -663,6 +645,57 @@ export default class Weight extends React.Component<IWeightProps, IState> {
}
};
+ renderForce = (force: IForce, index: number, asComponent: boolean) => {
+ if (force.magnitude < this.epsilon) return;
+
+ const angle = (force.directionInDegrees * Math.PI) / 180;
+ const arrowStartY = this.state.yPosition + this.props.radius - this.props.radius * Math.sin(angle);
+ const arrowStartX = this.state.xPosition + this.props.radius + this.props.radius * Math.cos(angle);
+ const arrowEndY = arrowStartY - Math.abs(force.magnitude) * Math.sin(angle) - this.props.radius * Math.sin(angle);
+ const arrowEndX = arrowStartX + Math.abs(force.magnitude) * Math.cos(angle) + this.props.radius * Math.cos(angle);
+
+ const color = '#0d0d0d';
+
+ let labelTop = arrowEndY + (force.directionInDegrees >= 0 && force.directionInDegrees < 180 ? 40 : -40);
+ let labelLeft = arrowEndX + (force.directionInDegrees > 90 && force.directionInDegrees < 270 ? -120 : 30);
+
+ labelTop = Math.max(Math.min(labelTop, this.props.yMax + 50), this.props.yMin);
+ labelLeft = Math.max(Math.min(labelLeft, this.props.xMax - 60), this.props.xMin);
+
+ return (
+ <div key={index} style={{ zIndex: 6, position: 'absolute' }}>
+ <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.panelHeight}>
+ <defs>
+ <marker id="forceArrow" markerWidth="4" markerHeight="4" refX="0" refY="2" orient="auto" markerUnits="strokeWidth">
+ <path d="M0,0 L0,4 L4,2 z" fill={color} />
+ </marker>
+ </defs>
+ <line strokeDasharray={asComponent ? '10,10' : undefined} 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,
+ }}>
+ <p>{force.description || 'Force'}</p>
+ {this.props.showForceMagnitudes && <p>{Math.round(100 * force.magnitude) / 100} N</p>}
+ </div>
+ </div>
+ );
+ };
+
// Render weight, spring, rod(s), vectors
render() {
return (
@@ -757,19 +790,16 @@ export default class Weight extends React.Component<IWeightProps, IState> {
description: 'Tension',
magnitude: tensionMag1,
directionInDegrees: (dir1T * 180) / Math.PI,
- component: false,
};
const tensionForce2: IForce = {
description: 'Tension',
magnitude: tensionMag2,
directionInDegrees: (dir2T * 180) / Math.PI,
- component: false,
};
const grav: IForce = {
description: 'Gravity',
magnitude: this.props.mass * Math.abs(this.props.gravity),
directionInDegrees: 270,
- component: false,
};
this.props.setForcesUpdated([tensionForce1, tensionForce2, grav]);
}
@@ -802,14 +832,7 @@ export default class Weight extends React.Component<IWeightProps, IState> {
)}
{this.props.simulationType == 'Pulley' && (
- <div
- className="rod"
- style={{
- pointerEvents: 'none',
- position: 'absolute',
- left: 0,
- top: 0,
- }}>
+ <div className="rod">
<svg width={this.panelWidth} height={this.panelHeight}>
<line //
x1={this.state.xPosition + this.props.radius}
@@ -837,14 +860,7 @@ export default class Weight extends React.Component<IWeightProps, IState> {
</div>
)}
{this.props.simulationType == 'Suspension' && (
- <div
- className="rod"
- style={{
- pointerEvents: 'none',
- position: 'absolute',
- left: 0,
- top: 0,
- }}>
+ <div className="rod">
<svg width={this.panelWidth} height={this.panelHeight}>
<line
x1={this.state.xPosition + this.props.radius}
@@ -867,14 +883,7 @@ export default class Weight extends React.Component<IWeightProps, IState> {
) / 100}
°
</p>
- <div
- className="rod"
- style={{
- pointerEvents: 'none',
- position: 'absolute',
- left: 0,
- top: 0,
- }}>
+ <div className="rod">
<svg width={this.props.panelWidth() + 'px'} height={this.panelHeight}>
<line
x1={this.state.xPosition + this.props.radius}
@@ -902,14 +911,7 @@ export default class Weight extends React.Component<IWeightProps, IState> {
</div>
)}
{this.props.simulationType == 'Circular Motion' && (
- <div
- className="rod"
- style={{
- pointerEvents: 'none',
- position: 'absolute',
- left: 0,
- top: 0,
- }}>
+ <div className="rod">
<svg width={this.panelWidth} height={this.panelHeight}>
<line
x1={this.state.xPosition + this.props.radius}
@@ -923,14 +925,7 @@ export default class Weight extends React.Component<IWeightProps, IState> {
</div>
)}
{this.props.simulationType == 'Pendulum' && (
- <div
- className="rod"
- style={{
- pointerEvents: 'none',
- position: 'absolute',
- left: 0,
- top: 0,
- }}>
+ <div className="rod">
<svg width={this.panelWidth} height={this.panelHeight}>
<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>
@@ -1056,136 +1051,8 @@ export default class Weight extends React.Component<IWeightProps, IState> {
</div>
</div>
)}
- {!this.state.dragging &&
- this.props.showComponentForces &&
- this.props.componentForces().map((force, index) => {
- if (force.magnitude < this.epsilon) {
- return;
- }
- const arrowStartY = this.state.yPosition + this.props.radius;
- const arrowStartX = this.state.xPosition + this.props.radius;
- const arrowEndY = arrowStartY - Math.abs(force.magnitude) * 20 * Math.sin((force.directionInDegrees * Math.PI) / 180);
- const arrowEndX = arrowStartX + Math.abs(force.magnitude) * 20 * Math.cos((force.directionInDegrees * Math.PI) / 180);
-
- const 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.panelHeight}>
- <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.forcesUpdated().map((force, index) => {
- if (force.magnitude < this.epsilon) {
- return;
- }
- const arrowStartY = this.state.yPosition + this.props.radius;
- const arrowStartX = this.state.xPosition + this.props.radius;
- const arrowEndY = arrowStartY - Math.abs(force.magnitude) * 20 * Math.sin((force.directionInDegrees * Math.PI) / 180);
- const arrowEndX = arrowStartX + Math.abs(force.magnitude) * 20 * Math.cos((force.directionInDegrees * Math.PI) / 180);
-
- const 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.panelHeight}>
- <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.showComponentForces && this.props.componentForces().map((force, index) => this.renderForce(force, index, true))}
+ {!this.state.dragging && this.props.showForces && this.props.forcesUpdated().map((force, index) => this.renderForce(force, index, false))}
</div>
);
}