Clip not behaving as expected in relation to graphic transformations (eg rotateX)

why does clip not behave as expected?

as my clip seems to be rotating the entire boarder instead of the contents inside of the border

clipping to GRAPHICS  400 400 200 200
clipping to WINDOW  3 20 194 177
clipping to WINDOW FINAL  403 420 594 577

even tho i have, for example

    graphics.clip(x,y,width,height);
    clearScreen();
    drawBorders();
    graphics.clip(x+window.x, y+window.y, x+window.width, y+window.height);
    // this should ONLY be affecting the clipped region
    graphics.lights();
    graphics.background(0);
    graphics.noStroke();
    graphics.translate(width/2, height/2);
    graphics.rotateX(frameCount/100.0);
    graphics.rotateY(frameCount/200.0);
    graphics.box(40);

this my expected display result

this expected result is achieved by using a single PGraphics object per window, unfortunately this produces scalable overhead: the FPS drops more as the number of windows increases due to beginDraw() and endDraw() being called 60 times per second for each window being displayed

for instance, with 5 windows i get 31 to 39 FPS, for 16 windows i get 11 FPS

this is my application

Compositor compositor;

void settings() {
  fullScreen(P3D); // 60 fps
  //size(400, 400, P3D); // 60 fps
}

class Applications_Cube extends Window {

  @Override
    void draw() {
    graphics.lights();
    graphics.background(0);
    graphics.noStroke();
    graphics.translate(width/2, height/2);
    graphics.rotateX(frameCount/100.0);
    graphics.rotateY(frameCount/200.0);
    graphics.box(40);
  }
}

void addApplications() {
  compositor.add(new Applications_Cube(), 200, 200);
  compositor.setLocation(0, 0);
  compositor.add(new Applications_Cube(), 200, 200);
  compositor.setLocation(0, 200);
  compositor.add(new Applications_Cube(), 200, 200);
  compositor.setLocation(200, 0);
  compositor.add(new Applications_Cube(), 200, 200);
  compositor.setLocation(200, 200);
  compositor.add(new Applications_Cube(), 200, 200);
  compositor.setLocation(400, 400);
}

void setup() {
  compositor = new Compositor(width, height);
  compositor.displayFPS = true;
  addApplications();
  compositor.setup();
}

void draw() {
  compositor.draw();
}

class Compositor {
  public PGraphics graphics;
  ArrayList<WindowObject> windows = new ArrayList<WindowObject>();
  WindowObject w;
  int windowFocus = -1;
  int lastWindowFocus = -1;
  boolean displayFPS = false;

  Compositor(int width, int height) {
    graphics = createGraphics(width, height, P3D);
  }

  void add(Window window, int width, int height) {
    w = new WindowObject(width, height);
    windows.add(w);
    w.attach(window);
  }

  void setLocation(int x, int y) {
    w.x = x;
    w.y = y;
  }

  void setup() {
    graphics.beginDraw();
    for (WindowObject window : windows) {
      graphics.clip(0,0,width,height);
      graphics.clip(window.x, window.y, window.width, window.height);
      window.graphics = graphics;
      window.setup();
      graphics.clip(0,0,width,height);
    }
    if (displayFPS) {
      int oldColor = graphics.fillColor;
      graphics.fill(255);
      graphics.textSize(16);
      graphics.text("FPS: " + frameRate, 10, 20);
      graphics.fill(oldColor);
    }
    graphics.endDraw();
    image(graphics, 0, 0, width, height);
  }

  void draw() {
    graphics.beginDraw();
    graphics.background(0);
    for (WindowObject window : windows) {
      graphics.clip(0,0,width,height);
      graphics.clip(window.x, window.y, window.width, window.height);
      window.graphics = graphics;
      window.draw();
      graphics.clip(0,0,width,height);
    }
    if (displayFPS) {
      int oldColor = graphics.fillColor;
      graphics.fill(255);
      graphics.textSize(16);
      graphics.text("FPS: " + frameRate, 10, 20);
      graphics.fill(oldColor);
    }
    graphics.endDraw();
    image(graphics, 0, 0, width, height);
  }
}

class Window {
  public PGraphics graphics = null;

