Game Development Community

Networking floats

by Gary "ChunkyKs" Briggs · in Torque Game Engine · 05/03/2007 (3:07 pm) · 5 replies

According to bitStream.h, BitStream::readFloat, writeFloat, /et al/, are for floats in the range 0..1.

I have code thus:
#define _I(i,j) I[(i)*4+(j)]

U32 tdMass::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
{
	U32 ret = Parent::packUpdate(conn, mask, stream);
	if(stream->writeFlag(mask&NewMass)) {
		int i,j;
		// Total mass of rigid body
		stream->writeFloat(mass.mass,32);
		
		// center of gravity position in body frame (x,y,z)
		for(i=0;i<=3;i++) {
			stream->writeFloat(mass.c[i],32);
		}
		
		// 3x3 inertia tensor in body frame, about POR
		for(i=0;i<3;i++) {
			for(j=0;j<3;j++) {
				stream->writeFloat(mass._I(i,j),32);
			}
		}
	}
	return ret;
}

void tdMass::unpackUpdate(NetConnection *conn, BitStream *stream)
{
	Parent::unpackUpdate(conn, stream);
	if(stream->readFlag()) {
		int i,j;
		
		mass.mass = stream->readFloat(32);
		
		for(i=0;i<=3;i++) {
			mass.c[i] = stream->readFloat(32);
		}
		
		for(i=0;i<3;i++) {
			for(j=0;j<3;j++) {
				mass._I(i,j) = stream->readFloat(32);
			}
		}
	}
}

The problem is, that my floats typically aren't in the range 0..1. The mass is thus:
// dReal is typed to either a float or a double

typedef dReal dVector4[4];
typedef dReal dMatrix3[4*3];

typedef struct dMass {
 dReal mass; // total mass of the rigid body
 dVector4 c; // center of gravity position in body frame (x,y,z)
 dMatrix3 I; // 3x3 inertia tensor in body frame, about POR
} dMass;

The fourth entry in each row of I is deliberately ignored.

How should I be sending this structure across a network? It only needs to be done once. Entries in it are typically zero, 10, 100.

According to my debugger, mass.* in packUpdate has sensible values.
When that unpack runs, I get nothing but NANs.

How should I go about doing this properly? Eventually, this stuff will go in a datablock, but I'll still need to transmit it across the network.

