Created
July 22, 2024 18:12
-
-
Save OrangoMango/a2e712ece044d3ee382686d96921c250 to your computer and use it in GitHub Desktop.
3D rendering in the console
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CubeRenderer{ | |
private static class Point3D{ | |
private double x, y, z; | |
public Point3D(double x, double y, double z){ | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
} | |
public double getX(){ | |
return this.x; | |
} | |
public double getY(){ | |
return this.y; | |
} | |
public double getZ(){ | |
return this.z; | |
} | |
} | |
private static class Mesh{ | |
private Point3D[][] triangles; | |
private String[] faces; | |
public Mesh(Point3D[] vertices, int[][] faces, String[] type){ | |
this.faces = type; | |
this.triangles = new Point3D[faces.length][3]; | |
for (int i = 0; i < faces.length; i++){ | |
this.triangles[i][0] = vertices[faces[i][0]]; | |
this.triangles[i][1] = vertices[faces[i][1]]; | |
this.triangles[i][2] = vertices[faces[i][2]]; | |
} | |
} | |
private Point3D rotate(Point3D pt){ | |
double[] point = new double[]{pt.getX(), pt.getY(), pt.getZ()}; | |
double[] p1 = CubeRenderer.multiply(CubeRenderer.getRotateX(0.05), point); | |
p1 = CubeRenderer.multiply(CubeRenderer.getRotateY(0.05), p1); | |
p1 = CubeRenderer.multiply(CubeRenderer.getRotateZ(0.05), p1); | |
return new Point3D(p1[0], p1[1], p1[2]); | |
} | |
public void update(){ | |
for (int i = 0; i < this.triangles.length; i++){ | |
this.triangles[i][0] = rotate(this.triangles[i][0]); | |
this.triangles[i][1] = rotate(this.triangles[i][1]); | |
this.triangles[i][2] = rotate(this.triangles[i][2]); | |
} | |
} | |
public void render(String[][] canvas, double[][] depthBuffer){ | |
for (int n = 0; n < this.triangles.length; n++){ | |
Point3D a = this.triangles[n][0]; | |
Point3D b = this.triangles[n][1]; | |
Point3D c = this.triangles[n][2]; | |
int x1 = (int)((a.getX()+1)/2*(WIDTH-1)); | |
int y1 = (int)((a.getY()+1)/2*(HEIGHT-1)); | |
int z1 = (int)((a.getZ()+1)/2*(DEPTH-1)); | |
int x2 = (int)((b.getX()+1)/2*(WIDTH-1)); | |
int y2 = (int)((b.getY()+1)/2*(HEIGHT-1)); | |
int z2 = (int)((b.getZ()+1)/2*(DEPTH-1)); | |
int x3 = (int)((c.getX()+1)/2*(WIDTH-1)); | |
int y3 = (int)((c.getY()+1)/2*(HEIGHT-1)); | |
int z3 = (int)((c.getZ()+1)/2*(DEPTH-1)); | |
if (y2 < y1){ | |
y1 = swap(y2, y2 = y1); | |
x1 = swap(x2, x2 = x1); | |
z1 = swap(z2, z2 = z1); | |
} | |
if (y3 < y1){ | |
y1 = swap(y3, y3 = y1); | |
x1 = swap(x3, x3 = x1); | |
z1 = swap(z3, z3 = z1); | |
} | |
if (y3 < y2){ | |
y2 = swap(y3, y3 = y2); | |
x2 = swap(x3, x3 = x2); | |
z2 = swap(z3, z3 = z2); | |
} | |
int dx1 = x2-x1; | |
int dy1 = y2-y1; | |
double dz1 = z2-z1; | |
int dx2 = x3-x1; | |
int dy2 = y3-y1; | |
double dz2 = z3-z1; | |
double dax_step = 0, dbx_step = 0, dz1_step = 0, dz2_step = 0; | |
double finalZ; | |
if (dy1 != 0) dax_step = dx1/(double)Math.abs(dy1); | |
if (dy2 != 0) dbx_step = dx2/(double)Math.abs(dy2); | |
if (dy1 != 0) dz1_step = dz1/Math.abs(dy1); | |
if (dy2 != 0) dz2_step = dz2/Math.abs(dy2); | |
if (dy1 != 0){ | |
for (int i = y1; i <= y2; i++){ | |
int ax = x1+(int)((i-y1)*dax_step); | |
int bx = x1+(int)((i-y1)*dbx_step); | |
double sz = z1+(i-y1)*dz1_step; | |
double ez = z1+(i-y1)*dz2_step; | |
if (ax > bx){ | |
ax = swap(bx, bx = ax); | |
sz = swap(ez, ez = sz); | |
} | |
double tstep = 1.0/(bx-ax); | |
double t = 0.0; | |
for (int j = ax; j < bx; j++){ | |
finalZ = (1-t)*sz+t*ez; | |
putInBuffer(depthBuffer, j, i, 1/finalZ); | |
putInCanvas(canvas, j, i, this.faces[n], depthBuffer, 1/finalZ); | |
t += tstep; | |
} | |
} | |
} | |
dx1 = x3-x2; | |
dy1 = y3-y2; | |
dz1 = z3-z2; | |
if (dy1 != 0) dax_step = dx1/(double)Math.abs(dy1); | |
dz1_step = 0; | |
if (dy1 != 0) dz1_step = dz1/Math.abs(dy1); | |
if (dy1 != 0){ | |
for (int i = y2; i <= y3; i++){ | |
int ax = x2+(int)((i-y2)*dax_step); | |
int bx = x1+(int)((i-y1)*dbx_step); | |
double sz = z2+(i-y2)*dz1_step; | |
double ez = z1+(i-y1)*dz2_step; | |
if (ax > bx){ | |
ax = swap(bx, bx = ax); | |
sz = swap(ez, ez = sz); | |
} | |
double tstep = 1.0/(bx-ax); | |
double t = 0.0; | |
for (int j = ax; j < bx; j++){ | |
finalZ = (1-t)*sz+t*ez; | |
putInBuffer(depthBuffer, j, i, 1/finalZ); | |
putInCanvas(canvas, j, i, this.faces[n], depthBuffer, 1/finalZ); | |
t += tstep; | |
} | |
} | |
} | |
} | |
} | |
} | |
private static final int WIDTH = 100; | |
private static final int HEIGHT = 50; | |
private static final int DEPTH = 50; | |
public static void main(String[] args) throws InterruptedException{ | |
double size = 0.5; | |
Mesh mesh = new Mesh(new Point3D[]{new Point3D(0, 0, 0), new Point3D(0, size, 0), new Point3D(size, size, 0), new Point3D(size, 0, 0), | |
new Point3D(0, 0, size), new Point3D(0, size, size), new Point3D(size, size, size), new Point3D(size, 0, size)}, | |
new int[][]{{0, 1, 2}, {0, 2, 3}, {3, 2, 6}, | |
{3, 6, 7}, {7, 6, 5}, {7, 5, 4}, | |
{4, 5, 1}, {4, 1, 0}, {1, 5, 6}, | |
{1, 6, 2}, {4, 0, 3}, {4, 3, 7}}, new String[]{"#", "#", "$", "$", "*", "*", ",", ",", "-", "-", ":", ":"}); | |
String[][] canvas = new String[WIDTH][HEIGHT]; | |
double[][] depthBuffer = new double[WIDTH][HEIGHT]; | |
while (true){ | |
System.out.print("\033[H"); | |
for (int i = 0; i < WIDTH; i++){ | |
for (int j = 0; j < HEIGHT; j++){ | |
canvas[i][j] = String.valueOf(' '); // Background | |
depthBuffer[i][j] = 0; | |
} | |
} | |
mesh.update(); | |
mesh.render(canvas, depthBuffer); | |
// Render | |
for (int y = 0; y < HEIGHT; y++){ | |
for (int x = 0; x < WIDTH; x++){ | |
System.out.print(canvas[x][y]); | |
} | |
System.out.println(); | |
} | |
Thread.sleep(50); | |
} | |
} | |
public static <T> T swap(T a, T b){ | |
return a; | |
} | |
public static void putInBuffer(double[][] buffer, int x, int y, double value){ | |
if (x >= 0 && y >= 0 && x < WIDTH && y < HEIGHT){ | |
if (value >= buffer[x][y]) buffer[x][y] = value; | |
} | |
} | |
public static void putInCanvas(String[][] canvas, int x, int y, String pixel, double[][] db, double v){ | |
if (x >= 0 && y >= 0 && x < WIDTH && y < HEIGHT){ | |
if (v >= db[x][y]) canvas[x][y] = pixel; | |
} | |
} | |
public static double[] multiply(double[][] mat, double[] vect){ | |
double[] out = new double[mat.length]; | |
for (int i = 0; i < out.length; i++){ | |
double sum = 0; | |
for (int j = 0; j < vect.length; j++){ | |
sum += mat[i][j]*vect[j]; | |
} | |
out[i] = sum; | |
} | |
return out; | |
} | |
public static double[][] getRotateX(double angle){ | |
return new double[][]{ | |
{1, 0, 0, 0}, | |
{0, Math.cos(angle), -Math.sin(angle), 0}, | |
{0, Math.sin(angle), Math.cos(angle), 0}, | |
{0, 0, 0, 1} | |
}; | |
} | |
public static double[][] getRotateY(double angle){ | |
return new double[][]{ | |
{Math.cos(angle), 0, -Math.sin(angle), 0}, | |
{0, 1, 0, 0}, | |
{Math.sin(angle), 0, Math.cos(angle), 0}, | |
{0, 0, 0, 1} | |
}; | |
} | |
public static double[][] getRotateZ(double angle){ | |
return new double[][]{ | |
{Math.cos(angle), Math.sin(angle), 0, 0}, | |
{-Math.sin(angle), Math.cos(angle), 0, 0}, | |
{0, 0, 1, 0}, | |
{0, 0, 0, 1} | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment