org.jtrfp.trcl.TerrainSystem.java Source code

Java tutorial

Introduction

Here is the source code for org.jtrfp.trcl.TerrainSystem.java

Source

/*******************************************************************************
 * This file is part of TERMINAL RECALL
 * Copyright (c) 2012-2014 Chuck Ritola
 * Part of the jTRFP.org project
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     chuck - initial API and implementation
 ******************************************************************************/
package org.jtrfp.trcl;

import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import org.jtrfp.trcl.core.TR;
import org.jtrfp.trcl.core.TRFutureTask;
import org.jtrfp.trcl.core.TextureDescription;
import org.jtrfp.trcl.file.DirectionVector;
import org.jtrfp.trcl.file.TDFFile;
import org.jtrfp.trcl.file.TDFFile.TunnelLogic;
import org.jtrfp.trcl.flow.LoadingProgressReporter;
import org.jtrfp.trcl.gpu.Model;
import org.jtrfp.trcl.obj.TerrainChunk;

public final class TerrainSystem extends RenderableSpacePartitioningGrid {
    final double gridSquareSize;
    final double heightScalar;
    final ArrayList<TerrainChunk> renderingCubes = new ArrayList<TerrainChunk>();
    private final TR tr;
    public static final double Y_NUDGE = -10000;
    /*
    * Y_NUDGE is a kludge. There is a tiny sliver of space
    * between the ceiling and ground, likely caused by model
    * vertex quantization in the rendering engine. I would
    * rather put up with this quirk than re-design the engine,
    * as the quantization exists as a side-effect of a
    * memory-space optimization in the GPU and accommodating
    * the fix of this bug could cause bigger problems further
    * down the road.
    */

