Files
Goober5000 86f75c5d17 fix normalized vector handling (#6650)
Although normalized vectors in FSO are supposed to stay normalized, occasionally small floating point errors can pop up.  In most cases these do not cause a problem, but in certain cases these errors can rapidly accumulate and corrupt an otherwise well-behaved matrix.  This can most easily be seen with daisy-chained dock sequences or docked ships with angled dockpoints.

This PR audits the usage of `vm_vector_2_matrix_norm` to re-normalize vectors in cases where floating point errors could accumulate.  There are also a few new Assertions for extra vigilance.

Follow-up to #6596.  Fixes #6634.
2025-03-28 23:54:51 +01:00

235 lines
6.1 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include "math/staticrand.h"
#include "math/vecmat.h"
#include "utils/Random.h"
bool Semirand_inited = false;
unsigned int Semirand[SEMIRAND_MAX];
/**
* @brief Initialize Semirand array. Doesn't have to be called.
*/
void init_semirand()
{
Semirand_inited = true;
// Originally this made a 30-bit rand by sticking two 15-bit rands from myrand() together. Instead we trim Random::next() down to size.
for (auto & number : Semirand)
number = (unsigned) (util::Random::next() & STATIC_RAND_MAX);
}
// TODO: figure out what to do with these
/**
* @brief Return a pseudo random 32 bit value given a reasonably small number.
*
* @param num Seed input number
* @return Pseudo random 32 bit value
*/
int static_rand(int num)
{
if (num < 0)
num *= -1;
if (!Semirand_inited)
init_semirand();
const unsigned int num_unsigned = num;
auto a = num_unsigned & (SEMIRAND_MAX - 1);
auto b = (num_unsigned >> SEMIRAND_MAX_LOG) & (SEMIRAND_MAX - 1);
auto c = (num_unsigned >> (2 * SEMIRAND_MAX_LOG)) & (SEMIRAND_MAX - 1);
return Semirand[a] ^ Semirand[b] ^ Semirand[c];
}
/**
* @brief Return a random float in 0.0f .. 1.0f- (ie, it will never return 1.0f).
*
* @param num Seed input number
* @return Random value in 0.0f .. 1.0f- (ie, it will never return 1.0f).
*/
float static_randf(int num)
{
unsigned int a = static_rand(num);
return (a & 0xffff) / 65536.0f;
}
/**
* @brief Return a random integer within a range. Note: min and max are inclusive
*
* @param num Seed input number
* @param min Minimum range integer to return
* @param max Maximum range integer to return
* @return Random integer within the range
*/
int static_rand_range(int num, int min, int max)
{
int rval = static_rand(num);
rval = (rval % (max - min + 1)) + min;
CLAMP(rval, min, max);
return rval;
}
/**
* @brief Return a random float within a range.
* Note: min and max are inclusive
*
* @param num Seed input number
* @param min Minimum range float to return
* @param max Maximum range float to return
* @return Random float within the range
*/
float static_randf_range(int num, float min, float max)
{
float rval = static_randf(num);
rval = rval * (max - min) + min;
return rval;
}
/**
* @brief Create a random, normalized vector in unit sphere
*
* @param num Seed input number
* @param vp Vector
*/
void static_randvec(int num, vec3d *vp)
{
vp->xyz.x = static_randf(num) - 0.5f;
vp->xyz.y = static_randf(num+1) - 0.5f;
vp->xyz.z = static_randf(num+2) - 0.5f;
vm_vec_normalize_quick(vp);
}
/**
*
* @brief Create a random, unnormalized vector in the (half) unit cube
*
* @param num Seed input vector
* @param vp Vector
*/
void static_randvec_unnormalized(int num, vec3d* vp)
{
vp->xyz.x = static_randf(num) - 0.5f;
vp->xyz.y = static_randf(num + 1) - 0.5f;
vp->xyz.z = static_randf(num + 2) - 0.5f;
}
/**
* @brief Randomly perturb a vector around a given (normalized vector) or optional orientation matrix.
*
* @param num
* @param out
* @param in
* @param max_angle
* @param orient
*/
void static_rand_cone(int num, vec3d *out, const vec3d* const in, float max_angle, const matrix* const orient)
{
vec3d t1, t2;
const matrix *rot;
matrix m;
// get an orientation matrix
if(orient != nullptr){
rot = orient;
} else {
Assertion(in != nullptr && vm_vec_is_normalized(in), "input vector must be normalized!");
vm_vector_2_matrix_norm(&m, in, nullptr, nullptr);
rot = &m;
}
// axis 1
vm_rot_point_around_line(&t1, in, fl_radians(static_randf_range(num,-max_angle, max_angle)), &vmd_zero_vector, &rot->vec.fvec);
// axis 2
vm_rot_point_around_line(&t2, &t1, fl_radians(static_randf_range(num+1,-max_angle, max_angle)), &vmd_zero_vector, &rot->vec.rvec);
// axis 3
vm_rot_point_around_line(out, &t2, fl_radians(static_randf_range(num+2,-max_angle, max_angle)), &vmd_zero_vector, &rot->vec.uvec);
}
//generates a random vector in a cone, with a min amd max angle.
//Clone of vm_vec_random_cone overload of the same function, adapted to use static_randf_range
void static_rand_cone(int num, vec3d* out, const vec3d* const in, float min_angle, float max_angle, const matrix* const orient)
{
vec3d temp;
const matrix* rot;
matrix m;
if (max_angle < min_angle) {
std::swap(min_angle, max_angle);
}
// get an orientation matrix
if (orient != nullptr) {
rot = orient;
} else {
Assertion(in != nullptr && vm_vec_is_normalized(in), "input vector must be normalized!");
vm_vector_2_matrix_norm(&m, in, nullptr, nullptr);
rot = &m;
}
// Get properly distributed spherical coordinates (DahBlount)
// This might not seem intuitive, but the min_angle is the angle that will have a larger z coordinate
float z = static_randf_range(num, cosf(fl_radians(max_angle)), cosf(fl_radians(min_angle))); // Take a 2-sphere slice
float phi = static_randf_range(num+1, 0.0f, PI2);
vm_vec_make(&temp, sqrtf(1.0f - z * z) * cosf(phi), sqrtf(1.0f - z * z) * sinf(phi), z); // Using the z-vec as the starting point
vm_vec_unrotate(out, &temp, rot); // We find the final vector by rotating temp to the correct orientation
}
/////////////////////////////////////////////////////////////////////
// Alternate random number generator, that doesn't affect rand() sequence
/////////////////////////////////////////////////////////////////////
constexpr unsigned int RND_MASK = 0x6000;
constexpr int RND_MAX = 0x7fff;
unsigned int Rnd_seed = 1;
/**
* @brief Seed the alternative random number generator.
* Doesn't have to be called.
*
* @param seed Seed input number
*/
void init_static_rand_alt(int seed)
{
Rnd_seed = seed;
}
/**
* @brief Get a random integer between 1 and RND_MAX.
*
* @return Random integer between 1 and RND_MAX
*/
int static_rand_alt()
{
static unsigned int x=Rnd_seed;
unsigned int old_x = x;
x >>= 1u;
if ( old_x & 1u ) {
x ^= RND_MASK;
}
return x;
}
/**
* @brief Get a random integer between 0 and 1.0.
*
* @return Random float between 0 and 1.0
*/
float static_randf_alt()
{
int r = static_rand_alt();
return i2fl(r)/RND_MAX;
}