Decal generator test app

I made this test app for the decal generator I’ve been working on. Still needs work and optimization but you can stick decals.

DecalTestApp.java:

[java]

/**
*

  • @author trblair
    */
    import com.jme3.app.SimpleApplication;
    import com.jme3.asset.AssetManager;
    import com.jme3.asset.plugins.FileLocator;
    import com.jme3.asset.plugins.ZipLocator;
    import com.jme3.bullet.BulletAppState;
    import com.jme3.bullet.collision.shapes.CollisionShape;
    import com.jme3.bullet.control.RigidBodyControl;
    import com.jme3.bullet.util.CollisionShapeFactory;
    import com.jme3.input.KeyInput;
    import com.jme3.input.MouseInput;
    import com.jme3.input.controls.ActionListener;
    import com.jme3.input.controls.KeyTrigger;
    import com.jme3.input.controls.MouseButtonTrigger;
    import com.jme3.light.AmbientLight;
    import com.jme3.light.DirectionalLight;
    import com.jme3.math.ColorRGBA;
    import com.jme3.math.Vector3f;
    import com.jme3.renderer.Camera;
    import com.jme3.scene.Node;
    import com.jme3.scene.Spatial;
    import java.util.HashMap;

/**

  • Example 9 - How to make walls and floors solid.
  • This collision code uses Physics and a custom Action Listener.
  • @author normen, with edits by Zathras
    */
    public class DecalTestApp extends SimpleApplication
    implements ActionListener {

public static HashMap<String, Boolean> keysPressed;
private Spatial sceneModel;
private BulletAppState bulletAppState;
private RigidBodyControl landscape;

public DecalPlayer player;
private Vector3f walkDirection = new Vector3f();

public static AssetManager assetManager;
public static Node rootNode;
public static Camera cam;
public static Node hitNode;
public static boolean showPreview = false;

public static void main(String[] args) {
DecalTestApp app = new DecalTestApp();
app.start();
}

public void simpleInitApp() {
/** Set up Physics */
assetManager = this.getAssetManager();
rootNode = this.getRootNode();
cam = this.getCamera();
keysPressed = new HashMap();
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
//bulletAppState.getPhysicsSpace().enableDebug(assetManager);

// We re-use the flyby camera for rotation, while positioning is handled by physics
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
flyCam.setMoveSpeed(100);
setUpKeys();
setUpLight();



// We load the scene from the zip file and adjust its size.

// assetManager.registerLocator(“assets/”, FileLocator.class);
assetManager.registerLocator(“wildhouse.zip”, ZipLocator.class);
sceneModel = assetManager.loadModel(“main.scene”);

// We set up collision detection for the scene by creating a
// compound collision shape and a static RigidBodyControl with mass zero.
CollisionShape sceneShape =
        CollisionShapeFactory.createMeshShape((Node) sceneModel);
landscape = new RigidBodyControl(sceneShape, 0);
sceneModel.addControl(landscape);
assetManager.registerLocator("assets/", FileLocator.class);

player = new DecalPlayer(new Vector3f(0,10,0));
player.init();
// We attach the scene and the player to the rootNode and the physics space,
// to make them appear in the game world.
hitNode = new Node();

hitNode.attachChild(sceneModel);
rootNode.attachChild(hitNode);
bulletAppState.getPhysicsSpace().add(landscape);
bulletAppState.getPhysicsSpace().add(player.player);

}

private void setUpLight() {
// We add light so we see the scene
AmbientLight al = new AmbientLight();
al.setColor(ColorRGBA.White.mult(1.3f));
rootNode.addLight(al);

DirectionalLight dl = new DirectionalLight();
dl.setColor(ColorRGBA.White);
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
rootNode.addLight(dl);

}

/** We over-write some navigational key mappings here, so we can

  • add physics-controlled walking and jumping: */
    private void setUpKeys() {
    inputManager.addMapping(“Left”, new KeyTrigger(KeyInput.KEY_A));
    inputManager.addMapping(“Right”, new KeyTrigger(KeyInput.KEY_D));
    inputManager.addMapping(“Up”, new KeyTrigger(KeyInput.KEY_W));
    inputManager.addMapping(“Down”, new KeyTrigger(KeyInput.KEY_S));
    inputManager.addMapping(“Jump”, new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addMapping(“shoot”, new KeyTrigger(KeyInput.KEY_Z));
    inputManager.addListener(this, “Left”);
    inputManager.addListener(this, “Right”);
    inputManager.addListener(this, “Up”);
    inputManager.addListener(this, “Down”);
    inputManager.addListener(this, “Jump”);
    inputManager.addListener(this, “shoot”);
inputManager.addMapping("splat", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(this, "splat");

inputManager.addMapping("show", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
inputManager.addListener(this, "show");

}

/** These are our custom actions triggered by key presses.

  • We do not walk yet, we just keep track of the direction the user pressed. */
    @Override
    public void onAction(String binding, boolean value, float tpf) {

    if (value && binding.equals(“show”)) {

       showPreview = (showPreview == true)? false : true;
    

    }

     keysPressed.put(binding, value);
    

}

/**

  • This is the main event loop–walking happens here.

  • We check in which direction the player is walking by interpreting

  • the camera direction forward (camDir) and to the side (camLeft).

  • The setWalkDirection() command is what lets a physics-controlled player walk.

  • We also make sure here that the camera moves with player.
    */
    @Override
    public void simpleUpdate(float tpf) {

    player.update();

}
}

[/java]

DecalPlayer.java;

[java]

/**
*

  • @author trblair
    */
    public class DecalPlayer {

    public DecalProjector projector;

    public Vector3f position;
    public CharacterControl player;

    public Vector3f walkDirection;

    public DecalPlayer(Vector3f position){

     this.position = position;
    

    }

    public void init(){

     CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
     player = new CharacterControl(capsuleShape, 0.05f);
     player.setJumpSpeed(20);
     player.setFallSpeed(30);
     player.setGravity(30);
     player.setPhysicsLocation(new Vector3f(0, 10, 0));
     
     
     Quaternion cam_rotation = DecalTestApp.cam.getRotation();
     Quaternion projector_rotation = new Quaternion(cam_rotation.getX(),cam_rotation.getY(),cam_rotation.getZ(),cam_rotation.getW());
     projector = new DecalProjector(position,projector_rotation,10);
     
     walkDirection = new Vector3f(0,0,0);
    

    }

    public void update(){

     Vector3f camDir = DecalTestApp.cam.getDirection().clone().multLocal(0.6f);
     Vector3f camLeft = DecalTestApp.cam.getLeft().clone().multLocal(0.4f);
     walkDirection.set(0, 0, 0);
     Boolean keypress = DecalTestApp.keysPressed.get("Left");
     if (keypress != null &amp;&amp; keypress){ walkDirection.addLocal(camLeft); }
     keypress = DecalTestApp.keysPressed.get("Right");
     if (keypress != null &amp;&amp; keypress) { walkDirection.addLocal(camLeft.negate()); }
     keypress = DecalTestApp.keysPressed.get("Up");
     if (keypress != null &amp;&amp; keypress)    { walkDirection.addLocal(camDir); }
     keypress = DecalTestApp.keysPressed.get("Down");
     if (keypress != null &amp;&amp; keypress)  { walkDirection.addLocal(camDir.negate()); }
     player.setWalkDirection(walkDirection);
     DecalTestApp.cam.setLocation(player.getPhysicsLocation());
     
     
     if(DecalTestApp.showPreview){
         projector.updateSelectionPreview();
     }else if(projector.decal_preview != null){
         projector.decal_preview.setCullHint(Spatial.CullHint.Always);
     }
    

    }

}

[/java]

DecalProjector.java:

[java]

import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.Quaternion;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
*

  • @author trblair
    */
    public class DecalProjector {

    public Geometry decal_preview = null;
    public Geometry crossHair;
    public DecalProjectionBox inner_projection;
    public DecalProjectionBox outer_projection;
    public float projection_size;
    public Material preview_material;
    public ArrayList triStrings;
    public HashMap<String,Vector3f> vertMap;
    public HashMap<String,Vector3f> normalMap;
    public HashMap<String,Vector2f> textureMap;
    public HashMap<String,Integer> vertKeys;
    public Vector3f[] finalVerts;
    public Vector3f[] finalNormals;
    public Vector2f[] finalTexCoords;
    public int[] finalIndices;
    public long wait_time, current_time, last_time = 0;

    public DecalProjector(Vector3f position, Quaternion rotation, float size){

     this.projection_size = size;
     inner_projection = new DecalProjectionBox( position, rotation, projection_size);
     outer_projection = new DecalProjectionBox( position, rotation, projection_size*2);
     
     preview_material = new Material(DecalTestApp.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
     preview_material.setTexture("ColorMap", DecalTestApp.assetManager.loadTexture("textures/transpBlue75.png"));
     preview_material.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
    

    }

    public void updateSelectionPreview(){

     CollisionResults results = new CollisionResults();
     Ray ray = new Ray(DecalTestApp.cam.getLocation(), DecalTestApp.cam.getDirection());
     DecalTestApp.rootNode.collideWith(ray, results);
     
     if(results.size()&gt;0){
         
         CollisionResult closest = results.getClosestCollision();
    
         generateSelection(closest);
         displayPreview();
    
         if(decal_preview != null){
             
             decal_preview.setCullHint(Spatial.CullHint.Inherit);
         
         }
         
         Boolean rightClickDown = DecalTestApp.keysPressed.get("splat");
         
         if((rightClickDown != null &amp;&amp; rightClickDown &amp;&amp; wait_time &gt; 500)){
             
             attachDecal();
             last_time = System.currentTimeMillis();
             
         }
         
         current_time = System.currentTimeMillis();
         wait_time = current_time - last_time;
         
     }else if(decal_preview != null){
    
         decal_preview.setCullHint(Spatial.CullHint.Always);
     
     }
    

    }

    public void generateSelection(CollisionResult collision){

     Vector3f hit_position = collision.getContactPoint();
     Vector3f hit_normal = collision.getContactNormal().normalize();
     Vector3f cam_direction = DecalTestApp.cam.getDirection();
     Vector3f direction = new Vector3f(cam_direction.x,cam_direction.y,cam_direction.z);
    
     Quaternion q = inner_projection.getRotation();
     q.lookAt(hit_normal, direction);
    
     inner_projection.update( hit_position, q);
     outer_projection.update( hit_position, q);
     
     triStrings = new ArrayList();
     vertMap = new HashMap();
     normalMap = new HashMap();
     textureMap = new HashMap();
     vertKeys = new HashMap();
     
     Geometry collisionGeometry = collision.getGeometry();
     Mesh collisionMesh = collisionGeometry.getMesh();
     
     clipSelectedMesh(collisionMesh);
     
     
     
     finalVerts = new Vector3f[vertMap.size()];
     finalNormals = new Vector3f[vertMap.size()];
     finalTexCoords = new Vector2f[vertMap.size()];
     finalIndices = new int[triStrings.size()];
     int vertCount = 0;
     
     for(Map.Entry&lt;String,Vector3f&gt; entry : vertMap.entrySet()){
         
         String key = entry.getKey();
         finalVerts[vertCount] = entry.getValue().add(normalMap.get(key).divide(10));
         finalNormals[vertCount] = normalMap.get(key);
         finalTexCoords[vertCount] = textureMap.get(key);
         vertKeys.put(key, vertCount);
         vertCount++;
         
     }
     
     int indexCount = 0;
     
     for(int i = 0;i&lt;triStrings.size();i+=3){
         
         finalIndices[indexCount] = vertKeys.get((String)triStrings.get(i));
         indexCount++;
         finalIndices[indexCount] = vertKeys.get((String)triStrings.get(i+1));
         indexCount++;
         finalIndices[indexCount] = vertKeys.get((String)triStrings.get(i+2));
         indexCount++;
         
     }
    

    }

    public void displayPreview(){

     if(decal_preview == null){
         
         Mesh newMesh = new Mesh();
         newMesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(finalVerts));
         newMesh.setBuffer(VertexBuffer.Type.Normal, 3, BufferUtils.createFloatBuffer(finalNormals));
         newMesh.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(finalIndices));
         newMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(finalTexCoords));
         newMesh.updateBound();
         decal_preview = new Geometry("decalPreview",newMesh);
         
         decal_preview.setMaterial(preview_material);
         decal_preview.setQueueBucket(RenderQueue.Bucket.Transparent);
         decal_preview.setUserData("debug",(new Boolean(true)));
         decal_preview.setLocalTranslation(0, 0, 0);
         DecalTestApp.rootNode.attachChild(decal_preview);
         
     }else{
         
         decal_preview.getMesh().setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(finalVerts));
         decal_preview.getMesh().setBuffer(VertexBuffer.Type.Normal, 3, BufferUtils.createFloatBuffer(finalNormals));
         decal_preview.getMesh().setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(finalIndices));
         decal_preview.getMesh().setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(finalTexCoords));
         decal_preview.getMesh().updateCounts();
         decal_preview.getMesh().updateBound();
         decal_preview.updateModelBound();
         
     }
    

    }

    public void attachDecal(){

     Texture decal_texture = DecalTestApp.assetManager.loadTexture("textures/Monkey.png");
     Material decal_material = new Material(DecalTestApp.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
     decal_material.setTexture("ColorMap", decal_texture);
     decal_material.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
     
     Mesh newMesh = new Mesh();
     newMesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(finalVerts));
     newMesh.setBuffer(VertexBuffer.Type.Normal, 3, BufferUtils.createFloatBuffer(finalNormals));
     newMesh.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(finalIndices));
     newMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(finalTexCoords));
     newMesh.updateBound();
     Geometry decal = new Geometry("decal",newMesh);
     
     
      
     decal.getMesh().updateBound();
     decal.setMaterial(decal_material);
     decal.setQueueBucket(RenderQueue.Bucket.Transparent);
     
     decal.setLocalTranslation(0, 0, 0);
     
     DecalTestApp.rootNode.attachChild(decal);
    

    }

    public void clipSelectedMesh(Mesh collisionMesh){

     ArrayList &lt;HashMap &lt;String, Vector3f[]&gt; &gt; to_clip = new ArrayList();
     
     FloatBuffer vertex_buffer = collisionMesh.getFloatBuffer(VertexBuffer.Type.Position);
     Vector3f[] vertex_array = BufferUtils.getVector3Array(vertex_buffer);
     
     FloatBuffer normal_buffer = collisionMesh.getFloatBuffer(VertexBuffer.Type.Normal);
     Vector3f[] normal_array = BufferUtils.getVector3Array(normal_buffer);
     
     IndexBuffer face_buffer = collisionMesh.getIndexBuffer();
         int[] index_array;
         
         // Check instance type of index buffer and extract indexing data accordingly.
         if(face_buffer.getBuffer() instanceof ShortBuffer){
             
             // ShortBuffer instace type is generally only encountered with JME primitive meshes.
             ShortBuffer index_buffer = (ShortBuffer)face_buffer.getBuffer();
             index_buffer.rewind();
             int length = index_buffer.limit();
             index_array = new int[length];
             int index_count = 0;
             
             // If we do encounter a ShortBuffer we must iterate the buffer 
             // and extract each element individually as there is no BufferUtils.getShortArray method.. 
             while(index_buffer.hasRemaining()){
                 
                 // Copy each element of index buffer to index array.
                 index_array[index_count] = (int)index_buffer.get();
                 index_count++;
             
             }
         
         }else{
             
             // If index buffer isn't a ShortBuffer we can just 
             // get the int array with BufferUtils. 
             IntBuffer index_buffer = (IntBuffer)face_buffer.getBuffer();
             index_array = BufferUtils.getIntArray(index_buffer);
         
         }
     
     for(int out_i = 0; out_i &lt; index_array.length; out_i+=3){
         
         int current_index = out_i;
         
         for(int in_i = 0; in_i &lt; 3; in_i++){
             
             Vector3f current_vertex = vertex_array[index_array[current_index + in_i]];
             
             if(outer_projection.contains(current_vertex)){
    
                 Vector3f[] tri_verts = { vertex_array[index_array[current_index]], vertex_array[index_array[current_index+1]], vertex_array[index_array[current_index+2]]};
                 Vector3f[] tri_norms = { normal_array[index_array[current_index]], normal_array[index_array[current_index+1]], normal_array[index_array[current_index+2]]};
                 HashMap&lt;String, Vector3f[]&gt;  tri_map = new HashMap();
                 tri_map.put("vertices", tri_verts);
                 tri_map.put("normals", tri_norms);
                 to_clip.add(tri_map);
                 break;
    
             }
                 
         }
         
     }
     
     
     float size = projection_size;
     float scale = size*2;
     ArrayList &lt;HashMap &lt;String, Vector3f[]&gt; &gt; clipped_inside = inner_projection.clipToProjection(to_clip, 0);
    
     for(int i = 0; i &lt; clipped_inside.size(); i++){
    
         HashMap &lt;String, Vector3f[]&gt; clipped_triangle = clipped_inside.get(i);
    
         Vector3f[] vertices = clipped_triangle.get("vertices");
         Vector3f[] normals = clipped_triangle.get("normals");
    
         for(int in_i = 0; in_i &lt; vertices.length; in_i++){
    
             Vector3f vert = vertices[in_i];
             String vertKey = vert.toString();
             vertMap.put(vertKey, vert );
             normalMap.put(vertKey, normals[in_i]);
             Vector3f local = new Vector3f(vert);
             local = inner_projection.projection_geometry.worldToLocal(local, local);
             Vector2f texCoord = new Vector2f((local.x+size)/scale,(local.y+size)/scale);
             textureMap.put(vertKey, texCoord);
             triStrings.add(vertKey);
    
         }
    
     }  
    

    }

}

