diff options
author | Kevin Rushforth <[email protected]> | 2006-09-29 18:04:59 +0000 |
---|---|---|
committer | Kevin Rushforth <[email protected]> | 2006-09-29 18:04:59 +0000 |
commit | 84db704dbd19cf4249879cb1e026568632712f30 (patch) | |
tree | 6a74aa076b3b7e114826110eacc5f71b4a411bf4 /src | |
parent | 5b994f050b817515e3425f0554bd93afee449373 (diff) |
Merged dev-1_5 branch back to MAIN trunk
git-svn-id: https://svn.java.net/svn/j3d-core-utils~svn/trunk@129 9497e636-51bd-65ba-982d-a4982e1767a5
Diffstat (limited to 'src')
51 files changed, 10767 insertions, 855 deletions
diff --git a/src/classes/ToolsVersion b/src/classes/ToolsVersion index d6e665f..35a52f3 100644 --- a/src/classes/ToolsVersion +++ b/src/classes/ToolsVersion @@ -1,6 +1,6 @@ Manifest-Version: 1.0 Specification-Title: -Specification-Version: 1.4 +Specification-Version: 1.5 Specification-Vendor: Sun Microsystems, Inc. Implementation-Title: Java 3D Utilities Runtime Environment Implementation-Version: @VERSION_BASE@ diff --git a/src/classes/share/com/sun/j3d/ExceptionStrings.properties b/src/classes/share/com/sun/j3d/ExceptionStrings.properties index bf031b7..363945c 100644 --- a/src/classes/share/com/sun/j3d/ExceptionStrings.properties +++ b/src/classes/share/com/sun/j3d/ExceptionStrings.properties @@ -24,6 +24,16 @@ KBSplinePathInterpolator1=KBSplinePathInterpolator: first key frame should have KBSplinePathInterpolator2=KBSplinePathInterpolator: last key frame should have knot value of 1.0 KBSplinePathInterpolator3=KBSplinePathInterpolator: Key Frame knot values not in sequence KBCubicSplineCurve0=KBCubicSplineCurve needs at least 4 key frames +CompressedGeometry0=CompressedGeometry: start+size exceeds geometry length +CompressedGeometry4=CompressedGeometry: target buffer is too small +CompressedGeometry7=CompressedGeometry: cannot directly access data in byReference mode +CompressedGeometry8=CompressedGeometry: must be in byReference mode to use this method +CompressedGeometry9=CompressedGeometry: NIO buffer support is not implemented +GeneralizedStrip0=GeneralizedStrip: strip ended incompletely +GeometryDecompressor0=GeometryDecompressor: start+length > data array size +GeometryDecompressor1=GeometryDecompressor: bad delta normal in compressed buffer +GeometryDecompressorShape3D0=GeometryDecompressorShape3D: bad triangle output type +GeometryDecompressorShape3D1=GeometryDecompressorShape3D: bad buffer data type GeometryInfo0=Illegal primitive. GeometryInfo1=Illegal use of deprecated setTextureCoordinateIndices(int[]) GeometryInfo2=Length of float array not a multiple of dimensionality of texcoords diff --git a/src/classes/share/com/sun/j3d/internal/UtilFreelistManager.java b/src/classes/share/com/sun/j3d/exp/swing/JCanvas3D.java index 05f7ea4..2bc3692 100644 --- a/src/classes/share/com/sun/j3d/internal/UtilFreelistManager.java +++ b/src/classes/share/com/sun/j3d/exp/swing/JCanvas3D.java @@ -42,57 +42,12 @@ * $State$ */ -package com.sun.j3d.internal; +package com.sun.j3d.exp.swing; - -public class UtilFreelistManager { - - private static final boolean DEBUG = false; - - // constants that represent the freelists managed by the Manager - public static final int VECTOR3D = 0; - public static final int POINT3D = 1; - public static final int PICKRESULT = 2; - public static final int MAXINT = 2; - - // what list we are going to shrink next - private static int currlist = 0; - - // the freelists managed by the manager - public static UtilMemoryFreelist vector3dFreelist = new UtilMemoryFreelist("javax.vecmath.Vector3d"); - public static UtilMemoryFreelist point3dFreelist = new UtilMemoryFreelist("javax.vecmath.Point3d"); - public static UtilMemoryFreelist pickResultFreelist = new UtilMemoryFreelist("com.sun.j3d.utils.picking.PickResult"); - - -// static MemoryFreeList[] freelist = new MemoryFreeList[MAXINT+1]; - -// static void createFreeLists() { -// freelist[VECTOR3D] = new MemoryFreeList("javax.vecmath.Vector3d"); -// freelist[POINT3D] = new MemoryFreeList("javax.vecmath.Point3d"); -// } - - -// // see if the current list can be shrunk -// static void manageLists() { -// // System.out.println("manageLists"); -// if (freelist[currlist] != null) { -// freelist[currlist].shrink(); -// } - -// currlist++; -// if (currlist > MAXINT) currlist = 0; -// } - -// // return the freelist specified by the list param -// static MemoryFreeList getFreeList(int list) { -// if (list < 0 || list > MAXINT) { -// if (DEBUG) System.out.println("illegal list"); -// return null; -// } -// else { -// return freelist[list]; -// } -// } - - +/** + * Placeholder for JCanvas3D class. + */ +public class JCanvas3D { + private JCanvas3D() { + } } diff --git a/src/classes/share/com/sun/j3d/exp/swing/impl/AutoOffScreenCanvas3D.java b/src/classes/share/com/sun/j3d/exp/swing/impl/AutoOffScreenCanvas3D.java new file mode 100644 index 0000000..9517506 --- /dev/null +++ b/src/classes/share/com/sun/j3d/exp/swing/impl/AutoOffScreenCanvas3D.java @@ -0,0 +1,58 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.exp.swing.impl; + +/** + * Tagging interface for Java 3D off-screen Canvas3D objects that are + * automatically rendered. This is used internally by the JCanvas3D + * implementation. + * <p> + * NOTE: this is an experimental interface, which is not intended for use + * by applications. + * + * @author pepe + */ +public interface AutoOffScreenCanvas3D { +} diff --git a/src/classes/share/com/sun/j3d/exp/swing/package.html b/src/classes/share/com/sun/j3d/exp/swing/package.html new file mode 100644 index 0000000..e1cff44 --- /dev/null +++ b/src/classes/share/com/sun/j3d/exp/swing/package.html @@ -0,0 +1,13 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> + <meta content="text/html; charset=ISO-8859-1" + http-equiv="content-type"> + <title>com.sun.j3d.exp.swing</title> +</head> +<body> +<p><i>EXPERIMENTAL</i>: Provides a lightweight JCanvas3D class. +Note that the API in this package is highly experimental and +subject to change at any time.</p> +</body> +</html> diff --git a/src/classes/share/com/sun/j3d/internal/UtilMemoryFreelist.java b/src/classes/share/com/sun/j3d/internal/UtilMemoryFreelist.java deleted file mode 100644 index 9e94287..0000000 --- a/src/classes/share/com/sun/j3d/internal/UtilMemoryFreelist.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * $RCSfile$ - * - * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistribution of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistribution 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 Sun Microsystems, Inc. or the names of - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * This software is provided "AS IS," without a warranty of any - * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND - * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY - * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL - * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF - * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS - * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR - * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, - * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND - * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR - * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGES. - * - * You acknowledge that this software is not designed, licensed or - * intended for use in the design, construction, operation or - * maintenance of any nuclear facility. - * - * $Revision$ - * $Date$ - * $State$ - */ - -package com.sun.j3d.internal; - -import java.util.*; - -// this class must be synchronized because different threads may try to access -// the freelists -public class UtilMemoryFreelist { // extends AbstractList { - - // never go smaller than the initial capacity - ArrayList elementData = null; - int size = 0; - int currBlockSize = 10; - Object[] currBlock = null; - int currBlockIndex = 0; - int spaceUsed = 0; - int numBlocks = 0; - int capacity = 0; - int minBlockSize = 0; - boolean justShrunk = false; - int initcap = 10; - - // the minimum size since the last shrink - int minSize = 0; - - Class c = null; - - public UtilMemoryFreelist(String className) { - this(className, 10); - } - - public UtilMemoryFreelist(String className, int initialCapacity) { - if (initialCapacity < 0) { - throw new IllegalArgumentException ("Illegal Capacity: " + - initialCapacity); - } - - try { - c = Class.forName(className); - } - catch (Exception e) { -// System.out.println(e); - } - - initcap = initialCapacity; - currBlockSize = initialCapacity; - minBlockSize = currBlockSize; - elementData = new ArrayList(); - // add the first block of memory to the arraylist - currBlock = new Object[currBlockSize]; - elementData.add(currBlock); - numBlocks++; - capacity += currBlockSize; - } - - public UtilMemoryFreelist(String className, Collection collection) { - try { - c = Class.forName(className); - } - catch (Exception e) { -// System.out.println(e); - } - size = collection.size(); - initcap = size; - currBlockSize = size; - minBlockSize = currBlockSize; - elementData = new ArrayList(); - currBlock = new Object[currBlockSize]; - collection.toArray(currBlock); - elementData.add(currBlock); - numBlocks++; - capacity += currBlockSize; - spaceUsed = size; - } - - public synchronized int size() { - return size; - } - - - public synchronized boolean add(Object o) { - if (justShrunk) { - // empty some space out in the current block instead of - // adding this message - if ((currBlockSize/2) < spaceUsed) { - size -= (spaceUsed - (currBlockSize/2)); - spaceUsed = (currBlockSize/2); - Arrays.fill(currBlock, spaceUsed, currBlockSize-1, null); - } - justShrunk = false; - return false; - } - else { - ensureCapacity(size+1); - - // check to see if the whole block is used and if so, reset the - // current block -// System.out.println("spaceUsed = " + spaceUsed + " currBlockSize = " + -// currBlockSize + " currBlockIndex = " + -// currBlockIndex + " currBlock = " + currBlock); - if ((currBlockIndex == -1) || (spaceUsed >= currBlockSize)) { - currBlockIndex++; - currBlock = (Object[])elementData.get(currBlockIndex); - currBlockSize = currBlock.length; - spaceUsed = 0; - } - int index = spaceUsed++; - currBlock[index] = o; - size++; - - return true; - } - } - - private synchronized Object removeLastElement() { -// System.out.println("removeLastElement: size = " + size); - int index = --spaceUsed; -// System.out.println("index = " + index); - Object elm = currBlock[index]; - currBlock[index] = null; - size--; - - // see if this block is empty now, and if it is set the previous - // block to the current block - if (spaceUsed == 0) { - currBlockIndex--; - if (currBlockIndex < 0) { - currBlock = null; - currBlockSize = 0; - } - else { - currBlock = (Object[])elementData.get(currBlockIndex); - currBlockSize = currBlock.length; - } - spaceUsed = currBlockSize; - } - - return elm; - } - - - public synchronized void shrink() { -// System.out.println("shrink size = " + size + " minSize = " + -// minSize); - if ((minSize > minBlockSize) && (numBlocks > 1)) { - justShrunk = true; - -// System.out.println("removing a block"); -// Runtime r = Runtime.getRuntime(); -// r.gc(); -// System.out.println("numBlocks = " + numBlocks + " size = " + size); -// System.out.println("free memory before shrink: " + r.freeMemory()); - - // remove the last block - Object[] block = (Object[])elementData.remove(numBlocks-1); - numBlocks--; - capacity -= block.length; - - // we only need to do this if the block removed was the current - // block. otherwise we just removed a null block. - if (numBlocks == currBlockIndex) { - size -= spaceUsed; - // set the current block to the last one - currBlockIndex = numBlocks-1; - currBlock = (Object[])elementData.get(currBlockIndex); - currBlockSize = currBlock.length; - - spaceUsed = currBlockSize; - - } - -// r.gc(); -// System.out.println("free memory after shrink: " + r.freeMemory()); -// System.out.println("numBlocks = " + numBlocks + " size = " + size); - } - else { - justShrunk = false; - } - minSize = size; - } - - public synchronized void ensureCapacity(int minCapacity) { -// System.out.println("ensureCapacity: size = " + size + " capacity: " + -// elementData.length); -// System.out.println("minCapacity = " + minCapacity + " capacity = " -// + capacity); - - if (minCapacity > capacity) { -// System.out.println("adding a block: numBlocks = " + numBlocks); - int lastBlockSize = - ((Object[])elementData.get(numBlocks-1)).length; - int prevBlockSize = 0; - if (numBlocks > 1) { - prevBlockSize = - ((Object[])elementData.get(numBlocks-2)).length; - } - currBlockSize = lastBlockSize + prevBlockSize; - currBlock = new Object[currBlockSize]; - elementData.add(currBlock); - numBlocks++; - currBlockIndex++; - capacity += currBlockSize; - // there is nothing used in this block yet - spaceUsed = 0; - } - } - - synchronized void rangeCheck(int index) { - if (index >= size || index < 0) { - throw new IndexOutOfBoundsException("Index: " + index + - ", Size: " + size); - } - } - - public synchronized void clear() { -// System.out.println("clear"); - elementData.clear(); - - // put an empty block in - currBlockSize = initcap; - minBlockSize = currBlockSize; - currBlock = new Object[currBlockSize]; - elementData.add(currBlock); - numBlocks = 1; - capacity = currBlockSize; - spaceUsed = 0; - size = 0; - currBlockIndex = 0; - justShrunk = false; - } - - public synchronized Object getObject() { - if (size > 0) { - return removeLastElement(); - } - else { - try { - return c.newInstance(); - } - catch (Exception e) { -// System.out.println("caught exception"); -// System.out.print(e); - return null; - } - } - } - -} - diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/picking/package.html b/src/classes/share/com/sun/j3d/utils/behaviors/picking/package.html index a0f813c..b5d0b77 100644 --- a/src/classes/share/com/sun/j3d/utils/behaviors/picking/package.html +++ b/src/classes/share/com/sun/j3d/utils/behaviors/picking/package.html @@ -6,7 +6,7 @@ <title>com.sun.j3d.utils.behaviors.picking</title> </head> <body> -<p><i><b>Deprecated</b>: this package is deprecated; use <code><a +<p><i><b>Deprecated</b>: Use <code><a href="../../pickfast/behaviors/package-summary.html">com.sun.j3d.utils.pickfast.behaviors</a></code> instead.</i></p> </body> diff --git a/src/classes/share/com/sun/j3d/utils/compression/CommandStream.java b/src/classes/share/com/sun/j3d/utils/compression/CommandStream.java index 15739a6..6be092e 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/CommandStream.java +++ b/src/classes/share/com/sun/j3d/utils/compression/CommandStream.java @@ -42,7 +42,7 @@ * $State$ */ -package com.sun.j3d.utils.compression ; +package com.sun.j3d.utils.compression; /** * This class is used to build the bit-level compression command stream which diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressedGeometryFile.java b/src/classes/share/com/sun/j3d/utils/compression/CompressedGeometryFile.java index ef42300..57bcad8 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/CompressedGeometryFile.java +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressedGeometryFile.java @@ -42,10 +42,13 @@ * $State$ */ -package com.sun.j3d.utils.compression ; +package com.sun.j3d.utils.compression; -import java.io.* ; -import javax.media.j3d.* ; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import javax.media.j3d.CompressedGeometry; +import javax.media.j3d.CompressedGeometryHeader; // // The compressed geometry file format supported by this class has a 32 @@ -65,6 +68,9 @@ import javax.media.j3d.* ; * files. These files usually end with the .cg extension and support * sequential as well as random access to multiple compressed geometry * objects. + * + * @deprecated As of Java 3D 1.5, replaced by + * com.sun.j3d.utils.geometry.compression.{@link com.sun.j3d.utils.geometry.compression.CompressedGeometryFile}. */ public class CompressedGeometryFile { private static final boolean print = false ; diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressionStream.java b/src/classes/share/com/sun/j3d/utils/compression/CompressionStream.java index 7757bed..c401103 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/CompressionStream.java +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressionStream.java @@ -42,16 +42,44 @@ * $State$ */ -package com.sun.j3d.utils.compression ; - -import java.util.* ; -import javax.vecmath.* ; -import javax.media.j3d.* ; -import com.sun.j3d.utils.geometry.* ; -import com.sun.j3d.internal.ByteBufferWrapper ; -import com.sun.j3d.internal.BufferWrapper ; -import com.sun.j3d.internal.FloatBufferWrapper ; -import com.sun.j3d.internal.DoubleBufferWrapper ; +package com.sun.j3d.utils.compression; + +import com.sun.j3d.internal.BufferWrapper; +import com.sun.j3d.internal.ByteBufferWrapper; +import com.sun.j3d.internal.DoubleBufferWrapper; +import com.sun.j3d.internal.FloatBufferWrapper; +import com.sun.j3d.utils.geometry.GeometryInfo; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import javax.media.j3d.Appearance; +import javax.media.j3d.CompressedGeometryHeader; +import javax.media.j3d.Geometry; +import javax.media.j3d.GeometryArray; +import javax.media.j3d.GeometryStripArray; +import javax.media.j3d.IndexedGeometryArray; +import javax.media.j3d.IndexedGeometryStripArray; +import javax.media.j3d.IndexedLineArray; +import javax.media.j3d.IndexedLineStripArray; +import javax.media.j3d.IndexedQuadArray; +import javax.media.j3d.IndexedTriangleArray; +import javax.media.j3d.IndexedTriangleFanArray; +import javax.media.j3d.IndexedTriangleStripArray; +import javax.media.j3d.J3DBuffer; +import javax.media.j3d.LineArray; +import javax.media.j3d.LineStripArray; +import javax.media.j3d.Material; +import javax.media.j3d.QuadArray; +import javax.media.j3d.Shape3D; +import javax.media.j3d.TriangleArray; +import javax.media.j3d.TriangleFanArray; +import javax.media.j3d.TriangleStripArray; +import javax.vecmath.Color3f; +import javax.vecmath.Color4f; +import javax.vecmath.Point3d; +import javax.vecmath.Point3f; +import javax.vecmath.Point3i; +import javax.vecmath.Vector3f; /** * This class is used as input to a geometry compressor. It collects elements @@ -60,6 +88,9 @@ import com.sun.j3d.internal.DoubleBufferWrapper ; * the compression process and used to build the compressed output buffer. * * @see GeometryCompressor + * + * @deprecated As of Java 3D 1.5, replaced by + * com.sun.j3d.utils.geometry.compression.{@link com.sun.j3d.utils.geometry.compression.CompressionStream}. */ public class CompressionStream { // diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamColor.java b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamColor.java index 63528b9..bc1527b 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamColor.java +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamColor.java @@ -42,8 +42,10 @@ * $State$ */ -package com.sun.j3d.utils.compression ; -import javax.vecmath.* ; +package com.sun.j3d.utils.compression; + +import javax.vecmath.Color3f; +import javax.vecmath.Color4f; /** * This class represents a color in a compression stream. It maintains both diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamElement.java b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamElement.java index 55d0c3d..240d35e 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamElement.java +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamElement.java @@ -42,7 +42,7 @@ * $State$ */ -package com.sun.j3d.utils.compression ; +package com.sun.j3d.utils.compression; /** * Instances of this class are used as elements in a CompressionStream. diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamNormal.java b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamNormal.java index 4cc9acc..642c7e0 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamNormal.java +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamNormal.java @@ -42,8 +42,9 @@ * $State$ */ -package com.sun.j3d.utils.compression ; -import javax.vecmath.* ; +package com.sun.j3d.utils.compression; + +import javax.vecmath.Vector3f; /** * This class represents a normal in a compression stream. It maintains both diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamVertex.java b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamVertex.java index 4a562e2..1fe7e7a 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamVertex.java +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamVertex.java @@ -42,8 +42,12 @@ * $State$ */ -package com.sun.j3d.utils.compression ; -import javax.vecmath.* ; +package com.sun.j3d.utils.compression; + +import javax.vecmath.Color3f; +import javax.vecmath.Color4f; +import javax.vecmath.Point3f; +import javax.vecmath.Vector3f; /** * This class represents a vertex in a compression stream. It maintains both diff --git a/src/classes/share/com/sun/j3d/utils/compression/GeometryCompressor.java b/src/classes/share/com/sun/j3d/utils/compression/GeometryCompressor.java index 43007d1..d4479a4 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/GeometryCompressor.java +++ b/src/classes/share/com/sun/j3d/utils/compression/GeometryCompressor.java @@ -42,12 +42,12 @@ * $State$ */ -package com.sun.j3d.utils.compression ; +package com.sun.j3d.utils.compression; -import javax.media.j3d.* ; -import javax.vecmath.* ; -import java.util.* ; -import java.io.* ; +import java.io.IOException; +import javax.media.j3d.CompressedGeometry; +import javax.media.j3d.CompressedGeometryHeader; +import javax.vecmath.Point3d; /** * A GeometryCompressor takes a stream of geometric elements and @@ -60,6 +60,9 @@ import java.io.* ; * @see CompressionStream * @see CompressedGeometry * @see CompressedGeometryFile + * + * @deprecated As of Java 3D 1.5, replaced by + * com.sun.j3d.utils.geometry.compression.{@link com.sun.j3d.utils.geometry.compression.GeometryCompressor}. */ public class GeometryCompressor { private static final boolean benchmark = false ; diff --git a/src/classes/share/com/sun/j3d/utils/compression/HuffmanNode.java b/src/classes/share/com/sun/j3d/utils/compression/HuffmanNode.java index e2b7d9c..d4b6279 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/HuffmanNode.java +++ b/src/classes/share/com/sun/j3d/utils/compression/HuffmanNode.java @@ -42,8 +42,10 @@ * $State$ */ -package com.sun.j3d.utils.compression ; -import java.util.* ; +package com.sun.j3d.utils.compression; + +import java.util.Collection; +import java.util.Comparator; /** * Instances of this class are used as the nodes of binary trees representing diff --git a/src/classes/share/com/sun/j3d/utils/compression/HuffmanTable.java b/src/classes/share/com/sun/j3d/utils/compression/HuffmanTable.java index 0f47135..3023eed 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/HuffmanTable.java +++ b/src/classes/share/com/sun/j3d/utils/compression/HuffmanTable.java @@ -42,8 +42,14 @@ * $State$ */ -package com.sun.j3d.utils.compression ; -import java.util.* ; +package com.sun.j3d.utils.compression; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.ListIterator; /** * This class maintains a map from compression stream elements (tokens) onto diff --git a/src/classes/share/com/sun/j3d/utils/compression/MeshBuffer.java b/src/classes/share/com/sun/j3d/utils/compression/MeshBuffer.java index b399042..cd8f873 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/MeshBuffer.java +++ b/src/classes/share/com/sun/j3d/utils/compression/MeshBuffer.java @@ -42,8 +42,12 @@ * $State$ */ -package com.sun.j3d.utils.compression ; -import javax.vecmath.* ; +package com.sun.j3d.utils.compression; + +import javax.vecmath.Color3f; +import javax.vecmath.Color4f; +import javax.vecmath.Point3f; +import javax.vecmath.Vector3f; /** * This class mirrors the vertex mesh buffer stack supported by the geometry diff --git a/src/classes/share/com/sun/j3d/utils/compression/package.html b/src/classes/share/com/sun/j3d/utils/compression/package.html index 538b9ea..628453b 100644 --- a/src/classes/share/com/sun/j3d/utils/compression/package.html +++ b/src/classes/share/com/sun/j3d/utils/compression/package.html @@ -6,6 +6,8 @@ <title>com.sun.j3d.utils.compression</title> </head> <body> -<p>Provides geometry compression utility classes.</p> +<p><i><b>Deprecated</b>: Use <code><a + href="../geometry/compression/package-summary.html">com.sun.j3d.utils.geometry.compression</a></code> +instead.</i></p> </body> </html> diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/CommandStream.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/CommandStream.java new file mode 100644 index 0000000..1f94a0d --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/CommandStream.java @@ -0,0 +1,265 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +/** + * This class is used to build the bit-level compression command stream which + * is the final result of the compression process. It defines the bit + * representations of the compression commands and provides a mechanism for + * the interleaving and forwarding of command headers and bodies required by + * the geometry compression specification. + */ +class CommandStream { + // Geometry compression commands. + static final int SET_NORM = 0xC0 ; + static final int SET_COLOR = 0x80 ; + static final int VERTEX = 0x40 ; + static final int MESH_B_R = 0x20 ; + static final int SET_STATE = 0x18 ; + static final int SET_TABLE = 0x10 ; + static final int V_NO_OP = 0x01 ; + + // Huffman table indices. + static final int POSITION_TABLE = 0 ; + static final int COLOR_TABLE = 1 ; + static final int NORMAL_TABLE = 2 ; + + // The buffer of compressed data and the current offset. + private byte bytes[] ; + private int byteOffset ; + private int bitOffset ; + + // Last command body for header forwarding. + private long lastBody ; + private int lastBodyLength ; + + /** + * Create an empty CommandStream with a default initial size. + */ + CommandStream() { + this(65536) ; + } + + /** + * Create an empty CommandStream with the given initial size. + * + * @param initSize initial capacity of CommandStream in bytes + */ + CommandStream(int initSize) { + bytes = new byte[initSize] ; + clear() ; + } + + /** + * Mark the CommandStream as empty so that its storage will be reused. + */ + void clear() { + // Initialize the first byte to 0. + // Subsequent bytes are cleared as they are written. + bytes[0] = 0 ; + + // Reset the number of valid bits. + bitOffset = 0 ; + byteOffset = 0 ; + + // The first command header is always followed by the body of an + // implicit variable length no-op to start the header-forwarding + // interleave required by hardware decompressor implementations. The + // only necessary bits are 5 bits of length set to zeros to indicate a + // fill of zero length. + lastBody = 0 ; + lastBodyLength = 5 ; + } + + /** + * Add a compression command to this instance.<p> + * + * A compression command includes an 8-bit header and can range up to 72 + * bits in length. The command with the maximum length is a 2-bit color + * command with a 6-bit tag in the header, followed by four 16-bit color + * components of data.<p> + * + * A subcommand is either a position, normal, or color, though in practice + * a position subcommand can only be part of a vertex command. Normal and + * color subcommands can be parts of separate global normal and color + * commands as well as parts of a vertex command.<p> + * + * A subcommand includes a 6-bit header. Its length is 2 bits less than + * the length of the corresponding command. + * + * @param header contains compression command header bits, right-justified + * within the bits of the int + * @param headerLength number of bits in header, either 8 for commands or + * 6 for subcommands + * @param body contains the body of the compression command, + * right-justified within the bits of the long + * @param bodyLength number of bits in the body + */ + void addCommand(int header, int headerLength, long body, int bodyLength) { + addByte(header, headerLength) ; + addLong(lastBody, lastBodyLength) ; + + lastBody = body ; + lastBodyLength = bodyLength ; + } + + // + // Add the rightmost bitCount bits of b to the end of the command stream. + // + private void addByte(int b, int bitCount) { + int bitsEmpty = 8 - bitOffset ; + b &= (int)CompressionStreamElement.lengthMask[bitCount] ; + + if (bitCount <= bitsEmpty) { + bytes[byteOffset] |= (b << (bitsEmpty - bitCount)) ; + bitOffset += bitCount ; + return ; + } + + if (bytes.length == byteOffset + 1) { + byte newBytes[] = new byte[bytes.length * 2] ; + System.arraycopy(bytes, 0, newBytes, 0, bytes.length) ; + bytes = newBytes ; + } + + bitOffset = bitCount - bitsEmpty ; + bytes[byteOffset] |= (b >>> bitOffset) ; + + byteOffset++ ; + bytes[byteOffset] = (byte)(b << (8 - bitOffset)) ; + } + + // + // Add the rightmost bitCount bits of l to the end of the command stream. + // + private void addLong(long l, int bitCount) { + int byteCount = bitCount / 8 ; + int excessBits = bitCount - byteCount * 8 ; + + if (excessBits > 0) + addByte((int)(l >>> (byteCount * 8)), excessBits) ; + + while (byteCount > 0) { + addByte((int)((l >>> ((byteCount - 1) * 8)) & 0xff), 8) ; + byteCount-- ; + } + } + + /** + * Add a no-op and the last command body. Pad out with additional no-ops + * to a 64-bit boundary if necessary. A call to this method is required + * in order to create a valid compression command stream. + */ + void end() { + int excessBytes, padBits ; + + // Add the 1st no-op and the last body. + addByte(V_NO_OP, 8) ; + addLong(lastBody, lastBodyLength) ; + + excessBytes = (byteOffset + 1) % 8 ; + if (excessBytes == 0 && bitOffset == 8) + // No padding necessary. + return ; + + // Need to add padding with a 2nd no-op. + addByte(V_NO_OP, 8) ; + excessBytes = (byteOffset + 1) % 8 ; + + if (excessBytes == 0) + padBits = 8 - bitOffset ; + else { + int fillBytes = 8 - excessBytes ; + padBits = (8 * fillBytes) + (8 - bitOffset) ; + } + + // The minimum length for a no-op command body is 5 bits. + if (padBits < 5) + // Have to cross the next 64-bit boundary. + padBits += 64 ; + + // The maximum length of a no-op body is a 5-bit length + 31 bits of + // fill for a total of 36. + if (padBits < 37) { + // Pad with the body of the 1st no-op. + addLong((padBits - 5) << (padBits - 5), padBits) ; + return ; + } + + // The number of bits to pad at this point is [37..68]. Knock off 24 + // bits with the body of the 1st no-op to reduce the number of pad + // bits to [13..44], which can be filled with 1 more no-op. + addLong(19 << 19, 24) ; + padBits -= 24 ; + + // Add a 3rd no-op. + addByte(V_NO_OP, 8) ; + padBits -= 8 ; + + // Complete padding with the body of the 2nd no-op. + addLong((padBits - 5) << (padBits - 5), padBits) ; + } + + /** + * Get the number of bytes in the compression command stream. + * + * @return size of compressed data in bytes + */ + int getByteCount() { + if (byteOffset + bitOffset == 0) + return 0 ; + else + return byteOffset + 1 ; + } + + /** + * Get the bytes composing the compression command stream. + * + * @return reference to array of bytes containing the compressed data + */ + byte[] getBytes() { + return bytes ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressedGeometryData.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressedGeometryData.java new file mode 100644 index 0000000..f8a8679 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressedGeometryData.java @@ -0,0 +1,546 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import com.sun.j3d.internal.J3dUtilsI18N; +import javax.media.j3d.J3DBuffer; +import javax.media.j3d.Shape3D; +import javax.vecmath.Point3d; + +/** + * The compressed geometry object is used to store geometry in a + * compressed format. Using compressed geometry may increase the speed + * objects can be sent over the network. Note that the geometry will + * be decompressed in memory, so the application will not see any + * memory savings. + * <p> + * Compressed geometry may be passed to this CompressedGeometryData object + * in one of two ways: by copying the data into this object using the + * existing constructor, or by passing a reference to the data. + * <p> + * <ul> + * <li> + * <b>By Copying:</b> + * In by-copy mode, the CompressedGeometryData constructor copies the buffer of + * compressed geometry data into this CompressedGeometryData object. This + * is appropriate for many applications, and allows Java 3D to verify + * the data once and then not worry about it again. + * </li> + * <li><b>By Reference:</b> + * In by-reference mode, the + * compressed geometry data is accessed by reference, directly from + * the user's array. To use this feature, you need to construct a + * CompressedGeometryData object with the <code>byReference</code> flag + * set to <code>true</code>. In this mode, a reference to the input + * data is saved, but the data itself is not necessarily copied. Note + * that the compressed geometry header is still copied into this + * compressed geometry object. Data referenced by a + * CompressedGeometryData object must not be modified after the + * CompressedGeometryData object is constructed. + * Applications + * must exercise care not to violate this rule. If any referenced + * compressed geometry data is modified after construction, + * the results are undefined. + * </li> + * </ul> + * + * @since Java 3D 1.5 + */ +public class CompressedGeometryData extends Object { + + private Header cgHeader; + private CompressedGeometryRetained retained; + + + /** + * Creates a new CompressedGeometryData object by copying + * the specified compressed geometry data into this object. + * If the version number of compressed geometry, as specified by + * the Header, is incompatible with the + * supported version of compressed geometry, then an exception + * will be thrown. + * + * @param hdr the compressed geometry header. This is copied + * into this CompressedGeometryData object. + * + * @param compressedGeometry the compressed geometry data. The + * geometry must conform to the format described in Appendix B of + * the <i>Java 3D API Specification</i>. + * + * @exception IllegalArgumentException if a problem is detected with the + * header. + */ + public CompressedGeometryData(Header hdr, + byte[] compressedGeometry) { + + this(hdr, compressedGeometry, false); + } + + /** + * Creates a new CompressedGeometryData object. The + * specified compressed geometry data is either copied into this + * object or is accessed by reference. + * If the version number of compressed geometry, as specified by + * the Header, is incompatible with the + * supported version of compressed geometry, then an exception + * will be thrown. + * + * @param hdr the compressed geometry header. This is copied + * into the CompressedGeometryData object. + * + * @param compressedGeometry the compressed geometry data. The + * geometry must conform to the format described in Appendix B of + * the <i>Java 3D API Specification</i>. + * + * @param byReference a flag that indicates whether the data is copied + * into this compressed geometry object or is accessed by reference. + * + * @exception IllegalArgumentException if a problem is detected with the + * header. + */ + public CompressedGeometryData(Header hdr, + byte[] compressedGeometry, + boolean byReference) { + + if ((hdr.size + hdr.start) > compressedGeometry.length) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("CompressedGeometry0")); + } + + // Create a separate copy of the given header. + cgHeader = new Header(); + hdr.copy(cgHeader); + + // Create the retained object. + retained = new CompressedGeometryRetained(); + this.retained.createCompressedGeometry(cgHeader, compressedGeometry, byReference); + + // This constructor is designed to accept byte arrays that may contain + // possibly many large compressed geometry blocks interspersed with + // non-J3D-specific metadata. Only one of these blocks is used per + // CompressedGeometry object, so set the geometry offset to zero in + // the header if the data itself is copied. + if (!byReference) + cgHeader.start = 0; + } + + /** + * Creates a new CompressedGeometryData object. The + * specified compressed geometry data is accessed by reference + * from the specified buffer. + * If the version number of compressed geometry, as specified by + * the Header, is incompatible with the + * supported version of compressed geometry, then an exception + * will be thrown. + * + * @param hdr the compressed geometry header. This is copied + * into the CompressedGeometryData object. + * + * @param compressedGeometry a buffer containing an NIO byte buffer + * of compressed geometry data. The + * geometry must conform to the format described in Appendix B of + * the <i>Java 3D API Specification</i>. + * + * @exception UnsupportedOperationException this method is not + * yet implemented + * + * @exception IllegalArgumentException if a problem is detected with the + * header, + * or if the java.nio.Buffer contained in the specified J3DBuffer + * is not a java.nio.ByteBuffer object. + * + * @see Header + */ + public CompressedGeometryData(Header hdr, + J3DBuffer compressedGeometry) { + + throw new UnsupportedOperationException("not implemented"); + } + + + /** + * Returns the size, in bytes, of the compressed geometry buffer. + * The size of the compressed geometry header is not included. + * + * @return the size, in bytes, of the compressed geometry buffer. + */ + public int getByteCount() { + return cgHeader.size; + } + + /** + * Copies the compressed geometry header from the CompressedGeometryData + * object into the passed in parameter. + * + * @param hdr the Header object into which to copy the + * CompressedGeometryData object's header; the offset field may differ + * from that which was originally specified if a copy of the original + * compressed geometry byte array was created. + */ + public void getCompressedGeometryHeader(Header hdr) { + cgHeader.copy(hdr); + } + + /** + * Retrieves the compressed geometry associated with the + * CompressedGeometryData object. Copies the compressed + * geometry from the CompressedGeometryData node into the given array. + * The array must be large enough to hold all of the bytes. + * The individual array elements must be allocated by the caller. + * + * @param compressedGeometry the array into which to copy the compressed + * geometry. + * + * @exception IllegalStateException if the data access mode for this + * object is by-reference. + * + * @exception ArrayIndexOutOfBoundsException if compressedGeometry byte + * array is not large enough to receive the compressed geometry + */ + public void getCompressedGeometry(byte[] compressedGeometry) { + if (isByReference()) { + throw new IllegalStateException( + J3dUtilsI18N.getString("CompressedGeometry7")); + } + + if (cgHeader.size > compressedGeometry.length) { + throw new ArrayIndexOutOfBoundsException( + J3dUtilsI18N.getString("CompressedGeometry4")); + } + + this.retained.copy(compressedGeometry); + } + + /** + * Decompresses the compressed geometry. Returns an array of Shape nodes + * containing the decompressed geometry objects, or null if the version + * number of the compressed geometry is incompatible with the decompressor + * in the current version of Java 3D. + * + * @return an array of Shape nodes containing the + * geometry decompressed from this CompressedGeometryData + * object, or null if its version is incompatible + */ + public Shape3D[] decompress() { + CompressedGeometryRetained cgr = this.retained; + + GeometryDecompressorShape3D decompressor = + new GeometryDecompressorShape3D(); + + // Decompress the geometry as TriangleStripArrays. A combination of + // TriangleStripArrays and TrianglesFanArrays is more compact but + // requires twice as many Shape3D objects, resulting in slower + // rendering performance. + // + // Using TriangleArray output is currently the fastest, given the + // strip sizes observed from various compressed geometry objects, but + // produces about twice as many vertices. TriangleStripArray produces + // the same number of Shape3D objects as TriangleArray using 1/2 + // to 2/3 of the vertices, with only a marginal performance penalty. + // + return decompressor.toTriangleStripArrays(cgr); + } + + + /** + * Retrieves the data access mode for this CompressedGeometryData object. + * + * @return <code>true</code> if the data access mode for this + * CompressedGeometryData object is by-reference; + * <code>false</code> if the data access mode is by-copying. + */ + public boolean isByReference() { + return this.retained.isByReference(); + } + + + /** + * Gets the compressed geometry data reference. + * + * @return the current compressed geometry data reference. + * + * @exception IllegalStateException if the data access mode for this + * object is not by-reference. + */ + public byte[] getCompressedGeometryRef() { + if (!isByReference()) { + throw new IllegalStateException( + J3dUtilsI18N.getString("CompressedGeometry8")); + } + + return this.retained.getReference(); + } + + + /** + * Gets the compressed geometry data buffer reference, which is + * always null since NIO buffers are not supported for + * CompressedGeometryData objects. + * + * @return null + */ + public J3DBuffer getCompressedGeometryBuffer() { + return null; + } + + + /** + * The Header class is a data container for the header information, + * used in conjunction with a CompressedGeometryData object. + * This information is used to aid the decompression of the compressed geometry. + * + * <p> + * All instance data is declared public and no get or set methods are + * provided. + * + * @since Java 3D 1.5 + */ + public static class Header extends Object { + + /** + * bufferType: compressed geometry is made up of individual points. + */ + public static final int POINT_BUFFER = 0; + + /** + * bufferType: compressed geometry is made up of line segments. + */ + public static final int LINE_BUFFER = 1; + + /** + * bufferType: compressed geometry is made up of triangles. + */ + public static final int TRIANGLE_BUFFER = 2; + + // Valid values for the bufferDataPresent field. + + /** + * bufferDataPresent: bit indicating that normal information is + * bundled with the vertices in the compressed geometry buffer. + */ + public static final int NORMAL_IN_BUFFER = 1; + + /** + * bufferDataPresent: bit indicating that RGB color information is + * bundled with the vertices in the compressed geometry buffer. + */ + public static final int COLOR_IN_BUFFER = 2; + + /** + * bufferDataPresent: bit indicating that alpha information is + * bundled with the vertices in the compressed geometry buffer. + */ + public static final int ALPHA_IN_BUFFER = 4; + + /** + * The major version number for the compressed geometry format that + * was used to compress the geometry. + * If the version number of compressed geometry is incompatible + * with the supported version of compressed geometry in the + * current version of Java 3D, the compressed geometry obejct will + * not be rendered. + * + * @see Canvas3D#queryProperties + */ + public int majorVersionNumber; + + /** + * The minor version number for the compressed geometry format that + * was used to compress the geometry. + * If the version number of compressed geometry is incompatible + * with the supported version of compressed geometry in the + * current version of Java 3D, the compressed geometry obejct will + * not be rendered. + * + * @see Canvas3D#queryProperties + */ + public int minorVersionNumber; + + /** + * The minor-minor version number for the compressed geometry format + * that was used to compress the geometry. + * If the version number of compressed geometry is incompatible + * with the supported version of compressed geometry in the + * current version of Java 3D, the compressed geometry obejct will + * not be rendered. + * + * @see Canvas3D#queryProperties + */ + public int minorMinorVersionNumber; + + /** + * Describes the type of data in the compressed geometry buffer. + * Only one type may be present in any given compressed geometry + * buffer. + */ + public int bufferType; + + /** + * Contains bits indicating what data is bundled with the vertices in the + * compressed geometry buffer. If this data is not present (e.g. color) + * then this info will be inherited from the Appearance node. + */ + public int bufferDataPresent; + + /** + * Size of the compressed geometry in bytes. + */ + public int size; + + /** + * Offset in bytes of the start of the compressed geometry from the + * beginning of the compressed geometry byte array passed to the + * CompressedGeometryData constructor. <p> + * + * If the CompressedGeometryData is created with reference access semantics, + * then this allow external compressors or file readers to embed several + * blocks of compressed geometry in a single large byte array, possibly + * interspersed with metadata that is not specific to Java 3D, without + * having to copy each block to a separate byte array. <p> + * + * If the CompressedGeometryData is created with copy access semantics, then + * <code>size</code> bytes of compressed geometry data are copied from the + * offset indicated by <code>start</code> instead of copying the entire + * byte array. The getCompressedGeometry() method will return only the + * bytes used to construct the object, and the getCompressedGeometryHeader() + * method will return a header with the <code>start</code> field set to 0. + */ + public int start; + + /** + * A point that defines the lower bound of the <i>x</i>, + * <i>y</i>, and <i>z</i> components for all positions in the + * compressed geometry buffer. If null, a lower bound of + * (-1,-1,-1) is assumed. Java 3D will use this information to + * construct a bounding box around compressed geometry objects + * that are used in nodes for which the auto compute bounds flag + * is true. The default value for this point is null. + */ + public Point3d lowerBound = null; + + /** + * A point that defines the upper bound of the <i>x</i>, + * <i>y</i>, and <i>z</i> components for all positions in the + * compressed geometry buffer. If null, an upper bound of (1,1,1) + * is assumed. Java 3D will use this information to construct a + * bounding box around compressed geometry objects that are used + * in nodes for which the auto compute bounds flag is true. The + * default value for this point is null. + */ + public Point3d upperBound = null; + + /** + * Creates a new Header object used for the + * creation of a CompressedGeometryData object. + * All instance data is declared public and no get or set methods are + * provided. All values are set to 0 by default and must be filled + * in by the application. + * + * @see CompressedGeometryData + */ + public Header() { + } + + /** + * Package-scoped method to copy current Header object + * to the passed-in Header object. + * + * @param hdr the Header object into which to copy the + * current Header. + */ + void copy(Header hdr) { + hdr.majorVersionNumber = this.majorVersionNumber; + hdr.minorVersionNumber = this.minorVersionNumber; + hdr.minorMinorVersionNumber = this.minorMinorVersionNumber; + hdr.bufferType = this.bufferType; + hdr.bufferDataPresent = this.bufferDataPresent; + hdr.size = this.size; + hdr.start = this.start; + hdr.lowerBound = this.lowerBound; + hdr.upperBound = this.upperBound; + } + + /** + * Returns a String describing the contents of the + * Header object. + * + * @return a String describing contents of the compressed geometry header + */ + public String toString() { + String type = "UNKNOWN"; + switch (bufferType) { + case POINT_BUFFER: type = "POINT_BUFFER"; break; + case LINE_BUFFER: type = "LINE_BUFFER"; break; + case TRIANGLE_BUFFER: type = "TRIANGLE_BUFFER"; break; + } + + String data = ""; + if ((bufferDataPresent & NORMAL_IN_BUFFER) != 0) + data = data + "NORMALS "; + if ((bufferDataPresent & COLOR_IN_BUFFER) != 0) + data = data + "COLORS "; + if ((bufferDataPresent & ALPHA_IN_BUFFER) != 0) + data = data + "ALPHA "; + + String lbound = "null"; + if (lowerBound != null) + lbound = lowerBound.toString(); + + String ubound = "null"; + if (upperBound != null) + ubound = upperBound.toString(); + + return + "majorVersionNumber: " + majorVersionNumber + " " + + "minorVersionNumber: " + minorVersionNumber + " " + + "minorMinorVersionNumber: " + minorMinorVersionNumber + "\n" + + "bufferType: " + type + " " + + "bufferDataPresent: " + data + "\n" + + "size: " + size + " " + + "start: " + start + "\n" + + "lower bound: " + lbound + "\n" + + "upper bound: " + ubound + " "; + } + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressedGeometryFile.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressedGeometryFile.java new file mode 100644 index 0000000..075f3cf --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressedGeometryFile.java @@ -0,0 +1,1011 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +// +// The compressed geometry file format supported by this class has a 32 +// byte header followed by multiple compressed geometry objects. +// +// Each object consists of a block of compressed data and an 8-byte +// individual block header describing its contents. +// +// The file ends with a directory data structure used for random access, +// containing a 64-bit offset for each object in the order in which it +// appears in the file. This is also used to find the size of the largest +// object in the file and must be present. +// + +/** + * This class provides methods to read and write compressed geometry resource + * files. These files usually end with the .cg extension and support + * sequential as well as random access to multiple compressed geometry + * objects. + * + * @since Java 3D 1.5 + */ +public class CompressedGeometryFile { + private static final boolean print = false ; + private static final boolean benchmark = false ; + + /** + * The magic number which identifies the compressed geometry file type. + */ + static final int MAGIC_NUMBER = 0xbaddfab4 ; + + /** + * Byte offset of the magic number from start of file. + */ + static final int MAGIC_NUMBER_OFFSET = 0 ; + + /** + * Byte offset of the major version number from start of file. + */ + static final int MAJOR_VERSION_OFFSET = 4 ; + + /** + * Byte offset of the minor version number from start of file. + */ + static final int MINOR_VERSION_OFFSET = 8 ; + + /** + * Byte offset of the minor minor version number from start of file. + */ + static final int MINOR_MINOR_VERSION_OFFSET = 12 ; + + /** + * Byte offset of the number of objects from start of file. + */ + static final int OBJECT_COUNT_OFFSET = 16 ; + + /** + * Byte offset of the directory offset from start of file. + * This offset is long word aligned since the directory offset is a long. + */ + static final int DIRECTORY_OFFSET_OFFSET = 24 ; + + /** + * File header total size in bytes. + */ + static final int HEADER_SIZE = 32 ; + + /** + * Byte offset of the object size from start of individual compressed + * geometry block. + */ + static final int OBJECT_SIZE_OFFSET = 0 ; + + /** + * Byte offset of the compressed geometry data descriptor from start of + * individual compressed geometry block. + */ + static final int GEOM_DATA_OFFSET = 4 ; + + /** + * Bits in compressed geometry data descriptor which encode the buffer type. + */ + static final int TYPE_MASK = 0x03 ; + + /** + * Bit in compressed geometry data descriptor encoding presence of normals. + */ + static final int NORMAL_PRESENT_MASK = 0x04 ; + + /** + * Bit in compressed geometry data descriptor encoding presence of colors. + */ + static final int COLOR_PRESENT_MASK = 0x08 ; + + /** + * Bit in compressed geometry data descriptor encoding presence of alphas. + */ + static final int ALPHA_PRESENT_MASK = 0x10 ; + + /** + * Value in compressed geometry data descriptor for a point buffer type. + */ + static final int TYPE_POINT = 1 ; + + /** + * Value in compressed geometry data descriptor for a line buffer type. + */ + static final int TYPE_LINE = 2 ; + + /** + * Value in compressed geometry data descriptor for a triangle buffer type. + */ + static final int TYPE_TRIANGLE = 3 ; + + /** + * Block header total size in bytes. + */ + static final int BLOCK_HEADER_SIZE = 8 ; + + // The name of the compressed geometry resource file. + String fileName = null ; + + // The major, minor, and subminor version number of the most recent + // compressor used to compress any of the objects in the compressed + // geometry resource file. + int majorVersionNumber ; + int minorVersionNumber ; + int minorMinorVersionNumber ; + + // The number of objects in the compressed geometry resource file. + int objectCount ; + + // The index of the current object in the file. + int objectIndex = 0 ; + + // The random access file associated with this instance. + RandomAccessFile cgFile = null ; + + // The magic number identifying the file type. + int magicNumber ; + + // These fields are set from each individual block of compressed geometry. + byte cgBuffer[] ; + int geomSize ; + int geomStart ; + int geomDataType ; + + // The directory of object offsets is read from the end of the file. + long directory[] ; + long directoryOffset ; + + // The object sizes are computed from the directory offsets. These are + // used to allocate a buffer large enough to hold the largest object and + // to determine how many consecutive objects can be read into that buffer. + int objectSizes[] ; + int bufferObjectStart ; + int bufferObjectCount ; + int bufferNextObjectCount ; + int bufferNextObjectOffset ; + + // The shared compressed geometry header object. + CompressedGeometryData.Header cgh ; + + // Flag indicating file update. + boolean fileUpdate = false ; + + /** + * Construct a new CompressedGeometryFile instance associated with the + * specified file. An attempt is made to open the file with read-only + * access; if this fails then a FileNotFoundException is thrown. + * + * @param file path to the compressed geometry resource file + * @exception FileNotFoundException if file doesn't exist or + * cannot be read + * @exception IllegalArgumentException if the file is not a compressed + * geometry resource file + * @exception IOException if there is a header or directory read error + */ + public CompressedGeometryFile(String file) throws IOException { + this(file, false) ; + } + + /** + * Construct a new CompressedGeometryFile instance associated with the + * specified file. + * + * @param file path to the compressed geometry resource file + * @param rw if true, opens the file for read and write access or attempts + * to create one if it doesn't exist; if false, opens the file with + * read-only access + * @exception FileNotFoundException if file doesn't exist or + * access permissions disallow access + * @exception IllegalArgumentException if the file is not a compressed + * geometry resource file + * @exception IOException if there is a header or directory read error + */ + public CompressedGeometryFile(String file, boolean rw) throws IOException { + // Open the file and read the file header. + open(file, rw) ; + + // Copy the file name. + fileName = new String(file) ; + + // Set up the file fields. + initialize() ; + } + + /** + * Construct a new CompressedGeometryFile instance associated with a + * currently open RandomAccessFile. + * + * @param file currently open RandomAccessFile + * @exception IllegalArgumentException if the file is not a compressed + * geometry resource file + * @exception IOException if there is a header or directory read error + */ + public CompressedGeometryFile(RandomAccessFile file) throws IOException { + // Copy the file reference. + cgFile = file ; + + // Set up the file fields. + initialize() ; + } + + /** + * Delete all compressed objects from this instance. This method may only + * be called after successfully creating a CompressedGeometryFile instance + * with read-write access, so a corrupted or otherwise invalid resource + * must be removed manually before it can be rewritten. The close() + * method must be called sometime after invoking clear() in order to write + * out the new directory structure. + * + * @exception IOException if clear fails + */ + public void clear() throws IOException { + // Truncate the file. + cgFile.setLength(0) ; + + // Set up the file fields. + initialize() ; + } + + /** + * Return a string containing the file name associated with this instance + * or null if there is none. + * + * @return file name associated with this instance or null if there is + * none + */ + public String getFileName() { + return fileName ; + } + + /** + * Return the major version number of the most recent compressor used to + * compress any of the objects in this instance. + * + * @return major version number + */ + public int getMajorVersionNumber() { + return majorVersionNumber ; + } + + /** + * Return the minor version number of the most recent compressor used to + * compress any of the objects in this instance. + * + * @return minor version number + */ + public int getMinorVersionNumber() { + return minorVersionNumber ; + } + + /** + * Return the subminor version number of the most recent compressor used to + * compress any of the objects in this instance. + * + * @return subminor version number + */ + public int getMinorMinorVersionNumber() { + return minorMinorVersionNumber ; + } + + /** + * Return the number of compressed objects in this instance. + * + * @return number of compressed objects + */ + public int getObjectCount() { + return objectCount ; + } + + /** + * Return the current object index associated with this instance. This is + * the index of the object that would be returned by an immediately + * following call to the readNext() method. Its initial value is 0; -1 + * is returned if the last object has been read. + * + * @return current object index, or -1 if at end + */ + public int getCurrentIndex() { + if (objectIndex == objectCount) + return -1 ; + else + return objectIndex ; + } + + /** + * Read the next compressed geometry object in the instance. This is + * initially the first object (index 0) in the instance; otherwise, it is + * whatever object is next after the last one read. The current object + * index is incremented by 1 after the read. When the last object is read + * the index becomes invalid and an immediately subsequent call to + * readNext() returns null. + * + * + * @return a CompressedGeometryData node component, or null if the last object + * has been read + * @exception IOException if read fails + */ + public CompressedGeometryData readNext() throws IOException { + return readNext(cgBuffer.length) ; + } + + /** + * Read all compressed geometry objects contained in the instance. The + * current object index becomes invalid; an immediately following call + * to readNext() will return null. + * + * @return an array of CompressedGeometryData node components. + * @exception IOException if read fails + */ + public CompressedGeometryData[] read() throws IOException { + long startTime = 0 ; + CompressedGeometryData cg[] = new CompressedGeometryData[objectCount] ; + + if (benchmark) + startTime = System.currentTimeMillis() ; + + objectIndex = 0 ; + setFilePointer(directory[0]) ; + bufferNextObjectCount = 0 ; + + for (int i = 0 ; i < objectCount ; i++) + cg[i] = readNext(cgBuffer.length) ; + + if (benchmark) { + long t = System.currentTimeMillis() - startTime ; + System.out.println("read " + objectCount + + " objects " + cgFile.length() + + " bytes in " + (t/1000f) + " sec.") ; + System.out.println((cgFile.length()/(float)t) + " Kbytes/sec.") ; + } + + return cg ; + } + + /** + * Read the compressed geometry object at the specified index. The + * current object index is set to the subsequent object unless the last + * object has been read, in which case the index becomes invalid and an + * immediately following call to readNext() will return null. + * + * @param index compressed geometry object to read + * @return a CompressedGeometryData node component + * @exception IndexOutOfBoundsException if object index is + * out of range + * @exception IOException if read fails + */ + public CompressedGeometryData read(int index) throws IOException { + objectIndex = index ; + + if (objectIndex < 0) { + throw new IndexOutOfBoundsException + ("\nobject index must be >= 0") ; + } + if (objectIndex >= objectCount) { + throw new IndexOutOfBoundsException + ("\nobject index must be < " + objectCount) ; + } + + // Check if object is in cache. + if ((objectIndex >= bufferObjectStart) && + (objectIndex < bufferObjectStart + bufferObjectCount)) { + if (print) System.out.println("\ngetting object from cache\n") ; + + bufferNextObjectOffset = (int) + (directory[objectIndex] - directory[bufferObjectStart]) ; + + bufferNextObjectCount = + bufferObjectCount - (objectIndex - bufferObjectStart) ; + + return readNext() ; + + } else { + // Move file pointer to correct offset. + setFilePointer(directory[objectIndex]) ; + + // Force a read from current offset. Disable cache read-ahead + // since cache hits are unlikely with random access. + bufferNextObjectCount = 0 ; + return readNext(objectSizes[objectIndex]) ; + } + } + + + /** + * Add a compressed geometry node component to the end of the instance. + * The current object index becomes invalid; an immediately following call + * to readNext() will return null. The close() method must be called at + * some later time in order to create a valid compressed geometry file. + * + * @param cg a compressed geometry node component + * @exception CapabilityNotSetException if unable to get compressed + * geometry data from the node component + * @exception IOException if write fails + */ + public void write(CompressedGeometryData cg) throws IOException { + CompressedGeometryData.Header cgh = new CompressedGeometryData.Header() ; + cg.getCompressedGeometryHeader(cgh) ; + + // Update the read/write buffer size if necessary. + if (cgh.size + BLOCK_HEADER_SIZE > cgBuffer.length) { + cgBuffer = new byte[cgh.size + BLOCK_HEADER_SIZE] ; + if (print) System.out.println("\ncgBuffer: reallocated " + + (cgh.size+BLOCK_HEADER_SIZE) + + " bytes") ; + } + + cg.getCompressedGeometry(cgBuffer) ; + write(cgh, cgBuffer) ; + } + + /** + * Add a buffer of compressed geometry data to the end of the + * resource. The current object index becomes invalid; an immediately + * following call to readNext() will return null. The close() method must + * be called at some later time in order to create a valid compressed + * geometry file. + * + * @param cgh a CompressedGeometryData.Header object describing the data. + * @param geometry the compressed geometry data + * @exception IOException if write fails + */ + public void write(CompressedGeometryData.Header cgh, byte geometry[]) + throws IOException { + + // Update the read/write buffer size if necessary. It won't be used + // in this method, but should be big enough to read any object in + // the file, including the one to be written. + if (cgh.size + BLOCK_HEADER_SIZE > cgBuffer.length) { + cgBuffer = new byte[cgh.size + BLOCK_HEADER_SIZE] ; + if (print) System.out.println("\ncgBuffer: reallocated " + + (cgh.size+BLOCK_HEADER_SIZE) + + " bytes") ; + } + + // Assuming backward compatibility, the version number of the file + // should be the maximum of all individual compressed object versions. + if ((cgh.majorVersionNumber > majorVersionNumber) + || + ((cgh.majorVersionNumber == majorVersionNumber) && + (cgh.minorVersionNumber > minorVersionNumber)) + || + ((cgh.majorVersionNumber == majorVersionNumber) && + (cgh.minorVersionNumber == minorVersionNumber) && + (cgh.minorMinorVersionNumber > minorMinorVersionNumber))) { + + majorVersionNumber = cgh.majorVersionNumber ; + minorVersionNumber = cgh.minorVersionNumber ; + minorMinorVersionNumber = cgh.minorMinorVersionNumber ; + + this.cgh.majorVersionNumber = cgh.majorVersionNumber ; + this.cgh.minorVersionNumber = cgh.minorVersionNumber ; + this.cgh.minorMinorVersionNumber = cgh.minorMinorVersionNumber ; + } + + // Get the buffer type and see what vertex components are present. + int geomDataType = 0 ; + + switch (cgh.bufferType) { + case CompressedGeometryData.Header.POINT_BUFFER: + geomDataType = TYPE_POINT ; + break ; + case CompressedGeometryData.Header.LINE_BUFFER: + geomDataType = TYPE_LINE ; + break ; + case CompressedGeometryData.Header.TRIANGLE_BUFFER: + geomDataType = TYPE_TRIANGLE ; + break ; + } + + if ((cgh.bufferDataPresent & + CompressedGeometryData.Header.NORMAL_IN_BUFFER) != 0) + geomDataType |= NORMAL_PRESENT_MASK ; + + if ((cgh.bufferDataPresent & + CompressedGeometryData.Header.COLOR_IN_BUFFER) != 0) + geomDataType |= COLOR_PRESENT_MASK ; + + if ((cgh.bufferDataPresent & + CompressedGeometryData.Header.ALPHA_IN_BUFFER) != 0) + geomDataType |= ALPHA_PRESENT_MASK ; + + // Allocate new directory and object size arrays if necessary. + if (objectCount == directory.length) { + long newDirectory[] = new long[2*objectCount] ; + int newObjectSizes[] = new int[2*objectCount] ; + + System.arraycopy(directory, 0, + newDirectory, 0, objectCount) ; + System.arraycopy(objectSizes, 0, + newObjectSizes, 0, objectCount) ; + + directory = newDirectory ; + objectSizes = newObjectSizes ; + + if (print) + System.out.println("\ndirectory and size arrays: reallocated " + + (2*objectCount) + " entries") ; + } + + // Update directory and object size array. + directory[objectCount] = directoryOffset ; + objectSizes[objectCount] = cgh.size + BLOCK_HEADER_SIZE ; + objectCount++ ; + + // Seek to the directory and overwrite from there. + setFilePointer(directoryOffset) ; + cgFile.writeInt(cgh.size) ; + cgFile.writeInt(geomDataType) ; + cgFile.write(geometry, 0, cgh.size) ; + if (print) + System.out.println("\nwrote " + cgh.size + + " byte compressed object to " + fileName + + "\nfile offset " + directoryOffset) ; + + // Update the directory offset. + directoryOffset += cgh.size + BLOCK_HEADER_SIZE ; + + // Return end-of-file on next read. + objectIndex = objectCount ; + + // Flag file update so close() will write out the directory. + fileUpdate = true ; + } + + /** + * Release the resources associated with this instance. + * Write out final header and directory if contents were updated. + * This method must be called in order to create a valid compressed + * geometry resource file if any updates were made. + */ + public void close() { + if (cgFile != null) { + try { + if (fileUpdate) { + writeFileDirectory() ; + writeFileHeader() ; + } + cgFile.close() ; + } + catch (IOException e) { + // Don't propagate this exception. + System.out.println("\nException: " + e.getMessage()) ; + System.out.println("failed to close " + fileName) ; + } + } + cgFile = null ; + cgBuffer = null ; + directory = null ; + objectSizes = null ; + } + + + // + // Open the file. Specifying a non-existent file creates a new one if + // access permissions allow. + // + void open(String fname, boolean rw) + throws FileNotFoundException, IOException { + + cgFile = null ; + String mode ; + + if (rw) + mode = "rw" ; + else + mode = "r" ; + + try { + cgFile = new RandomAccessFile(fname, mode) ; + if (print) System.out.println("\n" + fname + + ": opened mode " + mode) ; + } + catch (FileNotFoundException e) { + // N.B. this exception is also thrown on access permission errors + throw new FileNotFoundException(e.getMessage() + "\n" + fname + + ": open mode " + mode + " failed") ; + } + } + + // + // Seek to the specified offset in the file. + // + void setFilePointer(long offset) throws IOException { + cgFile.seek(offset) ; + + // Reset number of objects that can be read sequentially from cache. + bufferNextObjectCount = 0 ; + } + + // + // Initialize directory, object size array, read/write buffer, and the + // shared compressed geometry header. + // + void initialize() throws IOException { + int maxSize = 0 ; + + if (cgFile.length() == 0) { + // New file for writing: allocate nominal initial sizes for arrays. + objectCount = 0 ; + cgBuffer = new byte[32768] ; + directory = new long[16] ; + objectSizes = new int[directory.length] ; + + // Set fields as if they have been read. + magicNumber = MAGIC_NUMBER ; + majorVersionNumber = 1 ; + minorVersionNumber = 0 ; + minorMinorVersionNumber = 0 ; + directoryOffset = HEADER_SIZE ; + + // Write the file header. + writeFileHeader() ; + + } else { + // Read the file header. + readFileHeader() ; + + // Check file type. + if (magicNumber != MAGIC_NUMBER) { + close() ; + throw new IllegalArgumentException + ("\n" + fileName + " is not a compressed geometry file") ; + } + + // Read the directory and determine object sizes. + directory = new long[objectCount] ; + readDirectory(directoryOffset, directory) ; + + objectSizes = new int[objectCount] ; + for (int i = 0 ; i < objectCount-1 ; i++) { + objectSizes[i] = (int)(directory[i+1] - directory[i]) ; + if (objectSizes[i] > maxSize) maxSize = objectSizes[i] ; + } + + if (objectCount > 0) { + objectSizes[objectCount-1] = + (int)(directoryOffset - directory[objectCount-1]) ; + + if (objectSizes[objectCount-1] > maxSize) + maxSize = objectSizes[objectCount-1] ; + } + + // Allocate a buffer big enough to read the largest object. + cgBuffer = new byte[maxSize] ; + + // Move to the first object. + setFilePointer(HEADER_SIZE) ; + } + + // Set up common parts of the compressed geometry object header. + cgh = new CompressedGeometryData.Header() ; + cgh.majorVersionNumber = this.majorVersionNumber ; + cgh.minorVersionNumber = this.minorVersionNumber ; + cgh.minorMinorVersionNumber = this.minorMinorVersionNumber ; + + if (print) { + System.out.println(fileName + ": " + objectCount + " objects") ; + System.out.println("magic number 0x" + + Integer.toHexString(magicNumber) + + ", version number " + majorVersionNumber + + "." + minorVersionNumber + + "." + minorMinorVersionNumber) ; + System.out.println("largest object is " + maxSize + " bytes") ; + } + } + + // + // Read the file header. + // + void readFileHeader() throws IOException { + byte header[] = new byte[HEADER_SIZE] ; + + try { + setFilePointer(0) ; + if (cgFile.read(header) != HEADER_SIZE) { + close() ; + throw new IOException("failed header read") ; + } + } + catch (IOException e) { + if (cgFile != null) { + close() ; + } + throw e ; + } + + magicNumber = + ((header[MAGIC_NUMBER_OFFSET+0] & 0xff) << 24) | + ((header[MAGIC_NUMBER_OFFSET+1] & 0xff) << 16) | + ((header[MAGIC_NUMBER_OFFSET+2] & 0xff) << 8) | + ((header[MAGIC_NUMBER_OFFSET+3] & 0xff)) ; + + majorVersionNumber = + ((header[MAJOR_VERSION_OFFSET+0] & 0xff) << 24) | + ((header[MAJOR_VERSION_OFFSET+1] & 0xff) << 16) | + ((header[MAJOR_VERSION_OFFSET+2] & 0xff) << 8) | + ((header[MAJOR_VERSION_OFFSET+3] & 0xff)) ; + + minorVersionNumber = + ((header[MINOR_VERSION_OFFSET+0] & 0xff) << 24) | + ((header[MINOR_VERSION_OFFSET+1] & 0xff) << 16) | + ((header[MINOR_VERSION_OFFSET+2] & 0xff) << 8) | + ((header[MINOR_VERSION_OFFSET+3] & 0xff)) ; + + minorMinorVersionNumber = + ((header[MINOR_MINOR_VERSION_OFFSET+0] & 0xff) << 24) | + ((header[MINOR_MINOR_VERSION_OFFSET+1] & 0xff) << 16) | + ((header[MINOR_MINOR_VERSION_OFFSET+2] & 0xff) << 8) | + ((header[MINOR_MINOR_VERSION_OFFSET+3] & 0xff)) ; + + objectCount = + ((header[OBJECT_COUNT_OFFSET+0] & 0xff) << 24) | + ((header[OBJECT_COUNT_OFFSET+1] & 0xff) << 16) | + ((header[OBJECT_COUNT_OFFSET+2] & 0xff) << 8) | + ((header[OBJECT_COUNT_OFFSET+3] & 0xff)) ; + + directoryOffset = + ((long)(header[DIRECTORY_OFFSET_OFFSET+0] & 0xff) << 56) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+1] & 0xff) << 48) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+2] & 0xff) << 40) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+3] & 0xff) << 32) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+4] & 0xff) << 24) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+5] & 0xff) << 16) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+6] & 0xff) << 8) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+7] & 0xff)) ; + } + + // + // Write the file header based on current field values. + // + void writeFileHeader() throws IOException { + setFilePointer(0) ; + try { + cgFile.writeInt(MAGIC_NUMBER) ; + cgFile.writeInt(majorVersionNumber) ; + cgFile.writeInt(minorVersionNumber) ; + cgFile.writeInt(minorMinorVersionNumber) ; + cgFile.writeInt(objectCount) ; + cgFile.writeInt(0) ; // long word alignment + cgFile.writeLong(directoryOffset) ; + if (print) + System.out.println("wrote file header for " + fileName) ; + } + catch (IOException e) { + throw new IOException + (e.getMessage() + + "\ncould not write file header for " + fileName) ; + } + } + + // + // Read the directory of compressed geometry object offsets. + // + void readDirectory(long offset, long[] directory) + throws IOException { + + byte buff[] = new byte[directory.length * 8] ; + setFilePointer(offset) ; + + try { + cgFile.read(buff) ; + if (print) + System.out.println("read " + buff.length + " byte directory") ; + } + catch (IOException e) { + throw new IOException + (e.getMessage() + + "\nfailed to read " + buff.length + + " byte directory, offset " + offset + " in file " + fileName) ; + } + + for (int i = 0 ; i < directory.length ; i++) { + directory[i] = + ((long)(buff[i*8+0] & 0xff) << 56) | + ((long)(buff[i*8+1] & 0xff) << 48) | + ((long)(buff[i*8+2] & 0xff) << 40) | + ((long)(buff[i*8+3] & 0xff) << 32) | + ((long)(buff[i*8+4] & 0xff) << 24) | + ((long)(buff[i*8+5] & 0xff) << 16) | + ((long)(buff[i*8+6] & 0xff) << 8) | + ((long)(buff[i*8+7] & 0xff)) ; + } + } + + // + // Write the file directory. + // + void writeFileDirectory() throws IOException { + setFilePointer(directoryOffset) ; + + int directoryAlign = (int)(directoryOffset % 8) ; + if (directoryAlign != 0) { + // Align to long word before writing directory of long offsets. + byte bytes[] = new byte[8-directoryAlign] ; + + try { + cgFile.write(bytes) ; + if (print) + System.out.println ("wrote " + (8-directoryAlign) + + " bytes long alignment") ; + } + catch (IOException e) { + throw new IOException + (e.getMessage() + + "\ncould not write " + directoryAlign + + " bytes to long word align directory for " + fileName) ; + } + directoryOffset += 8-directoryAlign ; + } + + try { + for (int i = 0 ; i < objectCount ; i++) + cgFile.writeLong(directory[i]) ; + + if (print) + System.out.println("wrote file directory for " + fileName) ; + } + catch (IOException e) { + throw new IOException + (e.getMessage() + + "\ncould not write directory for " + fileName) ; + } + } + + // + // Get the next compressed object in the file, either from the read-ahead + // cache or from the file itself. + // + CompressedGeometryData readNext(int bufferReadLimit) + throws IOException { + if (objectIndex == objectCount) + return null ; + + if (bufferNextObjectCount == 0) { + // No valid objects are in the cache. + int curSize = 0 ; + bufferObjectCount = 0 ; + + // See how much we have room to read. + for (int i = objectIndex ; i < objectCount ; i++) { + if (curSize + objectSizes[i] > bufferReadLimit) break ; + curSize += objectSizes[i] ; + bufferObjectCount++ ; + } + + // Try to read that amount. + try { + int n = cgFile.read(cgBuffer, 0, curSize) ; + if (print) + System.out.println("\nread " + n + + " bytes from " + fileName) ; + } + catch (IOException e) { + throw new IOException + (e.getMessage() + + "\nfailed to read " + curSize + + " bytes, object " + objectIndex + " in file " + fileName) ; + } + + // Point at the first object in the buffer. + bufferObjectStart = objectIndex ; + bufferNextObjectCount = bufferObjectCount ; + bufferNextObjectOffset = 0 ; + } + + // Get block header info. + geomSize = + ((cgBuffer[bufferNextObjectOffset+OBJECT_SIZE_OFFSET+0]&0xff)<<24) | + ((cgBuffer[bufferNextObjectOffset+OBJECT_SIZE_OFFSET+1]&0xff)<<16) | + ((cgBuffer[bufferNextObjectOffset+OBJECT_SIZE_OFFSET+2]&0xff)<< 8) | + ((cgBuffer[bufferNextObjectOffset+OBJECT_SIZE_OFFSET+3]&0xff)) ; + + geomDataType = + ((cgBuffer[bufferNextObjectOffset+GEOM_DATA_OFFSET+0]&0xff) << 24) | + ((cgBuffer[bufferNextObjectOffset+GEOM_DATA_OFFSET+1]&0xff) << 16) | + ((cgBuffer[bufferNextObjectOffset+GEOM_DATA_OFFSET+2]&0xff) << 8) | + ((cgBuffer[bufferNextObjectOffset+GEOM_DATA_OFFSET+3]&0xff)) ; + + // Get offset of compressed geometry data from start of buffer. + geomStart = bufferNextObjectOffset + BLOCK_HEADER_SIZE ; + + if (print) { + System.out.println("\nobject " + objectIndex + + "\nfile offset " + directory[objectIndex] + + ", buffer offset " + bufferNextObjectOffset) ; + System.out.println("size " + geomSize + " bytes, " + + "data descriptor 0x" + + Integer.toHexString(geomDataType)) ; + } + + // Update cache info. + bufferNextObjectOffset += objectSizes[objectIndex] ; + bufferNextObjectCount-- ; + objectIndex++ ; + + return newCG(geomSize, geomStart, geomDataType) ; + } + + + // + // Construct and return a compressed geometry node. + // + CompressedGeometryData newCG(int geomSize, + int geomStart, + int geomDataType) { + cgh.size = geomSize ; + cgh.start = geomStart ; + + if ((geomDataType & TYPE_MASK) == TYPE_POINT) + cgh.bufferType = CompressedGeometryData.Header.POINT_BUFFER ; + else if ((geomDataType & TYPE_MASK) == TYPE_LINE) + cgh.bufferType = CompressedGeometryData.Header.LINE_BUFFER ; + else if ((geomDataType & TYPE_MASK) == TYPE_TRIANGLE) + cgh.bufferType = CompressedGeometryData.Header.TRIANGLE_BUFFER ; + + cgh.bufferDataPresent = 0 ; + + if ((geomDataType & NORMAL_PRESENT_MASK) != 0) + cgh.bufferDataPresent |= + CompressedGeometryData.Header.NORMAL_IN_BUFFER ; + + if ((geomDataType & COLOR_PRESENT_MASK) != 0) + cgh.bufferDataPresent |= + CompressedGeometryData.Header.COLOR_IN_BUFFER ; + + if ((geomDataType & ALPHA_PRESENT_MASK) != 0) + cgh.bufferDataPresent |= + CompressedGeometryData.Header.ALPHA_IN_BUFFER ; + + return new CompressedGeometryData(cgh, cgBuffer) ; + } + + /** + * Release file resources when this object is garbage collected. + */ + protected void finalize() { + close() ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressedGeometryRetained.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressedGeometryRetained.java new file mode 100644 index 0000000..4af6f50 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressedGeometryRetained.java @@ -0,0 +1,282 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import javax.media.j3d.BoundingBox; +import javax.media.j3d.Bounds; +import javax.media.j3d.Canvas3D; +import javax.media.j3d.GeometryArray; +import javax.media.j3d.PickInfo; +import javax.media.j3d.PickShape; +import javax.media.j3d.Transform3D; +import javax.vecmath.Point3d; + +/** + * The compressed geometry object is used to store geometry in a + * compressed format. Using compressed geometry reduces the amount + * of memory needed by a Java 3D application and increases the speed + * objects can be sent over the network. Once geometry decompression + * hardware support becomes available, increased rendering performance + * will also result from the use of compressed geometry. + */ +class CompressedGeometryRetained extends Object { + + // If not in by-reference mode, a 48-byte header as defined by the + // GL_SUNX_geometry_compression OpenGL extension is always concatenated to + // the beginning of the compressed geometry data and copied along with the + // it into a contiguous array. This allows hardware decompression using + // the obsolete experimental GL_SUNX_geometry_compression extension if + // that is all that is available. + // + // This is completely distinct and not to be confused with the cgHeader + // field on the non-retained side, although much of the data is + // essentially the same. + private static final int HEADER_LENGTH = 48 ; + + // These are the header locations examined. + private static final int HEADER_MAJOR_VERSION_OFFSET = 0 ; + private static final int HEADER_MINOR_VERSION_OFFSET = 1 ; + private static final int HEADER_MINOR_MINOR_VERSION_OFFSET = 2 ; + private static final int HEADER_BUFFER_TYPE_OFFSET = 3 ; + private static final int HEADER_BUFFER_DATA_OFFSET = 4 ; + + // The OpenGL compressed geometry extensions use bits instead of + // enumerations to represent the type of compressed geometry. + static final byte TYPE_POINT = 1 ; + static final byte TYPE_LINE = 2 ; + static final byte TYPE_TRIANGLE = 4 ; + + // Version number of this compressed geometry object. + int majorVersionNumber ; + int minorVersionNumber ; + int minorMinorVersionNumber ; + + // These fields are used by the native execute() method. + int packedVersion ; + int bufferType ; + int bufferContents ; + int renderFlags ; + int offset ; + int size ; + byte[] compressedGeometry ; + + // A reference to the original byte array with which this object was + // created. If hardware decompression is available but it doesn't support + // by-reference semantics, then an internal copy of the original byte array + // is made even when by-reference semantics have been requested. + private byte[] originalCompressedGeometry = null ; + + // Geometric bounds + private BoundingBox geoBounds = new BoundingBox(); + + // True if by-reference data access mode is in effect. + private boolean byReference = false ; + + /** + * The package-scoped constructor. + */ + CompressedGeometryRetained() { + // Compressed geometry is always bounded by [-1..1] on each axis, so + // set that as the initial bounding box. + geoBounds.setUpper( 1.0, 1.0, 1.0) ; + geoBounds.setLower(-1.0,-1.0,-1.0) ; + } + + /** + * Return true if the data access mode is by-reference. + */ + boolean isByReference() { + return this.byReference ; + } + + private void createByCopy(byte[] geometry) { + // Always copy a header along with the compressed geometry into a + // contiguous array in order to support hardware acceleration with the + // GL_SUNX_geometry_compression extension. The header is unnecessary + // if only the newer GL_SUN_geometry_compression API needs support. + compressedGeometry = new byte[HEADER_LENGTH + this.size] ; + + compressedGeometry[HEADER_MAJOR_VERSION_OFFSET] = + (byte)this.majorVersionNumber ; + + compressedGeometry[HEADER_MINOR_VERSION_OFFSET] = + (byte)this.minorVersionNumber ; + + compressedGeometry[HEADER_MINOR_MINOR_VERSION_OFFSET] = + (byte)this.minorMinorVersionNumber ; + + compressedGeometry[HEADER_BUFFER_TYPE_OFFSET] = + (byte)this.bufferType ; + + compressedGeometry[HEADER_BUFFER_DATA_OFFSET] = + (byte)this.bufferContents ; + + System.arraycopy(geometry, this.offset, + compressedGeometry, HEADER_LENGTH, this.size) ; + + this.offset = HEADER_LENGTH ; + } + + /** + * Creates the retained compressed geometry data. Data from the header is + * always copied; the compressed geometry is copied as well if the data + * access mode is not by-reference. + * + * @param hdr the compressed geometry header + * @param geometry the compressed geometry + * @param byReference if true then by-reference semantics requested + */ + void createCompressedGeometry(CompressedGeometryData.Header hdr, + byte[] geometry, boolean byReference) { + + this.byReference = byReference ; + + if (hdr.lowerBound != null) + this.geoBounds.setLower(hdr.lowerBound) ; + + if (hdr.upperBound != null) + this.geoBounds.setUpper(hdr.upperBound) ; + +//// this.centroid.set(geoBounds.getCenter()); +//// recompCentroid = false; + this.majorVersionNumber = hdr.majorVersionNumber ; + this.minorVersionNumber = hdr.minorVersionNumber ; + this.minorMinorVersionNumber = hdr.minorMinorVersionNumber ; + + this.packedVersion = + (hdr.majorVersionNumber << 24) | + (hdr.minorVersionNumber << 16) | + (hdr.minorMinorVersionNumber << 8) ; + + switch(hdr.bufferType) { + case CompressedGeometryData.Header.POINT_BUFFER: + this.bufferType = TYPE_POINT ; + break ; + case CompressedGeometryData.Header.LINE_BUFFER: + this.bufferType = TYPE_LINE ; + break ; + case CompressedGeometryData.Header.TRIANGLE_BUFFER: + this.bufferType = TYPE_TRIANGLE ; + break ; + } + + this.bufferContents = hdr.bufferDataPresent ; + this.renderFlags = 0 ; + + this.size = hdr.size ; + this.offset = hdr.start ; + + if (byReference) { + // Assume we can use the given reference, but maintain a second + // reference in case a copy is later needed. + this.compressedGeometry = geometry; + this.originalCompressedGeometry = geometry; + } else { + // Copy the original data into a format that can be used by both + // the software and native hardware decompressors. + createByCopy(geometry); + this.originalCompressedGeometry = null; + } + } + + /** + * Return a vertex format mask that's compatible with GeometryArray + * objects. + */ + int getVertexFormat() { + int vertexFormat = GeometryArray.COORDINATES; + + if ((bufferContents & CompressedGeometryData.Header.NORMAL_IN_BUFFER) != 0) { + vertexFormat |= GeometryArray.NORMALS; + } + + if ((bufferContents & CompressedGeometryData.Header.COLOR_IN_BUFFER) != 0) { + if ((bufferContents & CompressedGeometryData.Header.ALPHA_IN_BUFFER) != 0) { + vertexFormat |= GeometryArray.COLOR_4; + } else { + vertexFormat |= GeometryArray.COLOR_3; + } + } + + return vertexFormat ; + } + + /** + * Return a buffer type that's compatible with CompressedGeometryData.Header. + */ + int getBufferType() { + switch(this.bufferType) { + case TYPE_POINT: + return CompressedGeometryData.Header.POINT_BUFFER ; + case TYPE_LINE: + return CompressedGeometryData.Header.LINE_BUFFER ; + default: + case TYPE_TRIANGLE: + return CompressedGeometryData.Header.TRIANGLE_BUFFER ; + } + } + + /** + * Copies compressed geometry data into the given array of bytes. + * The internal header information is not copied. + * + * @param buff array of bytes into which to copy compressed geometry + */ + void copy(byte[] buff) { + System.arraycopy(compressedGeometry, offset, buff, 0, size) ; + } + + /** + * Returns a reference to the original compressed geometry byte array, + * which may have been copied even if by-reference semantics have been + * requested. It will be null if byCopy is in effect. + * + * @return reference to array of bytes containing the compressed geometry. + */ + byte[] getReference() { + return originalCompressedGeometry ; + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStream.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStream.java new file mode 100644 index 0000000..77ab5f4 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStream.java @@ -0,0 +1,2321 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import com.sun.j3d.internal.BufferWrapper; +import com.sun.j3d.internal.ByteBufferWrapper; +import com.sun.j3d.internal.DoubleBufferWrapper; +import com.sun.j3d.internal.FloatBufferWrapper; +import com.sun.j3d.utils.geometry.GeometryInfo; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import javax.media.j3d.Appearance; +import javax.media.j3d.Geometry; +import javax.media.j3d.GeometryArray; +import javax.media.j3d.GeometryStripArray; +import javax.media.j3d.IndexedGeometryArray; +import javax.media.j3d.IndexedGeometryStripArray; +import javax.media.j3d.IndexedLineArray; +import javax.media.j3d.IndexedLineStripArray; +import javax.media.j3d.IndexedQuadArray; +import javax.media.j3d.IndexedTriangleArray; +import javax.media.j3d.IndexedTriangleFanArray; +import javax.media.j3d.IndexedTriangleStripArray; +import javax.media.j3d.J3DBuffer; +import javax.media.j3d.LineArray; +import javax.media.j3d.LineStripArray; +import javax.media.j3d.Material; +import javax.media.j3d.QuadArray; +import javax.media.j3d.Shape3D; +import javax.media.j3d.TriangleArray; +import javax.media.j3d.TriangleFanArray; +import javax.media.j3d.TriangleStripArray; +import javax.vecmath.Color3f; +import javax.vecmath.Color4f; +import javax.vecmath.Point3d; +import javax.vecmath.Point3f; +import javax.vecmath.Point3i; +import javax.vecmath.Vector3f; + +/** + * This class is used as input to a geometry compressor. It collects elements + * such as vertices, normals, colors, mesh references, and quantization + * parameters in an ordered stream. This stream is then traversed during + * the compression process and used to build the compressed output buffer. + * + * @see GeometryCompressor + * + * @since Java 3D 1.5 + */ +public class CompressionStream { + // + // NOTE: For now, copies are made of all GeometryArray vertex components + // even when by-reference access is available. + // + // TODO: Retrofit all CompressionStreamElements and MeshBuffer to handle + // offsets to vertex data array references so that vertex components don't + // have to be copied. New CompressionStreamElements could be defined to + // set the current array reference during the quantization pass, or the + // reference could be included in every CompressionStreamElement along + // with the data offsets. + // + // TODO: Quantize on-the-fly when adding GeometryArray vertex data so that + // CompressionStreamElements don't need references to the original float, + // double, or byte data. Quantization is currently a separate pass since + // the 1st pass adds vertex data and gets the total object bounds, but + // this can be computed by merging the bounds of each GeometryArray + // compressed into a single object. The 2nd pass quantization is still + // needed for vertex data which isn't retrieved from a GeometryArray; for + // example, apps that might use the addVertex() methods directly instead + // of addGeometryArray(). + // + // TODO: To further optimize memory, create new subclasses of + // CompressionStream{Color, Normal} for bundled attributes and add them as + // explicit stream elements. Then CompressionStreamVertex won't need to + // carry references to them. This memory savings might be negated by the + // extra overhead of adding more elements to the stream, however. + // + // TODO: Keep the absolute quantized values in the mesh buffer mirror so + // that unmeshed CompressionStreamElements don't need to carry them. + // + // TODO: Support texture coordinate compression even though Level II is + // not supported by any hardware decompressor on any graphics card. + // Software decompression is still useful for applications interested in + // minimizing file space, transmission time, and object loading time. + // + private static final boolean debug = false ; + private static final boolean benchmark = false ; + + // Mesh buffer normal substitution is unavailable in Level I. + private static final boolean noMeshNormalSubstitution = true ; + + /** + * This flag indicates that a vertex starts a new triangle or line strip. + */ + static final int RESTART = 1 ; + + /** + * This flag indicates that the next triangle in the strip is defined by + * replacing the middle vertex of the previous triangle in the strip. + * Equivalent to REPLACE_OLDEST for line strips. + */ + static final int REPLACE_MIDDLE = 2 ; + + /** + * This flag indicates that the next triangle in the strip is defined by + * replacing the oldest vertex of the previous triangle in the strip. + * Equivalent to REPLACE_MIDDLE for line strips. + */ + static final int REPLACE_OLDEST = 3 ; + + /** + * This flag indicates that a vertex is to be pushed into the mesh buffer. + */ + static final int MESH_PUSH = 1 ; + + /** + * This flag indicates that a vertex does not use the mesh buffer. + */ + static final int NO_MESH_PUSH = 0 ; + + /** + * Byte to float scale factor for scaling byte color components. + */ + static final float ByteToFloatScale = 1.0f/255.0f; + + /** + * Type of this stream, either CompressedGeometryData.Header.POINT_BUFFER, + * CompressedGeometryData.Header.LINE_BUFFER, or + * CompressedGeometryData.Header.TRIANGLE_BUFFER + */ + int streamType ; + + /** + * A mask indicating which components are present in each vertex, as + * defined by GeometryArray. + */ + int vertexComponents ; + + /** + * Boolean indicating colors are bundled with the vertices. + */ + boolean vertexColors ; + + /** + * Boolean indicating RGB colors are bundled with the vertices. + */ + boolean vertexColor3 ; + + /** + * Boolean indicating RGBA colors are bundled with the vertices. + */ + boolean vertexColor4 ; + + /** + * Boolean indicating normals are bundled with the vertices. + */ + boolean vertexNormals ; + + /** + * Boolean indicating texture coordinates are present. + */ + boolean vertexTextures ; + + /** + * Boolean indicating that 2D texture coordinates are used. + * Currently only used to skip over textures in interleaved data. + */ + boolean vertexTexture2 ; + + /** + * Boolean indicating that 3D texture coordinates are used. + * Currently only used to skip over textures in interleaved data. + */ + boolean vertexTexture3 ; + + /** + * Boolean indicating that 4D texture coordinates are used. + * Currently only used to skip over textures in interleaved data. + */ + boolean vertexTexture4 ; + + /** + * Axes-aligned box enclosing all vertices in model coordinates. + */ + Point3d mcBounds[] = new Point3d[2] ; + + /** + * Axes-aligned box enclosing all vertices in normalized coordinates. + */ + Point3d ncBounds[] = new Point3d[2] ; + + /** + * Axes-aligned box enclosing all vertices in quantized coordinates. + */ + Point3i qcBounds[] = new Point3i[2] ; + + /** + * Center for normalizing positions to the unit cube. + */ + double center[] = new double[3] ; + + /** + * Maximum position range along the 3 axes. + */ + double positionRangeMaximum ; + + /** + * Scale for normalizing positions to the unit cube. + */ + double scale ; + + /** + * Current position component (X, Y, and Z) quantization value. This can + * range from 1 to 16 bits and has a default of 16.<p> + * + * At 1 bit of quantization it is not possible to express positive + * absolute or delta positions. + */ + int positionQuant ; + + /** + * Current color component (R, G, B, A) quantization value. This can + * range from 2 to 16 bits and has a default of 9.<p> + * + * A color component is represented with a signed fixed-point value in + * order to be able express negative deltas; the default of 9 bits + * corresponds to the 8-bit color component range of the graphics hardware + * commonly available. Colors must be non-negative, so the lower limit of + * quantization is 2 bits. + */ + int colorQuant ; + + /** + * Current normal component (U and V) quantization value. This can range + * from 0 to 6 bits and has a default of 6.<p> + * + * At 0 bits of quantization normals are represented only as 6 bit + * sextant/octant pairs and 14 specially encoded normals (the 6 axis + * normals and the 8 octant midpoint normals); since U and V can only be 0 + * at the minimum quantization, the totally number of unique normals is + * 12 + 14 = 26. + */ + int normalQuant ; + + /** + * Flag indicating position quantization change. + */ + boolean positionQuantChanged ; + + /** + * Flag indicating color quantization change. + */ + boolean colorQuantChanged ; + + /** + * Flag indicating normal quantization change. + */ + boolean normalQuantChanged ; + + /** + * Last quantized position. + */ + int lastPosition[] = new int[3] ; + + /** + * Last quantized color. + */ + int lastColor[] = new int[4] ; + + /** + * Last quantized normal's sextant. + */ + int lastSextant ; + + /** + * Last quantized normal's octant. + */ + int lastOctant ; + + /** + * Last quantized normal's U encoding parameter. + */ + int lastU ; + + /** + * Last quantized normal's V encoding parameter. + */ + int lastV ; + + /** + * Flag indicating last normal used a special encoding. + */ + boolean lastSpecialNormal ; + + /** + * Flag indicating the first position in this stream. + */ + boolean firstPosition ; + + /** + * Flag indicating the first color in this stream. + */ + boolean firstColor ; + + /** + * Flag indicating the first normal in this stream. + */ + boolean firstNormal ; + + /** + * The total number of bytes used to create the uncompressed geometric + * elements in this stream, useful for performance analysis. This + * excludes mesh buffer references. + */ + int byteCount ; + + /** + * The number of vertices created for this stream, excluding mesh buffer + * references. + */ + int vertexCount ; + + /** + * The number of mesh buffer references created for this stream. + */ + int meshReferenceCount ; + + /** + * Mesh buffer mirror used for computing deltas during quantization pass + * and a limited meshing algorithm for unstripped data. + */ + MeshBuffer meshBuffer = new MeshBuffer() ; + + + // Collection which holds the elements of this stream. + private Collection stream ; + + // True if preceding stream elements were colors or normals. Used to flag + // color and normal mesh buffer substitution when computing deltas during + // quantization pass. + private boolean lastElementColor = false ; + private boolean lastLastElementColor = false ; + private boolean lastElementNormal = false ; + private boolean lastLastElementNormal = false ; + + // Some convenient temporary holding variables. + private Point3f p3f = new Point3f() ; + private Color3f c3f = new Color3f() ; + private Color4f c4f = new Color4f() ; + private Vector3f n3f = new Vector3f() ; + + + // Private constructor for common initializations. + private CompressionStream() { + this.stream = new LinkedList() ; + + byteCount = 0 ; + vertexCount = 0 ; + meshReferenceCount = 0 ; + + mcBounds[0] = new Point3d(Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY) ; + mcBounds[1] = new Point3d(Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY) ; + + qcBounds[0] = new Point3i(Integer.MAX_VALUE, + Integer.MAX_VALUE, + Integer.MAX_VALUE) ; + qcBounds[1] = new Point3i(Integer.MIN_VALUE, + Integer.MIN_VALUE, + Integer.MIN_VALUE) ; + + /* normalized bounds computed from quantized bounds */ + ncBounds[0] = new Point3d() ; + ncBounds[1] = new Point3d() ; + } + + /** + * Creates a new CompressionStream for the specified geometry type and + * vertex format.<p> + * + * @param streamType type of data in this stream, either + * CompressedGeometryData.Header.POINT_BUFFER, + * CompressedGeometryData.Header.LINE_BUFFER, or + * CompressedGeometryData.Header.TRIANGLE_BUFFER + * @param vertexComponents a mask indicating which components are present + * in each vertex, as defined by GeometryArray: COORDINATES, NORMALS, and + * COLOR_3 or COLOR_4. + * @see GeometryCompressor + * @see GeometryArray + */ + CompressionStream(int streamType, int vertexComponents) { + this() ; + this.streamType = streamType ; + this.vertexComponents = getVertexComponents(vertexComponents) ; + } + + // See what vertex geometry components are present. The byReference, + // interleaved, useNIOBuffer, and useCoordIndexOnly flags are not + // examined. + private int getVertexComponents(int vertexFormat) { + int components = 0 ; + + vertexColors = vertexColor3 = vertexColor4 = vertexNormals = + vertexTextures = vertexTexture2 = vertexTexture3 = vertexTexture4 = + false ; + + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + vertexNormals = true ; + components &= GeometryArray.NORMALS ; + if (debug) System.out.println("vertexNormals") ; + } + + if ((vertexFormat & GeometryArray.COLOR_3) != 0) { + vertexColors = true ; + + if ((vertexFormat & GeometryArray.COLOR_4) != 0) { + vertexColor4 = true ; + components &= GeometryArray.COLOR_4 ; + if (debug) System.out.println("vertexColor4") ; + } + else { + vertexColor3 = true ; + components &= GeometryArray.COLOR_3 ; + if (debug) System.out.println("vertexColor3") ; + } + } + + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) { + vertexTextures = true ; + vertexTexture2 = true ; + components &= GeometryArray.TEXTURE_COORDINATE_2 ; + if (debug) System.out.println("vertexTexture2") ; + } + else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + vertexTextures = true ; + vertexTexture3 = true ; + components &= GeometryArray.TEXTURE_COORDINATE_3 ; + if (debug) System.out.println("vertexTexture3") ; + } + else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + vertexTextures = true ; + vertexTexture4 = true ; + components &= GeometryArray.TEXTURE_COORDINATE_4 ; + if (debug) System.out.println("vertexTexture4") ; + } + + if (vertexTextures) + // Throw exception for now until texture is supported. + throw new UnsupportedOperationException + ("\ncompression of texture coordinates is not supported") ; + + return components ; + } + + // Get the streamType associated with a GeometryArray instance. + private int getStreamType(GeometryArray ga) { + if (ga instanceof TriangleStripArray || + ga instanceof IndexedTriangleStripArray || + ga instanceof TriangleFanArray || + ga instanceof IndexedTriangleFanArray || + ga instanceof TriangleArray || + ga instanceof IndexedTriangleArray || + ga instanceof QuadArray || + ga instanceof IndexedQuadArray) + + return CompressedGeometryData.Header.TRIANGLE_BUFFER ; + + else if (ga instanceof LineArray || + ga instanceof IndexedLineArray || + ga instanceof LineStripArray || + ga instanceof IndexedLineStripArray) + + return CompressedGeometryData.Header.LINE_BUFFER ; + + else + return CompressedGeometryData.Header.POINT_BUFFER ; + } + + /** + * Iterates across all compression stream elements and applies + * quantization parameters, encoding consecutive vertices as delta values + * whenever possible. Each geometric element is mapped to a HuffmanNode + * object containing its resulting bit length, right shift (trailing 0 + * count), and absolute or relative status.<p> + * + * Positions are normalized to span a unit cube via an offset and a + * uniform scale factor that maps the midpoint of the object extents along + * each dimension to the origin, and the longest dimension of the object to + * the open interval (-1.0 .. +1.0). The geometric endpoints along that + * dimension are both one quantum away from unity; for example, at a + * position quantization of 6 bits, an object would be normalized so that + * its most negative dimension is at (-1 + 1/64) and the most positive is + * at (1 - 1/64).<p> + * + * Normals are assumed to be of unit length. Color components are clamped + * to the [0..1) range, where the right endpoint is one quantum less + * than 1.0.<p> + * + * @param huffmanTable Table which will map geometric compression stream + * elements to HuffmanNode objects describing each element's data + * representation. This table can then be processed with Huffman's + * algorithm to optimize the bit length of descriptor tags according to + * the number of geometric elements mapped to each tag. + */ + void quantize(HuffmanTable huffmanTable) { + // Set up default initial quantization parameters. The position and + // color parameters specify the number of bits for each X, Y, Z, R, G, + // B, or A component. The normal quantization parameter specifies the + // number of bits for each U and V component. + positionQuant = 16 ; + colorQuant = 9 ; + normalQuant = 6 ; + + // Compute position center and scaling for normalization to the unit + // cube. This is a volume bounded by the open intervals (-1..1) on + // each axis. + center[0] = (mcBounds[1].x + mcBounds[0].x) / 2.0 ; + center[1] = (mcBounds[1].y + mcBounds[0].y) / 2.0 ; + center[2] = (mcBounds[1].z + mcBounds[0].z) / 2.0 ; + + double xRange = mcBounds[1].x - mcBounds[0].x ; + double yRange = mcBounds[1].y - mcBounds[0].y ; + double zRange = mcBounds[1].z - mcBounds[0].z ; + + if (xRange > yRange) + positionRangeMaximum = xRange ; + else + positionRangeMaximum = yRange ; + + if (zRange > positionRangeMaximum) + positionRangeMaximum = zRange ; + + // Adjust the range of the unit cube to match the default + // quantization. + // + // This scale factor along with the center values computed above will + // produce 16-bit integer representations of the floating point + // position coordinates ranging symmetrically about 0 from -32767 to + // +32767. -32768 is not used and the normalized floating point + // position coordinates of -1.0 as well as +1.0 will not be + // represented. + // + // Applications which wish to seamlessly stitch together compressed + // objects will need to be aware that the range of normalized + // positions will be one quantum away from the [-1..1] endpoints of + // the unit cube and should adjust scale factors accordingly. + scale = (2.0 / positionRangeMaximum) * (32767.0 / 32768.0) ; + + // Flag quantization change. + positionQuantChanged = colorQuantChanged = normalQuantChanged = true ; + + // Flag first position, color, and normal. + firstPosition = firstColor = firstNormal = true ; + + // Apply quantization. + Iterator i = stream.iterator() ; + while (i.hasNext()) { + Object o = i.next() ; + + if (o instanceof CompressionStreamElement) { + ((CompressionStreamElement)o).quantize(this, huffmanTable) ; + + // Keep track of whether last two elements were colors or + // normals for mesh buffer component substitution semantics. + lastLastElementColor = lastElementColor ; + lastLastElementNormal = lastElementNormal ; + lastElementColor = lastElementNormal = false ; + + if (o instanceof CompressionStreamColor) + lastElementColor = true ; + else if (o instanceof CompressionStreamNormal) + lastElementNormal = true ; + } + } + + // Compute the bounds in normalized coordinates. + ncBounds[0].x = (double)qcBounds[0].x / 32768.0 ; + ncBounds[0].y = (double)qcBounds[0].y / 32768.0 ; + ncBounds[0].z = (double)qcBounds[0].z / 32768.0 ; + + ncBounds[1].x = (double)qcBounds[1].x / 32768.0 ; + ncBounds[1].y = (double)qcBounds[1].y / 32768.0 ; + ncBounds[1].z = (double)qcBounds[1].z / 32768.0 ; + } + + /** + * Iterates across all compression stream elements and builds the + * compressed geometry command stream output.<p> + * + * @param huffmanTable Table which maps geometric elements in this stream + * to tags describing the encoding parameters (length, shift, and + * absolute/relative status) to be used for their representations in the + * compressed output. All tags must be 6 bits or less in length, and the + * sum of the number of bits in the tag plus the number of bits in the + * data it describes must be at least 6 bits in length. + * + * @param outputBuffer CommandStream to use for collecting the compressed + * bits. + */ + void outputCommands(HuffmanTable huffmanTable, CommandStream outputBuffer) { + // + // The first command output is setState to indicate what data is + // bundled with each vertex. Although the semantics of geometry + // decompression allow setState to appear anywhere in the stream, this + // cannot be handled by the current Java 3D software decompressor, + // which internally decompresses an entire compressed buffer into a + // single retained object sharing a single consistent vertex format. + // This limitation may be removed in subsequent releases of Java 3D. + // + int bnv = (vertexNormals? 1 : 0) ; + int bcv = ((vertexColor3 || vertexColor4)? 1 : 0) ; + int cap = (vertexColor4? 1 : 0) ; + + int command = CommandStream.SET_STATE | bnv ; + long data = (bcv << 2) | (cap << 1) ; + + // Output the setState command. + outputBuffer.addCommand(command, 8, data, 3) ; + + // Output the Huffman table commands. + huffmanTable.outputCommands(outputBuffer) ; + + // Output each compression stream element's data. + Iterator i = stream.iterator() ; + while (i.hasNext()) { + Object o = i.next() ; + if (o instanceof CompressionStreamElement) + ((CompressionStreamElement)o).outputCommand(huffmanTable, + outputBuffer) ; + } + + // Finish the header-forwarding interleave and long-word align. + outputBuffer.end() ; + } + + /** + * Retrieve the total size of the uncompressed geometric data in bytes, + * excluding mesh buffer references. + * @return uncompressed byte count + */ + int getByteCount() { + return byteCount ; + } + + /** + * Retrieve the the number of vertices created for this stream, excluding + * mesh buffer references. + * @return vertex count + */ + int getVertexCount() { + return vertexCount ; + } + + /** + * Retrieve the number of mesh buffer references created for this stream. + * @return mesh buffer reference count + */ + int getMeshReferenceCount() { + return meshReferenceCount ; + } + + /** + * Stream element that sets position quantization during quantize pass. + */ + private class PositionQuant extends CompressionStreamElement { + int value ; + + PositionQuant(int value) { + this.value = value ; + } + + void quantize(CompressionStream s, HuffmanTable t) { + positionQuant = value ; + positionQuantChanged = true ; + + // Adjust range of unit cube scaling to match quantization. + scale = (2.0 / positionRangeMaximum) * + (((double)((1 << (value-1)) - 1))/((double)(1 << (value-1)))) ; + } + + public String toString() { + return "positionQuant: " + value ; + } + } + + /** + * Stream element that sets normal quantization during quantize pass. + */ + private class NormalQuant extends CompressionStreamElement { + int value ; + + NormalQuant(int value) { + this.value = value ; + } + + void quantize(CompressionStream s, HuffmanTable t) { + normalQuant = value ; + normalQuantChanged = true ; + } + + public String toString() { + return "normalQuant: " + value ; + } + } + + /** + * Stream element that sets color quantization during quantize pass. + */ + private class ColorQuant extends CompressionStreamElement { + int value ; + + ColorQuant(int value) { + this.value = value ; + } + + void quantize(CompressionStream s, HuffmanTable t) { + colorQuant = value ; + colorQuantChanged = true ; + } + + public String toString() { + return "colorQuant: " + value ; + } + } + + /** + * Stream element that references the mesh buffer. + */ + private class MeshReference extends CompressionStreamElement { + int stripFlag, meshIndex ; + + MeshReference(int stripFlag, int meshIndex) { + this.stripFlag = stripFlag ; + this.meshIndex = meshIndex ; + meshReferenceCount++ ; + } + + void quantize(CompressionStream s, HuffmanTable t) { + // Retrieve the vertex from the mesh buffer mirror and set up the + // data needed for the next stream element to compute its deltas. + CompressionStreamVertex v = meshBuffer.getVertex(meshIndex) ; + lastPosition[0] = v.xAbsolute ; + lastPosition[1] = v.yAbsolute ; + lastPosition[2] = v.zAbsolute ; + + // Set up last color data if it exists and previous elements + // don't override it. + if (v.color != null && !lastElementColor && + !(lastElementNormal && lastLastElementColor)) { + lastColor[0] = v.color.rAbsolute ; + lastColor[1] = v.color.gAbsolute ; + lastColor[2] = v.color.bAbsolute ; + lastColor[3] = v.color.aAbsolute ; + } + + // Set up last normal data if it exists and previous element + // doesn't override it. + if (v.normal != null && !lastElementNormal && + !(lastElementColor && lastLastElementNormal)) { + lastSextant = v.normal.sextant ; + lastOctant = v.normal.octant ; + lastU = v.normal.uAbsolute ; + lastV = v.normal.vAbsolute ; + lastSpecialNormal = v.normal.specialNormal ; + } + } + + void outputCommand(HuffmanTable t, CommandStream outputBuffer) { + int command = CommandStream.MESH_B_R ; + long data = stripFlag & 0x1 ; + + command |= (((meshIndex & 0xf) << 1) | (stripFlag >> 1)) ; + outputBuffer.addCommand(command, 8, data, 1) ; + } + + public String toString() { + return + "meshReference: stripFlag " + stripFlag + + " meshIndex " + meshIndex ; + } + } + + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, int stripFlag) { + stream.add(new CompressionStreamVertex(this, pos, + (Vector3f)null, (Color3f)null, + stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, Vector3f norm, int stripFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, (Color3f)null, stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, Color3f color, int stripFlag) { + stream.add(new CompressionStreamVertex + (this, pos, (Vector3f)null, color, stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, Color4f color, int stripFlag) { + stream.add(new CompressionStreamVertex + (this, pos, (Vector3f)null, color, stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, Vector3f norm, Color3f color, + int stripFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, color, stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, Vector3f norm, Color4f color, + int stripFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, color, stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, (Vector3f)null, (Color3f)null, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Vector3f norm, + int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, (Color3f)null, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Color3f color, + int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, (Vector3f)null, color, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Color4f color, + int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, (Vector3f)null, color, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Vector3f norm, Color3f color, + int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, color, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Vector3f norm, Color4f color, + int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, color, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param color color data, either Color3f or Color4f, determined by + * current vertex format + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Vector3f norm, + Object color, int stripFlag, int meshFlag) { + + if (vertexColor3) + stream.add(new CompressionStreamVertex + (this, pos, norm, (Color3f)color, stripFlag, meshFlag)) ; + else + stream.add(new CompressionStreamVertex + (this, pos, norm, (Color4f)color, stripFlag, meshFlag)) ; + } + + /** + * Add a mesh buffer reference to this stream. + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshIndex index of vertex to retrieve from the mesh buffer + */ + void addMeshReference(int stripFlag, int meshIndex) { + stream.add(new MeshReference(stripFlag, meshIndex)) ; + } + + /** + * Copy the given color to the end of this stream and use it as a global + * state change that applies to all subsequent vertices. + */ + void addColor(Color3f c3f) { + stream.add(new CompressionStreamColor(this, c3f)) ; + } + + /** + * Copy the given color to the end of this stream and use it as a global + * state change that applies to all subsequent vertices. + */ + void addColor(Color4f c4f) { + stream.add(new CompressionStreamColor(this, c4f)) ; + } + + /** + * Copy the given normal to the end of this stream and use it as a global + * state change that applies to all subsequent vertices. + */ + void addNormal(Vector3f n) { + stream.add(new CompressionStreamNormal(this, n)) ; + } + + /** + * Add a new position quantization value to the end of this stream that + * will apply to all subsequent vertex positions. + * + * @param value number of bits to quantize each position's X, Y, + * and Z components, ranging from 1 to 16 with a default of 16 + */ + void addPositionQuantization(int value) { + stream.add(new PositionQuant(value)) ; + } + + /** + * Add a new color quantization value to the end of this stream that will + * apply to all subsequent colors. + * + * @param value number of bits to quantize each color's R, G, B, and + * alpha components, ranging from 2 to 16 with a default of 9 + */ + void addColorQuantization(int value) { + stream.add(new ColorQuant(value)) ; + } + + /** + * Add a new normal quantization value to the end of this stream that will + * apply to all subsequent normals. This value specifies the number of + * bits for each normal's U and V components. + * + * @param value number of bits for quantizing U and V, ranging from 0 to + * 6 with a default of 6 + */ + void addNormalQuantization(int value) { + stream.add(new NormalQuant(value)) ; + } + + /** + * Interface to access GeometryArray vertex components and add them to the + * compression stream. + * + * A processVertex() implementation retrieves vertex components using the + * appropriate access semantics of a particular GeometryArray, and adds + * them to the compression stream. + * + * The implementation always pushes vertices into the mesh buffer unless + * they match ones already there; if they do, it generates mesh buffer + * references instead. This reduces the number of vertices when + * non-stripped abutting facets are added to the stream. + * + * Note: Level II geometry compression semantics allow the mesh buffer + * normals to be substituted with the value of an immediately + * preceding SetNormal command, but this is unavailable in Level I. + * + * @param index vertex offset from the beginning of its data array + * @param stripFlag RESTART, REPLACE_MIDDLE, or REPLACE_OLDEST + */ + private interface GeometryAccessor { + void processVertex(int index, int stripFlag) ; + } + + /** + * This class implements the GeometryAccessor interface for geometry + * arrays accessed with by-copy semantics. + */ + private class ByCopyGeometry implements GeometryAccessor { + Point3f[] positions = null ; + Vector3f[] normals = null ; + Color3f[] colors3 = null ; + Color4f[] colors4 = null ; + + ByCopyGeometry(GeometryArray ga) { + this(ga, ga.getInitialVertexIndex(), ga.getValidVertexCount()) ; + } + + ByCopyGeometry(GeometryArray ga, + int firstVertex, int validVertexCount) { + int i ; + positions = new Point3f[validVertexCount] ; + for (i = 0 ; i < validVertexCount ; i++) + positions[i] = new Point3f() ; + + ga.getCoordinates(firstVertex, positions) ; + + if (vertexNormals) { + normals = new Vector3f[validVertexCount] ; + for (i = 0 ; i < validVertexCount ; i++) + normals[i] = new Vector3f() ; + + ga.getNormals(firstVertex, normals) ; + } + + if (vertexColor3) { + colors3 = new Color3f[validVertexCount] ; + for (i = 0 ; i < validVertexCount ; i++) + colors3[i] = new Color3f() ; + + ga.getColors(firstVertex, colors3) ; + } + else if (vertexColor4) { + colors4 = new Color4f[validVertexCount] ; + for (i = 0 ; i < validVertexCount ; i++) + colors4[i] = new Color4f() ; + + ga.getColors(firstVertex, colors4) ; + } + } + + public void processVertex(int v, int stripFlag) { + Point3f p = positions[v] ; + int r = meshBuffer.getMeshReference(p) ; + + if ((r == meshBuffer.NOT_FOUND) || + (vertexNormals && noMeshNormalSubstitution && + (! normals[v].equals(meshBuffer.getNormal(r))))) { + + Vector3f n = vertexNormals? normals[v] : null ; + Object c = vertexColor3? (Object)colors3[v] : + vertexColor4? (Object)colors4[v] : null ; + + addVertex(p, n, c, stripFlag, MESH_PUSH) ; + meshBuffer.push(p, c, n) ; + } + else { + if (vertexNormals && !noMeshNormalSubstitution && + (! normals[v].equals(meshBuffer.getNormal(r)))) + addNormal(normals[v]) ; + + if (vertexColor3 && + (! colors3[v].equals(meshBuffer.getColor3(r)))) + addColor(colors3[v]) ; + + else if (vertexColor4 && + (! colors4[v].equals(meshBuffer.getColor4(r)))) + addColor(colors4[v]) ; + + addMeshReference(stripFlag, r) ; + } + } + } + + /** + * Class which holds index array references for a geometry array. + */ + private static class IndexArrays { + int colorIndices[] = null ; + int normalIndices[] = null ; + int positionIndices[] = null ; + } + + /** + * Retrieves index array references for the specified IndexedGeometryArray. + * Index arrays are copied starting from initialIndexIndex. + */ + private void getIndexArrays(GeometryArray ga, IndexArrays ia) { + IndexedGeometryArray iga = (IndexedGeometryArray)ga ; + + int initialIndexIndex = iga.getInitialIndexIndex() ; + int indexCount = iga.getValidIndexCount() ; + int vertexFormat = iga.getVertexFormat() ; + + boolean useCoordIndexOnly = false ; + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) != 0) { + if (debug) System.out.println("useCoordIndexOnly") ; + useCoordIndexOnly = true ; + } + + ia.positionIndices = new int[indexCount] ; + iga.getCoordinateIndices(initialIndexIndex, ia.positionIndices) ; + + if (vertexNormals) { + if (useCoordIndexOnly) { + ia.normalIndices = ia.positionIndices ; + } + else { + ia.normalIndices = new int[indexCount] ; + iga.getNormalIndices(initialIndexIndex, ia.normalIndices) ; + } + } + if (vertexColor3 || vertexColor4) { + if (useCoordIndexOnly) { + ia.colorIndices = ia.positionIndices ; + } + else { + ia.colorIndices = new int[indexCount] ; + iga.getColorIndices(initialIndexIndex, ia.colorIndices) ; + } + } + } + + /** + * Class which holds indices for a specific vertex of an + * IndexedGeometryArray. + */ + private static class VertexIndices { + int pi, ni, ci ; + } + + /** + * Retrieves vertex indices for a specific vertex in an + * IndexedGeometryArray. + */ + private void getVertexIndices(int v, IndexArrays ia, VertexIndices vi) { + vi.pi = ia.positionIndices[v] ; + if (vertexNormals) + vi.ni = ia.normalIndices[v] ; + if (vertexColors) + vi.ci = ia.colorIndices[v] ; + } + + /** + * This class implements the GeometryAccessor interface for indexed + * geometry arrays accessed with by-copy semantics. + */ + private class IndexedByCopyGeometry extends ByCopyGeometry { + IndexArrays ia = new IndexArrays() ; + VertexIndices vi = new VertexIndices() ; + + IndexedByCopyGeometry(GeometryArray ga) { + super(ga, 0, ga.getVertexCount()) ; + getIndexArrays(ga, ia) ; + } + + public void processVertex(int v, int stripFlag) { + getVertexIndices(v, ia, vi) ; + int r = meshBuffer.getMeshReference(vi.pi) ; + + if ((r == meshBuffer.NOT_FOUND) || + (vertexNormals && noMeshNormalSubstitution && + (vi.ni != meshBuffer.getNormalIndex(r)))) { + + Point3f p = positions[vi.pi] ; + Vector3f n = vertexNormals? normals[vi.ni] : null ; + Object c = vertexColor3? (Object)colors3[vi.ci] : + vertexColor4? (Object)colors4[vi.ci] : null ; + + addVertex(p, n, c, stripFlag, MESH_PUSH) ; + meshBuffer.push(vi.pi, vi.ci, vi.ni) ; + } + else { + if (vertexNormals && !noMeshNormalSubstitution && + vi.ni != meshBuffer.getNormalIndex(r)) + addNormal(normals[vi.ni]) ; + + if (vertexColor3 && vi.ci != meshBuffer.getColorIndex(r)) + addColor(colors3[vi.ci]) ; + + else if (vertexColor4 && vi.ci != meshBuffer.getColorIndex(r)) + addColor(colors4[vi.ci]) ; + + addMeshReference(stripFlag, r) ; + } + } + } + + // + // NOTE: For now, copies are made of all GeometryArray vertex components + // even when by-reference access is available. + // + private static class VertexCopy { + Object c = null ; + Point3f p = null ; + Vector3f n = null ; + Color3f c3 = null ; + Color4f c4 = null ; + } + + private void processVertexCopy(VertexCopy vc, int stripFlag) { + int r = meshBuffer.getMeshReference(vc.p) ; + + if ((r == meshBuffer.NOT_FOUND) || + (vertexNormals && noMeshNormalSubstitution && + (! vc.n.equals(meshBuffer.getNormal(r))))) { + + addVertex(vc.p, vc.n, vc.c, stripFlag, MESH_PUSH) ; + meshBuffer.push(vc.p, vc.c, vc.n) ; + } + else { + if (vertexNormals && !noMeshNormalSubstitution && + (! vc.n.equals(meshBuffer.getNormal(r)))) + addNormal(vc.n) ; + + if (vertexColor3 && (! vc.c3.equals(meshBuffer.getColor3(r)))) + addColor(vc.c3) ; + + else if (vertexColor4 && (! vc.c4.equals(meshBuffer.getColor4(r)))) + addColor(vc.c4) ; + + addMeshReference(stripFlag, r) ; + } + } + + private void processIndexedVertexCopy(VertexCopy vc, + VertexIndices vi, + int stripFlag) { + + int r = meshBuffer.getMeshReference(vi.pi) ; + + if ((r == meshBuffer.NOT_FOUND) || + (vertexNormals && noMeshNormalSubstitution && + (vi.ni != meshBuffer.getNormalIndex(r)))) { + + addVertex(vc.p, vc.n, vc.c, stripFlag, MESH_PUSH) ; + meshBuffer.push(vi.pi, vi.ci, vi.ni) ; + } + else { + if (vertexNormals && !noMeshNormalSubstitution && + vi.ni != meshBuffer.getNormalIndex(r)) + addNormal(vc.n) ; + + if (vertexColor3 && vi.ci != meshBuffer.getColorIndex(r)) + addColor(vc.c3) ; + + else if (vertexColor4 && vi.ci != meshBuffer.getColorIndex(r)) + addColor(vc.c4) ; + + addMeshReference(stripFlag, r) ; + } + } + + /** + * This abstract class implements the GeometryAccessor interface for + * concrete subclasses which handle float and NIO interleaved geometry + * arrays. + */ + private abstract class InterleavedGeometry implements GeometryAccessor { + VertexCopy vc = new VertexCopy() ; + + int vstride = 0 ; + int coffset = 0 ; + int noffset = 0 ; + int poffset = 0 ; + int tstride = 0 ; + int tcount = 0 ; + + InterleavedGeometry(GeometryArray ga) { + if (vertexTextures) { + if (vertexTexture2) tstride = 2 ; + else if (vertexTexture3) tstride = 3 ; + else if (vertexTexture4) tstride = 4 ; + + tcount = ga.getTexCoordSetCount() ; + vstride += tcount * tstride ; + } + + if (vertexColors) { + coffset = vstride ; + if (vertexColor3) vstride += 3 ; + else vstride += 4 ; + } + + if (vertexNormals) { + noffset = vstride ; + vstride += 3 ; + } + + poffset = vstride ; + vstride += 3 ; + } + + abstract void copyVertex(int pi, int ni, int ci, VertexCopy vc) ; + + public void processVertex(int v, int stripFlag) { + copyVertex(v, v, v, vc) ; + processVertexCopy(vc, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for float + * interleaved geometry arrays. + */ + private class InterleavedGeometryFloat extends InterleavedGeometry { + float[] vdata = null ; + + InterleavedGeometryFloat(GeometryArray ga) { + super(ga) ; + vdata = ga.getInterleavedVertices() ; + } + + void copyVertex(int pi, int ni, int ci, VertexCopy vc) { + int voffset ; + voffset = pi * vstride ; + vc.p = new Point3f(vdata[voffset + poffset + 0], + vdata[voffset + poffset + 1], + vdata[voffset + poffset + 2]) ; + + if (vertexNormals) { + voffset = ni * vstride ; + vc.n = new Vector3f(vdata[voffset + noffset + 0], + vdata[voffset + noffset + 1], + vdata[voffset + noffset + 2]) ; + } + if (vertexColor3) { + voffset = ci * vstride ; + vc.c3 = new Color3f(vdata[voffset + coffset + 0], + vdata[voffset + coffset + 1], + vdata[voffset + coffset + 2]) ; + vc.c = vc.c3 ; + } + else if (vertexColor4) { + voffset = ci * vstride ; + vc.c4 = new Color4f(vdata[voffset + coffset + 0], + vdata[voffset + coffset + 1], + vdata[voffset + coffset + 2], + vdata[voffset + coffset + 3]) ; + vc.c = vc.c4 ; + } + } + } + + /** + * This class implements the GeometryAccessor interface for indexed + * interleaved geometry arrays. + */ + private class IndexedInterleavedGeometryFloat + extends InterleavedGeometryFloat { + + IndexArrays ia = new IndexArrays() ; + VertexIndices vi = new VertexIndices() ; + + IndexedInterleavedGeometryFloat(GeometryArray ga) { + super(ga) ; + getIndexArrays(ga, ia) ; + } + + public void processVertex(int v, int stripFlag) { + getVertexIndices(v, ia, vi) ; + copyVertex(vi.pi, vi.ni, vi.ci, vc) ; + processIndexedVertexCopy(vc, vi, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for + * interleaved NIO geometry arrays. + */ + private class InterleavedGeometryNIO extends InterleavedGeometry { + FloatBufferWrapper fbw = null ; + + InterleavedGeometryNIO(GeometryArray ga) { + super(ga) ; + J3DBuffer buffer = ga.getInterleavedVertexBuffer() ; + if (BufferWrapper.getBufferType(buffer) == + BufferWrapper.TYPE_FLOAT) { + fbw = new FloatBufferWrapper(buffer) ; + } + else { + throw new IllegalArgumentException + ("\ninterleaved vertex buffer must be FloatBuffer") ; + } + } + + void copyVertex(int pi, int ni, int ci, VertexCopy vc) { + int voffset ; + voffset = pi * vstride ; + vc.p = new Point3f(fbw.get(voffset + poffset + 0), + fbw.get(voffset + poffset + 1), + fbw.get(voffset + poffset + 2)) ; + + if (vertexNormals) { + voffset = ni * vstride ; + vc.n = new Vector3f(fbw.get(voffset + noffset + 0), + fbw.get(voffset + noffset + 1), + fbw.get(voffset + noffset + 2)) ; + } + if (vertexColor3) { + voffset = ci * vstride ; + vc.c3 = new Color3f(fbw.get(voffset + coffset + 0), + fbw.get(voffset + coffset + 1), + fbw.get(voffset + coffset + 2)) ; + vc.c = vc.c3 ; + } + else if (vertexColor4) { + voffset = ci * vstride ; + vc.c4 = new Color4f(fbw.get(voffset + coffset + 0), + fbw.get(voffset + coffset + 1), + fbw.get(voffset + coffset + 2), + fbw.get(voffset + coffset + 3)) ; + vc.c = vc.c4 ; + } + } + } + + /** + * This class implements the GeometryAccessor interface for indexed + * interleaved NIO geometry arrays. + */ + private class IndexedInterleavedGeometryNIO extends InterleavedGeometryNIO { + IndexArrays ia = new IndexArrays() ; + VertexIndices vi = new VertexIndices() ; + + IndexedInterleavedGeometryNIO(GeometryArray ga) { + super(ga) ; + getIndexArrays(ga, ia) ; + } + + public void processVertex(int v, int stripFlag) { + getVertexIndices(v, ia, vi) ; + copyVertex(vi.pi, vi.ni, vi.ci, vc) ; + processIndexedVertexCopy(vc, vi, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for + * non-interleaved geometry arrays accessed with by-reference semantics. + */ + private class ByRefGeometry implements GeometryAccessor { + VertexCopy vc = new VertexCopy() ; + + byte[] colorsB = null ; + float[] colorsF = null ; + float[] normals = null ; + float[] positionsF = null ; + double[] positionsD = null ; + + int initialPositionIndex = 0 ; + int initialNormalIndex = 0 ; + int initialColorIndex = 0 ; + + ByRefGeometry(GeometryArray ga) { + positionsF = ga.getCoordRefFloat() ; + if (debug && positionsF != null) + System.out.println("float positions") ; + + positionsD = ga.getCoordRefDouble() ; + if (debug && positionsD != null) + System.out.println("double positions") ; + + if (positionsF == null && positionsD == null) + throw new UnsupportedOperationException + ("\nby-reference access to Point3{d,f} arrays") ; + + initialPositionIndex = ga.getInitialCoordIndex() ; + + if (vertexColors) { + colorsB = ga.getColorRefByte() ; + if (debug && colorsB != null) + System.out.println("byte colors") ; + + colorsF = ga.getColorRefFloat() ; + if (debug && colorsF != null) + System.out.println("float colors") ; + + if (colorsB == null && colorsF == null) + throw new UnsupportedOperationException + ("\nby-reference access to Color{3b,3f,4b,4f} arrays") ; + + initialColorIndex = ga.getInitialColorIndex() ; + } + + if (vertexNormals) { + normals = ga.getNormalRefFloat() ; + if (debug && normals != null) + System.out.println("float normals") ; + + if (normals == null) + throw new UnsupportedOperationException + ("\nby-reference access to Normal3f array") ; + + initialNormalIndex = ga.getInitialNormalIndex() ; + } + } + + void copyVertex(int pi, int ni, int ci, VertexCopy vc) { + pi *= 3 ; + if (positionsF != null) { + vc.p = new Point3f(positionsF[pi + 0], + positionsF[pi + 1], + positionsF[pi + 2]) ; + } + else { + vc.p = new Point3f((float)positionsD[pi + 0], + (float)positionsD[pi + 1], + (float)positionsD[pi + 2]) ; + } + + ni *= 3 ; + if (vertexNormals) { + vc.n = new Vector3f(normals[ni + 0], + normals[ni + 1], + normals[ni + 2]) ; + } + + if (vertexColor3) { + ci *= 3 ; + if (colorsB != null) { + vc.c3 = new Color3f + ((colorsB[ci + 0] & 0xff) * ByteToFloatScale, + (colorsB[ci + 1] & 0xff) * ByteToFloatScale, + (colorsB[ci + 2] & 0xff) * ByteToFloatScale) ; + } + else { + vc.c3 = new Color3f(colorsF[ci + 0], + colorsF[ci + 1], + colorsF[ci + 2]) ; + } + vc.c = vc.c3 ; + } + else if (vertexColor4) { + ci *= 4 ; + if (colorsB != null) { + vc.c4 = new Color4f + ((colorsB[ci + 0] & 0xff) * ByteToFloatScale, + (colorsB[ci + 1] & 0xff) * ByteToFloatScale, + (colorsB[ci + 2] & 0xff) * ByteToFloatScale, + (colorsB[ci + 3] & 0xff) * ByteToFloatScale) ; + } + else { + vc.c4 = new Color4f(colorsF[ci + 0], + colorsF[ci + 1], + colorsF[ci + 2], + colorsF[ci + 3]) ; + } + vc.c = vc.c4 ; + } + } + + public void processVertex(int v, int stripFlag) { + copyVertex(v + initialPositionIndex, + v + initialNormalIndex, + v + initialColorIndex, vc) ; + + processVertexCopy(vc, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for indexed + * non-interleaved geometry arrays accessed with by-reference semantics. + */ + private class IndexedByRefGeometry extends ByRefGeometry { + IndexArrays ia = new IndexArrays() ; + VertexIndices vi = new VertexIndices() ; + + IndexedByRefGeometry(GeometryArray ga) { + super(ga) ; + getIndexArrays(ga, ia) ; + } + + public void processVertex(int v, int stripFlag) { + getVertexIndices(v, ia, vi) ; + copyVertex(vi.pi, vi.ni, vi.ci, vc) ; + processIndexedVertexCopy(vc, vi, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for + * non-interleaved geometry arrays accessed with NIO. + */ + private class ByRefGeometryNIO implements GeometryAccessor { + VertexCopy vc = new VertexCopy() ; + + ByteBufferWrapper colorsB = null ; + FloatBufferWrapper colorsF = null ; + FloatBufferWrapper normals = null ; + FloatBufferWrapper positionsF = null ; + DoubleBufferWrapper positionsD = null ; + + int initialPositionIndex = 0 ; + int initialNormalIndex = 0 ; + int initialColorIndex = 0 ; + + ByRefGeometryNIO(GeometryArray ga) { + J3DBuffer buffer ; + buffer = ga.getCoordRefBuffer() ; + initialPositionIndex = ga.getInitialCoordIndex() ; + + switch (BufferWrapper.getBufferType(buffer)) { + case BufferWrapper.TYPE_FLOAT: + positionsF = new FloatBufferWrapper(buffer) ; + if (debug) System.out.println("float positions buffer") ; + break ; + case BufferWrapper.TYPE_DOUBLE: + positionsD = new DoubleBufferWrapper(buffer) ; + if (debug) System.out.println("double positions buffer") ; + break ; + default: + throw new IllegalArgumentException + ("\nposition buffer must be FloatBuffer or DoubleBuffer") ; + } + + if (vertexColors) { + buffer = ga.getColorRefBuffer() ; + initialColorIndex = ga.getInitialColorIndex() ; + + switch (BufferWrapper.getBufferType(buffer)) { + case BufferWrapper.TYPE_BYTE: + colorsB = new ByteBufferWrapper(buffer) ; + if (debug) System.out.println("byte colors buffer") ; + break ; + case BufferWrapper.TYPE_FLOAT: + colorsF = new FloatBufferWrapper(buffer) ; + if (debug) System.out.println("float colors buffer") ; + break ; + default: + throw new IllegalArgumentException + ("\ncolor buffer must be ByteBuffer or FloatBuffer") ; + } + } + + if (vertexNormals) { + buffer = ga.getNormalRefBuffer() ; + initialNormalIndex = ga.getInitialNormalIndex() ; + + switch (BufferWrapper.getBufferType(buffer)) { + case BufferWrapper.TYPE_FLOAT: + normals = new FloatBufferWrapper(buffer) ; + if (debug) System.out.println("float normals buffer") ; + break ; + default: + throw new IllegalArgumentException + ("\nnormal buffer must be FloatBuffer") ; + } + } + } + + void copyVertex(int pi, int ni, int ci, VertexCopy vc) { + pi *= 3 ; + if (positionsF != null) { + vc.p = new Point3f(positionsF.get(pi + 0), + positionsF.get(pi + 1), + positionsF.get(pi + 2)) ; + } + else { + vc.p = new Point3f((float)positionsD.get(pi + 0), + (float)positionsD.get(pi + 1), + (float)positionsD.get(pi + 2)) ; + } + + ni *= 3 ; + if (vertexNormals) { + vc.n = new Vector3f(normals.get(ni + 0), + normals.get(ni + 1), + normals.get(ni + 2)) ; + } + + if (vertexColor3) { + ci *= 3 ; + if (colorsB != null) { + vc.c3 = new Color3f + ((colorsB.get(ci + 0) & 0xff) * ByteToFloatScale, + (colorsB.get(ci + 1) & 0xff) * ByteToFloatScale, + (colorsB.get(ci + 2) & 0xff) * ByteToFloatScale) ; + } + else { + vc.c3 = new Color3f(colorsF.get(ci + 0), + colorsF.get(ci + 1), + colorsF.get(ci + 2)) ; + } + vc.c = vc.c3 ; + } + else if (vertexColor4) { + ci *= 4 ; + if (colorsB != null) { + vc.c4 = new Color4f + ((colorsB.get(ci + 0) & 0xff) * ByteToFloatScale, + (colorsB.get(ci + 1) & 0xff) * ByteToFloatScale, + (colorsB.get(ci + 2) & 0xff) * ByteToFloatScale, + (colorsB.get(ci + 3) & 0xff) * ByteToFloatScale) ; + } + else { + vc.c4 = new Color4f(colorsF.get(ci + 0), + colorsF.get(ci + 1), + colorsF.get(ci + 2), + colorsF.get(ci + 3)) ; + } + vc.c = vc.c4 ; + } + } + + public void processVertex(int v, int stripFlag) { + copyVertex(v + initialPositionIndex, + v + initialNormalIndex, + v + initialColorIndex, vc) ; + + processVertexCopy(vc, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for + * non-interleaved indexed geometry arrays accessed with NIO. + */ + private class IndexedByRefGeometryNIO extends ByRefGeometryNIO { + IndexArrays ia = new IndexArrays() ; + VertexIndices vi = new VertexIndices() ; + + IndexedByRefGeometryNIO(GeometryArray ga) { + super(ga) ; + getIndexArrays(ga, ia) ; + } + + public void processVertex(int v, int stripFlag) { + getVertexIndices(v, ia, vi) ; + copyVertex(vi.pi, vi.ni, vi.ci, vc) ; + processIndexedVertexCopy(vc, vi, stripFlag) ; + } + } + + /** + * Convert a GeometryArray to compression stream elements and add them to + * this stream. + * + * @param ga GeometryArray to convert + * @exception IllegalArgumentException if GeometryArray has a + * dimensionality or vertex format inconsistent with the CompressionStream + */ + void addGeometryArray(GeometryArray ga) { + int firstVertex = 0 ; + int validVertexCount = 0 ; + int vertexFormat = ga.getVertexFormat() ; + GeometryAccessor geometryAccessor = null ; + + if (streamType != getStreamType(ga)) + throw new IllegalArgumentException + ("GeometryArray has inconsistent dimensionality") ; + + if (vertexComponents != getVertexComponents(vertexFormat)) + throw new IllegalArgumentException + ("GeometryArray has inconsistent vertex components") ; + + // Set up for vertex data access semantics. + boolean NIO = (vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0 ; + boolean byRef = (vertexFormat & GeometryArray.BY_REFERENCE) != 0 ; + boolean interleaved = (vertexFormat & GeometryArray.INTERLEAVED) != 0 ; + boolean indexedGeometry = ga instanceof IndexedGeometryArray ; + + if (indexedGeometry) { + if (debug) System.out.println("indexed") ; + // Index arrays will be copied such that valid indices start at + // offset 0 in the copied arrays. + firstVertex = 0 ; + validVertexCount = ((IndexedGeometryArray)ga).getValidIndexCount() ; + } + + if (!byRef) { + if (debug) System.out.println("by-copy") ; + if (indexedGeometry) { + geometryAccessor = new IndexedByCopyGeometry(ga) ; + } + else { + firstVertex = 0 ; + validVertexCount = ga.getValidVertexCount() ; + geometryAccessor = new ByCopyGeometry(ga) ; + } + } + else if (interleaved && NIO) { + if (debug) System.out.println("interleaved NIO") ; + if (indexedGeometry) { + geometryAccessor = new IndexedInterleavedGeometryNIO(ga) ; + } + else { + firstVertex = ga.getInitialVertexIndex() ; + validVertexCount = ga.getValidVertexCount() ; + geometryAccessor = new InterleavedGeometryNIO(ga) ; + } + } + else if (interleaved && !NIO) { + if (debug) System.out.println("interleaved") ; + if (indexedGeometry) { + geometryAccessor = new IndexedInterleavedGeometryFloat(ga) ; + } + else { + firstVertex = ga.getInitialVertexIndex() ; + validVertexCount = ga.getValidVertexCount() ; + geometryAccessor = new InterleavedGeometryFloat(ga) ; + } + } + else if (!interleaved && NIO) { + if (debug) System.out.println("non-interleaved NIO") ; + if (indexedGeometry) { + geometryAccessor = new IndexedByRefGeometryNIO(ga) ; + } + else { + firstVertex = 0 ; + validVertexCount = ga.getValidVertexCount() ; + geometryAccessor = new ByRefGeometryNIO(ga) ; + } + } + else if (!interleaved && !NIO) { + if (debug) System.out.println("non-interleaved by-ref") ; + if (indexedGeometry) { + geometryAccessor = new IndexedByRefGeometry(ga) ; + } + else { + firstVertex = 0 ; + validVertexCount = ga.getValidVertexCount() ; + geometryAccessor = new ByRefGeometry(ga) ; + } + } + + // Set up for topology. + int stripCount = 0 ; + int stripCounts[] = null ; + int constantStripLength = 0 ; + int replaceCode = RESTART ; + boolean strips = false ; + boolean implicitStrips = false ; + + if (ga instanceof TriangleStripArray || + ga instanceof IndexedTriangleStripArray || + ga instanceof LineStripArray || + ga instanceof IndexedLineStripArray) { + + strips = true ; + replaceCode = REPLACE_OLDEST ; + if (debug) System.out.println("strips") ; + } + else if (ga instanceof TriangleFanArray || + ga instanceof IndexedTriangleFanArray) { + + strips = true ; + replaceCode = REPLACE_MIDDLE ; + if (debug) System.out.println("fans") ; + } + else if (ga instanceof QuadArray || + ga instanceof IndexedQuadArray) { + + // Handled as fan arrays with 4 vertices per fan. + implicitStrips = true ; + constantStripLength = 4 ; + replaceCode = REPLACE_MIDDLE ; + if (debug) System.out.println("quads") ; + } + + // Get strip counts. + if (strips) { + if (indexedGeometry) { + IndexedGeometryStripArray igsa ; + igsa = (IndexedGeometryStripArray)ga ; + + stripCount = igsa.getNumStrips() ; + stripCounts = new int[stripCount] ; + igsa.getStripIndexCounts(stripCounts) ; + + } else { + GeometryStripArray gsa ; + gsa = (GeometryStripArray)ga ; + + stripCount = gsa.getNumStrips() ; + stripCounts = new int[stripCount] ; + gsa.getStripVertexCounts(stripCounts) ; + } + } + + // Build the compression stream for this shape's geometry. + int v = firstVertex ; + if (strips) { + for (int i = 0 ; i < stripCount ; i++) { + geometryAccessor.processVertex(v++, RESTART) ; + for (int j = 1 ; j < stripCounts[i] ; j++) { + geometryAccessor.processVertex(v++, replaceCode) ; + } + } + } + else if (implicitStrips) { + while (v < firstVertex + validVertexCount) { + geometryAccessor.processVertex(v++, RESTART) ; + for (int j = 1 ; j < constantStripLength ; j++) { + geometryAccessor.processVertex(v++, replaceCode) ; + } + } + } + else { + while (v < firstVertex + validVertexCount) { + geometryAccessor.processVertex(v++, RESTART) ; + } + } + } + + /** + * Print the stream to standard output. + */ + void print() { + System.out.println("\nstream has " + stream.size() + " entries") ; + System.out.println("uncompressed size " + byteCount + " bytes") ; + System.out.println("upper position bound: " + mcBounds[1].toString()) ; + System.out.println("lower position bound: " + mcBounds[0].toString()) ; + System.out.println("X, Y, Z centers (" + + ((float)center[0]) + " " + + ((float)center[1]) + " " + + ((float)center[2]) + ")\n" + + "scale " + ((float)scale) + "\n") ; + + Iterator i = stream.iterator() ; + while (i.hasNext()) { + System.out.println(i.next().toString() + "\n") ; + } + } + + + //////////////////////////////////////////////////////////////////////////// + // // + // The following constructors and methods are currently the only public // + // members of this class. All other members are subject to revision. // + // // + //////////////////////////////////////////////////////////////////////////// + + /** + * Creates a CompressionStream from an array of Shape3D scene graph + * objects. These Shape3D objects may only consist of a GeometryArray + * component and an optional Appearance component. The resulting stream + * may be used as input to the GeometryCompressor methods.<p> + * + * Each Shape3D in the array must be of the same dimensionality (point, + * line, or surface) and have the same vertex format as the others. + * Texture coordinates are ignored.<p> + * + * If a color is specified in the material attributes for a Shape3D then + * that color is added to the CompressionStream as the current global + * color. Subsequent colors as well as any colors bundled with vertices + * will override it. Only the material diffuse colors are used; all other + * appearance attributes are ignored.<p> + * + * @param positionQuant + * number of bits to quantize each position's X, Y, + * and Z components, ranging from 1 to 16 + * + * @param colorQuant + * number of bits to quantize each color's R, G, B, and + * alpha components, ranging from 2 to 16 + * + * @param normalQuant + * number of bits for quantizing each normal's U and V components, ranging + * from 0 to 6 + * + * @param shapes + * an array of Shape3D scene graph objects containing + * GeometryArray objects, all with the same vertex format and + * dimensionality + * + * @exception IllegalArgumentException if any Shape3D has an inconsistent + * dimensionality or vertex format, or if any Shape3D contains a geometry + * component that is not a GeometryArray + * + * @see Shape3D + * @see GeometryArray + * @see GeometryCompressor + */ + public CompressionStream(int positionQuant, int colorQuant, + int normalQuant, Shape3D shapes[]) { + this() ; + if (debug) System.out.println("CompressionStream(Shape3D[]):") ; + + if (shapes == null) + throw new IllegalArgumentException("null Shape3D array") ; + + if (shapes.length == 0) + throw new IllegalArgumentException("zero-length Shape3D array") ; + + if (shapes[0] == null) + throw new IllegalArgumentException("Shape3D at index 0 is null") ; + + long startTime = 0 ; + if (benchmark) startTime = System.currentTimeMillis() ; + + Geometry g = shapes[0].getGeometry() ; + if (! (g instanceof GeometryArray)) + throw new IllegalArgumentException + ("Shape3D at index 0 is not a GeometryArray") ; + + GeometryArray ga = (GeometryArray)g ; + this.streamType = getStreamType(ga) ; + this.vertexComponents = getVertexComponents(ga.getVertexFormat()) ; + + // Add global quantization parameters to the start of the stream. + addPositionQuantization(positionQuant) ; + addColorQuantization(colorQuant) ; + addNormalQuantization(normalQuant) ; + + // Loop through all shapes. + for (int s = 0 ; s < shapes.length ; s++) { + if (debug) System.out.println("\nShape3D " + s + ":") ; + + g = shapes[s].getGeometry() ; + if (! (g instanceof GeometryArray)) + throw new IllegalArgumentException + ("Shape3D at index " + s + " is not a GeometryArray") ; + + // Check for material color and add it to the stream if it exists. + Appearance a = shapes[s].getAppearance() ; + if (a != null) { + Material m = a.getMaterial() ; + if (m != null) { + m.getDiffuseColor(c3f) ; + if (vertexColor4) { + c4f.set(c3f.x, c3f.y, c3f.z, 1.0f) ; + addColor(c4f) ; + } else + addColor(c3f) ; + } + } + + // Add the geometry array to the stream. + addGeometryArray((GeometryArray)g) ; + } + + if (benchmark) { + long t = System.currentTimeMillis() - startTime ; + System.out.println + ("\nCompressionStream:\n" + shapes.length + " shapes in " + + (t / 1000f) + " sec") ; + } + } + + /** + * Creates a CompressionStream from an array of Shape3D scene graph + * objects. These Shape3D objects may only consist of a GeometryArray + * component and an optional Appearance component. The resulting stream + * may be used as input to the GeometryCompressor methods.<p> + * + * Each Shape3D in the array must be of the same dimensionality (point, + * line, or surface) and have the same vertex format as the others. + * Texture coordinates are ignored.<p> + * + * If a color is specified in the material attributes for a Shape3D then + * that color is added to the CompressionStream as the current global + * color. Subsequent colors as well as any colors bundled with vertices + * will override it. Only the material diffuse colors are used; all other + * appearance attributes are ignored.<p> + * + * Defaults of 16, 9, and 6 bits are used as the quantization values for + * positions, colors, and normals respectively. These are the maximum + * resolution values defined for positions and normals; the default of 9 + * for color is the equivalent of the 8 bits of RGBA component resolution + * commonly available in graphics frame buffers.<p> + * + * @param shapes + * an array of Shape3D scene graph objects containing + * GeometryArray objects, all with the same vertex format and + * dimensionality. + * + * @exception IllegalArgumentException if any Shape3D has an inconsistent + * dimensionality or vertex format, or if any Shape3D contains a geometry + * component that is not a GeometryArray + * + * @see Shape3D + * @see GeometryArray + * @see GeometryCompressor + */ + public CompressionStream(Shape3D shapes[]) { + this(16, 9, 6, shapes) ; + } + + /** + * Creates a CompressionStream from an array of GeometryInfo objects. The + * resulting stream may be used as input to the GeometryCompressor + * methods.<p> + * + * Each GeometryInfo in the array must be of the same dimensionality + * (point, line, or surface) and have the same vertex format as the + * others. Texture coordinates are ignored.<p> + * + * @param positionQuant + * number of bits to quantize each position's X, Y, + * and Z components, ranging from 1 to 16 + * + * @param colorQuant + * number of bits to quantize each color's R, G, B, and + * alpha components, ranging from 2 to 16 + * + * @param normalQuant + * number of bits for quantizing each normal's U and V components, ranging + * from 0 to 6 + * + * @param geometry + * an array of GeometryInfo objects, all with the same + * vertex format and dimensionality + * + * @exception IllegalArgumentException if any GeometryInfo object has an + * inconsistent dimensionality or vertex format + * + * @see GeometryInfo + * @see GeometryCompressor + */ + public CompressionStream(int positionQuant, int colorQuant, + int normalQuant, GeometryInfo geometry[]) { + this() ; + if (debug) System.out.println("CompressionStream(GeometryInfo[])") ; + + if (geometry == null) + throw new IllegalArgumentException("null GeometryInfo array") ; + + if (geometry.length == 0) + throw new IllegalArgumentException + ("zero-length GeometryInfo array") ; + + if (geometry[0] == null) + throw new IllegalArgumentException + ("GeometryInfo at index 0 is null") ; + + long startTime = 0 ; + if (benchmark) startTime = System.currentTimeMillis() ; + + GeometryArray ga = geometry[0].getGeometryArray() ; + this.streamType = getStreamType(ga) ; + this.vertexComponents = getVertexComponents(ga.getVertexFormat()) ; + + // Add global quantization parameters to the start of the stream. + addPositionQuantization(positionQuant) ; + addColorQuantization(colorQuant) ; + addNormalQuantization(normalQuant) ; + + // Loop through all GeometryInfo objects and add them to the stream. + for (int i = 0 ; i < geometry.length ; i++) { + if (debug) System.out.println("\nGeometryInfo " + i + ":") ; + addGeometryArray(geometry[i].getGeometryArray()) ; + } + + if (benchmark) { + long t = System.currentTimeMillis() - startTime ; + System.out.println + ("\nCompressionStream:\n" + geometry.length + + " GeometryInfo objects in " + (t / 1000f) + " sec") ; + } + } + + /** + * Creates a CompressionStream from an array of GeometryInfo objects. The + * resulting stream may be used as input to the GeometryCompressor + * methods.<p> + * + * Each GeometryInfo in the array must be of the same dimensionality + * (point, line, or surface) and have the same vertex format as the + * others. Texture coordinates are ignored.<p> + * + * Defaults of 16, 9, and 6 bits are used as the quantization values for + * positions, colors, and normals respectively. These are the maximum + * resolution values defined for positions and normals; the default of 9 + * for color is the equivalent of the 8 bits of RGBA component resolution + * commonly available in graphics frame buffers.<p> + * + * @param geometry + * an array of GeometryInfo objects, all with the same + * vertex format and dimensionality + * + * @exception IllegalArgumentException if any GeometryInfo object has an + * inconsistent dimensionality or vertex format + * + * @see GeometryInfo + * @see GeometryCompressor + */ + public CompressionStream(GeometryInfo geometry[]) { + this(16, 9, 6, geometry) ; + } + + /** + * Get the original bounds of the coordinate data, in modeling coordinates. + * Coordinate data is positioned and scaled to a normalized cube after + * compression. + * + * @return Point3d array of length 2, where the 1st Point3d is the lower + * bounds and the 2nd Point3d is the upper bounds. + * @since Java 3D 1.3 + */ + public Point3d[] getModelBounds() { + Point3d[] bounds = new Point3d[2] ; + bounds[0] = new Point3d(mcBounds[0]) ; + bounds[1] = new Point3d(mcBounds[1]) ; + return bounds ; + } + + /** + * Get the bounds of the compressed object in normalized coordinates. + * These have an maximum bounds by [-1.0 .. +1.0] across each axis. + * + * @return Point3d array of length 2, where the 1st Point3d is the lower + * bounds and the 2nd Point3d is the upper bounds. + * @since Java 3D 1.3 + */ + public Point3d[] getNormalizedBounds() { + Point3d[] bounds = new Point3d[2] ; + bounds[0] = new Point3d(ncBounds[0]) ; + bounds[1] = new Point3d(ncBounds[1]) ; + return bounds ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamColor.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamColor.java new file mode 100644 index 0000000..d8aef35 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamColor.java @@ -0,0 +1,272 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import javax.vecmath.Color3f; +import javax.vecmath.Color4f; + +/** + * This class represents a color in a compression stream. It maintains both + * floating-point and quantized representations. This color may be bundled + * with a vertex or exist separately as a global color. + */ +class CompressionStreamColor extends CompressionStreamElement { + private int R, G, B, A ; + private boolean color3 ; + private boolean color4 ; + private float colorR, colorG, colorB, colorA ; + + int rAbsolute, gAbsolute, bAbsolute, aAbsolute ; + + /** + * Create a CompressionStreamColor. + * + * @param stream CompressionStream associated with this element + * @param color3 floating-point representation to be encoded + */ + CompressionStreamColor(CompressionStream stream, Color3f c3) { + this.color4 = false ; + this.color3 = true ; + colorR = c3.x ; + colorG = c3.y ; + colorB = c3.z ; + colorA = 0.0f ; + stream.byteCount += 12 ; + } + + /** + * Create a CompressionStreamColor. + * + * @param stream CompressionStream associated with this element + * @param color4 floating-point representation to be encoded + */ + CompressionStreamColor(CompressionStream stream, Color4f c4) { + this.color3 = false ; + this.color4 = true ; + colorR = c4.x ; + colorG = c4.y ; + colorB = c4.z ; + colorA = c4.w ; + stream.byteCount += 16 ; + } + + /** + * Quantize a floating point color to fixed point integer components of + * the specified number of bits. The bit length can range from a maximum + * of 16 to a minimum of 2 bits since negative colors are not defined.<p> + * + * The bit length is the total number of bits in the signed version of the + * fixed point representation of the input color, which is assumed to + * be normalized into the [0..1) range. With the maximum bit length of + * 16, 15 bits of positive colors can be represented; a bit length of 9 is + * needed to get the 8 bit positive color size in common use.<p> + * + * @param stream CompressionStream associated with this element + * @param table HuffmanTable for collecting data about the quantized + * representation of this element + */ + void quantize(CompressionStream stream, HuffmanTable huffmanTable) { + // Clamp quantization. + int quant = + (stream.colorQuant < 2? 2 : + (stream.colorQuant > 16? 16 : stream.colorQuant)) ; + + absolute = false ; + if (stream.firstColor || stream.colorQuantChanged) { + absolute = true ; + stream.lastColor[0] = 0 ; + stream.lastColor[1] = 0 ; + stream.lastColor[2] = 0 ; + stream.lastColor[3] = 0 ; + stream.firstColor = false ; + stream.colorQuantChanged = false ; + } + + // Convert the floating point position to s.15 2's complement. + if (color3) { + R = (int)(colorR * 32768.0) ; + G = (int)(colorG * 32768.0) ; + B = (int)(colorB * 32768.0) ; + A = 0 ; + } else if (color4) { + R = (int)(colorR * 32768.0) ; + G = (int)(colorG * 32768.0) ; + B = (int)(colorB * 32768.0) ; + A = (int)(colorA * 32768.0) ; + } + + // Clamp color components. + R = (R > 32767? 32767: (R < 0? 0: R)) ; + G = (G > 32767? 32767: (G < 0? 0: G)) ; + B = (B > 32767? 32767: (B < 0? 0: B)) ; + A = (A > 32767? 32767: (A < 0? 0: A)) ; + + // Compute quantized values. + R &= quantizationMask[quant] ; + G &= quantizationMask[quant] ; + B &= quantizationMask[quant] ; + A &= quantizationMask[quant] ; + + // Copy and retain absolute color for mesh buffer lookup. + rAbsolute = R ; + gAbsolute = G ; + bAbsolute = B ; + aAbsolute = A ; + + // Compute deltas. + R -= stream.lastColor[0] ; + G -= stream.lastColor[1] ; + B -= stream.lastColor[2] ; + A -= stream.lastColor[3] ; + + // Update last values. + stream.lastColor[0] += R ; + stream.lastColor[1] += G ; + stream.lastColor[2] += B ; + stream.lastColor[3] += A ; + + // Compute length and shift common to all components. + if (color3) + computeLengthShift(R, G, B) ; + + else if (color4) + computeLengthShift(R, G, B, A) ; + + // 0-length components are allowed only for normals. + if (length == 0) + length = 1 ; + + // Add this element to the Huffman table associated with this stream. + huffmanTable.addColorEntry(length, shift, absolute) ; + } + + /** + * Output a setColor command. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputCommand(HuffmanTable table, CommandStream output) { + outputColor(table, output, CommandStream.SET_COLOR, 8) ; + } + + /** + * Output a color subcommand. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputSubcommand(HuffmanTable table, CommandStream output) { + + outputColor(table, output, 0, 6) ; + } + + // + // Output the final compressed bits to the output command stream. + // + private void outputColor(HuffmanTable table, CommandStream output, + int header, int headerLength) { + HuffmanNode t ; + + // Look up the Huffman token for this compression stream element. + t = table.getColorEntry(length, shift, absolute) ; + + // Construct the color subcommand components. The maximum length of a + // color subcommand is 70 bits (a tag with a length of 6 followed by 4 + // components of 16 bits each). The subcommand is therefore + // constructed initially using just the first 3 components, with the + // 4th component added later after the tag has been shifted into the + // subcommand header. + int componentLength = t.dataLength - t.shift ; + int subcommandLength = t.tagLength + (3 * componentLength) ; + + R = (R >> t.shift) & (int)lengthMask[componentLength] ; + G = (G >> t.shift) & (int)lengthMask[componentLength] ; + B = (B >> t.shift) & (int)lengthMask[componentLength] ; + + long colorSubcommand = + (((long)t.tag) << (3 * componentLength)) | + (((long)R) << (2 * componentLength)) | + (((long)G) << (1 * componentLength)) | + (((long)B) << (0 * componentLength)) ; + + if (subcommandLength < 6) { + // The header will have some empty bits. The Huffman tag + // computation will prevent this if necessary. + header |= (int)(colorSubcommand << (6 - subcommandLength)) ; + subcommandLength = 0 ; + } + else { + // Move the 1st 6 bits of the subcommand into the header. + header |= (int)(colorSubcommand >>> (subcommandLength - 6)) ; + subcommandLength -= 6 ; + } + + // Add alpha if present. + if (color4) { + A = (A >> t.shift) & (int)lengthMask[componentLength] ; + colorSubcommand = (colorSubcommand << componentLength) | A ; + subcommandLength += componentLength ; + } + + // Add the header and body to the output buffer. + output.addCommand(header, headerLength, + colorSubcommand, subcommandLength) ; + } + + public String toString() { + String d = absolute? "" : "delta " ; + String c = (colorR + " " + colorG + " " + colorB + + (color4? (" " + colorA): "")) ; + + return + "color: " + c + "\n" + + " fixed point " + d + + R + " " + G + " " + B + "\n" + + " length " + length + " shift " + shift + + (absolute? " absolute" : " relative") ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamElement.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamElement.java new file mode 100644 index 0000000..7b66765 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamElement.java @@ -0,0 +1,359 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +/** + * Instances of this class are used as elements in a CompressionStream. + * @see CompressionStream + */ +abstract class CompressionStreamElement { + /** + * Bit length of quantized geometric components. + */ + int length ; + + /** + * Number of trailing zeros in quantized geometric components. + */ + int shift ; + + /** + * If false, geometric component values are represented as differences + * from those of the preceding element in the stream. + */ + boolean absolute ; + + /** + * Array with elements that can be used as masks to apply a quantization + * to the number of bits indicated by the referencing index [0..16]. + */ + static final int quantizationMask[] = { + 0xFFFF0000, 0xFFFF8000, 0xFFFFC000, 0xFFFFE000, + 0xFFFFF000, 0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00, + 0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0, 0xFFFFFFE0, + 0xFFFFFFF0, 0xFFFFFFF8, 0xFFFFFFFC, 0xFFFFFFFE, + 0xFFFFFFFF + } ; + + /** + * Array with elements that can be used as masks to retain the number of + * trailing bits of data indicated by the referencing index [0..64]. Used + * to clear the leading sign bits of fixed-point 2's complement numbers + * and in building the compressed output stream. + */ + static final long lengthMask[] = { + 0x0000000000000000L, 0x0000000000000001L, + 0x0000000000000003L, 0x0000000000000007L, + 0x000000000000000FL, 0x000000000000001FL, + 0x000000000000003FL, 0x000000000000007FL, + 0x00000000000000FFL, 0x00000000000001FFL, + 0x00000000000003FFL, 0x00000000000007FFL, + 0x0000000000000FFFL, 0x0000000000001FFFL, + 0x0000000000003FFFL, 0x0000000000007FFFL, + 0x000000000000FFFFL, 0x000000000001FFFFL, + 0x000000000003FFFFL, 0x000000000007FFFFL, + 0x00000000000FFFFFL, 0x00000000001FFFFFL, + 0x00000000003FFFFFL, 0x00000000007FFFFFL, + 0x0000000000FFFFFFL, 0x0000000001FFFFFFL, + 0x0000000003FFFFFFL, 0x0000000007FFFFFFL, + 0x000000000FFFFFFFL, 0x000000001FFFFFFFL, + 0x000000003FFFFFFFL, 0x000000007FFFFFFFL, + 0x00000000FFFFFFFFL, 0x00000001FFFFFFFFL, + 0x00000003FFFFFFFFL, 0x00000007FFFFFFFFL, + 0x0000000FFFFFFFFFL, 0x0000001FFFFFFFFFL, + 0x0000003FFFFFFFFFL, 0x0000007FFFFFFFFFL, + 0x000000FFFFFFFFFFL, 0x000001FFFFFFFFFFL, + 0x000003FFFFFFFFFFL, 0x000007FFFFFFFFFFL, + 0x00000FFFFFFFFFFFL, 0x00001FFFFFFFFFFFL, + 0x00003FFFFFFFFFFFL, 0x00007FFFFFFFFFFFL, + 0x0000FFFFFFFFFFFFL, 0x0001FFFFFFFFFFFFL, + 0x0003FFFFFFFFFFFFL, 0x0007FFFFFFFFFFFFL, + 0x000FFFFFFFFFFFFFL, 0x001FFFFFFFFFFFFFL, + 0x003FFFFFFFFFFFFFL, 0x007FFFFFFFFFFFFFL, + 0x00FFFFFFFFFFFFFFL, 0x01FFFFFFFFFFFFFFL, + 0x03FFFFFFFFFFFFFFL, 0x07FFFFFFFFFFFFFFL, + 0x0FFFFFFFFFFFFFFFL, 0x1FFFFFFFFFFFFFFFL, + 0x3FFFFFFFFFFFFFFFL, 0x7FFFFFFFFFFFFFFFL, + 0xFFFFFFFFFFFFFFFFL + } ; + + + /** + * Computes the quantized representation of this stream element. + * + * @param stream CompressionStream associated with this element + * @param table HuffmanTable for collecting data about the quantized + * representation of this element + */ + abstract void quantize(CompressionStream stream, HuffmanTable table) ; + + /** + * Outputs the compressed bits representing this stream element. + * Some instances of CompressionStreamElement don't require an + * implementation and will inherit the stub provided here. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputCommand(HuffmanTable table, CommandStream output) { + } + + /** + * Finds the minimum bits needed to represent the given 16-bit signed 2's + * complement integer. For positive integers, this include the first + * 1 starting from the left, plus a 0 sign bit; for negative integers, + * this includes the first 0 starting from the left, plus a 1 sign bit. + * 0 is a special case returning 0; however, 0-length components are valid + * ONLY for normals. + * + * The decompressor uses the data length to determine how many bits of + * sign extension to add to the data coming in from the compressed stream + * in order to create a 16-bit signed 2's complement integer. E.g., a data + * length of 12 indicates that 16-12=4 bits of sign are to be extended.<p> + * + * @param number a signed 2's complement integer representable in 16 bits + * or less + * @return minimum number of bits to represent the number + */ + private static final int getLength(int number) { + if (number == 0) + return 0 ; + + else if ((number & 0x8000) > 0) { + // negative numbers + if ((number & 0x4000) == 0) return 16 ; + if ((number & 0x2000) == 0) return 15 ; + if ((number & 0x1000) == 0) return 14 ; + if ((number & 0x0800) == 0) return 13 ; + if ((number & 0x0400) == 0) return 12 ; + if ((number & 0x0200) == 0) return 11 ; + if ((number & 0x0100) == 0) return 10 ; + if ((number & 0x0080) == 0) return 9 ; + if ((number & 0x0040) == 0) return 8 ; + if ((number & 0x0020) == 0) return 7 ; + if ((number & 0x0010) == 0) return 6 ; + if ((number & 0x0008) == 0) return 5 ; + if ((number & 0x0004) == 0) return 4 ; + if ((number & 0x0002) == 0) return 3 ; + if ((number & 0x0001) == 0) return 2 ; + + return 1 ; + + } else { + // positive numbers + if ((number & 0x4000) > 0) return 16 ; + if ((number & 0x2000) > 0) return 15 ; + if ((number & 0x1000) > 0) return 14 ; + if ((number & 0x0800) > 0) return 13 ; + if ((number & 0x0400) > 0) return 12 ; + if ((number & 0x0200) > 0) return 11 ; + if ((number & 0x0100) > 0) return 10 ; + if ((number & 0x0080) > 0) return 9 ; + if ((number & 0x0040) > 0) return 8 ; + if ((number & 0x0020) > 0) return 7 ; + if ((number & 0x0010) > 0) return 6 ; + if ((number & 0x0008) > 0) return 5 ; + if ((number & 0x0004) > 0) return 4 ; + if ((number & 0x0002) > 0) return 3 ; + + return 2 ; + } + } + + /** + * Finds the rightmost 1 bit in the given 16-bit integer. This value is + * used by the decompressor to indicate the number of trailing zeros to be + * added to the end of the data coming in from the compressed stream, + * accomplished by left shifting the data by the indicated amount. + * 0 is a special case returning 0.<p> + * + * @param number an integer representable in 16 bits or less + * @return number of trailing zeros + */ + private static final int getShift(int number) { + if (number == 0) return 0 ; + + if ((number & 0x0001) > 0) return 0 ; + if ((number & 0x0002) > 0) return 1 ; + if ((number & 0x0004) > 0) return 2 ; + if ((number & 0x0008) > 0) return 3 ; + if ((number & 0x0010) > 0) return 4 ; + if ((number & 0x0020) > 0) return 5 ; + if ((number & 0x0040) > 0) return 6 ; + if ((number & 0x0080) > 0) return 7 ; + if ((number & 0x0100) > 0) return 8 ; + if ((number & 0x0200) > 0) return 9 ; + if ((number & 0x0400) > 0) return 10 ; + if ((number & 0x0800) > 0) return 11 ; + if ((number & 0x1000) > 0) return 12 ; + if ((number & 0x2000) > 0) return 13 ; + if ((number & 0x4000) > 0) return 14 ; + + return 15 ; + } + + /** + * Computes common length and shift of 2 numbers. + */ + final void computeLengthShift(int n0, int n1) { + int s0 = n0 & 0x8000 ; + int s1 = n1 & 0x8000 ; + + // equal sign optimization + if (s0 == s1) + if (s0 == 0) + this.length = getLength(n0 | n1) ; + else + this.length = getLength(n0 & n1) ; + else + this.length = getMaximum(getLength(n0), getLength(n1)) ; + + this.shift = getShift(n0 | n1) ; + } + + + /** + * Computes common length and shift of 3 numbers. + */ + final void computeLengthShift(int n0, int n1, int n2) { + int s0 = n0 & 0x8000 ; + int s1 = n1 & 0x8000 ; + int s2 = n2 & 0x8000 ; + + // equal sign optimization + if (s0 == s1) + if (s1 == s2) + if (s2 == 0) + this.length = getLength(n0 | n1 | n2) ; + else + this.length = getLength(n0 & n1 & n2) ; + else + if (s1 == 0) + this.length = getMaximum(getLength(n0 | n1), + getLength(n2)) ; + else + this.length = getMaximum(getLength(n0 & n1), + getLength(n2)) ; + else + if (s1 == s2) + if (s2 == 0) + this.length = getMaximum(getLength(n1 | n2), + getLength(n0)) ; + else + this.length = getMaximum(getLength(n1 & n2), + getLength(n0)) ; + else + if (s0 == 0) + this.length = getMaximum(getLength(n0 | n2), + getLength(n1)) ; + else + this.length = getMaximum(getLength(n0 & n2), + getLength(n1)) ; + + this.shift = getShift(n0 | n1 | n2) ; + } + + + /** + * Computes common length and shift of 4 numbers. + */ + final void computeLengthShift(int n0, int n1, int n2, int n3) { + this.length = getMaximum(getLength(n0), getLength(n1), + getLength(n2), getLength(n3)) ; + + this.shift = getShift(n0 | n1 | n2 | n3) ; + } + + + /** + * Finds the maximum of two integers. + */ + private static final int getMaximum(int x, int y) { + if (x > y) + return x ; + else + return y ; + } + + /** + * Finds the maximum of three integers. + */ + private static final int getMaximum(int x, int y, int z) { + if (x > y) + if (x > z) + return x ; + else + return z ; + else + if (y > z) + return y ; + else + return z ; + } + + /** + * Finds the maximum of four integers. + */ + private static final int getMaximum(int x, int y, int z, int w) { + int n0, n1 ; + + if (x > y) + n0 = x ; + else + n0 = y ; + + if (z > w) + n1 = z ; + else + n1 = w ; + + if (n0 > n1) + return n0 ; + else + return n1 ; + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamNormal.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamNormal.java new file mode 100644 index 0000000..3a44125 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamNormal.java @@ -0,0 +1,674 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import javax.vecmath.Vector3f; + +/** + * This class represents a normal in a compression stream. It maintains both + * floating-point and quantized representations. This normal may be bundled + * with a vertex or exist separately as a global normal. + */ +class CompressionStreamNormal extends CompressionStreamElement { + private int u, v ; + private int specialOctant, specialSextant ; + private float normalX, normalY, normalZ ; + + int octant, sextant ; + boolean specialNormal ; + int uAbsolute, vAbsolute ; + + /** + * Create a CompressionStreamNormal. + * + * @param stream CompressionStream associated with this element + * @param normal floating-point representation to be encoded + */ + CompressionStreamNormal(CompressionStream stream, Vector3f normal) { + this.normalX = normal.x ; + this.normalY = normal.y ; + this.normalZ = normal.z ; + stream.byteCount += 12 ; + } + + // + // Normal Encoding Parameterization + // + // A floating point normal is quantized to a desired number of bits by + // comparing it to candidate entries in a table of every possible normal + // at that quantization and finding the closest match. This table of + // normals is indexed by the following encoding: + // + // First, points on a unit radius sphere are parameterized by two angles, + // th and psi, using usual spherical coordinates. th is the angle about + // the y axis, psi is the inclination to the plane containing the point. + // The mapping between rectangular and spherical coordinates is: + // + // x = cos(th)*cos(psi) + // y = sin(psi) + // z = sin(th)*cos(psi) + // + // Points on sphere are folded first by octant, and then by sort order + // of xyz into one of six sextants. All the table encoding takes place in + // the positive octant, in the region bounded by the half spaces: + // + // x >= z + // z >= y + // y >= 0 + // + // This triangular shaped patch runs from 0 to 45 degrees in th, and + // from 0 to as much as 0.615479709 (MAX_Y_ANG) in psi. The xyz bounds + // of the patch is: + // + // (1, 0, 0) (1/sqrt(2), 0, 1/sqrt(2)) (1/sqrt(3), 1/sqrt(3), 1/sqrt(3)) + // + // When dicing this space up into discrete points, the choice for y is + // linear quantization in psi. This means that if the y range is to be + // divided up into n segments, the angle of segment j is: + // + // psi(j) = MAX_Y_ANG*(j/n) + // + // The y height of the patch (in arc length) is *not* the same as the xz + // dimension. However, the subdivision quantization needs to treat xz and + // y equally. To achieve this, the th angles are re-parameterized as + // reflected psi angles. That is, the i-th point's th is: + // + // th(i) = asin(tan(psi(i))) = asin(tan(MAX_Y_ANG*(i/n))) + // + // To go the other direction, the angle th corresponds to the real index r + // (in the same 0-n range as i): + // + // r(th) = n*atan(sin(th))/MAX_Y_ANG + // + // Rounded to the nearest integer, this gives the closest integer index i + // to the xz angle th. Because the triangle has a straight edge on the + // line x=z, it is more intuitive to index the xz angles in reverse + // order. Thus the two equations above are replaced by: + // + // th(i) = asin(tan(psi(i))) = asin(tan(MAX_Y_ANG*((n-i)/n))) + // + // r(th) = n*(1 - atan(sin(th))/MAX_Y_ANG) + // + // Each level of quantization subdivides the triangular patch twice as + // densely. The case in which only the three vertices of the triangle are + // present is the first logical stage of representation, but because of + // how the table is encoded the first usable case starts one level of + // sub-division later. This three point level has an n of 2 by the above + // conventions. + // + private static final int MAX_UV_BITS = 6 ; + private static final int MAX_UV_ENTRIES = 64 ; + + private static final double cgNormals[][][][] = + new double[MAX_UV_BITS+1][MAX_UV_ENTRIES+1][MAX_UV_ENTRIES+1][3] ; + + private static final double MAX_Y_ANG = 0.615479709 ; + private static final double UNITY_14 = 16384.0 ; + + private static void computeNormals() { + int inx, iny, inz, n ; + double th, psi, qnx, qny, qnz ; + + for (int quant = 0 ; quant <= MAX_UV_BITS ; quant++) { + n = 1 << quant ; + + for (int j = 0 ; j <= n ; j++) { + for (int i = 0 ; i <= n ; i++) { + if (i+j > n) continue ; + + psi = MAX_Y_ANG*(j/((double) n)) ; + th = Math.asin(Math.tan(MAX_Y_ANG*((n-i)/((double) n)))) ; + + qnx = Math.cos(th)*Math.cos(psi) ; + qny = Math.sin(psi) ; + qnz = Math.sin(th)*Math.cos(psi) ; + + // The normal table uses 16-bit components and must be + // able to represent both +1.0 and -1.0, so convert the + // floating point normal components to fixed point with 14 + // fractional bits, a unity bit, and a sign bit (s1.14). + // Set them back to get the float equivalent. + qnx = qnx*UNITY_14 ; inx = (int)qnx ; + qnx = inx ; qnx = qnx/UNITY_14 ; + + qny = qny*UNITY_14 ; iny = (int)qny ; + qny = iny ; qny = qny/UNITY_14 ; + + qnz = qnz*UNITY_14 ; inz = (int)qnz ; + qnz = inz ; qnz = qnz/UNITY_14 ; + + cgNormals[quant][j][i][0] = qnx ; + cgNormals[quant][j][i][1] = qny ; + cgNormals[quant][j][i][2] = qnz ; + } + } + } + } + + // + // An inverse sine table is used for each quantization level to take the Y + // component of a normal (which is the sine of the inclination angle) and + // obtain the closest quantized Y angle. + // + // At any level of compression, there are a fixed number of different Y + // angles (between 0 and MAX_Y_ANG). The inverse table is built to have + // slightly more than twice as many entries as y angles at any particular + // level; this ensures that the inverse look-up will get within one angle + // of the right one. The size of the table should be as small as + // possible, but with its delta sine still smaller than the delta sine + // between the last two angles to be encoded. + // + // Example: the inverse sine table has a maximum angle of 0.615479709. At + // the maximum resolution of 6 bits there are 65 discrete angles used, + // but twice as many are needed for thresholding between angles, so the + // delta angle is 0.615479709/128. The difference then between the last + // two angles to be encoded is: + // sin(0.615479709*128.0/128.0) - sin(0.615479709*127.0/128.0) = 0.003932730 + // + // Using 8 significent bits below the binary point, fixed point can + // represent sines in increments of 0.003906250, just slightly smaller. + // However, because the maximum Y angle sine is 0.577350269, only 148 + // instead of 256 table entries are needed. + // + private static final short inverseSine[][] = new short[MAX_UV_BITS+1][] ; + + // UNITY_14 * sin(MAX_Y_ANGLE) + private static final short MAX_SIN_14BIT = 9459 ; + + private static void computeInverseSineTables() { + int intSin, deltaSin, intAngle ; + double floatSin, floatAngle ; + short sin14[] = new short[MAX_UV_ENTRIES+1] ; + + // Build table of sines in s1.14 fixed point for each of the + // discrete angles used at maximum resolution. + for (int i = 0 ; i <= MAX_UV_ENTRIES ; i++) { + sin14[i] = (short)(UNITY_14*Math.sin(i*MAX_Y_ANG/MAX_UV_ENTRIES)) ; + } + + for (int quant = 0 ; quant <= MAX_UV_BITS ; quant++) { + switch (quant) { + default: + case 6: + // Delta angle: MAX_Y_ANGLE/128.0 + // Bits below binary point for fixed point delta sine: 8 + // Integer delta sine: 64 + // Inverse sine table size: 148 entries + deltaSin = 1 << (14 - 8) ; + break ; + case 5: + // Delta angle: MAX_Y_ANGLE/64.0 + // Bits below binary point for fixed point delta sine: 7 + // Integer delta sine: 128 + // Inverse sine table size: 74 entries + deltaSin = 1 << (14 - 7) ; + break ; + case 4: + // Delta angle: MAX_Y_ANGLE/32.0 + // Bits below binary point for fixed point delta sine: 6 + // Integer delta sine: 256 + // Inverse sine table size: 37 entries + deltaSin = 1 << (14 - 6) ; + break ; + case 3: + // Delta angle: MAX_Y_ANGLE/16.0 + // Bits below binary point for fixed point delta sine: 5 + // Integer delta sine: 512 + // Inverse sine table size: 19 entries + deltaSin = 1 << (14 - 5) ; + break ; + case 2: + // Delta angle: MAX_Y_ANGLE/8.0 + // Bits below binary point for fixed point delta sine: 4 + // Integer delta sine: 1024 + // Inverse sine table size: 10 entries + deltaSin = 1 << (14 - 4) ; + break ; + case 1: + // Delta angle: MAX_Y_ANGLE/4.0 + // Bits below binary point for fixed point delta sine: 3 + // Integer delta sine: 2048 + // Inverse sine table size: 5 entries + deltaSin = 1 << (14 - 3) ; + break ; + case 0: + // Delta angle: MAX_Y_ANGLE/2.0 + // Bits below binary point for fixed point delta sine: 2 + // Integer delta sine: 4096 + // Inverse sine table size: 3 entries + deltaSin = 1 << (14 - 2) ; + break ; + } + + inverseSine[quant] = new short[(MAX_SIN_14BIT/deltaSin) + 1] ; + + intSin = 0 ; + for (int i = 0 ; i < inverseSine[quant].length ; i++) { + // Compute float representation of integer sine with desired + // number of fractional bits by effectively right shifting 14. + floatSin = intSin/UNITY_14 ; + + // Compute the angle with this sine value and quantize it. + floatAngle = Math.asin(floatSin) ; + intAngle = (int)((floatAngle/MAX_Y_ANG) * (1 << quant)) ; + + // Choose the closest of the three nearest quantized values + // intAngle-1, intAngle, and intAngle+1. + if (intAngle > 0) { + if (Math.abs(sin14[intAngle << (6-quant)] - intSin) > + Math.abs(sin14[(intAngle-1) << (6-quant)] - intSin)) + intAngle = intAngle-1 ; + } + + if (intAngle < (1 << quant)) { + if (Math.abs(sin14[intAngle << (6-quant)] - intSin) > + Math.abs(sin14[(intAngle+1) << (6-quant)] - intSin)) + intAngle = intAngle+1 ; + } + + inverseSine[quant][i] = (short)intAngle ; + intSin += deltaSin ; + } + } + } + + /** + * Compute static tables needed for normal quantization. + */ + static { + computeNormals() ; + computeInverseSineTables() ; + } + + /** + * Quantize the floating point normal to a 6-bit octant/sextant plus u,v + * components of [0..6] bits. Full resolution is 18 bits and the minimum + * is 6 bits. + * + * @param stream CompressionStream associated with this element + * @param table HuffmanTable for collecting data about the quantized + * representation of this element + */ + void quantize(CompressionStream stream, HuffmanTable huffmanTable) { + double nx, ny, nz, t ; + + // Clamp UV quantization. + int quant = + (stream.normalQuant < 0? 0 : + (stream.normalQuant > 6? 6 : stream.normalQuant)) ; + + nx = normalX ; + ny = normalY ; + nz = normalZ ; + + octant = 0 ; + sextant = 0 ; + u = 0 ; + v = 0 ; + + // Normalize the fixed point normal to the positive signed octant. + if (nx < 0.0) { + octant |= 4 ; + nx = -nx ; + } + if (ny < 0.0) { + octant |= 2 ; + ny = -ny ; + } + if (nz < 0.0) { + octant |= 1 ; + nz = -nz ; + } + + // Normalize the fixed point normal to the proper sextant of the octant. + if (nx < ny) { + sextant |= 1 ; + t = nx ; + nx = ny ; + ny = t ; + } + if (nz < ny) { + sextant |= 2 ; + t = ny ; + ny = nz ; + nz = t ; + } + if (nx < nz) { + sextant |= 4 ; + t = nx ; + nx = nz ; + nz = t ; + } + + // Convert the floating point y component to s1.14 fixed point. + int yInt = (int)(ny * UNITY_14) ; + + // The y component of the normal is the sine of the y angle. Quantize + // the y angle by using the fixed point y component as an index into + // the inverse sine table of the correct size for the quantization + // level. (12 - quant) bits of the s1.14 y normal component are + // rolled off with a right shift; the remaining bits then match the + // number of bits used to represent the delta sine of the table. + int yIndex = inverseSine[quant][yInt >> (12-quant)] ; + + // Search the two xz rows near y for the best match. + int ii = 0 ; + int jj = 0 ; + int n = 1 << quant ; + double dot, bestDot = -1 ; + + for (int j = yIndex-1 ; j < yIndex+1 && j <= n ; j++) { + if (j < 0) + continue ; + + for (int i = 0 ; i <= n ; i++) { + if (i+j > n) + continue ; + + dot = nx * cgNormals[quant][j][i][0] + + ny * cgNormals[quant][j][i][1] + + nz * cgNormals[quant][j][i][2] ; + + if (dot > bestDot) { + bestDot = dot ; + ii = i ; + jj = j ; + } + } + } + + // Convert u and v to standard grid form. + u = ii << (6 - quant) ; + v = jj << (6 - quant) ; + + // Check for special normals and specially encode them. + specialNormal = false ; + if (u == 64 && v == 0) { + // six coordinate axes case + if (sextant == 0 || sextant == 2) { + // +/- x-axis + specialSextant = 0x6 ; + specialOctant = ((octant & 4) != 0)? 0x2 : 0 ; + + } else if (sextant == 3 || sextant == 1) { + // +/- y-axis + specialSextant = 0x6 ; + specialOctant = 4 | (((octant & 2) != 0)? 0x2 : 0) ; + + } else if (sextant == 5 || sextant == 4) { + // +/- z-axis + specialSextant = 0x7 ; + specialOctant = ((octant & 1) != 0)? 0x2 : 0 ; + } + specialNormal = true ; + u = v = 0 ; + + } else if (u == 0 && v == 64) { + // eight mid point case + specialSextant = 6 | (octant >> 2) ; + specialOctant = ((octant & 0x3) << 1) | 1 ; + specialNormal = true ; + u = v = 0 ; + } + + // Compute deltas if possible. + // Use the non-normalized ii and jj indices. + int du = 0 ; + int dv = 0 ; + int uv64 = 64 >> (6 - quant) ; + + absolute = false ; + if (stream.firstNormal || stream.normalQuantChanged || + stream.lastSpecialNormal || specialNormal) { + // The first normal by definition is absolute, and normals cannot + // be represented as deltas to or from special normals, nor from + // normals with a different quantization. + absolute = true ; + stream.firstNormal = false ; + stream.normalQuantChanged = false ; + + } else if (stream.lastOctant == octant && + stream.lastSextant == sextant) { + // Deltas are always allowed within the same sextant/octant. + du = ii - stream.lastU ; + dv = jj - stream.lastV ; + + } else if (stream.lastOctant != octant && + stream.lastSextant == sextant && + (((sextant == 1 || sextant == 5) && + (stream.lastOctant & 3) == (octant & 3)) || + ((sextant == 0 || sextant == 4) && + (stream.lastOctant & 5) == (octant & 5)) || + ((sextant == 2 || sextant == 3) && + (stream.lastOctant & 6) == (octant & 6)))) { + // If the sextants are the same, the octants can differ only when + // they are bordering each other on the same edge that the + // sextant has. + du = ii - stream.lastU ; + dv = -jj - stream.lastV ; + + // Can't delta by less than -64. + if (dv < -uv64) absolute = true ; + + // Can't delta doubly defined points. + if (jj == 0) absolute = true ; + + } else if (stream.lastOctant == octant && + stream.lastSextant != sextant && + ((sextant == 0 && stream.lastSextant == 4) || + (sextant == 4 && stream.lastSextant == 0) || + (sextant == 1 && stream.lastSextant == 5) || + (sextant == 5 && stream.lastSextant == 1) || + (sextant == 2 && stream.lastSextant == 3) || + (sextant == 3 && stream.lastSextant == 2))) { + // If the octants are the same, the sextants must border on + // the i side (this case) or the j side (next case). + du = -ii - stream.lastU ; + dv = jj - stream.lastV ; + + // Can't delta by less than -64. + if (du < -uv64) absolute = true ; + + // Can't delta doubly defined points. + if (ii == 0) absolute = true ; + + } else if (stream.lastOctant == octant && + stream.lastSextant != sextant && + ((sextant == 0 && stream.lastSextant == 2) || + (sextant == 2 && stream.lastSextant == 0) || + (sextant == 1 && stream.lastSextant == 3) || + (sextant == 3 && stream.lastSextant == 1) || + (sextant == 4 && stream.lastSextant == 5) || + (sextant == 5 && stream.lastSextant == 4))) { + // If the octants are the same, the sextants must border on + // the j side (this case) or the i side (previous case). + if (((ii + jj ) != uv64) && (ii != 0) && (jj != 0)) { + du = uv64 - ii - stream.lastU ; + dv = uv64 - jj - stream.lastV ; + + // Can't delta by greater than +63. + if ((du >= uv64) || (dv >= uv64)) + absolute = true ; + } else + // Can't delta doubly defined points. + absolute = true ; + + } else + // Can't delta this normal. + absolute = true ; + + if (absolute == false) { + // Convert du and dv to standard grid form. + u = du << (6 - quant) ; + v = dv << (6 - quant) ; + } + + // Compute length and shift common to all components. + computeLengthShift(u, v) ; + + if (absolute && length > 6) { + // Absolute normal u, v components are unsigned 6-bit integers, so + // truncate the 0 sign bit for values > 0x001f. + length = 6 ; + } + + // Add this element to the Huffman table associated with this stream. + huffmanTable.addNormalEntry(length, shift, absolute) ; + + // Save current normal as last. + stream.lastSextant = sextant ; + stream.lastOctant = octant ; + stream.lastU = ii ; + stream.lastV = jj ; + stream.lastSpecialNormal = specialNormal ; + + // Copy and retain absolute normal for mesh buffer lookup. + uAbsolute = ii ; + vAbsolute = jj ; + } + + /** + * Output a setNormal command. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputCommand(HuffmanTable table, CommandStream output) { + outputNormal(table, output, CommandStream.SET_NORM, 8) ; + } + + /** + * Output a normal subcommand. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputSubcommand(HuffmanTable table, CommandStream output) { + outputNormal(table, output, 0, 6) ; + } + + // + // Output the final compressed bits to the output command stream. + // + private void outputNormal(HuffmanTable table, CommandStream output, + int header, int headerLength) { + + HuffmanNode t ; + + // Look up the Huffman token for this compression stream element. + t = table.getNormalEntry(length, shift, absolute) ; + + // Construct the normal subcommand. + int componentLength = t.dataLength - t.shift ; + int subcommandLength = 0 ; + long normalSubcommand = 0 ; + + if (absolute) { + // A 3-bit sextant and a 3-bit octant are always present. + subcommandLength = t.tagLength + 6 ; + + if (specialNormal) + // Use the specially-encoded sextant and octant. + normalSubcommand = + (t.tag << 6) | (specialSextant << 3) | specialOctant ; + else + // Use the general encoding rule. + normalSubcommand = + (t.tag << 6) | (sextant << 3) | octant ; + } else { + // The tag is immediately followed by the u and v delta components. + subcommandLength = t.tagLength ; + normalSubcommand = t.tag ; + } + + // Add the u and v values to the subcommand. + subcommandLength += (2 * componentLength) ; + + u = (u >> t.shift) & (int)lengthMask[componentLength] ; + v = (v >> t.shift) & (int)lengthMask[componentLength] ; + + normalSubcommand = + (normalSubcommand << (2 * componentLength)) | + (u << (1 * componentLength)) | + (v << (0 * componentLength)) ; + + if (subcommandLength < 6) { + // The header will have some empty bits. The Huffman tag + // computation will prevent this if necessary. + header |= (int)(normalSubcommand << (6 - subcommandLength)) ; + subcommandLength = 0 ; + } + else { + // Move the 1st 6 bits of the subcommand into the header. + header |= (int)(normalSubcommand >>> (subcommandLength - 6)) ; + subcommandLength -= 6 ; + } + + // Add the header and body to the output buffer. + output.addCommand(header, headerLength, + normalSubcommand, subcommandLength) ; + } + + public String toString() { + String fixed ; + + if (specialNormal) + fixed = " special normal, sextant " + specialSextant + + " octant " + specialOctant ; + + else if (absolute) + fixed = " sextant " + sextant + " octant " + octant + + " u " + u + " v " + v ; + else + fixed = " du " + u + " dv " + v ; + + return + "normal: " + normalX + " " + normalY + " " + normalZ + "\n" + + fixed + "\n" + " length " + length + " shift " + shift + + (absolute? " absolute" : " relative") ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamVertex.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamVertex.java new file mode 100644 index 0000000..77e396d --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/CompressionStreamVertex.java @@ -0,0 +1,322 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import javax.vecmath.Color3f; +import javax.vecmath.Color4f; +import javax.vecmath.Point3f; +import javax.vecmath.Vector3f; + +/** + * This class represents a vertex in a compression stream. It maintains both + * floating-point and quantized representations of the vertex position along + * with meshing and vertex replacement flags for line and surface + * primitives. If normals or colors are bundled with geometry vertices then + * instances of this class will also contain references to normal or color + * stream elements. + */ +class CompressionStreamVertex extends CompressionStreamElement { + private int X, Y, Z ; + private int meshFlag ; + private int stripFlag ; + private float floatX, floatY, floatZ ; + + int xAbsolute, yAbsolute, zAbsolute ; + CompressionStreamColor color = null ; + CompressionStreamNormal normal = null ; + + /** + * Create a CompressionStreamVertex with the given parameters. + * + * @param stream CompressionStream associated with this vertex + * @param p position + * @param n normal bundled with this vertex or null if not bundled + * @param c color bundled with this vertex or null if not bundled + * @param stripFlag CompressionStream.RESTART, + * CompressionStream.REPLACE_OLDEST, or CompressionStream.REPLACE_MIDDLE + * @param meshFlag CompressionStream.MESH_PUSH or + * CompressionStream.NO_MESH_PUSH + */ + CompressionStreamVertex(CompressionStream stream, + Point3f p, Vector3f n, Color3f c, + int stripFlag, int meshFlag) { + + this(stream, p, n, stripFlag, meshFlag) ; + + if (stream.vertexColor3) + color = new CompressionStreamColor(stream, c) ; + } + + /** + * Create a CompressionStreamVertex with the given parameters. + * + * @param stream CompressionStream associated with this vertex + * @param p position + * @param n normal bundled with this vertex or null if not bundled + * @param c color bundled with this vertex or null if not bundled + * @param stripFlag CompressionStream.RESTART, + * CompressionStream.REPLACE_OLDEST, or CompressionStream.REPLACE_MIDDLE + * @param meshFlag CompressionStream.MESH_PUSH or + * CompressionStream.NO_MESH_PUSH + */ + CompressionStreamVertex(CompressionStream stream, + Point3f p, Vector3f n, Color4f c, + int stripFlag, int meshFlag) { + + this(stream, p, n, stripFlag, meshFlag) ; + + if (stream.vertexColor4) + color = new CompressionStreamColor(stream, c) ; + } + + /** + * Create a CompressionStreamVertex with the given parameters. + * + * @param stream CompressionStream associated with this vertex + * @param p position + * @param n normal bundled with this vertex or null if not bundled + * @param stripFlag CompressionStream.RESTART, + * CompressionStream.REPLACE_OLDEST, or CompressionStream.REPLACE_MIDDLE + * @param meshFlag CompressionStream.MESH_PUSH or + * CompressionStream.NO_MESH_PUSH + */ + CompressionStreamVertex(CompressionStream stream, Point3f p, Vector3f n, + int stripFlag, int meshFlag) { + + this.stripFlag = stripFlag ; + this.meshFlag = meshFlag ; + this.floatX = p.x ; + this.floatY = p.y ; + this.floatZ = p.z ; + + stream.byteCount += 12 ; + stream.vertexCount++ ; + + if (p.x < stream.mcBounds[0].x) stream.mcBounds[0].x = p.x ; + if (p.y < stream.mcBounds[0].y) stream.mcBounds[0].y = p.y ; + if (p.z < stream.mcBounds[0].z) stream.mcBounds[0].z = p.z ; + + if (p.x > stream.mcBounds[1].x) stream.mcBounds[1].x = p.x ; + if (p.y > stream.mcBounds[1].y) stream.mcBounds[1].y = p.y ; + if (p.z > stream.mcBounds[1].z) stream.mcBounds[1].z = p.z ; + + if (stream.vertexNormals) + normal = new CompressionStreamNormal(stream, n) ; + } + + /** + * Quantize the floating point position to fixed point integer components + * of the specified number of bits. The bit length can range from 1 to 16. + * + * @param stream CompressionStream associated with this element + * @param table HuffmanTable for collecting data about the quantized + * representation of this element + */ + void quantize(CompressionStream stream, HuffmanTable huffmanTable) { + double px, py, pz ; + + // Clamp quantization. + int quant = + (stream.positionQuant < 1? 1 : + (stream.positionQuant > 16? 16 : stream.positionQuant)) ; + + absolute = false ; + if (stream.firstPosition || stream.positionQuantChanged) { + absolute = true ; + stream.lastPosition[0] = 0 ; + stream.lastPosition[1] = 0 ; + stream.lastPosition[2] = 0 ; + stream.firstPosition = false ; + stream.positionQuantChanged = false ; + } + + // Normalize position to the unit cube. This is bounded by the open + // intervals (-1..1) on each axis. + px = (floatX - stream.center[0]) * stream.scale ; + py = (floatY - stream.center[1]) * stream.scale ; + pz = (floatZ - stream.center[2]) * stream.scale ; + + // Convert the floating point position to s.15 2's complement. + // ~1.0 -> 32767 (0x00007fff) [ ~1.0 = 32767.0/32768.0] + // ~-1.0 -> -32767 (0xffff8001) [~-1.0 = -32767.0/32768.0] + X = (int)(px * 32768.0) ; + Y = (int)(py * 32768.0) ; + Z = (int)(pz * 32768.0) ; + + // Compute quantized values. + X &= quantizationMask[quant] ; + Y &= quantizationMask[quant] ; + Z &= quantizationMask[quant] ; + + // Update quantized bounds. + if (X < stream.qcBounds[0].x) stream.qcBounds[0].x = X ; + if (Y < stream.qcBounds[0].y) stream.qcBounds[0].y = Y ; + if (Z < stream.qcBounds[0].z) stream.qcBounds[0].z = Z ; + + if (X > stream.qcBounds[1].x) stream.qcBounds[1].x = X ; + if (Y > stream.qcBounds[1].y) stream.qcBounds[1].y = Y ; + if (Z > stream.qcBounds[1].z) stream.qcBounds[1].z = Z ; + + // Copy and retain absolute position for mesh buffer lookup. + xAbsolute = X ; + yAbsolute = Y ; + zAbsolute = Z ; + + // Compute deltas. + X -= stream.lastPosition[0] ; + Y -= stream.lastPosition[1] ; + Z -= stream.lastPosition[2] ; + + // Update last values. + stream.lastPosition[0] += X ; + stream.lastPosition[1] += Y ; + stream.lastPosition[2] += Z ; + + // Deltas which exceed the range of 16-bit signed 2's complement + // numbers are handled by sign-extension of the 16th bit in order to + // effect a 16-bit wrap-around. + X = (X << 16) >> 16 ; + Y = (Y << 16) >> 16 ; + Z = (Z << 16) >> 16 ; + + // Compute length and shift common to all components. + computeLengthShift(X, Y, Z) ; + + // 0-length components are allowed only for normals. + if (length == 0) + length = 1 ; + + // Add this element to the Huffman table associated with this stream. + huffmanTable.addPositionEntry(length, shift, absolute) ; + + // Quantize any bundled color or normal. + if (color != null) + color.quantize(stream, huffmanTable) ; + + if (normal != null) + normal.quantize(stream, huffmanTable) ; + + // Push this vertex into the mesh buffer mirror, if necessary, so it + // can be retrieved for computing deltas when mesh buffer references + // are subsequently encountered during the quantization pass. + if (meshFlag == stream.MESH_PUSH) + stream.meshBuffer.push(this) ; + } + + /** + * Output the final compressed bits to the compression command stream. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputCommand(HuffmanTable huffmanTable, CommandStream outputBuffer) { + + HuffmanNode t ; + int command = CommandStream.VERTEX ; + + // Look up the Huffman token for this compression stream element. The + // values of length and shift found there will override the + // corresponding fields in this element, which represent best-case + // compression without regard to tag length. + t = huffmanTable.getPositionEntry(length, shift, absolute) ; + + // Construct the position subcommand. + int componentLength = t.dataLength - t.shift ; + int subcommandLength = t.tagLength + (3 * componentLength) ; + + X = (X >> t.shift) & (int)lengthMask[componentLength] ; + Y = (Y >> t.shift) & (int)lengthMask[componentLength] ; + Z = (Z >> t.shift) & (int)lengthMask[componentLength] ; + + long positionSubcommand = + (((long)t.tag) << (3 * componentLength)) | + (((long)X) << (2 * componentLength)) | + (((long)Y) << (1 * componentLength)) | + (((long)Z) << (0 * componentLength)) ; + + if (subcommandLength < 6) { + // The header will have some empty bits. The Huffman tag + // computation will prevent this if necessary. + command |= (int)(positionSubcommand << (6 - subcommandLength)) ; + subcommandLength = 0 ; + } + else { + // Move the 1st 6 bits of the subcommand into the header. + command |= (int)(positionSubcommand >>> (subcommandLength - 6)) ; + subcommandLength -= 6 ; + } + + // Construct the vertex command body. + long body = + (((long)stripFlag) << (subcommandLength + 1)) | + (((long)meshFlag) << (subcommandLength + 0)) | + (positionSubcommand & lengthMask[subcommandLength]) ; + + // Add the vertex command to the output buffer. + outputBuffer.addCommand(command, 8, body, subcommandLength + 3) ; + + // Output any normal and color subcommands. + if (normal != null) + normal.outputSubcommand(huffmanTable, outputBuffer) ; + + if (color != null) + color.outputSubcommand(huffmanTable, outputBuffer) ; + } + + public String toString() { + String d = absolute? "" : "delta " ; + String c = (color == null? "": "\n\n " + color.toString()) ; + String n = (normal == null? "": "\n\n " + normal.toString()) ; + + return + "position: " + floatX + " " + floatY + " " + floatZ + "\n" + + "fixed point " + d + + X + " " + Y + " " + Z + "\n" + + "length " + length + " shift " + shift + + (absolute? " absolute" : " relative") + "\n" + + "strip flag " + stripFlag + " mesh flag " + meshFlag + + c + n ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/GeneralizedStrip.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeneralizedStrip.java new file mode 100644 index 0000000..194c9c9 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeneralizedStrip.java @@ -0,0 +1,908 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import com.sun.j3d.internal.J3dUtilsI18N; + + +/** + * This class provides static methods to support topological + * transformations on generalized strips. This is used by the + * GeometryDecompressor. These methods only need to look at the + * vertex replacement flags to determine how the vertices in the strip + * are connected. The connections are rearranged in different ways to + * transform generalized strips to GeometryArray representations. + * + * @see GeneralizedStripFlags + * @see GeneralizedVertexList + * @see GeometryDecompressor + */ +class GeneralizedStrip { + private static final boolean debug = false ; + + // Private convenience copies of various constants. + private static final int CW = + GeneralizedStripFlags.FRONTFACE_CW ; + private static final int CCW = + GeneralizedStripFlags.FRONTFACE_CCW ; + private static final int RESTART_CW = + GeneralizedStripFlags.RESTART_CW ; + private static final int RESTART_CCW = + GeneralizedStripFlags.RESTART_CCW ; + private static final int REPLACE_MIDDLE = + GeneralizedStripFlags.REPLACE_MIDDLE ; + private static final int REPLACE_OLDEST = + GeneralizedStripFlags.REPLACE_OLDEST ; + + /** + * The IntList is like an ArrayList, but avoids the Integer + * object wrapper and accessor overhead for simple lists of ints. + */ + static class IntList { + /** + * The array of ints. + */ + int ints[] ; + + /** + * The number of ints in this instance. + */ + int count ; + + /** + * Construct a new empty IntList of the given initial size. + * @param initialSize initial size of the backing array + */ + IntList(int initialSize) { + ints = new int[initialSize] ; + count = 0 ; + } + + /** + * Constructs an IntList with the given contents. + * @param ints the array of ints to use as the contents + */ + IntList(int ints[]) { + this.ints = ints ; + this.count = ints.length ; + } + + /** + * Add a new int to the end of this list. + * @param i the int to be appended to this list + */ + void add(int i) { + if (count == ints.length) { + int newints[] = new int[2*count] ; + System.arraycopy(ints, 0, newints, 0, count) ; + ints = newints ; + if (debug) + System.out.println + ("GeneralizedStrip.IntList: reallocated " + + (2*count) + " ints") ; + } + ints[count++] = i ; + } + + /** + * Trim the backing array to the current count and return the + * resulting backing array. + */ + int[] trim() { + if (count != ints.length) { + int newints[] = new int[count] ; + System.arraycopy(ints, 0, newints, 0, count) ; + ints = newints ; + } + return ints ; + } + + /** + * Fill the list with consecutive integers starting from 0. + */ + void fillAscending() { + for (int i = 0 ; i < ints.length ; i++) + ints[i] = i ; + + count = ints.length ; + } + + public String toString() { + String s = new String("[") ; + for (int i = 0 ; i < count-1 ; i++) + s = s + Integer.toString(ints[i]) + ", " ; + return s + Integer.toString(ints[count-1]) + "]" ; + } + } + + /** + * The StripArray class is used as the output of some conversion methods + * in the GeneralizedStrip class. + */ + static class StripArray { + /** + * A list of indices into the vertices of the original generalized + * strip. It specifies the order in which vertices in the original + * strip should be followed to build GeometryArray objects. + */ + IntList vertices ; + + /** + * A list of strip counts. + */ + IntList stripCounts ; + + /** + * Creates a StripArray with the specified vertices and stripCounts. + * @param vertices IntList containing vertex indicies. + * @param stripCounts IntList containing strip lengths. + */ + StripArray(IntList vertices, IntList stripCounts) { + this.vertices = vertices ; + this.stripCounts = stripCounts ; + } + } + + /** + * Interprets the vertex flags associated with a class implementing + * GeneralizedStripFlags, constructing and returning a 2-element array of + * StripArray objects. The first StripArray will contain triangle strips + * and the second will contain triangle fans. + * + * @param vertices an object implementing GeneralizedStripFlags + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @return a 2-element array containing strips in 0 and fans in 1 + */ + static StripArray[] toStripsAndFans(GeneralizedStripFlags vertices, + int frontFace) { + + int size = vertices.getFlagCount() ; + + // Initialize IntLists to worst-case sizes. + IntList stripVerts = new IntList(size*3) ; + IntList fanVerts = new IntList(size*3) ; + IntList stripCounts = new IntList(size) ; + IntList fanCounts = new IntList(size) ; + + toStripsAndFans(vertices, frontFace, + stripVerts, stripCounts, fanVerts, fanCounts) ; + + // Construct the StripArray output. + StripArray sa[] = new StripArray[2] ; + + if (stripCounts.count > 0) + sa[0] = new StripArray(stripVerts, stripCounts) ; + + if (fanCounts.count > 0) + sa[1] = new StripArray(fanVerts, fanCounts) ; + + return sa ; + } + + private static void toStripsAndFans(GeneralizedStripFlags vertices, + int frontFace, + IntList stripVerts, + IntList stripCounts, + IntList fanVerts, + IntList fanCounts) { + int newFlag, curFlag, winding ; + int v, size, stripStart, stripLength ; + boolean transition = false ; + + stripStart = 0 ; + stripLength = 3 ; + curFlag = vertices.getFlag(0) ; + winding = (curFlag == RESTART_CW ? CW : CCW) ; + size = vertices.getFlagCount() ; + + // Vertex replace flags for the first 3 vertices are irrelevant since + // they can only define a single triangle. The first meaningful + // replace flag starts at the 4th vertex. + v = 3 ; + if (v < size) + curFlag = vertices.getFlag(v) ; + + while (v < size) { + newFlag = vertices.getFlag(v) ; + + if ((newFlag == curFlag) && + (newFlag != RESTART_CW) && (newFlag != RESTART_CCW)) { + // The last flag was the same as this one, and it wasn't a + // restart: proceed to the next vertex. + stripLength++ ; + v++ ; + + } else { + // Either this vertex flag changed from the last one, or + // the flag explicitly specifies a restart: process the + // last strip and start up a new one. + if (curFlag == REPLACE_MIDDLE) + addFan(fanVerts, fanCounts, stripStart, stripLength, + frontFace, winding, transition) ; + else + addStrip(stripVerts, stripCounts, stripStart, stripLength, + frontFace, winding) ; + + // Restart: skip to the 4th vertex of the new strip. + if ((newFlag == RESTART_CW) || (newFlag == RESTART_CCW)) { + winding = (newFlag == RESTART_CW ? CW : CCW) ; + stripStart = v ; + stripLength = 3 ; + v += 3 ; + transition = false ; + if (v < size) + curFlag = vertices.getFlag(v) ; + } + // Strip/fan transition: decrement start of strip. + else { + if (newFlag == REPLACE_OLDEST) { + // Flip winding order when transitioning from fans + // to strips. + winding = (winding == CW ? CCW : CW) ; + stripStart = v-2 ; + stripLength = 3 ; + } else { + // Flip winding order when transitioning from + // strips to fans only if the preceding strip has + // an even number of vertices. + if ((stripLength & 0x01) == 0) + winding = (winding == CW ? CCW : CW) ; + stripStart = v-3 ; + stripLength = 4 ; + } + v++ ; + transition = true ; + curFlag = newFlag ; + } + } + } + + // Finish off the last strip or fan. + // If v > size then the strip is degenerate. + if (v == size) + if (curFlag == REPLACE_MIDDLE) + addFan(fanVerts, fanCounts, stripStart, stripLength, + frontFace, winding, transition) ; + else + addStrip(stripVerts, stripCounts, stripStart, stripLength, + frontFace, winding) ; + else + throw new IllegalArgumentException + (J3dUtilsI18N.getString("GeneralizedStrip0")) ; + + if (debug) { + System.out.println("GeneralizedStrip.toStripsAndFans") ; + if (v > size) + System.out.println(" ended with a degenerate triangle:" + + " number of vertices: " + (v-size)) ; + + System.out.println("\n number of strips: " + stripCounts.count) ; + if (stripCounts.count > 0) { + System.out.println(" number of vertices: " + stripVerts.count) ; + System.out.println(" vertices/strip: " + + (float)stripVerts.count/stripCounts.count) ; + System.out.println(" strip counts: " + stripCounts.toString()) ; + // System.out.println(" indices: " + stripVerts.toString()) ; + } + + System.out.println("\n number of fans: " + fanCounts.count) ; + if (fanCounts.count > 0) { + System.out.println(" number of vertices: " + fanVerts.count) ; + System.out.println(" vertices/strip: " + + (float)fanVerts.count/fanCounts.count) ; + System.out.println(" fan counts: " + fanCounts.toString()) ; + // System.out.println(" indices: " + fanVerts.toString()) ; + } + System.out.println("\n total vertices: " + + (stripVerts.count + fanVerts.count) + + "\n original number of vertices: " + size + + "\n") ; + } + } + + // + // Java 3D specifies that the vertices of front-facing polygons + // have counter-clockwise (CCW) winding order when projected to + // the view surface. Polygons with clockwise (CW) vertex winding + // will be culled as back-facing by default. + // + // Generalized triangle strips can flip the orientation of their + // triangles with the RESTART_CW and RESTART_CCW vertex flags. + // Strips flagged with an orientation opposite to what has been + // specified as front-facing must have their windings reversed in + // order to have the correct face orientation when represented as + // GeometryArray objects. + // + private static void addStrip(IntList stripVerts, + IntList stripCounts, + int start, int length, + int frontFace, int winding) { + int vindex = start ; + + if (winding == frontFace) { + // Maintain original order. + stripCounts.add(length) ; + while (vindex < start + length) { + stripVerts.add(vindex++) ; + } + } else if ((length & 0x1) == 1) { + // Reverse winding order if number of vertices is odd. + stripCounts.add(length) ; + vindex += length-1 ; + while (vindex >= start) { + stripVerts.add(vindex--) ; + } + } else if (length == 4) { + // Swap middle vertices. + stripCounts.add(4) ; + stripVerts.add(vindex) ; + stripVerts.add(vindex+2) ; + stripVerts.add(vindex+1) ; + stripVerts.add(vindex+3) ; + } else { + // Make the 1st triangle a singleton with reverse winding. + stripCounts.add(3) ; + stripVerts.add(vindex) ; + stripVerts.add(vindex+2) ; + stripVerts.add(vindex+1) ; + if (length > 3) { + // Copy the rest of the vertices in original order. + vindex++ ; + stripCounts.add(length-1) ; + while (vindex < start + length) { + stripVerts.add(vindex++) ; + } + } + } + } + + private static void addFan(IntList fanVerts, + IntList fanCounts, + int start, int length, + int frontFace, int winding, + boolean transition) { + int vindex = start ; + fanVerts.add(vindex++) ; + + if (winding == frontFace) { + if (transition) { + // Skip 1st triangle if this is the result of a transition. + fanCounts.add(length-1) ; + vindex++ ; + } else { + fanCounts.add(length) ; + fanVerts.add(vindex++) ; + } + while (vindex < start + length) { + fanVerts.add(vindex++) ; + } + } else { + // Reverse winding order. + vindex += length-2 ; + while (vindex > start+1) { + fanVerts.add(vindex--) ; + } + if (transition) { + // Skip 1st triangle if this is the result of a transition. + fanCounts.add(length-1) ; + } else { + fanCounts.add(length) ; + fanVerts.add(vindex) ; + } + } + } + + /** + * Interprets the vertex flags associated with a class implementing + * GeneralizedStripFlags, constructing and returning a StripArray containing + * exclusively strips. + * + * @param vertices an object implementing GeneralizedStripFlags + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @return a StripArray containing the converted strips + */ + static StripArray toTriangleStrips(GeneralizedStripFlags vertices, + int frontFace) { + + int size = vertices.getFlagCount() ; + + // initialize lists to worst-case sizes. + IntList stripVerts = new IntList(size*3) ; + IntList fanVerts = new IntList(size*3) ; + IntList stripCounts = new IntList(size) ; + IntList fanCounts = new IntList(size) ; + + toStripsAndFans(vertices, frontFace, + stripVerts, stripCounts, fanVerts, fanCounts) ; + + if (fanCounts.count == 0) + if (stripCounts.count > 0) + return new StripArray(stripVerts, stripCounts) ; + else + return null ; + + // convert each fan to one or more strips + int i, v = 0 ; + for (i = 0 ; i < fanCounts.count ; i++) { + fanToStrips(v, fanCounts.ints[i], fanVerts.ints, + stripVerts, stripCounts, false) ; + v += fanCounts.ints[i] ; + } + + // create the StripArray output + StripArray sa = new StripArray(stripVerts, stripCounts) ; + + if (debug) { + System.out.println("GeneralizedStrip.toTriangleStrips" + + "\n number of strips: " + + sa.stripCounts.count) ; + if (sa.stripCounts.count > 0) { + System.out.println(" number of vertices: " + + sa.vertices.count + + "\n vertices/strip: " + + ((float)sa.vertices.count / + (float)sa.stripCounts.count)) ; + System.out.print(" strip counts: [") ; + for (i = 0 ; i < sa.stripCounts.count-1 ; i++) + System.out.print(sa.stripCounts.ints[i] + ", ") ; + System.out.println(sa.stripCounts.ints[i] + "]") ; + } + System.out.println() ; + } + return sa ; + } + + private static void fanToStrips(int v, int length, int fans[], + IntList stripVerts, + IntList stripCounts, + boolean convexPlanar) { + if (convexPlanar) { + // Construct a strip by criss-crossing across the interior. + stripCounts.add(length) ; + stripVerts.add(fans[v]) ; + + int j = v + 1 ; + int k = v + (length - 1) ; + while (j <= k) { + stripVerts.add(fans[j++]) ; + if (j > k) break ; + stripVerts.add(fans[k--]) ; + } + } else { + // Traverse non-convex or non-planar fan, biting off 3-triangle + // strips or less. First 5 vertices produce 1 strip of 3 + // triangles, and every 4 vertices after that produce another + // strip of 3 triangles. Each remaining strip adds 2 vertices. + int fanStart = v ; + v++ ; + while (v+4 <= fanStart + length) { + stripVerts.add(fans[v]) ; + stripVerts.add(fans[v+1]) ; + stripVerts.add(fans[fanStart]) ; + stripVerts.add(fans[v+2]) ; + stripVerts.add(fans[v+3]) ; + stripCounts.add(5) ; + v += 3 ; + } + + // Finish off the fan. + if (v+1 < fanStart + length) { + stripVerts.add(fans[v]) ; + stripVerts.add(fans[v+1]) ; + stripVerts.add(fans[fanStart]) ; + v++ ; + + if (v+1 < fanStart + length) { + stripVerts.add(fans[v+1]) ; + stripCounts.add(4) ; + } + else + stripCounts.add(3) ; + } + } + } + + /** + * Interprets the vertex flags associated with a class implementing + * GeneralizedStripFlags, constructing and returning an array of vertex + * references representing the original generalized strip as individual + * triangles. Each sequence of three consecutive vertex references in the + * output defines a single triangle. + * + * @param vertices an object implementing GeneralizedStripFlags + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @return an array of indices into the original vertex array + */ + static int[] toTriangles(GeneralizedStripFlags vertices, int frontFace) { + + int vertexCount = 0 ; + StripArray sa[] = toStripsAndFans(vertices, frontFace) ; + + if (sa[0] != null) + vertexCount = 3 * getTriangleCount(sa[0].stripCounts) ; + if (sa[1] != null) + vertexCount += 3 * getTriangleCount(sa[1].stripCounts) ; + + if (debug) + System.out.println("GeneralizedStrip.toTriangles\n" + + " number of triangles: " + vertexCount/3 + "\n" + + " number of vertices: " + vertexCount + "\n") ; + int t = 0 ; + int triangles[] = new int[vertexCount] ; + + if (sa[0] != null) + t = stripsToTriangles(t, triangles, + 0, sa[0].vertices.ints, + 0, sa[0].stripCounts.ints, + sa[0].stripCounts.count) ; + if (sa[1] != null) + t = fansToTriangles(t, triangles, + 0, sa[1].vertices.ints, + 0, sa[1].stripCounts.ints, + sa[1].stripCounts.count) ; + return triangles ; + } + + private static int stripsToTriangles(int tstart, int tbuff[], + int vstart, int vertices[], + int stripStart, int stripCounts[], + int stripCount) { + int t = tstart ; + int v = vstart ; + for (int i = 0 ; i < stripCount ; i++) { + for (int j = 0 ; j < stripCounts[i+stripStart] - 2 ; j++) { + if ((j & 0x01) == 0) { + // even-numbered triangles + tbuff[t*3 +0] = vertices[v+0] ; + tbuff[t*3 +1] = vertices[v+1] ; + tbuff[t*3 +2] = vertices[v+2] ; + } else { + // odd-numbered triangles + tbuff[t*3 +0] = vertices[v+1] ; + tbuff[t*3 +1] = vertices[v+0] ; + tbuff[t*3 +2] = vertices[v+2] ; + } + t++ ; v++ ; + } + v += 2 ; + } + return t ; + } + + private static int fansToTriangles(int tstart, int tbuff[], + int vstart, int vertices[], + int stripStart, int stripCounts[], + int stripCount) { + int t = tstart ; + int v = vstart ; + for (int i = 0 ; i < stripCount ; i++) { + for (int j = 0 ; j < stripCounts[i+stripStart] - 2 ; j++) { + tbuff[t*3 +0] = vertices[v] ; + tbuff[t*3 +1] = vertices[v+j+1] ; + tbuff[t*3 +2] = vertices[v+j+2] ; + t++ ; + } + v += stripCounts[i+stripStart] ; + } + return t ; + } + + /** + * Interprets the vertex flags associated with a class implementing + * GeneralizedStripFlags, constructing and returning a 2-element array of + * StripArray objects. The first StripArray will contain triangle strips + * and the second will contain individual triangles in the vertices + * field. Short strips will be converted to individual triangles. + * + * @param vertices an object implementing GeneralizedStripFlags + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @param shortStripSize strips this size or less will be converted to + * individual triangles if there are more than maxShortStrips of them + * @param maxShortStrips maximum number of short strips allowed before + * creating individual triangles + * @return a 2-element array containing strips in 0 and triangles in 1 + */ + static StripArray[] toStripsAndTriangles(GeneralizedStripFlags vertices, + int frontFace, int shortStripSize, + int maxShortStrips) { + int longStripCount = 0 ; + int longStripVertexCount = 0 ; + int shortStripCount = 0 ; + int triangleCount = 0 ; + + StripArray sa[] = new StripArray[2] ; + StripArray ts = toTriangleStrips(vertices, frontFace) ; + + for (int i = 0 ; i < ts.stripCounts.count ; i++) + if (ts.stripCounts.ints[i] <= shortStripSize) { + shortStripCount++ ; + triangleCount += ts.stripCounts.ints[i] - 2 ; + } else { + longStripCount++ ; + longStripVertexCount += ts.stripCounts.ints[i] ; + } + + if (debug) + System.out.print("GeneralizedStrip.toStripsAndTriangles\n" + + " short strip size: " + shortStripSize + + " short strips tolerated: " + maxShortStrips + + " number of short strips: " + shortStripCount + + "\n\n") ; + + if (shortStripCount <= maxShortStrips) { + sa[0] = ts ; + sa[1] = null ; + } else { + int si = 0 ; int newStripVerts[] = new int[longStripVertexCount] ; + int ci = 0 ; int newStripCounts[] = new int[longStripCount] ; + int ti = 0 ; int triangles[] = new int[3*triangleCount] ; + int vi = 0 ; + + for (int i = 0 ; i < ts.stripCounts.count ; i++) { + if (ts.stripCounts.ints[i] <= shortStripSize) { + ti = stripsToTriangles(ti, triangles, + vi, ts.vertices.ints, + i, ts.stripCounts.ints, 1) ; + vi += ts.stripCounts.ints[i] ; + } else { + newStripCounts[ci++] = ts.stripCounts.ints[i] ; + for (int j = 0 ; j < ts.stripCounts.ints[i] ; j++) + newStripVerts[si++] = ts.vertices.ints[vi++] ; + } + } + + if (longStripCount > 0) + sa[0] = new StripArray(new IntList(newStripVerts), + new IntList(newStripCounts)) ; + else + sa[0] = null ; + + sa[1] = new StripArray(new IntList(triangles), null) ; + + if (debug) { + System.out.println(" triangles separated: " + triangleCount) ; + if (longStripCount > 0) { + System.out.println + (" new vertices/strip: " + + ((float)longStripVertexCount/(float)longStripCount)) ; + + System.out.print(" long strip counts: [") ; + for (int i = 0 ; i < longStripCount-1 ; i++) + System.out.print(newStripCounts[i++] + ", ") ; + + System.out.println + (newStripCounts[longStripCount-1] + "]\n") ; + } + } + } + return sa ; + } + + /** + * Interprets the vertex flags associated with a class implementing + * GeneralizedStripFlags, constructing and returning a StripArray. + * + * RESTART_CW and RESTART_CCW are treated as equivalent, as are + * REPLACE_MIDDLE and REPLACE_OLDEST. + * + * @param vertices an object implementing GeneralizedStripFlags + * @return a StripArray representing an array of line strips + */ + static StripArray toLineStrips(GeneralizedStripFlags vertices) { + int v, size, stripStart, stripLength, flag ; + + stripStart = 0 ; + stripLength = 2 ; + size = vertices.getFlagCount() ; + + // Initialize IntLists to worst-case sizes. + IntList stripVerts = new IntList(size*2) ; + IntList stripCounts = new IntList(size) ; + + // Vertex replace flags for the first two vertices are irrelevant. + v = 2 ; + while (v < size) { + flag = vertices.getFlag(v) ; + + if ((flag != RESTART_CW) && (flag != RESTART_CCW)) { + // proceed to the next vertex. + stripLength++ ; + v++ ; + + } else { + // Record the last strip. + stripCounts.add(stripLength) ; + for (int i = stripStart ; i < stripStart+stripLength ; i++) + stripVerts.add(i) ; + + // Start a new strip and skip to its 3rd vertex. + stripStart = v ; + stripLength = 2 ; + v += 2 ; + } + } + + // Finish off the last strip. + // If v > size then the strip is degenerate. + if (v == size) { + stripCounts.add(stripLength) ; + for (int i = stripStart ; i < stripStart+stripLength ; i++) + stripVerts.add(i) ; + } else + throw new IllegalArgumentException + (J3dUtilsI18N.getString("GeneralizedStrip0")) ; + + if (debug) { + System.out.println("GeneralizedStrip.toLineStrips\n") ; + if (v > size) + System.out.println(" ended with a degenerate line") ; + + System.out.println(" number of strips: " + stripCounts.count) ; + if (stripCounts.count > 0) { + System.out.println(" number of vertices: " + stripVerts.count) ; + System.out.println(" vertices/strip: " + + (float)stripVerts.count/stripCounts.count) ; + System.out.println(" strip counts: " + stripCounts.toString()) ; + // System.out.println(" indices: " + stripVerts.toString()) ; + } + System.out.println() ; + } + + if (stripCounts.count > 0) + return new StripArray(stripVerts, stripCounts) ; + else + return null ; + } + + /** + * Counts the number of lines defined by arrays of line strips. + * + * @param stripCounts array of strip counts, as used by the + * GeometryStripArray object + * @return number of lines in the strips + */ + static int getLineCount(int stripCounts[]) { + int count = 0 ; + for (int i = 0 ; i < stripCounts.length ; i++) + count += (stripCounts[i] - 1) ; + return count ; + } + + /** + * Counts the number of triangles defined by arrays of + * triangle strips or fans. + * + * @param stripCounts array of strip counts, as used by the + * GeometryStripArray object + * @return number of triangles in the strips or fans + */ + static int getTriangleCount(int stripCounts[]) { + int count = 0 ; + for (int i = 0 ; i < stripCounts.length ; i++) + count += (stripCounts[i] - 2) ; + return count ; + } + + /** + * Counts the number of triangles defined by arrays of + * triangle strips or fans. + * + * @param stripCounts IntList of strip counts + * @return number of triangles in the strips or fans + */ + static int getTriangleCount(IntList stripCounts) { + int count = 0 ; + for (int i = 0 ; i < stripCounts.count ; i++) + count += (stripCounts.ints[i] - 2) ; + return count ; + } + + /** + * Breaks up triangle strips into separate triangles. + * + * @param stripCounts array of strip counts, as used by the + * GeometryStripArray object + * @return array of ints which index into the original vertex array; each + * set of three consecutive vertex indices defines a single triangle + */ + static int[] stripsToTriangles(int stripCounts[]) { + int triangleCount = getTriangleCount(stripCounts) ; + int tbuff[] = new int[3*triangleCount] ; + IntList vertices = new IntList(triangleCount + 2*stripCounts.length) ; + + vertices.fillAscending() ; + stripsToTriangles(0, tbuff, + 0, vertices.ints, + 0, stripCounts, + stripCounts.length) ; + return tbuff ; + } + + /** + * Breaks up triangle fans into separate triangles. + * + * @param stripCounts array of strip counts, as used by the + * GeometryStripArray object + * @return array of ints which index into the original vertex array; each + * set of three consecutive vertex indices defines a single triangle + */ + static int[] fansToTriangles(int stripCounts[]) { + int triangleCount = getTriangleCount(stripCounts) ; + int tbuff[] = new int[3*triangleCount] ; + IntList vertices = new IntList(triangleCount + 2*stripCounts.length) ; + + vertices.fillAscending() ; + fansToTriangles(0, tbuff, + 0, vertices.ints, + 0, stripCounts, + stripCounts.length) ; + return tbuff ; + } + + /** + * Takes a fan and converts it to one or more strips. + * + * @param v index into the fans array of the first vertex in the fan + * @param length number of vertices in the fan + * @param fans array of vertex indices representing one or more fans + * @param convexPlanar if true indicates that the fan is convex and + * planar; such fans will always be converted into a single strip + * @return a StripArray containing the converted strips + */ + static StripArray fanToStrips(int v, int length, int fans[], + boolean convexPlanar) { + + // Initialize IntLists to worst-case sizes. + IntList stripVerts = new IntList(length*3) ; + IntList stripCounts = new IntList(length) ; + + fanToStrips(v, length, fans, stripVerts, stripCounts, convexPlanar) ; + return new StripArray(stripVerts, stripCounts) ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/GeneralizedStripFlags.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeneralizedStripFlags.java new file mode 100644 index 0000000..5d7fa08 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeneralizedStripFlags.java @@ -0,0 +1,105 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +/** + * A class which implements GeneralizedStripFlags provides the means to access + * the vertex replace code flags associated with each vertex of a generalized + * strip. This allows a flexible representation of generalized strips for + * various classes and makes it possible to provide a common subset of static + * methods which operate only on their topology. + * + * @see GeneralizedStrip + * @see GeneralizedVertexList + */ +interface GeneralizedStripFlags { + + /** + * This flag indicates that a vertex starts a new strip with clockwise + * winding. + */ + static final int RESTART_CW = 0 ; + + /** + * This flag indicates that a vertex starts a new strip with + * counter-clockwise winding. + */ + static final int RESTART_CCW = 1 ; + + /** + * This flag indicates that the next triangle in the strip is defined by + * replacing the middle vertex of the previous triangle in the strip. + */ + static final int REPLACE_MIDDLE = 2 ; + + /** + * This flag indicates that the next triangle in the strip is defined by + * replacing the oldest vertex of the previous triangle in the strip. + */ + static final int REPLACE_OLDEST = 3 ; + + /** + * This constant is used to indicate that triangles with clockwise vertex + * winding are front facing. + */ + static final int FRONTFACE_CW = 0 ; + + /** + * This constant is used to indicate that triangles with counter-clockwise + * vertex winding are front facing. + */ + static final int FRONTFACE_CCW = 1 ; + + /** + * Return the number of flags. This should be the same as the number of + * vertices in the generalized strip. + */ + int getFlagCount() ; + + /** + * Return the flag associated with the vertex at the specified index. + */ + int getFlag(int index) ; +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/GeneralizedVertexList.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeneralizedVertexList.java new file mode 100644 index 0000000..f5baf04 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeneralizedVertexList.java @@ -0,0 +1,429 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import java.util.ArrayList; +import javax.media.j3d.GeometryArray; +import javax.media.j3d.GeometryStripArray; +import javax.media.j3d.LineStripArray; +import javax.media.j3d.PointArray; +import javax.media.j3d.TriangleArray; +import javax.media.j3d.TriangleFanArray; +import javax.media.j3d.TriangleStripArray; +import javax.vecmath.Color3f; +import javax.vecmath.Color4f; +import javax.vecmath.Point3f; +import javax.vecmath.Vector3f; + +/** + * The GeneralizedVertexList class is a variable-size list used to + * collect the vertices for a generalized strip of points, lines, or + * triangles. This is used by the GeometryDecompressor. This class + * implements the GeneralizedStripFlags interface and provides methods + * for copying instance vertex data into various fixed-size + * GeometryArray representations. + * + * @see GeneralizedStrip + * @see GeometryDecompressor + */ +class GeneralizedVertexList implements GeneralizedStripFlags { + + // The ArrayList containing the vertices. + private ArrayList vertices ; + + // Booleans for individual vertex components. + private boolean hasColor3 = false ; + private boolean hasColor4 = false ; + private boolean hasNormals = false ; + + // Indicates the vertex winding of front-facing triangles in this strip. + private int frontFace ; + + /** + * Count of number of strips generated after conversion to GeometryArray. + */ + int stripCount ; + + /** + * Count of number of vertices generated after conversion to GeometryArray. + */ + int vertexCount ; + + /** + * Count of number of triangles generated after conversion to GeometryArray. + */ + int triangleCount ; + + /** + * Bits describing the data bundled with each vertex. This is specified + * using the GeometryArray mask components. + */ + int vertexFormat ; + + /** + * Creates a new GeneralizedVertexList for the specified vertex format. + * @param vertexFormat a mask indicating which components are + * present in each vertex, as used by GeometryArray. + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @param initSize initial number of elements + * @see GeometryArray + */ + GeneralizedVertexList(int vertexFormat, int frontFace, int initSize) { + this.frontFace = frontFace ; + setVertexFormat(vertexFormat) ; + + if (initSize == 0) + vertices = new ArrayList() ; + else + vertices = new ArrayList(initSize) ; + + stripCount = 0 ; + vertexCount = 0 ; + triangleCount = 0 ; + } + + /** + * Creates a new GeneralizedVertexList for the specified vertex format. + * @param vertexFormat a mask indicating which components are + * present in each vertex, as used by GeometryArray. + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @see GeometryArray + */ + GeneralizedVertexList(int vertexFormat, int frontFace) { + this(vertexFormat, frontFace, 0) ; + } + + /** + * Sets the vertex format for this vertex list. + * @param vertexFormat a mask indicating which components are + * present in each vertex, as used by GeometryArray. + */ + void setVertexFormat(int vertexFormat) { + this.vertexFormat = vertexFormat ; + + if ((vertexFormat & GeometryArray.NORMALS) != 0) + hasNormals = true ; + + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) + hasColor4 = true ; + else if ((vertexFormat & GeometryArray.COLOR_3) == GeometryArray.COLOR_3) + hasColor3 = true ; + } + + /** + * A class with fields corresponding to all the data that can be bundled + * with the vertices of generalized strips. + */ + class Vertex { + int flag ; + Point3f coord ; + Color3f color3 ; + Color4f color4 ; + Vector3f normal ; + + Vertex(Point3f p, Vector3f n, Color4f c, int flag) { + this.flag = flag ; + coord = new Point3f(p) ; + + if (hasNormals) + normal = new Vector3f(n) ; + + if (hasColor3) + color3 = new Color3f(c.x, c.y, c.z) ; + + else if (hasColor4) + color4 = new Color4f(c) ; + } + } + + /** + * Copy vertex data to a new Vertex object and add it to this list. + */ + void addVertex(Point3f pos, Vector3f norm, Color4f color, int flag) { + vertices.add(new Vertex(pos, norm, color, flag)) ; + } + + /** + * Return the number of vertices in this list. + */ + int size() { + return vertices.size() ; + } + + // GeneralizedStripFlags interface implementation + public int getFlagCount() { + return vertices.size() ; + } + + // GeneralizedStripFlags interface implementation + public int getFlag(int index) { + return ((Vertex)vertices.get(index)).flag ; + } + + // Copy vertices in the given order to a fixed-length GeometryArray. + // Using the array versions of the GeometryArray set() methods results in + // a significant performance improvement despite needing to create + // fixed-length arrays to hold the vertex elements. + private void copyVertexData(GeometryArray ga, + GeneralizedStrip.IntList indices) { + Vertex v ; + Point3f p3f[] = new Point3f[indices.count] ; + + if (hasNormals) { + Vector3f v3f[] = new Vector3f[indices.count] ; + if (hasColor3) { + Color3f c3f[] = new Color3f[indices.count] ; + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + v3f[i] = v.normal ; + c3f[i] = v.color3 ; + } + ga.setColors(0, c3f) ; + + } else if (hasColor4) { + Color4f c4f[] = new Color4f[indices.count] ; + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + v3f[i] = v.normal ; + c4f[i] = v.color4 ; + } + ga.setColors(0, c4f) ; + + } else { + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + v3f[i] = v.normal ; + } + } + ga.setNormals(0, v3f) ; + + } else { + if (hasColor3) { + Color3f c3f[] = new Color3f[indices.count] ; + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + c3f[i] = v.color3 ; + } + ga.setColors(0, c3f) ; + + } else if (hasColor4) { + Color4f c4f[] = new Color4f[indices.count] ; + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + c4f[i] = v.color4 ; + } + ga.setColors(0, c4f) ; + + } else { + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + } + } + } + ga.setCoordinates(0, p3f) ; + } + + /** + * Output a PointArray. + */ + PointArray toPointArray() { + int size = vertices.size() ; + + if (size > 0) { + PointArray pa = new PointArray(size, vertexFormat) ; + GeneralizedStrip.IntList il = new GeneralizedStrip.IntList(size) ; + + il.fillAscending() ; + copyVertexData(pa, il) ; + + vertexCount += size ; + return pa ; + } + else + return null ; + } + + /** + * Output a TriangleArray. + */ + TriangleArray toTriangleArray() { + int vertices[] = GeneralizedStrip.toTriangles(this, frontFace) ; + + if (vertices != null) { + TriangleArray ta ; + GeneralizedStrip.IntList il ; + + ta = new TriangleArray(vertices.length, vertexFormat) ; + il = new GeneralizedStrip.IntList(vertices) ; + copyVertexData(ta, il) ; + + vertexCount += vertices.length ; + triangleCount += vertices.length/3 ; + return ta ; + } else + return null ; + } + + /** + * Output a LineStripArray. + */ + LineStripArray toLineStripArray() { + GeneralizedStrip.StripArray stripArray = + GeneralizedStrip.toLineStrips(this) ; + + if (stripArray != null) { + LineStripArray lsa ; + lsa = new LineStripArray(stripArray.vertices.count, + vertexFormat, + stripArray.stripCounts.trim()) ; + + copyVertexData(lsa, stripArray.vertices) ; + + vertexCount += stripArray.vertices.count ; + stripCount += stripArray.stripCounts.count ; + return lsa ; + } else + return null ; + } + + /** + * Output a TriangleStripArray. + */ + TriangleStripArray toTriangleStripArray() { + GeneralizedStrip.StripArray stripArray = + GeneralizedStrip.toTriangleStrips(this, frontFace) ; + + if (stripArray != null) { + TriangleStripArray tsa ; + tsa = new TriangleStripArray(stripArray.vertices.count, + vertexFormat, + stripArray.stripCounts.trim()) ; + + copyVertexData(tsa, stripArray.vertices) ; + + vertexCount += stripArray.vertices.count ; + stripCount += stripArray.stripCounts.count ; + return tsa ; + } else + return null ; + } + + /** + * Output triangle strip and triangle fan arrays. + * @return a 2-element array of GeometryStripArray; element 0 if non-null + * will contain a TriangleStripArray, and element 1 if non-null will + * contain a TriangleFanArray. + */ + GeometryStripArray[] toStripAndFanArrays() { + GeneralizedStrip.StripArray stripArray[] = + GeneralizedStrip.toStripsAndFans(this, frontFace) ; + + GeometryStripArray gsa[] = new GeometryStripArray[2] ; + + if (stripArray[0] != null) { + gsa[0] = new TriangleStripArray(stripArray[0].vertices.count, + vertexFormat, + stripArray[0].stripCounts.trim()) ; + + copyVertexData(gsa[0], stripArray[0].vertices) ; + + vertexCount += stripArray[0].vertices.count ; + stripCount += stripArray[0].stripCounts.count ; + } + + if (stripArray[1] != null) { + gsa[1] = new TriangleFanArray(stripArray[1].vertices.count, + vertexFormat, + stripArray[1].stripCounts.trim()) ; + + copyVertexData(gsa[1], stripArray[1].vertices) ; + + vertexCount += stripArray[1].vertices.count ; + stripCount += stripArray[1].stripCounts.count ; + } + return gsa ; + } + + /** + * Output triangle strip and and triangle arrays. + * @return a 2-element array of GeometryArray; element 0 if non-null + * will contain a TriangleStripArray, and element 1 if non-null will + * contain a TriangleArray. + */ + GeometryArray[] toStripAndTriangleArrays() { + GeneralizedStrip.StripArray stripArray[] = + GeneralizedStrip.toStripsAndTriangles(this, frontFace, 4, 12) ; + + GeometryArray ga[] = new GeometryArray[2] ; + + if (stripArray[0] != null) { + ga[0] = new TriangleStripArray(stripArray[0].vertices.count, + vertexFormat, + stripArray[0].stripCounts.trim()) ; + + copyVertexData(ga[0], stripArray[0].vertices) ; + + vertexCount += stripArray[0].vertices.count ; + stripCount += stripArray[0].stripCounts.count ; + } + + if (stripArray[1] != null) { + ga[1] = new TriangleArray(stripArray[1].vertices.count, + vertexFormat) ; + + copyVertexData(ga[1], stripArray[1].vertices) ; + triangleCount += stripArray[1].vertices.count/3 ; + } + return ga ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/GeometryCompressor.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeometryCompressor.java new file mode 100644 index 0000000..90b0e89 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeometryCompressor.java @@ -0,0 +1,204 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import java.io.IOException; +import javax.vecmath.Point3d; + +/** + * A GeometryCompressor takes a stream of geometric elements and + * quantization parameters (the CompressionStream object) and + * compresses it into a stream of commands as defined by appendix B + * of the Java 3D specification. The resulting data may be output + * in the form of a CompressedGeometryData node component or appended + * to a CompressedGeometryFile. + * + * @see CompressionStream + * @see CompressedGeometryData + * @see CompressedGeometryFile + * + * @since Java 3D 1.5 + */ +public class GeometryCompressor { + private static final boolean benchmark = false ; + private static final boolean printStream = false ; + private static final boolean printHuffman = false ; + + private HuffmanTable huffmanTable ; + private CommandStream outputBuffer ; + private CompressedGeometryData.Header cgHeader ; + private long startTime ; + + public GeometryCompressor() { + // Create a compressed geometry header. + cgHeader = new CompressedGeometryData.Header() ; + + // v1.0.0 - pre-FCS + // v1.0.1 - fixed winding order, FCS version (J3D 1.1.2) + // v1.0.2 - normal component length maximum 6, LII hardware (J3D 1.2) + cgHeader.majorVersionNumber = 1 ; + cgHeader.minorVersionNumber = 0 ; + cgHeader.minorMinorVersionNumber = 2 ; + } + + /** + * Compress a stream into a CompressedGeometryData node component. + * + * + * @param stream CompressionStream containing the geometry to be compressed + * @return a CompressedGeometryData node component + */ + public CompressedGeometryData compress(CompressionStream stream) { + CompressedGeometryData cg ; + + compressStream(stream) ; + cg = new CompressedGeometryData(cgHeader, outputBuffer.getBytes()) ; + + outputBuffer.clear() ; + return cg ; + } + + /** + * Compress a stream and append the output to a CompressedGeometryFile. + * The resource remains open for subsequent updates; its close() method + * must be called to create a valid compressed geometry resource file. + * + * @param stream CompressionStream containing the geometry to be compressed + * @param f a currently open CompressedGeometryFile with write access + * @exception IOException if write fails + */ + public void compress(CompressionStream stream, CompressedGeometryFile f) + throws IOException { + + compressStream(stream) ; + f.write(cgHeader, outputBuffer.getBytes()) ; + + outputBuffer.clear() ; + } + + // + // Compress the stream and put the results in the output buffer. + // Set up the CompressedGeometryData.Header object. + // + private void compressStream(CompressionStream stream) { + if (benchmark) startTime = System.currentTimeMillis() ; + + // Create the Huffman table. + huffmanTable = new HuffmanTable() ; + + // Quantize the stream, compute deltas between consecutive elements if + // possible, and histogram the data length distribution. + stream.quantize(huffmanTable) ; + + // Compute tags for stream tokens. + huffmanTable.computeTags() ; + + // Create the output buffer and assemble the compressed output. + outputBuffer = new CommandStream(stream.getByteCount() / 3) ; + stream.outputCommands(huffmanTable, outputBuffer) ; + + // Print any desired info. + if (benchmark) printBench(stream) ; + if (printStream) stream.print() ; + if (printHuffman) huffmanTable.print() ; + + // Set up the compressed geometry header object. + cgHeader.bufferType = stream.streamType ; + cgHeader.bufferDataPresent = 0 ; + cgHeader.lowerBound = new Point3d(stream.ncBounds[0]) ; + cgHeader.upperBound = new Point3d(stream.ncBounds[1]) ; + + if (stream.vertexNormals) + cgHeader.bufferDataPresent |= + CompressedGeometryData.Header.NORMAL_IN_BUFFER ; + + if (stream.vertexColor3 || stream.vertexColor4) + cgHeader.bufferDataPresent |= + CompressedGeometryData.Header.COLOR_IN_BUFFER ; + + if (stream.vertexColor4) + cgHeader.bufferDataPresent |= + CompressedGeometryData.Header.ALPHA_IN_BUFFER ; + + cgHeader.start = 0 ; + cgHeader.size = outputBuffer.getByteCount() ; + + // Clear the huffman table for next use. + huffmanTable.clear() ; + } + + private void printBench(CompressionStream stream) { + long t = System.currentTimeMillis() - startTime ; + int vertexCount = stream.getVertexCount() ; + int meshReferenceCount = stream.getMeshReferenceCount() ; + int totalVertices = meshReferenceCount + vertexCount ; + float meshPercent = 100f * meshReferenceCount/(float)totalVertices ; + + float compressionRatio = + stream.getByteCount() / ((float)outputBuffer.getByteCount()) ; + + int vertexBytes = + 12 + (stream.vertexColor3 ? 12 : 0) + + (stream.vertexColor4 ? 16 : 0) + (stream.vertexNormals ? 12 : 0) ; + + float compressedVertexBytes = + outputBuffer.getByteCount() / (float)totalVertices ; + + System.out.println + ("\nGeometryCompressor:\n" + totalVertices + " total vertices\n" + + vertexCount + " streamed vertices\n" + meshReferenceCount + + " mesh buffer references (" + meshPercent + "%)\n" + + stream.getByteCount() + " bytes streamed geometry compressed to " + + outputBuffer.getByteCount() + " in " + (t/1000f) + " sec\n" + + (stream.getByteCount()/(float)t) + " kbytes/sec, " + + "stream compression ratio " + compressionRatio + "\n\n" + + vertexBytes + " original bytes per vertex, " + + compressedVertexBytes + " compressed bytes per vertex\n" + + "total vertex compression ratio " + + (vertexBytes / (float)compressedVertexBytes) + "\n\n" + + "lower bound " + stream.ncBounds[0].toString() +"\n" + + "upper bound " + stream.ncBounds[1].toString()) ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/GeometryDecompressor.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeometryDecompressor.java new file mode 100644 index 0000000..62a5646 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeometryDecompressor.java @@ -0,0 +1,1236 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import com.sun.j3d.internal.J3dUtilsI18N; +import javax.vecmath.Color4f; +import javax.vecmath.Point3f; +import javax.vecmath.Vector3f; + +/** + * This abstract class provides the base methods needed to create a geometry + * decompressor. Subclasses must implement a backend to handle the output, + * consisting of a generalized triangle strip, line strip, or point array, + * along with possible global color and normal changes. + */ +abstract class GeometryDecompressor { + private static final boolean debug = false ; + private static final boolean benchmark = false ; + + /** + * Compressed geometry format version supported. + */ + static final int majorVersionNumber = 1 ; + static final int minorVersionNumber = 0 ; + static final int minorMinorVersionNumber = 2 ; + + /** + * This method is called when a SetState command is encountered in the + * decompression stream. + * + * @param bundlingNorm true indicates normals are bundled with vertices + * @param bundlingColor true indicates colors are bundled with vertices + * @param doingAlpha true indicates alpha values are bundled with vertices + */ + abstract void outputVertexFormat(boolean bundlingNorm, + boolean bundlingColor, + boolean doingAlpha) ; + + /** + * This method captures the vertex output of the decompressor. The normal + * or color references may be null if the corresponding data is not + * bundled with the vertices in the compressed geometry buffer. Alpha + * values may be included in the color. + * + * @param position The coordinates of the vertex. + * @param normal The normal bundled with the vertex. May be null. + * @param color The color bundled with the vertex. May be null. + * Alpha may be present. + * @param vertexReplaceCode Specifies the generalized strip flag + * that is bundled with each vertex. + * @see GeneralizedStripFlags + * @see CompressedGeometryHeader + */ + abstract void outputVertex(Point3f position, Vector3f normal, + Color4f color, int vertexReplaceCode) ; + + /** + * This method captures the global color output of the decompressor. It + * is only invoked if colors are not bundled with the vertex data. The + * global color applies to all succeeding vertices until the next time the + * method is invoked. + * + * @param color The current global color. + */ + abstract void outputColor(Color4f color) ; + + /** + * This method captures the global normal output of the decompressor. It + * is only invoked if normals are not bundled with the vertex data. The + * global normal applies to all succeeding vertices until the next time the + * method is invoked. + * + * @param normal The current global normal. + */ + abstract void outputNormal(Vector3f normal) ; + + // Geometry compression opcodes. + private static final int GC_VERTEX = 0x40 ; + private static final int GC_SET_NORM = 0xC0 ; + private static final int GC_SET_COLOR = 0x80 ; + private static final int GC_MESH_B_R = 0x20 ; + private static final int GC_SET_STATE = 0x18 ; + private static final int GC_SET_TABLE = 0x10 ; + private static final int GC_PASS_THROUGH = 0x08 ; + private static final int GC_EOS = 0x00 ; + private static final int GC_V_NO_OP = 0x01 ; + private static final int GC_SKIP_8 = 0x07 ; + + // Three 64-entry decompression tables are used: gctables[0] for + // positions, gctables[1] for colors, and gctables[2] for normals. + private HuffmanTableEntry gctables[][] ; + + /** + * Decompression table entry. + */ + static class HuffmanTableEntry { + int tagLength, dataLength ; + int rightShift, absolute ; + + public String toString() { + return + " tag length: " + tagLength + + " data length: " + dataLength + + " shift: " + rightShift + + " abs/rel: " + absolute ; + } + } + + // A 16-entry mesh buffer is used. + private MeshBufferEntry meshBuffer[] ; + private int meshIndex = 15 ; + private int meshState ; + + // meshState values. These are needed to determine if colors and/or + // normals should come from meshBuffer or from SetColor or SetNormal. + private static final int USE_MESH_NORMAL = 0x1 ; + private static final int USE_MESH_COLOR = 0x2 ; + + /** + * Mesh buffer entry containing position, normal, and color. + */ + static class MeshBufferEntry { + short x, y, z ; + short octant, sextant, u, v ; + short r, g, b, a ; + } + + // Geometry compression state variables. + private short curX, curY, curZ ; + private short curR, curG, curB, curA ; + private int curSex, curOct, curU, curV ; + + // Current vertex data. + private Point3f curPos ; + private Vector3f curNorm ; + private Color4f curColor ; + private int repCode ; + + // Flags indicating what data is bundled with the vertex. + private boolean bundlingNorm ; + private boolean bundlingColor ; + private boolean doingAlpha ; + + // Internal decompression buffering variables. + private int currentHeader = 0 ; + private int nextHeader = 0 ; + private int bitBuffer = 0 ; + private int bitBufferCount = 32 ; + + // Used for benchmarking if so configured. + private long startTime ; + private int vertexCount ; + + // Bit-field masks: BMASK[i] = (1<<i)-1 + private static final int BMASK[] = { + 0x0, 0x1, 0x3, 0x7, + 0xF, 0x1F, 0x3F, 0x7F, + 0xFF, 0x1FF, 0x3FF, 0x7FF, + 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, + 0xFFFF, 0x1FFFF, 0x3FFFF, 0x7FFFF, + 0xFFFFF, 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, + 0xFFFFFF, 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, + 0xFFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, + 0xFFFFFFFF, + } ; + + // A reference to the compressed data and the current offset. + private byte gcData[] ; + private int gcIndex ; + + // The normals table for decoding 6-bit [u,v] spherical sextant coordinates. + private static final double gcNormals[][][] ; + private static final double NORMAL_MAX_Y_ANG = 0.615479709 ; + private static final boolean printNormalTable = false ; + + /** + * Initialize the normals table. + */ + static { + int i, j, inx, iny, inz ; + double th, psi, qnx, qny, qnz ; + + gcNormals = new double[65][65][3] ; + + for (i = 0 ; i < 65 ; i++) { + for (j = 0 ; j < 65 ; j++) { + if (i+j > 64) continue ; + + psi = NORMAL_MAX_Y_ANG * (i / 64.0) ; + th = Math.asin(Math.tan(NORMAL_MAX_Y_ANG * ((64-j)/64.0))) ; + + qnx = Math.cos(th) * Math.cos(psi) ; + qny = Math.sin(psi) ; + qnz = Math.sin(th) * Math.cos(psi) ; + + // Convert the floating point normal to s1.14 bit notation, + // then back again. + qnx = qnx*16384.0 ; inx = (int)qnx ; + qnx = (double)inx ; qnx = qnx/16384.0 ; + + qny = qny*16384.0 ; iny = (int)qny ; + qny = (double)iny ; qny = qny/16384.0 ; + + qnz = qnz*16384.0 ; inz = (int)qnz ; + qnz = (double)inz ; qnz = qnz/16384.0 ; + + gcNormals[i][j][0] = qnx ; + gcNormals[i][j][1] = qny ; + gcNormals[i][j][2] = qnz ; + } + } + + if (printNormalTable) { + System.out.println("struct {") ; + System.out.println(" double nx, ny, nz ;") ; + System.out.println("} gcNormals[65][65] = {"); + for (i = 0 ; i <= 64 ; i++) { + System.out.println("{") ; + for (j = 0 ; j <= 64 ; j++) { + if (j+i > 64) continue ; + System.out.println("{ " + gcNormals[i][j][0] + + ", " + gcNormals[i][j][1] + + ", " + gcNormals[i][j][2] + " }") ; + } + System.out.println("},") ; + } + System.out.println("}") ; + } + } + + // + // The constructor. + // + GeometryDecompressor() { + curPos = new Point3f() ; + curNorm = new Vector3f() ; + curColor = new Color4f() ; + gctables = new HuffmanTableEntry[3][64] ; + + for (int i = 0 ; i < 64 ; i++) { + gctables[0][i] = new HuffmanTableEntry() ; + gctables[1][i] = new HuffmanTableEntry() ; + gctables[2][i] = new HuffmanTableEntry() ; + } + + meshBuffer = new MeshBufferEntry[16] ; + for (int i = 0 ; i < 16 ; i++) + meshBuffer[i] = new MeshBufferEntry() ; + } + + /** + * Check version numbers and return true if compatible. + */ + boolean checkVersion(int majorVersionNumber, int minorVersionNumber) { + return ((majorVersionNumber < this.majorVersionNumber) || + ((majorVersionNumber == this.majorVersionNumber) && + (minorVersionNumber <= this.minorVersionNumber))) ; + } + + /** + * Decompress data and invoke abstract output methods. + * + * @param start byte offset to start of compressed geometry in data array + * @param length size of compressed geometry in bytes + * @param data array containing compressed geometry buffer of the + * specified length at the given offset from the start of the array + * @exception ArrayIndexOutOfBoundsException if start+length > data size + */ + void decompress(int start, int length, byte data[]) { + if (debug) + System.out.println("GeometryDecompressor.decompress\n" + + " start: " + start + + " length: " + length + + " data array size: " + data.length) ; + if (benchmark) + benchmarkStart(length) ; + + if (start+length > data.length) + throw new ArrayIndexOutOfBoundsException + (J3dUtilsI18N.getString("GeometryDecompressor0")) ; + + // Set reference to compressed data and skip to start of data. + gcData = data ; + gcIndex = start ; + + // Initialize state. + bitBufferCount = 0 ; + meshState = 0 ; + bundlingNorm = false ; + bundlingColor = false ; + doingAlpha = false ; + repCode = 0 ; + + // Headers are interleaved for hardware implementations, so the + // first is always a nullop. + nextHeader = GC_V_NO_OP ; + + // Enter decompression loop. + while (gcIndex < start+length) + processDecompression() ; + + // Finish out any bits left in bitBuffer. + while (bitBufferCount > 0) + processDecompression() ; + + if (benchmark) + benchmarkPrint(length) ; + } + + // + // Return the next bitCount bits of compressed data. + // + private int getBits(int bitCount, String d) { + int bits ; + + if (debug) + System.out.print(" getBits(" + bitCount + ") " + d + ", " + + bitBufferCount + " available at gcIndex " + + gcIndex) ; + + if (bitCount == 0) { + if (debug) System.out.println(": got 0x0") ; + return 0 ; + } + + if (bitBufferCount == 0) { + bitBuffer = (((gcData[gcIndex++] & 0xff) << 24) | + ((gcData[gcIndex++] & 0xff) << 16) | + ((gcData[gcIndex++] & 0xff) << 8) | + ((gcData[gcIndex++] & 0xff))) ; + + bitBufferCount = 32 ; + } + + if (bitBufferCount >= bitCount) { + bits = (bitBuffer >>> (32 - bitCount)) & BMASK[bitCount] ; + bitBuffer = bitBuffer << bitCount ; + bitBufferCount -= bitCount ; + } else { + bits = (bitBuffer >>> (32 - bitCount)) & BMASK[bitCount] ; + bits = bits >>> (bitCount - bitBufferCount) ; + bits = bits << (bitCount - bitBufferCount) ; + + bitBuffer = (((gcData[gcIndex++] & 0xff) << 24) | + ((gcData[gcIndex++] & 0xff) << 16) | + ((gcData[gcIndex++] & 0xff) << 8) | + ((gcData[gcIndex++] & 0xff))) ; + + bits = bits | + ((bitBuffer >>> (32 - (bitCount - bitBufferCount))) & + BMASK[bitCount - bitBufferCount]) ; + + bitBuffer = bitBuffer << (bitCount - bitBufferCount) ; + bitBufferCount = 32 - (bitCount - bitBufferCount) ; + } + + if (debug) + System.out.println(": got 0x" + Integer.toHexString(bits)) ; + + return bits ; + } + + // + // Shuffle interleaved headers and opcodes. + // + private void processDecompression() { + int mbp ; + currentHeader = nextHeader ; + + if ((currentHeader & 0xC0) == GC_VERTEX) { + // Process a vertex. + if (!bundlingNorm && !bundlingColor) { + // get next opcode, process current position opcode + nextHeader = getBits(8, "header") ; + mbp = processDecompressionOpcode(0) ; + + } else if (bundlingNorm && !bundlingColor) { + // get normal header, process current position opcode + nextHeader = getBits(6, "normal") ; + mbp = processDecompressionOpcode(0) ; + currentHeader = nextHeader | GC_SET_NORM ; + + // get next opcode, process current normal opcode + nextHeader = getBits(8, "header") ; + processDecompressionOpcode(mbp) ; + + } else if (!bundlingNorm && bundlingColor) { + // get color header, process current position opcode + nextHeader = getBits(6, "color") ; + mbp = processDecompressionOpcode(0) ; + currentHeader = nextHeader | GC_SET_COLOR ; + + // get next opcode, process current color opcode + nextHeader = getBits(8, "header") ; + processDecompressionOpcode(mbp) ; + + } else { + // get normal header, process current position opcode + nextHeader = getBits(6, "normal") ; + mbp = processDecompressionOpcode(0) ; + currentHeader = nextHeader | GC_SET_NORM ; + + // get color header, process current normal opcode + nextHeader = getBits(6, "color") ; + processDecompressionOpcode(mbp) ; + currentHeader = nextHeader | GC_SET_COLOR ; + + // get next opcode, process current color opcode + nextHeader = getBits(8, "header") ; + processDecompressionOpcode(mbp) ; + } + + // Send out the complete vertex. + outputVertex(curPos, curNorm, curColor, repCode) ; + if (benchmark) vertexCount++ ; + + // meshState bits get turned off in the setColor and setNormal + // routines in order to keep track of what data a mesh buffer + // reference should use. + meshState |= USE_MESH_NORMAL ; + meshState |= USE_MESH_COLOR ; + + } else { + // Non-vertex case: get next opcode, then process current opcode. + nextHeader = getBits(8, "header") ; + processDecompressionOpcode(0) ; + } + } + + // + // Decode the opcode in currentHeader, and dispatch to the appropriate + // processing method. + // + private int processDecompressionOpcode(int mbp) { + if ((currentHeader & 0xC0) == GC_SET_NORM) + processSetNormal(mbp) ; + else if ((currentHeader & 0xC0) == GC_SET_COLOR) + processSetColor(mbp) ; + else if ((currentHeader & 0xC0) == GC_VERTEX) + // Return the state of the mesh buffer push bit + // when processing a vertex. + return processVertex() ; + else if ((currentHeader & 0xE0) == GC_MESH_B_R) { + processMeshBR() ; + + // Send out the complete vertex. + outputVertex(curPos, curNorm, curColor, repCode) ; + if (benchmark) vertexCount++ ; + + // meshState bits get turned off in the setColor and setNormal + // routines in order to keep track of what data a mesh buffer + // reference should use. + meshState |= USE_MESH_NORMAL ; + meshState |= USE_MESH_COLOR ; + } + else if ((currentHeader & 0xF8) == GC_SET_STATE) + processSetState() ; + else if ((currentHeader & 0xF8) == GC_SET_TABLE) + processSetTable() ; + else if ((currentHeader & 0xFF) == GC_EOS) + processEos() ; + else if ((currentHeader & 0xFF) == GC_V_NO_OP) + processVNoop() ; + else if ((currentHeader & 0xFF) == GC_PASS_THROUGH) + processPassThrough() ; + else if ((currentHeader & 0xFF) == GC_SKIP_8) + processSkip8() ; + + return 0 ; + } + + // + // Process a set state opcode. + // + private void processSetState() { + int ii ; + if (debug) + System.out.println("GeometryDecompressor.processSetState") ; + + ii = getBits(3, "bundling") ; + + bundlingNorm = ((currentHeader & 0x1) != 0) ; + bundlingColor = (((ii >>> 2) & 0x1) != 0) ; + doingAlpha = (((ii >>> 1) & 0x1) != 0) ; + + if (debug) + System.out.println(" bundling normal: " + bundlingNorm + + " bundling color: " + bundlingColor + + " alpha present: " + doingAlpha) ; + + // Call the abstract output implementation. + outputVertexFormat(bundlingNorm, bundlingColor, doingAlpha) ; + } + + // + // Process a set decompression table opcode. + // + // Extract the parameters of the table set command, + // and set the approprate table entries. + // + private void processSetTable() { + HuffmanTableEntry gct[] ; + int i, adr, tagLength, dataLength, rightShift, absolute ; + int ii, index ; + + if (debug) + System.out.println("GeometryDecompressor.processSetTable") ; + + // Get reference to approprate 64 entry table. + index = (currentHeader & 0x6) >>> 1 ; + gct = gctables[index] ; + + // Get the remaining bits of the set table command. + ii = getBits(15, "set table") ; + + // Extract the individual fields from the two bit strings. + adr = ((currentHeader & 0x1) << 6) | ((ii >>> 9) & 0x3F) ; + + // Get data length. For positions and colors, 0 really means 16, as 0 + // lengths are meaningless for them. Normal components are allowed to + // have lengths of 0. + dataLength = (ii >>> 5) & 0x0F ; + if (dataLength == 0 && index != 2) + dataLength = 16 ; + + rightShift = ii & 0x0F ; + absolute = (ii >>> 4) & 0x1 ; + + // + // Decode the tag length from the address field by finding the + // first set 1 from the left in the bitfield. + // + for (tagLength = 6 ; tagLength > 0 ; tagLength--) { + if ((adr >> tagLength) != 0) break ; + } + + // Shift the address bits up into place, and off the leading 1. + adr = (adr << (6 - tagLength)) & 0x3F ; + + if (debug) + System.out.println(" table " + ((currentHeader & 0x6) >>> 1) + + " address " + adr + + " tag length " + tagLength + + " data length " + dataLength + + " shift " + rightShift + + " absolute " + absolute) ; + + // Fill in the table fields with the specified values. + for (i = 0 ; i < (1 << (6 - tagLength)) ; i++) { + gct[adr+i].tagLength = tagLength ; + gct[adr+i].dataLength = dataLength ; + gct[adr+i].rightShift = rightShift ; + gct[adr+i].absolute = absolute ; + } + } + + + // + // Process a vertex opcode. Any bundled normal and/or color will be + // processed by separate methods. Return the mesh buffer push indicator. + // + private int processVertex() { + HuffmanTableEntry gct ; + float fX, fY, fZ ; + short dx, dy, dz ; + int mbp, x, y, z, dataLen ; + int ii ; + + // If the next command is a mesh buffer reference + // then use colors and normals from the mesh buffer. + meshState = 0 ; + + // Get a reference to the approprate tag table entry. + gct = gctables[0][currentHeader & 0x3F] ; + + if (debug) System.out.println("GeometryDecompressor.processVertex\n" + + gct.toString()) ; + + // Get the true length of the data. + dataLen = gct.dataLength - gct.rightShift ; + + // Read in the replace code and mesh buffer push bits, + // if they're not in the current header. + if (6 - (3 * dataLen) - gct.tagLength > 0) { + int numBits = 6 - (3 * dataLen) - gct.tagLength ; + int jj ; + + jj = currentHeader & BMASK[numBits] ; + ii = getBits(3 - numBits, "repcode/mbp") ; + ii |= (jj << (3 - numBits)) ; + } + else + ii = getBits(3, "repcode/mbp") ; + + repCode = ii >>> 1 ; + mbp = ii & 0x1 ; + + // Read in x, y, and z components. + x = currentHeader & BMASK[6-gct.tagLength] ; + + if (gct.tagLength + dataLen == 6) { + y = getBits(dataLen, "y") ; + z = getBits(dataLen, "z") ; + } else if (gct.tagLength + dataLen < 6) { + x = x >> (6 - gct.tagLength - dataLen) ; + + y = currentHeader & BMASK[6 - gct.tagLength - dataLen] ; + if (gct.tagLength + 2*dataLen == 6) { + z = getBits(dataLen, "z") ; + } else if (gct.tagLength + 2*dataLen < 6) { + y = y >> (6 - gct.tagLength - 2*dataLen) ; + + z = currentHeader & BMASK[6 - gct.tagLength - 2*dataLen] ; + if (gct.tagLength + 3*dataLen < 6) { + z = z >> (6 - gct.tagLength - 3*dataLen) ; + } else if (gct.tagLength + 3*dataLen > 6) { + ii = getBits(dataLen - (6 - gct.tagLength - 2*dataLen), + "z") ; + z = (z << (dataLen - (6 - gct.tagLength - 2*dataLen))) + | ii ; + } + } else { + ii = getBits(dataLen - (6 - gct.tagLength - dataLen), "y") ; + y = (y << (dataLen - (6 - gct.tagLength - dataLen))) | ii ; + z = getBits(dataLen, "z") ; + } + } else { + ii = getBits(dataLen - (6 - gct.tagLength), "x") ; + x = (x << (dataLen - (6 - gct.tagLength))) | ii ; + y = getBits(dataLen, "y") ; + z = getBits(dataLen, "z") ; + } + + // Sign extend delta x y z components. + x = x << (32 - dataLen) ; x = x >> (32 - dataLen) ; + y = y << (32 - dataLen) ; y = y >> (32 - dataLen) ; + z = z << (32 - dataLen) ; z = z >> (32 - dataLen) ; + + // Normalize values. + dx = (short)(x << gct.rightShift) ; + dy = (short)(y << gct.rightShift) ; + dz = (short)(z << gct.rightShift) ; + + // Update current position, first adding deltas if in relative mode. + if (gct.absolute != 0) { + curX = dx ; curY = dy ; curZ = dz ; + if (debug) System.out.println(" absolute position: " + + curX + " " + curY + " " + curZ) ; + } else { + curX += dx ; curY += dy ; curZ += dz ; + if (debug) System.out.println(" delta position: " + + dx + " " + dy + " " + dz) ; + } + + // Do optional mesh buffer push. + if (mbp != 0) { + // Increment to next position (meshIndex is initialized to 15). + meshIndex = (meshIndex + 1) & 0xF ; + meshBuffer[meshIndex].x = curX ; + meshBuffer[meshIndex].y = curY ; + meshBuffer[meshIndex].z = curZ ; + if (debug) + System.out.println(" pushed position into mesh buffer at " + + meshIndex) ; + } + + // Convert point back to [-1..1] floating point. + fX = curX ; fX /= 32768.0 ; + fY = curY ; fY /= 32768.0 ; + fZ = curZ ; fZ /= 32768.0 ; + if (debug) + System.out.println(" result position " + fX + " " + fY + " " + fZ) ; + + curPos.set(fX, fY, fZ) ; + return mbp ; + } + + + // + // Process a set current normal opcode. + // + private void processSetNormal(int mbp) { + HuffmanTableEntry gct ; + int index, du, dv, n, dataLength ; + int ii ; + + // if next command is a mesh buffer reference, use this normal + meshState &= ~USE_MESH_NORMAL ; + + // use table 2 for normals + gct = gctables[2][currentHeader & 0x3F] ; + + if (debug) + System.out.println("GeometryDecompressor.processSetNormal\n" + + gct.toString()) ; + + // subtract up-shift amount to get true data (u, v) length + dataLength = gct.dataLength - gct.rightShift ; + + if (gct.absolute != 0) { + // + // Absolute normal case. Extract index from 6-bit tag. + // + index = currentHeader & BMASK[6-gct.tagLength] ; + + if (gct.tagLength != 0) { + // read in the rest of the 6-bit sex/oct pair (index) + ii = getBits(6 - (6 - gct.tagLength), "sex/oct") ; + index = (index << (6 - (6 - gct.tagLength))) | ii ; + } + + // read in u and v data + curU = getBits(dataLength, "u") ; + curV = getBits(dataLength, "v") ; + + // normalize u, v, sextant, and octant + curU = curU << gct.rightShift ; + curV = curV << gct.rightShift ; + + curSex = (index >> 3) & 0x7 ; + curOct = index & 0x7 ; + + if (debug) { + if (curSex < 6) + System.out.println(" absolute normal: sex " + curSex + + " oct " + curOct + + " u " + curU + " v " + curV) ; + else + System.out.println(" special normal: sex " + curSex + + " oct " + curOct) ; + } + } else { + // + // Relative normal case. Extract du from 6-bit tag. + // + du = currentHeader & BMASK[6-gct.tagLength] ; + + if (gct.tagLength + dataLength < 6) { + // normalize du, get dv + du = du >> (6 - gct.tagLength - dataLength) ; + dv = currentHeader & BMASK[6 - gct.tagLength - dataLength] ; + + if (gct.tagLength + 2*dataLength < 6) { + // normalize dv + dv = dv >> (6 - gct.tagLength - 2*dataLength) ; + } else if (gct.tagLength + 2*dataLength > 6) { + // read in rest of dv and normalize it + ii = getBits(dataLength - + (6 - gct.tagLength - dataLength), "dv") ; + dv = (dv << (dataLength - + (6 - gct.tagLength - dataLength))) | ii ; + } + } else if (gct.tagLength + dataLength > 6) { + // read in rest of du and normalize it + ii = getBits(dataLength - (6 - gct.tagLength), "du") ; + du = (du << (dataLength - (6 - gct.tagLength))) | ii ; + // read in dv + dv = getBits(dataLength, "dv") ; + } else { + // read in dv + dv = getBits(dataLength, "dv") ; + } + + // Sign extend delta uv components. + du = du << (32 - dataLength) ; du = du >> (32 - dataLength) ; + dv = dv << (32 - dataLength) ; dv = dv >> (32 - dataLength) ; + + // normalize values + du = du << gct.rightShift ; + dv = dv << gct.rightShift ; + + // un-delta + curU += du ; + curV += dv ; + + if (debug) + System.out.println(" delta normal: du " + du + " dv " + dv) ; + + // + // Check for normal wrap. + // + if (! ((curU >= 0) && (curV >= 0) && (curU + curV <= 64))) + if ((curU < 0) && (curV >= 0)) { + // wrap on u, same octant, different sextant + curU = -curU ; + switch (curSex) { + case 0: curSex = 4 ; break ; + case 1: curSex = 5 ; break ; + case 2: curSex = 3 ; break ; + case 3: curSex = 2 ; break ; + case 4: curSex = 0 ; break ; + case 5: curSex = 1 ; break ; + } + } else if ((curU >= 0) && (curV < 0)) { + // wrap on v, same sextant, different octant + curV = -curV ; + switch (curSex) { + case 1: case 5: + curOct = curOct ^ 4 ; // invert x axis + break ; + case 0: case 4: + curOct = curOct ^ 2 ; // invert y axis + break ; + case 2: case 3: + curOct = curOct ^ 1 ; // invert z axis + break ; + } + } else if (curU + curV > 64) { + // wrap on uv, same octant, different sextant + curU = 64 - curU ; + curV = 64 - curV ; + switch (curSex) { + case 0: curSex = 2 ; break ; + case 1: curSex = 3 ; break ; + case 2: curSex = 0 ; break ; + case 3: curSex = 1 ; break ; + case 4: curSex = 5 ; break ; + case 5: curSex = 4 ; break ; + } + } else { + throw new IllegalArgumentException + (J3dUtilsI18N.getString("GeometryDecompressor1")) ; + } + } + + // do optional mesh buffer push + if (mbp != 0) { + if (debug) + System.out.println(" pushing normal into mesh buffer at " + + meshIndex) ; + + meshBuffer[meshIndex].sextant = (short)curSex ; + meshBuffer[meshIndex].octant = (short)curOct ; + meshBuffer[meshIndex].u = (short)curU ; + meshBuffer[meshIndex].v = (short)curV ; + } + + // convert normal back to [-1..1] floating point + indexNormal(curSex, curOct, curU, curV, curNorm) ; + + // a set normal opcode when normals aren't bundled with the vertices + // is a global normal change. + if (! bundlingNorm) outputNormal(curNorm) ; + } + + + // + // Get the floating point normal from its sextant, octant, u, and v. + // + private void indexNormal(int sex, int oct, int u, int v, Vector3f n) { + float nx, ny, nz, t ; + + if (debug) System.out.println(" sextant " + sex + " octant " + oct + + " u " + u + " v " + v) ; + if (sex > 5) { + // special normals + switch (oct & 0x1) { + case 0: // six coordinate axes + switch (((sex & 0x1) << 1) | ((oct & 0x4) >> 2)) { + case 0: nx = 1.0f ; ny = nz = 0.0f ; break ; + case 1: ny = 1.0f ; nx = nz = 0.0f ; break ; + default: + case 2: nz = 1.0f ; nx = ny = 0.0f ; break ; + } + sex = 0 ; oct = (oct & 0x2) >> 1 ; + oct = (oct << 2) | (oct << 1) | oct ; + break ; + case 1: // eight mid + default: + oct = ((sex & 0x1) << 2) | (oct >> 1) ; + sex = 0 ; + nx = ny = nz = (float)(1.0/Math.sqrt(3.0)) ; + break ; + } + if ((oct & 0x1) != 0) nz = -nz ; + if ((oct & 0x2) != 0) ny = -ny ; + if ((oct & 0x4) != 0) nx = -nx ; + + } else { + // regular normals + nx = (float)gcNormals[v][u][0] ; + ny = (float)gcNormals[v][u][1] ; + nz = (float)gcNormals[v][u][2] ; + + // reverse the swap + if ((sex & 0x4) != 0) { t = nx ; nx = nz ; nz = t ; } + if ((sex & 0x2) != 0) { t = ny ; ny = nz ; nz = t ; } + if ((sex & 0x1) != 0) { t = nx ; nx = ny ; ny = t ; } + + // reverse the sign flip + if ((oct & 0x1) != 0) nz = -nz ; + if ((oct & 0x2) != 0) ny = -ny ; + if ((oct & 0x4) != 0) nx = -nx ; + } + + // return resulting normal + n.set(nx, ny, nz) ; + if (debug) + System.out.println(" result normal: " + nx + " " + ny + " " + nz) ; + } + + + // + // Process a set current color command. + // + private void processSetColor(int mbp) { + HuffmanTableEntry gct ; + short dr, dg, db, da ; + float fR, fG, fB, fA ; + int r, g, b, a, index, dataLength ; + int ii ; + + // If the next command is a mesh buffer reference, use this color. + meshState &= ~USE_MESH_COLOR ; + + // Get the huffman table entry. + gct = gctables[1][currentHeader & 0x3F] ; + + if (debug) + System.out.println("GeometryDecompressor.processSetColor\n" + + gct.toString()) ; + + // Get the true length of the data. + dataLength = gct.dataLength - gct.rightShift ; + + // Read in red, green, blue, and possibly alpha. + r = currentHeader & BMASK[6 - gct.tagLength] ; + a = 0 ; + + if (gct.tagLength + dataLength == 6) { + g = getBits(dataLength, "g") ; + b = getBits(dataLength, "b") ; + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + else if (gct.tagLength + dataLength < 6) { + r = r >> (6 - gct.tagLength - dataLength) ; + + g = currentHeader & BMASK[6-gct.tagLength-dataLength] ; + if (gct.tagLength + 2*dataLength == 6) { + b = getBits(dataLength, "b") ; + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + else if (gct.tagLength + 2*dataLength < 6) { + g = g >> (6 - gct.tagLength - 2*dataLength) ; + + b = currentHeader & BMASK[6-gct.tagLength-2*dataLength] ; + if (gct.tagLength + 3*dataLength == 6) { + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + else if (gct.tagLength + 3*dataLength < 6) { + b = b >> (6 - gct.tagLength - 3*dataLength) ; + + if (doingAlpha) { + a = currentHeader & + BMASK[6 - gct.tagLength - 4*dataLength] ; + if (gct.tagLength + 4 * dataLength < 6) { + a = a >> (6 - gct.tagLength - 3*dataLength) ; + } + else if (gct.tagLength + 4 * dataLength > 6) { + ii = getBits(dataLength - + (6-gct.tagLength - 3*dataLength), "a") ; + a = (a << (dataLength - + (6-gct.tagLength - 3*dataLength))) | ii ; + } + } + } else { + ii = getBits(dataLength - + (6 - gct.tagLength - 2*dataLength), "b") ; + b = (b << (dataLength - + (6 - gct.tagLength - 2*dataLength))) | ii ; + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + } else { + ii = getBits(dataLength - (6 - gct.tagLength - dataLength), + "g") ; + g = (g << (dataLength - + (6 - gct.tagLength - dataLength))) | ii ; + b = getBits(dataLength, "b") ; + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + } else { + ii = getBits(dataLength - (6 - gct.tagLength), "r") ; + r = (r << (dataLength - (6 - gct.tagLength))) | ii ; + g = getBits(dataLength, "g") ; + b = getBits(dataLength, "b") ; + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + + // Sign extend delta x y z components. + r <<= (32 - dataLength) ; r >>= (32 - dataLength) ; + g <<= (32 - dataLength) ; g >>= (32 - dataLength) ; + b <<= (32 - dataLength) ; b >>= (32 - dataLength) ; + a <<= (32 - dataLength) ; a >>= (32 - dataLength) ; + + // Normalize values. + dr = (short)(r << gct.rightShift) ; + dg = (short)(g << gct.rightShift) ; + db = (short)(b << gct.rightShift) ; + da = (short)(a << gct.rightShift) ; + + // Update current position, first adding deltas if in relative mode. + if (gct.absolute != 0) { + curR = dr ; curG = dg ; curB = db ; + if (doingAlpha) curA = da ; + if (debug) System.out.println(" absolute color: r " + curR + + " g " + curG + " b " + curB + + " a " + curA) ; + } else { + curR += dr ; curG += dg ; curB += db ; + if (doingAlpha) curA += da ; + if (debug) System.out.println(" delta color: dr " + dr + + " dg " + dg + " db " + db + + " da " + da) ; + } + + // Do optional mesh buffer push. + if (mbp != 0) { + if (debug) + System.out.println(" pushing color into mesh buffer at " + + meshIndex) ; + + meshBuffer[meshIndex].r = curR ; + meshBuffer[meshIndex].g = curG ; + meshBuffer[meshIndex].b = curB ; + meshBuffer[meshIndex].a = curA ; + } + + // Convert point back to [-1..1] floating point. + fR = curR ; fR /= 32768.0 ; + fG = curG ; fG /= 32768.0 ; + fB = curB ; fB /= 32768.0 ; + fA = curA ; fA /= 32768.0 ; + + curColor.set(fR, fG, fB, fA) ; + if (debug) System.out.println(" result color: " + fR + + " " + fG + " " + fB + " " + fA) ; + + // A set color opcode when colors aren't bundled with the vertices + // is a global color change. + if (! bundlingColor) outputColor(curColor) ; + } + + + // + // Process a mesh buffer reference command. + // + private void processMeshBR() { + MeshBufferEntry entry ; + int index, normal ; + int ii ; + + if (debug) + System.out.println("GeometryDecompressor.processMeshBR") ; + + ii = getBits(1, "mbr") ; + + index = (currentHeader >>> 1) & 0xF ; + repCode = ((currentHeader & 0x1) << 1) | ii ; + + // Adjust index to proper place in fifo. + index = (meshIndex - index) & 0xf ; + if (debug) + System.out.println(" using index " + index) ; + + // Get reference to mesh buffer entry. + entry = meshBuffer[index] ; + curX = entry.x ; + curY = entry.y ; + curZ = entry.z ; + + // Convert point back to [-1..1] floating point. + curPos.set(((float)curX)/32768.0f, + ((float)curY)/32768.0f, + ((float)curZ)/32768.0f) ; + + if (debug) System.out.println(" retrieved position " + curPos.x + + " " + curPos.y + " " + curPos.z + + " replace code " + repCode) ; + + // Get mesh buffer normal if previous opcode was not a setNormal. + if (bundlingNorm && ((meshState & USE_MESH_NORMAL) != 0)) { + curSex = entry.sextant ; + curOct = entry.octant ; + curU = entry.u ; + curV = entry.v ; + + // Convert normal back to -1.0 - 1.0 floating point from index. + normal = (curSex<<15) | (curOct<<12) | (curU<<6) | curV ; + + if (debug) System.out.println(" retrieving normal") ; + indexNormal(curSex, curOct, curU, curV, curNorm) ; + } + + // Get mesh buffer color if previous opcode was not a setColor. + if (bundlingColor && ((meshState & USE_MESH_COLOR) != 0)) { + curR = entry.r ; + curG = entry.g ; + curB = entry.b ; + + // Convert point back to -1.0 - 1.0 floating point. + curColor.x = curR ; curColor.x /= 32768.0 ; + curColor.y = curG ; curColor.y /= 32768.0 ; + curColor.z = curB ; curColor.z /= 32768.0 ; + + if (doingAlpha) { + curA = entry.a ; + curColor.w = curA ; curColor.w /= 32768.0 ; + } + if (debug) + System.out.println(" retrieved color " + curColor.x + + " " + curColor.y + " " + curColor.z + + " " + curColor.w) ; + } + + // Reset meshState. + meshState = 0 ; + } + + + // Process a end-of-stream opcode. + private void processEos() { + if (debug) System.out.println("GeometryDecompressor.processEos") ; + } + + // Process a variable length no-op opcode. + private void processVNoop() { + int ii, ct ; + if (debug) System.out.println("GeometryDecompressor.processVNoop") ; + + ct = getBits(5, "noop count") ; + ii = getBits(ct, "noop bits") ; + } + + // Process a pass-through opcode. + private void processPassThrough() { + int ignore ; + if (debug) + System.out.println("GeometryDecompressor.processPassThrough") ; + + ignore = getBits(24, "passthrough") ; + ignore = getBits(32, "passthrough") ; + } + + // Process a skip-8 opcode. + private void processSkip8() { + int skip ; + if (debug) System.out.println("GeometryDecompressor.processSkip8") ; + + skip = getBits(8, "skip8") ; + } + + private void benchmarkStart(int length) { + vertexCount = 0 ; + System.out.println(" GeometryDecompressor: decompressing " + + length + " bytes...") ; + startTime = System.currentTimeMillis() ; + } + + private void benchmarkPrint(int length) { + float t = (System.currentTimeMillis() - startTime) / 1000.0f ; + System.out.println + (" done in " + t + " sec." + "\n" + + " decompressed " + vertexCount + " vertices at " + + (vertexCount/t) + " vertices/sec\n") ; + + System.out.print(" vertex data present: coords") ; + int floatVertexSize = 12 ; + if (bundlingNorm) { + System.out.print(" normals") ; + floatVertexSize += 12 ; + } + if (bundlingColor) { + System.out.println(" colors") ; + floatVertexSize += 12 ; + } + if (doingAlpha) { + System.out.println(" alpha") ; + floatVertexSize += 4 ; + } + System.out.println() ; + + System.out.println + (" bytes of data in generalized strip output: " + + (vertexCount * floatVertexSize) + "\n" + + " compression ratio: " + + (length / (float)(vertexCount * floatVertexSize)) + "\n") ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/GeometryDecompressorShape3D.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeometryDecompressorShape3D.java new file mode 100644 index 0000000..3af98a5 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/GeometryDecompressorShape3D.java @@ -0,0 +1,523 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import com.sun.j3d.internal.J3dUtilsI18N; +import java.util.ArrayList; +import javax.media.j3d.Appearance; +import javax.media.j3d.GeometryArray; +import javax.media.j3d.GeometryStripArray; +import javax.media.j3d.LineStripArray; +import javax.media.j3d.Material; +import javax.media.j3d.PointArray; +import javax.media.j3d.Shape3D; +import javax.media.j3d.TriangleArray; +import javax.media.j3d.TriangleStripArray; +import javax.vecmath.Color4f; +import javax.vecmath.Point3f; +import javax.vecmath.Vector3f; + +/** + * This class implements a Shape3D backend for the abstract + * GeometryDecompressor. + */ +class GeometryDecompressorShape3D extends GeometryDecompressor { + private static final boolean debug = false ; + private static final boolean benchmark = false ; + private static final boolean statistics = false ; + private static final boolean printInfo = debug || benchmark || statistics ; + + // Type of connections in the compressed data: + // TYPE_POINT (1), TYPE_LINE (2), or TYPE_TRIANGLE (4). + private int bufferDataType ; + + // Data bundled with each vertex: bitwise combination of + // NORMAL_IN_BUFFER (1), COLOR_IN_BUFFER (2), ALPHA_IN_BUFFER (4). + private int dataPresent ; + + // List for accumulating the output of the decompressor and converting to + // GeometryArray representations. + private GeneralizedVertexList vlist ; + + // Accumulates Shape3D objects constructed from decompressor output. + private ArrayList shapes ; + + // Decompressor output state variables. + private Color4f curColor ; + private Vector3f curNormal ; + + // Variables for gathering statistics. + private int origVertexCount ; + private int stripCount ; + private int vertexCount ; + private int triangleCount ; + private long startTime ; + private long endTime ; + + // Triangle array type to construct. + private int triOutputType ; + + // Types of triangle output available. + private static final int TRI_SET = 0 ; + private static final int TRI_STRIP_SET = 1 ; + private static final int TRI_STRIP_AND_FAN_SET = 2 ; + private static final int TRI_STRIP_AND_TRI_SET = 3 ; + + // Private convenience copies of various constants. + private static final int TYPE_POINT = + CompressedGeometryRetained.TYPE_POINT ; + private static final int TYPE_LINE = + CompressedGeometryRetained.TYPE_LINE ; + private static final int TYPE_TRIANGLE = + CompressedGeometryRetained.TYPE_TRIANGLE ; + private static final int FRONTFACE_CCW = + GeneralizedStripFlags.FRONTFACE_CCW ; + + /** + * Decompress the given compressed geometry. + * @param cgr CompressedGeometryRetained object with compressed geometry + * @return an array of Shape3D with TriangleArray geometry if compressed + * data contains triangles; otherwise, Shape3D array containing PointArray + * or LineStripArray geometry + * @see CompressedGeometry + * @see GeometryDecompressor + */ + Shape3D[] toTriangleArrays(CompressedGeometryRetained cgr) { + return decompress(cgr, TRI_SET) ; + } + + + /** + * Decompress the given compressed geometry. + * @param cgr CompressedGeometryRetained object with compressed geometry + * @return an array of Shape3D with TriangleStripArray geometry if + * compressed data contains triangles; otherwise, Shape3D array containing + * PointArray or LineStripArray geometry + * @see CompressedGeometry + * @see GeometryDecompressor + */ + Shape3D[] toTriangleStripArrays(CompressedGeometryRetained cgr) { + return decompress(cgr, TRI_STRIP_SET) ; + } + + + /** + * Decompress the given compressed geometry. + * @param cgr CompressedGeometryRetained object with compressed geometry + * @return an array of Shape3D with TriangleStripArray and + * TriangleFanArray geometry if compressed data contains triangles; + * otherwise, Shape3D array containing PointArray or LineStripArray + * geometry + * @see CompressedGeometry + * @see GeometryDecompressor + */ + Shape3D[] toStripAndFanArrays(CompressedGeometryRetained cgr) { + return decompress(cgr, TRI_STRIP_AND_FAN_SET) ; + } + + + /** + * Decompress the given compressed geometry. + * @param cgr CompressedGeometryRetained object with compressed geometry + * @return an array of Shape3D with TriangleStripArray and + * TriangleArray geometry if compressed data contains triangles; + * otherwise, Shape3D array containing PointArray or LineStripArray + * geometry + * @see CompressedGeometry + * @see GeometryDecompressor + */ + Shape3D[] toStripAndTriangleArrays(CompressedGeometryRetained cgr) { + return decompress(cgr, TRI_STRIP_AND_TRI_SET) ; + } + + /** + * Decompress the data contained in a CompressedGeometryRetained and + * return an array of Shape3D objects using the specified triangle output + * type. The triangle output type is ignored if the compressed data + * contains points or lines. + */ + private Shape3D[] decompress(CompressedGeometryRetained cgr, + int triOutputType) { + + if (! checkVersion(cgr.majorVersionNumber, cgr.minorVersionNumber)) { + return null ; + } + + vlist = null ; + curColor = null ; + curNormal = null ; + + // Get descriptors for compressed data. + bufferDataType = cgr.bufferType ; + dataPresent = cgr.bufferContents ; + if (printInfo) beginPrint() ; + + // Initialize the decompressor backend. + this.triOutputType = triOutputType ; + shapes = new ArrayList() ; + + // Call the superclass decompress() method which calls the output + // methods of this subclass. The results are stored in vlist. + super.decompress(cgr.offset, cgr.size, cgr.compressedGeometry) ; + + // Convert the decompressor output to Shape3D objects. + addShape3D() ; + if (printInfo) endPrint() ; + + // Return the fixed-length output array. + Shape3D shapeArray[] = new Shape3D[shapes.size()] ; + return (Shape3D[])shapes.toArray(shapeArray) ; + } + + /** + * Initialize the vertex output list based on the vertex format provided + * by the SetState decompression command. + */ + void outputVertexFormat(boolean bundlingNorm, boolean bundlingColor, + boolean doingAlpha) { + + if (vlist != null) + // Construct shapes using the current vertex format. + addShape3D() ; + + int vertexFormat = GeometryArray.COORDINATES ; + + if (bundlingNorm) { + vertexFormat |= GeometryArray.NORMALS ; + } + + if (bundlingColor) { + if (doingAlpha) { + vertexFormat |= GeometryArray.COLOR_4; + } else { + vertexFormat |= GeometryArray.COLOR_3; + } + } + + vlist = new GeneralizedVertexList(vertexFormat, FRONTFACE_CCW) ; + } + + /** + * Add a new decompressed vertex to the current list. + */ + void outputVertex(Point3f position, Vector3f normal, + Color4f color, int vertexReplaceCode) { + + if (curNormal != null) normal = curNormal ; + vlist.addVertex(position, normal, color, vertexReplaceCode) ; + + if (debug) { + System.out.println(" outputVertex: flag " + vertexReplaceCode) ; + System.out.println(" position " + position.toString()) ; + if (normal != null) + System.out.println(" normal " + normal.toString()) ; + if (color != null) + System.out.println(" color " + color.toString()) ; + } + } + + /** + * Create a Shape3D using the current color for both the ambient and + * diffuse material colors, then start a new vertex list for the new + * color. The outputColor() method is never called if colors are bundled + * with each vertex in the compressed buffer. + */ + void outputColor(Color4f color) { + if (debug) System.out.println(" outputColor: " + color.toString()) ; + + if (vlist.size() > 0) { + // Construct Shape3D using the current color. + addShape3D() ; + + // Start a new vertex list for the new color. + vlist = new GeneralizedVertexList(vlist.vertexFormat, + FRONTFACE_CCW) ; + } + if (curColor == null) curColor = new Color4f() ; + curColor.set(color) ; + } + + /** + * Set the current normal that will be copied to each succeeding vertex + * output by the decompressor. The per-vertex copy is needed since in + * Java 3D a normal is always associated with a vertex. This method is + * never called if normals are bundled with each vertex in the compressed + * buffer. + */ + void outputNormal(Vector3f normal) { + if (debug) System.out.println(" outputNormal: " + normal.toString()) ; + + if ((vlist.vertexFormat & GeometryArray.NORMALS) == 0) { + if (vlist.size() > 0) + // Construct Shape3D using the current vertex format. + addShape3D() ; + + // Start a new vertex list with the new format. + vlist = new GeneralizedVertexList + (vlist.vertexFormat|GeometryArray.NORMALS, FRONTFACE_CCW) ; + } + if (curNormal == null) curNormal = new Vector3f() ; + curNormal.set(normal) ; + } + + /** + * Create a Shape3D object of the desired type from the current vertex + * list. Apply the current color, if non-null, as a Material attribute. + */ + private void addShape3D() { + Material m = new Material() ; + + if (curColor != null) { + if ((vlist.vertexFormat & GeometryArray.COLOR_4) != GeometryArray.COLOR_4) { + m.setAmbientColor(curColor.x, curColor.y, curColor.z) ; + m.setDiffuseColor(curColor.x, curColor.y, curColor.z) ; + } + else { + m.setAmbientColor(curColor.x, curColor.y, curColor.z) ; + m.setDiffuseColor(curColor.x, curColor.y, curColor.z, + curColor.w) ; + } + } + + if ((vlist.vertexFormat & GeometryArray.NORMALS) == 0) + m.setLightingEnable(false) ; + else + m.setLightingEnable(true) ; + + Appearance a = new Appearance() ; + a.setMaterial(m) ; + + switch(bufferDataType) { + case TYPE_TRIANGLE: + switch(triOutputType) { + case TRI_SET: + TriangleArray ta = vlist.toTriangleArray() ; + if (ta != null) + shapes.add(new Shape3D(ta, a)) ; + break ; + case TRI_STRIP_SET: + TriangleStripArray tsa = vlist.toTriangleStripArray() ; + if (tsa != null) + shapes.add(new Shape3D(tsa, a)) ; + break ; + case TRI_STRIP_AND_FAN_SET: + GeometryStripArray gsa[] = vlist.toStripAndFanArrays() ; + if (gsa[0] != null) + shapes.add(new Shape3D(gsa[0], a)) ; + if (gsa[1] != null) + shapes.add(new Shape3D(gsa[1], a)) ; + break ; + case TRI_STRIP_AND_TRI_SET: + GeometryArray ga[] = vlist.toStripAndTriangleArrays() ; + if (ga[0] != null) + shapes.add(new Shape3D(ga[0], a)) ; + if (ga[1] != null) + shapes.add(new Shape3D(ga[1], a)) ; + break ; + default: + throw new IllegalArgumentException + (J3dUtilsI18N.getString("GeometryDecompressorShape3D0")) ; + } + break ; + + case TYPE_LINE: + LineStripArray lsa = vlist.toLineStripArray() ; + if (lsa != null) + shapes.add(new Shape3D(lsa, a)) ; + break ; + + case TYPE_POINT: + PointArray pa = vlist.toPointArray() ; + if (pa != null) + shapes.add(new Shape3D(pa, a)) ; + break ; + + default: + throw new IllegalArgumentException + (J3dUtilsI18N.getString("GeometryDecompressorShape3D1")) ; + } + + if (benchmark || statistics) { + origVertexCount += vlist.size() ; + vertexCount += vlist.vertexCount ; + stripCount += vlist.stripCount ; + triangleCount += vlist.triangleCount ; + } + } + + private void beginPrint() { + System.out.println("\nGeometryDecompressorShape3D") ; + + switch(bufferDataType) { + case TYPE_TRIANGLE: + System.out.println(" buffer TYPE_TRIANGLE") ; + break ; + case TYPE_LINE: + System.out.println(" buffer TYPE_LINE") ; + break ; + case TYPE_POINT: + System.out.println(" buffer TYPE_POINT") ; + break ; + default: + throw new IllegalArgumentException + (J3dUtilsI18N.getString("GeometryDecompressorShape3D1")) ; + } + + System.out.print(" buffer data present: coords") ; + + if ((dataPresent & CompressedGeometryData.Header.NORMAL_IN_BUFFER) != 0) + System.out.print(" normals") ; + if ((dataPresent & CompressedGeometryData.Header.COLOR_IN_BUFFER) != 0) + System.out.print(" colors") ; + if ((dataPresent & CompressedGeometryData.Header.ALPHA_IN_BUFFER) != 0) + System.out.print(" alpha") ; + + System.out.println() ; + + stripCount = 0 ; + vertexCount = 0 ; + triangleCount = 0 ; + origVertexCount = 0 ; + + startTime = System.currentTimeMillis() ; + } + + private void endPrint() { + endTime = System.currentTimeMillis() ; + + if (benchmark || statistics) + printBench() ; + + if (statistics) + printStats() ; + } + + private void printBench() { + float t = (endTime - startTime) / 1000.0f ; + System.out.println + (" decompression + strip conversion took " + t + " sec.") ; + + switch(bufferDataType) { + case TYPE_POINT: + System.out.println + (" points decompressed: " + vertexCount + "\n" + + " net decompression rate: " + (vertexCount/t) + + " points/sec.\n") ; + break ; + case TYPE_LINE: + System.out.println + (" lines decompressed: " + (vertexCount - stripCount) + "\n" + + " net decompression rate: " + ((vertexCount - stripCount)/t) + + " lines/sec.\n") ; + break ; + case TYPE_TRIANGLE: + System.out.println + (" triangles decompressed: " + + (vertexCount - 2*stripCount) + "\n" + + " net decompression rate: " + + ((vertexCount - 2*stripCount)/t) + " triangles/sec.\n") ; + break ; + } + } + + private void printStats() { + switch(triOutputType) { + case TRI_SET: + System.out.println(" using individual triangle output") ; + break ; + case TRI_STRIP_SET: + System.out.println(" using strip output") ; + break ; + case TRI_STRIP_AND_FAN_SET: + System.out.println(" using strips and fans for output") ; + break ; + case TRI_STRIP_AND_TRI_SET: + System.out.println(" using strips and triangles for output") ; + break ; + } + + System.out.print + (" number of Shape3D objects: " + shapes.size() + + "\n number of Shape3D decompressed vertices: ") ; + + if (triOutputType == TRI_SET || bufferDataType == TYPE_POINT) { + System.out.println(vertexCount) ; + } + else if (triOutputType == TRI_STRIP_AND_TRI_SET) { + System.out.println((vertexCount + triangleCount*3) + + "\n number of strips: " + stripCount + + "\n number of individual triangles: " + + triangleCount) ; + if (stripCount > 0) + System.out.println + (" vertices/strip: " + (float)vertexCount/stripCount + + "\n triangles represented in strips: " + + (vertexCount - 2*stripCount)) ; + } + else { + System.out.println(vertexCount + + "\n number of strips: " + stripCount) ; + if (stripCount > 0) + System.out.println + (" vertices/strip: " + (float)vertexCount/stripCount) ; + } + + System.out.print(" vertex data present in last Shape3D: coords") ; + if ((vlist.vertexFormat & GeometryArray.NORMALS) != 0) + System.out.print(" normals") ; + + boolean color4 = + (vlist.vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4; + boolean color3 = !color4 && + (vlist.vertexFormat & GeometryArray.COLOR_3) == GeometryArray.COLOR_3; + if (color3 || color4) { + System.out.print(" colors") ; + if (color4) + System.out.print(" alpha") ; + } + System.out.println() ; + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/HuffmanNode.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/HuffmanNode.java new file mode 100644 index 0000000..c04fa7e --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/HuffmanNode.java @@ -0,0 +1,225 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import java.util.Collection; +import java.util.Comparator; + +/** + * Instances of this class are used as the nodes of binary trees representing + * mappings of tags to compression stream elements. Tags are descriptors + * inserted into the compression command stream that specify the encoding of + * immediately succeeding data elements.<p> + * + * The tag assignments in such a tree are computed from the paths taken from + * the root to the leaf nodes. Each leaf node represents the particular way + * one or more compression stream elements wound up being encoded with respect + * to various combinations of data lengths, shifts, and absolute/relative + * status.<p> + * + * Huffman's algorithm for constructing binary trees with minimal weighted + * path lengths can be used to optimize the bit lengths of the tags with + * respect to the frequency of occurrence of their associated data encodings + * in the compression stream. The weighted path length is the sum of the + * frequencies of all the leaf nodes times their path lengths to the root of + * the tree.<p> + * + * The length of the longest tag determines the size of the table mapping tags + * to data representations. The geometry compression specification limits the + * size of the table to 64 entries, so tags cannot be longer than 6 bits. The + * depth of the tree is reduced through a process of increasing the data + * lengths of less frequently occuring nodes so they can be merged with other + * more frequent nodes. + */ +class HuffmanNode { + int tag, tagLength ; + int shift, dataLength ; + boolean absolute ; + + private int frequency ; + private HuffmanNode child0, child1, mergeNode ; + private boolean merged, unmergeable, cleared ; + + void clear() { + tag = -1 ; + tagLength = -1 ; + + shift = -1 ; + dataLength = -1 ; + absolute = false ; + + child0 = null ; + child1 = null ; + mergeNode = null ; + + frequency = 0 ; + merged = false ; + unmergeable = false ; + cleared = true ; + } + + HuffmanNode() { + clear() ; + } + + HuffmanNode(int length, int shift, boolean absolute) { + this() ; + set(length, shift, absolute) ; + } + + final void set(int length, int shift, boolean absolute) { + this.dataLength = length ; + this.shift = shift ; + this.absolute = absolute ; + this.cleared = false ; + } + + final boolean cleared() { + return cleared ; + } + + final void addCount() { + frequency++ ; + } + + final boolean hasCount() { + return frequency > 0 ; + } + + final boolean tokenEquals(HuffmanNode node) { + return + this.absolute == node.absolute && + this.dataLength == node.dataLength && + this.shift == node.shift ; + } + + void addChildren(HuffmanNode child0, HuffmanNode child1) { + this.child0 = child0 ; + this.child1 = child1 ; + this.frequency = child0.frequency + child1.frequency ; + } + + void collectLeaves(int tag, int tagLength, Collection collection) { + if (child0 == null) { + this.tag = tag ; + this.tagLength = tagLength ; + collection.add(this) ; + } else { + child0.collectLeaves((tag << 1) | 0, tagLength + 1, collection) ; + child1.collectLeaves((tag << 1) | 1, tagLength + 1, collection) ; + } + } + + boolean mergeInto(HuffmanNode node) { + if (this.absolute == node.absolute) { + if (this.dataLength > node.dataLength) + node.dataLength = this.dataLength ; + + if (this.shift < node.shift) + node.shift = this.shift ; + + node.frequency += this.frequency ; + this.mergeNode = node ; + this.merged = true ; + return true ; + + } else + return false ; + } + + int incrementLength() { + if (shift > 0) + shift-- ; + else + dataLength++ ; + + return dataLength - shift ; + } + + final boolean merged() { + return merged ; + } + + final HuffmanNode getMergeNode() { + return mergeNode ; + } + + void setUnmergeable() { + unmergeable = true ; + } + + final boolean unmergeable() { + return unmergeable ; + } + + public String toString() { + return + "shift " + shift + " data length " + dataLength + + (absolute? " absolute " : " relative ") + + "\ntag 0x" + Integer.toHexString(tag) + " tag length " + tagLength + + "\nfrequency: " + frequency ; + } + + /** + * Sorts nodes in ascending order by frequency. + */ + static class FrequencyComparator implements Comparator { + public final int compare(Object o1, Object o2) { + return ((HuffmanNode)o1).frequency - ((HuffmanNode)o2).frequency ; + } + } + + /** + * Sorts nodes in descending order by tag bit length. + */ + static class TagLengthComparator implements Comparator { + public final int compare(Object o1, Object o2) { + return ((HuffmanNode)o2).tagLength - ((HuffmanNode)o1).tagLength ; + } + } + + static FrequencyComparator frequencyComparator = new FrequencyComparator() ; + static TagLengthComparator tagLengthComparator = new TagLengthComparator() ; +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/HuffmanTable.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/HuffmanTable.java new file mode 100644 index 0000000..595a1ea --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/HuffmanTable.java @@ -0,0 +1,483 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.ListIterator; + +/** + * This class maintains a map from compression stream elements (tokens) onto + * HuffmanNode objects. A HuffmanNode contains a tag describing the + * associated token's data length, right shift value, and absolute/relative + * status.<p> + * + * The tags are computed using Huffman's algorithm to build a binary tree with + * a minimal total weighted path length. The frequency of each token is + * used as its node's weight when building the tree. The path length from the + * root to the token's node then indicates the bit length that should be used + * for that token's tag in order to minimize the total size of the compressed + * stream. + */ +class HuffmanTable { + private static final int MAX_TAG_LENGTH = 6 ; + + private HuffmanNode positions[] ; + private HuffmanNode normals[] ; + private HuffmanNode colors[] ; + + /** + * Create a new HuffmanTable with entries for all possible position, + * normal, and color tokens. + */ + HuffmanTable() { + // + // Position and color components can have data lengths up to 16 + // bits, with right shifts up to 15 bits. The position and color + // lookup tables are therefore 2*17*16=544 entries in length to + // account for all possible combinations of data lengths, shifts, + // and relative or absolute status. + // + colors = new HuffmanNode[544] ; + positions = new HuffmanNode[544] ; + + // + // Delta normals can have uv components up to 7 bits in length with + // right shifts up to 6 bits. Absolute normals can have uv components + // up to 6 bits in length with right shifts up to 5 bits. The normal + // lookup table is therefore 2*8*7=112 entries in length. + // + normals = new HuffmanNode[112] ; + } + + private final int getPositionIndex(int len, int shift, boolean absolute) { + return (absolute? 1:0)*272 + len*16 + shift ; + } + + private final int getNormalIndex(int length, int shift, boolean absolute) { + return (absolute? 1:0)*56 + length*7 + shift ; + } + + private final int getColorIndex(int length, int shift, boolean absolute) { + return getPositionIndex(length, shift, absolute) ; + } + + + /** + * Add a position entry with the given length, shift, and absolute + * status. + * + * @param length number of bits in each X, Y, and Z component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous vertex in the compression stream + */ + void addPositionEntry(int length, int shift, boolean absolute) { + addEntry(positions, getPositionIndex(length, shift, absolute), + length, shift, absolute) ; + } + + /** + * Get the position entry associated with the specified length, shift, and + * absolute status. This will contain a tag indicating the actual + * encoding to be used in the compression command stream, not necessarily + * the same as the original length and shift with which the the entry was + * created. + * + * @param length number of bits in each X, Y, and Z component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous vertex in the compression stream + * @return HuffmanNode mapped to the specified parameters + */ + HuffmanNode getPositionEntry(int length, int shift, boolean absolute) { + return getEntry(positions, getPositionIndex(length, shift, absolute)) ; + } + + /** + * Add a color entry with the given length, shift, and absolute + * status. + * + * @param length number of bits in each R, G, B, and A component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous color in the compression stream + */ + void addColorEntry(int length, int shift, boolean absolute) { + addEntry(colors, getColorIndex(length, shift, absolute), + length, shift, absolute) ; + } + + /** + * Get the color entry associated with the specified length, shift, and + * absolute status. This will contain a tag indicating the actual + * encoding to be used in the compression command stream, not necessarily + * the same as the original length and shift with which the the entry was + * created. + * + * @param length number of bits in each R, G, B, and A component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous color in the compression stream + * @return HuffmanNode mapped to the specified parameters + */ + HuffmanNode getColorEntry(int length, int shift, boolean absolute) { + return getEntry(colors, getColorIndex(length, shift, absolute)) ; + } + + /** + * Add a normal entry with the given length, shift, and absolute + * status. + * + * @param length number of bits in each U and V component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous normal in the compression stream + */ + void addNormalEntry(int length, int shift, boolean absolute) { + addEntry(normals, getNormalIndex(length, shift, absolute), + length, shift, absolute) ; + } + + /** + * Get the normal entry associated with the specified length, shift, and + * absolute status. This will contain a tag indicating the actual + * encoding to be used in the compression command stream, not necessarily + * the same as the original length and shift with which the the entry was + * created. + * + * @param length number of bits in each U and V component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous normal in the compression stream + * @return HuffmanNode mapped to the specified parameters + */ + HuffmanNode getNormalEntry(int length, int shift, boolean absolute) { + return getEntry(normals, getNormalIndex(length, shift, absolute)) ; + } + + + private void addEntry(HuffmanNode table[], int index, + int length, int shift, boolean absolute) { + + if (table[index] == null) + table[index] = new HuffmanNode(length, shift, absolute) ; + + else if (table[index].cleared()) + table[index].set(length, shift, absolute) ; + + table[index].addCount() ; + } + + private HuffmanNode getEntry(HuffmanNode table[], int index) { + HuffmanNode t = table[index] ; + + while (t.merged()) + t = t.getMergeNode() ; + + return t ; + } + + private void getEntries(HuffmanNode table[], Collection c) { + for (int i = 0 ; i < table.length ; i++) + if (table[i] != null && !table[i].cleared() && + table[i].hasCount() && !table[i].merged()) + c.add(table[i]) ; + } + + + /** + * Clear this HuffmanTable instance. + */ + void clear() { + for (int i = 0 ; i < positions.length ; i++) + if (positions[i] != null) + positions[i].clear() ; + + for (int i = 0 ; i < colors.length ; i++) + if (colors[i] != null) + colors[i].clear() ; + + for (int i = 0 ; i < normals.length ; i++) + if (normals[i] != null) + normals[i].clear() ; + } + + /** + * Compute optimized tags for each position, color, and normal entry. + */ + void computeTags() { + LinkedList nodeList = new LinkedList() ; + getEntries(positions, nodeList) ; + computeTags(nodeList, 3) ; + + nodeList.clear() ; + getEntries(colors, nodeList) ; + computeTags(nodeList, 3) ; + + nodeList.clear() ; + getEntries(normals, nodeList) ; + computeTags(nodeList, 2) ; + } + + // + // Compute tags for a list of Huffman tokens. + // + private void computeTags(LinkedList nodes, int minComponentCount) { + HuffmanNode node0, node1, node2 ; + + // Return if there's nothing to do. + if (nodes.isEmpty()) + return ; + + while (true) { + // Sort the nodes in ascending order by frequency. + Collections.sort(nodes, HuffmanNode.frequencyComparator) ; + + // Apply Huffman's algorithm to construct a binary tree with a + // minimum total weighted path length. + node0 = (HuffmanNode)nodes.removeFirst() ; + while (nodes.size() > 0) { + node1 = (HuffmanNode)nodes.removeFirst() ; + node2 = new HuffmanNode() ; + + node2.addChildren(node0, node1) ; + addNodeInOrder(nodes, node2, HuffmanNode.frequencyComparator) ; + + node0 = (HuffmanNode)nodes.removeFirst() ; + } + + // node0 is the root of the resulting binary tree. Traverse it + // assigning tags and lengths to the leaf nodes. The leaves are + // collected into the now empty node list. + node0.collectLeaves(0, 0, nodes) ; + + // Sort the nodes in descending order by tag length. + Collections.sort(nodes, HuffmanNode.tagLengthComparator) ; + + // Check for tag length overrun. + if (((HuffmanNode)nodes.getFirst()).tagLength > MAX_TAG_LENGTH) { + // Tokens need to be merged and the tree rebuilt with the new + // combined frequencies. + merge(nodes) ; + + } else { + // Increase tag length + data length if they're too small. + expand(nodes, minComponentCount) ; + break ; + } + } + } + + // + // Merge a token with a long tag into some other token. The merged token + // will be removed from the list along with any duplicate node the merge + // created, reducing the size of the list by 1 or 2 elements until only + // unmergeable tokens are left. + // + private void merge(LinkedList nodes) { + ListIterator i = nodes.listIterator(0) ; + HuffmanNode node0, node1, node2 ; + int index = 0 ; + + while (i.hasNext()) { + // Get the node with the longest possibly mergeable tag. + node0 = (HuffmanNode)i.next() ; + if (node0.unmergeable()) continue ; + + // Try to find a node that can be merged with node0. This is any + // node that matches its absolute/relative status. + i.remove() ; + while (i.hasNext()) { + node1 = (HuffmanNode)i.next() ; + if (node0.mergeInto(node1)) { + // Search for a duplicate of the possibly modified node1 + // and merge into it so that node weights remain valid. + // If a duplicate exists it must be further in the list, + // otherwise node0 would have merged into it. + i.remove() ; + while (i.hasNext()) { + node2 = (HuffmanNode)i.next() ; + if (node1.tokenEquals(node2)) { + node1.mergeInto(node2) ; + return ; + } + } + // node1 has no duplicate, so return it to the list. + i.add(node1) ; + return ; + } + } + + // node0 can't be merged with any other node; it must be the only + // relative or absolute node in the list. Mark it as unmergeable + // to avoid unnecessary searches on subsequent calls to merge() + // and return it to the list. + node0.setUnmergeable() ; + i.add(node0) ; + + // Restart the iteration. + i = nodes.listIterator(0) ; + } + } + + // + // Empty bits within a compression command header are not allowed. If + // the tag length plus the total data length is less than 6 bits then + // the token's length must be increased. + // + private void expand(LinkedList nodes, int minComponentCount) { + Iterator i = nodes.iterator() ; + + while (i.hasNext()) { + HuffmanNode n = (HuffmanNode)i.next() ; + + while (n.tagLength + + (minComponentCount * (n.dataLength - n.shift)) < 6) { + + n.incrementLength() ; + } + } + } + + // + // Insert a node into the correct place in a sorted list of nodes. + // + private void addNodeInOrder(LinkedList l, HuffmanNode node, Comparator c) { + ListIterator i = l.listIterator(0) ; + + while (i.hasNext()) { + HuffmanNode n = (HuffmanNode)i.next() ; + if (c.compare(n, node) > 0) { + n = (HuffmanNode)i.previous() ; + break ; + } + } + i.add(node) ; + } + + /** + * Create compression stream commands for decompressors to use to set up + * their decompression tables. + * + * @param output CommandStream which receives the compression commands + */ + void outputCommands(CommandStream output) { + LinkedList nodeList = new LinkedList() ; + getEntries(positions, nodeList) ; + outputCommands(nodeList, output, CommandStream.POSITION_TABLE) ; + + nodeList.clear() ; + getEntries(colors, nodeList) ; + outputCommands(nodeList, output, CommandStream.COLOR_TABLE) ; + + nodeList.clear() ; + getEntries(normals, nodeList) ; + outputCommands(nodeList, output, CommandStream.NORMAL_TABLE) ; + } + + // + // Output a setTable command for each unique token. + // + private void outputCommands(Collection nodes, + CommandStream output, int tableId) { + + Iterator i = nodes.iterator() ; + while (i.hasNext()) { + HuffmanNode n = (HuffmanNode)i.next() ; + int addressRange = (1 << n.tagLength) | n.tag ; + int dataLength = (n.dataLength == 16? 0 : n.dataLength) ; + + int command = + CommandStream.SET_TABLE | (tableId << 1) | (addressRange >> 6) ; + + long body = + ((addressRange & 0x3f) << 9) | (dataLength << 5) | + (n.absolute? 0x10 : 0) | n.shift ; + + output.addCommand(command, 8, body, 15) ; + } + } + + /** + * Print a collection of HuffmanNode objects to standard out. + * + * @param header descriptive string + * @param nodes Collection of HuffmanNode objects to print + */ + void print(String header, Collection nodes) { + System.out.println(header + "\nentries: " + nodes.size() + "\n") ; + + Iterator i = nodes.iterator() ; + while(i.hasNext()) { + HuffmanNode n = (HuffmanNode)i.next() ; + System.out.println(n.toString() + "\n") ; + } + } + + /** + * Print the contents of this instance to standard out. + */ + void print() { + LinkedList nodeList = new LinkedList() ; + + getEntries(positions, nodeList) ; + Collections.sort(nodeList, HuffmanNode.frequencyComparator) ; + print("\nposition tokens and tags", nodeList) ; + + nodeList.clear() ; + getEntries(colors, nodeList) ; + Collections.sort(nodeList, HuffmanNode.frequencyComparator) ; + print("\ncolor tokens and tags", nodeList) ; + + nodeList.clear() ; + getEntries(normals, nodeList) ; + Collections.sort(nodeList, HuffmanNode.frequencyComparator) ; + print("\nnormal tokens and tags", nodeList) ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/MeshBuffer.java b/src/classes/share/com/sun/j3d/utils/geometry/compression/MeshBuffer.java new file mode 100644 index 0000000..6b15dce --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/MeshBuffer.java @@ -0,0 +1,242 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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 Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry.compression; + +import javax.vecmath.Color3f; +import javax.vecmath.Color4f; +import javax.vecmath.Point3f; +import javax.vecmath.Vector3f; + +/** + * This class mirrors the vertex mesh buffer stack supported by the geometry + * compression semantics. + */ +class MeshBuffer { + // + // The fixed-length mesh buffer stack is represented by circular buffers. + // Three stack representations are provided: vertices, positions, and + // indices. + // + // The vertex representation stores references to CompressionStreamVertex + // objects. The position representation stores references to Point3f, + // Vector3f, Color3f, and Color4f objects, while the index representation + // stores indices into externally maintained arrays of those objects. All + // these representations may be used independently and all provide access + // to the stored references via a mesh buffer index. + // + // In addition, the position and index representations provide lookup + // mechanisms to check if positions or indices exist in the mesh buffer + // and return their mesh buffer indices if they do. This is used to + // implement a limited meshing algorithm which reduces the number of + // vertices when non-stripped abutting facets are added to a compression + // stream. + // + static final int NOT_FOUND = -1 ; + + private static final int SIZE = 16 ; + private static final int NAN_HASH = + new Point3f(Float.NaN, Float.NaN, Float.NaN).hashCode() ; + + private int topIndex = SIZE - 1 ; + private int positionIndices[] = new int[SIZE] ; + private int normalIndices[] = new int[SIZE] ; + private int colorIndices[] = new int[SIZE] ; + + private int topPosition = SIZE - 1 ; + private int positionHashCodes[] = new int[SIZE] ; + private Point3f positions[] = new Point3f[SIZE] ; + private Vector3f normals[] = new Vector3f[SIZE] ; + private Color3f colors3[] = new Color3f[SIZE] ; + private Color4f colors4[] = new Color4f[SIZE] ; + + private int topVertex = SIZE - 1 ; + private CompressionStreamVertex vertices[] = + new CompressionStreamVertex[SIZE] ; + + MeshBuffer() { + for (int i = 0 ; i < SIZE ; i++) { + positionHashCodes[i] = NAN_HASH ; + + positionIndices[i] = NOT_FOUND ; + normalIndices[i] = NOT_FOUND ; + colorIndices[i] = NOT_FOUND ; + } + } + + private static int nextTop(int top) { + // The stack top references an element in the fixed-length backing + // array in which the stack is stored. Stack elements below it have + // decreasing indices into the backing array until element 0, at which + // point the indices wrap to the end of the backing array and back to + // the top. + // + // A push is accomplished by incrementing the stack top in a circular + // buffer and storing the data into the new stack element it + // references. The bottom of the stack is the element with the next + // higher index from the top in the backing array, and is overwritten + // with each new push. + return (top + 1) % SIZE ; + } + + private static int flipOffset(int top, int offset) { + // Flips an offset relative to the beginning of the backing array to + // an offset from the top of the stack. Also works in reverse, from + // an offset from the top of the stack to an offset from the beginning + // of the backing array. + if (offset > top) offset -= SIZE ; + return top - offset ; + } + + // + // Mesh buffer vertex stack. This is currently only used for vertex + // lookup during the quantization pass in order to compute delta values; + // no mesh reference lookup is necessary. + // + void push(CompressionStreamVertex v) { + topVertex = nextTop(topVertex) ; + vertices[topVertex] = v ; + } + + CompressionStreamVertex getVertex(int meshReference) { + return vertices[flipOffset(topVertex, meshReference)] ; + } + + + // + // Mesh buffer index stack and index reference lookup support. + // + void push(int positionIndex, int normalIndex) { + topIndex = nextTop(topIndex) ; + + positionIndices[topIndex] = positionIndex ; + normalIndices[topIndex] = normalIndex ; + } + + void push(int positionIndex, int colorIndex, int normalIndex) { + push(positionIndex, normalIndex) ; + colorIndices[topIndex] = colorIndex ; + } + + int getMeshReference(int positionIndex) { + int index ; + for (index = 0 ; index < SIZE ; index++) + if (positionIndices[index] == positionIndex) + break ; + + if (index == SIZE) return NOT_FOUND ; + return flipOffset(topIndex, index) ; + } + + int getPositionIndex(int meshReference) { + return positionIndices[flipOffset(topIndex, meshReference)] ; + } + + int getColorIndex(int meshReference) { + return colorIndices[flipOffset(topIndex, meshReference)] ; + } + + int getNormalIndex(int meshReference) { + return normalIndices[flipOffset(topIndex, meshReference)] ; + } + + + // + // Mesh buffer position stack and position reference lookup support. + // + void push(Point3f position, Vector3f normal) { + topPosition = nextTop(topPosition) ; + + positionHashCodes[topPosition] = position.hashCode() ; + positions[topPosition] = position ; + normals[topPosition] = normal ; + } + + void push(Point3f position, Color3f color, Vector3f normal) { + push(position, normal) ; + colors3[topPosition] = color ; + } + + void push(Point3f position, Color4f color, Vector3f normal) { + push(position, normal) ; + colors4[topPosition] = color ; + } + + void push(Point3f position, Object color, Vector3f normal) { + push(position, normal) ; + if (color instanceof Color3f) + colors3[topPosition] = (Color3f)color ; + else + colors4[topPosition] = (Color4f)color ; + } + + int getMeshReference(Point3f position) { + int index ; + int hashCode = position.hashCode() ; + + for (index = 0 ; index < SIZE ; index++) + if (positionHashCodes[index] == hashCode) + if (positions[index].equals(position)) + break ; + + if (index == SIZE) return NOT_FOUND ; + return flipOffset(topPosition, index) ; + } + + Point3f getPosition(int meshReference) { + return positions[flipOffset(topPosition, meshReference)] ; + } + + Color3f getColor3(int meshReference) { + return colors3[flipOffset(topPosition, meshReference)] ; + } + + Color4f getColor4(int meshReference) { + return colors4[flipOffset(topPosition, meshReference)] ; + } + + Vector3f getNormal(int meshReference) { + return normals[flipOffset(topPosition, meshReference)] ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/compression/package.html b/src/classes/share/com/sun/j3d/utils/geometry/compression/package.html new file mode 100644 index 0000000..2977b07 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/compression/package.html @@ -0,0 +1,13 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> + <meta content="text/html; charset=ISO-8859-1" + http-equiv="content-type"> + <title>com.sun.j3d.utils.geometry.compression</title> +</head> +<body> +<p>Provides compressed geometry utility classes. + This package supersedes the javax.media.j3d.CompressedGeometry class and + the com.sun.j3d.utils.compression package.</p> +</body> +</html> diff --git a/src/classes/share/com/sun/j3d/utils/image/TextureLoader.java b/src/classes/share/com/sun/j3d/utils/image/TextureLoader.java index 15dc01e..8fae388 100644 --- a/src/classes/share/com/sun/j3d/utils/image/TextureLoader.java +++ b/src/classes/share/com/sun/j3d/utils/image/TextureLoader.java @@ -73,7 +73,7 @@ public class TextureLoader extends Object { /** * Optional flag - specifies that mipmaps are generated for all levels - **/ + */ public static final int GENERATE_MIPMAP = 0x01; /** @@ -81,7 +81,7 @@ public class TextureLoader extends Object { * access the image data by reference * * @since Java 3D 1.2 - **/ + */ public static final int BY_REFERENCE = 0x02; /** @@ -90,10 +90,27 @@ public class TextureLoader extends Object { * lower left * * @since Java 3D 1.2 - **/ + */ public static final int Y_UP = 0x04; /** + * Optional flag - specifies that the ImageComponent2D is allowed + * to have dimensions that are not a power of two. If this flag is set, + * TextureLoader will not perform any scaling of images. If this flag + * is not set, images will be scaled to the nearest power of two. This is + * the default mode. + * <p> + * Note that non-power-of-two textures may not be supported by all graphics + * cards. Applications should check whether a particular Canvas3D supports + * non-power-of-two textures by calling the {@link Canvas3D#queryProperties} + * method, and checking whether the + * <code>textureNonPowerOfTwoAvailable</code> property is set to true. + * + * @since Java 3D 1.5 + */ + public static final int ALLOW_NON_POWER_OF_TWO = 0x08; + + /* * Private declaration for BufferedImage allocation */ private static ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); @@ -107,8 +124,9 @@ public class TextureLoader extends Object { private int textureFormat = Texture.RGBA; private int imageComponentFormat = ImageComponent.FORMAT_RGBA; private int flags; - private boolean byRef; - private boolean yUp; + private boolean byRef = false; + private boolean yUp = false; + private boolean forcePowerOfTwo = true; /** * Contructs a TextureLoader object using the specified BufferedImage @@ -157,6 +175,9 @@ public class TextureLoader extends Object { if ((flags & Y_UP) != 0) { yUp = true; } + if ((flags & ALLOW_NON_POWER_OF_TWO) != 0) { + forcePowerOfTwo = false; + } } /** @@ -216,6 +237,9 @@ public class TextureLoader extends Object { if ((flags & Y_UP) != 0) { yUp = true; } + if ((flags & ALLOW_NON_POWER_OF_TWO) != 0) { + forcePowerOfTwo = false; + } } /** @@ -293,6 +317,9 @@ public class TextureLoader extends Object { if ((flags & Y_UP) != 0) { yUp = true; } + if ((flags & ALLOW_NON_POWER_OF_TWO) != 0) { + forcePowerOfTwo = false; + } } /** @@ -369,6 +396,9 @@ public class TextureLoader extends Object { if ((flags & Y_UP) != 0) { yUp = true; } + if ((flags & ALLOW_NON_POWER_OF_TWO) != 0) { + forcePowerOfTwo = false; + } } @@ -434,8 +464,16 @@ public class TextureLoader extends Object { if (tex == null) { if (bufferedImage==null) return null; - int width = getClosestPowerOf2(bufferedImage.getWidth()); - int height = getClosestPowerOf2(bufferedImage.getHeight()); + int width; + int height; + + if (forcePowerOfTwo) { + width = getClosestPowerOf2(bufferedImage.getWidth()); + height = getClosestPowerOf2(bufferedImage.getHeight()); + } else { + width = bufferedImage.getWidth(); + height = bufferedImage.getHeight(); + } if ((flags & GENERATE_MIPMAP) != 0) { @@ -455,8 +493,17 @@ public class TextureLoader extends Object { byRef, yUp); tex.setImage(i, scaledImageComponents[i]); - if (newW > 1) newW >>= 1; - if (newH > 1) newH >>= 1; + if (forcePowerOfTwo) { + if (newW > 1) newW >>= 1; + if (newH > 1) newH >>= 1; + } else { + if (newW > 1) { + newW = (int) Math.floor(newW / 2.0); + } + if (newH > 1) { + newH = (int) Math.floor(newH / 2.0); + } + } origImage = scaledBufferedImages[i]; } diff --git a/src/classes/share/com/sun/j3d/utils/pickfast/PickTool.java b/src/classes/share/com/sun/j3d/utils/pickfast/PickTool.java index 5fe3d08..9ae035b 100644 --- a/src/classes/share/com/sun/j3d/utils/pickfast/PickTool.java +++ b/src/classes/share/com/sun/j3d/utils/pickfast/PickTool.java @@ -261,7 +261,8 @@ public class PickTool { } /** Sets the PickInfo content flags. The default is PickInfo.NODE. - * @param flags One of the following: + * @param flags specified as one or more individual bits that are + * bitwise "OR"ed together : * <ul> * <code>PickInfo.SCENEGRAPHPATH</code> - request for computed SceneGraphPath.<br> * <code>PickInfo.NODE</code> - request for computed intersected Node.<br> @@ -381,8 +382,8 @@ public class PickTool { } /** Select one of the nodes that intersect the PickShape - @return An array of <code>PickInfo</code> objects which will contain - information about the picked instances. <code>null</code> if nothing + @return A <code>PickInfo</code> object which will contain + information about the picked instance. <code>null</code> if nothing was picked. */ public PickInfo pickAny () { @@ -417,8 +418,8 @@ public class PickTool { intersects the PickShape. See note above to see how "closest" is determined. <p> - @return An array of <code>PickInfo</code> objects which will contain - information about the picked instances. <code>null</code> if nothing + @return A <code>PickInfo</code> object which will contain + information about the picked instance. <code>null</code> if nothing was picked. */ public PickInfo pickClosest () { diff --git a/src/classes/share/com/sun/j3d/utils/picking/PickResult.java b/src/classes/share/com/sun/j3d/utils/picking/PickResult.java index e58b13e..541c244 100644 --- a/src/classes/share/com/sun/j3d/utils/picking/PickResult.java +++ b/src/classes/share/com/sun/j3d/utils/picking/PickResult.java @@ -268,41 +268,6 @@ public class PickResult { } } - // similar to the constructor, resets the data in PickResult so it can be - // reused via a freelist - void reset(SceneGraphPath sgp, PickShape ps) { - firstIntersectOnly = false; - geometryArrays = null; - compressGeomShape3Ds = null; - pickShapeBounds = null; - intersections = null; - pickedSceneGraphPath = sgp; - pickedNode = sgp.getObject(); - localToVWorld = sgp.getTransform(); - pickShape = ps; - initPickShape(); - } - - // similar to the constructor, resets the data in PickResult so - // it can be reused via a freelist - void reset(Node pn, Transform3D l2vw, PickShape ps) { - if ((pn instanceof Shape3D) || (pn instanceof Morph)) { - firstIntersectOnly = false; - geometryArrays = null; - compressGeomShape3Ds = null; - pickShapeBounds = null; - intersections = null; - pickedSceneGraphPath = null; - pickedNode = pn; - localToVWorld = l2vw; - pickShape = ps; - initPickShape(); - } - else { - throw new IllegalArgumentException(); - } - } - void initPickShape() { if(pickShape instanceof PickRay) { if (pickShapeStart == null) pickShapeStart = new Point3d(); @@ -713,7 +678,7 @@ public class PickResult { for (int i=0; i < numPts; i++) { // Need to transform each pnt by localToVWorld. - pnts[i] = getPoint3d(); + pnts[i] = new Point3d(); pnts[i].x = doubleData[offset++]; pnts[i].y = doubleData[offset++]; pnts[i].z = doubleData[offset++]; @@ -726,7 +691,7 @@ public class PickResult { for (int i=0; i < numPts; i++) { // Need to transform each pnt by localToVWorld. - pnts[i] = getPoint3d(); + pnts[i] = new Point3d(); pnts[i].x = floatData[offset++]; pnts[i].y = floatData[offset++]; pnts[i].z = floatData[offset++]; @@ -738,7 +703,7 @@ public class PickResult { for (int i=0; i < numPts; i++) { // Need to transform each pnt by localToVWorld. - pnts[i] = getPoint3d(); + pnts[i] = new Point3d(); pnts[i].set(p3fData[i]); localToVWorld.transform(pnts[i]); } @@ -747,7 +712,7 @@ public class PickResult { for (int i=0; i < numPts; i++) { // Need to transform each pnt by localToVWorld. - pnts[i] = getPoint3d(); + pnts[i] = new Point3d(); pnts[i].set(p3dData[i]); localToVWorld.transform(pnts[i]); } @@ -774,7 +739,7 @@ public class PickResult { for (int i=0; i < numPts; i++) { // Need to transform each pnt by localToVWorld. - pnts[i] = getPoint3d(); + pnts[i] = new Point3d(); pnts[i].x = floatData[offset]; pnts[i].y = floatData[offset+1]; pnts[i].z = floatData[offset+2]; @@ -835,13 +800,6 @@ public class PickResult { } else { throw new RuntimeException ("incorrect class type"); } - - if(retFlag == false) { - for (int i = 0; i < numPts; i++) { - freePoint3d(pnts[i]); - } - } - return retFlag; } @@ -2503,13 +2461,11 @@ public class PickResult { static boolean intersectRay(Point3d coordinates[], PickRay ray, PickIntersection pi) { - Point3d origin = getPoint3d(); - Vector3d direction = getVector3d(); + Point3d origin = new Point3d(); + Vector3d direction = new Vector3d(); boolean result; ray.get (origin, direction); result = intersectRayOrSegment(coordinates, direction, origin, pi, false); - freeVector3d(direction); - freePoint3d(origin); return result; } @@ -2523,9 +2479,9 @@ public class PickResult { Vector3d vec0, vec1, pNrm, tempV3d; Point3d iPnt; - vec0 = getVector3d(); - vec1 = getVector3d(); - pNrm = getVector3d(); + vec0 = new Vector3d(); + vec1 = new Vector3d(); + pNrm = new Vector3d(); double absNrmX, absNrmY, absNrmZ, pD = 0.0; double pNrmDotrDir = 0.0; @@ -2574,11 +2530,6 @@ public class PickResult { origin, direction, pi); - - // put the Vectors on the freelist - freeVector3d(vec0); - freeVector3d(vec1); - freeVector3d(pNrm); return isIntersect; } @@ -2606,14 +2557,11 @@ public class PickResult { break; } } - freeVector3d(vec0); - freeVector3d(vec1); - freeVector3d(pNrm); return isIntersect; } // Plane equation: (p - p0)*pNrm = 0 or p*pNrm = pD; - tempV3d = getVector3d(); + tempV3d = new Vector3d(); tempV3d.set((Tuple3d) coordinates[0]); pD = pNrm.dot(tempV3d); tempV3d.set((Tuple3d) origin); @@ -2629,16 +2577,12 @@ public class PickResult { (isSegment && (dist > 1.0+EPS))) { // Ray intersects the plane behind the ray's origin // or intersect point not fall in Segment - freeVector3d(vec0); - freeVector3d(vec1); - freeVector3d(pNrm); - freeVector3d(tempV3d); return false; } // Now, one thing for sure the ray intersect the plane. // Find the intersection point. - iPnt = getPoint3d(); + iPnt = new Point3d(); iPnt.x = origin.x + direction.x * dist; iPnt.y = origin.y + direction.y * dist; iPnt.z = origin.z + direction.z * dist; @@ -2792,11 +2736,6 @@ public class PickResult { pi.setDistance(dist*direction.length()); pi.setPointCoordinatesVW(iPnt); } - freeVector3d(vec0); - freeVector3d(vec1); - freeVector3d(pNrm); - freeVector3d(tempV3d); - freePoint3d(iPnt); return isIntersect; } @@ -2807,18 +2746,15 @@ public class PickResult { */ static boolean intersectSegment (Point3d coordinates[], PickSegment segment, PickIntersection pi) { - Point3d start = getPoint3d(); - Point3d end = getPoint3d(); - Vector3d direction = getVector3d(); + Point3d start = new Point3d(); + Point3d end = new Point3d(); + Vector3d direction = new Vector3d(); boolean result; segment.get(start, end); direction.x = end.x - start.x; direction.y = end.y - start.y; direction.z = end.z - start.z; result = intersectRayOrSegment(coordinates, direction, start, pi, true); - freeVector3d(direction); - freePoint3d(start); - freePoint3d(end); return result; } @@ -2991,7 +2927,6 @@ public class PickResult { pi.setDistance(0); } } - //freeVector3d(lDir); return isIntersect; } // Find the inverse. @@ -3043,12 +2978,12 @@ public class PickResult { static boolean intersectCylinder (Point3d coordinates[], PickCylinder cyl, PickIntersection pi) { - Point3d origin = getPoint3d(); - Point3d end = getPoint3d(); - Vector3d direction = getVector3d(); - Point3d iPnt1 = getPoint3d(); - Point3d iPnt2 = getPoint3d(); - Vector3d originToIpnt = getVector3d(); + Point3d origin = new Point3d(); + Point3d end = new Point3d(); + Vector3d direction = new Vector3d(); + Point3d iPnt1 = new Point3d(); + Point3d iPnt2 = new Point3d(); + Vector3d originToIpnt = new Vector3d(); // Get cylinder information cyl.getOrigin (origin); @@ -3064,25 +2999,11 @@ public class PickResult { if (coordinates.length > 2) { if (cyl instanceof PickCylinderRay) { if (intersectRay (coordinates, new PickRay (origin, direction), pi)) { - freePoint3d(origin); - freePoint3d(end); - freeVector3d(direction); - freePoint3d(iPnt1); - freePoint3d(iPnt2); - freeVector3d(originToIpnt); - return true; } } else { if (intersectSegment (coordinates, new PickSegment (origin, end), pi)) { - freePoint3d(origin); - freePoint3d(end); - freeVector3d(direction); - freePoint3d(iPnt1); - freePoint3d(iPnt2); - freeVector3d(originToIpnt); - return true; } } @@ -3107,25 +3028,9 @@ public class PickResult { pi.setPointCoordinatesVW (iPnt2); originToIpnt.sub (iPnt1, origin); pi.setDistance (originToIpnt.length()); - - freePoint3d(origin); - freePoint3d(end); - freeVector3d(direction); - freePoint3d(iPnt1); - freePoint3d(iPnt2); - freeVector3d(originToIpnt); - return true; } } - - freePoint3d(origin); - freePoint3d(end); - freeVector3d(direction); - freePoint3d(iPnt1); - freePoint3d(iPnt2); - freeVector3d(originToIpnt); - return false; } @@ -3136,15 +3041,15 @@ public class PickResult { static boolean intersectCone (Point3d coordinates[], PickCone cone, PickIntersection pi) { - Point3d origin = getPoint3d(); - Point3d end = getPoint3d(); - Vector3d direction = getVector3d(); - Vector3d originToIpnt = getVector3d(); + Point3d origin = new Point3d(); + Point3d end = new Point3d(); + Vector3d direction = new Vector3d(); + Vector3d originToIpnt = new Vector3d(); double distance; - Point3d iPnt1 = getPoint3d(); - Point3d iPnt2 = getPoint3d(); - Vector3d vector = getVector3d(); + Point3d iPnt1 = new Point3d(); + Point3d iPnt2 = new Point3d(); + Vector3d vector = new Vector3d(); // Get cone information cone.getOrigin (origin); @@ -3160,26 +3065,12 @@ public class PickResult { if (coordinates.length > 2) { if (cone instanceof PickConeRay) { if (intersectRay (coordinates, new PickRay (origin, direction), pi)) { - freePoint3d(origin); - freePoint3d(end); - freePoint3d(iPnt1); - freePoint3d(iPnt2); - freeVector3d(direction); - freeVector3d(originToIpnt); - freeVector3d(vector); return true; } } else { if (intersectSegment (coordinates, new PickSegment (origin, end), pi)) { - freePoint3d(origin); - freePoint3d(end); - freePoint3d(iPnt1); - freePoint3d(iPnt2); - freeVector3d(direction); - freeVector3d(originToIpnt); - freeVector3d(vector); return true; } } @@ -3207,27 +3098,9 @@ public class PickResult { // System.out.println ("intersectCone: edge "+i+" intersected"); pi.setPointCoordinatesVW (iPnt2); pi.setDistance (distance); - - freePoint3d(origin); - freePoint3d(end); - freePoint3d(iPnt1); - freePoint3d(iPnt2); - freeVector3d(direction); - freeVector3d(originToIpnt); - freeVector3d(vector); - return true; } } - - freePoint3d(origin); - freePoint3d(end); - freePoint3d(iPnt1); - freePoint3d(iPnt2); - freeVector3d(direction); - freeVector3d(originToIpnt); - freeVector3d(vector); - return false; } @@ -3239,11 +3112,11 @@ public class PickResult { static boolean intersectCylinder (Point3d pt, PickCylinder cyl, PickIntersection pi) { - Point3d origin = getPoint3d(); - Point3d end = getPoint3d(); - Vector3d direction = getVector3d(); - Point3d iPnt = getPoint3d(); - Vector3d originToIpnt = getVector3d(); + Point3d origin = new Point3d(); + Point3d end = new Point3d(); + Vector3d direction = new Vector3d(); + Point3d iPnt = new Point3d(); + Vector3d originToIpnt = new Vector3d(); // Get cylinder information cyl.getOrigin (origin); @@ -3262,19 +3135,8 @@ public class PickResult { pi.setPointCoordinatesVW (pt); originToIpnt.sub (iPnt, origin); pi.setDistance (originToIpnt.length()); - - freePoint3d(origin); - freePoint3d(end); - freeVector3d(direction); - freePoint3d(iPnt); - freeVector3d(originToIpnt); return true; } - freePoint3d(origin); - freePoint3d(end); - freeVector3d(direction); - freePoint3d(iPnt); - freeVector3d(originToIpnt); return false; } /** @@ -3286,11 +3148,11 @@ public class PickResult { // System.out.println ("Intersect.intersectCone point"); - Point3d origin = getPoint3d(); - Point3d end = getPoint3d(); - Vector3d direction = getVector3d(); - Point3d iPnt = getPoint3d();// the closest point on the cone vector - Vector3d originToIpnt = getVector3d(); + Point3d origin = new Point3d(); + Point3d end = new Point3d(); + Vector3d direction = new Vector3d(); + Point3d iPnt = new Point3d();// the closest point on the cone vector + Vector3d originToIpnt = new Vector3d(); // Get cone information cone.getOrigin (origin); @@ -3312,41 +3174,9 @@ public class PickResult { if (sqDist <= radius*radius) { pi.setPointCoordinatesVW (pt); pi.setDistance (distance); - - freePoint3d(origin); - freePoint3d(end); - freeVector3d(direction); - freePoint3d(iPnt); - freeVector3d(originToIpnt); - return true; } - freePoint3d(origin); - freePoint3d(end); - freeVector3d(direction); - freePoint3d(iPnt); - freeVector3d(originToIpnt); - return false; } - static Vector3d getVector3d() { - //return (Vector3d)UtilFreelistManager.vector3dFreelist.getObject(); - return new Vector3d(); - } - - static void freeVector3d(Vector3d v) { - //UtilFreelistManager.vector3dFreelist.add(v); - } - - static Point3d getPoint3d() { - //return (Point3d)UtilFreelistManager.point3dFreelist.getObject(); - return new Point3d(); - } - - static void freePoint3d(Point3d p) { - //UtilFreelistManager.point3dFreelist.add(p); - } - - } // PickResult diff --git a/src/classes/share/com/sun/j3d/utils/picking/PickTool.java b/src/classes/share/com/sun/j3d/utils/picking/PickTool.java index d188f56..e888b4a 100644 --- a/src/classes/share/com/sun/j3d/utils/picking/PickTool.java +++ b/src/classes/share/com/sun/j3d/utils/picking/PickTool.java @@ -218,13 +218,11 @@ public class PickTool { /** - * This method is not supported. - * - * @exception UnsupportedOperationException this method is not supported - * + * @deprecated This method does nothing other than return its + * input parameter. */ public Locale setBranchGroup (Locale l) { - throw new UnsupportedOperationException(); + return l; } /** @@ -485,8 +483,8 @@ p @param end The end of the segment } /** Select one of the nodes that intersect the PickShape - @return An array of <code>PickResult</code> objects which will contain - information about the picked instances. <code>null</code> if nothing + @return A <code>PickResult</code> object which will contain + information about the picked instance. <code>null</code> if nothing was picked. */ public PickResult pickAny () { @@ -553,8 +551,8 @@ p @param end The end of the segment intersects the PickShape. See note above to see how "closest" is determined. <p> - @return An array of <code>PickResult</code> objects which will contain - information about the picked instances. <code>null</code> if nothing + @return A <code>PickResult</code> object which will contain + information about the picked instance. <code>null</code> if nothing was picked. */ public PickResult pickClosest () { @@ -865,8 +863,7 @@ p @param end The end of the segment PickResult[] pr = new PickResult[sgp.length]; for (i=0; i<sgp.length; i++) { -// pr[i] = new PickResult(sgp[i], pickShape); - pr[i] = getPickResult(sgp[i], pickShape); + pr[i] = new PickResult(sgp[i], pickShape); int numIntersection = pr[i].numIntersections(); if (numIntersection > 0) { // System.out.println ("numIntersection " + numIntersection); @@ -940,9 +937,6 @@ p @param end The end of the segment if (pr == null) { return null; } else { - for (int i = 1; i < pr.length; i++) { - freePickResult(pr[i]); - } return pr[0]; } } @@ -1017,20 +1011,6 @@ p @param end The end of the segment if (l<r) quicksort(i, r, dist, pos); } - PickResult getPickResult(SceneGraphPath spg, PickShape ps) { - //PickResult pr = (PickResult)UtilFreelistManager.pickResultFreelist.getObject(); - PickResult pr = new PickResult(spg,ps); -// if (pr == null) { -// pr = new PickResult(); -// } -// pr.reset(spg, ps); - return pr; - } - - void freePickResult(PickResult pr) { - //UtilFreelistManager.pickResultFreelist.add(pr); - } - } // PickTool diff --git a/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickMouseBehavior.java b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickMouseBehavior.java index c4f14df..87ec0b0 100644 --- a/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickMouseBehavior.java +++ b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickMouseBehavior.java @@ -174,9 +174,5 @@ public abstract class PickMouseBehavior extends Behavior { */ public abstract void updateScene(int xpos, int ypos); - void freePickResult(PickResult pr) { - UtilFreelistManager.pickResultFreelist.add(pr); - } - } diff --git a/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickRotateBehavior.java b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickRotateBehavior.java index 589fcf2..5d6ff05 100644 --- a/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickRotateBehavior.java +++ b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickRotateBehavior.java @@ -142,7 +142,8 @@ public class PickRotateBehavior extends PickMouseBehavior implements MouseBehavi drag.wakeup(); currentTG = tg; // free the PickResult - freePickResult(pr); + // Need to clean up Issue 123 --- Chien + // freePickResult(pr); } else if (callback!=null) callback.transformChanged( PickingCallback.NO_PICK, null ); } diff --git a/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickTranslateBehavior.java b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickTranslateBehavior.java index 7b19149..b2c996e 100644 --- a/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickTranslateBehavior.java +++ b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickTranslateBehavior.java @@ -126,7 +126,8 @@ public class PickTranslateBehavior extends PickMouseBehavior implements MouseBeh translate.setTransformGroup(tg); translate.wakeup(); currentTG = tg; - freePickResult(pr); + // Need to clean up Issue 123 --- Chien + // freePickResult(pr); } else if (callback!=null) callback.transformChanged( PickingCallback.NO_PICK, null ); } diff --git a/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickZoomBehavior.java b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickZoomBehavior.java index 4ec5ebc..bf7e137 100644 --- a/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickZoomBehavior.java +++ b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickZoomBehavior.java @@ -126,7 +126,8 @@ public class PickZoomBehavior extends PickMouseBehavior implements MouseBehavior zoom.setTransformGroup(tg); zoom.wakeup(); currentTG = tg; - freePickResult(pr); + // Need to clean up Issue 123 --- Chien + // freePickResult(pr); } else if (callback!=null) callback.transformChanged( PickingCallback.NO_PICK, null ); } diff --git a/src/classes/share/com/sun/j3d/utils/timer/J3DTimer.java b/src/classes/share/com/sun/j3d/utils/timer/J3DTimer.java index 133529c..984c48e 100644 --- a/src/classes/share/com/sun/j3d/utils/timer/J3DTimer.java +++ b/src/classes/share/com/sun/j3d/utils/timer/J3DTimer.java @@ -42,13 +42,9 @@ * $State$ */ -/** - * A High Resolution operating system dependent interval timer. - */ package com.sun.j3d.utils.timer; /** - * * A High Resolution interval timer. The timer resolution is * operating system dependent and can be queried using * getTimerResolution(). @@ -56,16 +52,21 @@ package com.sun.j3d.utils.timer; * These methods are not reentrant and should not * be called concurrently from multiple threads. * + * @deprecated Use java.lang.System.nanoTime() instead. */ public class J3DTimer { + // Since we can't get the resolution from the JDK, we will hard-code it + // at 1000 (microsecond resolution). + private static final long resolution = 1000L; + /** * Private constructor because users should * not construct instances of this class */ private J3DTimer() { } - + /** * Get the timer value, in nanoseconds. * The initial value of the timer is OS dependent. @@ -73,30 +74,15 @@ public class J3DTimer { * @return The current timer value in nanoseconds. */ public static long getValue() { - return getNativeTimer(); + return System.nanoTime(); } - + /** * Get the nanosecond resolution of the timer * * @return The timer resolution in nanoseconds. */ public static long getResolution() { - return getNativeTimerResolution(); - } - - private static native long getNativeTimer(); - - private static native long getNativeTimerResolution(); - - static { - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Object run() { - System.loadLibrary("j3dutils"); - return null; - } - }); - + return resolution; } } diff --git a/src/classes/share/com/sun/j3d/utils/timer/package.html b/src/classes/share/com/sun/j3d/utils/timer/package.html index 33e3b6b..b842c0a 100644 --- a/src/classes/share/com/sun/j3d/utils/timer/package.html +++ b/src/classes/share/com/sun/j3d/utils/timer/package.html @@ -3,9 +3,9 @@ <head> <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type"> - <title>com.sun.j3d.XXXXX</title> + <title>com.sun.j3d.utils.timer</title> </head> <body> -<p>Provides a high-resolution, operating-system-dependent interval timer.</p> +<p><i><b>Deprecated</b>: Use java.lang.System.nanoTime() instead.</i></p> </body> </html> diff --git a/src/classes/share/com/sun/j3d/utils/universe/ViewInfo.java b/src/classes/share/com/sun/j3d/utils/universe/ViewInfo.java index 55df965..92d2cbf 100644 --- a/src/classes/share/com/sun/j3d/utils/universe/ViewInfo.java +++ b/src/classes/share/com/sun/j3d/utils/universe/ViewInfo.java @@ -45,6 +45,7 @@ package com.sun.j3d.utils.universe ; import java.awt.GraphicsConfiguration ; +import java.awt.GraphicsEnvironment; import java.awt.Point ; import java.awt.Rectangle ; import java.text.DecimalFormat ; @@ -2728,7 +2729,14 @@ public class ViewInfo { } } - this.screenBounds = graphicsConfiguration.getBounds() ; + GraphicsConfiguration gc1 = graphicsConfiguration; + // Workaround for Issue 316 - use the default config for screen 0 + // if the graphics config is null + if (gc1 == null) { + gc1 = GraphicsEnvironment.getLocalGraphicsEnvironment(). + getDefaultScreenDevice().getDefaultConfiguration(); + } + this.screenBounds = gc1.getBounds() ; double mpx = screenWidth / (double)screenBounds.width ; double mpy = screenHeight / (double)screenBounds.height ; if ((mpx != metersPerPixelX) || (mpy != metersPerPixelY)) { diff --git a/src/classes/share/com/sun/j3d/utils/universe/Viewer.java b/src/classes/share/com/sun/j3d/utils/universe/Viewer.java index c171632..46afef2 100644 --- a/src/classes/share/com/sun/j3d/utils/universe/Viewer.java +++ b/src/classes/share/com/sun/j3d/utils/universe/Viewer.java @@ -50,10 +50,8 @@ import java.net.URL; import java.util.*; import javax.media.j3d.*; import javax.swing.*; -import javax.vecmath.*; import com.sun.j3d.audioengines.AudioEngine3DL2; import java.lang.reflect.Constructor; -import java.applet.*; /** * The Viewer class holds all the information that describes the physical @@ -550,9 +548,11 @@ public class Viewer { "No GraphicsConfiguration on screen " + cs[i].frameBufferNumber + " conforms to template"); - bounds = cfg.getBounds(); + // Workaround for Issue 316 - use the default config for the screen + GraphicsConfiguration defCfg = cfg.getDevice().getDefaultConfiguration(); + bounds = defCfg.getBounds(); cs[i].j3dJFrame = j3dJFrames[i] = - new JFrame(cs[i].instanceName, cfg); + new JFrame(cs[i].instanceName, defCfg); if (cs[i].noBorderFullScreen) { try { @@ -978,26 +978,37 @@ public class Viewer { throw new UnsupportedOperationException("No AudioDevice specified"); } - ClassLoader audioDeviceClassLoader = - (ClassLoader) java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Object run() { - return ClassLoader.getSystemClassLoader(); - } - }); - - if (audioDeviceClassLoader == null) { - throw new IllegalStateException("System ClassLoader is null"); - } + // Issue 341: try the current class loader first before trying the + // system class loader + Class audioDeviceClass = null; + try { + audioDeviceClass = Class.forName(audioDeviceClassName); + } catch (ClassNotFoundException ex) { + // Ignore excpetion and try system class loader + } + + if (audioDeviceClass == null) { + ClassLoader audioDeviceClassLoader = + (ClassLoader) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return ClassLoader.getSystemClassLoader(); + } + }); + + if (audioDeviceClassLoader == null) { + throw new IllegalStateException("System ClassLoader is null"); + } + + audioDeviceClass = Class.forName(audioDeviceClassName, true, audioDeviceClassLoader); + } - Class audioDeviceClass = - Class.forName(audioDeviceClassName, true, audioDeviceClassLoader); Class physEnvClass = PhysicalEnvironment.class; Constructor audioDeviceConstructor = audioDeviceClass.getConstructor(new Class[] {physEnvClass}); PhysicalEnvironment[] args = new PhysicalEnvironment[] { physicalEnvironment }; AudioEngine3DL2 mixer = - (AudioEngine3DL2) audioDeviceConstructor.newInstance(args); + (AudioEngine3DL2) audioDeviceConstructor.newInstance((Object[])args); mixer.initialize(); return mixer; } diff --git a/src/native/share/J3DTimer.c b/src/native/share/J3DTimer.c deleted file mode 100644 index 6725d2f..0000000 --- a/src/native/share/J3DTimer.c +++ /dev/null @@ -1,175 +0,0 @@ -/* - * $RCSfile$ - * - * Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistribution of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistribution 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 Sun Microsystems, Inc. or the names of - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * This software is provided "AS IS," without a warranty of any - * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND - * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY - * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL - * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF - * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS - * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR - * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, - * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND - * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR - * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGES. - * - * You acknowledge that this software is not designed, licensed or - * intended for use in the design, construction, operation or - * maintenance of any nuclear facility. - * - * $Revision$ - * $Date$ - * $State$ - */ - -/* - * Portions of this code were derived from work done by the Blackdown - * group (www.blackdown.org), who did the initial Linux implementation - * of the Java 3D API. - */ - -#include "com_sun_j3d_utils_timer_J3DTimer.h" - -#define NSEC_PER_SEC ((jlong)1000000000) - -#ifdef __linux__ -#include <sys/time.h> -#include <time.h> -#include <unistd.h> -#endif - -#ifdef SOLARIS -#include <time.h> -#include <sys/systeminfo.h> -#include <string.h> -#ifndef CLOCK_HIGHRES -#define CLOCK_HIGHRES 4 /* Solaris 7 does not define this */ -#endif /* constant. When run on Solaris 7 */ -#endif /* CLOCK_HIGHRES is not used. */ - -#ifdef WIN32 -#include <Windows.h> -#include <math.h> -static double timerScale = -1.0; -#endif - -/* - * Class: com_sun_j3d_utils_timer_J3DTimer - * Method: getNativeTimer - * Signature: ()J - */ -JNIEXPORT jlong JNICALL -Java_com_sun_j3d_utils_timer_J3DTimer_getNativeTimer(JNIEnv *env, - jclass clazz) -{ - jlong timerNsec; - -#ifdef SOLARIS - /* - struct timespec tp; - clock_gettime( CLOCK_HIGHRES, &tp ); - - return (jlong)tp.tv_nsec + (jlong)tp.tv_sec * NSEC_PER_SEC; - */ - - timerNsec = (jlong)gethrtime(); -#endif /* SOLARIS */ - -#ifdef WIN32 - LARGE_INTEGER time; - LARGE_INTEGER freq; - - if (timerScale < 0.0) { - QueryPerformanceFrequency( &freq ); - if (freq.QuadPart <= 0) { - timerScale = 0.0; - } - else { - timerScale = (double) NSEC_PER_SEC / (double)freq.QuadPart; - } - } - - QueryPerformanceCounter(&time); - timerNsec = (jlong)((double)time.QuadPart * timerScale); - -#endif /* WIN32 */ - -#ifdef __linux__ - struct timeval t; - - gettimeofday(&t, 0); - timerNsec = ((jlong)t.tv_sec) * NSEC_PER_SEC + ((jlong)t.tv_usec) * ((jlong)1000); -#endif /* __linux__ */ - - return timerNsec; -} - - -/* - * Class: com_sun_j3d_utils_timer_J3DTimer - * Method: getNativeTimerResolution - * Signature: ()J - */ -JNIEXPORT jlong JNICALL -Java_com_sun_j3d_utils_timer_J3DTimer_getNativeTimerResolution(JNIEnv *env, - jclass clazz) -{ - jlong res; - -#ifdef SOLARIS - char buf[4]; - - sysinfo( SI_RELEASE, &buf[0], 4 ); - - if (strcmp( "5.7", &buf[0] )==0) { - /* Hard-coded for Solaris 7, since clock_getres isn't available */ - res = (jlong)3; - } else { - struct timespec tp; - clock_getres( CLOCK_HIGHRES, &tp ); - res = (jlong)tp.tv_nsec; - } -#endif /* SOLARIS */ - -#ifdef WIN32 - LARGE_INTEGER freq; - QueryPerformanceFrequency( &freq ); - - if ((jlong)freq.QuadPart <= 0) - res = 0; - else { - res = (NSEC_PER_SEC + (jlong)freq.QuadPart - 1) / ((jlong)freq.QuadPart); - - if (res < 1) { - res = 1; - } - } -#endif - -#ifdef __linux__ - /* Hard-coded at 1 microsecond -- the resolution of gettimeofday */ - res = 1000; -#endif /* __linux__ */ - - return res; -} |