# Back-face culling not working

I'm trying to figure out back-face culling, with little results.

My code, from what i've gathered:

    int a = tri[t].t[0], b = tri[t].t[1], c = tri[t].t[2]; // point identifiers from a triangle array
Point3D pa = draw[a], pb = draw[b], pc = draw[c]; // points of a triangle
Point3D la = new Point3D(pb.x - pa.x, pb.y - pa.y, pb.z - pa.z); // Line a
Point3D lb = new Point3D(pc.x - pa.x, pc.y - pa.y, pc.z - pa.z); // Line b

Point3D cross = new Point3D(la.y * lb.z - la.z * lb.y, la.z * lb.x - la.x * lb.z, la.x * lb.y - la.y * lb.x);

cross.x = cross.x * pa.x;
cross.y = cross.y * pa.y;
cross.z = cross.z * pa.z;

if(cross.z < 0.0){
// draw triangle
}


On the left looks like with the above code, after fixing it and adding projection, the culling is broken again, as seen on right.

So what I'm asking is, what am I doing wrong?

A whole example to duplicate gif:

• Updated for flexibiilty
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main extends JPanel {

public class LINE {
public LINE () {
this.slope = 0.0;
this.start = 0.0;
this.steps = 0.0;
this.end = 0.0;
this.rev = false;
}

public double slope, start, steps, end;
public boolean rev;

public void Calc(Point3D from, Point3D to){
Point3D a = from, b = to;
double slope = 0.0, steps = 0.0, start = 0.0, end = 0.0;
boolean rev = false;
if(a.x != b.x) slope = (b.y - a.y) / (b.x - a.x);

if(a.x != b.x && Math.abs(slope) <= 1.0){
if(a.x > b.x){
a = to;
b = from;
}
start = a.x;
end = b.x;
steps = a.y - slope * a.x;
} else {
if(a.y > b.y){
a = to;
b = from;
}
slope = (b.x - a.x) / (b.y - a.y);
start = a.y;
end = b.y;
steps = a.x - slope * a.y;
rev = true;
}
this.slope = slope;
this.end = end;
this.start = start;
this.rev = rev;
this.steps = steps;
}
}

static public class Point3D {
public Point3D (double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
public Point3D (int x, int y, int z) {
this.x = (double)x;
this.y = (double)y;
this.z = (double)z;
}
public double x, y, z;

}

static public class CONT {
CONT(int a, int b, int c){
this.t = new int[3];
t[0] = a;
t[1] = b;
t[2] = c;
}

public int[] t;

}
public Main () {

}

public static Point3D[] points = new Point3D[8], draw = new Point3D[8], calc = new Point3D[8];
public static int[][] lines = new int[6][4];
public static CONT[] tri = new CONT[12];
public static boolean[] dt = new boolean[12];
public static Point3D center = new Point3D(0, 0, 0);
public static Point3D rotation = new Point3D(0, 0, 0);

public static void main(String[] args){

JFrame frame = new JFrame();
Dimension size = new Dimension(640, 480);
Main canvas = new Main();
canvas.setPreferredSize(size);
canvas.setBackground(new Color(0, 0, 0));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocation(10, 10);
frame.setTitle("Title");
frame.setVisible(true);

points[0] = new Point3D(-1.0, -1.0, -1.0);
points[1] = new Point3D(1.0, -1.0, -1.0);
points[2] = new Point3D(1.0, 1.0, -1.0);
points[3] = new Point3D(-1.0, 1.0, -1.0);
points[4] = new Point3D(-1.0, -1.0, 1.0);
points[5] = new Point3D(1.0, -1.0, 1.0);
points[6] = new Point3D(1.0, 1.0, 1.0);
points[7] = new Point3D(-1.0, 1.0, 1.0);

draw[0] = new Point3D(-1.0, -1.0, -1.0);
draw[1] = new Point3D(1.0, -1.0, -1.0);
draw[2] = new Point3D(1.0, 1.0, -1.0);
draw[3] = new Point3D(-1.0, 1.0, -1.0);
draw[4] = new Point3D(-1.0, -1.0, 1.0);
draw[5] = new Point3D(1.0, -1.0, 1.0);
draw[6] = new Point3D(1.0, 1.0, 1.0);
draw[7] = new Point3D(-1.0, 1.0, 1.0);

calc[0] = new Point3D(-1.0, -1.0, -1.0);
calc[1] = new Point3D(1.0, -1.0, -1.0);
calc[2] = new Point3D(1.0, 1.0, -1.0);
calc[3] = new Point3D(-1.0, 1.0, -1.0);
calc[4] = new Point3D(-1.0, -1.0, 1.0);
calc[5] = new Point3D(1.0, -1.0, 1.0);
calc[6] = new Point3D(1.0, 1.0, 1.0);
calc[7] = new Point3D(-1.0, 1.0, 1.0);

lines[0][0] = 0;
lines[0][1] = 1;
lines[0][2] = 2;
lines[0][3] = 3;

lines[1][0] = 4;
lines[1][1] = 5;
lines[1][2] = 6;
lines[1][3] = 7;

lines[2][0] = 0;
lines[2][1] = 4;
lines[2][2] = 7;
lines[2][3] = 3;

lines[3][0] = 1;
lines[3][1] = 5;
lines[3][2] = 6;
lines[3][3] = 2;

lines[4][0] = 0;
lines[4][1] = 4;
lines[4][2] = 5;
lines[4][3] = 1;

lines[5][0] = 3;
lines[5][1] = 7;
lines[5][2] = 6;
lines[5][3] = 2;

tri[0] = new CONT(0, 1, 3);
tri[1] = new CONT(1, 2, 3);
tri[2] = new CONT(0, 4, 7);
tri[3] = new CONT(0, 3, 7);
tri[4] = new CONT(1, 5, 2);
tri[5] = new CONT(2, 6, 5);
tri[6] = new CONT(4, 5, 7);
tri[7] = new CONT(5, 6, 7);
tri[8] = new CONT(0, 4, 1);
tri[9] = new CONT(4, 5, 1);
tri[10] = new CONT(3, 7, 2);
tri[11] = new CONT(7, 6, 2);

for(int i = 0; i < 8; i++) {
center.x += points[i].x;
center.y += points[i].y;
center.z += points[i].z;
}
center.x /= 8;
center.y /= 8;
center.z /= 8;
while(true){
RotatePoints();
canvas.repaint();
try{
} catch(InterruptedException e) {}
}
}

public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(new Color(125, 23, 238));
LINE line = new LINE();
for(int t = 0; t < 12; t++){
int a = tri[t].t[0], b = tri[t].t[1], c = tri[t].t[2]; // point identifiers from a triangle array
Point3D pa = calc[a], pb = calc[b], pc = calc[c]; // points of a triangle
Point3D la = new Point3D(pb.x - pa.x, pb.y - pa.y, pb.z - pa.z); // Line a
Point3D lb = new Point3D(pc.x - pa.x, pc.y - pa.y, pc.z - pa.z); // Line b

Point3D cross = new Point3D(la.y * lb.z - la.z * lb.y, la.z * lb.x - la.x * lb.z, la.x * lb.y - la.y * lb.x);
double e = Math.sqrt(cross.x * cross.x + cross.y * cross.y + cross.z * cross.z);
cross.z /= e;
dt[t] = (cross.z < 0.0);
//cross.z /= e;
/*
double size = 20.0;
*/
}
double size = 120.0;
for(int p = 0; p < 8; p++){
draw[p].x = calc[p].x * size + 250.0;
draw[p].y = calc[p].y * size + 250.0;
draw[p].z = calc[p].z * size + 250.0;
}

for(int t = 0; t < 12; t++){
if(dt[t]){
int a = tri[t].t[0], b = tri[t].t[1], c = tri[t].t[2]; // point identifiers from a triangle array
Point3D pa = draw[a], pb = draw[b], pc = draw[c]; // points of a triangle
line.Calc(pa, pb);
for(; line.start < line.end; line.start += 1.0){
double w = line.slope * line.start + line.steps;
if(line.rev){
g.drawRect((int)w, (int)line.start, 1, 1);
} else {
g.drawRect((int)line.start, (int)w, 1, 1);
}
}

line.Calc(pb, pc);
for(; line.start < line.end; line.start += 1.0){
double w = line.slope * line.start + line.steps;
if(line.rev){
g.drawRect((int)w, (int)line.start, 1, 1);
} else {
g.drawRect((int)line.start, (int)w, 1, 1);
}
}
line.Calc(pc, pa);
for(; line.start < line.end; line.start += 1.0){
double w = line.slope * line.start + line.steps;
if(line.rev){
g.drawRect((int)w, (int)line.start, 1, 1);
} else {
g.drawRect((int)line.start, (int)w, 1, 1);
}
}
}
}

}

public static void RotatePoints(){
rotation.x += 0.25;
rotation.y += 0.5;
rotation.z += 0.75;

center = new Point3D(0, 0, 0);
for(int i = 0; i < 8; i++){
Point3D spot = points[i];//new Point3D(points[i].x, points[i].y, points[i].z);
calc[i].y = (cx * (spot.y - center.y) - sx * (spot.z - center.z)) + center.y;
calc[i].z = (sx * (spot.y - center.y) + cx * (spot.z - center.z)) + center.z;
spot = new Point3D(spot.x, calc[i].y, calc[i].z);
calc[i].x = (cy * (spot.x - center.x) - sy * (spot.z - center.z)) + center.x;
calc[i].z = (sy * (spot.x - center.x) + cy * (spot.z - center.z)) + center.z;
spot = new Point3D(calc[i].x, calc[i].y, calc[i].z);
calc[i].x = (cz * (spot.x - center.x) - sz * (spot.y - center.y)) + center.x;
calc[i].y = (cz * (spot.y - center.y) + sz * (spot.x - center.x)) + center.y;
}
}

}

