physics-simulations

A collection of physics simulations
git clone https://git.ashermorgan.net/physics-simulations/
Log | Files | Refs | README

commit e1aef44d3f38eba8d8fb0912fe5cccf364498ecd
parent 9d9dee7e5f1bff45e8fb3975bb3603e2ecf35a30
Author: AsherMorgan <59518073+AsherMorgan@users.noreply.github.com>
Date:   Sun, 28 Feb 2021 11:59:40 -0800

Add circular motion simulation

Diffstat:
Aimages/pause.svg | 2++
Aimages/play.svg | 2++
Aimages/reset.svg | 2++
Mindex.html | 4++++
Asimulations/circular-motion.html | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asimulations/circular-motion.js | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asimulations/styles.css | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 287 insertions(+), 0 deletions(-)

diff --git a/images/pause.svg b/images/pause.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-pause-circle"><circle cx="12" cy="12" r="10"></circle><line x1="10" y1="15" x2="10" y2="9"></line><line x1="14" y1="15" x2="14" y2="9"></line></svg> +\ No newline at end of file diff --git a/images/play.svg b/images/play.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-play-circle"><circle cx="12" cy="12" r="10"></circle><polygon points="10 8 16 12 10 16 10 8"></polygon></svg> +\ No newline at end of file diff --git a/images/reset.svg b/images/reset.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-refresh-ccw"><polyline points="1 4 1 10 7 10"></polyline><polyline points="23 20 23 14 17 14"></polyline><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path></svg> +\ No newline at end of file diff --git a/index.html b/index.html @@ -13,5 +13,9 @@ </header> <p>A collection of physics simulations</p> + + <ul> + <li><a href="simulations/circular-motion.html">Circular Motion Simulation</a></li> + </ul> </body> </html> diff --git a/simulations/circular-motion.html b/simulations/circular-motion.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <title>Circular Motion Simulation</title> + <meta name="Description" content="Circular motion physics simulation and calculator"> + <meta name="viewport" content="width=device-width"> + <script src="https://cdn.jsdelivr.net/npm/vue@3.0.6"></script> + <link rel="stylesheet" href="styles.css"> + <script src="circular-motion.js"></script> + </head> + <body onload="createApp()"> + <div id="app"> + <header> + <h1>Circular Motion Simulation</h1> + </header> + + <noscript> + <p>This simulation requires JavaScript</p> + </noscript> + + <div id="input" hidden> + <section> + <label for="massInput"><b>Mass:</b> {{ mass.toFixed(1) }} Kg</label> + <input type="range" min="1" max="10" step="0.1" v-model.number="mass" @input="reset" :disabled="active" id="massInput"> + </section> + <section> + <label for="forceInput"><b>Centripetal Force:</b> {{ force.toFixed(1) }} N</label> + <input type="range" min="1" max="10" step="0.1" v-model.number="force" @input="reset" :disabled="active" id="forceInput"> + </section> + <section> + <label for="radiusInput"><b>Radius:</b> {{ radius.toFixed(2) }} m</label> + <input type="range" min="0.1" max="1" step="0.01" v-model.number="radius" @input="reset" :disabled="active" id="radiusInput"> + </section> + <button @click="toggle" class="icon" :title="active ? 'Pause' : (time === 0 ? 'Start' : 'Resume')"> + <img alt="" :src="active ? '../images/pause.svg' : '../images/play.svg'"> + </button> + <button @click="reset" class="icon" title="Reset"> + <img alt="" src="../images/reset.svg"> + </button> + </div> + + <div id="output" hidden> + <svg viewBox="-1.5 -1.5 3 3"> + <!-- Circle outline --> + <circle cx="0" cy="0" :r="radius" stroke="#808080" stroke-width="0.01" fill="none" stroke-dasharray="0.05,0.05"/> + <circle cx="0" cy="0" r="0.025" fill="#000000"></circle> + + <!-- Velocity vector--> + <line :x1="position[0]" :y1="position[1]" :x2="velocityVector[0]" :y2="velocityVector[1]" stroke="black" stroke-width="0.025"/> + <path :d="`M${velocityVector[2]} ${velocityVector[3]} L${velocityVector[0]} ${velocityVector[1]} L${velocityVector[4]} ${velocityVector[5]} Z`" stroke="#000000" stroke-width="0.025" fill="#000000"/> + + <!-- Centripetal force vector--> + <line :x1="position[0]" :y1="position[1]" :x2="forceVector[0]" :y2="forceVector[1]" stroke="gray" stroke-width="0.025"/> + <path :d="`M${forceVector[2]} ${forceVector[3]} L${forceVector[0]} ${forceVector[1]} L${forceVector[4]} ${forceVector[5]} Z`" stroke="#808080" stroke-width="0.025" fill="#808080"/> + + <!-- Object --> + <circle :cx="position[0]" :cy="position[1]" :r="(0.005*mass)+0.025" fill="#ff0000"/> + </svg> + </div> + + <div id="data" hidden> + <label><b>Time:</b> {{ time.toFixed(2) }} s</label> + <label><b>Speed:</b> {{ speed.toFixed(2) }} m/s</label> + <label><b>Period:</b> {{ period.toFixed(2) }} s</label> + </div> + </div> + </body> +</html> diff --git a/simulations/circular-motion.js b/simulations/circular-motion.js @@ -0,0 +1,142 @@ +const App = { + data: function() { + return { + mass: 5, // The object's mass (Kg) + force: 5, // The centripetal force on the object (N) + radius: 1, // The radius of the circle (m) + time: 0, // The time (s) + active: false, // Whether the simulation is active + refreshRate: 0.01, // The simulation refresh rate (s) + intervalId: null, // The value returned by setInterval + } + }, + computed: { + /** + * The speed of the object + */ + speed: function() { + return Math.sqrt((this.force * this.radius) / this.mass); + }, + + /** + * The circumference of the circle + */ + circumference: function() { + return 2 * Math.PI * this.radius; + }, + + /** + * The time it takes the object to complete 1 rotation + */ + period: function() { + return this.circumference / this.speed; + }, + + /** + * The angle of the object as a percentage (0 - 1) + */ + angle: function() { + return ((this.time * this.speed) / this.circumference) % 1; + }, + + /** + * The coordinates of the object + */ + position: function() { + let x = this.getX(this.angle, this.radius); + let y = this.getY(this.angle, this.radius); + return [x, y]; + }, + + /** + * The coordinates of the velocity vector + */ + velocityVector: function() { + let x = this.getX(this.angle + 0.25, (0.25 * this.speed * this.radius) + 0.1); + let y = this.getY(this.angle + 0.25, (0.25 * this.speed * this.radius) + 0.1); + let arrow1x = x - this.getX(this.angle + 0.35, 0.02); + let arrow1y = y - this.getY(this.angle + 0.35, 0.02); + let arrow2x = x - this.getX(this.angle + 0.15, 0.02); + let arrow2y = y - this.getY(this.angle + 0.15, 0.02); + return [ + x + this.position[0], y + this.position[1], // Center point + arrow1x + this.position[0], arrow1y + this.position[1], // Arrow line #1 + arrow2x + this.position[0], arrow2y + this.position[1], // Arrow line #2 + ]; + }, + + /** + * The coordinates of the centripetal force vector + */ + forceVector: function() { + let x = this.getX(this.angle, (0.9 - (0.08 * this.force)) * this.radius); + let y = this.getY(this.angle, (0.9 - (0.08 * this.force)) * this.radius); + let arrow1x = x + this.getX(this.angle + 0.1, 0.02); + let arrow1y = y + this.getY(this.angle + 0.1, 0.02); + let arrow2x = x + this.getX(this.angle - 0.1, 0.02); + let arrow2y = y + this.getY(this.angle - 0.1, 0.02); + return [ + x, y, // Center point + arrow1x, arrow1y, // Arrow line #1 + arrow2x, arrow2y, // Arrow line #2 + ]; + } + }, + methods: { + /** + * Toggle whether the simulation is active + */ + toggle: function() { + this.active = !this.active; + if (this.active) this.intervalID = setInterval(this.update, this.refreshRate * 1000); + else clearInterval(this.intervalID); + }, + + /** + * Reset the simulation + */ + reset: function() { + this.time = 0; + }, + + /** + * Update the simulation output + */ + update: function() { + this.time += this.refreshRate; + }, + + /** + * Get the C coordinate of a point from an angle and distance + * @param {Number} angle The angle as a percentage (0 - 1) + * @param {Number} distance The distance + * @returns {Number} The X coordinate + */ + getX: function(angle, distance) { + return Math.sin(angle * 2 * Math.PI) * distance; + }, + + /** + * Get the Y coordinate of a point from an angle and distance + * @param {Number} angle The angle as a percentage (0 - 1) + * @param {Number} distance The distance + * @returns {Number} The Y coordinate + */ + getY: function(angle, distance) { + return -Math.cos(angle * 2 * Math.PI) * distance; + }, + }, +} + + + +// Create Vue app +function createApp() { + // Create app + Vue.createApp(App).mount("#app"); + + // Unhide app divs + document.getElementById("input").hidden = false; + document.getElementById("output").hidden = false; + document.getElementById("data").hidden = false; +} diff --git a/simulations/styles.css b/simulations/styles.css @@ -0,0 +1,66 @@ +* { + padding: 0px; + margin: 0px; + box-sizing: border-box; + font-family: Segoe UI, sans-serif; +} +div[hidden] { + display: none !important; +} + +#app { + display: flex; + flex-direction: column; + align-items: center; +} + +header { + background-color: #00ced1; + width: 100%; + text-align: center; + padding: 10px; + margin-bottom: 10px; +} +header h1 { + font-size: 25px; +} + +noscript { + color: #ff0000; + margin-bottom: 10px; +} + +.icon, .icon:focus { + background-color: #00000000; + border: none; + padding: 5px; + cursor: pointer; + vertical-align: middle; + outline: 0; +} + +#input, #data { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + flex-wrap: wrap; + padding: 5px; + background-color: #f0f0f0; +} +#input>*, #data>* { + margin: 5px 10px; +} +#input>section>* { + display:block; + width: 200px; +} + +#output svg { + width: 400px; +} +@media only screen and (max-width: 800px) { + #output svg { + width: 100%; + } +}