Currently showing src/Xith3DTerrainTest.java
/**
 * Copyright (c) 2003, Xith3D Project Group
 * All rights reserved.
 *
 * Portions based on the Java3D interface, Copyright by Sun Microsystems.
 * Many thanks to the developers of Java3D and Sun Microsystems for their
 * innovation and design.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the 'Xith3D Project Group' nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * Modified by Michael Wright - Janus Research Group, Inc.
 * michael.wright@janusresearch.com
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A
 * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE
 *
 */
package com.janusresearch.concept;
import com.xith3d.test.*;
import com.xith3d.utility.logs.*;

import java.awt.*;
import java.awt.event.*;

import com.xith3d.scenegraph.*;
import com.xith3d.render.*;
import com.xith3d.render.jogl.*;
import javax.vecmath.*;
import javax.swing.*;

import com.xith3d.image.*;
import com.xith3d.loaders.texture.*;
import com.xith3d.userinterface.UIWindowManager;
import com.xith3d.userinterface.UIWindow;
import com.xith3d.userinterface.UIEventAdapter;
import com.xith3d.terrain.TerrainSampleInterface;
import com.xith3d.terrain.Terrain;
import com.xith3d.terrain.TerrainRenderInterface;
import com.xith3d.utility.noise.Perlin2;
import com.xith3d.utility.noise.Noise;

import java.io.*;

/**
 * Simple Xith3D Coloring Attributes test
 * 
 * <p>Copyright: Copyright (c) 2003</p>
 * <p>Company: JProof</p>
 * @author YVG
 */
public class Xith3DTerrainTest 
{
    Transform3D testRotateY = new Transform3D();
    TransformGroup testRotateYGroup = new TransformGroup();
    Material mat;
    UIWindowManager windowMgr;
    Terrain terrain;
    TerrainShape terrainShape;
    float speed= 4;

	protected BranchGroup createSceneGraph() throws Exception
	{
        BranchGroup objRoot = new BranchGroup();

        AmbientLight light = new AmbientLight(true,new Color3f(0,0,1));
        objRoot.addChild(light);

		// Rotate a bit around X axis to see the transfrorm effect
		Transform3D testRotateX = new Transform3D();
		TransformGroup testRotateXGroup = new TransformGroup();
		//		testRotateX.rotX(0.6f);
		testRotateX.rotX(0.0f);
		testRotateXGroup.setTransform(testRotateX);
		objRoot.addChild(testRotateXGroup);

		// Add animation TransformGroup
		testRotateYGroup.setTransform(testRotateY);
		testRotateXGroup.addChild(testRotateYGroup);

		// Make extra transfrom we may want to play with (scale etc.)
		TransformGroup sceneRootTransform = new TransformGroup();
		Transform3D t = new Transform3D();
		t.setIdentity();
		sceneRootTransform.setTransform(t);
		testRotateYGroup.addChild(sceneRootTransform);
		
		
		mat = new Material();
		mat.setAmbientColor(0.75f,0.75f,0.75f);
		mat.setLightingEnable(true);
		
		Shape3D shape = new Shape3D();
		Geometry g = TestUtils.createCubeViaTriangles(0f, 0f, -0.6f, 0.8f, true);
		Appearance a = new Appearance();
		a.setPolygonAttributes(new PolygonAttributes(PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_BACK, 0));
		a.setMaterial(mat);
		shape.setAppearance(a);
		shape.setGeometry(g);
		sceneRootTransform.addChild(shape);

		return objRoot;
	}

	View view;

