se.llbit.chunky.renderer.test.TestRenderer.java Source code

Java tutorial

Introduction

Here is the source code for se.llbit.chunky.renderer.test.TestRenderer.java

Source

/* Copyright (c) 2012 Jesper qvist <jesper@llbit.se>
 *
 * This file is part of Chunky.
 *
 * Chunky is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Chunky is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with Chunky.  If not, see <http://www.gnu.org/licenses/>.
 */
package se.llbit.chunky.renderer.test;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;

import org.apache.commons.math3.util.FastMath;

import se.llbit.chunky.PersistentSettings;
import se.llbit.chunky.renderer.Refreshable;
import se.llbit.chunky.renderer.Renderer;
import se.llbit.chunky.renderer.scene.Camera;
import se.llbit.chunky.renderer.scene.Scene;
import se.llbit.chunky.renderer.ui.Chunk3DView;
import se.llbit.chunky.renderer.ui.ViewListener;
import se.llbit.chunky.resources.Texture;
import se.llbit.chunky.world.Block;
import se.llbit.log.Log;
import se.llbit.math.Color;
import se.llbit.math.Matrix3d;
import se.llbit.math.Quad;
import se.llbit.math.QuickMath;
import se.llbit.math.Ray;
import se.llbit.math.Vector3d;
import se.llbit.math.Vector4d;

/**
 * Test renderer
 * @author Jesper qvist <jesper@llbit.se>
 */
@SuppressWarnings("unused")
public class TestRenderer extends Thread implements ViewListener, Renderer, Refreshable {

    private static final int NUM_BUFFERS = 3;

    private Chunk3DView view;
    private BufferedImage buffer;
    private BufferedImage backBuffer;
    private final int width;
    private final int height;
    private final Camera nextCamera;
    private final Camera camera;
    private boolean refresh = true;
    private final Vector3d camPos = new Vector3d();
    private final Matrix3d rot = new Matrix3d();
    private final Matrix3d tmpRot = new Matrix3d();
    private double distance;
    private double nextDistance = 1.5;
    private final int blockId;

    private final Object renderLock = new Object();

    /**
     * Mock scene object required by some block renderers
     */
    private final Scene scene;

    private static final Texture[] tex = { new Texture("east"), new Texture("west"), new Texture("north"),
            new Texture("south"), };

    private final Quad[] quads = {
            new Quad(new Vector3d(1, 0, 0), new Vector3d(1, 0, 1), new Vector3d(1, 1, 0), new Vector4d(0, 1, 0, 1)),
            new Quad(new Vector3d(0, 0, 1), new Vector3d(0, 0, 0), new Vector3d(0, 1, 1), new Vector4d(0, 1, 0, 1)),
            new Quad(new Vector3d(0, 0, 0), new Vector3d(1, 0, 0), new Vector3d(0, 1, 0), new Vector4d(0, 1, 0, 1)),
            new Quad(new Vector3d(1, 0, 1), new Vector3d(0, 0, 1), new Vector3d(1, 1, 1),
                    new Vector4d(0, 1, 0, 1)), };

    private final TestModel testModel = new TestModel();
    private final String targetFile;

    /**
     * Constructor
     * @param parent
     */
    public TestRenderer(JFrame parent) {
        this(parent, -1);
    }

    /**
     * Constructor
     * @param parent
     * @param blockId
     */
    public TestRenderer(JFrame parent, int blockId) {
        this(parent, blockId, "");
    }

    /**
     * Render a block and write the image to a target file
     * @param parent
     * @param blockId
     * @param targetFile
     */
    public TestRenderer(JFrame parent, int blockId, String targetFile) {
        super("Test Renderer");

        this.blockId = blockId;
        this.targetFile = targetFile;
        scene = new Scene();
        scene.setBiomeColorsEnabled(false);

        width = PersistentSettings.DEFAULT_3D_CANVAS_WIDTH;
        height = PersistentSettings.DEFAULT_3D_CANVAS_HEIGHT;

        buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        backBuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

        camera = new Camera(this);
        nextCamera = new Camera(this);
        nextCamera.setView(-3 * Math.PI / 4, -5 * Math.PI / 6, 0);

        if (targetFile.isEmpty()) {
            view = new Chunk3DView(this, parent);
            view.setRenderer(this);
            view.setVisible(true);
        }
    }

    @Override
    public void run() {

        try {
            while (!isInterrupted()) {

                testModel.setUp();
                waitRefresh();
                synchronized (renderLock) {
                    camera.set(nextCamera);
                    distance = nextDistance;
                }

                synchronized (backBuffer) {
                    raytrace();

                    if (!targetFile.isEmpty()) {
                        writeBufferToFile(backBuffer, targetFile);
                        return;
                    } else {
                        // flip buffers
                        BufferedImage tmp = backBuffer;
                        backBuffer = buffer;
                        buffer = tmp;
                    }
                }

                view.getCanvas().repaint();

            }
        } catch (InterruptedException e) {
        }
    }

    private void writeBufferToFile(BufferedImage buffer, String fileName) {
        try {
            ImageIO.write(buffer, "png", new FileOutputStream(fileName));
        } catch (FileNotFoundException e) {
            Log.error(e);
        } catch (IOException e) {
            Log.error(e);
        }
    }

