Newer
Older
Import / research / ui / toolkit / test / formfactors_test.cpp
#include <cmath>
#include "Maths.h"


struct vec4f
{
  union {
    float v[4];
    struct { float x, y, z, w; };
  };
};


struct mat4f
{
  union {
    vec4f m[4];
    struct { vec4f t, b, n, w; };  // tangent, bi-normal, normal
  };
};


vec4f addVector(const vec4f& a, const vec4f& b)
{
  vec4f res;
  res.x = a.x + b.x;
  res.y = a.y + b.y;
  res.z = a.z + b.z;
  res.w = a.w + b.w;
  return res;
}


vec4f subtractVector(const vec4f& a, const vec4f& b)
{
  vec4f res;
  res.x = a.x - b.x;
  res.y = a.y - b.y;
  res.z = a.z - b.z;
  res.w = a.w - b.w;
  return res;
}


void rotateVector(vec4f& vec, const mat4f& mat)
{
  vec4f res;
  Math::transformVector(&res.x, &mat.t.x, &vec.x);
  vec = res;
}


// An area - like a quad, but modelled as a 2D circular area floating in 3D space
struct Patch
{
  vec4f  normal; // <- instead a reference to some group it belongs to with shared TBN
  vec4f  position; // w of position is the radius
  float  intensity;
};


bool inline isNearZero(float val)
{
  const float epsilon = 0.0001f;
  return (val < epsilon && val > -epsilon);
}


vec4f orthogonalVector(const vec4f& normal)
{
  // A vector orthogonal to (a, b, c) is (-b, a, 0), or (-c, 0, a) or (0, -c, b)
  // derivation:
  //   plane through origin
  //     ax + by + cz = 0
  //   orthogonal vector is any point on this plane, eg satisfies the equation
  // so let x be 0
  // by = -cz
  // y = -c/b z
  // let y be c
  // c = -c/b z
  // c/ (-c/b) = z
  // z = -b
  // x = 0, y = c, z = -b
  vec4f v;
  if (!isNearZero(normal.z) || !isNearZero(normal.y)) {
    v.x = 0.0f; v.y = normal.z; v.z = -normal.y;
  } else if (!isNearZero(normal.x) || !isNearZero(normal.y)) {
    v.x = normal.y; v.y = -normal.x; v.z = 0.0f;
  } else if (!isNearZero(normal.x) || !isNearZero(normal.z)) {
    v.x = normal.z; v.y = 0.0f; v.z = -normal.x;
  //} else if (isNearZero(normal.x) && isNearZero(normal.z) && isNearZero(normal.y)) {
  } else {
    printf("vector is zero, so can't make orthogonal vector\n");
    //printf("last case\n");
    //v.x = normal.z; v.y = 0.0f; v.z = -normal.x;
  }
  v.w = 0.0f;
  return v;
}


// This info is same across a polygon which is broken in to patches
// Ideally it should be calculated per polygon and then each patch references this
mat4f patchAxisVectors(const Patch& patch)
{
  // Calc tangent and bi-normals for patch
  mat4f res;
  res.t = orthogonalVector(patch.normal); // orthogonal to normal
  Math::normalizeVector(&res.t.x);
  Math::crossProduct(&res.b.x, &patch.normal.x, &res.t.x);
  res.b.w = 0.0f;
  Math::normalizeVector(&res.b.x);
  res.n = patch.normal;
  res.n.w = 0.0f;
  res.w.x = 0.0f;
  res.w.y = 0.0f;
  res.w.z = 0.0f;
  res.w.w = 1.0f;
  return res;
}