	public void init() throws Exception
	{
		ConsoleLog myLog = new ConsoleLog(LogType.ALL);
		Log.log.registerLog(myLog);
		// Prepare full-screen canvas adapted for current resolution
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

		VirtualUniverse universe = new VirtualUniverse();
		view = new View();
		view.setBackClipDistance(32000f);
		view.setProjectionPolicy(View.PERSPECTIVE_PROJECTION);
		view.getTransform().lookAt(
			new Vector3f(100, 300, 100),
			new Vector3f(2000, 100, 2000),
			new Vector3f(0, 1, 0));
		Locale locale = new Locale();
		universe.addLocale(locale);
		universe.addView(view);

		RenderPeer renderPeer = new RenderPeerImpl();
		// Awwww, but what if we're dual-headed? :-/
		int screenWidth = screenSize.width;
		int screenHeight = screenSize.height;
		float aspectRatio = (float)screenWidth / (float)screenHeight;
		System.out.println("ratio: "+aspectRatio);
		if (aspectRatio > 2) {
						screenWidth= (screenWidth / 2);
		}
		CanvasPeer canvasPeer =
			renderPeer.makeCanvas(
				null,
				screenWidth - 200,
				screenHeight - 200,
				16,
				true);
		canvasPeer.getWindow().setLocation(100, 100);
		Canvas3D canvas = new Canvas3D();
		canvas.set3DPeer(canvasPeer);
		view.addCanvas3D(canvas);

        // construct a window manager for this canvas

        windowMgr = new UIWindowManager(canvas);
        TestWindow w = new TestWindow(100,200);
        windowMgr.addOverlay(w);
        windowMgr.setPosition(w,10,10);
        windowMgr.setVisible(w,true);

        UIEventAdapter eventAdapter = new UIEventAdapter(windowMgr);
        canvas.get3DPeer().getComponent().addKeyListener(eventAdapter);
        canvas.get3DPeer().getComponent().addMouseListener(eventAdapter);
        canvas.get3DPeer().getComponent().addMouseMotionListener(eventAdapter);
        canvas.get3DPeer().getComponent().setFocusable(true);

		BranchGroup scene = createSceneGraph();
		locale.addBranchGraph(scene);

        buildTerrain();
				buildSkybox(scene);
        terrainShape.rebuild(view);
        scene.addChild(terrainShape);

		runTest();
	}

    private void buildTerrain() {

        // we are creating a terrain that can handle and area of 2^13 x 2^13, broken into banks of
        // 2^10 x 2^10
        terrain = new Terrain(12,10);

        System.out.println("Building terrain...");
        terrain.addData(new TerrainGenerator());
        System.out.println("Nodes = "+ terrain.CountNodes());
        System.out.println("Removing nodes which are irrelevent...");
        terrain.cullStaticData(40,4);
        System.out.println("Nodes = "+ terrain.CountNodes());
        terrainShape = new TerrainShape();


    }
    
	public QuadArray getSkySide() {
		return new QuadArray(
			4,
			GeometryArray.TEXTURE_COORDINATE_2 | GeometryArray.COORDINATES
		);
	}
	
	private Texture2D getSkyTexture(String texName) {
		TextureLoader.tf.registerPath("./");
		TextureLoader.tf.registerPath("../");
		TextureLoader.tf.registerPath("./resources/textures/");
		TextureLoader.tf.registerJarPath("/textures/");
		return (Texture2D)TextureLoader.tf.getTextureClamp(texName);
	}
	
