javascriptalgorithmoptimizationp5.js4d

Japavascript and P5.js - Optimizing a 4D projection code


I know that "how to optimize this code?" kind of question is generally not welcomed in stack overflow. But I think this is only the way i could phrase my question. I wrote a code that projects a 4 dimensional points onto a 3 dimensional space. I then draw the 3d points using the p5.js library.

Here is my code: https://jsfiddle.net/dfq8ykLw/

Now my question here is, how am I supposed to make this run faster, and optimize the code? Since I am supposed to draw few thousand points (sometimes more) per frame, and calculate the rotation for each of those points, my code tend to run incredibly slowly (mac devices can run this a little faster for some reason).

I tried drawing points instead of vertices, which ended up running even slower.

Are there any suggestion of how to improve the performance? Or an advice of what kind of library to use for drawing 3 dimensional shapes?

Just to explain, my program stores the points as nested array, just like [[Point object, Point object, ...], ...]. Each array in the data works as a face, with each Point object being the vertices. I first rotate each of these points by applying 6 rotations (for axis xy, xz, xw, yz, yw and zw), then draw them by projecting them onto the 3d space.

Any help is appreciated, as I am terribly stuck!


Solution

  • in source code

    Begin shape drawing. However in WEBGL mode, application performance will likely drop as a result of too many calls to beginShape() / endShape(). As a high performance alternative, ... _main.default.RendererGL.prototype.beginShape

    So we may want to avoid too many beginShape calls. Idem call it on cube instead of face

    beginShape()
    data.forEach((hyperobject, i) => {
        // face
        for (var p in hyperobject){
            hyperobject[p].rotate(angles[0], angles[1], angles[2], angles[3], angles[4], angles[5])
            hyperobject[p].draw()
        }
        if (i % 6 === 0) {
            endShape(CLOSE);
            beginShape()
        }
    })
    endShape()
    

    However there are some ugly drawn line, because default mode is TRIANGLE_FAN

    _main.default.RendererGL.prototype.beginShape = function(mode) { this.immediateMode.shapeMode = mode !== undefined ? mode : constants.TRIANGLE_FAN;

    So we may specify TRIANGLES instead:

    
    function draw(){
        //noLoop()
        background(0);
        // translate(250, 250);
        for (var a in angles){
            angles[a] += angleSpeeds[a];
        }
        beginShape(TRIANGLES)
        data.forEach((hyperobject, i) => {
            // face
            const [a, b, c, d] = hyperobject.map(a => {
                a.rotate(angles[0], angles[1], angles[2], angles[3], angles[4], angles[5])
                return a
            })
            //first triangle
            a.draw()
            b.draw()
            c.draw()
    
            a.draw()
            b.draw()
            d.draw()
            if (i % 6 === 0) {
                endShape()
                beginShape(TRIANGLES)
            }
        })
        endShape()
    }
    

    Note that you could factorize the rotation

    
      const [axy, axz, axw, ayz, ayw, azw] = angles
      const f = x => [Math.cos(x), Math.sin(x)]
      const [Ca, Sa] = f(axy)
      const [Cb, Sb] = f(axz)
      const [Cc, Sc] = f(axw)
      const [Cd, Sd] = f(ayz)
      const [Ce, Se] = f(ayw)
      const [Cf, Sf] = f(azw)
      const R = [
        [Ca*Cb*Cc, -Cb*Cc*Sa, -Cc*Sb, -Sc],
        [Ca*(-Cb*Sc*Se-Ce*Sb*Sd)+Cd*Ce*Sa, -Sa*(-Cb*Sc*Se-Ce*Sb*Sd)+Ca*Cd*Ce, -Cb*Ce*Sd+Sb*Sc*Se, -Cc*Se],
        [Ca*(Sb*(Sd*Se*Sf+Cd*Cf)-Cb*Ce*Sc*Sf)+Sa*(-Cd*Se*Sf+Cf*Sd), -Sa*(Sb*(Sd*Se*Sf+Cd*Cf)-Cb*Ce*Sc*Sf)+Ca*(-Cd*Se*Sf+Cf*Sd), Cb*(Sd*Se*Sf+Cd*Cf)+Ce*Sb*Sc*Sf, -Cc*Ce*Sf],
        [Ca*(Sb*(-Cf*Sd*Se+Cd*Sf)+Cb*Ce*Cf*Sc)+Sa*(Cd*Cf*Se+Sd*Sf),-Sa*(Sb*(-Cf*Sd*Se+Cd*Sf)+Cb*Ce*Cf*Sc)+Ca*(Cd*Cf*Se+Sd*Sf), Cb*(-Cf*Sd*Se+Cd*Sf)-Ce*Cf*Sb*Sc, Cc*Ce*Cf]
      ]
    
    
      Point.prototype.rotate = function (R) {
        const X = [this.origx, this.origy, this.origz, this.origw]
        const [x,y,z,w] = prod(R, X)
        Object.assign(this, { x, y, z, w })
      }
    

    but this is not the bottleneck, (like 1ms to 50ms for drawing), so keeping your matrix decomposition may be preferable.


    I can't put the code snippet here since webgl not secure https://jsfiddle.net/gk4Lvptm/