jtrace.object.SceneObject.java Source code

Java tutorial

Introduction

Here is the source code for jtrace.object.SceneObject.java

Source

/*
 * Copyright (C) 2012 Tim Vaughan <tgvaughan@gmail.com>
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package jtrace.object;

import java.util.ArrayList;
import java.util.List;
import jtrace.Colour;
import jtrace.LightSource;
import jtrace.Ray;
import jtrace.Scene;
import jtrace.object.transformation.Transformation;
import jtrace.texture.Texture;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;

/**
 * Abstract class for an object in a scene.  The primary method which needs
 * to be implemented for any real object is the getFirstCollision() method
 * responsible for determining where rays hit the object and the surface
 * normal of the object at those points.
 *
 * @author Tim Vaughan <tgvaughan@gmail.com>
 */
public abstract class SceneObject {

    /**
     * Scene that object resides in
     */
    private Scene scene;

    /**
     * Coordinate transformations to apply to object.
     */
    private final List<Transformation> transformations;

    /**
     * Textures to apply to object.
     */
    private final List<Texture> textures;

    /**
     * Details of last collision.
     */
    protected Ray incidentRay;
    protected Ray normalRay, normalRayRef, normalRayTrans;
    protected Ray reflectedRay;
    protected boolean internal;

    /**
     * The small value collision locations are moved out from their surfaces
     * by to prevent artifacts.
     */
    static double EPSILON = 1e-5;

    /**
     * UV coordinates of collision point for texture mapping.
     */
    protected double u, v;

    /**
     * Light sources visible from last collision point.
     */
    private List<LightSource> visibleLights;

    public SceneObject() {
        transformations = new ArrayList<>();
        textures = new ArrayList<>();
    }

    public void setScene(Scene scene) {
        this.scene = scene;
    }

    public Scene getScene() {
        return scene;
    }

    public void addTexture(Texture texture) {
        textures.add(texture);
    }

    public void addTransformation(Transformation transformation) {
        transformations.add(transformation);
    }

    public Vector3D objectToSceneVector(Vector3D sceneVec) {
        Vector3D objectVec = sceneVec;
        for (Transformation transformation : transformations) {
            objectVec = transformation.apply(objectVec);
        }

        return objectVec;
    }

    public Vector3D sceneToObjectVector(Vector3D objectVec) {
        Vector3D sceneVec = objectVec;
        for (int i = transformations.size() - 1; i >= 0; i--)
            sceneVec = transformations.get(i).applyInverse(sceneVec);
        return sceneVec;
    }

    public Ray sceneToObjectRay(Ray sceneRay) {
        return new Ray(sceneToObjectVector(sceneRay.origin), sceneToObjectVector(sceneRay.direction));
    }

    public Ray objectToSceneRay(Ray objectRay) {
        return new Ray(objectToSceneVector(objectRay.origin), objectToSceneVector(objectRay.direction));
    }

    public Ray getIncidentRay() {
        return incidentRay;
    }

    public Ray getNormalRay() {
        return normalRay;
    }

    public boolean isInternal() {
        return internal;
    }

    /**
     * Retrieve ray normal to surface with the origin shifted by epsilon
     * so that the surface itself cannot be hit by a REFLECTED ray starting
     * at this location.
     * 
     * @return normal ray
     */
    public Ray getNormalRayRef() {
        if (normalRayRef == null) {
            normalRayRef = new Ray();
            normalRayRef.direction = normalRay.direction;
            if (internal)
                normalRayRef.origin = normalRay.origin.subtract(EPSILON, normalRay.direction);
            else
                normalRayRef.origin = normalRay.origin.add(EPSILON, normalRay.direction);
        }

        return normalRayRef;
    }

