#include <survive.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
#define OLD_ANGLES_BUFF_LEN 3
FLT oldAngles[SENSORS_PER_OBJECT][2][NUM_LIGHTHOUSES][OLD_ANGLES_BUFF_LEN]; // sensor, sweep axis, lighthouse, instance
int angleIndex[NUM_LIGHTHOUSES][2]; // index into circular buffer ahead. separate index for each axis.
int lastAxis[NUM_LIGHTHOUSES];
int hitCount[SENSORS_PER_OBJECT][NUM_LIGHTHOUSES][2];
} OctavioRadiiData;
#include <stdio.h>
#include <stdlib.h>
#include "linmath.h"
#include <string.h>
#include <stdint.h>
#include <math.h>
#define PTS 32
#define MAX_CHECKS 40000
#define MIN_HITS_FOR_VALID 10
FLT hmd_points[PTS * 3];
FLT hmd_norms[PTS * 3];
FLT hmd_point_angles[PTS * 2];
int hmd_point_counts[PTS * 2];
int best_hmd_target = 0;
//Values used for RunTest()
FLT LighthousePos[3] = { 0, 0, 0 };
FLT LighthouseQuat[4] = { 1, 0, 0, 0 };
#define MAX_POINT_PAIRS 100
typedef struct
{
FLT x;
FLT y;
FLT z;
} Point;
typedef struct
{
Point point; // location of the sensor on the tracked object;
Point normal; // unit vector indicating the normal for the sensor
double theta; // "horizontal" angular measurement from lighthouse radians
double phi; // "vertical" angular measurement from lighthouse in radians.
int id;
} TrackedSensor;
typedef struct
{
size_t numSensors;
TrackedSensor sensor[0];
} TrackedObject;
typedef struct
{
unsigned char index1;
unsigned char index2;
FLT KnownDistance;
} PointPair;
static FLT distance(Point a, Point b)
{
FLT x = a.x - b.x;
FLT y = a.y - b.y;
FLT z = a.z - b.z;
return FLT_SQRT(x*x + y*y + z*z);
}
typedef struct
{
FLT HorizAngle;
FLT VertAngle;
} SensorAngles;
#define SQUARED(x) ((x)*(x))
static FLT calculateFitnessOld(SensorAngles *angles, FLT *radii, PointPair *pairs, size_t numPairs)
{
FLT fitness = 0;
for (size_t i = 0; i < numPairs; i++)
{
FLT estimatedDistanceBetweenPoints =
SQUARED(radii[pairs[i].index1])
+ SQUARED(radii[pairs[i].index2])
- 2 * radii[pairs[i].index1] * radii[pairs[i].index2]
* FLT_SIN(angles[pairs[i].index1].HorizAngle) * FLT_SIN(angles[pairs[i].index2].HorizAngle)
* FLT_COS(angles[pairs[i].index1].VertAngle - angles[pairs[i].index2].VertAngle)
+ FLT_COS(angles[pairs[i].index1].VertAngle) * FLT_COS(angles[pairs[i].index2].VertAngle);
fitness += SQUARED(estimatedDistanceBetweenPoints - pairs[i].KnownDistance);
}
return FLT_SQRT(fitness);
}
// angles is an array of angles between a sensor pair
// pairs is an array of point pairs
// radii is the guess at the radii of those angles
static FLT calculateFitnessOld2(SensorAngles *angles, FLT *radii, PointPair *pairs, size_t numPairs)
{
FLT fitness = 0;
for (size_t i = 0; i < numPairs; i++)
{
// These are the vectors that represent the direction for the two points.
// TODO: optimize by precomputing the tangent.
FLT v1[3], v2[3], diff[3];
v1[0] = 1;
v2[0] = 1;
v1[1] = tan(angles[pairs[i].index1].HorizAngle); // can be precomputed
v2[1] = tan(angles[pairs[i].index2].HorizAngle); // can be precomputed
v1[2] = tan(angles[pairs[i].index1].VertAngle); // can be precomputed
v2[2] = tan(angles[pairs[i].index2].VertAngle); // can be precomputed
// Now, normalize the vectors
normalize3d(v1, v1); // can be precomputed
normalize3d(v2, v2); // can be precomputed
// Now, given the specified radii, find where the new points are
scale3d(v1, v1, radii[pairs[i].index1]);
scale3d(v2, v2, radii[pairs[i].index2]);
// Cool, now find the vector between these two points
// TODO: optimize the following two funcs into one.
sub3d(diff, v1, v2);
FLT distance = magnitude3d(diff);
FLT t1 = magnitude3d(v1);
FLT t2 = magnitude3d(v2);
FLT estimatedDistanceBetweenPoints =
SQUARED(radii[pairs[i].index1])
+ SQUARED(radii[pairs[i].index2])
- 2 * radii[pairs[i].index1] * radii[pairs[i].index2]
* FLT_SIN(angles[pairs[i].index1].HorizAngle) * FLT_SIN(angles[pairs[i].index2].HorizAngle)
* FLT_COS(angles[pairs[i].index1].VertAngle - angles[pairs[i].index2].VertAngle)
+ FLT_COS(angles[pairs[i].index1].VertAngle) * FLT_COS(angles[pairs[i].index2].VertAngle);
//fitness += SQUARED(estimatedDistanceBetweenPoints - pairs[i].KnownDistance);
fitness += SQUARED(distance - pairs[i].KnownDistance);
}
return FLT_SQRT(fitness);
}
static FLT angleBetweenSensors(SensorAngles *a, SensorAngles *b)
{
FLT angle = FLT_ACOS(FLT_COS(a->VertAngle - b->VertAngle)*FLT_COS(a->HorizAngle - b->HorizAngle));
//FLT angle2 = FLT_ACOS(FLT_COS(b->phi - a->phi)*FLT_COS(b->theta - a->theta));
return angle;
}
// angles is an array of angles between a sensor pair
// pairs is an array of point pairs
// radii is the guess at the radii of those angles
static FLT calculateFitness(SensorAngles *angles, FLT *radii, PointPair *pairs, size_t numPairs)
{
FLT fitness = 0;
for (size_t i = 0; i < numPairs; i++)
{
FLT angle = angleBetweenSensors(&angles[pairs[i].index1], &angles[pairs[i].index2]);
// now we have a side-angle-side triangle, and we need to find the third side.
// The Law of Cosines says: a^2 = b^2 + c^2 ? 2bc * cosA,
// where A is the angle opposite side a.
// Transform this to:
// a = sqrt(b^2 + c^2 - 2bc * cosA) and we know the length of the missing side!
FLT b2 = (SQUARED(radii[pairs[i].index1]));
FLT c2 = (SQUARED(radii[pairs[i].index2]));
FLT bc2 = (2 * radii[pairs[i].index1] * radii[pairs[i].index2]);
FLT cosA = (FLT_COS(angle));
FLT angleInDegrees = angle * 180 / LINMATHPI;
FLT dist = sqrt( (SQUARED(radii[pairs[i].index1])) +
(SQUARED(radii[pairs[i].index2])) -
( (2 * radii[pairs[i].index1] * radii[pairs[i].index2]) *
(FLT_COS(angle))));
FLT fitnessAdder = SQUARED(dist - pairs[i].KnownDistance);
if (isnan(fitnessAdder))
{
int a = 0;
}
//printf(" %2d %f\n", i, fitnessAdder);
//fitness += SQUARED(estimatedDistanceBetweenPoints - pairs[i].KnownDistance);
fitness += SQUARED(dist - pairs[i].KnownDistance);
}
//fitness = 1 / fitness;
return FLT_SQRT(fitness);
}
#define MAX_RADII 32
// note gradientOut will be of the same degree as numRadii
static void getGradient(FLT *gradientOut, SensorAngles *angles, FLT *radii, size_t numRadii, PointPair *pairs, size_t numPairs, const FLT precision)
{
FLT baseline = calculateFitness(angles, radii, pairs, numPairs);
for (size_t i = 0; i < numRadii; i++)
{
FLT tmpPlus[MAX_RADII];
memcpy(tmpPlus, radii, sizeof(*radii) * numRadii);
tmpPlus[i] += precision;
gradientOut[i] = -(calculateFitness(angles, tmpPlus, pairs, numPairs) - baseline);
}
return;
}
static void normalizeAndMultiplyVector(FLT *vectorToNormalize, size_t count, FLT desiredMagnitude)
{
FLT distanceIn = 0;
for (size_t i = 0; i < count; i++)
{
distanceIn += SQUARED(vectorToNormalize[i]);
}
distanceIn = FLT_SQRT(distanceIn);
FLT scale = desiredMagnitude / distanceIn;
for (size_t i = 0; i < count; i++)
{
vectorToNormalize[i] *= scale;
}
return;
}
static void RefineEstimateUsingGradientDescentRadii(FLT *estimateOut, SensorAngles *angles, FLT *initialEstimate, size_t numRadii, PointPair *pairs, size_t numPairs, FILE *logFile)
{
int i = 0;
FLT lastMatchFitness = calculateFitness(angles, initialEstimate, pairs, numPairs);
if (estimateOut != initialEstimate)
{
memcpy(estimateOut, initialEstimate, sizeof(*estimateOut) * numRadii);
}
// The values below are somewhat magic, and definitely tunable
// The initial vlue of g will represent the biggest step that the gradient descent can take at first.
// bigger values may be faster, especially when the initial guess is wildly off.
// The downside to a bigger starting guess is that if we've picked a good guess at the local minima
// if there are other local minima, we may accidentally jump to such a local minima and get stuck there.
// That's fairly unlikely with the lighthouse problem, from expereince.
// The other downside is that if it's too big, we may have to spend a few iterations before it gets down
// to a size that doesn't jump us out of our minima.
// The terminal value of g represents how close we want to get to the local minima before we're "done"
// The change in value of g for each iteration is intentionally very close to 1.
// in fact, it probably could probably be 1 without any issue. The main place where g is decremented
// is in the block below when we've made a jump that results in a worse fitness than we're starting at.
// In those cases, we don't take the jump, and instead lower the value of g and try again.
for (FLT g = 0.4; g > 0.00001; g *= 0.9999)
{
i++;
FLT point1[MAX_RADII];
memcpy(point1, estimateOut, sizeof(*point1) * numRadii);
// let's get 3 iterations of gradient descent here.
FLT gradient1[MAX_RADII];
getGradient(gradient1, angles, point1, numRadii, pairs, numPairs, g / 1000 /*somewhat arbitrary*/);
normalizeAndMultiplyVector(gradient1, numRadii, g);
FLT point2[MAX_RADII];
for (size_t i = 0; i < numRadii; i++)
{
point2[i] = point1[i] + gradient1[i];
}
FLT gradient2[MAX_RADII];
getGradient(gradient2, angles, point2, numRadii, pairs, numPairs, g / 1000 /*somewhat arbitrary*/);
normalizeAndMultiplyVector(gradient2, numRadii, g);
FLT point3[MAX_RADII];
for (size_t i = 0; i < numRadii; i++)
{
point3[i] = point2[i] + gradient2[i];
}
// remember that gradient descent has a tendency to zig-zag when it encounters a narrow valley?
// Well, solving the lighthouse problem presents a very narrow valley, and the zig-zag of a basic
// gradient descent is kinda horrible here. Instead, think about the shape that a zig-zagging
// converging gradient descent makes. Instead of using the gradient as the best indicator of
// the direction we should follow, we're looking at one side of the zig-zag pattern, and specifically
// following *that* vector. As it turns out, this works *amazingly* well.
FLT specialGradient[MAX_RADII];
for (size_t i = 0; i < numRadii; i++)
{
specialGradient[i] = point3[i] - point1[i];
}
// The second parameter to this function is very much a tunable parameter. Different values will result
// in a different number of iterations before we get to the minimum. Numbers between 3-10 seem to work well
// It's not clear what would be optimum here.
normalizeAndMultiplyVector(specialGradient, numRadii, g / 4);
FLT point4[MAX_RADII];
for (size_t i = 0; i < numRadii; i++)
{
point4[i] = point3[i] + specialGradient[i];
}
FLT newMatchFitness = calculateFitness(angles, point4, pairs, numPairs);
if (newMatchFitness < lastMatchFitness)
{
//if (logFile)
//{
// writePoint(logFile, lastPoint.x, lastPoint.y, lastPoint.z, 0xFFFFFF);
//}
lastMatchFitness = newMatchFitness;
memcpy(estimateOut, point4, sizeof(*estimateOut) * numRadii);
#ifdef RADII_DEBUG
printf("+ %d %0.9f (%0.9f) \n", i, newMatchFitness, g);
#endif
g = g * 1.05;
}
else
{
//#ifdef RADII_DEBUG
// printf("-");
//printf("- %d %0.9f (%0.9f) [%0.9f] \n", i, newMatchFitness, g, estimateOut[0]);
//#endif
// if it wasn't a match, back off on the distance we jump
g *= 0.7;
}
#ifdef RADII_DEBUG
FLT avg = 0;
FLT diffFromAvg[MAX_RADII];
for (size_t m = 0; m < numRadii; m++)
{
avg += estimateOut[m];
}
avg = avg / numRadii;
for (size_t m = 0; m < numRadii; m++)
{
diffFromAvg[m] = estimateOut[m] - avg;;
}
printf("[avg:%f] ", avg);
for (size_t x = 0; x < numRadii; x++)
{
printf("%f, ", diffFromAvg[x]);
//printf("%f, ", estimateOut[x]);
}
printf("\n");
#endif
}
printf(" i=%d ", i);
}
static void SolveForLighthouseRadii(Point *objPosition, FLT *objOrientation, TrackedObject *obj)
{
FLT estimate[MAX_RADII];
for (size_t i = 0; i < MAX_RADII; i++)
{
estimate[i] = 2.38;
}
//for (int i=0; i < obj->numSensors; i++)
//{
// printf("%d, ", obj->sensor[i].id);
//}
SensorAngles angles[MAX_RADII];
PointPair pairs[MAX_POINT_PAIRS];
size_t pairCount = 0;
//obj->numSensors = 5; // TODO: HACK!!!!
for (size_t i = 0; i < obj->numSensors; i++)
{
angles[i].HorizAngle = obj->sensor[i].theta;
angles[i].VertAngle = obj->sensor[i].phi;
}
for (unsigned char i = 0; i < obj->numSensors - 1; i++)
{
for (unsigned char j = i + 1; j < obj->numSensors; j++)
{
pairs[pairCount].index1 = i;
pairs[pairCount].index2 = j;
pairs[pairCount].KnownDistance = distance(obj->sensor[i].point, obj->sensor[j].point);
pairCount++;
}
}
RefineEstimateUsingGradientDescentRadii(estimate, angles, estimate, obj->numSensors, pairs, pairCount, NULL);
// we should now have an estimate of the radii.
//for (int i = 0; i < obj->numSensors; i++)
for (int i = 0; i < 1; i++)
{
printf("radius[%d]: %f\n", i, estimate[i]);
}
// (FLT *estimateOut, SensorAngles *angles, FLT *initialEstimate, size_t numRadii, PointPair *pairs, size_t numPairs, FILE *logFile)
return;
}
static void QuickPose(SurviveObject *so)
{
OctavioRadiiData * td = so->PoserData;
//for (int i=0; i < so->sensor_ct; i++)
//{
// FLT x0=td->oldAngles[i][0][0][td->angleIndex[0][0]];
// FLT y0=td->oldAngles[i][1][0][td->angleIndex[0][1]];
// //FLT x1=td->oldAngles[i][0][1][td->angleIndex[1][0]];
// //FLT y1=td->oldAngles[i][1][1][td->angleIndex[1][1]];
// //printf("%2d: %8.8f, %8.8f %8.8f, %8.8f \n",
// // i,
// // x0,
// // y0,
// // x1,
// // y1
// // );
// printf("%2d: %8.8f, %8.8f \n",
// i,
// x0,
// y0
// );
//}
//printf("\n");
TrackedObject *to;
to = malloc(sizeof(TrackedObject) + (SENSORS_PER_OBJECT * sizeof(TrackedSensor)));
{
int sensorCount = 0;
for (int i = 0; i < so->sensor_ct; i++)
{
int lh = 0;
//printf("%d[%d], ",i,td->hitCount[i][lh][0]);
int angleIndex0 = (td->angleIndex[lh][0] + 1 + OLD_ANGLES_BUFF_LEN) % OLD_ANGLES_BUFF_LEN;
int angleIndex1 = (td->angleIndex[lh][1] + 1 + OLD_ANGLES_BUFF_LEN) % OLD_ANGLES_BUFF_LEN;
if ((td->oldAngles[i][0][lh][angleIndex0] != 0 && td->oldAngles[i][1][lh][angleIndex1] != 0))
{
if (td->hitCount[i][lh][0] > 10 && td->hitCount[i][lh][1] > 10)
{
FLT norm[3] = { so->sensor_normals[i * 3 + 0] , so->sensor_normals[i * 3 + 1] , so->sensor_normals[i * 3 + 2] };
FLT point[3] = { so->sensor_locations[i * 3 + 0] , so->sensor_locations[i * 3 + 1] , so->sensor_locations[i * 3 + 2] };
to->sensor[sensorCount].normal.x = norm[0];
to->sensor[sensorCount].normal.y = norm[1];
to->sensor[sensorCount].normal.z = norm[2];
to->sensor[sensorCount].point.x = point[0];
to->sensor[sensorCount].point.y = point[1];
to->sensor[sensorCount].point.z = point[2];
to->sensor[sensorCount].theta = td->oldAngles[i][0][lh][angleIndex0] + LINMATHPI / 2; // lighthouse 0, angle 0 (horizontal)
to->sensor[sensorCount].phi = td->oldAngles[i][1][lh][angleIndex1] + LINMATHPI / 2; // lighthouse 0, angle 1 (vertical)
to->sensor[sensorCount].id=i;
//printf("%2d: %8.8f, %8.8f \n",
// i,
// to->sensor[sensorCount].theta,
// to->sensor[sensorCount].phi
// );
sensorCount++;
}
}
}
//printf("\n");
to->numSensors = sensorCount;
if (sensorCount > 4)
{
Point pos;
FLT orient[4];
SolveForLighthouseRadii(&pos, orient, to);
}
}
free(to);
}
int PoserOctavioRadii( SurviveObject * so, PoserData * pd )
{
PoserType pt = pd->pt;
SurviveContext * ctx = so->ctx;
OctavioRadiiData * dd = so->PoserData;
if( !dd )
{
so->PoserData = dd = malloc( sizeof(OctavioRadiiData) );
memset(dd, 0, sizeof(OctavioRadiiData));
}
switch( pt )
{
case POSERDATA_IMU:
{
PoserDataIMU * imu = (PoserDataIMU*)pd;
//printf( "IMU:%s (%f %f %f) (%f %f %f)\n", so->codename, imu->accel[0], imu->accel[1], imu->accel[2], imu->gyro[0], imu->gyro[1], imu->gyro[2] );
break;
}
case POSERDATA_LIGHT:
{
PoserDataLight * l = (PoserDataLight*)pd;
if (l->lh >= NUM_LIGHTHOUSES || l->lh < 0)
{
// should never happen. Famous last words...
break;
}
int axis = l->acode & 0x1;
//printf("%d ", l->sensor_id);
//printf( "LIG:%s %d @ %f rad, %f s (AC %d) (TC %d)\n", so->codename, l->sensor_id, l->angle, l->length, l->acode, l->timecode );
if ((dd->lastAxis[l->lh] != (l->acode & 0x1)) )
{
int lastAxis = dd->lastAxis[l->lh];
//printf("\n");
//if (0 == l->lh)
// printf("or[%d,%d] ", l->lh,lastAxis);
for (int i=0; i < SENSORS_PER_OBJECT; i++)
{
//FLT oldAngles[SENSORS_PER_OBJECT][2][NUM_LIGHTHOUSES][OLD_ANGLES_BUFF_LEN]; // sensor, sweep axis, lighthouse, instance
int index = dd->angleIndex[l->lh][axis];
if (dd->oldAngles[i][axis][l->lh][dd->angleIndex[l->lh][axis]] != 0)
{
//if (0 == l->lh)
// printf("%d ", i);
dd->hitCount[i][l->lh][axis]++;
}
else
{
dd->hitCount[i][l->lh][axis] = (int)((double)dd->hitCount[i][l->lh][axis] * 0.5);
}
}
//if (0 == l->lh)
// printf("\n");
//int foo = l->acode & 0x1;
//printf("%d", foo);
//if (axis)
{
if (0 == l->lh && axis) // only once per full cycle...
{
static unsigned int counter = 1;
counter++;
// let's just do this occasionally for now...
if (counter % 4 == 0)
QuickPose(so);
}
// axis changed, time to increment the circular buffer index.
dd->angleIndex[l->lh][axis]++;
dd->angleIndex[l->lh][axis] = dd->angleIndex[l->lh][axis] % OLD_ANGLES_BUFF_LEN;
// and clear out the data.
for (int i=0; i < SENSORS_PER_OBJECT; i++)
{
dd->oldAngles[i][axis][l->lh][dd->angleIndex[l->lh][axis]] = 0;
}
}
dd->lastAxis[l->lh] = axis;
}
//if (0 == l->lh)
// printf("(%d) ", l->sensor_id);
//FLT oldAngles[SENSORS_PER_OBJECT][2][NUM_LIGHTHOUSES][OLD_ANGLES_BUFF_LEN]; // sensor, sweep axis, lighthouse, instance
dd->oldAngles[l->sensor_id][axis][l->lh][dd->angleIndex[l->lh][axis]] = l->angle;
break; }
case POSERDATA_FULL_SCENE:
{
TrackedObject *to;
PoserDataFullScene * fs = (PoserDataFullScene*)pd;
to = malloc(sizeof(TrackedObject) + (SENSORS_PER_OBJECT * sizeof(TrackedSensor)));
//FLT lengths[SENSORS_PER_OBJECT][NUM_LIGHTHOUSES][2];
//FLT angles[SENSORS_PER_OBJECT][NUM_LIGHTHOUSES][2]; //2 Axes (Angles in LH space)
//FLT synctimes[SENSORS_PER_OBJECT][NUM_LIGHTHOUSES];
//to->numSensors = so->sensor_ct;
{
int sensorCount = 0;
for (int i = 0; i < so->sensor_ct; i++)
{
if (fs->lengths[i][0][0] != -1 && fs->lengths[i][0][1] != -1) //lh 0
{
to->sensor[sensorCount].normal.x = so->sensor_normals[i * 3 + 0];
to->sensor[sensorCount].normal.y = so->sensor_normals[i * 3 + 1];
to->sensor[sensorCount].normal.z = so->sensor_normals[i * 3 + 2];
to->sensor[sensorCount].point.x = so->sensor_locations[i * 3 + 0];
to->sensor[sensorCount].point.y = so->sensor_locations[i * 3 + 1];
to->sensor[sensorCount].point.z = so->sensor_locations[i * 3 + 2];
to->sensor[sensorCount].theta = fs->angles[i][0][0] + LINMATHPI / 2; // lighthouse 0, angle 0 (horizontal)
to->sensor[sensorCount].phi = fs->angles[i][0][1] + LINMATHPI / 2; // lighthosue 0, angle 1 (vertical)
to->sensor[sensorCount].id=i;
sensorCount++;
}
}
to->numSensors = sensorCount;
Point position;
FLT orientation[4];
SolveForLighthouseRadii(&position, orientation, to);
}
{
int sensorCount = 0;
int lh = 1;
for (int i = 0; i < so->sensor_ct; i++)
{
if (fs->lengths[i][lh][0] != -1 && fs->lengths[i][lh][1] != -1)
{
to->sensor[sensorCount].normal.x = so->sensor_normals[i * 3 + 0];
to->sensor[sensorCount].normal.y = so->sensor_normals[i * 3 + 1];
to->sensor[sensorCount].normal.z = so->sensor_normals[i * 3 + 2];
to->sensor[sensorCount].point.x = so->sensor_locations[i * 3 + 0];
to->sensor[sensorCount].point.y = so->sensor_locations[i * 3 + 1];
to->sensor[sensorCount].point.z = so->sensor_locations[i * 3 + 2];
to->sensor[sensorCount].theta = fs->angles[i][lh][0] + LINMATHPI / 2; // lighthouse 0, angle 0 (horizontal)
to->sensor[sensorCount].phi = fs->angles[i][lh][1] + LINMATHPI / 2; // lighthosue 0, angle 1 (vertical)
to->sensor[sensorCount].id=i;
sensorCount++;
}
}
to->numSensors = sensorCount;
Point position;
FLT orientation[4];
SolveForLighthouseRadii(&position, orientation, to);
}
//printf( "Full scene data.\n" );
break;
}
case POSERDATA_DISASSOCIATE:
{
free( dd );
so->PoserData = 0;
//printf( "Need to disassociate.\n" );
break;
}
}
return 0;
}
REGISTER_LINKTIME( PoserOctavioRadii );