// Calculates the influence of one patch on another patch
// represents the projection of the patch on to the hemi-sphere then projected down to the circle
// The calculated value is called the 'Nusselt analog'
// Alternative simplified calculation might be to do a dot-product between each normals and
// the vector between the patches and attenuate by the distance between them as an approximation
// doesn't take in to account obscurence by intervening patches - need some other logic to calculate
// possibly with use of BSP trees - probably a kind of ray-tracing from the patch out to test it
// reaches other patches
float nusseltFormFactor(const Patch& patch1, const Patch& patch2)
{
  // Could cache the tangents and bi-normals
  //mat4f p1 = scene.quads[patch1.quadIndex].basis;
  //mat4f p2 = scene.quads[patch2.quadIndex].basis;
  mat4f p1 = patchAxisVectors(patch1);
  mat4f p2 = patchAxisVectors(patch2);

  // get relative position of patch 2 to patch 1
  vec4f vecToPatch2 = subtractVector(patch2.position, patch1.position);

  // The corners in world space could be cached instead but at more storage cost
  vec4f corner1 = addVector(addVector(vecToPatch2, p2.t), p2.b);
  vec4f corner2 = addVector(subtractVector(vecToPatch2, p2.t), p2.b);
  vec4f corner3 = subtractVector(subtractVector(vecToPatch2, p2.t), p2.b);
  vec4f corner4 = subtractVector(addVector(vecToPatch2, p2.t), p2.b);

  // This is the projection on to a unit hemisphere
  Math::normalizeVector(&corner1.x);
  Math::normalizeVector(&corner2.x);
  Math::normalizeVector(&corner3.x);
  Math::normalizeVector(&corner4.x);

  // rotate in to coordinate space of patch 1
  rotateVector(corner1, p1);
  rotateVector(corner2, p1);
  rotateVector(corner3, p1);
  rotateVector(corner4, p1);

  // Now the x,y of the corners is their projection on to the circle
  vec4f v = subtractVector(corner1, corner2);
  vec4f w = subtractVector(corner3, corner2);
  float area = 0.5f * std::fabs(v.x * w.y - v.y * w.x);
  v = subtractVector(corner3, corner4);
  w = subtractVector(corner1, corner4);
  area += 0.5f * std::fabs(v.x * w.y - v.y * w.x);

  return area;
}


// Simplified form factor calculation - need to compare the results from this and test to see how good it is
// https://en.wikipedia.org/wiki/View_factor
float simpleFormFactor(const Patch& patch1, const Patch& patch2)
{
  // get direction of patch 1 to patch 2
  vec4f patch2patch = subtractVector(patch2.position, patch1.position);
  //printf("p2p: %f %f %f %f\n", patch2patch.x, patch2patch.y, patch2patch.z, patch2patch.w);
  float magSqr = Math::dotProduct(&patch2patch.x, &patch2patch.x, 1, 1, 3);
  float dot1 = patch1.normal.x * patch2patch.x + patch1.normal.y * patch2patch.y + patch1.normal.z * patch2patch.z;
  float dot2 = patch2.normal.x * patch2patch.x + patch2.normal.y * patch2patch.y + patch2.normal.z * patch2patch.z;
  //return (dot1 * dot2 * 11.5) / (M_PI * magSqr * magSqr); // the additional magSqr is a built-in normalization of patch2patch that is optimized away
  return (dot1 * dot2) / (M_PI * magSqr * magSqr); // the additional magSqr is a built-in normalization of patch2patch that is optimized away
}


void formFactorUnitTest()
{
  printf("testing form factors\n");

  Patch p1 = { {{{ 0.1f, 1.0f, 0.0f, 0.0f }}}, {{{ 0.0f, 0.0f, 0.0f, 0.0f }}} };
  Patch p2 = { {{{ 0.1f, 1.0f, 0.0f, 0.0f }}}, {{{ 20.0f, 20.0f, 20.0f, 0.0f }}} };
  Math::normalizeVector(&p1.normal.x);
  Math::normalizeVector(&p2.normal.x);

  for (int i = 0; i < 16; i++)
  {
    p2.position.x = 20.0f + i * 10.0f;
    float ff1 = nusseltFormFactor(p1, p2);
    float ff2 = simpleFormFactor(p1, p2);
    printf("x: %i  ff: %f  %f\n", i, ff1, ff2);
  }
  p2.position.x = 20.0f;
  for (int i = 0; i < 16; i++)
  {
    p2.position.y = 20.0f + i * 10.0f;
    float ff1 = nusseltFormFactor(p1, p2);
    float ff2 = simpleFormFactor(p1, p2);
    printf("x: %i  ff: %f  %f\n", i, ff1, ff2);
  }
  p2.position.y = 20.0f;
  for (int i = 0; i < 16; i++)
  {
    p2.position.z = 20.0f + i * 10.0f;
    float ff1 = nusseltFormFactor(p1, p2);
    float ff2 = simpleFormFactor(p1, p2);
    printf("x: %i  ff: %f  %f\n", i, ff1, ff2);
  }
}