diff options
author | bobzel <zzzman@gmail.com> | 2023-05-25 10:47:14 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2023-05-25 10:47:14 -0400 |
commit | 55f3c138354101664df80b1ce48416de6adf2da0 (patch) | |
tree | 639e30e9e11c9ffd2be86dc30f8be30caa7f9e4a | |
parent | 9e1c341955fb016f4a62339e4f11ac42d9572e95 (diff) |
physics layout fixes for window resizing and for resetting walls properly
3 files changed, 59 insertions, 62 deletions
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss index 5f142df19..ac2c611c7 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss @@ -15,7 +15,7 @@ left: 60%; padding: 1em; height: 100%; - overflow: auto; + transform-origin: top left; .mechanicsSimulationControls { display: flex; diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx index f4acba2a6..be8dbbd40 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, IReactionDisposer, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { NumListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; @@ -76,6 +76,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP } _widthDisposer: IReactionDisposer | undefined; + @observable _simReset = 0; // semi-Constants xMin = 0; @@ -215,6 +216,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP directionInDegrees: 270, }); + @action setupSimulation = () => { const simulationType = this.simulationType; const mode = this.simulationMode; @@ -237,8 +239,6 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP 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; - ``; break; case 'Inclined Plane': this.setupInclinedPlane(); break; case 'Pendulum': this.setupPendulum(); break; @@ -247,7 +247,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP case 'Pulley': this.setupPulley(); break; case 'Suspension': this.setupSuspension();break; } - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; + this._simReset++; } else if (mode == 'Review') { this.dataDoc.simulation_showComponentForces = false; this.dataDoc.simulation_showForceMagnitudes = true; @@ -333,7 +333,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.simulation_showForceMagnitudes = tutorials.suspension.steps[0].showMagnitude; break; } - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; + this._simReset++; } }; @@ -604,6 +604,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP }; // Default setup for uniform circular motion simulation + @action setupCircular = (value: number) => { this.dataDoc.simulation_showComponentForces = false; this.dataDoc.mass1_velocityYstart = 0; @@ -619,7 +620,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP }; this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce]); this.dataDoc.mass1_forcesStart = JSON.stringify([tensionForce]); - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; + this._simReset++; }; setupInclinedPlane = () => { @@ -662,6 +663,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP }; // Default setup for spring simulation + @action setupSpring = () => { this.dataDoc.simulation_showComponentForces = false; this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]); @@ -671,10 +673,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.spring_constant = 0.5; this.dataDoc.spring_lengthRest = 200; this.dataDoc.spring_lengthStart = 200; - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; + this._simReset++; }; // Default setup for suspension simulation + @action setupSuspension = () => { let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius; let yPos = this.yMin + 200; @@ -696,10 +699,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP const gravity = this.gravityForce(this.mass1); this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]); this.dataDoc.mass1_forcesStart = JSON.stringify([tensionForce1, tensionForce2, gravity]); - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; + this._simReset++; }; // Default setup for pulley simulation + @action setupPulley = () => { this.dataDoc.simulation_showComponentForces = false; this.dataDoc.mass1_positionYstart = (this.yMax + this.yMin) / 2; @@ -728,8 +732,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.mass2_positionX = (this.xMin + this.xMax) / 2 + 5; this.dataDoc.mass2_forcesUpdated = JSON.stringify([gravityForce2, tensionForce2]); this.dataDoc.mass2_forcesStart = JSON.stringify([gravityForce2, tensionForce2]); - - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; + this._simReset++; }; public static parseJSON(json: string) { @@ -799,12 +802,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP setSpringLength = (length: number) => { this.dataDoc.spring_lengthStart = length; }; + resetRequest = () => this._simReset; render() { const commonWeightProps = { pause: this.pause, paused: BoolCast(this.dataDoc.simulation_paused), panelWidth: this.props.PanelWidth, panelHeight: this.props.PanelHeight, + resetRequest: this.resetRequest, xMax: this.xMax, xMin: this.xMin, yMax: this.yMax, @@ -830,7 +835,6 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP startPendulumAngle: this.pendulumAngleStart, startPendulumLength: this.pendulumLengthStart, radius: this.mass1Radius, - reset: BoolCast(this.dataDoc.simulation_reset), simulationSpeed: NumCast(this.dataDoc.simulation_speed, 2), showAcceleration: BoolCast(this.dataDoc.simulation_showAcceleration), showForceMagnitudes: BoolCast(this.dataDoc.simulation_showForceMagnitudes), @@ -916,7 +920,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </div> </div> </div> - <div className="mechanicsSimulationEquationContainer"> + <div className="mechanicsSimulationEquationContainer" style={{ overflow: 'auto', height: `${(1000 / this.props.PanelWidth()) * 100}%`, transform: `scale(${this.props.PanelWidth() / 1000})` }}> <div className="mechanicsSimulationControls"> <Stack direction="row" spacing={1}> {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && ( @@ -930,7 +934,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </IconButton> )} {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && ( - <IconButton onClick={() => (this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset)}> + <IconButton onClick={action(() => this._simReset++)}> <ReplayIcon /> </IconButton> )} @@ -1291,11 +1295,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP </p> <div style={{ display: 'flex', flexDirection: 'column' }}> <Button - onClick={() => { - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; + onClick={action(() => { + this._simReset++; this.checkAnswers(); this.dataDoc.simulation_showIcon = true; - }} + })} variant="outlined"> <p>Submit</p> </Button> @@ -1427,7 +1431,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP unit={'N/m'} upperBound={500} value={this.springConstant} - effect={(val: number) => (this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset)} + effect={action(() => this._simReset++)} radianEquivalent={false} mode={'Freeform'} labelWidth={'7em'} @@ -1441,7 +1445,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP unit="" upperBound={500} value={this.springLengthRest} - effect={(val: number) => (this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset)} + effect={action(() => this._simReset++)} radianEquivalent={false} mode="Freeform" labelWidth={'7em'} @@ -1455,11 +1459,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP unit="" upperBound={this.springLengthRest} value={this.springLengthStart - this.springLengthRest} - effect={(val: number) => { + effect={action((val: number) => { this.dataDoc.mass1_positionYstart = this.springLengthRest + val; this.dataDoc.spring_lengthStart = this.springLengthRest + val; - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; - }} + this._simReset++; + })} radianEquivalent={false} mode="Freeform" labelWidth={'7em'} @@ -1477,10 +1481,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP unit={'°'} upperBound={49} value={this.wedgeAngle} - effect={(val: number) => { + effect={action((val: number) => { this.changeWedgeBasedOnNewAngle(val); - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; - }} + this._simReset++; + })} radianEquivalent={true} mode={'Freeform'} labelWidth={'2em'} @@ -1498,13 +1502,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP unit={''} upperBound={1} value={NumCast(this.dataDoc.coefficientOfStaticFriction) ?? 0} - effect={(val: number) => { + effect={action((val: number) => { this.updateForcesWithFriction(val); if (val < NumCast(this.dataDoc.coefficientOfKineticFriction)) { this.dataDoc.soefficientOfKineticFriction = val; } - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; - }} + this._simReset++; + })} mode={'Freeform'} labelWidth={'2em'} /> @@ -1521,9 +1525,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP unit={''} upperBound={NumCast(this.dataDoc.coefficientOfStaticFriction)} value={NumCast(this.dataDoc.coefficientOfKineticFriction) ?? 0} - effect={(val: number) => { - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; - }} + effect={action(() => this._simReset++)} mode={'Freeform'} labelWidth={'2em'} /> @@ -1556,7 +1558,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP unit={'°'} upperBound={59} value={NumCast(this.dataDoc.pendulum_angle, 30)} - effect={value => { + effect={action(value => { this.dataDoc.pendulum_angleStart = value; this.dataDoc.pendulum_lengthStart = this.dataDoc.pendulum_length; if (this.simulationType == 'Pendulum') { @@ -1589,9 +1591,9 @@ 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([forceOfTension, gravityParallel, gravityPerpendicular]); - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; + this._simReset++; } - }} + })} radianEquivalent={true} mode="Freeform" labelWidth="5em" @@ -1605,13 +1607,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP unit="m" upperBound={400} value={Math.round(this.pendulumLength)} - effect={value => { + effect={action(value => { if (this.simulationType == 'Pendulum') { this.dataDoc.pendulum_angleStart = this.pendulumAngle; this.dataDoc.pendulum_lengthStart = value; - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; + this._simReset++; } - }} + })} radianEquivalent={false} mode="Freeform" labelWidth="5em" @@ -1768,10 +1770,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP unit={'m/s'} upperBound={50} value={NumCast(this.dataDoc.mass1_velocityX)} - effect={value => { + effect={action(value => { this.dataDoc.mass1_velocityXstart = value; - this.dataDoc.simulation_reset = !this.dataDoc.simulation_reset; - }} + this._simReset++; + })} small={true} mode="Freeform" /> diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx index f533df109..2165c8ba9 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx @@ -1,8 +1,7 @@ -import { computed } from 'mobx'; +import { computed, IReactionDisposer, reaction } from 'mobx'; import { observer } from 'mobx-react'; import './PhysicsSimulationBox.scss'; import React = require('react'); -import _ = require('lodash'); interface IWallProps { length: number; @@ -19,6 +18,7 @@ export interface IWeightProps { pause: () => void; panelWidth: () => number; panelHeight: () => number; + resetRequest: () => number; circularMotionRadius: number; coefficientOfKineticFriction: number; color: string; @@ -35,7 +35,6 @@ export interface IWeightProps { pendulumAngle: number; pendulumLength: number; radius: number; - reset: boolean; showAcceleration: boolean; showComponentForces: boolean; showForceMagnitudes: boolean; @@ -83,7 +82,6 @@ interface IState { timer: number; updatedStartPosX: any; updatedStartPosY: any; - walls: IWallProps[]; xPosition: number; xVelocity: number; yPosition: number; @@ -106,7 +104,6 @@ export default class Weight extends React.Component<IWeightProps, IState> { timer: 0, updatedStartPosX: this.props.startPosX, updatedStartPosY: this.props.startPosY, - walls: [], xPosition: this.props.startPosX, xVelocity: this.props.startVelX, yPosition: this.props.startPosY, @@ -117,12 +114,13 @@ export default class Weight extends React.Component<IWeightProps, IState> { } _timer: NodeJS.Timeout | undefined; + _resetDisposer: IReactionDisposer | undefined; componentWillUnmount() { this._timer && clearTimeout(this._timer); + this._resetDisposer?.(); } componentWillUpdate(nextProps: Readonly<IWeightProps>, nextState: Readonly<IState>, nextContext: any): void { - if (nextProps.simulationType !== this.props.simulationType) setTimeout(() => this.setState({ timer: this.state.timer + 1 })); if (nextProps.paused) { this._timer && clearTimeout(this._timer); this._timer = undefined; @@ -142,6 +140,10 @@ export default class Weight extends React.Component<IWeightProps, IState> { @computed get panelWidth() { return Math.max(1000, this.props.panelWidth()) + 'px'; } + + @computed get walls() { + return ['One Weight', 'Inclined Plane'].includes(this.props.simulationType) ? this.props.wallPositions : []; + } epsilon = 0.0001; labelBackgroundColor = `rgba(255,255,255,0.5)`; @@ -179,7 +181,9 @@ export default class Weight extends React.Component<IWeightProps, IState> { this.props.setAcceleration(xAccel, yAccel); this.setState({ xAccel, yAccel }); }; - + componentDidMount() { + this._resetDisposer = reaction(() => this.props.resetRequest(), this.resetEverything); + } componentDidUpdate(prevProps: Readonly<IWeightProps>, prevState: Readonly<IState>, snapshot?: any): void { if (prevProps.simulationType != this.props.simulationType) { this.setState({ xVelocity: this.props.startVelX, yVelocity: this.props.startVelY }); @@ -251,11 +255,6 @@ export default class Weight extends React.Component<IWeightProps, IState> { this.setDisplayValues(); } - // Reset everything on reset button click - if (prevProps.reset != this.props.reset) { - this.resetEverything(); - } - // Convert from static to kinetic friction if/when weight slips on inclined plane if (prevState.xVelocity != this.state.xVelocity) { if (this.props.simulationType == 'Inclined Plane' && Math.abs(this.state.xVelocity) > 0.1 && this.props.simulationMode != 'Review' && !this.state.kineticFriction) { @@ -300,11 +299,6 @@ export default class Weight extends React.Component<IWeightProps, IState> { } } - // Add/remove walls when simulation type changes - if (prevProps.simulationType != this.props.simulationType) { - this.setState({ walls: ['One Weight', 'Inclined Plane'].includes(this.props.simulationType) ? this.props.wallPositions : [] }); - } - // Update x position when start pos x changes if (prevProps.startPosX != this.props.startPosX) { if (this.props.paused && !isNaN(this.props.startPosX)) { @@ -322,7 +316,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { } // Update wedge coordinates - if (!this.state.coordinates || prevProps.wedgeWidth != this.props.wedgeWidth || prevProps.wedgeHeight != this.props.wedgeHeight) { + if (!this.state.coordinates || this.props.yMax !== prevProps.yMax || prevProps.wedgeWidth != this.props.wedgeWidth || prevProps.wedgeHeight != this.props.wedgeHeight) { const left = this.props.xMax * 0.25; const coordinatePair1 = Math.round(left) + ',' + this.props.yMax + ' '; const coordinatePair2 = Math.round(left + this.props.wedgeWidth) + ',' + this.props.yMax + ' '; @@ -365,6 +359,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { this.props.setPosition(this.state.updatedStartPosX, this.state.updatedStartPosY); this.props.setVelocity(this.props.startVelX, this.props.startVelY); this.props.setAcceleration(0, 0); + setTimeout(() => this.setState({ timer: this.state.timer + 1 })); }; // Compute x acceleration from forces, F=ma @@ -438,7 +433,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { checkForCollisionsWithWall = () => { let collision = false; if (this.state.xVelocity !== 0) { - this.state.walls + this.walls .filter(wall => wall.angleInDegrees === 90) .forEach(wall => { const wallX = (wall.xPos / 100) * this.props.panelWidth(); @@ -462,7 +457,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { const minY = this.state.yPosition; const maxY = this.state.yPosition + 2 * this.props.radius; if (this.state.yVelocity > 0) { - this.state.walls.forEach(wall => { + this.walls.forEach(wall => { if (wall.angleInDegrees == 0 && wall.yPos > 0.4) { const groundY = (wall.yPos / 100) * this.props.panelHeight(); const gravity = this.gravityForce(); @@ -488,7 +483,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { }); } if (this.state.yVelocity < 0) { - this.state.walls.forEach(wall => { + this.walls.forEach(wall => { if (wall.angleInDegrees == 0 && wall.yPos < 0.4) { const groundY = (wall.yPos / 100) * this.props.panelHeight(); if (minY < groundY) { @@ -708,7 +703,7 @@ export default class Weight extends React.Component<IWeightProps, IState> { top: this.state.yPosition + this.props.radius + 2 * (magY / mag) * this.props.radius + magY + 'px', lineHeight: 1, }}> - {/* <p>{label}</p> */} + <p style={{ background: 'white' }}>{label}</p> </div> </div> ); |