/*
 * $RCSfile: CompressionStreamVertex.java,v $
 *
 * 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.
 *
 * $Revision: 1.5 $
 * $Date: 2007/02/09 17:20:16 $
 * $State: Exp $
 */

package com.sun.j3d.utils.compression;

import javax.vecmath.Color3f;
import javax.vecmath.Color4f;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

/**
 * This class represents a vertex in a compression stream.  It maintains both
 * floating-point and quantized representations of the vertex position along
 * with meshing and vertex replacement flags for line and surface
 * primitives. If normals or colors are bundled with geometry vertices then
 * instances of this class will also contain references to normal or color
 * stream elements.
 */
class CompressionStreamVertex extends CompressionStreamElement {
    private int X, Y, Z ;
    private int meshFlag ;
    private int stripFlag ;
    private float floatX, floatY, floatZ ;

    int xAbsolute, yAbsolute, zAbsolute ;
    CompressionStreamColor color = null ;
    CompressionStreamNormal normal = null ;
	    
    /**
     * Create a CompressionStreamVertex with the given parameters.
     *
     * @param stream CompressionStream associated with this vertex
     * @param p position
     * @param n normal bundled with this vertex or null if not bundled
     * @param c color bundled with this vertex or null if not bundled
     * @param stripFlag CompressionStream.RESTART,
     * CompressionStream.REPLACE_OLDEST, or CompressionStream.REPLACE_MIDDLE
     * @param meshFlag CompressionStream.MESH_PUSH or
     * CompressionStream.NO_MESH_PUSH
     */
    CompressionStreamVertex(CompressionStream stream,
			    Point3f p, Vector3f n, Color3f c,
			    int stripFlag, int meshFlag) {

	this(stream, p, n, stripFlag, meshFlag) ;

	if (stream.vertexColor3)
	    color = new CompressionStreamColor(stream, c) ;
    }

    /**
     * Create a CompressionStreamVertex with the given parameters.
     *
     * @param stream CompressionStream associated with this vertex
     * @param p position
     * @param n normal bundled with this vertex or null if not bundled
     * @param c color bundled with this vertex or null if not bundled
     * @param stripFlag CompressionStream.RESTART,
     * CompressionStream.REPLACE_OLDEST, or CompressionStream.REPLACE_MIDDLE
     * @param meshFlag CompressionStream.MESH_PUSH or
     * CompressionStream.NO_MESH_PUSH
     */
    CompressionStreamVertex(CompressionStream stream,
			    Point3f p, Vector3f n, Color4f c,
			    int stripFlag, int meshFlag) {

	this(stream, p, n, stripFlag, meshFlag) ;

	if (stream.vertexColor4)
	    color = new CompressionStreamColor(stream, c) ;
    }

    /**
     * Create a CompressionStreamVertex with the given parameters.
     *
     * @param stream CompressionStream associated with this vertex
     * @param p position
     * @param n normal bundled with this vertex or null if not bundled
     * @param stripFlag CompressionStream.RESTART,
     * CompressionStream.REPLACE_OLDEST, or CompressionStream.REPLACE_MIDDLE
     * @param meshFlag CompressionStream.MESH_PUSH or
     * CompressionStream.NO_MESH_PUSH
     */
    CompressionStreamVertex(CompressionStream stream, Point3f p, Vector3f n,
			    int stripFlag, int meshFlag) {

	this.stripFlag = stripFlag ;
	this.meshFlag = meshFlag ;
	this.floatX = p.x ;
	this.floatY = p.y ;
	this.floatZ = p.z ;

	stream.byteCount += 12 ;
	stream.vertexCount++ ;
		
	if (p.x < stream.mcBounds[0].x) stream.mcBounds[0].x = p.x ;
	if (p.y < stream.mcBounds[0].y) stream.mcBounds[0].y = p.y ;
	if (p.z < stream.mcBounds[0].z) stream.mcBounds[0].z = p.z ;

	if (p.x > stream.mcBounds[1].x) stream.mcBounds[1].x = p.x ;
	if (p.y > stream.mcBounds[1].y) stream.mcBounds[1].y = p.y ;
	if (p.z > stream.mcBounds[1].z) stream.mcBounds[1].z = p.z ;

	if (stream.vertexNormals)
	    normal = new CompressionStreamNormal(stream, n) ;
    }