    /**
     * Retrieve ray normal to surface with the origin shifted by epsilon
     * so that the surface itself cannot be hit by a TRANSMITTED ray starting
     * at this location.
     * 
     * @return normal ray
     */
    public Ray getNormalRayTrans() {
        if (normalRayTrans == null) {
            normalRayTrans = new Ray();
            normalRayTrans.direction = normalRay.direction;
            if (internal)
                normalRayTrans.origin = normalRay.origin.add(EPSILON, normalRay.direction);
            else
                normalRayTrans.origin = normalRay.origin.subtract(EPSILON, normalRay.direction);
        }

        return normalRayTrans;
    }

    /**
     * Retrieve ray reflected from point of last collision.
     * 
     * @return reflected ray
     */
    public Ray getReflectedRay() {

        // Return existing value if it's already been calculated:
        if (reflectedRay != null)
            return reflectedRay;

        Vector3D reflectedOrigin = getNormalRayRef().origin;
        Vector3D reflectedDir = incidentRay.direction
                .add(-2.0 * incidentRay.direction.dotProduct(normalRay.direction), normalRay.direction);

        reflectedRay = new Ray(reflectedOrigin, reflectedDir);

        return reflectedRay;
    }

    /**
     * Responsible for calculating first collision of ray with object,
     * returning the distance to this point from the origin of the ray.
     * Records the colliding and normal rays for future interrogation.
     * 
     * @param ray incoming ray
     * @return distance from origin of ray to first intersection
     */
    public double getFirstCollision(Ray ray) {
        double dist = getFirstCollisionObjectFrame(sceneToObjectRay(ray));

        if (dist > 0 && dist < Double.POSITIVE_INFINITY) {
            incidentRay = objectToSceneRay(incidentRay);
            normalRay = objectToSceneRay(normalRay);
        }

        return dist;
    }

    public abstract double getFirstCollisionObjectFrame(Ray ray);

    /**
     * Mix values obtained from the layered textures to obtain the colour at
     * the point of collision.
     * 
     * @return colour at collision point
     */
    public Colour getCollisionColour() {

        // Clear calculated collision information (allows for caching):
        visibleLights = null;
        reflectedRay = null;
        normalRayRef = null;
        normalRayTrans = null;

        internal = incidentRay.direction.dotProduct(normalRay.direction) > 0;

        Colour colour = new Colour(0, 0, 0);
        for (Texture texture : textures) {
            colour = texture.layerTextureColour(this, colour);
        }
        return colour;
    }

    /**
     * Get u component for texture mapping.
     * 
     * @return u coordinate
     */
    public abstract double getU();

    /**
     * Get v component for texture mapping.
     * 
     * @return v coordinate
     */
    public abstract double getV();

    /**
     * Retrieve list of light sources visible from location of last
     * collision.
     * 
     * @return List of visible light sources.
     */
    public List<LightSource> getVisibleLights() {

        // Return existing list if it's already been calculated:
        if (visibleLights != null)
            return visibleLights;

        visibleLights = new ArrayList<LightSource>();
        Vector3D location = getNormalRayRef().origin;

        for (LightSource light : scene.getLightSources()) {

            Vector3D dirToLight = light.getLocation().subtract(location).normalize();
            Ray rayToLight = new Ray(location, dirToLight);

            boolean occluded = false;

            for (SceneObject object : scene.getSceneObjects()) {
                if (object.getFirstCollision(rayToLight) < Double.POSITIVE_INFINITY) {
                    occluded = true;
                    break;
                }
            }

            if (!occluded)
                visibleLights.add(light);
        }

        return visibleLights;
    }

    /**
     * Obtain a list of length 2 arrays of vectors representing edges in a
     * wireframe representation of this object.
     * 
     * @return wireframe edges
     */
    public final List<Vector3D[]> getWireFrame() {
        List<Vector3D[]> res = new ArrayList<>();
        for (Vector3D[] coord : getWireFrameObjectFrame()) {
            Vector3D[] sceneCoord = { objectToSceneVector(coord[0]), objectToSceneVector(coord[1]) };
            res.add(sceneCoord);
        }

        return res;
    }

    public abstract List<Vector3D[]> getWireFrameObjectFrame();
}