Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
import javax.swing.*;
import java.awt.*;
import java.util.List;
import java.util.ArrayList;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
public class DemoViewer {
public static void main(String[] args) {
JFrame frame = new JFrame();
Container pane = frame.getContentPane();
pane.setLayout(new BorderLayout());
// slider to control horizontal rotation
JSlider headingSlider = new JSlider(-180, 180, 0);
pane.add(headingSlider, BorderLayout.SOUTH);
// slider to control vertical rotation
JSlider pitchSlider = new JSlider(SwingConstants.VERTICAL, -90, 90, 0);
pane.add(pitchSlider, BorderLayout.EAST);
// panel to display render results
JPanel renderPanel = new JPanel() {
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, getWidth(), getHeight());
List<Triangle> tris = new ArrayList<>();
tris.add(new Triangle(new Vertex(100, 100, 100),
new Vertex(-100, -100, 100),
new Vertex(-100, 100, -100),
Color.WHITE));
tris.add(new Triangle(new Vertex(100, 100, 100),
new Vertex(-100, -100, 100),
new Vertex(100, -100, -100),
Color.RED));
tris.add(new Triangle(new Vertex(-100, 100, -100),
new Vertex(100, -100, -100),
new Vertex(100, 100, 100),
Color.GREEN));
tris.add(new Triangle(new Vertex(-100, 100, -100),
new Vertex(100, -100, -100),
new Vertex(-100, -100, 100),
Color.BLUE));
for (int i = 0; i < 4; i++) {
tris = inflate(tris);
}
double heading = Math.toRadians(headingSlider.getValue());
Matrix3 headingTransform = new Matrix3(new double[] {
Math.cos(heading), 0, -Math.sin(heading),
0, 1, 0,
Math.sin(heading), 0, Math.cos(heading)
});
double pitch = Math.toRadians(pitchSlider.getValue());
Matrix3 pitchTransform = new Matrix3(new double[] {
1, 0, 0,
0, Math.cos(pitch), Math.sin(pitch),
0, -Math.sin(pitch), Math.cos(pitch)
});
Matrix3 transform = headingTransform.multiply(pitchTransform);
BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
double[] zBuffer = new double[img.getWidth() * img.getHeight()];
// initialize array with extremely far away depths
for (int q = 0; q < zBuffer.length; q++) {
zBuffer[q] = Double.NEGATIVE_INFINITY;
}
for (Triangle t : tris) {
Vertex v1 = transform.transform(t.v1);
v1.x += getWidth() / 2;
v1.y += getHeight() / 2;
Vertex v2 = transform.transform(t.v2);
v2.x += getWidth() / 2;
v2.y += getHeight() / 2;
Vertex v3 = transform.transform(t.v3);
v3.x += getWidth() / 2;
v3.y += getHeight() / 2;
Vertex ab = new Vertex(v2.x - v1.x, v2.y - v1.y, v2.z - v1.z);
Vertex ac = new Vertex(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z);
Vertex norm = new Vertex(
ab.y * ac.z - ab.z * ac.y,
ab.z * ac.x - ab.x * ac.z,
ab.x * ac.y - ab.y * ac.x
);
double normalLength = Math.sqrt(norm.x * norm.x + norm.y * norm.y + norm.z * norm.z);
norm.x /= normalLength;
norm.y /= normalLength;
norm.z /= normalLength;
double angleCos = Math.abs(norm.z);
int minX = (int) Math.max(0, Math.ceil(Math.min(v1.x, Math.min(v2.x, v3.x))));
int maxX = (int) Math.min(img.getWidth() - 1, Math.floor(Math.max(v1.x, Math.max(v2.x, v3.x))));
int minY = (int) Math.max(0, Math.ceil(Math.min(v1.y, Math.min(v2.y, v3.y))));
int maxY = (int) Math.min(img.getHeight() - 1, Math.floor(Math.max(v1.y, Math.max(v2.y, v3.y))));
double triangleArea = (v1.y - v3.y) * (v2.x - v3.x) + (v2.y - v3.y) * (v3.x - v1.x);
for (int y = minY; y <= maxY; y++) {
for (int x = minX; x <= maxX; x++) {
double b1 = ((y - v3.y) * (v2.x - v3.x) + (v2.y - v3.y) * (v3.x - x)) / triangleArea;
double b2 = ((y - v1.y) * (v3.x - v1.x) + (v3.y - v1.y) * (v1.x - x)) / triangleArea;
double b3 = ((y - v2.y) * (v1.x - v2.x) + (v1.y - v2.y) * (v2.x - x)) / triangleArea;
if (b1 >= 0 && b1 <= 1 && b2 >= 0 && b2 <= 1 && b3 >= 0 && b3 <= 1) {
double depth = b1 * v1.z + b2 * v2.z + b3 * v3.z;
int zIndex = y * img.getWidth() + x;
if (zBuffer[zIndex] < depth) {
img.setRGB(x, y, getShade(t.color, angleCos).getRGB());
zBuffer[zIndex] = depth;
}
}
}
}
}
g2.drawImage(img, 0, 0, null);
}
};
pane.add(renderPanel, BorderLayout.CENTER);
headingSlider.addChangeListener(e -> renderPanel.repaint());
pitchSlider.addChangeListener(e -> renderPanel.repaint());
frame.setSize(400, 400);
frame.setVisible(true);
}
public static Color getShade(Color color, double shade) {
double redLinear = Math.pow(color.getRed(), 2.4) * shade;
double greenLinear = Math.pow(color.getGreen(), 2.4) * shade;
double blueLinear = Math.pow(color.getBlue(), 2.4) * shade;
int red = (int) Math.pow(redLinear, 1/2.4);
int green = (int) Math.pow(greenLinear, 1/2.4);
int blue = (int) Math.pow(blueLinear, 1/2.4);
return new Color(red, green, blue);
}
public static List<Triangle> inflate(List<Triangle> tris) {
List<Triangle> result = new ArrayList<>();
for (Triangle t : tris) {
Vertex m1 = new Vertex((t.v1.x + t.v2.x)/2, (t.v1.y + t.v2.y)/2, (t.v1.z + t.v2.z)/2);
Vertex m2 = new Vertex((t.v2.x + t.v3.x)/2, (t.v2.y + t.v3.y)/2, (t.v2.z + t.v3.z)/2);
Vertex m3 = new Vertex((t.v1.x + t.v3.x)/2, (t.v1.y + t.v3.y)/2, (t.v1.z + t.v3.z)/2);
result.add(new Triangle(t.v1, m1, m3, t.color));
result.add(new Triangle(t.v2, m1, m2, t.color));
result.add(new Triangle(t.v3, m2, m3, t.color));
result.add(new Triangle(m1, m2, m3, t.color));
}
for (Triangle t : result) {
for (Vertex v : new Vertex[] { t.v1, t.v2, t.v3 }) {
double l = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) / Math.sqrt(30000);
v.x /= l;
v.y /= l;
v.z /= l;
}
}
return result;
}
}
class Vertex {
double x;
double y;
double z;
Vertex(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
}
class Triangle {
Vertex v1;
Vertex v2;
Vertex v3;
Color color;
Triangle(Vertex v1, Vertex v2, Vertex v3, Color color) {
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
this.color = color;
}
}
class Matrix3 {
double[] values;
Matrix3(double[] values) {
this.values = values;
}
Matrix3 multiply(Matrix3 other) {
double[] result = new double[9];
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
for (int i = 0; i < 3; i++) {
result[row * 3 + col] +=
this.values[row * 3 + i] * other.values[i * 3 + col];
}
}
}
return new Matrix3(result);
}
Vertex transform(Vertex in) {
return new Vertex(
in.x * values[0] + in.y * values[3] + in.z * values[6],
in.x * values[1] + in.y * values[4] + in.z * values[7],
in.x * values[2] + in.y * values[5] + in.z * values[8]
);
}
}
@cergean