    public TerrainSystem(final InterpolatingAltitudeMap altitude, final TextureMesh textureMesh,
            final double gridSquareSize, final SpacePartitioningGrid parent,
            final RenderableSpacePartitioningGrid terrainMirror, final TR tr, final TDFFile tdf,
            final boolean flatShading, final LoadingProgressReporter terrainReporter) {
        super(parent);
        final int numCores = Runtime.getRuntime().availableProcessors();
        this.tr = tr;
        final int width = (int) altitude.getWidth();
        int height = (int) altitude.getHeight();
        this.gridSquareSize = gridSquareSize;
        this.heightScalar = tr.getWorld().sizeY / 2;
        final int chunkSideLength = TR.terrainChunkSideLengthInSquares;
        final double u[] = { 0, 1, 1, 0 };
        final double v[] = { 0, 0, 1, 1 };
        final double cu[] = { 0, 1, 1, 0 };
        final double cv[] = { 1, 1, 0, 0 };

        // Come up with a point list for tunnel entrances and exits
        TDFFile.Tunnel[] tunnels = tdf.getTunnels();
        final HashMap<Integer, TunnelPoint> points = new HashMap<Integer, TunnelPoint>();
        final HashMap<String, TDFFile.Tunnel> tunnelsByName = new HashMap<String, TDFFile.Tunnel>();
        if (tunnels != null) {// Null means no tunnels
            for (int i = 0; i < tunnels.length; i++) {
                final TDFFile.Tunnel tun = tunnels[i];
                if (tun.getEntranceLogic() != TunnelLogic.invisible) {
                    final TunnelPoint tp = new TunnelPoint(tun, true);
                    points.put(tp.hashCode(), tp);
                }
                if (tun.getExitLogic() != TunnelLogic.invisible) {
                    final TunnelPoint tp = new TunnelPoint(tun, false);
                    points.put(tp.hashCode(), tp);
                    tunnelsByName.put(tun.getTunnelLVLFile(), tunnels[i]);
                } //end if(invisible)
            } // end for(tunnels)
        } // end if(tunnels)

        final LoadingProgressReporter[] reporters = terrainReporter.generateSubReporters(256 / chunkSideLength);
        int reporterIndex = 0;
        TRFutureTask<Void>[] rowTasks = new TRFutureTask[numCores * 2];
        int taskIdx = 0;
        // For each chunk
        for (int gZ = 0; gZ < height; gZ += chunkSideLength) {
            reporters[reporterIndex++].complete();
            final int _gZ = gZ;
            rowTasks[taskIdx++] = tr.getThreadManager().submitToThreadPool(new Callable<Void>() {
                @Override
                public Void call() throws Exception {
                    for (int gX = 0; gX < width; gX += chunkSideLength) {
                        // GROUND
                        {// Start scope
                            final double objectX = Math
                                    .round(((double) gX + ((double) chunkSideLength / 2.)) * gridSquareSize);
                            final double objectZ = Math
                                    .round(((double) _gZ + ((double) chunkSideLength / 2.)) * gridSquareSize);
                            final double objectY = Math.round(altitude.heightAt(gX, _gZ) * heightScalar);
                            final Model m = new Model(false, tr);
                            // for each square
                            for (int cZ = _gZ; cZ < _gZ + chunkSideLength; cZ++) {
                                for (int cX = gX; cX < gX + chunkSideLength; cX++) {
                                    final double hTL = altitude.heightAt(cX, cZ) * heightScalar;
                                    final double hTR = altitude.heightAt((cX + 1), cZ) * heightScalar;
                                    final double hBR = altitude.heightAt((cX + 1), (cZ + 1)) * heightScalar;
                                    final double hBL = altitude.heightAt(cX, (cZ + 1)) * heightScalar;
                                    final double xPos = cX * gridSquareSize;
                                    final double zPos = cZ * gridSquareSize;

                                    Vector3D norm0, norm1, norm2, norm3;
                                    Vector3D norm = altitude.normalAt(cX, cZ);
                                    norm3 = new Vector3D(norm.getX() * 3, norm.getY(), norm.getZ() * 3).normalize();
                                    norm = altitude.normalAt(cX + 1, cZ);
                                    norm2 = new Vector3D(norm.getX() * 3, norm.getY(), norm.getZ() * 3).normalize();
                                    norm = altitude.normalAt(cX + 1, cZ + 1);
                                    norm1 = new Vector3D(norm.getX() * 3, norm.getY(), norm.getZ() * 3).normalize();
                                    norm = altitude.normalAt(cX, cZ + 1);
                                    norm0 = new Vector3D(norm.getX() * 3, norm.getY(), norm.getZ() * 3).normalize();

                                    if (flatShading)
                                        norm0 = norm1 = norm2 = norm3 = altitude.normalAt(cX + .5, cZ + .5);

                                    final Integer tpi = cX + cZ * 256;
                                    TextureDescription td = (TextureDescription) (points.containsKey(tpi)
                                            ? points.get(tpi).getTexture()
                                            : textureMesh.textureAt(cX, cZ));
                                    Triangle[] tris = Triangle.quad2Triangles(
                                            // COUNTER-CLOCKWISE
                                            // //x
                                            new double[] { xPos - objectX, xPos + gridSquareSize - objectX,
                                                    xPos + gridSquareSize - objectX, xPos - objectX },
                                            new double[] { hBL - objectY, hBR - objectY, hTR - objectY,
                                                    hTL - objectY },
                                            new double[] { zPos + gridSquareSize - objectZ,
                                                    zPos + gridSquareSize - objectZ, zPos - objectZ,
                                                    zPos - objectZ },
                                            u, v, td, RenderMode.STATIC,
                                            new Vector3D[] { norm0, norm1, norm2, norm3 }, cX + cZ % 4);
                                    m.addTriangle(tris[0]);
                                    m.addTriangle(tris[1]);
                                } // end for(cX)
                            } // end for(cZ)
                              // Add to grid
                            if (m.finalizeModel().getTriangleList() != null) {
                                final TerrainChunk chunkToAdd = new TerrainChunk(tr, m, altitude);
                                final double[] chunkPos = chunkToAdd.getPosition();
                                chunkPos[0] = objectX;
                                chunkPos[1] = objectY;
                                chunkPos[2] = objectZ;
                                chunkToAdd.notifyPositionChange();
                                add(chunkToAdd);
                            } else {
                                System.out.println("Rejected chunk: " + m.getDebugName());
                            }
                        } // end scope

                        {// start scope ///// CEILING
                            final double objectX = Math
                                    .round(((double) gX + ((double) chunkSideLength / 2.)) * gridSquareSize);
                            final double objectZ = Math
                                    .round(((double) _gZ + ((double) chunkSideLength / 2.)) * gridSquareSize);
                            final double objectY = Math
                                    .round((2. - altitude.heightAt(gX, _gZ)) * heightScalar + Y_NUDGE);
                            final Model m = new Model(false, tr);
                            // for each square
                            for (int cZ = _gZ; cZ < _gZ + chunkSideLength; cZ++) {
                                for (int cX = gX; cX < gX + chunkSideLength; cX++) {
                                    final double hTL = (2. - altitude.heightAt(cX, cZ)) * heightScalar + Y_NUDGE;
                                    final double hTR = (2. - altitude.heightAt((cX + 1), cZ)) * heightScalar
                                            + Y_NUDGE;
                                    final double hBR = (2. - altitude.heightAt((cX + 1), (cZ + 1))) * heightScalar
                                            + Y_NUDGE;
                                    final double hBL = (2. - altitude.heightAt(cX, (cZ + 1))) * heightScalar
                                            + Y_NUDGE;
                                    final double xPos = cX * gridSquareSize;
                                    final double zPos = cZ * gridSquareSize;

                                    Vector3D norm0, norm1, norm2, norm3;
                                    Vector3D norm = altitude.normalAt(cX, cZ);
                                    norm3 = altitude.heightAt(cX, cZ) < .9
                                            ? new Vector3D(norm.getX() * 3, norm.getY() * -1, norm.getZ() * 3)
                                                    .normalize()
                                            : new Vector3D(norm.getX() * 3, norm.getY(), norm.getZ() * 3)
                                                    .normalize();
                                    norm = altitude.normalAt(cX + 1, cZ);
                                    norm2 = altitude.heightAt(cX + 1, cZ) < .9
                                            ? new Vector3D(norm.getX() * 3, norm.getY() * -1, norm.getZ() * 3)
                                                    .normalize()
                                            : new Vector3D(norm.getX() * 3, norm.getY(), norm.getZ() * 3)
                                                    .normalize();
                                    norm = altitude.normalAt(cX + 1, cZ + 1);
                                    norm1 = altitude.heightAt(cX + 1, cZ + 1) < .9
                                            ? new Vector3D(norm.getX() * 3, norm.getY() * -1, norm.getZ() * 3)
                                                    .normalize()
                                            : new Vector3D(norm.getX() * 3, norm.getY(), norm.getZ() * 3)
                                                    .normalize();
                                    norm = altitude.normalAt(cX, cZ + 1);
                                    norm0 = altitude.heightAt(cX, cZ + 1) < .9
                                            ? new Vector3D(norm.getX() * 3, norm.getY() * -1, norm.getZ() * 3)
                                                    .normalize()
                                            : new Vector3D(norm.getX() * 3, norm.getY(), norm.getZ() * 3)
                                                    .normalize();

                                    if (flatShading)
                                        norm0 = norm1 = norm2 = norm3 = altitude.normalAt(cX + .5, cZ + .5);

                                    // Ceiling texture cell X (Z in this engine) value
                                    // is offset by 10.
                                    // No tunnelpoints on ceiling
                                    TextureDescription td = (TextureDescription) (textureMesh.textureAt(cX,
                                            cZ + 10));
                                    norm = new Vector3D(norm.getX() * 3, norm.getY(), norm.getZ() * 3).normalize();// Exaggerate
                                    // features.
                                    Triangle[] tris = Triangle.quad2Triangles(
                                            // CLOCKWISE (else backface culling will eat
                                            // it)
                                            new double[] { xPos - objectX, xPos + gridSquareSize - objectX,
                                                    xPos + gridSquareSize - objectX, xPos - objectX }, // x
                                            new double[] { hTL - objectY, hTR - objectY, hBR - objectY,
                                                    hBL - objectY },
                                            new double[] { zPos - objectZ, zPos - objectZ,
                                                    zPos + gridSquareSize - objectZ,
                                                    zPos + gridSquareSize - objectZ },
                                            cu, cv, td, RenderMode.STATIC,
                                            new Vector3D[] { norm3, norm2, norm1, norm0 }, cX + cZ % 4);
                                    m.addTriangle(tris[0]);
                                    m.addTriangle(tris[1]);
                                } // end for(cX)
                            } // end for(cZ)
                              // Add to grid
                            if (m.finalizeModel().getTriangleList() != null) {
                                final TerrainChunk chunkToAdd = new TerrainChunk(tr, m, altitude);
                                final double[] chunkPos = chunkToAdd.getPosition();
                                chunkPos[0] = objectX;
                                chunkPos[1] = objectY;
                                chunkPos[2] = objectZ;
                                chunkToAdd.notifyPositionChange();
                                chunkToAdd.setCeiling(true);
                                terrainMirror.add(chunkToAdd);
                            } else {
                                System.out.println("Rejected chunk: " + m.getDebugName());
                            }
                        } // end scope(CEILING)
                    } // end for(gX)
                    return null;
                }
            });
            if (taskIdx >= rowTasks.length)
                taskIdx = 0;
        } // end for(gZ)
        terrainMirror.deactivate();
    }// end constructor