    /**
     * Raytrace one frame.
     */
    private void raytrace() {

        double aspect = width / (double) height;

        Ray ray = new Ray();

        camPos.set(0, -distance, 0);
        camera.transform(camPos);
        camPos.add(.5, .5, .5);

        for (int x = 0; x < width; ++x) {

            double fovTan = Camera.clampedFovTan(70);
            double rayx = fovTan * aspect * (.5 - ((double) x) / width);

            for (int y = 0; y < height; ++y) {

                ray.setDefault();
                ray.d.set(rayx, 1, fovTan * (.5 - ((double) y) / height));
                ray.d.normalize();
                camera.transform(ray.d);

                ray.o.set(camPos);
                raytrace(ray);

                ray.color.x = QuickMath.min(1, FastMath.sqrt(ray.color.x));
                ray.color.y = QuickMath.min(1, FastMath.sqrt(ray.color.y));
                ray.color.z = QuickMath.min(1, FastMath.sqrt(ray.color.z));
                backBuffer.setRGB(x, y, Color.getRGB(ray.color));
            }
        }
    }

    private void raytrace(Ray ray) {
        double[] nearfar = new double[2];
        enterBlock(ray, nearfar);
        double tNear = nearfar[0];
        double tFar = nearfar[1];

        ray.color.set(1, 1, 1, 1);

        if (tNear <= tFar && tFar >= 0) {
            ray.o.scaleAdd(tNear, ray.d);
            ray.distance += tNear;

            if (blockId == -1) {
                renderTestModel(ray);
            } else {
                ray.setPrevMat(Block.AIR, 0);
                Block theBlock = Block.get(blockId);
                ray.setCurrentMat(theBlock, blockId);
                theBlock.intersect(ray, scene);
            }
        }
    }

    private void renderTestModel(Ray ray) {
        ray.t = Double.POSITIVE_INFINITY;
        for (int i = 0; i < quads.length; ++i) {
            if (quads[i].intersect(ray)) {
                ray.t = ray.tNext;
                tex[i].getColor(ray);
            }
        }

        ray.t = Double.POSITIVE_INFINITY;
        testModel.intersect(ray);
    }

    private void enterBlock(Ray ray, double[] nearfar) {
        int level = 0;
        double t1, t2;
        double tNear = Double.NEGATIVE_INFINITY;
        double tFar = Double.POSITIVE_INFINITY;
        Vector3d d = ray.d;
        Vector3d o = ray.o;

        if (d.x != 0) {
            t1 = -o.x / d.x;
            t2 = ((1 << level) - o.x) / d.x;

            if (t1 > t2) {
                double t = t1;
                t1 = t2;
                t2 = t;
            }

            if (t1 > tNear)
                tNear = t1;
            if (t2 < tFar)
                tFar = t2;
        }

        if (d.y != 0) {
            t1 = -o.y / d.y;
            t2 = ((1 << level) - o.y) / d.y;

            if (t1 > t2) {
                double t = t1;
                t1 = t2;
                t2 = t;
            }

            if (t1 > tNear)
                tNear = t1;
            if (t2 < tFar)
                tFar = t2;
        }

        if (d.z != 0) {
            t1 = -o.z / d.z;
            t2 = ((1 << level) - o.z) / d.z;

            if (t1 > t2) {
                double t = t1;
                t1 = t2;
                t2 = t;
            }

            if (t1 > tNear)
                tNear = t1;
            if (t2 < tFar)
                tFar = t2;
        }

        nearfar[0] = tNear;
        nearfar[1] = tFar;
    }

    @Override
    public void onStrafeLeft() {
        synchronized (renderLock) {
            nextCamera.strafeLeft(.5);
        }
        refresh();
    }

    @Override
    public void onStrafeRight() {
        synchronized (renderLock) {
            nextCamera.strafeRight(.5);
        }
        refresh();
    }

    @Override
    public void onMoveForward() {
        synchronized (renderLock) {
            nextDistance -= .1;
            nextDistance = QuickMath.max(.1, nextDistance);
        }
        refresh();
    }

    @Override
    public void onMoveBackward() {
        synchronized (renderLock) {
            nextDistance += .1;
            nextDistance = QuickMath.max(.1, nextDistance);
        }
        refresh();
    }

    @Override
    public void onMoveForwardFar() {
        // do nothing
    }

    @Override
    public void onMoveBackwardFar() {
        // do nothing
    }

    @Override
    public void onMoveUp() {
        synchronized (renderLock) {
            nextCamera.moveUp(.5);
        }
        refresh();
    }

    @Override
    public void onMoveDown() {
        synchronized (renderLock) {
            nextCamera.moveDown(.5);
        }
        refresh();
    }

    @Override
    public void onMouseDragged(int dx, int dy) {
        synchronized (renderLock) {
            nextCamera.rotateView((Math.PI / 250) * dx, (Math.PI / 250) * dy);
        }
        refresh();
    }

    @Override
    public void setViewVisible(boolean visible) {
        if (!visible) {
            interrupt();
            view.dispose();
        }
    }

    @Override
    public void onZoom(int diff) {
        synchronized (renderLock) {
            nextDistance += .1 * diff;
            nextDistance = QuickMath.max(.1, nextDistance);
        }
        refresh();
    }

    @Override
    public void drawBufferedImage(Graphics g, int width, int height) {
        synchronized (backBuffer) {
            g.drawImage(backBuffer, 0, 0, width, height, null);
        }
    }

    @Override
    public void setBufferFinalization(boolean flag) {
        // do nothing
    }

    @Override
    public synchronized void refresh() {
        synchronized (renderLock) {
            refresh = true;
            renderLock.notifyAll();
        }
    }

    private void waitRefresh() throws InterruptedException {
        synchronized (renderLock) {
            while (!refresh) {
                wait();
            }
            refresh = false;
        }
    }
}