org.scijava.java3d.utils.geometry.compression.CompressionStream Maven / Gradle / Ivy
Show all versions of j3dutils Show documentation
/*
* Copyright (c) 2007 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.
*
*/
package org.scijava.java3d.utils.geometry.compression;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import org.scijava.java3d.Appearance;
import org.scijava.java3d.Geometry;
import org.scijava.java3d.GeometryArray;
import org.scijava.java3d.GeometryStripArray;
import org.scijava.java3d.IndexedGeometryArray;
import org.scijava.java3d.IndexedGeometryStripArray;
import org.scijava.java3d.IndexedLineArray;
import org.scijava.java3d.IndexedLineStripArray;
import org.scijava.java3d.IndexedQuadArray;
import org.scijava.java3d.IndexedTriangleArray;
import org.scijava.java3d.IndexedTriangleFanArray;
import org.scijava.java3d.IndexedTriangleStripArray;
import org.scijava.java3d.J3DBuffer;
import org.scijava.java3d.LineArray;
import org.scijava.java3d.LineStripArray;
import org.scijava.java3d.Material;
import org.scijava.java3d.QuadArray;
import org.scijava.java3d.Shape3D;
import org.scijava.java3d.TriangleArray;
import org.scijava.java3d.TriangleFanArray;
import org.scijava.java3d.TriangleStripArray;
import org.scijava.vecmath.Color3f;
import org.scijava.vecmath.Color4f;
import org.scijava.vecmath.Point3d;
import org.scijava.vecmath.Point3f;
import org.scijava.vecmath.Point3i;
import org.scijava.vecmath.Vector3f;
import org.scijava.java3d.internal.BufferWrapper;
import org.scijava.java3d.utils.geometry.GeometryInfo;
/**
* 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.
*
* 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.
*
* 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.
*
* 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.
*
* @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.
*
* 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).
*
* 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.
*
* @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.
*
* @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 ;
}
@Override
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)))) ;
}
@Override
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 ;
}
@Override
void quantize(CompressionStream s, HuffmanTable t) {
normalQuant = value ;
normalQuantChanged = true ;
}
@Override
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 ;
}
@Override
void quantize(CompressionStream s, HuffmanTable t) {
colorQuant = value ;
colorQuantChanged = true ;
}
@Override
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++ ;
}
@Override
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 ;
}
}
@Override
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) ;
}
@Override
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) ;
}
}
@Override
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) ;
}
@Override
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) ;
@Override
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() ;
}
@Override
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) ;
}
@Override
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 {
FloatBuffer fbw = null ;
InterleavedGeometryNIO(GeometryArray ga) {
super(ga) ;
J3DBuffer buffer = ga.getInterleavedVertexBuffer() ;
if (BufferWrapper.getBufferType(buffer) ==
BufferWrapper.TYPE_FLOAT) {
fbw = (FloatBuffer)buffer.getBuffer();
}
else {
throw new IllegalArgumentException
("\ninterleaved vertex buffer must be FloatBuffer") ;
}
}
@Override
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) ;
}
@Override
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 ;
}
}
@Override
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) ;
}
@Override
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() ;
ByteBuffer colorsB = null ;
FloatBuffer colorsF = null ;
FloatBuffer normals = null ;
FloatBuffer positionsF = null ;
DoubleBuffer 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 = (FloatBuffer)buffer.getBuffer();
if (debug) System.out.println("float positions buffer") ;
break ;
case BufferWrapper.TYPE_DOUBLE:
positionsD = (DoubleBuffer)buffer.getBuffer();
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 = (ByteBuffer)buffer.getBuffer();
if (debug) System.out.println("byte colors buffer") ;
break ;
case BufferWrapper.TYPE_FLOAT:
colorsF = (FloatBuffer)buffer.getBuffer();
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 = (FloatBuffer)buffer.getBuffer();
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 ;
}
}
@Override
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) ;
}
@Override
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.
*
* 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.
*
* 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.
*
* @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.
*
* 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.
*
* 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.
*
* 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.
*
* @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.
*
* 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.
*
* @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.
*
* 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.
*
* 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.
*
* @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 ;
}
}