/*
* Copyright 1998-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*
*/
package javax.media.j3d;
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 ;
@Override
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.err.println("struct {") ;
System.err.println(" double nx, ny, nz ;") ;
System.err.println("} gcNormals[65][65] = {");
for (i = 0 ; i <= 64 ; i++) {
System.err.println("{") ;
for (j = 0 ; j <= 64 ; j++) {
if (j+i > 64) continue ;
System.err.println("{ " + gcNormals[i][j][0] +
", " + gcNormals[i][j][1] +
", " + gcNormals[i][j][2] + " }") ;
}
System.err.println("},") ;
}
System.err.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.err.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
(J3dI18N.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.err.print(" getBits(" + bitCount + ") " + d + ", " +
bitBufferCount + " available at gcIndex " +
gcIndex) ;
if (bitCount == 0) {
if (debug) System.err.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.err.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.err.println("GeometryDecompressor.processSetState") ;
ii = getBits(3, "bundling") ;
bundlingNorm = ((currentHeader & 0x1) != 0) ;
bundlingColor = (((ii >>> 2) & 0x1) != 0) ;
doingAlpha = (((ii >>> 1) & 0x1) != 0) ;
if (debug)
System.err.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.err.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.err.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.err.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.err.println(" absolute position: " +
curX + " " + curY + " " + curZ) ;
} else {
curX += dx ; curY += dy ; curZ += dz ;
if (debug) System.err.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.err.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.err.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.err.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.err.println(" absolute normal: sex " + curSex +
" oct " + curOct +
" u " + curU + " v " + curV) ;
else
System.err.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.err.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
(J3dI18N.getString("GeometryDecompressor1")) ;
}
}
// do optional mesh buffer push
if (mbp != 0) {
if (debug)
System.err.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.err.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.err.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.err.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.err.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.err.println(" delta color: dr " + dr +
" dg " + dg + " db " + db +
" da " + da) ;
}
// Do optional mesh buffer push.
if (mbp != 0) {
if (debug)
System.err.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.err.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.err.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.err.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.err.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.err.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.err.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.err.println("GeometryDecompressor.processEos") ;
}
// Process a variable length no-op opcode.
private void processVNoop() {
int ii, ct ;
if (debug) System.err.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.err.println("GeometryDecompressor.processPassThrough") ;
ignore = getBits(24, "passthrough") ;
ignore = getBits(32, "passthrough") ;
}
// Process a skip-8 opcode.
private void processSkip8() {
int skip ;
if (debug) System.err.println("GeometryDecompressor.processSkip8") ;
skip = getBits(8, "skip8") ;
}
private void benchmarkStart(int length) {
vertexCount = 0 ;
System.err.println(" GeometryDecompressor: decompressing " +
length + " bytes...") ;
startTime = J3dClock.currentTimeMillis() ;
}
private void benchmarkPrint(int length) {
float t = (J3dClock.currentTimeMillis() - startTime) / 1000.0f ;
System.err.println
(" done in " + t + " sec." + "\n" +
" decompressed " + vertexCount + " vertices at " +
(vertexCount/t) + " vertices/sec\n") ;
System.err.print(" vertex data present: coords") ;
int floatVertexSize = 12 ;
if (bundlingNorm) {
System.err.print(" normals") ;
floatVertexSize += 12 ;
}
if (bundlingColor) {
System.err.println(" colors") ;
floatVertexSize += 12 ;
}
if (doingAlpha) {
System.err.println(" alpha") ;
floatVertexSize += 4 ;
}
System.err.println() ;
System.err.println
(" bytes of data in generalized strip output: " +
(vertexCount * floatVertexSize) + "\n" +
" compression ratio: " +
(length / (float)(vertexCount * floatVertexSize)) + "\n") ;
}
}