How to correctly apply Perlin noise to a sphere ?

edited March 2018 in Python Mode

Hi,

I'm trying to add Perlin noise to an icoSphere (icosahedron / geodesic sphere) but for some reason the terrain (noise) generated is too "homogeneous": "peaks" are allocated almost evenly and heights (vertices heights) don't vary much.

Ideally I'd like to be able to adjust the noise so as to get real random values (heterogeneous distribution and heights) like in the following pictures.

The vertices positions are stored in array list (here named "knots") and all I do is adding a variable (named "r") to the x, y, z position of each vertex:

for e in knots:
        r = noise(random(1))
        for i, f in enumerate(e):
            particles[e[i]] = Particle(particles[e[i]].x() + particles[e[i]].x() * r  , particles[e[i]].y() + particles[e[i]].y() * r, particles[e[i]].z() + particles[e[i]].z() * r)

I guess this is not an appropriate way to apply Perlin noise and would love some help to tweak the snippet above. Any tip, idea, code (both Java or Python) is welcomed !

EDIT: This question is a "programming question". Even though the code is in Python, the question is about Perlin noise programmation. Answers or suggestions in Java are totally fine !

full code:

add_library('peasycam')
from particle import Particle

def setup():
    global ICOSUBDIVISION, vdata, tindices,  particles
    size(800, 600, OPENGL)
    smooth(10)

    cam = PeasyCam(this, 450)


    particles = []
    ICOSUBDIVISION = 0
    X, Z = 0.525731112119133606, 0.850650808352039932
    vdata = [[-X, 0, Z], [X, 0, Z], [-X, 0, -Z], 
             [X, 0, -Z], [0, Z, X], [0, Z, -X], 
             [0, -Z, X], [0, -Z, -X], [Z, X, 0], 
             [-Z, X, 0], [Z, -X, 0], [-Z, -X, 0]]
    tindices = [[0, 4, 1], [0, 9, 4], [9, 5, 4], [4, 5, 8],
                [4, 8, 1], [8, 10, 1], [8, 3, 10], [5, 3, 8],
                [5, 2, 3], [2, 7, 3], [7, 10, 3], [7, 6, 10],
                [7, 11, 6], [11, 0, 6], [ 0, 1, 6], [ 6, 1, 10],
                [ 9, 0, 11], [ 9, 11, 2], [ 9, 2, 5], [ 7, 2, 11 ]]

    Icosahedron()

def draw():
    background(360)
    lights()

    f1, f2, f3 = PVector(), PVector(), PVector()

    for e in range(0, len(particles), 3):
        f1.x, f1.y, f1.z = particles[e].x(), particles[e].y(), particles[e].z()
        f2.x, f2.y, f2.z = particles[e+1].x(), particles[e+1].y(), particles[e+1].z()
        f3.x, f3.y, f3.z = particles[e+2].x(), particles[e+2].y(), particles[e+2].z()

        noStroke()
        colorMode(HSB, 360, 105, 100)
        fill(abs(f1.x), abs(f1.y), abs(f1.z))
        beginShape()
        vertex( f1.x, f1.y, f1.z )
        vertex( f2.x, f2.y, f2.z )
        vertex( f3.x, f3.y, f3.z )
        endShape( CLOSE )

def keyPressed():
    global ICOSUBDIVISION, particles
    if keyCode == UP:
        if ICOSUBDIVISION == 5:
            return 
        ICOSUBDIVISION += 1
        particles = []
        Icosahedron()
    elif keyCode == DOWN:
        if ICOSUBDIVISION == 0:
            return
        ICOSUBDIVISION -= 1
        particles = []
        Icosahedron() 

def Icosahedron():
    global vertexList

    vertexList =  []

    [subdivide(vdata[tindices[e][0]], vdata[tindices[e][1]], vdata[tindices[e][2]], ICOSUBDIVISION) for e in range(20)]

    for e in range(0, len(vertexList), 3):
        x, y, z = 0, 1, 2
        p = Particle(vertexList[e+x]* 200, vertexList[e+y]* 200, vertexList[e+z]* 200)
        particles.append(p)

    VertPos = set(particles)
    knots = [[] for e in VertPos]

    for i, v in enumerate(VertPos):
        for j, p in enumerate(particles):
            if p == v:
                knots[i].append(j)

    for e in knots:
        r = noise(random(1))
        for i, f in enumerate(e):
            particles[e[i]] = Particle(particles[e[i]].x() + particles[e[i]].x() * r  , particles[e[i]].y() + particles[e[i]].y() * r, particles[e[i]].z() + particles[e[i]].z() * r)

def norme(v):
    ln = 0
    for e in range(3):
        ln += (v[e] * v[e])

    ln = sqrt(ln)
    for e in range(3):
        v[e] /= ln

def addi(v):
    for e in range(3):
        vertexList.append(v[e])

def subdivide(v1, v2, v3, depth):
    if depth == 0:
        addi(v1)
        addi(v2)
        addi(v3)
        return

    v12, v23, v31 = [0,0,0], [0,0,0], [0,0,0]

    for e in range(3):
        v12[e] = (v1[e] + v2[e]) / 2
        v23[e] = (v2[e] + v3[e]) / 2
        v31[e] = (v3[e] + v1[e]) / 2

    norme(v12)
    norme(v23)
    norme(v31)

    subdivide(v1, v12, v31, depth - 1)
    subdivide(v2, v23, v12, depth - 1)
    subdivide(v3, v31, v23, depth - 1)