  int height;
  int width;
  int x, y;
  int mouseX, mouseY;
  Window() {
  } // implicit super constructor required
  void onBeforeResize() {
  }
  String onRequestType() { 
    return P3D;
  }
  void onAfterResize() {
  }
  void setup() {
    graphics.background(0);
  }
  void draw() {
    graphics.background(0);
  }
}

class WindowObject {
  public PGraphics graphics;
  Window window;

  int height;
  int width;
  int x;
  int y;

  int borderTop = 20;
  int borderLeft = 3;
  int borderBottom = 3;
  int borderRight = 3;

  WindowObject() {
  } // implicit super constructor required

  WindowObject(int width, int height) {
    this.width = width;
    this.height = height;
  }

  void attach(Window window) {
    this.window = window;

    this.window.x = borderLeft;
    this.window.width = width-borderLeft-borderRight;

    this.window.y = borderTop;
    this.window.height = height-borderTop-borderBottom;
  }

  void clearScreen() {
    graphics.background(0);
  }

  void drawBordersWithFill(int fill__) {
    graphics.rectMode(CORNER);
    graphics.stroke(0);
    graphics.fill(fill__);
    graphics.rect(0, 0, width, height, 10);
  }
  
  void drawBorders() {
    drawBordersWithFill(87);
  }
  
  void setup() {
    graphics.clip(x,y,width,height);
    clearScreen();
    drawBorders();
    graphics.clip(x+window.x, y+window.y, x+window.width, y+window.height);
    window.graphics = graphics;
    window.setup();
    graphics.clip(x,y,width,height);
  }

  void draw() {
    graphics.clip(x,y,width,height);
    clearScreen();
    drawBorders();
    graphics.clip(x+window.x, y+window.y, x+window.width, y+window.height);
    // this should ONLY be affecting the clipped region
    graphics.lights();
    graphics.background(0);
    graphics.noStroke();
    graphics.translate(width/2, height/2);
    graphics.rotateX(frameCount/100.0);
    graphics.rotateY(frameCount/200.0);
    graphics.box(40);
  }
}
1 Like

I haven’t dug into your layer of classes, but a first guess is that it looks like you aren’t using pushMatrix() / popMatrix() to remove your translations and rotations from PGraphics matrix once you are done with them – you may also need to use pushStyle() / popStyle(), e.g. for noStroke etc.

Here is a quick demo of what I think (???) you are trying to do – pass the global canvas into your window objects, then have each window object draw to that canvas, in a clipped way, within its own coordinate space and style space on the global stack.

I have simplified this example to show only what I believe (?) is at issue – it is much easier to experiment with a simple example than to work through a nested set of inherited and encapsulated classes. Perhaps work out the basics of how coordinates and style work first in simpler examples, then use that to engineer your whole framework.

Win w1, w2;

void setup() {
  size(200, 200);
  
  // concentric circles
  w1 = new Win(g, 25, 25, 100, 100) {
    @Override public void content() {
      pg.noFill();
      pg.translate(w/2, h/2);
      for (int i=(int)(width*1.6); i>mouseX; i-=6) {
        pg.ellipse(0, 0, i, i);
      }
    }
  };
  
  // a rotated rectangle
  w2 = new Win(g, 75, 75, 100, 100) {
    @Override public void content() {
      pg.fill(128);
      pg.rotate(PI/4 + mouseX/300.0);
      pg.rect(0,0,2*w, h);
    }
  };
}

void draw() {
  background(192);
  // simple interaction
  w1.x = mouseX;
  w1.y = mouseY;
  // draw windows
  w1.show();
  w2.show();
}

class Win {
  PGraphics pg;
  int x, y, w, h;
  Win(PGraphics pg, int x, int y, int w, int h) {
    // store drawing target and frame.
    this.pg = pg;
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
  }
  // default is main sketch canvas, PApplet.g
  Win(int x, int y, int w, int h) {
    this(g, x, y, w, h);
  }

