tge/engine/core/bitStream.cc
2025-02-17 23:17:30 -06:00

1059 lines
21 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/bitStream.h"
#include "core/tVector.h"
#include "math/mPoint.h"
#include "math/mMatrix.h"
#include "math/mQuat.h"
#include "math/mathIO.h"
#include "platform/event.h"
#include "console/consoleObject.h"
static BitStream gPacketStream(NULL, 0);
static U8 gPacketBuffer[MaxPacketDataSize];
// bitstream utility functions
void BitStream::setStringBuffer(char buffer[256])
{
stringBuffer = buffer;
}
BitStream *BitStream::getPacketStream(U32 writeSize)
{
if(!writeSize)
writeSize = MaxPacketDataSize;
gPacketStream.setBuffer(gPacketBuffer, writeSize, MaxPacketDataSize);
gPacketStream.setPosition(0);
return &gPacketStream;
}
void BitStream::sendPacketStream(const NetAddress *addr)
{
Net::sendto(addr, gPacketStream.getBuffer(), gPacketStream.getPosition());
}
// FIXMEFIXMEFIXME MATH
inline bool IsEqual(F32 a, F32 b) { return a == b; }
ResizeBitStream::ResizeBitStream(U32 minSpace, U32 initialSize) : BitStream(NULL, 0, 0)
{
mMinSpace = minSpace;
if(!initialSize)
initialSize = minSpace * 2;
U8 *buf = (U8 *) dMalloc(initialSize);
setBuffer(buf, initialSize, initialSize);
}
ResizeBitStream::~ResizeBitStream()
{
dFree(dataPtr);
}
void ResizeBitStream::validate()
{
if(getPosition() + mMinSpace > bufSize)
{
bufSize = getPosition() + mMinSpace * 2;
dataPtr = (U8 *) dRealloc(dataPtr, bufSize);
maxReadBitNum = bufSize << 3;
maxWriteBitNum = bufSize << 3;
}
}
class HuffmanProcessor
{
static const U32 csm_charFreqs[256];
bool m_tablesBuilt;
void buildTables();
struct HuffNode {
U32 pop;
S16 index0;
S16 index1;
};
struct HuffLeaf {
U32 pop;
U8 numBits;
U8 symbol;
U32 code; // no code should be longer than 32 bits.
};
// We have to be a bit careful with these, mSince they are pointers...
struct HuffWrap {
HuffNode* pNode;
HuffLeaf* pLeaf;
public:
HuffWrap() : pNode(NULL), pLeaf(NULL) { }
void set(HuffLeaf* in_leaf) { pNode = NULL; pLeaf = in_leaf; }
void set(HuffNode* in_node) { pLeaf = NULL; pNode = in_node; }
U32 getPop() { if (pNode) return pNode->pop; else return pLeaf->pop; }
};
Vector<HuffNode> m_huffNodes;
Vector<HuffLeaf> m_huffLeaves;
S16 determineIndex(HuffWrap&);
void generateCodes(BitStream&, S32, S32);
public:
HuffmanProcessor() : m_tablesBuilt(false) { }
static HuffmanProcessor g_huffProcessor;
bool readHuffBuffer(BitStream* pStream, char* out_pBuffer);
bool writeHuffBuffer(BitStream* pStream, const char* out_pBuffer, S32 maxLen);
};
HuffmanProcessor HuffmanProcessor::g_huffProcessor;
void BitStream::setBuffer(void *bufPtr, S32 size, S32 maxSize)
{
dataPtr = (U8 *) bufPtr;
bitNum = 0;
bufSize = size;
maxReadBitNum = size << 3;
if(maxSize < 0)
maxSize = size;
maxWriteBitNum = maxSize << 3;
error = false;
mCompressRelative = false;
}
U32 BitStream::getPosition() const
{
return (bitNum + 7) >> 3;
}
bool BitStream::setPosition(const U32 pos)
{
bitNum = pos << 3;
return (true);
}
U32 BitStream::getStreamSize()
{
return bufSize;
}
U8 *BitStream::getBytePtr()
{
return dataPtr + getPosition();
}
U32 BitStream::getReadByteSize()
{
return (maxReadBitNum >> 3) - getPosition();
}
void BitStream::clear()
{
dMemset(dataPtr, 0, bufSize);
}
void BitStream::writeClassId(U32 classId, U32 classType, U32 classGroup)
{
AssertFatal(classType < NetClassTypesCount, "Out of range class type.");
AssertFatal(classId < AbstractClassRep::NetClassCount[classGroup][classType], "Out of range class id.");
writeInt(classId, AbstractClassRep::NetClassBitSize[classGroup][classType]);
}
S32 BitStream::readClassId(U32 classType, U32 classGroup)
{
AssertFatal(classType < NetClassTypesCount, "Out of range class type.");
S32 ret = readInt(AbstractClassRep::NetClassBitSize[classGroup][classType]);
if(ret > AbstractClassRep::NetClassCount[classGroup][classType])
return -1;
return ret;
}
void BitStream::writeBits(S32 bitCount, const void *bitPtr)
{
if(!bitCount)
return;
if(bitCount + bitNum > maxWriteBitNum)
{
error = true;
AssertFatal(false, "Out of range write");
return;
}
const U8 *ptr = (U8 *) bitPtr;
U8 *stPtr = dataPtr + (bitNum >> 3);
U8 *endPtr = dataPtr + ((bitCount + bitNum - 1) >> 3);
S32 upShift = bitNum & 0x7;
S32 downShift= 8 - upShift;
U8 lastMask = 0xFF >> (7 - ((bitNum + bitCount - 1) & 0x7));
U8 startMask = 0xFF >> downShift;
U8 curB = *ptr++;
*stPtr = (curB << upShift) | (*stPtr & startMask);
stPtr++;
while(stPtr <= endPtr)
{
U8 nextB = *ptr++;
*stPtr++ = (curB >> downShift) | (nextB << upShift);
curB = nextB;
}
*endPtr &= lastMask;
bitNum += bitCount;
}
void BitStream::setBit(S32 bitCount, bool set)
{
if(set)
*(dataPtr + (bitCount >> 3)) |= (1 << (bitCount & 0x7));
else
*(dataPtr + (bitCount >> 3)) &= ~(1 << (bitCount & 0x7));
}
bool BitStream::testBit(S32 bitCount)
{
return (*(dataPtr + (bitCount >> 3)) & (1 << (bitCount & 0x7))) != 0;
}
bool BitStream::writeFlag(bool val)
{
if(bitNum + 1 > maxWriteBitNum)
{
error = true;
AssertFatal(false, "Out of range write");
return false;
}
if(val)
*(dataPtr + (bitNum >> 3)) |= (1 << (bitNum & 0x7));
else
*(dataPtr + (bitNum >> 3)) &= ~(1 << (bitNum & 0x7));
bitNum++;
return (val);
}
void BitStream::readBits(S32 bitCount, void *bitPtr)
{
if(!bitCount)
return;
if(bitCount + bitNum > maxReadBitNum)
{
error = true;
//AssertFatal(false, "Out of range read");
AssertWarn(false, "Out of range read");
return;
}
U8 *stPtr = dataPtr + (bitNum >> 3);
S32 byteCount = (bitCount + 7) >> 3;
U8 *ptr = (U8 *) bitPtr;
S32 downShift = bitNum & 0x7;
S32 upShift = 8 - downShift;
U8 curB = *stPtr;
while(byteCount--)
{
U8 nextB = *++stPtr;
*ptr++ = (curB >> downShift) | (nextB << upShift);
curB = nextB;
}
bitNum += bitCount;
}
bool BitStream::_read(U32 size, void *dataPtr)
{
readBits(size << 3, dataPtr);
return true;
}
bool BitStream::_write(U32 size, const void *dataPtr)
{
writeBits(size << 3, dataPtr);
return true;
}
S32 BitStream::readInt(S32 bitCount)
{
S32 ret = 0;
readBits(bitCount, &ret);
ret = convertLEndianToHost(ret);
if(bitCount == 32)
return ret;
else
ret &= (1 << bitCount) - 1;
return ret;
}
void BitStream::writeInt(S32 val, S32 bitCount)
{
val = convertHostToLEndian(val);
writeBits(bitCount, &val);
}
void BitStream::writeFloat(F32 f, S32 bitCount)
{
writeInt((S32)(f * ((1 << bitCount) - 1)), bitCount);
}
F32 BitStream::readFloat(S32 bitCount)
{
return readInt(bitCount) / F32((1 << bitCount) - 1);
}
void BitStream::writeSignedFloat(F32 f, S32 bitCount)
{
writeInt((S32)(((f + 1) * .5) * ((1 << bitCount) - 1)), bitCount);
}
F32 BitStream::readSignedFloat(S32 bitCount)
{
return readInt(bitCount) * 2 / F32((1 << bitCount) - 1) - 1.0f;
}
void BitStream::writeSignedInt(S32 value, S32 bitCount)
{
if(writeFlag(value < 0))
writeInt(-value, bitCount - 1);
else
writeInt(value, bitCount - 1);
}
S32 BitStream::readSignedInt(S32 bitCount)
{
if(readFlag())
return -readInt(bitCount - 1);
else
return readInt(bitCount - 1);
}
void BitStream::writeNormalVector(const Point3F& vec, S32 bitCount)
{
F32 phi = mAtan(vec.x, vec.y) / M_PI;
F32 theta = mAtan(vec.z, mSqrt(vec.x*vec.x + vec.y*vec.y)) / (M_PI/2.0);
writeSignedFloat(phi, bitCount+1);
writeSignedFloat(theta, bitCount);
}
void BitStream::readNormalVector(Point3F *vec, S32 bitCount)
{
F32 phi = readSignedFloat(bitCount+1) * M_PI;
F32 theta = readSignedFloat(bitCount) * (M_PI/2.0);
vec->x = mSin(phi)*mCos(theta);
vec->y = mCos(phi)*mCos(theta);
vec->z = mSin(theta);
}
Point3F BitStream::dumbDownNormal(const Point3F& vec, S32 bitCount)
{
U8 buffer[128];
BitStream temp(buffer, 128);
temp.writeNormalVector(vec, bitCount);
temp.setCurPos(0);
Point3F ret;
temp.readNormalVector(&ret, bitCount);
return ret;
}
void BitStream::writeNormalVector(const Point3F& vec, S32 angleBitCount, S32 zBitCount)
{
writeSignedFloat( vec.z, zBitCount );
// don't need to write x and y if they are both zero, which we can assess
// by checking for |z| == 1
if(!IsEqual(mFabs(vec.z), 1.0f))
{
writeSignedFloat( mAtan(vec.x,vec.y) / M_2PI, angleBitCount );
}
else
{
// angle won't matter...
writeSignedFloat(0.0f,angleBitCount);
}
}
void BitStream::readNormalVector(Point3F * vec, S32 angleBitCount, S32 zBitCount)
{
vec->z = readSignedFloat(zBitCount);
F32 angle = M_2PI * readSignedFloat(angleBitCount);
F32 mult = mSqrt(1.0f - vec->z * vec->z);
vec->x = mult * mSin(angle);
vec->y = mult * mCos(angle);
}
void BitStream::writeAffineTransform(const MatrixF& matrix)
{
// AssertFatal(matrix.isAffine() == true,
// "BitStream::writeAffineTransform: Error, must write only affine transforms!");
Point3F pos;
matrix.getColumn(3, &pos);
mathWrite(*this, pos);
QuatF q(matrix);
q.normalize();
write(q.x);
write(q.y);
write(q.z);
writeFlag(q.w < 0.0);
}
void BitStream::readAffineTransform(MatrixF* matrix)
{
Point3F pos;
QuatF q;
mathRead(*this, &pos);
read(&q.x);
read(&q.y);
read(&q.z);
q.w = mSqrt(1.0 - getMin(F32(((q.x * q.x) + (q.y * q.y) + (q.z * q.z))), 1.f));
if (readFlag())
q.w = -q.w;
q.setMatrix(matrix);
matrix->setColumn(3, pos);
// AssertFatal(matrix->isAffine() == true,
// "BitStream::readAffineTransform: Error, transform should be affine after this function!");
}
//----------------------------------------------------------------------------
void BitStream::clearCompressionPoint()
{
mCompressRelative = false;
}
void BitStream::setCompressionPoint(const Point3F& p)
{
mCompressRelative = true;
mCompressPoint = p;
}
static U32 gBitCounts[4] = {
16, 18, 20, 32
};
void BitStream::writeCompressedPoint(const Point3F& p,F32 scale)
{
// Same # of bits for all axis
Point3F vec;
F32 invScale = 1 / scale;
U32 type;
if(mCompressRelative)
{
vec = p - mCompressPoint;
F32 dist = vec.len() * invScale;
if(dist < (1 << 15))
type = 0;
else if(dist < (1 << 17))
type = 1;
else if(dist < (1 << 19))
type = 2;
else
type = 3;
}
else
type = 3;
writeInt(type, 2);
if (type != 3)
{
type = gBitCounts[type];
writeSignedInt(S32(vec.x * invScale),type);
writeSignedInt(S32(vec.y * invScale),type);
writeSignedInt(S32(vec.z * invScale),type);
}
else
{
write(p.x);
write(p.y);
write(p.z);
}
}
void BitStream::readCompressedPoint(Point3F* p,F32 scale)
{
// Same # of bits for all axis
U32 type = readInt(2);
if(type == 3)
{
read(&p->x);
read(&p->y);
read(&p->z);
}
else
{
type = gBitCounts[type];
p->x = readSignedInt(type);
p->y = readSignedInt(type);
p->z = readSignedInt(type);
p->x = mCompressPoint.x + p->x * scale;
p->y = mCompressPoint.y + p->y * scale;
p->z = mCompressPoint.z + p->z * scale;
}
}
//------------------------------------------------------------------------------
InfiniteBitStream::InfiniteBitStream()
{
//
}
InfiniteBitStream::~InfiniteBitStream()
{
//
}
void InfiniteBitStream::reset()
{
// Rewing back to beginning
setPosition(0);
}
void InfiniteBitStream::validate(U32 upcomingBytes)
{
if(getPosition() + upcomingBytes + mMinSpace > bufSize)
{
bufSize = getPosition() + upcomingBytes + mMinSpace;
dataPtr = (U8 *) dRealloc(dataPtr, bufSize);
maxReadBitNum = bufSize << 3;
maxWriteBitNum = bufSize << 3;
}
}
void InfiniteBitStream::compact()
{
// Prepare to copy...
U32 oldSize = bufSize;
U8 *tmp = (U8*)dMalloc(bufSize);
// Copy things...
bufSize = getPosition() + mMinSpace * 2;
dMemcpy(tmp, dataPtr, oldSize);
// And clean up.
dFree(dataPtr);
dataPtr = tmp;
maxReadBitNum = bufSize << 3;
maxWriteBitNum = bufSize << 3;
}
void InfiniteBitStream::writeToStream(Stream &s)
{
s.write(getPosition(), dataPtr);
}
//------------------------------------------------------------------------------
void BitStream::readString(char buf[256])
{
if(stringBuffer)
{
if(readFlag())
{
S32 offset = readInt(8);
HuffmanProcessor::g_huffProcessor.readHuffBuffer(this, stringBuffer + offset);
dStrcpy(buf, stringBuffer);
return;
}
}
HuffmanProcessor::g_huffProcessor.readHuffBuffer(this, buf);
if(stringBuffer)
dStrcpy(stringBuffer, buf);
}
void BitStream::writeString(const char *string, S32 maxLen)
{
if(!string)
string = "";
if(stringBuffer)
{
S32 j;
for(j = 0; j < maxLen && stringBuffer[j] == string[j] && string[j];j++)
;
dStrncpy(stringBuffer, string, maxLen);
stringBuffer[maxLen] = 0;
if(writeFlag(j > 2))
{
writeInt(j, 8);
HuffmanProcessor::g_huffProcessor.writeHuffBuffer(this, string + j, maxLen - j);
return;
}
}
HuffmanProcessor::g_huffProcessor.writeHuffBuffer(this, string, maxLen);
}
void HuffmanProcessor::buildTables()
{
AssertFatal(m_tablesBuilt == false, "Cannot build tables twice!");
m_tablesBuilt = true;
S32 i;
// First, construct the array of wraps...
//
m_huffLeaves.setSize(256);
m_huffNodes.reserve(256);
m_huffNodes.increment();
for (i = 0; i < 256; i++) {
HuffLeaf& rLeaf = m_huffLeaves[i];
rLeaf.pop = csm_charFreqs[i] + 1;
rLeaf.symbol = U8(i);
dMemset(&rLeaf.code, 0, sizeof(rLeaf.code));
rLeaf.numBits = 0;
}
S32 currWraps = 256;
HuffWrap* pWrap = new HuffWrap[256];
for (i = 0; i < 256; i++) {
pWrap[i].set(&m_huffLeaves[i]);
}
while (currWraps != 1) {
U32 min1 = 0xfffffffe, min2 = 0xffffffff;
S32 index1 = -1, index2 = -1;
for (i = 0; i < currWraps; i++) {
if (pWrap[i].getPop() < min1) {
min2 = min1;
index2 = index1;
min1 = pWrap[i].getPop();
index1 = i;
} else if (pWrap[i].getPop() < min2) {
min2 = pWrap[i].getPop();
index2 = i;
}
}
AssertFatal(index1 != -1 && index2 != -1 && index1 != index2, "hrph");
// Create a node for this...
m_huffNodes.increment();
HuffNode& rNode = m_huffNodes.last();
rNode.pop = pWrap[index1].getPop() + pWrap[index2].getPop();
rNode.index0 = determineIndex(pWrap[index1]);
rNode.index1 = determineIndex(pWrap[index2]);
S32 mergeIndex = index1 > index2 ? index2 : index1;
S32 nukeIndex = index1 > index2 ? index1 : index2;
pWrap[mergeIndex].set(&rNode);
if (index2 != (currWraps - 1)) {
pWrap[nukeIndex] = pWrap[currWraps - 1];
}
currWraps--;
}
AssertFatal(currWraps == 1, "wrong wraps?");
AssertFatal(pWrap[0].pNode != NULL && pWrap[0].pLeaf == NULL, "Wrong wrap type!");
// Ok, now we have one wrap, which is a node. we need to make sure that this
// is the first node in the node list.
m_huffNodes[0] = *(pWrap[0].pNode);
delete [] pWrap;
U32 code = 0;
BitStream bs(&code, 4);
generateCodes(bs, 0, 0);
}
void HuffmanProcessor::generateCodes(BitStream& rBS, S32 index, S32 depth)
{
if (index < 0) {
// leaf node, copy the code in, and back out...
HuffLeaf& rLeaf = m_huffLeaves[-(index + 1)];
dMemcpy(&rLeaf.code, rBS.dataPtr, sizeof(rLeaf.code));
rLeaf.numBits = depth;
} else {
HuffNode& rNode = m_huffNodes[index];
S32 pos = rBS.getCurPos();
rBS.writeFlag(false);
generateCodes(rBS, rNode.index0, depth + 1);
rBS.setCurPos(pos);
rBS.writeFlag(true);
generateCodes(rBS, rNode.index1, depth + 1);
rBS.setCurPos(pos);
}
}
S16 HuffmanProcessor::determineIndex(HuffWrap& rWrap)
{
if (rWrap.pLeaf != NULL) {
AssertFatal(rWrap.pNode == NULL, "Got a non-NULL pNode in a HuffWrap with a non-NULL leaf.");
return -((rWrap.pLeaf - m_huffLeaves.address()) + 1);
} else {
AssertFatal(rWrap.pNode != NULL, "Got a NULL pNode in a HuffWrap with a NULL leaf.");
return rWrap.pNode - m_huffNodes.address();
}
}
bool HuffmanProcessor::readHuffBuffer(BitStream* pStream, char* out_pBuffer)
{
if (m_tablesBuilt == false)
buildTables();
if (pStream->readFlag()) {
S32 len = pStream->readInt(8);
for (S32 i = 0; i < len; i++) {
S32 index = 0;
while (true) {
if (index >= 0) {
if (pStream->readFlag() == true) {
index = m_huffNodes[index].index1;
} else {
index = m_huffNodes[index].index0;
}
} else {
out_pBuffer[i] = m_huffLeaves[-(index+1)].symbol;
break;
}
}
}
out_pBuffer[len] = '\0';
return true;
} else {
// Uncompressed string...
U32 len = pStream->readInt(8);
pStream->read(len, out_pBuffer);
out_pBuffer[len] = '\0';
return true;
}
}
bool HuffmanProcessor::writeHuffBuffer(BitStream* pStream, const char* out_pBuffer, S32 maxLen)
{
if (out_pBuffer == NULL) {
pStream->writeFlag(false);
pStream->writeInt(0, 8);
return true;
}
if (m_tablesBuilt == false)
buildTables();
S32 len = out_pBuffer ? dStrlen(out_pBuffer) : 0;
AssertWarn(len <= 255, "String TOO long for writeString");
AssertWarn(len <= 255, out_pBuffer);
if (len > maxLen)
len = maxLen;
S32 numBits = 0;
S32 i;
for (i = 0; i < len; i++)
numBits += m_huffLeaves[(unsigned char)out_pBuffer[i]].numBits;
if (numBits >= (len * 8)) {
pStream->writeFlag(false);
pStream->writeInt(len, 8);
pStream->write(len, out_pBuffer);
} else {
pStream->writeFlag(true);
pStream->writeInt(len, 8);
for (i = 0; i < len; i++) {
HuffLeaf& rLeaf = m_huffLeaves[((unsigned char)out_pBuffer[i])];
pStream->writeBits(rLeaf.numBits, &rLeaf.code);
}
}
return true;
}
const U32 HuffmanProcessor::csm_charFreqs[256] = {
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
329 ,
21 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
2809 ,
68 ,
0 ,
27 ,
0 ,
58 ,
3 ,
62 ,
4 ,
7 ,
0 ,
0 ,
15 ,
65 ,
554 ,
3 ,
394 ,
404 ,
189 ,
117 ,
30 ,
51 ,
27 ,
15 ,
34 ,
32 ,
80 ,
1 ,
142 ,
3 ,
142 ,
39 ,
0 ,
144 ,
125 ,
44 ,
122 ,
275 ,
70 ,
135 ,
61 ,
127 ,
8 ,
12 ,
113 ,
246 ,
122 ,
36 ,
185 ,
1 ,
149 ,
309 ,
335 ,
12 ,
11 ,
14 ,
54 ,
151 ,
0 ,
0 ,
2 ,
0 ,
0 ,
211 ,
0 ,
2090 ,
344 ,
736 ,
993 ,
2872 ,
701 ,
605 ,
646 ,
1552 ,
328 ,
305 ,
1240 ,
735 ,
1533 ,
1713 ,
562 ,
3 ,
1775 ,
1149 ,
1469 ,
979 ,
407 ,
553 ,
59 ,
279 ,
31 ,
0 ,
0 ,
0 ,
68 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0
};