[/java]

DecalProjectionBox.java:

[java]

import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;

/**
*

  • @author trblair
    */
    public class DecalProjectionBox {

    public Quaternion rotation;
    public Vector3f position;
    public float size;
    public int[] triangles;
    public Vector3f[] local_vertices;
    public Vector3f[] world_vertices;
    public Vector3f[] projection_planes;
    public Geometry projection_geometry;
    public static final int COPLANAR = 0, FRONT = 1, BACK = 2, SPANNING = 3;
    public static final double EPSILON = 1e-5;
    public int bottomPlaneIndex;

    public DecalProjectionBox(Vector3f position, Quaternion rotation, float size){

     this.position = position;
     this.rotation = rotation;
     this.size = size;
     
     local_vertices = new Vector3f[8];
     world_vertices = new Vector3f[8];
     projection_planes = new Vector3f[6];
     triangles = new int[36];
     init();
    

    }

    public void init() {

     local_vertices[0] = new Vector3f((size*-1),(size*-1),(size*-1));
     local_vertices[1] = new Vector3f((size*-1),size,(size*-1));
     local_vertices[2] = new Vector3f(size,size,(size*-1));
     local_vertices[3] = new Vector3f(size,(size*-1),(size*-1));
     local_vertices[4] = new Vector3f(size,size,size);
     local_vertices[5] = new Vector3f((size*-1),size,size);
     local_vertices[6] = new Vector3f((size*-1),(size*-1),size);//0,1  2,3  4,7  5,6
     local_vertices[7] = new Vector3f(size,(size*-1),size);
     
     //front plane
     
     triangles[0] = 3; triangles[1] = 0; triangles[2] = 1;
     projection_planes[0] = new Vector3f(3,0,1);
     triangles[3] = 1; triangles[4] = 2; triangles[5] = 3;
     
     //left plane
     
     triangles[6] = 7; triangles[7] = 3; triangles[8] = 2;
     projection_planes[1] = new Vector3f(7,3,2);
     triangles[9] = 2; triangles[10] = 4; triangles[11] = 7;
    
     //back plane
     
     triangles[12] = 6; triangles[13] = 7; triangles[14] = 4;
     projection_planes[2] = new Vector3f(6,7,4);
     triangles[15] = 4; triangles[16] = 5; triangles[17] = 6;
    
     //right plane
     
     triangles[18] = 0; triangles[19] = 6; triangles[20] = 5;
     projection_planes[3] = new Vector3f(0,6,5);
     triangles[21] = 5; triangles[22] = 1; triangles[23] = 0;
     
     //top plane
     
     triangles[24] = 2; triangles[25] = 1; triangles[26] = 5;
     projection_planes[4] = new Vector3f(2,1,5);
     triangles[27] = 5; triangles[28] = 4; triangles[29] = 2;
     
     //bottom plane
     
     triangles[30] = 7; triangles[31] = 6; triangles[32] = 0;
     projection_planes[5] = new Vector3f(7,6,0);
     triangles[33] = 0; triangles[34] = 3; triangles[35] = 7;
     bottomPlaneIndex = 5;
     
     Mesh box_mesh = new Mesh();
     box_mesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(local_vertices));
     box_mesh.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(triangles));
    
     projection_geometry = new Geometry("see-through box", box_mesh);
     
     update(position, rotation);
    

    }

    // updates cube position, rotation and world_vertices array
    public void update(Vector3f position, Quaternion rotation){

     this.position = position;
     projection_geometry.setLocalTranslation(position);
     
     this.rotation = rotation;
     projection_geometry.setLocalRotation(rotation);
         
     FloatBuffer vertBuff = projection_geometry.getMesh().getFloatBuffer(VertexBuffer.Type.Position);
     Vector3f[] verts = BufferUtils.getVector3Array(vertBuff);
     
     for(int i = 0; i &lt; verts.length; i++){
         
         Vector3f point = verts[i];
         projection_geometry.localToWorld(point, point);
         world_vertices[i] = point;
         
     }
    

    }

    public Vector3f[] getWorldVertices(){

     return world_vertices;
    

    }

    public Quaternion getRotation(){

     return rotation;
    

    }

    public Vector3f getOrientationNormal(){

     Vector3f planeIndices = projection_planes[bottomPlaneIndex];
     Vector3f plane_normal = calculateNormal(world_vertices[(int)planeIndices.x], world_vertices[(int)planeIndices.y], world_vertices[(int)planeIndices.z]);
     return plane_normal.normalize().negate();
    

    }

    // return true if given point lies within the planar bounds of the cube
    public boolean contains(Vector3f point){

     boolean contains = false;
     
     for(int i = 0 ;i &lt; projection_planes.length; i++){
         
         Vector3f planeIndices = projection_planes[i];
         Vector3f plane_normal = calculateNormal(world_vertices[(int)planeIndices.x], world_vertices[(int)planeIndices.y], world_vertices[(int)planeIndices.z]);
         float plane_constant = plane_normal.dot(world_vertices[(int)planeIndices.x]);
         double t = plane_normal.dot(point) - plane_constant;
         
         if(t &lt;= EPSILON){
         
             contains = true;
         
         }else{
             
             contains = false;
             return contains;
         
         }
         
     }
     
     return contains;
    

    }

    // recursively clips polygons to the cubes six planes. used in our decal projection algorithm
    public ArrayList clipToProjection(ArrayList <HashMap <String, Vector3f[]> > unsplit_polygons, int plane_index){

     if(plane_index &gt; (projection_planes.length-1) || unsplit_polygons.isEmpty()){
         
         return unsplit_polygons;
         
     }
     
     Vector3f plane = projection_planes[plane_index];
     Vector3f plane_normal = calculateNormal(world_vertices[(int)plane.x], world_vertices[(int)plane.y], world_vertices[(int)plane.z]);
     float plane_constant = plane_normal.dot(world_vertices[(int)plane.x]);
     
     ArrayList &lt;HashMap &lt;String, Vector3f[]&gt; &gt; split_polygons = new ArrayList();
     
     for(int outer_i = 0; outer_i &lt; unsplit_polygons.size(); outer_i++){
         
         HashMap &lt;String, Vector3f[]&gt; unsplit_polygon_map = unsplit_polygons.get(outer_i);
         Vector3f[] unsplit_polygon_vertices = unsplit_polygon_map.get("vertices");
         Vector3f[] unsplit_polygon_normals = unsplit_polygon_map.get("normals");
         
         int[] types = new int[unsplit_polygon_vertices.length];
         int polygon_type = 0;
         
         for(int middle_i = 0; middle_i &lt; unsplit_polygon_vertices.length; middle_i++){
    
             Vector3f current_vert =  new Vector3f(unsplit_polygon_vertices[middle_i]);
             
             double t = plane_normal.dot(current_vert) - plane_constant;
             int type; 
             if (t &lt; EPSILON*-1) {type = BACK;} else if (t &gt; EPSILON) { type = FRONT;} else { type = COPLANAR;}
             polygon_type |= type;
             types[middle_i] = type;
    
         }
    
         if(polygon_type == SPANNING){
    
             ArrayList &lt;Vector3f&gt; clipped_back_vertices = new ArrayList();
             
             ArrayList &lt;Vector3f&gt; clipped_back_normals = new ArrayList();
             
             for(int inner_i = 0; inner_i &lt; unsplit_polygon_vertices.length; inner_i++){
    
                 int j = (inner_i + 1) % unsplit_polygon_vertices.length;
                 
                 Vector3f vertI =  new Vector3f(unsplit_polygon_vertices[inner_i]);
                 Vector3f vertJ =  new Vector3f(unsplit_polygon_vertices[j]);
                 
                 Vector3f normI =  new Vector3f(unsplit_polygon_normals[inner_i]);
                 Vector3f normJ =  new Vector3f(unsplit_polygon_normals[j]);
                 
                 int typeI = types[inner_i];
                 int typeJ = types[j];
    
                 if(typeI != FRONT){
    
                     clipped_back_vertices.add(new Vector3f(vertI));
                     clipped_back_normals.add(new Vector3f(normI));
    
                 }
    
                 if((typeI | typeJ) == SPANNING){
                     
                     float t = (plane_constant - plane_normal.dot(vertI)) / plane_normal.dot(vertJ.subtract(vertI));
                     
                     Vector3f v = vertI.interpolate(vertJ, t);
                     
                     clipped_back_vertices.add(v);
                     
                     Vector3f n = normI.interpolate(normJ, t).normalize();
                     
                     clipped_back_normals.add(n);
                     
                 } 
    
             }
             
             if(clipped_back_vertices.size() == 4){
                 
                 HashMap &lt;String,Vector3f[]&gt; clipped_back_map_a = new HashMap();
                 HashMap &lt;String,Vector3f[]&gt; clipped_back_map_b = new HashMap();
                 
                 Vector3f[] clipped_back_vertices_a = {clipped_back_vertices.get(0),clipped_back_vertices.get(1),clipped_back_vertices.get(2)};
                 Vector3f[] clipped_back_vertices_b = {clipped_back_vertices.get(2),clipped_back_vertices.get(3),clipped_back_vertices.get(0)};
                 
                 Vector3f[] clipped_back_normals_a = {clipped_back_normals.get(0),clipped_back_normals.get(1),clipped_back_normals.get(2)};
                 Vector3f[] clipped_back_normals_b = {clipped_back_normals.get(2),clipped_back_normals.get(3),clipped_back_normals.get(0)};
                 
                 clipped_back_map_a.put("vertices", clipped_back_vertices_a);
                 clipped_back_map_a.put("normals", clipped_back_normals_a);
                 
                 clipped_back_map_b.put("vertices", clipped_back_vertices_b);
                 clipped_back_map_b.put("normals", clipped_back_normals_b);
                 
                 split_polygons.add(clipped_back_map_a);
                 split_polygons.add(clipped_back_map_b);
    
             }else if(clipped_back_vertices.size() == 3){
                 
                 HashMap &lt;String,Vector3f[]&gt; clipped_back_map_a = new HashMap();
                 Vector3f[] clipped_back_vertices_a = {clipped_back_vertices.get(0),clipped_back_vertices.get(1),clipped_back_vertices.get(2)};
                 Vector3f[] clipped_back_normals_a = {clipped_back_normals.get(0),clipped_back_normals.get(1),clipped_back_normals.get(2)};
                 clipped_back_map_a.put("vertices", clipped_back_vertices_a);
                 clipped_back_map_a.put("normals", clipped_back_normals_a);
                 split_polygons.add(clipped_back_map_a);
    
             }else{
                 
                 System.err.println("Clipping algorithm has encounter a degenerate case!! Output polygon is a line!!");
                 
             }
             
         }else if(polygon_type != FRONT){
             
             split_polygons.add(unsplit_polygon_map);
             
         }
         
     }
     
     return clipToProjection(split_polygons, plane_index + 1);
    

    }

    public Vector3f calculateNormal(Vector3f vertA, Vector3f vertB, Vector3f vertC){

     // the normal is equal to the normalized value of the cross product of B minus A and C minus A.
     Vector3f normal = vertB.subtract(vertA).cross((vertC.subtract(vertA))).normalize();
     
     // return the computed normal.
     return normal;
    

    }

}

[/java]

2 Likes

1 Like