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);
}
```