• I don't know why you're multiplying cross by pa — can you clarify what the reasoning for that is, or what source suggested to do that? Commented Apr 21 at 14:11
• @DMGregory It is just what I ended up with, was just playing around with variables hoping to get a better result. Commented Apr 21 at 14:19
• What behaviour do you see without that? Can you record a fresh gif? Commented Apr 21 at 14:54
• @DMGregory The behaviour is, from what I can tell without posting another gif, exactly the same. I can't tell the difference. Commented Apr 21 at 15:00
• Next thing to check: are your cube's triangles wound the right way? Can you share the vertex/index data you're using? Commented Apr 21 at 15:40

Your triangles have a mix of clockwise and counterclockwise winding.

Try these indices instead, which should all have the same winding:

tri[0] = new CONT(0, 1, 3);
tri[1] = new CONT(1, 2, 3);
tri[2] = new CONT(0, 7, 4);
tri[3] = new CONT(0, 3, 7);
tri[4] = new CONT(1, 5, 2);
tri[5] = new CONT(2, 5, 6);
tri[6] = new CONT(4, 6, 5);
tri[7] = new CONT(4, 7, 6);
tri[8] = new CONT(0, 4, 1);
tri[9] = new CONT(4, 5, 1);
tri[10] = new CONT(3, 2, 7);
tri[11] = new CONT(7, 2, 6);


You can also delete these lines, since they don't change the sign of cross.z:

 double e = Math.sqrt(cross.x * cross.x + cross.y * cross.y + cross.z * cross.z);
cross.z /= e;

• This worked very well, and now the cubes cull accordingly. Next step I tried adding projection, which sort of broke the culling, every triangle but the front are drawn. Commented Apr 22 at 14:16
• Flip the sign of your cross.z test or reverse the winding of the triangles then. Commented Apr 22 at 19:49