	public void buildSkybox(BranchGroup scene) {
		float boxSize = (float)terrain.getWidth();
		
		QuadArray[] skyboxGeo = new QuadArray[] {
			getSkySide(),
			getSkySide(),
			getSkySide(),
			getSkySide(),
			getSkySide()
		};
		
		TexCoord2f[] texCoords = new TexCoord2f[]{
			new TexCoord2f(1,0),
			new TexCoord2f(1,1),
			new TexCoord2f(0,1),
			new TexCoord2f(0,0)
		};
			
		skyboxGeo[0].setCoordinates(
			0,
			new Point3f[] {
				new Point3f(boxSize, 0.f, boxSize),
				new Point3f(boxSize, boxSize, boxSize),
				new Point3f(0.f, boxSize, boxSize),
				new Point3f(0.f, 0.f, boxSize)
			}
		);
		skyboxGeo[1].setCoordinates(
			0,
			new Point3f[] {
				new Point3f(0.f, 0.f, 0.f),
				new Point3f(0.f, boxSize, 0.f),
				new Point3f(boxSize, boxSize, 0.f),
				new Point3f(boxSize, 0.f, 0.f)
			}
		);
		skyboxGeo[2].setCoordinates(
			0,
			new Point3f[] {
				new Point3f(0.f, 0.f, boxSize),
				new Point3f(0.f, boxSize, boxSize),
				new Point3f(0.f, boxSize, 0.f),
				new Point3f(0.f, 0.f, 0.f)
			}
		);
		skyboxGeo[3].setCoordinates(
			0,
			new Point3f[] {
				new Point3f(boxSize, 0.f, 0.f),
				new Point3f(boxSize, boxSize, 0.f),
				new Point3f(boxSize, boxSize, boxSize),
				new Point3f(boxSize, 0.f, boxSize)
			}
		);
		skyboxGeo[4].setCoordinates(
			0,
			new Point3f[] {
				new Point3f(boxSize, boxSize, boxSize),
				new Point3f(boxSize, boxSize, 0.f),
				new Point3f(0.f, boxSize, 0.f),
				new Point3f(0.f, boxSize, boxSize)
			}
		);

		
		Appearance[] appearance = new Appearance[] {
			new Appearance(),
			new Appearance(),
			new Appearance(),
			new Appearance(),
			new Appearance()
		};
		
		appearance[0].setTexture(getSkyTexture("ft.jpg"));
		appearance[1].setTexture(getSkyTexture("bk.jpg"));
		appearance[2].setTexture(getSkyTexture("lt.jpg"));
		appearance[3].setTexture(getSkyTexture("rt.jpg"));
		appearance[4].setTexture(getSkyTexture("up.jpg"));

		for(int i=0; i<5; i++) {
			skyboxGeo[i].setTextureCoordinates(0,0,texCoords);
			scene.addChild(new Shape3D(skyboxGeo[i], appearance[i]));
		}
	}

	
		
    final static float MAX_HEIGHT = 600;

    class TerrainShape extends BranchGroup implements TerrainRenderInterface {

        final static int MAX_INDICES = 20000;
        final static int MAX_VERTICES = 30000;

        int startIndex = 0;
        int totalIndex = 0;
        int totalVerts = 0;
        Shape3D shape;
        IndexedTriangleArray geo;
        int index[] = new int[MAX_INDICES];
        float quad[] = new float[50];
        int vertexMap[] = new int[100];
        PolygonAttributes pa;

        public TerrainShape() {
	    
	    /**
             * One thing to note here, you need GeometryArray.TEXTURE_COORDINATE_2 
	     * to do what we're doing in initVert
	     *
	     */
	    geo = new IndexedTriangleArray(
	        MAX_VERTICES,
		GeometryArray.COORDINATES | GeometryArray.NORMALS | GeometryArray.COLOR_3 | GeometryArray.TEXTURE_COORDINATE_2,
		MAX_INDICES);
            Appearance a = new Appearance();
	    pa = new PolygonAttributes(PolygonAttributes.POLYGON_FILL,PolygonAttributes.CULL_BACK,0);
	    a.setPolygonAttributes(pa);
	    
	    // Set us up the texture!
	    TextureLoader.tf.registerPath(".");
	    TextureLoader.tf.registerPath("..");
	    TextureLoader.tf.registerPath("./resources/");
	    // Let's add this for a sanity check on our jar
	    TextureLoader.tf.registerJarPath("/");
	    Texture2D texture = (Texture2D) TextureLoader.tf.getMinMapTexture("stone.jpg");
	    a.setTexture(texture);

            shape = new Shape3D(geo,a);
            shape.setBoundsAutoCompute(false);
            shape.setBounds(new BoundingSphere(new Point3f(0,0,0),100000));
            addChild(shape);
        }

        public void setLineMode(boolean yes) {
            if (yes) pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
            else pa.setPolygonMode(PolygonAttributes.POLYGON_FILL);
        }

        public void rebuild(View view) {

            Point3f point = new Point3f();
            view.getTransform().get(point);
            terrain.update(point,117);
            geo.drawStart();
            totalIndex = 0;
            totalVerts = 0;
            terrain.render(this);
            System.out.println("total vertices = "+totalVerts);
            System.out.println("total indices = "+totalIndex);
            geo.drawEnd();
            geo.setIndex(index);
            geo.setValidIndexCount(totalIndex);
            geo.setValidVertexCount(totalVerts);

        }

        /**
         * called at the beginning of rendering the triangles for one adaptive quad square
         */
        public void start() {
        }

        public void initVert(int i, float x, float y, float z) {
//            System.out.println("coordinate is "+x+","+y+","+z);
            if (totalVerts>=MAX_VERTICES) return;
            geo.newVertex();
            geo.setCoordinate(x,y,z);
	    
	    /**
	     * Ok, here we go with the texture coordinate stuff. 
	     *
	     * First I set a texture scaling factor. This isn't really a good name for this. 
	     * It's not really a scale, more like a wrapping multiplier. This number should
	     * defines how many copies of the texture will appear on *each side* of the
	     * terrain. Square this number and you get the total copies of the texture that 
	     * will be seen on the terrain. To stretch a single texture across the entire terrain,
	     * you would set this number to 1.
	    */
	    int textureScale = 16;
	    
	    /**
	     * Next we have to divide out the texture coordinates. 2D textures have coordinates
	     * from 0 to 1 in what is called "S,T" space. Each coordinate in our X,Z world
	     * has to be mapped to its appropriate S,T texture coordinate. If we are stretching
	     * the texture all the way across the terrain, then 0,0 in X,Z would be 0,0 in S,T and
	     * the farthest point in X,Z from the origin would be 1,1 in S,T. Thus, mathematically,
	     * we should divide X and Z by the width and depth (should be equal) of the terrain. To
	     * wrap the texture, you would divide this depth and width by some number (set by textureScale)
	     * to make the textured patches smaller.
	    */
	    float texCoordx = x / (terrain.getWidth() / textureScale);
	    float texCoordz = z / (terrain.getWidth() / textureScale);
	    
	    // Now we need to use these figures to set up a TexCoord2f
	    TexCoord2f texCoords = new TexCoord2f(texCoordx, texCoordz);
	    
	    /**
	     * Now for the fun part. We've already set the coordinate, now we set the texture
	     * coordinate. According to the Java3D API (from which Xith was birthed):
	     *
	     * <cut from API>
	     * public void setTextureCoordinate(int texCoordSet,
	     *                                  int index,
	     *                                  TexCoord2f texCoord)
	     *
	     *        Sets the texture coordinate associated with the vertex at the specified 
	     *        index in the specified texture coordinate set for this object.
	     *
	     *        Parameters:
	     *            texCoordSet - texture coordinate set in this geometry array
	     *            index - destination vertex index in this geometry array
	     *            texCoord - the TexCoord2f containing the new texture coordinate
	     * </cut from API>
	     *
	     * In our case, we only have one texture coordinate set in this geometry array, so
	     * we'll set texCoordSet to 0. For our index, we'll use the totalVerts number that is
	     * being incremented as we step through this process (note the last line of this method).
	     * For the texCoord, we'll use our TexCoord2f that we set up above.
	    */
	    geo.setTextureCoordinate(0,totalVerts,texCoords);
	    
	    // We want prettier colors :-)
	    float c = y/1500;
	    geo.setColor(0.48f+c, 0.48f+c, 0.39f+c);
	    
	    vertexMap[i] = totalVerts++;
	}
	    
        public void tri(int a, int b, int c) {
            if (totalIndex+3>=MAX_INDICES) return;
            index[totalIndex++] = vertexMap[a];
            index[totalIndex++] = vertexMap[b];
            index[totalIndex++] = vertexMap[c];
        }

        /**
         * called at the end of rendering the triangles for one adaptive quad square
         */
        public void done() {
        }

    }