This comment has been minimized.

Copy link

@cergean cergean commented Mar 11, 2016

if the vertices in triangles was correctly ordered (in same order for each triangle clockwise for example) - it would be very easy to implement backface culling right here. for example, if norm.z<0 you could skip drawing entire triangle. You need just swap second and third points for first and third Triangle in initial tris list, and do the same for second and third resulting triangles in the inflate method. Thank you for such an simple yet working code)

@Strauteka

This comment has been minimized.

Copy link

@Strauteka Strauteka commented Jul 7, 2016

You saved my day! Thanks!!

@kerrman

This comment has been minimized.

Copy link

@kerrman kerrman commented Jul 23, 2018

How do I write a texture to the "triangle"? I was hoping to be able to do that with a basic 3d render program.

@rvishwajith

This comment has been minimized.

Copy link

@rvishwajith rvishwajith commented Mar 2, 2020

Any chance you would be able to do this with camera motion as well? Like in a game engine. If you would be willing to do this, that would be immensely helpful for something I'm trying to do.

@Rogach

This comment has been minimized.

Copy link
Owner Author

@Rogach Rogach commented Mar 2, 2020

@rohithv2312 Unfortunately I'm currently very pressed for time, so I won't be able to create a big example. But the basic idea for camera movement is already present in the code - what headingSlider, pitchSlider and rollSlider do is actually to move the camera around the object (and panOutTransform shows an example of translation alongside Z axis). I think that you can just move the panOutTransform before all the other transforms in the multiplication:

                    Matrix4 transform =
                        panOutTransform
                        .multiply(headingTransform)
                        .multiply(pitchTransform)
                        .multiply(rollTransform);

and you will get a camera that rotates around its own position, not around the object. After that you will only need to create the controls to change X, Y, Z coordinates of the camera (and place those into panOutTransform, which by then should probably be called cameraPositionTransform).

@rvishwajith

This comment has been minimized.

Copy link

@rvishwajith rvishwajith commented Mar 2, 2020

Thank you so much! I made the mistake of thinking the cube was rotating on its own.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.