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:
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%;
+ }
+}