From 4c96bbd25d84964811838d005ff4e40487e1ec41 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 24 May 2023 15:04:13 -0400 Subject: more phys code streamlining --- .../nodes/PhysicsBox/PhysicsSimulationBox.scss | 6 + .../nodes/PhysicsBox/PhysicsSimulationBox.tsx | 168 +++++--------- .../nodes/PhysicsBox/PhysicsSimulationWeight.tsx | 251 +++++---------------- 3 files changed, 117 insertions(+), 308 deletions(-) (limited to 'src') 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 { - 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 [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 ({ + 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 { - 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 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 { - 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 { - 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 {(this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') && ( (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />} + control={ (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />} label="Show component force vectors" labelPlacement="start" /> @@ -1616,26 +1566,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent void; @@ -250,13 +249,11 @@ export default class Weight extends React.Component { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { } }; + 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 ( +
+
+ + + + + + + + +
+
+

{force.description || 'Force'}

+ {this.props.showForceMagnitudes &&

{Math.round(100 * force.magnitude) / 100} N

} +
+
+ ); + }; + // Render weight, spring, rod(s), vectors render() { return ( @@ -757,19 +790,16 @@ export default class Weight extends React.Component { 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 { )} {this.props.simulationType == 'Pulley' && ( -
+
{
)} {this.props.simulationType == 'Suspension' && ( -
+
{ ) / 100} °

-
+
{
)} {this.props.simulationType == 'Circular Motion' && ( -
+
{
)} {this.props.simulationType == 'Pendulum' && ( -
+
@@ -1056,136 +1051,8 @@ export default class Weight extends React.Component {
)} - {!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 ( -
-
- - - - - - - {force.component == true && } - {force.component == false && } - -
-
- {force.description &&

{force.description}

} - {!force.description &&

Force

} - {this.props.showForceMagnitudes &&

{Math.round(100 * force.magnitude) / 100} N

} -
-
- ); - })} - {!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 ( -
-
- - - - - - - {force.component == true && } - {force.component == false && } - -
-
- {force.description &&

{force.description}

} - {!force.description &&

Force

} - {this.props.showForceMagnitudes &&

{Math.round(100 * force.magnitude) / 100} N

} -
-
- ); - })} + {!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))}
); } -- cgit v1.2.3-70-g09d2