Making basic physics object class I'm creating a 2D game in JS, and I've made the generic class that all objects in the game will be (I've decided they are all circular for now).

An object has motion and angMotion array properties which contain the 0th, 1st and 2nd derivatives of the object's (angular) motion.

I've also introduced aliases for each of these derivatives (like acc for acceleration, etc).

Upon initialization, the default for all derivatives should be 0.

I'm satisfied with how the class works as it is now as long as it remains 2 dimensional and I don't need higher derivatives of (angular) motion.



However, I feel that my code is repetitive in some places which would require changes in multiple places supposing I want to change how it works slightly.

Eg. I've considered the possibility of wanting to include higher derivatives of motion (without introducing more aliases like jerk) or changing the dimension I'm working in (ignore angular motion in this case).



Note: I wanted the derivatives of (angular) motion to be stored in arrays so that I can loop over them to simulate Physics using Taylor's Expansion as shown below. The simulatePhysics function works regardless of the number of derivatives or dimension.

I'm also wondering from a design point of view - would it be a better idea to put simulatePhysics as a method of the game instead of the object?

class Game2DObject {
  constructor(options) {

    if (options) {

      this.motion = options.motion || []
      this.angMotion = options.angMotion || []

      this.dis = options.dis || [0, 0]
      this.vel = options.vel || [0, 0]
      this.acc = options.acc || [0, 0]

      this.angDis = options.angDis || 0
      this.angVel = options.angVel || 0
      this.angAcc = options.angAcc || 0

      this.radius = options.radius || 0
      this.mass = options.mass || 0

    } else {

      this.motion = [[0, 0], [0, 0], [0, 0]]
      this.angMotion = [0, 0, 0]
      this.radius = 0
      this.mass = 0
    }
  }
  //motion
  get dis() {
    return this.motion[0]
  }

  set dis(x) {
    this.motion[0] = x
  }

  get vel() {
    return this.motion[1]
  }

  set vel(x) {
    this.motion[1] = x
  }

  get acc() {
    return this.motion[2]
  }

  set acc(x) {
    this.motion[2] = x
  }
  //angMotion
  get angDis() {
    return this.angMotion[0]
  }

  set angDis(x) {
    this.angMotion[0] = x
  }

  get angVel() {
    return this.angMotion[1]
  }

  set angVel(x) {
    this.angMotion[1] = x
  }

  get angAcc() {
    return this.angMotion[2]
  }

  set angAcc(x) {
    this.angMotion[2] = x
  }

  simulatePhysics(dt) {
    function taylor(arr) {
      return arr.reduceRight((acc, cur, i) =&gt; acc * dt / (i + 1) + cur, 0)
    }

    for (var dim = 0; dim &lt; this.motion[0].length; dim++) {
      for (var deriv = 0; deriv &lt; this.motion.length; deriv++) {
        this.motion[deriv][dim] = taylor(this.motion.slice(deriv).map((cur) =&gt; cur[dim]))
      }
    }

    for (var deriv = 0; deriv &lt; this.angMotion.length; deriv++) {
      this.angMotion[deriv] = taylor(this.angMotion.slice(deriv))
    }
  }
}

//*DEBUG

var a = new Game2DObject({acc: [-6, 0], angVel: 9})
console.log(a)
console.log(a.dis[0] = 1)
console.log(a.dis[1] = 5)
console.log(a.acc[1] = -2)
console.log(a)

a.simulatePhysics(1)
console.log(a)
a.simulatePhysics(1)
console.log(a)
a.simulatePhysics(1)
console.log(a)


Output:
Poor performance

For games performance dominates all levels of design but becomes critical as you approach the low level building blocks of the game. Every increase in performance opens up your market, there are far more low end devices than high end with a 20% performance increase doubling the number of devices that can run the game at acceptable levels.

If you are not interested in marketing and its just for fun, then you can solve performance problems with hardware and ignore this answer.

Some points on performance


Avoid getters and setters as they are slower than direct property access.
Avoid array iteration for small arrays. Inline the operations.
Be memory efficient, pre-allocate, reuse rather than deference (delete)
Use small generic objects to cover a wide range of uses, rather than many complex targeted objects


Array.slice and Array.map require allocation and GC overhead. 
Array iterators require closure, counters, and logic overheads. For large arrays the overheads become insignificant, but for small arrays 2,3 items long the overhead becomes a significant part of the processing.

One of the biggest overheads of using array iteration is that the coder hides a large number of redundant operations because the iterators obscure the expanded calculations.

Needless CPU wastage

Lets analyze and simplify the function simulatePhysics as it is very inefficient with a lot of repeated calculations, or completely unneeded calculations. Every time it is called it create a lot of garbage for GC to clean up (15 new Array objects)


  simulatePhysics(dt) {
    function taylor(arr) {
      return arr.reduceRight((acc, cur, i) =&gt; acc * dt / (i + 1) + cur, 0)
    }

    for (var dim = 0; dim &lt; this.motion[0].length; dim++) {
      for (var deriv = 0; deriv &lt; this.motion.length; deriv++) {
        this.motion[deriv][dim] = taylor(this.motion.slice(deriv).map((cur) =&gt; cur[dim]))
      }
    }

    for (var deriv = 0; deriv &lt; this.angMotion.length; deriv++) {
      this.angMotion[deriv] = taylor(this.angMotion.slice(deriv))
    }
  }



This function requires the creation of 15 new arrays, 27 call stack push and pops, 30 temp variable, and 108 math operations. 

It can be significantly reduced to no new arrays, no call stack overhead, 6 temp variable, and 19 math operations.

Reducing simulatePhysics

The steps I used to simplify the function and remove unneeded overhead.

1 First to remove all the coding noise we can create some aliases for the names.

 const m = this.motion;
 const aM = this.angMotion;


2 Inline the 2 loops removing all the iterators slice and map overhead.

 m[0][0] = taylor([m[0][0], m[1][0], m[2][0]])
 m[1][0] = taylor([m[1][0], m[2][0]])
 m[2][0] = taylor([m[2][0]])
 m[0][1] = taylor([m[0][1], m[1][1], m[2][1]])
 m[1][1] = taylor([m[1][1], m[2][1]])
 m[2][1] = taylor([m[2][1]])   


 aM[0] = taylor([aM[0], aM[1], aM[2]])  
 aM[1] = taylor([aM[1], aM[2]])  
 aM[2] = taylor([aM[2]])  


3 Replace the taylor function with inlined calculations 

  m[0][0] = ((0 * dt / 3 + m[2][0]) * dt / 2 + m[1][0]) * dt / 1 + m[0][0]
  m[1][0] = (0 * dt / 2 + m[2][0]) * dt / 1 + m[1][0]
  m[2][0] = 0 * dt / 1 + m[2][0]
  m[0][1] = ((0 * dt / 3 + m[2][1]) * dt / 2 + m[1][1]) * dt / 1 + m[0][1]
  m[1][1] = (0 * dt / 2 + m[2][1]) * dt / 1 + m[1][1]
  m[2][1] = 0 * dt / 1 + m[2][1]


  aM[0] = ((0 * dt / 3 + aM[2]) * dt / 2 + aM[1]) * dt / 1 + aM[0]
  aM[1] = (0 * dt / 2 + aM[2]) * dt / 1 + aM[1]
  aM[2] = 0 * dt / 1 + aM[2]


4 Remove the multiply by zero and divide by ones

  m[0][0] = (m[2][0] * dt / 2 + m[1][0]) * dt + m[0][0]
  m[1][0] = m[2][0] * dt + m[1][0]
  m[2][0] = m[2][0]
  m[0][1] = (m[2][1] * dt / 2 + m[1][1]) * dt + m[0][1]
  m[1][1] = m[2][1] * dt + m[1][1]
  m[2][1] = m[2][1]


  aM[0] = (aM[2] * dt / 2 + aM[1]) * dt + aM[0]
  aM[1] = aM[2] * dt + aM[1]
  aM[2] = aM[2]


5 Cache and substitute with constants, remove unneeded assignments.

 const dt2 = dt / 2;

 m[0][0] = (m[2][0] * dt2 + m[1][0]) * dt + m[0][0]
 m[1][0] = m[2][0] * dt + m[1][0]
 m[0][1] = (m[2][1] * dt2 + m[1][1]) * dt + m[0][1]
 m[1][1] = m[2][1] * dt + m[1][1]

 aM[0] = (aM[2] * dt2 + aM[1]) * dt + aM[0]
 aM[1] = aM[2] * dt + aM[1]


6 More aliases to reduce indexing overhead and rebuild the function

simulatePhysics(dt) {
    const m = this.motion, m0 = m[0], m1 = m[1], m2 = m[2];
    const aM = this.angMotion;
    const dt2 = dt / 2;

    m0[0] = (m2[0] * dt2 + m1[0]) * dt + m0[0];
    m1[0] =  m2[0] * dt  + m1[0];
    m0[1] = (m2[1] * dt2 + m1[1]) * dt + m0[1];
    m1[1] =  m2[1] * dt  + m1[1];
    aM[0] = (aM[2] * dt2 + aM[1]) * dt + aM[0];
    aM[1] =  aM[2] * dt  + aM[1];

}


Whats the gain

Arguably there is some loss in readability, personally it makes a lot more sense than a collection of arrays iterators and copying. But the gain is (tested on chrome) huge.

// tested on random set of 1000 objects
// µs is 1/1,000,000th second. OPS is operations per second. 
// An operation is a single call to the function being tested.
Optimized..: MeanTime 0.175µs OPS 5,710,020 Test Total   322ms 1,836,000 operations
Original...: MeanTime 4.198µs OPS   238,185 Test Total 6,566ms 1,564,000 operations


The optimized version is 25 times faster, able to do 5.7million operations in the same time as the original could do 0.3million

Is readability more important than performance? for games you must seriously consider what you lose via traditional coding styles.