    private class TunnelPoint {
        final int x, z;
        TextureDescription textureToInsert;

        public TunnelPoint(TDFFile.Tunnel tun, boolean entrance) {
            try {
                final String texFile = entrance ? tun.getEntranceTerrainTextureFile()
                        : tun.getExitTerrainTextureFile();
                textureToInsert = tr.getResourceManager().getRAWAsTexture(texFile, tr.getGlobalPaletteVL(), null,
                        false);
            } catch (Exception e) {
                e.printStackTrace();
            }
            DirectionVector v = entrance ? tun.getEntrance() : tun.getExit();
            x = (int) TR.legacy2MapSquare(v.getZ());
            z = (int) TR.legacy2MapSquare(v.getX());
        }

        public TextureDescription getTexture() {
            return textureToInsert;
        }

        @Override
        public boolean equals(Object other) {
            return other.hashCode() == this.hashCode();
        }

        @Override
        public int hashCode() {
            return (int) (x + z * 256);
        }
    }

    /**
     * @return the gridSquareSize
     */
    public double getGridSquareSize() {
        return gridSquareSize;
    }

    /**
     * @return the heightScalar
     */
    public double getHeightScalar() {
        return heightScalar;
    }

    /**
     * @return the renderingCubes
     */
    public ArrayList<TerrainChunk> getRenderingCubes() {
        return renderingCubes;
    }
}//end TerrainSystem