PDF renderer drawing shapes in different order then the Sketch

Hi everyone, i am having a problem exporting my sketch to PDF.

As you can see on the Sketch’s screenshot, everything looks as it should. However the result i get on PDF, the other screenshot, is missing some elements, noticeably part of the building on the left side of the model.

Luckily it’s easy to see that the wall is indeed being drawn to PDF, however it is draw BEFORE the floor and other shapes, so it gets hidden. See the video of the PDF reader rendering the scene presumably by the order it was export.

This city is a single model, so the drawing order should be the same for the renderer and the PDF export, but its not. I am using the beginRaw() / endRaw() and hint(PConstants.DISABLE_OPTIMIZED_STROKE) to prevent strokes from being draw over shapes. Is there another hint that solves my problem or can i order the shapes manually before exporting?

Sketch:

PDF Reader:

Video: https://streamable.com/jtkdwc

1 Like

Hi @LuisFerreira,

Is the model stored in a PShape? How are the faces of the model ordered? Would it be possible to sort them according to distance of a face’s center along the look direction from the camera – in Processing or otherwise? (There’s more than one way to define and calculate the center… or you could use any other criteria you choose with a comparator function.)

Are you using PGraphics3D (P3D) for the first screen shot? If so, it inherits hint from PGraphicsOpenGL; the source code is here. Specifically, have a look at ENABLE_DEPTH_SORT and DISABLE_DEPTH_SORT.

1 Like

Is the model stored in a PShape ?

Yes.

How are the faces of the model ordered?

The main PShape has no vertexes, just children, about 12000 of them. So it basically has 2 levels, but as for the order of each shape (all triangles) i don’t know, or at least i didn’t change it.

Would it be possible to sort them according to distance of a face’s center along the look direction from the camera – in Processing or otherwise?

I think it is, but i would prefer a solution that wouldn’t need “model specific” code, since i plan to change the model later and i don’t want to re-code a whole new ordering algorithm for each shape i load. And if it renders correctly in P3D, without any re-ordering or hinting, it should work on PDF as well right?

Are you using PGraphics3D ( P3D ) for the first screen shot?

Yes, P3D for the sketch shot, and PDF for the pdf video and shot.

Specifically, have a look at ENABLE_DEPTH_SORT and DISABLE_DEPTH_SORT.

i will test this again although i already did. But i didn’t see any change in the PDF export when i turn these on and off. Thanks for the link

1 Like

Hi @LuisFerreira ,

I’ll post code – still rather messy – and a picture. If you’re still interested, I’d be happy to explain more. If you’re not interested, or if someone comes up with an easier one, no harm no foul. The model of a heart I used came from here. I reduced the number of faces in the model in Blender and converted from .stl to .obj.


Notice that there are artifacts, or seams, between the faces. So that’s a drawback to this approach.

Best,
Jeremy

import processing.pdf.*;
import java.util.*;

boolean recording = true;
PShape original;
ArrayList<PShape> faces;
ArrayList<PShape> projected;
PGraphics3D rndr;

