Java tutorial
/******************************************************************************* * 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.obj; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import org.apache.commons.math3.exception.MathArithmeticException; import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; import org.jtrfp.trcl.ObjectDefinitionWindow; import org.jtrfp.trcl.PrimitiveList; import org.jtrfp.trcl.SpacePartitioningGrid; import org.jtrfp.trcl.Submitter; import org.jtrfp.trcl.beh.Behavior; import org.jtrfp.trcl.beh.BehaviorNotFoundException; import org.jtrfp.trcl.beh.CollisionBehavior; import org.jtrfp.trcl.beh.NullBehavior; import org.jtrfp.trcl.core.TR; import org.jtrfp.trcl.gpu.GPU; import org.jtrfp.trcl.gpu.Model; import org.jtrfp.trcl.math.Mat4x4; import org.jtrfp.trcl.math.Vect3D; import org.jtrfp.trcl.mem.VEC4Address; public class WorldObject implements PositionedRenderable { public static final boolean LOOP = true; private double[] heading = new double[] { 0, 0, 1 }; private double[] top = new double[] { 0, 1, 0 }; protected volatile double[] position = new double[3]; protected double[] modelOffset = new double[3]; private final double[] positionWithOffset = new double[3]; private boolean needToRecalcMatrix = true; private final TR tr; private boolean visible = true; private Model model; private List<PositionedRenderable> lastContainingList; private int[] triangleObjectDefinitions; private int[] transparentTriangleObjectDefinitions; protected Integer matrixID; private ByteBuffer opaqueObjectDefinitionAddressesInVec4 = ByteBuffer.allocate(0);// defaults to empty private ByteBuffer transparentObjectDefinitionAddressesInVec4 = ByteBuffer.allocate(0);// defaults to empty private WeakReference<SpacePartitioningGrid> containingGrid; private ArrayList<Behavior> inactiveBehaviors = new ArrayList<Behavior>(); private ArrayList<CollisionBehavior> collisionBehaviors = new ArrayList<CollisionBehavior>(); private ArrayList<Behavior> tickBehaviors = new ArrayList<Behavior>(); private final NullBehavior nullBehavior; private boolean active = true; private byte renderFlags = 0; private boolean immuneToOpaqueDepthTest = false; protected final double[] aX = new double[3]; protected final double[] aY = new double[3]; protected final double[] aZ = new double[3]; protected final double[] rotTransM = new double[16]; protected final double[] camM = new double[16]; protected final double[] rMd = new double[16]; protected final double[] tMd = new double[16]; protected double[] cMd = new double[16]; private boolean respondToTick = true; private VEC4Address[] opaqueObjectDefinitionAddressesInVEC4; private VEC4Address[] transparentObjectDefinitionAddressesInVEC4; public WorldObject(TR tr) { this.nullBehavior = new NullBehavior(this); this.tr = tr; matrixID = tr.matrixWindow.get().create(); // Matrix constants setup rMd[15] = 1; tMd[0] = 1; tMd[5] = 1; tMd[10] = 1; tMd[15] = 1; } public WorldObject(TR tr, Model m) { this(tr); setModel(m); }// end constructor void proposeCollision(WorldObject other) { for (int i = 0; i < collisionBehaviors.size(); i++) { collisionBehaviors.get(i).proposeCollision(other); } // end for(collisionBehaviors) }// end proposeCollision(...) public boolean isCollideable() { return !collisionBehaviors.isEmpty(); } public <T extends Behavior> T addBehavior(T ob) { if (ob.isEnabled()) { if (ob instanceof CollisionBehavior) collisionBehaviors.add((CollisionBehavior) ob); tickBehaviors.add(ob); } else { inactiveBehaviors.add(ob); } ob.setParent(this); return ob; } protected boolean recalcMatrixWithEachFrame() { return false; } public <T> T probeForBehavior(Class<T> bC) { if (bC.isAssignableFrom(CollisionBehavior.class)) { for (int i = 0; i < collisionBehaviors.size(); i++) { if (bC.isAssignableFrom(collisionBehaviors.get(i).getClass())) { return (T) collisionBehaviors.get(i); } } // end if(instanceof) } // emd if(isAssignableFrom(CollisionBehavior.class)) for (int i = 0; i < inactiveBehaviors.size(); i++) { if (bC.isAssignableFrom(inactiveBehaviors.get(i).getClass())) { return (T) inactiveBehaviors.get(i); } } // end if(instanceof) for (int i = 0; i < tickBehaviors.size(); i++) { if (bC.isAssignableFrom(tickBehaviors.get(i).getClass())) { return (T) tickBehaviors.get(i); } } // end if(instanceof) throw new BehaviorNotFoundException("Cannot find behavior of type " + bC.getName() + " in behavior sandwich owned by " + this.toString()); }// end probeForBehavior public <T> void probeForBehaviors(Submitter<T> sub, Class<T> type) { synchronized (collisionBehaviors) { if (type.isAssignableFrom(CollisionBehavior.class)) { for (int i = 0; i < collisionBehaviors.size(); i++) { if (type.isAssignableFrom(collisionBehaviors.get(i).getClass())) { sub.submit((T) collisionBehaviors.get(i)); } } // end if(instanceof) } // end isAssignableFrom(CollisionBehavior.class) } synchronized (inactiveBehaviors) { for (int i = 0; i < inactiveBehaviors.size(); i++) { if (type.isAssignableFrom(inactiveBehaviors.get(i).getClass())) sub.submit((T) inactiveBehaviors.get(i)); } // end if(instanceof) } synchronized (tickBehaviors) { for (int i = 0; i < tickBehaviors.size(); i++) { if (type.isAssignableFrom(tickBehaviors.get(i).getClass())) sub.submit((T) tickBehaviors.get(i)); } // end for (tickBehaviors) } //end sync(tickBehaviors) }// end probeForBehaviors(...) public void tick(long time) { if (!respondToTick) return; synchronized (tickBehaviors) { for (int i = 0; i < tickBehaviors.size() && isActive(); i++) tickBehaviors.get(i).tick(time); } //end sync(tickBehaviors) }// end tick(...) private final int[] emptyIntArray = new int[0]; public void setModel(Model m) { if (m == null) throw new RuntimeException("Passed model cannot be null."); model = m; model.finalizeModel(); int numObjDefs, sizeInVerts; if (m.getTriangleList() == null) triangleObjectDefinitions = emptyIntArray; else { sizeInVerts = m.getTriangleList().getTotalSizeInGPUVertices(); numObjDefs = sizeInVerts / GPU.GPU_VERTICES_PER_BLOCK; if (sizeInVerts % GPU.GPU_VERTICES_PER_BLOCK != 0) numObjDefs++; triangleObjectDefinitions = new int[numObjDefs]; for (int i = 0; i < numObjDefs; i++) { triangleObjectDefinitions[i] = tr.objectDefinitionWindow.get().create(); } } if (m.getTransparentTriangleList() == null) transparentTriangleObjectDefinitions = emptyIntArray; else { sizeInVerts = m.getTransparentTriangleList().getTotalSizeInGPUVertices(); numObjDefs = sizeInVerts / GPU.GPU_VERTICES_PER_BLOCK; if (sizeInVerts % GPU.GPU_VERTICES_PER_BLOCK != 0) numObjDefs++; transparentTriangleObjectDefinitions = new int[numObjDefs]; for (int i = 0; i < numObjDefs; i++) { transparentTriangleObjectDefinitions[i] = tr.objectDefinitionWindow.get().create(); } } initializeObjectDefinitions(); }// end setModel(...) public synchronized void setDirection(ObjectDirection dir) { if (dir.getHeading().getNorm() == 0 || dir.getTop().getNorm() == 0) { System.err.println("Warning: Rejecting zero-norm for object direction. " + dir); new Exception().printStackTrace(); return; } setHeading(dir.getHeading()); setTop(dir.getTop()); } @Override public String toString() { final String modelDebugName; if (model != null) modelDebugName = model.getDebugName(); else modelDebugName = "[null model]"; return "WorldObject Model=" + modelDebugName + " pos=" + this.getPosition() + " class=" + getClass().getName(); } public final void initializeObjectDefinitions() { if (model == null) throw new NullPointerException("Model is null. Did you forget to set it?"); final ArrayList<Integer> opaqueIndicesList = new ArrayList<Integer>(); final ArrayList<Integer> transparentIndicesList = new ArrayList<Integer>(); tr.getThreadManager().submitToThreadPool(new Callable<Void>() { @Override public Void call() throws Exception { tr.getThreadManager().submitToGPUMemAccess(new Callable<Void>() { @Override public Void call() throws Exception { processPrimitiveList(model.getTriangleList(), triangleObjectDefinitions, opaqueIndicesList); processPrimitiveList(model.getTransparentTriangleList(), transparentTriangleObjectDefinitions, transparentIndicesList); return null; } }).get();//TODO: Make non-blocking ByteOrder order = getTr().gpu.get().getByteOrder(); opaqueObjectDefinitionAddressesInVec4 = ByteBuffer.allocateDirect(opaqueIndicesList.size() * 4) .order(order);// 4 bytes per int opaqueObjectDefinitionAddressesInVEC4 = new VEC4Address[opaqueIndicesList.size()]; for (int i = 0; i < opaqueIndicesList.size(); i++) opaqueObjectDefinitionAddressesInVEC4[i] = new VEC4Address(opaqueIndicesList.get(i)); transparentObjectDefinitionAddressesInVec4 = ByteBuffer .allocateDirect(transparentIndicesList.size() * 4).order(order); transparentObjectDefinitionAddressesInVEC4 = new VEC4Address[transparentIndicesList.size()]; for (int i = 0; i < transparentIndicesList.size(); i++) transparentObjectDefinitionAddressesInVEC4[i] = new VEC4Address(transparentIndicesList.get(i)); IntBuffer trans = transparentObjectDefinitionAddressesInVec4.asIntBuffer(), opaque = opaqueObjectDefinitionAddressesInVec4.asIntBuffer(); for (Integer elm : transparentIndicesList) trans.put(elm); for (Integer elm : opaqueIndicesList) opaque.put(elm); return null; } }); }// end initializeObjectDefinitions() private void processPrimitiveList(PrimitiveList<?> primitiveList, int[] objectDefinitions, ArrayList<Integer> indicesList) { if (primitiveList == null) return; // Nothing to do, no primitives here //int vec4sRemaining = primitiveList.getTotalSizeInVec4s(); final int gpuVerticesPerElement = primitiveList.getGPUVerticesPerElement(); final int elementsPerBlock = GPU.GPU_VERTICES_PER_BLOCK / gpuVerticesPerElement; int gpuVerticesRemaining = primitiveList.getNumElements() * gpuVerticesPerElement; // For each of the allocated-but-not-yet-initialized object definitions. final ObjectDefinitionWindow odw = tr.objectDefinitionWindow.get(); int odCounter = 0; final int memoryWindowIndicesPerElement = primitiveList.getNumMemoryWindowIndicesPerElement(); for (final int index : objectDefinitions) { final int vertexOffsetVec4s = primitiveList.getMemoryWindow().getPhysicalAddressInBytes( odCounter * elementsPerBlock * memoryWindowIndicesPerElement) / GPU.BYTES_PER_VEC4; final int matrixOffsetVec4s = tr.matrixWindow.get().getPhysicalAddressInBytes(matrixID) / GPU.BYTES_PER_VEC4; odw.matrixOffset.set(index, matrixOffsetVec4s); odw.vertexOffset.set(index, vertexOffsetVec4s); odw.mode.set(index, (byte) (primitiveList.getPrimitiveRenderMode() | (renderFlags << 4) & 0xF0)); odw.modelScale.set(index, (byte) primitiveList.getPackedScale()); if (gpuVerticesRemaining >= GPU.GPU_VERTICES_PER_BLOCK) { odw.numVertices.set(index, (byte) GPU.GPU_VERTICES_PER_BLOCK); } else if (gpuVerticesRemaining > 0) { odw.numVertices.set(index, (byte) (gpuVerticesRemaining)); } else { throw new RuntimeException("Ran out of vec4s."); } gpuVerticesRemaining -= GPU.GPU_VERTICES_PER_BLOCK; indicesList.add(odw.getPhysicalAddressInBytes(index) / GPU.BYTES_PER_VEC4); odCounter++; } // end for(ObjectDefinition) }// end processPrimitiveList(...) public synchronized final void updateStateToGPU() { attemptLoop(); if (needToRecalcMatrix) { recalculateTransRotMBuffer(); needToRecalcMatrix = recalcMatrixWithEachFrame(); } if (model != null) model.proposeAnimationUpdate(); }//end updateStateToGPU() protected void attemptLoop() { if (LOOP) { final Vector3D camPos = tr.mainRenderer.get().getCamera().getCameraPosition(); double delta = position[0] - camPos.getX(); if (delta > TR.mapWidth / 2.) { position[0] -= TR.mapWidth; needToRecalcMatrix = true; } else if (delta < -TR.mapWidth / 2.) { position[0] += TR.mapWidth; needToRecalcMatrix = true; } delta = position[1] - camPos.getY(); if (delta > TR.mapWidth / 2.) { position[1] -= TR.mapWidth; needToRecalcMatrix = true; } else if (delta < -TR.mapWidth / 2.) { position[1] += TR.mapWidth; needToRecalcMatrix = true; } delta = position[2] - camPos.getZ(); if (delta > TR.mapWidth / 2.) { position[2] -= TR.mapWidth; needToRecalcMatrix = true; } else if (delta < -TR.mapWidth / 2.) { position[2] += TR.mapWidth; needToRecalcMatrix = true; } } //end if(LOOP) }//end attemptLoop() protected void recalculateTransRotMBuffer() { try { Vect3D.normalize(heading, aZ); Vect3D.cross(top, aZ, aX); Vect3D.cross(aZ, aX, aY); rMd[0] = aX[0]; rMd[1] = aY[0]; rMd[2] = aZ[0]; rMd[4] = aX[1]; rMd[5] = aY[1]; rMd[6] = aZ[1]; rMd[8] = aX[2]; rMd[9] = aY[2]; rMd[10] = aZ[2]; if (isVisible() && isActive()) { tMd[3] = position[0] + modelOffset[0]; tMd[7] = position[1] + modelOffset[1]; tMd[11] = position[2] + modelOffset[2]; } else { tMd[3] = Double.POSITIVE_INFINITY; tMd[7] = Double.POSITIVE_INFINITY; tMd[11] = Double.POSITIVE_INFINITY; } //end (!visible) if (translate()) { Mat4x4.mul(tMd, rMd, rotTransM); } else { System.arraycopy(rMd, 0, rotTransM, 0, 16); } tr.matrixWindow.get().setTransposed(rotTransM, matrixID, scratchMatrixArray);//New version } catch (MathArithmeticException e) { } // Don't crash. }// end recalculateTransRotMBuffer() protected final double[] scratchMatrixArray = new double[16]; protected boolean translate() { return true; } /** * @return the visible */ public boolean isVisible() { return visible; } /** * @param visible * the visible to set */ public void setVisible(boolean visible) { if (this.visible == visible) return; needToRecalcMatrix = true; if (!this.visible && visible) { this.visible = true; tr.mainRenderer.get().temporarilyMakeImmediatelyRelevant(this); } else this.visible = visible; tr.threadManager.submitToGPUMemAccess(new Callable<Void>() { @Override public Void call() throws Exception { WorldObject.this.updateStateToGPU(); return null; } }); }//end setvisible() /** * @return the position */ public final double[] getPosition() { return position; } /** * @param position * the position to set */ public WorldObject setPosition(double[] position) { this.position[0] = position[0]; this.position[1] = position[1]; this.position[2] = position[2]; notifyPositionChange(); return this; }// end setPosition() public synchronized WorldObject notifyPositionChange() { if (position[0] == Double.NaN) throw new RuntimeException("Invalid position."); needToRecalcMatrix = true; synchronized (position) { final SpacePartitioningGrid<PositionedRenderable> containingGrid = getContainingGrid(); if (containingGrid == null) {//Not in grid if (lastContainingList != null) {//Removed from grid synchronized (lastContainingList) { lastContainingList.remove(this); } lastContainingList = null; } //end if(lastContainingList!=null) return this; } //end if(not in grid) //Possibly moved from cube-to-cube final List<PositionedRenderable> newList = (this instanceof RelevantEverywhere) ? containingGrid.getAlwaysVisibleList() : containingGrid.world2List(position[0], position[1], position[2], true); if (lastContainingList != newList) {//Definitely moved from cube-to-cube if (lastContainingList != null) { synchronized (newList) { synchronized (lastContainingList) { lastContainingList.remove(this); newList.add(this); } } //end sync(new and last) } //end (moved) else synchronized (newList) { newList.add(this); } lastContainingList = newList; } //end if(posChange) } //end sync(position) return this; }//end notifyPositionChange() /** * @return the heading */ public final Vector3D getLookAt() { return new Vector3D(heading); } /** * @param heading * the heading to set */ public synchronized void setHeading(Vector3D nHeading) { heading[0] = nHeading.getX(); heading[1] = nHeading.getY(); heading[2] = nHeading.getZ(); needToRecalcMatrix = true; } public Vector3D getHeading() { assert !(top[0] == 0 && top[1] == 0 && top[2] == 0); return new Vector3D(heading); } /** * @return the top */ public final Vector3D getTop() { assert !(top[0] == 0 && top[1] == 0 && top[2] == 0); return new Vector3D(top); } /** * @param top * the top to set */ public synchronized void setTop(Vector3D nTop) { top[0] = nTop.getX(); top[1] = nTop.getY(); top[2] = nTop.getZ(); needToRecalcMatrix = true; } public final VEC4Address[] getOpaqueObjectDefinitionAddress() { return opaqueObjectDefinitionAddressesInVEC4; } public final VEC4Address[] getTransparentObjectDefinitionAddress() { return transparentObjectDefinitionAddressesInVEC4; } public final ByteBuffer getOpaqueObjectDefinitionAddresses() { opaqueObjectDefinitionAddressesInVec4.clear(); return opaqueObjectDefinitionAddressesInVec4; } public final ByteBuffer getTransparentObjectDefinitionAddresses() { transparentObjectDefinitionAddressesInVec4.clear(); return transparentObjectDefinitionAddressesInVec4; } /** * @return the tr */ public TR getTr() { return tr; } public synchronized void destroy() { if (containingGrid != null) { SpacePartitioningGrid g = getContainingGrid(); if (g != null) if (lastContainingList != null) synchronized (lastContainingList) { lastContainingList.remove(this); } } //end if(grid!=null) containingGrid = null; // Send it to the land of wind and ghosts. //final double[] pos = getPosition(); setActive(false); //pos[0] = Double.NEGATIVE_INFINITY; //pos[1] = Double.NEGATIVE_INFINITY; //pos[2] = Double.NEGATIVE_INFINITY; notifyPositionChange(); } @Override public void setContainingGrid(SpacePartitioningGrid grid) { containingGrid = new WeakReference<SpacePartitioningGrid>(grid); notifyPositionChange(); } public SpacePartitioningGrid<PositionedRenderable> getContainingGrid() { try { return containingGrid.get(); } catch (NullPointerException e) { return null; } } public Model getModel() { return model; } public Behavior getBehavior() { return nullBehavior; } /** * @return the active */ public boolean isActive() { return active; } /** * @param active * the active to set */ public void setActive(boolean active) { if (this.active != active) needToRecalcMatrix = true; if (!this.active && active && isVisible()) { this.active = true; tr.mainRenderer.get().temporarilyMakeImmediatelyRelevant(this); tr.threadManager.submitToGPUMemAccess(new Callable<Void>() { @Override public Void call() throws Exception { WorldObject.this.updateStateToGPU(); return null; } }); } this.active = active; }//end setActive(...) public synchronized void movePositionBy(Vector3D delta) { position[0] += delta.getX(); position[1] += delta.getY(); position[2] += delta.getZ(); notifyPositionChange(); } public synchronized void setPosition(double x, double y, double z) { position[0] = x; position[1] = y; position[2] = z; notifyPositionChange(); } public double[] getHeadingArray() { return heading; } public double[] getTopArray() { return top; } public void enableBehavior(Behavior behavior) { if (!inactiveBehaviors.contains(behavior)) { throw new RuntimeException("Tried to enabled an unregistered behavior."); } if (behavior instanceof CollisionBehavior) { if (!collisionBehaviors.contains(behavior) && behavior instanceof CollisionBehavior) { collisionBehaviors.add((CollisionBehavior) behavior); } } if (!tickBehaviors.contains(behavior)) { tickBehaviors.add(behavior); } }// end enableBehavior(...) public void disableBehavior(Behavior behavior) { if (!inactiveBehaviors.contains(behavior)) synchronized (inactiveBehaviors) { inactiveBehaviors.add(behavior); } if (behavior instanceof CollisionBehavior) synchronized (collisionBehaviors) { collisionBehaviors.remove(behavior); } synchronized (tickBehaviors) { tickBehaviors.remove(behavior); } }//end disableBehavior(...) /** * @return the renderFlags */ public int getRenderFlags() { return renderFlags; } /** * @param renderFlags the renderFlags to set */ public void setRenderFlags(byte renderFlags) { this.renderFlags = renderFlags; } /** * @return the respondToTick */ public boolean isRespondToTick() { return respondToTick; } /** * @param respondToTick the respondToTick to set */ public void setRespondToTick(boolean respondToTick) { this.respondToTick = respondToTick; } @Override public void finalize() throws Throwable { if (matrixID != null) tr.matrixWindow.get().free(matrixID); if (transparentTriangleObjectDefinitions != null) for (int def : transparentTriangleObjectDefinitions) tr.objectDefinitionWindow.get().free(def); if (triangleObjectDefinitions != null) for (int def : triangleObjectDefinitions) tr.objectDefinitionWindow.get().free(def); super.finalize(); }//end finalize() /** * @param modelOffset the modelOffset to set */ public void setModelOffset(double x, double y, double z) { modelOffset[0] = x; modelOffset[1] = y; modelOffset[2] = z; } public double[] getPositionWithOffset() { positionWithOffset[0] = position[0] + modelOffset[0]; positionWithOffset[1] = position[1] + modelOffset[1]; positionWithOffset[2] = position[2] + modelOffset[2]; return positionWithOffset; } public boolean isImmuneToOpaqueDepthTest() { return immuneToOpaqueDepthTest; } /** * @param immuneToDepthTest the immuneToDepthTest to set */ public WorldObject setImmuneToOpaqueDepthTest(boolean immuneToDepthTest) { this.immuneToOpaqueDepthTest = immuneToDepthTest; return this; } /*public void checkPositionSanity() { if(position[0]==Double.NaN||position[1]==Double.NaN||position[2]==Double.NaN) throw new RuntimeException("Invalid position"); }*/ }// end WorldObject