    /**
     * Quantize the floating point position to fixed point integer components
     * of the specified number of bits.  The bit length can range from 1 to 16.
     *
     * @param stream CompressionStream associated with this element
     * @param table HuffmanTable for collecting data about the quantized
     * representation of this element
     */
    void quantize(CompressionStream stream, HuffmanTable huffmanTable) {
	double px, py, pz ;

	// Clamp quantization.
	int quant = 
	    (stream.positionQuant < 1? 1 :
	     (stream.positionQuant > 16? 16 : stream.positionQuant)) ;

	absolute = false ;
	if (stream.firstPosition || stream.positionQuantChanged) {
	    absolute = true ;
	    stream.lastPosition[0] = 0 ;
	    stream.lastPosition[1] = 0 ;
	    stream.lastPosition[2] = 0 ;
	    stream.firstPosition = false ;
	    stream.positionQuantChanged = false ;
	}

	// Normalize position to the unit cube.  This is bounded by the open
	// intervals (-1..1) on each axis.
	px = (floatX - stream.center[0]) * stream.scale ;
	py = (floatY - stream.center[1]) * stream.scale ;
	pz = (floatZ - stream.center[2]) * stream.scale ;

	// Convert the floating point position to s.15 2's complement.
	//  ~1.0 ->  32767 (0x00007fff) [ ~1.0 =  32767.0/32768.0]
	// ~-1.0 -> -32767 (0xffff8001) [~-1.0 = -32767.0/32768.0]
	X = (int)(px * 32768.0) ;
	Y = (int)(py * 32768.0) ;
	Z = (int)(pz * 32768.0) ;

	// Compute quantized values.
	X &= quantizationMask[quant] ;
	Y &= quantizationMask[quant] ;
	Z &= quantizationMask[quant] ;

	// Update quantized bounds.
	if (X < stream.qcBounds[0].x) stream.qcBounds[0].x = X ;
	if (Y < stream.qcBounds[0].y) stream.qcBounds[0].y = Y ;
	if (Z < stream.qcBounds[0].z) stream.qcBounds[0].z = Z ;

	if (X > stream.qcBounds[1].x) stream.qcBounds[1].x = X ;
	if (Y > stream.qcBounds[1].y) stream.qcBounds[1].y = Y ;
	if (Z > stream.qcBounds[1].z) stream.qcBounds[1].z = Z ;

	// Copy and retain absolute position for mesh buffer lookup.
	xAbsolute = X ;
	yAbsolute = Y ;
	zAbsolute = Z ;

	// Compute deltas.
	X -= stream.lastPosition[0] ;
	Y -= stream.lastPosition[1] ;
	Z -= stream.lastPosition[2] ;

	// Update last values.
	stream.lastPosition[0] += X ;
	stream.lastPosition[1] += Y ;
	stream.lastPosition[2] += Z ;

	// Deltas which exceed the range of 16-bit signed 2's complement
	// numbers are handled by sign-extension of the 16th bit in order to
	// effect a 16-bit wrap-around.
	X = (X << 16) >> 16 ;
	Y = (Y << 16) >> 16 ;
	Z = (Z << 16) >> 16 ;

	// Compute length and shift common to all components.
	computeLengthShift(X, Y, Z) ;

	// 0-length components are allowed only for normals.
	if (length == 0)
	    length = 1 ;
	
	// Add this element to the Huffman table associated with this stream.
	huffmanTable.addPositionEntry(length, shift, absolute) ;

	// Quantize any bundled color or normal.
	if (color != null)
	    color.quantize(stream, huffmanTable) ;

	if (normal != null)
	    normal.quantize(stream, huffmanTable) ;

	// Push this vertex into the mesh buffer mirror, if necessary, so it
	// can be retrieved for computing deltas when mesh buffer references
	// are subsequently encountered during the quantization pass.
	if (meshFlag == stream.MESH_PUSH)
	    stream.meshBuffer.push(this) ;
    }

    /**
     * Output the final compressed bits to the compression command stream.
     *
     * @param table HuffmanTable mapping quantized representations to
     * compressed encodings
     * @param output CommandStream for collecting compressed output
     */
    void outputCommand(HuffmanTable huffmanTable, CommandStream outputBuffer) {

	HuffmanNode t ;
	int command = CommandStream.VERTEX ;

	// Look up the Huffman token for this compression stream element.  The
	// values of length and shift found there will override the
	// corresponding fields in this element, which represent best-case
	// compression without regard to tag length.
	t = huffmanTable.getPositionEntry(length, shift, absolute) ;

	// Construct the position subcommand.
	int componentLength = t.dataLength - t.shift ;
	int subcommandLength = t.tagLength + (3 * componentLength) ;

	X = (X >> t.shift) & (int)lengthMask[componentLength] ;
	Y = (Y >> t.shift) & (int)lengthMask[componentLength] ;
	Z = (Z >> t.shift) & (int)lengthMask[componentLength] ;

	long positionSubcommand = 
	    (((long)t.tag) << (3 * componentLength)) |
	    (((long)X)     << (2 * componentLength)) |
	    (((long)Y)     << (1 * componentLength)) |
	    (((long)Z)     << (0 * componentLength)) ;
	    
	if (subcommandLength < 6) {
	    // The header will have some empty bits.  The Huffman tag
	    // computation will prevent this if necessary.
	    command |= (int)(positionSubcommand << (6 - subcommandLength)) ;
	    subcommandLength = 0 ;
	}
	else {
	    // Move the 1st 6 bits of the subcommand into the header.
	    command |= (int)(positionSubcommand >>> (subcommandLength - 6)) ;
	    subcommandLength -= 6 ;
	}

	// Construct the vertex command body.
	long body =
	    (((long)stripFlag) << (subcommandLength + 1)) |
	    (((long)meshFlag)  << (subcommandLength + 0)) |
	    (positionSubcommand & lengthMask[subcommandLength]) ;

	// Add the vertex command to the output buffer.
	outputBuffer.addCommand(command, 8, body, subcommandLength + 3) ;

	// Output any normal and color subcommands.
	if (normal != null)
	    normal.outputSubcommand(huffmanTable, outputBuffer) ;

	if (color != null)
	    color.outputSubcommand(huffmanTable, outputBuffer) ;
    }

    public String toString() {
	String d = absolute? "" : "delta " ;
	String c = (color  == null? "": "\n\n " + color.toString()) ;
	String n = (normal == null? "": "\n\n " + normal.toString()) ;

	return
	    "position: " + floatX + " " + floatY + " " + floatZ + "\n" +
	    "fixed point " + d + + X + " " + Y + " " + Z + "\n" +
	    "length " + length + " shift " + shift +
	    (absolute? " absolute" : " relative") + "\n" +
	    "strip flag " + stripFlag + " mesh flag " + meshFlag + 
	    c + n ;
    }
}