  // draw a frame, set contents clipping, coords, and style 
  void show() {
    pg.beginDraw();
    // object defined by contents area, frame-around
    pg.rect(x-1, y-1, w+1, h+1);
    pg.clip(x, y, w, h);
    // ...or frame-in, could be confusing for object developers
    //pg.rect(x, y, w, h);
    //pg.clip(x+1, y+1, w-1, h-1);
    pg.pushMatrix();
    pg.pushStyle();
    pg.translate(x, y);
    this.content();
    pg.popStyle();
    pg.popMatrix();
    pg.noClip();
    pg.endDraw();
  }

  // override for window contents
  void content() {
  }
}

WindowClipping--screenshot

1 Like

interesting, however this unfortunately relies on beginDraw and endDraw, which is gets slower as more windows are rendered

Only to be generic. If you are passing g, just delete those beginDraw / endDraw lines – it works fine. They are redundant across objects, and in fact in this case you don’t even need one because the context is already created by the main sketch draw().

Try it.

1 Like

this seems to fail

graphics.clip(x+window.x, y+window.y, x+window.width-100, y+window.height-100);

i should be seeing the exact same as top left window, in my other three windows

image

also this appears to be mirrored

image

class Compositor {
  public PGraphics graphics;
  ArrayList<WindowObject> windows = new ArrayList<WindowObject>();
  WindowObject w;
  int windowFocus = -1;
  int lastWindowFocus = -1;
  boolean displayFPS = false;

  Compositor(int width, int height) {
    graphics = createGraphics(width, height, P3D);
  }

  void add(Window window, int width, int height) {
    w = new WindowObject(width, height);
    windows.add(w);
    w.attach(window);
  }

  void setLocation(int x, int y) {
    w.x = x;
    w.y = y;
  }

  void setup() {
    graphics.beginDraw();
    for (WindowObject window : windows) {
      graphics.noClip();
      graphics.clip(window.x, window.y, window.width, window.height);
      window.graphics = graphics;
      graphics.pushMatrix();
      graphics.pushStyle();
      graphics.translate(window.x, window.y);
      window.setup();
      graphics.popStyle();
      graphics.popMatrix();
      graphics.noClip();
    }
    if (displayFPS) {
      int oldColor = graphics.fillColor;
      graphics.fill(255);
      graphics.textSize(16);
      graphics.text("FPS: " + frameRate, 10, 20);
      graphics.fill(oldColor);
    }
    graphics.endDraw();
    image(graphics, 0, 0, width, height);
  }

  void draw() {
    graphics.beginDraw();
    graphics.background(0);
    for (WindowObject window : windows) {
      graphics.noClip();
      graphics.clip(window.x, window.y, window.width, window.height);
      window.graphics = graphics;
      graphics.pushMatrix();
      graphics.pushStyle();
      graphics.translate(window.x, window.y);
      window.draw();
      graphics.popStyle();
      graphics.popMatrix();
      graphics.noClip();
    }
    if (displayFPS) {
      int oldColor = graphics.fillColor;
      graphics.fill(255);
      graphics.textSize(16);
      graphics.text("FPS: " + frameRate, 10, 20);
      graphics.fill(oldColor);
    }
    graphics.endDraw();
    image(graphics, 0, 0, width, height);
  }
}

class Window {
  public PGraphics graphics = null;

  int height;
  int width;
  int x, y;
  int mouseX, mouseY;
  Window() {
  } // implicit super constructor required
  void onBeforeResize() {
  }
  String onRequestType() { 
    return P3D;
  }
  void onAfterResize() {
  }
  void setup() {
    graphics.background(0);
  }
  void draw() {
    graphics.background(0);
  }
}

class WindowObject {
  public PGraphics graphics;
  Window window;

  int height;
  int width;
  int x;
  int y;

  int borderTop = 20;
  int borderLeft = 3;
  int borderBottom = 3;
  int borderRight = 3;

  WindowObject() {
  } // implicit super constructor required

  WindowObject(int width, int height) {
    this.width = width;
    this.height = height;
  }

  void attach(Window window) {
    this.window = window;

    this.window.x = borderLeft;
    this.window.width = width-borderLeft-borderRight;

    this.window.y = borderTop;
    this.window.height = height-borderTop-borderBottom;
  }

  void clearScreen() {
    graphics.background(0);
  }