void setup() {
  size(512, 512, P3D);

  rndr = (PGraphics3D)getGraphics();
  rndr.hint(DISABLE_DEPTH_TEST);
  rndr.hint(DISABLE_DEPTH_MASK);
  rndr.hint(DISABLE_DEPTH_SORT);
  rndr.perspective();
  rndr.camera(
    0.0, 0.0, rndr.height * 0.86602,
    0.0, 0.0, 0.0,
    0.0, 1.0, 0.0);

  original = loadShape("heartModel.obj");

  // Extract faces from model.
  int len = original.getChildCount();
  faces = new ArrayList<PShape>(len);
  for (int i = 0; i < len; ++i) {
    PShape face = original.getChild(i);
    faces.add(face);
  }

  // Apply transform to face vertices permanently;
  // do not use shape transform.
  PMatrix3D mat = new PMatrix3D();
  mat.rotate(THIRD_PI * 0.5, 0.0, 1.0, 0.0);
  mat.scale(15.0);

  PVector temp = new PVector();
  PVector transformed = new PVector();
  for (PShape face : faces) {
    int vertCount = face.getVertexCount();
    for (int i = 0; i < vertCount; ++i) {
      PVector vertex = face.getVertex(i, temp);
      mat.mult(vertex, transformed);
      face.setVertex(i, transformed);
    }
  }

  Collections.sort(faces, new FaceComparator());

  // Project from 3D world to 2D screen.
  projected = new ArrayList<PShape>(len);
  PVector projVert = new PVector();
  for (int i = 0; i < len; ++i) {
    PShape face = faces.get(i);
    int c = lerpColor(#ff0000, #00ffff, i / (len - 1.0), HSB);
    face.setFill(c);
    face.setStroke(false);

    int vertCount = face.getVertexCount();
    PShape projectedFace = createShape();
    projectedFace.beginShape();
    for (int j = 0; j < vertCount; ++j) {
      PVector vertex = face.getVertex(j, temp);
      screen(rndr, vertex, projVert);
      projectedFace.vertex(projVert.x, projVert.y);
    }
    projectedFace.endShape();

    projectedFace.setFill(c);
    projectedFace.setStroke(false);

    projected.add(projectedFace);
  }
}

void draw() {
  if (recording) {
    beginRecord(PDF, "foo.pdf");
  }

  background(#fff7d5);

  if (!recording) {
    // DISABLE_DEPTH_TEST does not provide a solution,
    // it helps diagnose the problem.
    if (mousePressed) {
      shape(original);
    } else {
      for (PShape face : faces) {
        shape(face);
      }
    }
  } else {
    for (PShape projFace : projected) {
      shape(projFace);
    }
  }

  if (recording) {
    endRecord();
    recording = false;
  }
}

void keyReleased() {
  recording = true;
}

static class FaceComparator implements Comparator<PShape> {

  int compare(PShape a, PShape b) {

    PVector aVec = new PVector();
    PVector bVec = new PVector();

    PVector aMean = new PVector();
    PVector bMean = new PVector();

    // Find mean center of a.
    int aVertCount = a.getVertexCount();
    for (int i = 0; i < aVertCount; ++i) {
      PVector.add(aMean, a.getVertex(i, aVec), aMean);
    }
    if (aVertCount != 0) {
      PVector.div(aMean, aVertCount, aMean);
    }

    // Find mean center of b.
    int bVertCount = b.getVertexCount();
    for (int i = 0; i < bVertCount; ++i) {
      PVector.add(bMean, b.getVertex(i, bVec), bMean);
    }
    if (bVertCount != 0) {
      PVector.div(bMean, bVertCount, bMean);
    }

    return bMean.z > aMean.z ? -1 :
      aMean.z > bMean.z ? 1 : 0;
  }
}

PVector screen(PGraphicsOpenGL rndr, PVector source, PVector target) {
  return screen(rndr, source.x, source.y, source.z, target);
}

PVector screen (
  PGraphicsOpenGL rndr,
  float xSource,
  float ySource,
  float zSource,
  PVector target ) {

  screen1s(rndr, xSource, ySource, zSource, target);
  return target.set(
    rndr.width * ( 1.0 + target.x ) * 0.5,
    rndr.height * ( 1.0 - ( 1.0 + target.y ) * 0.5 ),
    ( 1.0 + target.z ) * 0.5);
}

PVector screen1s (
  PGraphicsOpenGL rndr,
  float xSource,
  float ySource,
  float zSource,
  PVector target ) {

  float aw = rndr.modelview.m30 * xSource +
    rndr.modelview.m31 * ySource +
    rndr.modelview.m32 * zSource +
    rndr.modelview.m33;

  float ax = rndr.modelview.m00 * xSource +
    rndr.modelview.m01 * ySource +
    rndr.modelview.m02 * zSource +
    rndr.modelview.m03;

  float ay = rndr.modelview.m10 * xSource +
    rndr.modelview.m11 * ySource +
    rndr.modelview.m12 * zSource +
    rndr.modelview.m13;

  float az = rndr.modelview.m20 * xSource +
    rndr.modelview.m21 * ySource +
    rndr.modelview.m22 * zSource +
    rndr.modelview.m23;

  float bw = rndr.projection.m30 * ax +
    rndr.projection.m31 * ay +
    rndr.projection.m32 * az +
    rndr.projection.m33 * aw;

  if ( bw == 0.0 ) {
    return target.set(0.0, 0.0, 0.0);
  }

  float bx = rndr.projection.m00 * ax +
    rndr.projection.m01 * ay +
    rndr.projection.m02 * az +
    rndr.projection.m03 * aw;

  float by = rndr.projection.m10 * ax +
    rndr.projection.m11 * ay +
    rndr.projection.m12 * az +
    rndr.projection.m13 * aw;

  float bz = rndr.projection.m20 * ax +
    rndr.projection.m21 * ay +
    rndr.projection.m22 * az +
    rndr.projection.m23 * aw;

  if ( bw != 1.0 ) {
    float wInv = 1.0 / bw;
    return target.set(
      bx * wInv,
      by * wInv,
      bz * wInv);
  }

  return target.set(bx, by, bz);
}
1 Like

Hi again,

Nevermind, I double checked the reference for beginRaw (as opposed to beginRecord as used above) and read the important part here:

Using hint(ENABLE_DEPTH_SORT) can improve the appearance of 3D geometry drawn to 2D file formats.

I assumed wrongly that depth sorting was enabled by default and that you’d still get improperly sorted faces with it on.

Best,
Jeremy

1 Like

Thank you for the help behreajj. I like the idea of your FaceComparator but i haven’t tried it. I did try again with and without hints and I couldn’t find a way to draw the faces in the right order.

I wanted to have this feature in a library i am developing, you can check it at: https://github.com/LFBFerreira/MediaExport

1 Like