# 3d view maze generator (java/android)

**URL:** https://discourse.processing.org/t/3d-view-maze-generator-java-android/29057
**Category:** Project Guidance
**Created:** [April 1, 2021, 8:19pm UTC](https://discourse.processing.org/t/3d-view-maze-generator-java-android/29057 "2021-04-01T20:19:42Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![JoseMY](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/josemy/32/2016_2.png) [@JoseMY](https://discourse.processing.org/u/JoseMY)
#### Post date: [April 1, 2021, 8:19pm UTC](https://discourse.processing.org/t/3d-view-maze-generator-java-android/29057/1 "2021-04-01T20:19:42Z")

</div>

Hi!  
I made a small maze generator. My idea was to made a first-person maze escape game.

But, as you can see in the code below, 3d view is weird. You can change the value of show3d to make sure the camera is not inside a box, but when you turn left or right, he scene is rendered as if the camera was inside a box

 ![Screenshot_20210401-222131](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/3/3c0989fd7efbb17ef6e08c1c953e0705c41c6319.jpeg)  
[Screenshot\_20210401-222131|503x500](https://discourse.processing.org/uploads/short-url/8z75iDbYtffIL9pxuQaCbWvZcTf.jpeg) ![20210401_222214](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/e/ed745caa1581d8d57354cf469b6ad986a1339f9a.jpeg)

(The code below was used in an android machine. It does not contain androud-specific words, but you could need changing size() and createGraphics() to a smaller value. At least, my home computer has fewer resolution than my phone).

Do you think there is a way to fix how the camera renders the too-near boxes?

```auto
/* This intends to be a 3d labyrinth 
 made with Processing
 */

/* map dimensions*/
int alto=40;
int ancho=40;
float h, w, xx, yy;
/* 3d camera float direction*/
float fdir=0;
/* map generation x and y */
int x, y;
/* map generation status (true=ended)*/
boolean acabado=false;
/* map matrix (array) */
int[][] mapa=new int[alto][ancho];
/* int directions. 0=north, 1=east*/
static PVector[] dir={
  new PVector(-1, 0), 
  new PVector(0, 1), 
  new PVector(1, 0), 
  new PVector(0, -1), 
};
/* 3d canvas*/
PGraphics lienzo;

/* configuration*/
void setup() {
  int f, g;

  /* 2D screen*/
  size(1000, 1000, P2D);
  /* 3d canvas*/
  lienzo=createGraphics(1000, 1000, P3D);

  /* map generator tile height and width*/
  /* w is used for 3d cube size, also */
  w=width/ancho;
  h=height/alto;
  //println(w);
  //println(h);

  /* initialize map */
  /* inside is -1, borders are 9.*/
  for (f=0; f<alto; f++) {
    mapa[f][0]=9;
    mapa[f][ancho-1]=9;
    for (g=1; g<(ancho-1); g++) {
      mapa[f][g]=((f==0|f==(alto-1)) ? 9: -1);
    }
  }

  x=ancho/2; /* map generator start x*/
  xx=width/2; /*3d view start x*/
  y=alto/2; /*map generator start y*/
  yy=height/2; /* 3d view start y*/
  mapa[x][y]=5; /* marks start/end tile*/
}

void draw() {
  if (acabado==false) {
    /* generate map until done */
    generar();
  } else {
    /* if map is done, show 3d view */
    /* tresd is 3d spelt in Spanish*/
    tresde();
  }
}
/* Show 3d view */
void tresde() {
  /* variables for for... loop */
  float ix, iy;
  /* we draw in a 3d canvas
     so we can mix 3d and 2d
  */
  lienzo.beginDraw();
  lienzo.background(128);
  lienzo.ambientLight(102, 102, 102);
  lienzo.directionalLight(xx, yy, 0, 0, 0, 1);

  final boolean show3d=true;
  if (!show3d) {
    /*Experimental - disable 
     fist person
     for debug purposes
     */
    lienzo.pushMatrix();
    lienzo.translate(xx, yy);
    lienzo.sphere(w);
    lienzo.popMatrix();
  } else {
    /* Normal: 1st person view */
    lienzo.camera(xx, yy, 0, xx+cos(fdir), yy+sin(fdir), 0, 0, 0, 1);
  }
  
  /* draw blocks as cubes */
  for (ix=0; ix<ancho; ix++) {
    for (iy=0; iy<alto; iy++) {

      lienzo.pushMatrix();
      lienzo.translate(ix*w, iy*w, 0);
      if (mapa[int(ix)][int(iy)]==-1) {
        lienzo.box(w);
      }
      lienzo.popMatrix();
    }
  }
  lienzo.endDraw();
  /* show the canvas */
  image(lienzo, 0, 0);
  
  /* draw some buttons */
  fill(128, 255, 128);
  stroke(200, 255, 200);
  float w3=width/3;
  float w6=w3/2;
  float h3=200/2;
  rect(0, 800, width/3, 200);
  rect(2*width/3, 800, width/3, 200);
  fill(255);
  stroke(0);
  equilatero(w6, 800+h3, 100, PI);
  equilatero(5*w6, 800+h3, 100, 0);
}

/* draw regular triangle */
void equilatero(float tx, float ty, float radi, float ang) {
  beginShape(TRIANGLE);
  float a;
  a=ang;
  for (int f=0; f<3; f++) {
    vertex(tx+radi*cos(a), 
      ty+radi*sin(a));
    a+=TWO_PI/3;
  }
  endShape();
}

/* detect mousepress / touch */
/* and manage buttons */
void mousePressed() {
  if (mouseY>800) {
    
    switch (int(3*mouseX/1000)) {
    case 0:
      /* left*/
      fdir+=PI/16;
      if (fdir>TWO_PI) fdir-=TWO_PI;
      //println("<");
      break;
    case 2:
    /* right */
      fdir-=PI/16;
      if (fdir<0) fdir+=TWO_PI;
      //println(">");
      break;
    };
  }
}

/* Map generator.
   Based on what I recall from
   something I typed from
   a ZX Spectrum magazine back in the
   eighties
*/
void generar() {

  int f;
  /* valid directions*/
  int[] dirValidas={};
  /* current direction*/
  int curDir;
  
  if (acabado) return;
  
  //println(str(x)+","+str(y));
  
  /* check valid direction
   (directions within map and
   without a path)
   */
  for (f=0; f<4; f++) {
    if (testValida(int(x+dir[f].x*2), int(y+dir[f].y*2))) {
      //println("dirvalida="+str(f));
      dirValidas=append(dirValidas, f);
      /* valid, push into array */
    } else {
      //println("dir no valida="+str(f));
      /* invalid */
    }
  }
  //println(dirValidas);
  if (dirValidas.length==0) {
    /* no valid direction, go back */
    if (mapa[x][y]>4) {
      /* we are back on first square */
      /* or we hit a map limiter wall */
      /* despite of tests against it */
      println("acabado o error");
      acabado=true;
    } else {
      /* Going back */
      //println("back");
      /* 
        How did we come here?
        add 180 degrees to it
        (curDir+2)
        so we go back
      */
      curDir=(mapa[x][y]+2)%4;
      /* always move 2 steps */
      for (f=0; f<2; f++) {
        mapa[x][y]=8;
        x+=dir[curDir].x;
        y+=dir[curDir].y;
        if (mapa[x][y]==5) acabado=true;
      }

      /* hemos acabado?*/
      if (mapa[x][y]==5) acabado=true;
    }
  } else {
    // Choose a random direction
    // from valid ones.
    //println("forward to");
    curDir=dirValidas[int(random(dirValidas.length))];
    //println(curDir);
    /* always move 2 steps */
    for (f=0; f<2; f++) {
      x+=dir[curDir].x;
      y+=dir[curDir].y;
      /* store direction on map
      so we can go back */
      mapa[x][y]=curDir;
    }
  }

  dibujar();
  /* End Text */
  /* just in case we disable 3d view */
  if (acabado) {
    textSize(40);
    fill(255);
    textAlign(CENTER);
    text("Acabado", height/2, width/2);
    textSize(38);
    fill(0);
    text("Acabado", height/2, width/2);
  }
}

/* Is (vx,vy) a valid square
   for new route?
*/
boolean testValida(int vx, int vy) {
  /* 1) test for boundary */
  if (vx<0|vx>=ancho) return false;
  if (vy<0|vy>=alto) return false;
  /* 2) test for used tile (>=0) */
  if (mapa[vx][vy]>=0 ) {
    //println("["+str(vx)+"]["+str(vy)+"]="+str(mapa[vx][vy]));
    return false;
  } 
  return true;
}

/* draws 2d map at generation time */
void dibujar() {
  int f, g;
  for (f=0; f<alto; f++) {
    for (g=0; g<ancho; g++) {
      switch (mapa[f][g]) {
      case -1: 
      /* free square / wall */
        fill(0);
        break;
      case 9:
        /* borders*/
        fill(128, 0, 0);
        break;
      case 8: 
      /* no way out */
        fill(255);
        break;
      case 0: /* way N*/
        fill(255, 0, 0);
        break;
      case 1: /* way E*/
        fill(0, 255, 0);
        break;
      case 2: /* way S*/
        fill(128, 0, 128);
        break;
      case 3: /* way W*/
        fill(0, 128, 128);
        break;
      case 5: /* start/end */
        fill(255, 128, 128);
        break;
      }
      rectMode(CORNER);
      rect(g*w, f*h, w, h);
    }
  }
}

```

Regards,  
José!

---

<div class="post-metadata">

### Author: ![josephh](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/josephh/32/210_2.png) [@josephh](https://discourse.processing.org/u/josephh)
#### Post date: [April 1, 2021, 9:44pm UTC](https://discourse.processing.org/t/3d-view-maze-generator-java-android/29057/2 "2021-04-01T21:44:16Z")

</div>

Hi,

I think that what you are looking for is changing the clipping plane distance of the camera.

See the Wikipedia article on that :

> **[Clipping (computer graphics)](https://en.wikipedia.org/wiki/Clipping_(computer_graphics))**
>
> Clipping, in the context of computer graphics, is a method to selectively enable or disable rendering operations within a defined region of interest. Mathematically, clipping can be described using the terminology of constructive geometry. A rendering algorithm only draws pixels in the intersection between the clip region and the scene model. Lines and surfaces outside the view volume (aka. frustum) are removed.
> Clip regions are commonly specified to improve render performance. A well-chose...

In the Processing reference for the [`perspective()`](https://processing.org/reference/perspective_.html) function :

> Sets a perspective projection applying foreshortening, making distant objects appear smaller than closer ones. The parameters define a viewing volume with the shape of truncated pyramid.

So you have `perspective(fovy, aspect, zNear, zFar)` with :

- `zNear` → float: z-position of nearest clipping plane

- `zFar` → float: z-position of farthest clipping plane

---

<div class="post-metadata">

### Author: ![JoseMY](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/josemy/32/2016_2.png) [@JoseMY](https://discourse.processing.org/u/JoseMY)
#### Post date: [April 2, 2021, 9:49am UTC](https://discourse.processing.org/t/3d-view-maze-generator-java-android/29057/3 "2021-04-02T09:49:34Z")

</div>

Thank you. It worked.

Just a question. What you think is faster: rendering the whole maze one box at a time, or making a PShape from it?

I could make a routine to convert each contiguous wall into a Shape, but I don’t know if the effort will pay for it.

Regards,  
José

---

<div class="post-metadata">

### Author: ![JoseMY](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/josemy/32/2016_2.png) [@JoseMY](https://discourse.processing.org/u/JoseMY)
#### Post date: [April 2, 2021, 1:04pm UTC](https://discourse.processing.org/t/3d-view-maze-generator-java-android/29057/4 "2021-04-02T13:04:49Z")

</div>

Hello again!

I share here the current playable version.

There are still some things to do:

1. Add an exit (a tunnel straight from the outside of the maze to the first inner tunnel).

2. Add some shortcuts.

3. Add enemies?

4. Add closest path algorithm, either to guide enemies or to help player. What library should I use?

```auto
/* This intends to be a 3d labyrinth 
 made with Processing
 */

/* map dimensions*/
int alto=40;
int ancho=40;
float h, w, xx, yy;
/* 3d camera float direction*/
float fdir=0;
/* map generation x and y */
int x, y;
/* map generation status (true=ended)*/
boolean acabado=false;
/* map matrix (array) */
int[][] mapa=new int[alto][ancho];
/* int directions. 0=N, 1=E*/
static PVector[] dir={
  new PVector(0, -1), 
  new PVector(1, 0), 
  new PVector(0, 1), 
  new PVector(-1, 0), 
};
String[] messages={};
/* 3d canvas*/
PGraphics lienzo;

/* configuration*/
void setup() {
  int f, g;

  /* 2D screen*/
  size(1000, 1000, P2D);
  /* 3d canvas*/
  lienzo=createGraphics(1000, 1000, P3D);

  /* map generator tile height and width*/
  /* w is used for 3d cube size, also */
  w=width/ancho;
  h=height/alto;
  //printms(w);
  //printms(h);

  /* initialize map */
  /* inside is -1, borders are 9.*/
  for (f=0; f<alto; f++) {
    mapa[f][0]=9;
    mapa[f][ancho-1]=9;
    for (g=1; g<(ancho-1); g++) {
      mapa[f][g]=((f==0|f==(alto-1)) ? 9: -1);
    }
  }

  x=ancho/2; /* map generator start x*/
  xx=width/2; /*3d view start x*/
  y=alto/2; /*map generator start y*/
  yy=height/2; /* 3d view start y*/
  mapa[x][y]=5; /* marks start/end tile*/
}

void draw() {
  int curDir=-1;
  if (acabado==false) {
    /* generate map until done */
    curDir=generar();
    if (curDir!=-1) {
      // Set fdir to look at an exit on
      // first frame.
      fdir=HALF_PI * ((curDir+1)%4);
    }
  } else {
   
    /* if map is done, show 3d view */
    /* tresd is 3d spelt in Spanish*/
    tresde();
    
  }
  showMessages();
}
/* Show 3d view */
void tresde() {
  /* variables for for... loop */
  float ix, iy;
  /* we draw in a 3d canvas
   so we can mix 3d and 2d
   */
  lienzo.beginDraw();
  lienzo.background(128);
  lienzo.perspective(HALF_PI, 1.0, .5, w*40.0);
  lienzo.ambientLight(102, 102, 102);
  lienzo.directionalLight(xx, yy, 0, 0, 0, 1);

  final boolean show3d=true;
  if (!show3d) {
    /*Experimental - disable 
     fist person
     for debug purposes
     */
    lienzo.pushMatrix();
    lienzo.translate(xx, yy);
    lienzo.sphere(w);
    lienzo.popMatrix();
  } else {
    /* Normal: 1st person view */
    lienzo.camera(xx, yy, 0, xx+cos(fdir), yy+sin(fdir), 0, 0, 0, 1);
  }

  /* draw blocks as cubes */
  for (ix=0; ix<ancho; ix++) {
    for (iy=0; iy<alto; iy++) {

      lienzo.pushMatrix();
      lienzo.translate(ix*w, iy*w, 0);
      if (mapa[int(ix)][int(iy)]==-1) {
        lienzo.box(w);
      }
      lienzo.popMatrix();
    }
  }
  lienzo.endDraw();
  /* show the canvas */
  image(lienzo, 0, 0);

  /* draw some buttons */

  float w3=width/3;
  float w6=w3/2;
  float h3=200/2;

  stroke(75, 175, 75);
  fill(100, 200, 100);
  rect(0, 800, w3, 200);
  fill(128, 255, 128);
  rect(w3, 800, w3, 200);
  fill(100, 200, 100);
  rect(0, 800, w3, 200);
  rect(2*w3, 800, w3, 200);
  fill(255);
  stroke(0);
  equilatero(w6, 800+h3, 100, PI);
  equilatero(width/2, 800+h3, 100, -HALF_PI);
  equilatero(5*w6, 800+h3, 100, 0);
  textAlign(CENTER);
  text(fdir, width/2,50);
}

/* draw regular triangle */
void equilatero(float tx, float ty, float radi, float ang) {
  beginShape(TRIANGLE);
  float a;
  a=ang;
  for (int f=0; f<3; f++) {
    vertex(tx+radi*cos(a), 
      ty+radi*sin(a));
    a+=TWO_PI/3;
  }
  endShape();
}

/* detect mousepress / touch */
/* and manage buttons */
void mousePressed() {
  if (mouseY>800) {

    switch (int(3*mouseX/1000)) {
    case 0:
      /* left*/
      fdir+=PI/16;
      if (fdir>TWO_PI) fdir-=TWO_PI;
      printms("<");
      break;
    case 2:
      /* right */
      fdir-=PI/16;
      if (fdir<0) fdir+=TWO_PI;
      printms(">");
      break;
    case 1:
      int ix=round(xx/w+cos(fdir));
      int iy=round(yy/w+sin(fdir));
      
      printms ("test map["+ix+"]["+iy+"]");
      if ((ix>0) && (iy>0)) {
        if ((ix<ancho) && (iy<alto)) {
          if (mapa[int(ix)][int(iy)]!=-1) {
            printms("advance");
            xx+=w*cos(fdir);
            yy+=w*sin(fdir);
          } else {
            printms("hit wall");
          }
        } else {
          printms("out of upper bounds");
        }
      } else {
        printms("out of lower bounds");
      }
      break;
    };
  }
}

/* Map generator.
 Based on what I recall from
 something I typed from
 a ZX Spectrum magazine back in the
 eighties
 */
int generar() {

  int f;
  /* valid directions*/
  int[] dirValidas={};
  /* current direction*/
  int curDir=-1;

  // if (acabado) return;

  //printms(str(x)+","+str(y));

  /* check valid direction
   (directions within map and
   without a path)
   */
  for (f=0; f<4; f++) {
    if (testValida(int(x+dir[f].x*2), int(y+dir[f].y*2))) {
      //printms("dirvalida="+str(f));
      dirValidas=append(dirValidas, f);
      /* valid, push into array */
    } else {
      //printms("dir no valida="+str(f));
      /* invalid */
    }
  }
  //printms(dirValidas);
  if (dirValidas.length==0) {
    /* no valid direction, go back */
    if (mapa[x][y]>4) {
      /* we are back on first square */
      /* or we hit a map limiter wall */
      /* despite of tests against it */
      printms("acabado o error");
      acabado=true;
    } else {
      /* Going back */
      //printms("back");
      /* 
       How did we come here?
       add 180 degrees to it
       (curDir+2)
       so we go back
       */
      curDir=(mapa[x][y]+2)%4;
      /* always move 2 steps */
      for (f=0; f<2; f++) {
        mapa[x][y]=8;
        x+=dir[curDir].x;
        y+=dir[curDir].y;
        if (mapa[x][y]==5) acabado=true;
      }

      /* hemos acabado?*/
      if (mapa[x][y]==5) acabado=true;
    }
  } else {
    // Choose a random direction
    // from valid ones.
    //printms("forward to");
    curDir=dirValidas[int(random(dirValidas.length))];
    //printms(curDir);

    /* always move 2 steps */
    for (f=0; f<2; f++) {
      x+=dir[curDir].x;
      y+=dir[curDir].y;
      /* store direction on map
       so we can go back */
      mapa[x][y]=curDir;
    }
  }

  dibujar();

  if (acabado) {
    /* End Text */
    /* just in case we disable 3d view */
    textSize(40);
    fill(255);
    textAlign(CENTER);
    text("Acabado", height/2, width/2);
    textSize(38);
    fill(0);
    text("Acabado", height/2, width/2);

    return curDir;
  } else {
    return -1;
  }
}

/* Is (vx,vy) a valid square
 for new route?
 */
boolean testValida(int vx, int vy) {
  /* 1) test for boundary */
  if (vx<0|vx>=ancho) return false;
  if (vy<0|vy>=alto) return false;
  /* 2) test for used tile (>=0) */
  if (mapa[vx][vy]>=0 ) {
    //printms("["+str(vx)+"]["+str(vy)+"]="+str(mapa[vx][vy]));
    return false;
  } 
  return true;
}

/* draws 2d map at generation time */
void dibujar() {
  int f, g;
  for (f=0; f<alto; f++) {
    for (g=0; g<ancho; g++) {
      switch (mapa[f][g]) {
      case -1: 
        /* free square / wall */
        fill(0);
        break;
      case 9:
        /* borders*/
        fill(128, 0, 0);
        break;
      case 8: 
        /* no way out */
        fill(255);
        break;
      case 0: /* way N*/
        fill(255, 0, 0);
        break;
      case 1: /* way E*/
        fill(0, 255, 0);
        break;
      case 2: /* way S*/
        fill(128, 0, 128);
        break;
      case 3: /* way W*/
        fill(0, 128, 128);
        break;
      case 5: /* start/end */
        fill(255, 128, 128);
        break;
      }
      rectMode(CORNER);
      rect(g*w, f*h, w, h);
    }
  }
}
void printms(String A){
  println(A);
  messages=append(messages,A);
}
void showMessages(){
  int f;
  if (messages.length==0) return;
  if (messages.length>5){
    messages=subset(messages, messages.length-5,5);
  }
  textAlign(LEFT);
  fill(0);
  for (f=0;f<messages.length; f++){
    text(messages[f],50,f*50);
  }
}
```

---

<div class="post-metadata">

### Author: ![micuat](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/micuat/32/19407_2.png) [@micuat](https://discourse.processing.org/u/micuat)
#### Post date: [April 2, 2021, 4:53pm UTC](https://discourse.processing.org/t/3d-view-maze-generator-java-android/29057/5 "2021-04-02T16:53:29Z")

</div>

Making a PShape is definitely faster in rendering. Basically if you create a PShape it will be stored in the GPU memory. Regular begin/endShape (often called “immediate mode”) is more flexible but slower because you need to send instructions every frame to the GPU.