  void drawBordersWithFill(int fill__) {
    graphics.rectMode(CORNER);
    graphics.stroke(0);
    graphics.fill(fill__);
    graphics.rect(0, 0, width, height, 10);
  }
  
  void drawBorders() {
    drawBordersWithFill(87);
  }
  
  void setup() {
    clearScreen();
    drawBorders();
    graphics.clip(x+window.x, y+window.y, x+window.width, y+window.height);
    window.graphics = graphics;
    window.setup();
  }

  void draw() {
    clearScreen();
    drawBorders();
    graphics.clip(x+window.x, y+window.y, x+window.width-100, y+window.height-100);
    // this should ONLY be affecting the clipped region
    graphics.lights();
    graphics.background(0);
    graphics.noStroke();
    graphics.translate(width/2, height/2);
    graphics.rotateX(frameCount/100.0);
    graphics.rotateY(frameCount/200.0);
    graphics.box(40);
  }
}

I think that might be normal 3D camera perspective because you are using P3D with one canvas. Create a P3D sketch with four box() calls in draw and it will do that.

I’m not sure what you are trying to do as window content, but if you want 3D content and you want it to stay the same regardless of screen location then you could try orthographic perspective with ortho().

Otherwise you might need separate canvases or you might (?) be able to update camera(), although I’m not sure if that will work or not with deferred endDraw().

In you Compositor.draw, you set clipping, then you translate, then you call window.draw

You then set clipping again in WindowObject.draw! This clears the previous clipping. Also, you translated before you set it, so this clipping is double-shifted, w * 2, h * 2.

so orth works, however there is still the issue of… this

in which the borders are not being drawn fully

yes, as first i draw the border, then i clip to inside the border to draw my content:

// cube.pde
    graphics.lights();
    graphics.background(0);
    graphics.noStroke();
    graphics.translate(width/2, height/2);
    graphics.rotateX(frameCount/100.0);
    graphics.rotateY(frameCount/200.0);
    graphics.box(40);

also even with orth, clipping to graphics.clip(x+window.x, y+window.y, x+window.width-100, y+window.height-100); still gives image (minus the pre-orth rendering)

also have a look at PGraphics framebuffer can be rendered in `noLoop` mode but cannot be rendered in `loop` mode

as if i can find a way to grab the PGraphics framebuffer data as quickly as possible with as little performance impact as possible then i could in theory just directly render the framebuffer directly to g (sketch PGraphics) and then figure out a way to set up and switch to multiple framebuffers without needing to call endDraw(); as a way of quick switching the PGraphic framebuffer associated with the main PGraphics FBO stack

eg

switchFramebuffer(g2);
g2.draw();
g2.render();
switchFramebuffer(g3);
g3.draw();
g3.render();

as this clipping approach does look promising, however it seems to present more problems than it solves

In that case every WindowObject x, y should be eg 3, 5 (just a frame offset, and each should be identical). If it holds absolute coordinates and you are using it after translating by it, then you are doing it wrong.

Your code above just shows the Compositor / Window / WindowObject classes, so I can’t tell how you are using it. However I can guess from the screenshot that your translate is incorrectly shifting your second clipping coordinates.

…or perhaps it is also the “-100”. Honestly, this calls for an MCVE.

I would be interested in seeing an MCVE as well. :)

For anyone who is interested:

:)

I’m lost here. Could you please elaborate on the working of this method?

@noel – this is a class that is designed to render arbitrary code with an empty method, “content”.

If you look at setup, you will see a new object declared. Normally it would look like this:

  w1 = new Win(g, 25, 25, 100, 100);

but instead it looks like this:

  w1 = new Win(g, 25, 25, 100, 100) {
    @Override public void content() {
      pg.noFill();
      pg.translate(w/2, h/2);
      for (int i=(int)(width*1.6); i>mouseX; i-=6) {
        pg.ellipse(0, 0, i, i);
      }
    }
  };

…because, when the object is declared, its custom content() method is defined as drawing some circles – just for that one object, not for the class. So the object definition says “create a new Win 25,25,100,100 … and its content method should be: draw some circles.” If you don’t, the Win will have an empty content method, and it will draw nothing inside its frame.