creating a grid (sorta)

hi guys,

I'm trying to draw a grid (sort of) with p5js, but I'm not sure what to look for. Right now I'm just double looping through an array of particles and displaying them, but perhaps vertices work better(?).

Anyhow, I have this picture:

grid

As you can see it's not a grid, because on the left the particles are bigger then on the right and there are more. I'm sure some tricky math is involved, any pointers would be appreciated :)

the actual goal is to draw them, apply forces and dynamically animate them with the p5 sound library

Tagged:

Answers

  • It's just 3d, so all the tricky maths you're worried about would be handled by p5: just spread your grid along the z axis and position the 'camera' as required.

    Having said that using full 3d may be more computationally expensive, and producing pseudo 3d to match this isn't so hard...

  • Hi blindfish, thanks for answering! I heard 3d before, so this might be easiest way to accomplish it. What do you mean with pseudo 3d?

  • ok so i changed the renderer which makes other functionality unavailable (like stroke, ellipse, etc). Does anyone have a mathematical solution to this? :-B

  • Answer ✓

    @flowen - what I meant by 'pseudo 3d' is using a simple formula to give the impression of 3d; but rendering with the 2d graphics engine. IIRC 3d in p5js is still in its early stages (haven't looked into it as it's not a priority); which may explain the limitations you discovered.

    If you don't need things like 3d shading; complex depth sorting etc. (looking at your example image you don't) then pseudo 3d is a good option as it's relatively simple to implement and probably less processor intensive. I've used it in the past in Flash and there's an excellent explanation in Actionscript Animation: a book I can't recommend enough since many of the principles apply whatever the language you're coding in...

  • @blindfish thanks so much for your answer! Right now i'm diving into the nature of code books and courses on Kadenze. I don't think it covers pseudo 3d, but I'll have a look at your recommendation. Thanks again!

  • edited June 2016 Answer ✓

    Since I like to put the advice I give to the test; here's a simplified implementation that could do with some refinement (using instance mode):

    "use strict";
    
    
    // - - - SKETCH
    // This stores a global reference to the p5 object
    // whilst keeping global scope clean of p5js pollution
    window['$p'] = new p5(function (p) {
    
        var cols = 10;
        var rows = 6;
        // focal length as per the book's explanation
        // and yes seems fairly arbitrary
        var fl = 250;
    
        var stepX, stepY;
        // foo is actually a multiplier being applied to the z depth
        var zMultiplier = 1;
        var vpX, vpY;
    
        p.setup = function () {
            p.createCanvas(600, 400);
    
            stepX = p.width/cols;
            stepY = p.height/rows;
            // vanishing point is not working as expected:
            // appears to set the camera position
            // and not the vanishing point
            // I need to double check if this is expected behaviour...
            vpX = 0;// p.width/2;
            vpY = 0;//p.height/2;
        };
    
        p.draw = function() {
    
            p.background(0);
            p.noStroke();
            p.fill(255);
    
            for(var i = 0; i < cols; i++){
              // z position in '3d space'
              // z goes further away in each column
              var z = i * zMultiplier;
              // the magic formula - check the book for an explanation
              var scale = fl/(fl+z);
    
              // x position in '3d space'
              //stepX/2 simply applies an offset from edge of the sketch
              var x = stepX/2 + stepX * i;
    
              // apply the formula to get the 2d position            
              var x2D = vpX + (x * scale);
              // scale to give sense of depth
              var size = 40 * scale;
    
              for (var j = 0; j < rows; j++) {
                // y position in '3d space'  
                var y = stepY/2 + stepY * j;
                // apply the formula to get the 2d position
                var y2D = vpY + (y * scale);
                p.ellipse(x2D, y2D, size, size);
              }
            }        
    
        };
    
        p.mouseMoved = function() {
          // fairly arbitrary values that generated nice results
          zMultiplier = p.mouseX/p.width * 25;
        };
    
    }, "sketch01");
    

    Remove noStroke() in draw and move the mouse to the left to see why this won't always deliver convincing 3d. Even that issue can be resolved if need be...

    You'll want to shift the 'vanishing point' down to get the effect you want; but I didn't get as far as incorporating that...

    edit: updated code with comments...

  • Here is an example of a sphere in different Z pos using 3D:

    int z=0;
    void setup() {
      size(400,400, P3D); 
    }
    
    void draw() {
    
      //Mouse Click to see Z effect
      pushMatrix();
      background(200);
      stroke(255, 50);
      translate(50, 50, z);      
      fill(mouseX * 2, 0, 160);      
      sphere(40);
      popMatrix();
    
      //Static demo
      int ypos=30;
      for(int i=25;i<height;i=i+25){
        pushMatrix();
        translate(150, ypos, -200+i*2.0);
        ypos+=30;
        sphere(8);
        popMatrix();
      }
    
    }
    
    void mousePressed(){
    
      z=z+50;
      if(z==50)
      z=-850;
    
    }
    

    I hope this helps,

    Kf

  • edited June 2016

    @blindfish wow thanks so much for that answer, I'm trying to wrap my head around the code. So I commented it and have some further questions if you don't mind :)

    offtopic: for some reason I can't get the syntax highlighter to work, I'm using this one https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code


    "use strict"; // - - - SKETCH // did this in order to keep everything in it's own scope? window['$p'] = new p5(function (p) { var cols = 10; var rows = 6; // is fl (focal length, right?) just an arbitrary number? var fl = 7; var stepX, stepY; var xAxis = 1; //changed foo to xAxis p.setup = function () { p.createCanvas(600, 400); stepX = p.width/cols; stepY = p.height/rows; }; p.draw = function() { p.background(0); p.noStroke(); p.fill(255); for(var i = 0; i < cols; i++){ // decrease the scale for each column var scale = fl/(fl+i); // stepX/2 = offset from left // xAxis is mouseX // I see scale is the magic number here to keep the grid 'tight' instead of 'curvy' // but I can't wrap my head around why this is?? var x = stepX/2 + stepX * i * xAxis * scale; var size = 40 * scale; for (var j = 0; j < rows; j++) { var y = stepY/2 + stepY * j * scale; p.ellipse(x, y, size, size); } } }; p.mouseMoved = function() { // wondering if 2.5 is just some arbitrary number xAxis = p.mouseX/p.width * 2.5; }; }, "sketch01");
  • @kfrajer thanks! But I'm looking for a 2d solution in p5

  • @flowen. I've updated my code example above with some explanatory comments. The vanishing point code is doing the reverse of what I would expect. It's a while since I did anything in Flash so I'll have to remind myself if there are any differences in its coordinate system that would explain this.

    Ultimately you're just applying fairly arbitrary calculations to do linear, consistent scaling (of size and position) along a separate 'axis'.

  • // This stores a global reference to the p5 object
    // whilst keeping global scope clean of p5js pollution
    window['$p'] = new p5(function (p) {

    If the intention is minimize global context "pollution", why create another global property called $p? :-/ Simply instantiate p5 w/ new w/o assigning the returned object to any variable or property. B-)

  • If the intention is minimize global context "pollution", why create another global property called $p?

    @GoToLoop: I imagine you already know that this is accepted practice for most JS libraries: jQuery $, underscore _ etc. give you a single global variable from which you access their methods.

    From what I've seen of your code you avoid the need for a single global by passing references to the p5 instance around as a parameter in function calls. I prefer a single global variable...

  • edited June 2016

    ... you avoid the need for a single global by passing references to the p5 instance around as a parameter in function calls.

    Yup! The parameter p from new p5(function (p) { is itself a reference to the new p5 object instance.
    Therefore parameter p is sketch's this. And we can use this in place of p if we wanna.

    So when some external class needs to access the sketch's reference or canvas too, it should request it from its constructor & store it. That's exactly what 3rd-party libs do in Processing's Java Mode btW. ~O)

  • edited June 2016

    jQuery's $, underscore's _, etc. give you a single global variable from which you access their methods.

    AFaIK $ & _ are function constructors rather than instances of those corresponding libraries. :-?
    And they're still constructors even though keyword command new isn't necessary for them btW. :P

    In the same vein, p5.js' p5 is also a constructor for the library. :>
    That makes your global $p an instance of class p5, not an actual constructor like the others.

    Another issue is that external classes would need to assume there's some global variable named $p instead of choosing its name internally. A serious coupling issue. 8-X

    Even worse, if we create another p5 instance object, what'd be the name for that next global variable now? How would external 3rd-party classes get access to that new instance same way as the 1st $p? :-O

  • Interesting discussion, having p5 run in it's own scope is important I guess, but kinda offtopic ;)

    Still trying to find how to shift the 'vanishing point', but I have the feeling it requires a totally different formula. Although if we multiply the scale var with a negative 1 we get the reversed result, I don't seem to get it when mapping the values to -.5 and .5.

    Right now i'm faking it, by making 2 grids and stack them on top of each other ;)

  • Answer ✓

    @flowen: haven't had a chance to look into this until now. The solution was to apply and offset - based on the vanishing point - to the 3D coordinate system:

    "use strict";
    
    
    // - - - SKETCH
    // This stores a global reference to the p5 object
    // whilst keeping global scope clean of p5js pollution
    window['$p5'] = new p5(function (p) {
    
        var cols = 10;
        var rows = 6;
        // focal length as per the book's explanation
        // and yes seems fairly arbitrary
        var fl = 250;
    
        var stepX, stepY;
        // foo is actually a multiplier being applied to the z depth
        var zMultiplier = 1;
        var vpX, vpY;
        var offsetX, offsetY;
    
        p.setup = function () {
            p.createCanvas(600, 400);
    
            stepX = p.width/cols;
            stepY = p.height/rows;
    
            // vanishing point works...
            vpX = p.width * 0.9;
            vpY = p.height * 0.85;
            // but you have to offset your starting position:
            offsetX = -vpX + stepX/2;
            offsetY = -vpY + stepY/2;
            //note: stepX/Y/2 are just arbitrary offsets from the edge    
        };
    
        p.draw = function() {
    
            p.background(0);
            p.noStroke();
            p.fill(255);
    
            for(var i = 0; i < cols; i++){
              // z position in '3d space'
              // z goes further away in each column
              var z = i * zMultiplier;
              // the magic formula - check the book for an explanation
              var scale = fl/(fl+z);
    
              // x position in '3d space'
              var x = offsetX + stepX * i;
    
              // apply the formula to get the 2d position            
              var x2D = vpX + x * scale;
              // scale to give sense of depth
              var size = 40 * scale;
    
              for (var j = 0; j < rows; j++) {
                // y position in '3d space'  
                var y = offsetY + stepY * j;
                // apply the formula to get the 2d position
                var y2D = vpY + y * scale;
                p.ellipse(x2D, y2D, size, size);
              }
            }        
    
        };
    
        p.mouseMoved = function() {
          // fairly arbitrary values that generated nice results
          zMultiplier = p.mouseX/p.width * 25;
        };
    
    }, "sketch01");
    

    This produces quite nice results. Probably not super-accurate 3D; but good enough :)

    As to the off-topic discussion: IMHO it's all a question of personal coding style. The global reference works well enough for me when I'm only working with a single sketch (most of the time) and is analogous to how I work with other libraries; regardless of what's happening internally. I find it odd to create an object instance without having an explicit reference... Others don't :-?

  • @blindfish - this is so awesome, thanks so much! I was thinking of an offset, but didn't do it in the proper way so just assumed it must be some other formula. Learned a lot from you, thanks again!

Sign In or Register to comment.