Thanks,
Gary (-;

#1
05/03/2007 (4:04 pm)
Pick a constant that normalizes your float values down to 0..1 when the value to be networked is divided by the constant.

In unpackUpdate, simply "un-normalize" the value back to it's original state by multiplying by the constant.

IIRC, you also do not give a number after the float value like you do with ints...that second parameter is used with ints for saying how many bits should be used in the bitstream, which is not applicable for write/readFloat.
#2
05/03/2007 (4:31 pm)
That feels somewhat hacky; what if my mass accidentally ends up bigger than my divisor? Choosing a better divisor is hard...

And from bitstream:
// read and write floats... floats are 0 to 1 inclusive, signed floats are -1 to 1 inclusive

   F32  readFloat(S32 bitCount);
   F32  readSignedFloat(S32 bitCount);

   void writeFloat(F32 f, S32 bitCount);
   void writeSignedFloat(F32 f, S32 bitCount);

Gary (-;
#3
05/03/2007 (4:45 pm)
Hm. So I tried that:

#define _I(i,j) I[(i)*4+(j)]
// Constant divisor. If your masses are bigger than this, you have a problem
#define MASS_DIVISOR 256.0F

U32 tdMass::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
{
	U32 ret = Parent::packUpdate(conn, mask, stream);
	if(stream->writeFlag(mask&NewMass)) {
		int i,j;
		// Total mass of rigid body
		stream->writeFloat(mass.mass/MASS_DIVISOR,32);
		Con::printf("Wrote mass.mass: %f", i, mass.mass/MASS_DIVISOR);
		
		// center of gravity position in body frame (x,y,z)
		for(i=0;i<=3;i++) {
			stream->writeFloat(mass.c[i]/MASS_DIVISOR,32);
			Con::printf("Wrote mass.c[%i]: %f", i, mass.c[i]/MASS_DIVISOR);
		}
		
		// 3x3 inertia tensor in body frame, about POR
		for(i=0;i<3;i++) {
			for(j=0;j<3;j++) {
				stream->writeFloat(mass._I(i,j)/MASS_DIVISOR,32);
				Con::printf("Wrote mass._I(%i,%i): %f", i, j, mass._I(i,j)/MASS_DIVISOR);
			}
		}
	}
	return ret;
}

void tdMass::unpackUpdate(NetConnection *conn, BitStream *stream)
{
	Parent::unpackUpdate(conn, stream);
	if(stream->readFlag()) {
		int i,j;
		
		mass.mass = stream->readFloat(32) * MASS_DIVISOR;
		Con::printf("Read mass.mass: %f", mass.mass);
		
		for(i=0;i<=3;i++) {
			mass.c[i] = stream->readFloat(32) * MASS_DIVISOR;
			Con::printf("Read mass.c[%i]: %f", i, mass.c[i]);
		}
		
		for(i=0;i<3;i++) {
			for(j=0;j<3;j++) {
				mass._I(i,j) = stream->readFloat(32) * MASS_DIVISOR;
				Con::printf("Read mass._I(%i,%i): %f", i, j, mass._I(i,j));
			}
		}
	}
}

And this appears in the console:
Quote:Wrote mass.mass: 0.011719
Wrote mass.c[0]: 0.000000
Wrote mass.c[1]: 0.000000
Wrote mass.c[2]: 0.000000
Wrote mass.c[3]: 0.000000
Wrote mass._I(0,0): 0.042188
Wrote mass._I(0,1): 0.000000
Wrote mass._I(0,2): 0.000000
Wrote mass._I(1,0): 0.000000
Wrote mass._I(1,1): 0.042188
Wrote mass._I(1,2): 0.000000
Wrote mass._I(2,0): 0.000000
Wrote mass._I(2,1): 0.000000
Wrote mass._I(2,2): 0.042188
Read mass.mass: nan
Read mass.c[0]: nan
Read mass.c[1]: nan
Read mass.c[2]: nan
Read mass.c[3]: nan
Read mass._I(0,0): nan
Read mass._I(0,1): nan
Read mass._I(0,2): nan
Read mass._I(1,0): nan
Read mass._I(1,1): nan
Read mass._I(1,2): nan
Read mass._I(2,0): nan
Read mass._I(2,1): nan
Read mass._I(2,2): nan
==>quit();

I don't really understand what's going on here. Thanks for the suggestion, though. It's what I was kinda hoping to avoid, but if that's the blessed way of doing it, then so be it :-)

Gary (-;
#4
05/03/2007 (5:00 pm)
I told you this on IRC, but if anyone else is wondering ...

BitStream derives from Stream, Stream implements a write(F32) method. Thus, you can just do stream->write(myF32); / stream->read(&myF32);

The method in BitStream you mention is just a specialised optimization for normalized floats.

T.
#5
05/03/2007 (5:05 pm)
Woo! Thank-you :-)

// Next line ripped from ode's mass.cpp
#define _I(i,j) I[(i)*4+(j)]

U32 tdMass::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
{
	U32 ret = Parent::packUpdate(conn, mask, stream);
	if(stream->writeFlag(mask&NewMass)) {
		int i,j;
		// Total mass of rigid body
		stream->write((F32)mass.mass);
		Con::printf("Wrote mass.mass: %f", mass.mass);
		
		// center of gravity position in body frame (x,y,z)
		for(i=0;i<=3;i++) {
			stream->write((F32)mass.c[i]);
			Con::printf("Wrote mass.c[%i]: %f", i, mass.c[i]);
		}
		
		// 3x3 inertia tensor in body frame, about POR
		for(i=0;i<3;i++) {
			for(j=0;j<3;j++) {
				stream->write((F32)mass._I(i,j));
				Con::printf("Wrote mass._I(%i,%i): %f", i, j, mass._I(i,j));
			}
		}
	}
	return ret;
}

void tdMass::unpackUpdate(NetConnection *conn, BitStream *stream)
{
	Parent::unpackUpdate(conn, stream);
	if(stream->readFlag()) {
		int i,j;
		
		stream->read((F32 *)&mass.mass);
		Con::printf("Read mass.mass: %f", mass.mass);
		
		for(i=0;i<=3;i++) {
			stream->read((F32 *)&mass.c[i]);
			Con::printf("Read mass.c[%i]: %f", i, mass.c[i]);
		}
		
		for(i=0;i<3;i++) {
			for(j=0;j<3;j++) {
				stream->read((F32 *)&mass._I(i,j));
				Con::printf("Read mass._I(%i,%i): %f", i, j, mass._I(i,j));
			}
		}
	}
}

Gives this:
Quote:Wrote mass.mass: 3.000000
Wrote mass.c[0]: 0.000000
Wrote mass.c[1]: 0.000000
Wrote mass.c[2]: 0.000000
Wrote mass.c[3]: 0.000000
Wrote mass._I(0,0): 10.800000
Wrote mass._I(0,1): 0.000000
Wrote mass._I(0,2): 0.000000
Wrote mass._I(1,0): 0.000000
Wrote mass._I(1,1): 10.800000
Wrote mass._I(1,2): 0.000000
Wrote mass._I(2,0): 0.000000
Wrote mass._I(2,1): 0.000000
Wrote mass._I(2,2): 10.800000
Read mass.mass: 3.000000
Read mass.c[0]: 0.000000
Read mass.c[1]: 0.000000
Read mass.c[2]: 0.000000
Read mass.c[3]: 0.000000
Read mass._I(0,0): 10.800000
Read mass._I(0,1): 0.000000
Read mass._I(0,2): 0.000000
Read mass._I(1,0): 0.000000
Read mass._I(1,1): 10.800000
Read mass._I(1,2): 0.000000
Read mass._I(2,0): 0.000000
Read mass._I(2,1): 0.000000
Read mass._I(2,2): 10.800000
==>quit();

Thanks Tom & Stephen!

Gary (-;