in case anyone wants to render triangles in 3d, heres 2 ways you can:
wireframe:
shaded:
both of these methods were originally made in this blog post, and adapted to greenfoot by me. a full project using this can be foundhere.
i am planning to also make a rendering engine for greenfoot that uses openGL, because this is using the CPU for rendering when it would be way faster to use the GPU, which openGL lets you do.
import greenfoot.*; // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.util.ArrayList;
import java.awt.Color;
public class Render3d extends Actor
{
public int cycles;
java.util.List<Triangle> tris = new ArrayList<>();
public void act()
{
if(cycles == 0) {//only runs once
tris.add(new Triangle(new Vertex(100, 100, 100), new Vertex(-100, -100, 100), new Vertex(-100, 100, -100), new Color(0xFF, 0x33, 0))); // add triangles to list tris
tris.add(new Triangle(new Vertex(100, 100, 100), new Vertex(-100, -100, 100), new Vertex(100, -100, -100), new Color(0x66, 0xFF, 0x66)));
tris.add(new Triangle(new Vertex(-100, 100, -100), new Vertex(100, -100, -100), new Vertex(100, 100, 100), new Color(0xFF, 0, 0xFF)));
tris.add(new Triangle(new Vertex(-100, 100, -100), new Vertex(100, -100, -100), new Vertex(-100, -100, 100), new Color(0x33, 0xCC, 0xFF)));
}
renderTris();
cycles++;
}
public void renderTris() {
getWorld().getBackground().setColor(Color.BLACK);//fill background black
getWorld().getBackground().fill();
int mouseX = 0;
int mouseY = 0;
MouseInfo mouse = Greenfoot.getMouseInfo();
if(mouse != null) {
mouseX = mouse.getX();
mouseY = mouse.getY();//set variables to mouse location(if the mouse is on the screen)
}
double heading = Math.toRadians(mouseX);//calculate heading
Matrix3 headingTransform = new Matrix3(new double[] {//creating transform matricies for rotation
Math.cos(heading), 0, Math.sin(heading),
0, 1, 0,
-Math.sin(heading), 0, Math.cos(heading)
});
double pitch = Math.toRadians(mouseY);//calculate pitch
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);//making one matrix that does both heading and pitch rotation
for (Triangle t : tris) { // wireframe render(only works with tetrahedron)
Vertex v1 = transform.transform(t.v1);
Vertex v2 = transform.transform(t.v2);
Vertex v3 = transform.transform(t.v3);
getWorld().getBackground().setColor(Color.WHITE);
getWorld().getBackground().drawLine(centerX + (int)v1.x, centerY + (int) v1.y, centerX + (int) v2.x, centerY + (int) v2.y);
getWorld().getBackground().drawLine(centerX + (int)v1.x, centerY + (int) v1.y, centerX + (int) v3.x, centerY + (int) v3.y);
getWorld().getBackground().drawLine(centerX + (int)v2.x, centerY + (int) v2.y, centerX + (int) v3.x, centerY + (int) v3.y);
}
}import greenfoot.*; // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.util.ArrayList;
import java.awt.Color;
/**
* Write a description of class Render3d here.
*
* @author (your name)
* @version (a version number or a date)
*/
public class Render3d extends Actor
{
public int cycles;
java.util.List<Triangle> tris = new ArrayList<>();
public void act()
{
if(cycles == 0) {//only runs once
tris.add(new Triangle(new Vertex(100, 100, 100), new Vertex(-100, -100, 100), new Vertex(-100, 100, -100), new Color(0xFF, 0x33, 0))); // add triangles to list tris
tris.add(new Triangle(new Vertex(100, 100, 100), new Vertex(-100, -100, 100), new Vertex(100, -100, -100), new Color(0x66, 0xFF, 0x66)));
tris.add(new Triangle(new Vertex(-100, 100, -100), new Vertex(100, -100, -100), new Vertex(100, 100, 100), new Color(0xFF, 0, 0xFF)));
tris.add(new Triangle(new Vertex(-100, 100, -100), new Vertex(100, -100, -100), new Vertex(-100, -100, 100), new Color(0x33, 0xCC, 0xFF)));
}
renderTris();
cycles++;
}
public void renderTris() {
getWorld().getBackground().setColor(Color.BLACK);//fill background black
getWorld().getBackground().fill();
int mouseX = 0;
int mouseY = 0;
MouseInfo mouse = Greenfoot.getMouseInfo();
if(mouse != null) {
mouseX = mouse.getX();
mouseY = mouse.getY();//set variables to mouse location(if the mouse is on the screen)
}
double heading = Math.toRadians(mouseX);//calculate heading
Matrix3 headingTransform = new Matrix3(new double[] {//creating transform matricies for rotation
Math.cos(heading), 0, Math.sin(heading),
0, 1, 0,
-Math.sin(heading), 0, Math.cos(heading)
});
double pitch = Math.toRadians(mouseY);//calculate pitch
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);//making one matrix that does both heading and pitch rotation
GreenfootImage img = getWorld().getBackground();//
double[] zBuffer = new double[img.getWidth() * img.getHeight()];
// initialize array with negative infinite depths
for (int q = 0; q < zBuffer.length; q++) {
zBuffer[q] = Double.NEGATIVE_INFINITY;
}
for (Triangle t : tris) {
Vertex v1 = transform.transform(t.v1);
Vertex v2 = transform.transform(t.v2);
Vertex v3 = transform.transform(t.v3);
v1.x += 400;//center object
v1.y += 300;
v2.x += 400;
v2.y += 300;
v3.x += 400;
v3.y += 300;
// compute rectangular bounds for triangle
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);
// transform vertices before calculating normal...
Vertex ab = new Vertex(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
Vertex ac = new Vertex(v1.x - v3.x, v1.y - v3.y, v1.z - v3.z);
Vertex norm = new Vertex(//calculate normal vector for shading
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);
for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {//iterate over every pixel(inneficient)
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;
// handle rasterization...
// for each rasterized pixel:
if (b1 >= 0 && b1 <= 1 && b2 >= 0 && b2 <= 1 && b3 >= 0 && b3 <= 1) {//check if pixel is inside the triangle
double depth = b1 * v1.z + b2 * v2.z + b3 * v3.z; //calculate depth
int zIndex = y * img.getWidth() + x;
if (zBuffer[zIndex] < depth) {
img.setColor(getShade(t.color, angleCos));//calculate shade
img.drawRect(x, y, 1, 1);//draw pixel
zBuffer[zIndex] = depth;
}
}
}
}
}
}
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);
}
}
