physics-simulations

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

projectile-motion.js (8167B)


      1 const App = {
      2     data: function() {
      3         return {
      4             // Data
      5             height: 1,          // The projectile's initial height (m)
      6             initialVelocity: 5, // The projectile's initial velocity (N)
      7             angle: 0,           // The projectile's intial trajectory (degrees)
      8             gravity: 9.8,       // The acceleration due to gravity (m/s/s)
      9             time: 0,            // The time (s)
     10             positions: [],      // A list of the projectile's positions
     11 
     12             // Simulation properties
     13             active: false,      // Whether the simulation is active
     14             refreshRate: 0.01,  // The simulation refresh rate (s)
     15             intervalId: null,   // The value returned by setInterval
     16             infoVisible: false,
     17             svg: null,
     18         }
     19     },
     20     computed: {
     21         /**
     22          * The position of the projectile
     23          */
     24         position: function() {
     25             let x = this.getAdj(this.angle, this.initialVelocity) * this.time;
     26             let y = this.height + (this.getOpp(this.angle, this.initialVelocity) * this.time) - (0.5 * this.gravity * this.time * this.time);
     27             return {x, y};
     28         },
     29 
     30         /**
     31          * The current velocity of the projectile
     32          */
     33         velocity: function() {
     34             let x = this.getAdj(this.angle, this.initialVelocity);
     35             let y = this.getOpp(this.angle, this.initialVelocity) - (this.gravity * this.time);
     36             let total = Math.sqrt(x*x + y*y);
     37             let angle = total != 0 ? this.getAng(y, x) : 0;
     38             return {x, y, total, angle};
     39         },
     40 
     41         /**
     42          * The coordinates of the launch vector arrow
     43          */
     44         launchArrow: function() {
     45             if (this.time !== 0 || this.initialVelocity === 0) {
     46                 return [
     47                     this.position,
     48                     this.position,
     49                     this.position
     50                 ];
     51             }
     52             else {
     53                 let x = this.position.x + this.getAdj(this.angle, this.initialVelocity / 4 + 0.4);
     54                 let y = this.position.y + this.getOpp(this.angle, this.initialVelocity / 4 + 0.4);
     55                 let arrow1x = x - this.getAdj(this.angle + 20, 0.25);
     56                 let arrow1y = y - this.getOpp(this.angle + 20, 0.25);
     57                 let arrow2x = x - this.getAdj(this.angle - 20, 0.25);
     58                 let arrow2y = y - this.getOpp(this.angle - 20, 0.25);
     59                 return [
     60                     { x: arrow1x, y: arrow1y },  // Arrow side #1
     61                     { x: x,       y: y       },  // Center point
     62                     { x: arrow2x, y: arrow2y },  // Arrow side #2
     63                 ];
     64             }
     65         }
     66     },
     67     methods: {
     68         /**
     69          * Handle a keyup event (implements keyboard shortcuts)
     70          * @param {object} e - The event args
     71          */
     72         keyup: function(e) {
     73             if (e.key === "Escape") {
     74                 if (this.infoVisible) this.infoVisible = false;
     75                 else window.location.href = "../";
     76             }
     77         },
     78 
     79         /**
     80          * Toggle whether the simulation is active
     81          */
     82         toggle: function() {
     83             this.active = !this.active;
     84             if (this.active) this.intervalID = setInterval(this.update, this.refreshRate * 1000);
     85             else clearInterval(this.intervalID);
     86         },
     87 
     88         /**
     89          * Reset the simulation
     90          */
     91         reset: function() {
     92             this.time = 0;
     93             this.positions = [];
     94         },
     95 
     96         /**
     97          * Update the simulation
     98          */
     99         update: function() {
    100             // Update time
    101             this.time += this.refreshRate;
    102 
    103             // Update positions
    104             this.positions.push(this.position);
    105 
    106             // Stop simulation when the projectile hits the ground
    107             if (this.position.y <= 0) this.toggle();
    108         },
    109 
    110         /**
    111          * Get the length of the opposite side of a triangle
    112          * @param {Number} angle The angle in degrees
    113          * @param {Number} distance The length of the hypotenuse
    114          * @returns {Number} The length of the opposite side
    115          */
    116         getOpp: function(angle, distance) {
    117             return Math.sin(angle / 360 * 2 * Math.PI) * distance;
    118         },
    119 
    120         /**
    121          * Get the length of the adjacent side of a triangle
    122          * @param {Number} angle The angle in degrees
    123          * @param {Number} distance The length of the hypotenuse
    124          * @returns {Number} The length of the adjacent side
    125          */
    126         getAdj: function(angle, distance) {
    127             return Math.cos(angle / 360 * 2 * Math.PI) * distance;
    128         },
    129 
    130         /**
    131          * Get the angle of a triangle from two of it's sides
    132          * @param {Number} opposite The length of the opposite side
    133          * @param {Number} adjacent The length of the adjacent side
    134          * @returns {Number} The angle in degrees
    135          */
    136         getAng: function(opposite, adjacent) {
    137             return Math.atan(opposite / adjacent) / (2 * Math.PI) * 360;
    138         },
    139     },
    140     created: function() {
    141         // Add keyup handler
    142         window.addEventListener("keyup", this.keyup);
    143     },
    144     destroyed: function() {
    145         // Remove keyup handler
    146         window.removeEventListener("keyup", this.keyup);
    147     },
    148 }
    149 
    150 
    151 
    152 // Create Vue app
    153 function createApp() {
    154     // Create app
    155     let app = Vue.createApp(App).mount("#app");
    156 
    157     // Unhide app divs
    158     document.getElementById("input").hidden = false;
    159     document.getElementById("output").hidden = false;
    160     document.getElementById("data").hidden = false;
    161     document.getElementById("info").hidden = false;
    162 
    163     // Enable zooming
    164     let mobileZoomPanHandler = {
    165         haltEventListeners: ["touchstart", "touchend", "touchmove", "touchleave", "touchcancel"],
    166         init: function(options) {
    167             var instance = options.instance;
    168             let initialScale = 1;
    169             let pannedX = 0;
    170             let pannedY = 0;
    171 
    172             // Init Hammer
    173             // Listen only for pointer and touch events
    174             this.hammer = Hammer(options.svgElement, {
    175                 inputClass: Hammer.SUPPORT_POINTER_EVENTS ? Hammer.PointerEventInput : Hammer.TouchInput
    176             });
    177 
    178             // Enable pinch
    179             this.hammer.get("pinch").set({enable: true});
    180 
    181             // Handle double tap
    182             this.hammer.on("doubletap", function(ev){
    183                 instance.zoomIn()
    184             });
    185 
    186             // Handle pan
    187             this.hammer.on("panstart panmove", function(ev){
    188                 // On pan start reset panned variables
    189                 if (ev.type === "panstart") {
    190                     pannedX = 0;
    191                     pannedY = 0;
    192                 }
    193 
    194                 // Pan only the difference
    195                 instance.panBy({x: ev.deltaX - pannedX, y: ev.deltaY - pannedY});
    196                 pannedX = ev.deltaX;
    197                 pannedY = ev.deltaY;
    198             })
    199 
    200             // Handle pinch
    201             this.hammer.on("pinchstart pinchmove", function(ev) {
    202                 // Calculate true zoom center
    203                 const el = ev.target;
    204                 const rect = el.getBoundingClientRect();
    205                 const pos = {
    206                     x: (ev.center.x - rect.left),
    207                     y: (ev.center.y - rect.top),
    208                 };
    209 
    210                 // On pinch start remember initial zoom
    211                 if (ev.type === "pinchstart") {
    212                     initialScale = instance.getZoom();
    213                     instance.zoomAtPoint(initialScale * ev.scale, {x: pos.x, y: pos.y});
    214                 }
    215 
    216                 instance.zoomAtPoint(initialScale * ev.scale, {x: pos.x, y: pos.y});
    217             });
    218 
    219             // Prevent moving the page on some devices when panning over SVG
    220             options.svgElement.addEventListener("touchmove", function(e){ e.preventDefault(); });
    221         },
    222 
    223         destroy: function(){
    224             this.hammer.destroy();
    225         }
    226     }
    227     app.svg = svgPanZoom("svg", {
    228         dblClickZoomEnabled: false,
    229         minZoom: 0.1,
    230         maxZoom: 10,
    231         customEventsHandler: mobileZoomPanHandler,
    232     });
    233 }