subdivide(v12, v23, v31, depth - 1)

Answers

  • Answer ✓

    just came up with this:

    for e in knots:
            for i, f in enumerate(e):
                p = particles[e[i]].normalize()
                r = map(noise(p.x*2 , p.y*2 , p.z *2),0, 1, 40, 100)
                p.mult(r*2)
    

    The final result is somewhat better but still no perfect.

  • @GoToLoop: Do you actually think it's a correct way to apply Perlin noise ?

  • Sorry. Dunno much about perlin or noise().
    Most I've got close were these 2 online sketches I've refactored a long time ago:

    1. http://Studio.ProcessingTogether.com/sp/pad/export/ro.9DrxZ3Xdrd4eI
    2. http://Studio.ProcessingTogether.com/sp/pad/export/ro.981lbSfJSyiPY
  • edited March 2018

    r = map(noise(p.x * 2 , p.y * 2 , p.z * 2), 0, 1, 40, 100)

    @solub what is p in the above line? If you draw p without the noise, would you get a perfect sphere?

    Related to your first post:

    for some reason the terrain (noise) generated is too "homogeneous": "peaks" are allocated almost evenly and heights (vertices heights) don't vary much.

    Where are you applying perlin noise? From what I know in 1D, perlin noise variation depends on the sample difference between calls to perlin noise. If you want to see more variation, you just need to sample at larger intervals aka. perlin_noise_function_generator(t) where t is your sampling interval. However, perlinNoise and noise is not the same function.

    Kf

  • edited March 2018

    @kfrajer: Each p is a vertex, p.x, p.y, p.z are the vertices positions. So if I draw p without the noise I would indeed get a perfect sphere.

    In the snippet I normalize each vertex, compute noise and multiply them by the noise value:

    for e in knots:
            for i, f in enumerate(e):
                p = particles[e[i]].normalize()
                r = map(noise(p.x*2 , p.y*2 , p.z *2),0, 1, 40, 100)
                p.mult(r*2)
    

    And this gives me someting like this:

    (I can indeed map the noise between lower than 40 (say 30 or 20) and 100 to get more variations. )

    I guess it's ok but am still not sure if it's an appropriate way to apply Perlin noise.

    Take this picture for instance (found on google images after searching "icosphere" + "perlin noise") How Perlin noise can give you such variation in heights ? There must be somehtin I'm missing.

  • edited March 2018

    @solub --

    If the example you found on the internet is indeed using perlin noise then the two issues are:

    1. The scale of the input coordinates
    2. The amplitude of the output as a change in the surface of your sphere.

    I would demonstrate on your provided sketch, but it fails on

    from particle import Particle
    

    ImportError: no module named particle

    (also, for some reason the forum inserted emoticon HTML into your line 15).

    At first glance it looks like you are approaching things in the right way but your base radius is much larger than the reference example, and your output amplitude is way smaller, and you need the highest facet resolution shown in your demo (high resolution is required for sharp peaks). The regularity / irregularity on the surface is extremely sensitive to the scale of input.

    To read more about the scale of input coordinates, see past discussions of "perlin" on this forum:

  • edited March 2018

    @jeremydouglass My apologies, I forgot to copy/paste the "particle" Class. After a google search I found that the shape in the example was actually created by accident.

    "I accidentally divided each radius by a noise value rather than multiplying them. ... Wherever the noise value approached zero, the radii would be very large, this resulted in long, sharp spikes in the mesh."

    (original article here).

    I tried to reproduce the same kind of shape following the explanation:

    def terrain():
        for i, e in enumerate(particles):
            p = particles[i].normalize()
            r = map(noise(p.x*2 + m  , p.y*2 + m , p.z*2  + m ),0, 1, 50, 120)
            p.div(r*2) <--- dividing instead of multiplying
    

    But it didn't work. Dividing "each radius by a noise value" make the sphere shrink so much that it almost disapear.

    Anyway, thanks for the links.

    Full (working) code:

    add_library('peasycam')
    
    def setup():
        global ICOSUBDIVISION, vdata, tindices, particles, m, r,  f
        size(800, 600, OPENGL)
        smooth(10)
    
        cam = PeasyCam(this, 450)
    
        particles = []
        m, r = .4, 0
        ICOSUBDIVISION = 0
        X, Z = 0.525731112119133606, 0.850650808352039932
        vdata = [[-X, 0, Z], [X, 0, Z], [-X, 0, -Z],  <-- this is supposed to be  [-x, 0, -z]
                 [X, 0, -Z], [0, Z, X], [0, Z, -X], 
                 [0, -Z, X], [0, -Z, -X], [Z, X, 0], 
                 [-Z, X, 0], [Z, -X, 0], [-Z, -X, 0]]
        tindices = [[0, 4, 1], [0, 9, 4], [9, 5, 4], [4, 5, 8],
                    [4, 8, 1], [8, 10, 1], [8, 3, 10], [5, 3, 8],
                    [5, 2, 3], [2, 7, 3], [7, 10, 3], [7, 6, 10],
                    [7, 11, 6], [11, 0, 6], [ 0, 1, 6], [ 6, 1, 10],
                    [ 9, 0, 11], [ 9, 11, 2], [ 9, 2, 5], [ 7, 2, 11 ]]
        Icosahedron()
    
    def draw():
        global m, r
        background(0)
    
        text(frameRate, - width / 2.4, - height / 2.5 )
    
        f1, f2, f3 = PVector(), PVector(), PVector()
        rotateY(degrees(PI) + r *.3)
        rotateX(degrees(HALF_PI)*1.190)
    
        for i, e in enumerate(range(0, len(particles), 3)):
            f1.x, f1.y, f1.z = particles[e].x, particles[e].y, particles[e].z
            f2.x, f2.y, f2.z = particles[e+1].x, particles[e+1].y, particles[e+1].z
            f3.x, f3.y, f3.z = particles[e+2].x, particles[e+2].y, particles[e+2].z
            colorMode(HSB, 360, 100, 100)
    
            noFill()
            strokeWeight(1)
            stroke(190 - (f2.x + f1.y+ f3.z) / 10 ,  100 , 70 + abs(f2.y+ f1.x + f3.z) /4)
    
    
            beginShape()
            vertex( f1.x, f1.y, f1.z )
            vertex( f2.x, f2.y, f2.z )
            vertex( f3.x, f3.y, f3.z )
            endShape( CLOSE )
    
    
        terrain()
        #m += .04 #make the terrain move
        r += .04
    
    
    def keyPressed():
        global ICOSUBDIVISION, particles
        if keyCode == UP:
            if ICOSUBDIVISION == 5:
                return 
            ICOSUBDIVISION += 1
            particles = []
            Icosahedron()
        elif keyCode == DOWN:
            if ICOSUBDIVISION == 0:
                return
            ICOSUBDIVISION -= 1
            particles = []
            Icosahedron()
    
    def Icosahedron():
        global vertexList, m
    
        vertexList =  []
    
        [subdivide(vdata[tindices[e][0]], vdata[tindices[e][1]], vdata[tindices[e][2]], ICOSUBDIVISION) for e in range(20)]
    
        for e in range(0, len(vertexList), 3):
            x, y, z = 0, 1, 2
            p = PVector(vertexList[e+x]* 200, vertexList[e+y]* 200, vertexList[e+z]* 200)
            particles.append(p)
    
    def terrain():
        for i, e in enumerate(particles):
            p = particles[i].normalize()
            r = map(noise(p.x*2 + m  , p.y*2 + m , p.z*2  + m ),0, 1, 50, 120)
            p.mult(r*2)
    
    
    def norme(v):
        ln = 0
        for e in range(3):
            ln += (v[e] * v[e])
    
        ln = sqrt(ln)
        for e in range(3):
            v[e] /= ln
    
    def addi(v):
        for e in range(3):
            vertexList.append(v[e])
    
    def subdivide(v1, v2, v3, depth):
        if depth == 0:
            addi(v1)
            addi(v2)
            addi(v3)
            return
    
        v12, v23, v31 = [0,0,0], [0,0,0], [0,0,0]
    
        for e in range(3):
            v12[e] = (v1[e] + v2[e]) / 2
            v23[e] = (v2[e] + v3[e]) / 2
            v31[e] = (v3[e] + v1[e]) / 2
    
        norme(v12)
        norme(v23)
        norme(v31)
    
        subdivide(v1, v12, v31, depth - 1)
        subdivide(v2, v23, v12, depth - 1)
        subdivide(v3, v31, v23, depth - 1)
        subdivide(v12, v23, v31, depth - 1)
    
  • I‘d use noStroke(); and lights();

  • edited March 2018

    EDIT: It actually works if you divide the radii by a very small amount of the noise.

    def terrain():
        for i, e in enumerate(particles):
            p = particles[i].normalize()
            r = map(noise(p.x*2 + m  , p.y*2 + m , p.z*2  + m ),0, 1, 10, 120)
            p.div(r*.0001) <-- radii divided by a ten thousandth of the noise
    

    @Chrisir: If I use noStroke() and lights() nothing would be drawn on the screen since I only display strokes.

  • It actually works if you divide the radii by a very small amount of the noise.

    You only need to do that because you've mapped the noise to a large range in the line above. Because of line 4 you have a range of 10 to 120 which you divide by, giving 1/10 to 1/120, both tiny.

    If you didn't do the map you'd have a range of 1/1 (original size) to 1/0 (huge!)

  • @koogs: indeed !

  • If I use noStroke() and lights() nothing would be drawn on the screen since I only display strokes.

    maybe you can connect the vertices to triangles...?

  • edited March 2018

    Hello, I'm really impressed and interested in the above code, but I don't know anything about Python :(

    I'm trying to translate to Java (I don't know if it's even possible), but I' struggling with the various for() loop since I don't know methods like enumerate() or others..

    The biggest problems are:

    I don't know hot to translate this: line 78:

    [subdivide(vdata[tindices[e][0]], vdata[tindices[e][1]], vdata[tindices[e][2]], ICOSUBDIVISION) for e in range(20)]
    

    norme(), addi(), subdivide() methods

    and more and more..

    I can leave here what I've tried to do until now, with no results :(

    import peasy.*;
    import peasy.org.apache.commons.math.*;
    import peasy.org.apache.commons.math.geometry.*;
    
    int ICOSUBDIVISION = 0;
    
    float m = 0.4;
    float r = 0;
    float f;
    
    ArrayList<PVector> particles;
    int[][] tindices = {{0, 4, 1}, {0, 9, 4}, {9, 5, 4}, {4, 5, 8}, 
      {4, 8, 1}, {8, 10, 1}, {8, 3, 10}, {5, 3, 8}, 
      {5, 2, 3}, {2, 7, 3}, {7, 10, 3}, {7, 6, 10}, 
      {7, 11, 6}, {11, 0, 6}, { 0, 1, 6}, { 6, 1, 10}, 
      { 9, 0, 11}, { 9, 11, 2}, { 9, 2, 5}, { 7, 2, 11 }};
    
    ArrayList<Float> vertexList = new ArrayList();
    
    float X = 0.525731112119133606;
    float Z = 0.850650808352039932;
    
    float[][] vdata = {{-X, 0, Z}, {X, 0, Z}, {-X, 0, -Z}, 
      {X, 0, -Z}, {0, Z, X}, {0, Z, -X}, 
      {0, -Z, X}, {0, -Z, -X}, {Z, X, 0}, 
      {-Z, X, 0}, {Z, -X, 0}, {-Z, -X, 0}};
    
    PeasyCam cam;
    
    void setup() {
      size(800, 600, P3D);
    
      cam = new PeasyCam(this, 450);
      Icosahedron();
    }
    
    void draw() {
      background(0);
    
      PVector f1, f2, f3;
      rotateY(degrees(PI) + r *.3);
      rotateX(degrees(HALF_PI)*1.190);
    
      for (int i = 0; i < particles.size(); i += 3) {
    
    
        f1 = new PVector(particles.get(i).x, particles.get(i).y, particles.get(i).z);
        f2 = new PVector(particles.get(i+1).x, particles.get(i+1).y, particles.get(i+1).z);
        f3 = new PVector(particles.get(i+2).x, particles.get(i+2).y, particles.get(i+2).z);
        colorMode(HSB, 360, 100, 100);
    
        noFill();
        strokeWeight(1);
    
    
        beginShape();
        vertex( f1.x, f1.y, f1.z );
        vertex( f2.x, f2.y, f2.z );
        vertex( f3.x, f3.y, f3.z );
        endShape(CLOSE);
    
        terrain();
    
        m += 0.04;
        r += 0.04;
      }
    }
    
    void keyPressed() {
    
      if (keyCode == UP) {
        ICOSUBDIVISION += 1;
      }
    
      if (keyCode == DOWN) {
        ICOSUBDIVISION -= 1;
        particles = new ArrayList();
        Icosahedron();
      }
    }
    
    
    void Icosahedron() {
    
    vertexList = {subdivide(vdata[tindices[e][0]]};
    
      for (int i = 0; i < vertexList.size(); i+=3) {
        int x = 0;
        int y = 1;
        int z = 2;
        PVector p = new PVector(vertexList.get(i+x)* 200, vertexList.get(i+y)* 200, vertexList.get(i+z)* 200);
        particles.add(p);
      }
    }
    
    
    
    void terrain() {
    
      for (int i = 0; i < particles.size(); i+=3) {
        PVector p = particles.get(i).normalize();
        r = map(noise(p.x* 2 + m, p.y * 2 + m, p.z * 2 + m), 0, 1, 50, 120);
        p.mult(r*2);
      }
    }
    
    void norme(float[] v) {
      float ln = 0;
      for (int i = 0; i<3; i++) {
        ln += (v[i] * v[i]);
      }
    
      ln = sqrt(ln);
    
      for (int i = 0; i<3; i++) {
        v[i] /= ln;
      }
    }
    
    void addi(float[] v) {
    for (int i = 0; i<3; i++) {
        vertexList.add(v[i]);
      }
    }
    
    void subdivide(float v1, float v2, float v3, int depth) {
    
      float[] v12 = {0, 0, 0};
      float[] v23 = {0, 0, 0};
      float[] v32 = {0, 0, 0};
    
      for (int i = 0; i<3; i++) {
        v12[i] = (v1[i] + v2[i]) / 2
          v23[i] = (v2[i] + v3[i]) / 2
          v31[i] = (v3[i] + v1[i]) / 2
      }
    }
    

    Hope you can help me :) Thank you in advance

  • line 78 is just a for loop, the loop counter is at the end (because python!)

    for (int e = 0 ; e < 20 ; e++) {
      subdivide(vdata[tindices[e][0]], vdata[tindices[e][1]], vdata[tindices[e][2]], ICOSUBDIVISION);
    }
    
  • edited March 2018

    [subdivide(vdata[tindices[e][0]], vdata[tindices[e][1]], vdata[tindices[e][2]], ICOSUBDIVISION) for e in range(20)]

    That is a python list comprehension: http://www.pythonforbeginners.com/basics/list-comprehensions-in-python

    range(20) is a built-in -- it returns [0, 1, 2, 3 ... 19]

    for e in range(20) is like a classic loop -- and that loop drives the initial statement. the term from a list in the back part of the statement goes repeatedly into the first part of the statement.

    So the Java equivalent is:

    for (int e=0; e<20; e++){
      // subdivide(vdata[tindices[e][0]], vdata[tindices[e][1]], vdata[tindices[e][2]], ICOSUBDIVISION);
    }
    

    ...and these results appear to be returned inside a list of 20 things [] although I haven't studied the original code you are translating.

  • edited March 2018
    void subdivide(float v1, float v2, float v3, int depth) {
    

    v1, v2, v3 aren't floats, they are PVectors, or float[3]

  • edited March 2018

    Thank you everybody, your suggestions have really helped me out of this :)

    so here is where I am if anyone will need the code in JAVA:

    import peasy.*;
    import peasy.org.apache.commons.math.*;
    import peasy.org.apache.commons.math.geometry.*;
    
    int ICOSUBDIVISION = 0;
    
    float m = 0.4;
    float r = 0;
    float f;
    
    ArrayList<PVector> particles = new ArrayList();
    ArrayList<Float> vertexList = new ArrayList();
    
    int[][] tindices = {{0, 4, 1}, {0, 9, 4}, {9, 5, 4}, {4, 5, 8}, 
      {4, 8, 1}, {8, 10, 1}, {8, 3, 10}, {5, 3, 8}, 
      {5, 2, 3}, {2, 7, 3}, {7, 10, 3}, {7, 6, 10}, 
      {7, 11, 6}, {11, 0, 6}, { 0, 1, 6}, { 6, 1, 10}, 
      { 9, 0, 11}, { 9, 11, 2}, { 9, 2, 5}, { 7, 2, 11 }};
    
    float X = 0.525731112119133606;
    float Z = 0.850650808352039932;
    
    float[][] vdata = {{-X, 0, Z}, {X, 0, Z}, {-X, 0, -Z}, 
      {X, 0, -Z}, {0, Z, X}, {0, Z, -X}, 
      {0, -Z, X}, {0, -Z, -X}, {Z, X, 0}, 
      {-Z, X, 0}, {Z, -X, 0}, {-Z, -X, 0}};
    
    PeasyCam cam;
    
    void setup() {
      size(800, 600, P3D);
      cam = new PeasyCam(this, 450);
      Icosahedron();
    }
    
    void draw() {
      background(255);
    
      PVector f1, f2, f3;
    
      for (int i = 0; i < particles.size(); i += 3) {
    
        f1 = new PVector(particles.get(i).x, particles.get(i).y, particles.get(i).z);
        f2 = new PVector(particles.get(i+1).x, particles.get(i+1).y, particles.get(i+1).z);
        f3 = new PVector(particles.get(i+2).x, particles.get(i+2).y, particles.get(i+2).z);
    
        colorMode(HSB, 360, 100, 100);
    
        noFill();
        strokeWeight(1);
    
        beginShape();
        vertex( f1.x, f1.y, f1.z );
        vertex( f2.x, f2.y, f2.z );
        vertex( f3.x, f3.y, f3.z );
        endShape(CLOSE);
    
        terrain();
    
        //m += 0.04;
        //r += 0.04;
      }
    }
    
    void keyPressed() {
    
      if (keyCode == UP) {
        if (ICOSUBDIVISION == 5) {
          return;
        }
        ICOSUBDIVISION += 1;
        particles = new ArrayList();
        Icosahedron();
      }
    
      if (keyCode == DOWN) {
        if (ICOSUBDIVISION == 0) {
          return;
        }
        ICOSUBDIVISION -= 1;
        particles = new ArrayList();
        Icosahedron();
      }
    }
    
    
    void Icosahedron() {
    
      for (int e = 0; e < 20; e++) {
        subdivide(vdata[tindices[e][0]], vdata[tindices[e][1]], vdata[tindices[e][2]], ICOSUBDIVISION);
      }
    
      for (int i = 0; i < vertexList.size(); i+=3) {
        int x = 0;
        int y = 1;
        int z = 2;
        PVector p = new PVector(vertexList.get(i+x)* 200, vertexList.get(i+y)* 200, vertexList.get(i+z)* 200);
        particles.add(p);
      }
    }
    
    void terrain() {
      for (int i = 0; i < particles.size(); i++) {
        PVector p = particles.get(i).normalize();
        r = map(noise(p.x * 2 + m, p.y * 2 + m, p.z * 2 + m), 0, 1, 50, 120);
        p.mult(r*2);
      }
    }
    
    void norme(float[] v) {
      float ln = 0;
      for (int i = 0; i < 3; i++) {
        ln += (v[i] * v[i]);
      }
    
      ln = sqrt(ln);
    
      for (int i = 0; i < 3; i++) {
        v[i] /= ln;
      }
    }
    
    void addi(float[] v) {
      for (int i = 0; i < 3; i++) {
        vertexList.add(v[i]);
      }
    }
    
    void subdivide(float[] v1, float[] v2, float[] v3, int depth) {
    
      if (depth == 0) {
        addi(v1);
        addi(v2);
        addi(v3);
        return;
      }
    
      float[] v12 = {0, 0, 0};
      float[] v23 = {0, 0, 0};
      float[] v31 = {0, 0, 0};
    
      for (int i = 0; i < 3; i++) {
        v12[i] = (v1[i] + v2[i]) / 2;
        v23[i] = (v2[i] + v3[i]) / 2;
        v31[i] = (v3[i] + v1[i]) / 2;
      }
    
      norme(v12);
      norme(v23);
      norme(v31);
    
      subdivide(v1, v12, v31, depth - 1);
      subdivide(v2, v23, v12, depth - 1);
      subdivide(v3, v31, v23, depth - 1);
      subdivide(v12, v23, v31, depth - 1);
    }
    

    Apparently the Python written code works better and runs fluidly then the JAVA one, in fact I can make one more ICOSUBDIVISION without lowering the frame rate.

    Can this be optimized in some way :D ?

    plus: I'm finding difference also in how the terrain move:

    in python m += 0.04; r += 0.04; are working really good make a nice smooth movement.. in JAVA is just a mess, everything moves frenetically without order. anyone with some suggestions? :)

  • Can this be optimized in some way?

    yes. use pshapes. rather than defining the shape every frame, sending it 3x vertices every time, calculate a pshape once, when it changes, and just draw that.

    you mix up pvector and float[3] more than i like. if it was all pvector then you can probably use pvector's own normalise() method rather than norme(). subdivide becomes neater too.

    you don't seem to be removing the old shape when adding the new subdivided one. that may be deliberate but it makes the new shape look less tidy.

    the 20 on line 89, what is it? is it's tindices.size() then use that.

  • Thank you koogs, your help is really appreciated :)

    So.. I managed somehow to use PShape and I can see an enormous difference!! Much much better in runtime :)

    there's something more that I have to ask, I can't find a way to make an appropriate fill() to te shape.. as you can see it creates line inside the volume, not only in surface, and maybe that is where the error starts.. but I really can't find a way to correct it :(

    I tried to avoided float[] and PVector mix up, but if I convert every float[] in PVector and even eliminate the norme() method it runs slower than ever!

    I also re-initialize ArrayList when adding the new subdivided shape, this way I remove old ones or not?

    here is the code until now:

    import peasy.*;
    import peasy.org.apache.commons.math.*;
    import peasy.org.apache.commons.math.geometry.*;
    
    int ICOSUBDIVISION = 0;
    
    float m = 0.4;
    float r = 0;
    float f;
    
    ArrayList<PVector> particles;
    ArrayList<Float> vertexList;
    
    int[][] tindices = {{0, 4, 1}, {0, 9, 4}, {9, 5, 4}, {4, 5, 8}, 
      {4, 8, 1}, {8, 10, 1}, {8, 3, 10}, {5, 3, 8}, 
      {5, 2, 3}, {2, 7, 3}, {7, 10, 3}, {7, 6, 10}, 
      {7, 11, 6}, {11, 0, 6}, { 0, 1, 6}, { 6, 1, 10}, 
      { 9, 0, 11}, { 9, 11, 2}, { 9, 2, 5}, { 7, 2, 11 }};
    
    float X = 0.525731112119133606;
    float Z = 0.850650808352039932;
    
    float[][] vdata = {{-X, 0, Z}, {X, 0, Z}, {-X, 0, -Z}, 
      {X, 0, -Z}, {0, Z, X}, {0, Z, -X}, 
      {0, -Z, X}, {0, -Z, -X}, {Z, X, 0}, 
      {-Z, X, 0}, {Z, -X, 0}, {-Z, -X, 0}};
    
    PeasyCam cam;
    
    PShape ico;
    
    void setup() {
      size(800, 600, P3D);
      cam = new PeasyCam(this, 450);
      particles = new ArrayList();
      Icosahedron();
      drawIco();
    }
    
    void draw() {
      background(0, 0, 100);
      m += 0.01;
      r += 0.01;
      terrain();
      shape(ico);
    }
    
    void drawIco() {
      ico = createShape();
      ico.beginShape();
    
      PVector f1, f2, f3;
      colorMode(HSB, 360, 100, 100);
    
      for (int i = 0; i < particles.size(); i += 3) {
    
        f1 = new PVector(particles.get(i).x, particles.get(i).y, particles.get(i).z);
        f2 = new PVector(particles.get(i+1).x, particles.get(i+1).y, particles.get(i+1).z);
        f3 = new PVector(particles.get(i+2).x, particles.get(i+2).y, particles.get(i+2).z);
        //noFill();    
        ico.vertex( f1.x, f1.y, f1.z );
        ico.vertex( f2.x, f2.y, f2.z );
        ico.vertex( f3.x, f3.y, f3.z );
      }
      ico.endShape();
    }
    
    void Icosahedron() {
      vertexList = new ArrayList();
      for (int e = 0; e < 20; e++) {
        subdivide(vdata[tindices[e][0]], vdata[tindices[e][1]], vdata[tindices[e][2]], ICOSUBDIVISION);
      }
    
      for (int i = 0; i < vertexList.size(); i+=3) {
        int x = 0;
        int y = 1;
        int z = 2;
        PVector p = new PVector(vertexList.get(i+x)* 200, vertexList.get(i+y)* 200, vertexList.get(i+z)* 200);
        particles.add(p);
      }
    }
    
    void terrain() {
      for (int i = 0; i < particles.size(); i++) {
        PVector p = particles.get(i).normalize();
        r = map(noise(p.x * 2 + m, p.y * 2 + m, p.z * 2 + m), 0, 1, 50, 120);
        p.mult(r*2);
      }
      drawIco();
    }
    
    void norme(float[] v) {
      float ln = 0;
      for (int i = 0; i<3; i++) {
        ln += (v[i] * v[i]);
      }
    
      ln = sqrt(ln);
    
      for (int i = 0; i<3; i++) {
        v[i] /= ln;
      }
    }
    
    void addi(float[] v) {
      for (int i = 0; i<3; i++) {
        vertexList.add(v[i]);
      }
    }
    
    void subdivide(float[] v1, float[] v2, float[] v3, int depth) {
    
      if (depth == 0) {
        addi(v1);
        addi(v2);
        addi(v3);
        return;
      }
      float[] v12 = {0, 0, 0};
      float[] v23 = {0, 0, 0};
      float[] v31 = {0, 0, 0};
    
      for (int i = 0; i < 3; i++) {
        v12[i] = (v1[i] + v2[i]) / 2;
        v23[i] = (v2[i] + v3[i]) / 2;
        v31[i] = (v3[i] + v1[i]) / 2;
      }
    
      norme(v12);
      norme(v23);
      norme(v31);
    
      subdivide(v1, v12, v31, depth - 1);
      subdivide(v2, v23, v12, depth - 1);
      subdivide(v3, v31, v23, depth - 1);
      subdivide(v12, v23, v31, depth - 1);
    }
    
    
    void keyPressed() {
    
      if (keyCode == UP) {
        if (ICOSUBDIVISION == 5) {
          return;
        }
        ICOSUBDIVISION += 1;
        particles = new ArrayList();
        Icosahedron();
        drawIco();
      }
    
      if (keyCode == DOWN) {
        if (ICOSUBDIVISION == 0) {
          return;
        }
        ICOSUBDIVISION -= 1;
        particles = new ArrayList();
        Icosahedron();
        drawIco();
      }
    
      if (keyCode == 'f') {
        saveFrame("out/#######out.png");
      }
    }
    

    I leave here also a frame to show the various errors0000009out

  • @BrokenCode --

    as you can see it creates line inside the volume, not only in surface

    Hmm. Maybe z-fighting? Possibly play around with depth test hints? (untested)

    hint(DISABLE_DEPTH_TEST);
    hint(ENABLE_DEPTH_TEST);
    

    I also re-initialize ArrayList when adding the new subdivided shape, this way I remove old ones or not?

    Yes, re-initializing removes everything -- the variable name is now pointed at a new ArrayList object, as the initialization line says.

  • edited March 2018

    Thank you for suggestions and clarification :)

    Ok, so, I have to read how hint(DISABLE_DEPTH_TEST); hint(ENABLE_DEPTH_TEST); works, but I tried to put them somewhere in the code and both seems to do nothing :(

  • edited March 2018

    Hi @BrokenCode. You can find a somewhat similar Java version of my code here: https://www.openprocessing.org/sketch/92464

    I hope it can help!

    PS: If you decide for some reason to run the python version instead I'd recommend avoiding list comprehensions as it significantly slow down the sketch.

    JAVA version (from the link)

    Icosahedron ico;
    static int ICOSUBDIVISION = 0;
    
    void setup() {
      size( 800,600, OPENGL );
      ico = new Icosahedron();
      noStroke();
      fill( 255,0,0 );
    }
    
    void draw() {
    
      float rx = frameCount / 800.f;
      float ry = frameCount / 430.f;
    
      background( 255 );
    
    
      fill( 255,0,0 );
      PVector f1 = new PVector();
      PVector f2 = new PVector();
      PVector f3 = new PVector();
      int col = 0;
      stroke( 0 );
      pushMatrix();
        translate( width * 0.5, height * 0.5, 0 );
        rotateX( rx );
        rotateY( ry );
        for ( int i = 0; i < ico.vertexList.size(); i+=9 ) {
          f1.x = ico.vertexList.get(i) * 200;
          f1.y = ico.vertexList.get(i+1) * 200;
          f1.z = ico.vertexList.get(i+2) * 200;
          f2.x = ico.vertexList.get(i+3) * 200;
          f2.y = ico.vertexList.get(i+4) * 200;
          f2.z = ico.vertexList.get(i+5) * 200;
          f3.x = ico.vertexList.get(i+6) * 200;
          f3.y = ico.vertexList.get(i+7) * 200;
          f3.z = ico.vertexList.get(i+8) * 200;
          if (col == 0) {
            fill( 255,60,0 );
            col++;
          } else if (col == 1) {
            fill( 100,255,0 );
            col++;
          } else if (col == 2) {
            fill( 255,180,0 );
            col++;
          } else {
            fill( 0,255,255 );
            col = 0;
          }
          beginShape();
            vertex( f1.x, f1.y, f1.z );
            vertex( f2.x, f2.y, f2.z );
            vertex( f3.x, f3.y, f3.z );
          endShape( CLOSE );
    
        }
      popMatrix(); 
    
      fill( 0,0,0 );
      text("icosahedron division: "+int( ICOSUBDIVISION ),20,40);
      text("vertices: "+int( ico.vertexList.size() ),20,60);
      text("fps: "+int(frameRate),20,80);
      text("press up & down to change division",20,100);
    
    }
    
    void keyPressed() {
      if ( keyCode == 38 ) { // [up]
        if ( ICOSUBDIVISION == 5 )
          return;
        ICOSUBDIVISION++;
        ico = new Icosahedron();
      } else if ( keyCode == 40 ) {  // [down]
        if ( ICOSUBDIVISION == 0 )
          return;
        ICOSUBDIVISION--;
        ico = new Icosahedron();
      } else {
        println( keyCode );
      }
    }
    
    
    // copied and adapted from http:// code.google.com/p/webgltimer/source/browse/src/net/icapsid/counter/client/Icosahedron.java?r=170e4fcc41bf20700dcb6dc67272073af112c65c
    
    import java.util.List;
    
    public class Icosahedron {
    
            public List<Float> vertexNormalsList = new ArrayList<Float>();
            public List<Float> vertexList = new ArrayList<Float>();
    
            final float X = 0.525731112119133606f;
            final float Z = 0.850650808352039932f;
    
            final float vdata[][] = { 
                            { -X, 0.0f, Z }, 
                            { X, 0.0f, Z },
                            { -X, 0.0f, -Z }, 
                            { X, 0.0f, -Z }, 
                            { 0.0f, Z, X },
                            { 0.0f, Z, -X }, 
                            { 0.0f, -Z, X }, 
                            { 0.0f, -Z, -X },
                            { Z, X, 0.0f }, 
                            { -Z, X, 0.0f }, 
                            { Z, -X, 0.0f },
                            { -Z, -X, 0.0f }
                          };
    
            final int tindices[][] = { { 0, 4, 1 }, { 0, 9, 4 }, { 9, 5, 4 },
                            { 4, 5, 8 }, { 4, 8, 1 }, { 8, 10, 1 }, { 8, 3, 10 },
                            { 5, 3, 8 }, { 5, 2, 3 }, { 2, 7, 3 }, { 7, 10, 3 },
                            { 7, 6, 10 }, { 7, 11, 6 }, { 11, 0, 6 }, { 0, 1, 6 },
                            { 6, 1, 10 }, { 9, 0, 11 }, { 9, 11, 2 }, { 9, 2, 5 },
                            { 7, 2, 11 } };
    
            public Icosahedron() {
    
                    List<Float> texCoordsList = new ArrayList<Float>();
                    List<Integer> indicesList = new ArrayList<Integer>();
    
                    // Iterate over points
                    for (int i = 0; i < 20; ++i) {
                            subdivide(
                              vdata[tindices[i][0]], 
                              vdata[tindices[i][1]],
                              vdata[tindices[i][2]], ICOSUBDIVISION );
                    }
    
                    // verticesArray = floatListToFloatArray(vertexList);
                    // texCoordsArray = floatListToFloatArray(texCoordsList);
                    // vertexNormalsArray = floatListToFloatArray(vertexNormalsList);
                    // indices = integerListToIntegerArray(indicesList);
                    // vertexCount = vertexList.size() / 3;
            }
    
            private void norm(float v[]){
    
                    float len = 0;
    
                    for(int i = 0; i < 3; ++i){
                            len += v[i] *  v[i];
                    }
    
                    len = (float) Math.sqrt(len);
    
                    for(int i = 0; i < 3; ++i){
                            v[i] /= len;
                    }
            }
    
            private void add(float v[]){
                    for (int k = 0; k < 3; ++k) {
                            vertexList.add(v[k]);
                            vertexNormalsList.add(v[k]);
                    }
            }
    
            private void subdivide(float v1[], float v2[], float v3[], int depth) {
    
                    if (depth == 0) {
                            add(v1);
                            add(v2);
                            add(v3);
                            return;
                    }
    
                    float v12[] = new float[3];
                    float v23[] = new float[3];
                    float v31[] = new float[3];
    
                    for (int i = 0; i < 3; ++i) {
                            v12[i] = (v1[i] + v2[i]) / 2f;
                            v23[i] = (v2[i] + v3[i]) / 2f;
                            v31[i] = (v3[i] + v1[i]) / 2f;
                    }
    
                    norm(v12);
                    norm(v23);
                    norm(v31);
    
                    subdivide(v1, v12, v31, depth - 1);
                    subdivide(v2, v23, v12, depth - 1);
                    subdivide(v3, v31, v23, depth - 1);
                    subdivide(v12, v23, v31, depth - 1);
            }
    }
    
  • Thank you, I will dig it! :D

  • edited May 2018

    Following this thread (where I was asking for help to make an icosphere), I would like to share a simpler approach to compute a sphere and apply Perlin noise to it.

    The following example displays a Fibonacci sphere but could certainly work with a simple point sphere.

    add_library('hemesh')
    
    phi = (sqrt(5) + 1) / 2 - 1 #Golden Ratio
    angle = phi * 2 * PI #Golden Angle
    n_points, radius, t = 200, 200, 0
    liste = []
    
    def setup():
        global triangles, render
        size(600, 600, P3D)
        smooth(8)
    
        render = WB_Render(this)
    
        for p in range(n_points):
            lon = angle * p
            lon /= 2 * PI; lon -= floor(lon); lon *= 2 * PI
            if lon > PI: lon -= 2 * PI 
            lat = asin(-1 + 2 * p / float(n_points))
            new_points = WB_Point(radius * cos(lat) * cos(lon), radius * cos(lat) * sin(lon), radius * sin(lat))
            liste.append(new_points)
    
        triangulation = WB_Triangulate.alphaTriangulate3D(liste)
        triangles = triangulation.getAlphaTriangles(radius+1)
    
        noFill()
        beginShape(TRIANGLES)
    
    def draw():
        global t
        background(255)
    
        translate(width/2, height/2)
        rotateY(t/2)   
    
        for i in range(0, len(triangles), 3):
            p1 = PVector(liste[triangles[i]].xd(), liste[triangles[i]].yd(), liste[triangles[i]].zd()).normalize()
            p2 = PVector(liste[triangles[i+1]].xd(), liste[triangles[i+1]].yd(), liste[triangles[i+1]].zd()).normalize()
            p3 = PVector(liste[triangles[i+2]].xd(), liste[triangles[i+2]].yd(), liste[triangles[i+2]].zd()).normalize()
    
            for p in [p1, p2, p3]:
                n = map(noise(p.x/2 + t , p.y/2 + t , p.z/2 + t), 0, 1, 40, 100)
                p.mult(n*2)
                vertex(p.x, p.y, p.z)
        endShape()
    
        t += .02
    

    (Don't mind the yellow, that's a glitch from the GIF conversion)

  • Thanks so much for sharing this, @solub -- using hemesh triangulation on the Fibonacci sphere is an elegant approach, and noising the points looks great.

Sign In or Register to comment.