	public void runTest()
	{
		float angle = 0f;

        Point3f position = new Point3f(20,20,20);
        Point3f lookat = new Point3f();
        float distance = 20;
        Transform3D viewT = new Transform3D();
        Point3f lastUpdate = new Point3f(0,0,0);
        float direction = 1;
		while (true)
		{
            if (position.x>4000) {
                position.x = 4000;
                position.z = 4000;
                direction = -1;
            } else if (position.x<0) {
                position.x = 0;
                position.z = 0;
                direction = 1;
            }

            position.x += speed*direction;
            position.z += speed*direction;
            position.y = terrain.getY(position.x, position.z) + 200;

            lookat.set(position);
            lookat.x += distance*direction;
            lookat.z += distance*direction;
            lookat.y = terrain.getY(lookat.x, lookat.z) + 200;

            viewT.lookAt(new Point3f(position),new Point3f(lookat),new Point3f(0,1,0));
//            System.out.println("position = "+position);
            view.setTransform(viewT);

            // now if we have moved 200 meters update the terrain to me optimal for new position

            if (lastUpdate.distance(position)>200) {
                terrainShape.rebuild(view);
                lastUpdate.set(position);
            }
			view.renderOnce();
			try
			{
				Thread.sleep(10L);
			}
			catch (Exception e)
			{
			}
			angle += 0.005f;
			testRotateY.rotY(angle);
			testRotateYGroup.setTransform(testRotateY);
		}
	}

    /**
     * The following class is used to supply height sampling information to
     * the terrain manager.
     */
    class TerrainGenerator implements TerrainSampleInterface {

        int scale;
        Perlin2 perlin;

        public TerrainGenerator() {
            perlin = new Perlin2(Perlin2.METHOD_BASIC,0.5,2,2,1,0,new Noise());
            scale = 3;
        }
        public int getScale() {
            return scale;
        }

        public float sample(int x, int z) {
            return (float)(perlin.value((float)x/1400f,(float)z/1400f)+1f)*MAX_HEIGHT;
        }

        public float getXOrg() {
            return 0;
        }

        public float getZOrg() {
            return 0;
        }

        public int getXDim() {
            return 4000;
        }

        public int getZDim() {
            return 4000;
        }

    }
    class TestWindow extends UIWindow {

        public TestWindow(int width, int height) {
            super(width, height, false, false );
            setRoot(buildGUI(width,height));
        }

        private JComponent buildGUI (int width, int height) {

            JPanel p = new JPanel();
            p.setDoubleBuffered(true);
            p.setSize(new Dimension(width,height));
            p.setLocation(0,0);
            p.setBackground(Color.darkGray);

            // add a button for disabling lighting

            final JToggleButton lightButton = new JToggleButton();
            lightButton.setText("Lines");
            lightButton.setMargin(new Insets(0,0,0,0));
            lightButton.setPreferredSize(new Dimension(90,25));
            lightButton.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e) {
                    if (lightButton.isSelected()) {
                        terrainShape.setLineMode(true);
                        lightButton.setText("Fill");
                    } else {
                        terrainShape.setLineMode(false);
                        lightButton.setText("Lines");
                    }

                }
            });
            p.add(lightButton);

            JButton exitButton = new JButton();
            exitButton.setPreferredSize(new Dimension(80,25));
            exitButton.setText("Exit");
            exitButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    System.exit(0);
                }
            });
            p.add(exitButton);

            final JScrollBar progress = new JScrollBar(JScrollBar.HORIZONTAL,(int)speed,1,1,10);
            progress.setPreferredSize(new Dimension(80,25));
            progress.addAdjustmentListener(new AdjustmentListener() {
                public void adjustmentValueChanged(AdjustmentEvent e) {
                    speed = progress.getValue();
                }
            });
            p.add(progress);


            return p;
        }
    }

	public static void main(String[] args)
	{
		System.out.println("Hit SPACE to toggle projection policy, or ESC to exit");
		try
		{
			Xith3DTerrainTest test = new Xith3DTerrainTest();
			test.init();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

}
     

Total 634 Lines of Code.
Source code formatted using showsrc by William Denniss