/* * This file is part of the DXX-Rebirth project . * It is copyright by its individual contributors, as recorded in the * project's Git history. See COPYING.txt at the top level for license * terms and a link to the Git history. */ /* * * C version of vecmat library * */ #include #include #include // for sqrt #include "maths.h" #include "vecmat.h" #include "dxxerror.h" namespace dcx { //#define USE_ISQRT 1 constexpr vms_matrix vmd_identity_matrix = IDENTITY_MATRIX; constexpr vms_vector vmd_zero_vector{}; //adds two vectors, fills in dest, returns ptr to dest //ok for dest to equal either source, but should use vm_vec_add2() if so vms_vector &vm_vec_add(vms_vector &dest,const vms_vector &src0,const vms_vector &src1) { dest.x = src0.x + src1.x; dest.y = src0.y + src1.y; dest.z = src0.z + src1.z; return dest; } //subs two vectors, fills in dest, returns ptr to dest //ok for dest to equal either source, but should use vm_vec_sub2() if so vms_vector &_vm_vec_sub(vms_vector &dest,const vms_vector &src0,const vms_vector &src1) { dest.x = src0.x - src1.x; dest.y = src0.y - src1.y; dest.z = src0.z - src1.z; return dest; } //adds one vector to another. returns ptr to dest //dest can equal source void vm_vec_add2(vms_vector &dest,const vms_vector &src) { dest.x += src.x; dest.y += src.y; dest.z += src.z; } //subs one vector from another, returns ptr to dest //dest can equal source void vm_vec_sub2(vms_vector &dest,const vms_vector &src) { dest.x -= src.x; dest.y -= src.y; dest.z -= src.z; } static inline fix avg_fix(fix64 a, fix64 b) { return (a + b) / 2; } //averages two vectors. returns ptr to dest //dest can equal either source void vm_vec_avg(vms_vector &dest,const vms_vector &src0,const vms_vector &src1) { dest.x = avg_fix(src0.x, src1.x); dest.y = avg_fix(src0.y, src1.y); dest.z = avg_fix(src0.z, src1.z); } //scales a vector in place. returns ptr to vector vms_vector &vm_vec_scale(vms_vector &dest,fix s) { return vm_vec_copy_scale(dest, dest, s); } //scales and copies a vector. returns ptr to dest vms_vector &vm_vec_copy_scale(vms_vector &dest,const vms_vector &src,fix s) { dest.x = fixmul(src.x,s); dest.y = fixmul(src.y,s); dest.z = fixmul(src.z,s); return dest; } //scales a vector, adds it to another, and stores in a 3rd vector //dest = src1 + k * src2 void vm_vec_scale_add(vms_vector &dest,const vms_vector &src1,const vms_vector &src2,fix k) { dest.x = src1.x + fixmul(src2.x,k); dest.y = src1.y + fixmul(src2.y,k); dest.z = src1.z + fixmul(src2.z,k); } //scales a vector and adds it to another //dest += k * src void vm_vec_scale_add2(vms_vector &dest,const vms_vector &src,fix k) { dest.x += fixmul(src.x,k); dest.y += fixmul(src.y,k); dest.z += fixmul(src.z,k); } //scales a vector in place, taking n/d for scale. returns ptr to vector //dest *= n/d void vm_vec_scale2(vms_vector &dest,fix n,fix d) { #if 0 // DPH: Kludge: this was overflowing a lot, so I made it use the FPU. float nd; nd = f2fl(n) / f2fl(d); dest.x = fl2f( f2fl(dest.x) * nd); dest.y = fl2f( f2fl(dest.y) * nd); dest.z = fl2f( f2fl(dest.z) * nd); #else dest.x = fixmuldiv(dest.x,n,d); dest.y = fixmuldiv(dest.y,n,d); dest.z = fixmuldiv(dest.z,n,d); #endif } [[nodiscard]] static fix vm_vec_dot3(fix x,fix y,fix z,const vms_vector &v) { #if 0 quadint q; q.low = q.high = 0; fixmulaccum(&q,x,v->x); fixmulaccum(&q,y,v->y); fixmulaccum(&q,z,v->z); return fixquadadjust(&q); #else int64_t x0 = x; int64_t x1 = v.x; int64_t y0 = y; int64_t y1 = v.y; int64_t z0 = z; int64_t z1 = v.z; int64_t p = (x0 * x1) + (y0 * y1) + (z0 * z1); /* Convert back to fix and return. */ return p >> 16; #endif } fix vm_vec_dot(const vms_vector &v0,const vms_vector &v1) { return vm_vec_dot3(v0.x, v0.y, v0.z, v1); } vm_magnitude_squared vm_vec_mag2(const vms_vector &v) { const int64_t x = v.x; const int64_t y = v.y; const int64_t z = v.z; return vm_magnitude_squared{static_cast((x * x) + (y * y) + (z * z))}; } vm_magnitude vm_vec_mag(const vms_vector &v) { quadint q; q.q = static_cast(vm_vec_mag2(v)); return vm_magnitude{quad_sqrt(q)}; } //computes the distance between two points. (does sub and mag) vm_distance vm_vec_dist(const vms_vector &v0,const vms_vector &v1) { return vm_vec_mag(vm_vec_sub(v0,v1)); } vm_distance_squared vm_vec_dist2(const vms_vector &v0,const vms_vector &v1) { return vm_vec_mag2(vm_vec_sub(v0,v1)); } //computes an approximation of the magnitude of the vector //uses dist = largest + next_largest*3/8 + smallest*3/16 vm_magnitude vm_vec_mag_quick(const vms_vector &v) { fix a,b,c,bc; a = labs(v.x); b = labs(v.y); c = labs(v.z); if (a < b) { std::swap(a, b); } if (b < c) { std::swap(b, c); if (a < b) { std::swap(a, b); } } bc = (b>>2) + (c>>3); return vm_magnitude{static_cast(a + bc + (bc>>1))}; } //computes an approximation of the distance between two points. //uses dist = largest + next_largest*3/8 + smallest*3/16 vm_distance vm_vec_dist_quick(const vms_vector &v0,const vms_vector &v1) { return vm_vec_mag_quick(vm_vec_sub(v0,v1)); } //normalize a vector. returns mag of source vec vm_magnitude vm_vec_copy_normalize(vms_vector &dest,const vms_vector &src) { auto m = vm_vec_mag(src); if (m) { vm_vec_divide(dest, src, m); } return m; } void vm_vec_divide(vms_vector &dest,const vms_vector &src, fix m) { dest.x = fixdiv(src.x,m); dest.y = fixdiv(src.y,m); dest.z = fixdiv(src.z,m); } //normalize a vector. returns mag of source vec vm_magnitude vm_vec_normalize(vms_vector &v) { return vm_vec_copy_normalize(v,v); } //normalize a vector. returns mag of source vec. uses approx mag vm_magnitude vm_vec_copy_normalize_quick(vms_vector &dest,const vms_vector &src) { auto m = vm_vec_mag_quick(src); if (m) { vm_vec_divide(dest, src, m); } return m; } //normalize a vector. returns 1/mag of source vec. uses approx 1/mag vm_magnitude vm_vec_normalize_quick(vms_vector &v) { return vm_vec_copy_normalize_quick(v,v); } //return the normalized direction vector between two points //dest = normalized(end - start). Returns 1/mag of direction vector //NOTE: the order of the parameters matches the vector subtraction vm_magnitude vm_vec_normalized_dir_quick(vms_vector &dest,const vms_vector &end,const vms_vector &start) { return vm_vec_normalize_quick(vm_vec_sub(dest,end,start)); } //return the normalized direction vector between two points //dest = normalized(end - start). Returns mag of direction vector //NOTE: the order of the parameters matches the vector subtraction vm_magnitude vm_vec_normalized_dir(vms_vector &dest,const vms_vector &end,const vms_vector &start) { return vm_vec_normalize(vm_vec_sub(dest,end,start)); } //computes surface normal from three points. result is normalized //returns ptr to dest //dest CANNOT equal either source void vm_vec_normal(vms_vector &dest,const vms_vector &p0,const vms_vector &p1,const vms_vector &p2) { vm_vec_perp(dest,p0,p1,p2); vm_vec_normalize(dest); } //make sure a vector is reasonably sized to go into a cross product static void check_vec(vms_vector *v) { fix check; int cnt = 0; check = labs(v->x) | labs(v->y) | labs(v->z); if (check == 0) return; if (check & 0xfffc0000) { //too big while (check & 0xfff00000) { cnt += 4; check >>= 4; } while (check & 0xfffc0000) { cnt += 2; check >>= 2; } v->x >>= cnt; v->y >>= cnt; v->z >>= cnt; } else //maybe too small if ((check & 0xffff8000) == 0) { //yep, too small while ((check & 0xfffff000) == 0) { cnt += 4; check <<= 4; } while ((check & 0xffff8000) == 0) { cnt += 2; check <<= 2; } v->x >>= cnt; v->y >>= cnt; v->z >>= cnt; } } //computes cross product of two vectors. //Note: this magnitude of the resultant vector is the //product of the magnitudes of the two source vectors. This means it is //quite easy for this routine to overflow and underflow. Be careful that //your inputs are ok. void vm_vec_cross(vms_vector &dest,const vms_vector &src0,const vms_vector &src1) { quadint q; Assert(&dest!=&src0 && &dest!=&src1); q.q = 0; fixmulaccum(&q,src0.y,src1.z); fixmulaccum(&q,-src0.z,src1.y); dest.x = fixquadadjust(&q); q.q = 0; fixmulaccum(&q,src0.z,src1.x); fixmulaccum(&q,-src0.x,src1.z); dest.y = fixquadadjust(&q); q.q = 0; fixmulaccum(&q,src0.x,src1.y); fixmulaccum(&q,-src0.y,src1.x); dest.z = fixquadadjust(&q); } //computes non-normalized surface normal from three points. //returns ptr to dest //dest CANNOT equal either source void vm_vec_perp(vms_vector &dest,const vms_vector &p0,const vms_vector &p1,const vms_vector &p2) { auto t0 = vm_vec_sub(p1,p0); auto t1 = vm_vec_sub(p2,p1); check_vec(&t0); check_vec(&t1); vm_vec_cross(dest,t0,t1); } //computes the delta angle between two vectors. //vectors need not be normalized. if they are, call vm_vec_delta_ang_norm() //the forward vector (third parameter) can be NULL, in which case the absolute //value of the angle in returned. Otherwise the angle around that vector is //returned. fixang vm_vec_delta_ang(const vms_vector &v0,const vms_vector &v1,const vms_vector &fvec) { vms_vector t0,t1; if (!vm_vec_copy_normalize(t0,v0) || !vm_vec_copy_normalize(t1,v1)) return 0; return vm_vec_delta_ang_norm(t0,t1,fvec); } //computes the delta angle between two normalized vectors. fixang vm_vec_delta_ang_norm(const vms_vector &v0,const vms_vector &v1,const vms_vector &fvec) { fixang a; a = fix_acos(vm_vec_dot(v0,v1)); if (vm_vec_dot(vm_vec_cross(v0,v1),fvec) < 0) a = -a; return a; } static void sincos_2_matrix(vms_matrix &m, const fixang bank, const fix_sincos_result p, const fix_sincos_result h) { #define DXX_S2M_DECL(V) \ const auto &sin##V = V.sin; \ const auto &cos##V = V.cos DXX_S2M_DECL(p); DXX_S2M_DECL(h); m.fvec.y = -sinp; //m6 m.fvec.x = fixmul(sinh,cosp); //m3 m.fvec.z = fixmul(cosh,cosp); //m9 const auto &&b = fix_sincos(bank); DXX_S2M_DECL(b); #undef DXX_S2M_DECL m.rvec.y = fixmul(sinb,cosp); //m4 m.uvec.y = fixmul(cosb,cosp); //m5 const auto cbch = fixmul(cosb,cosh); const auto sbsh = fixmul(sinb,sinh); m.rvec.x = cbch + fixmul(sinp,sbsh); //m1 m.uvec.z = sbsh + fixmul(sinp,cbch); //m8 const auto sbch = fixmul(sinb,cosh); const auto cbsh = fixmul(cosb,sinh); m.uvec.x = fixmul(sinp,cbsh) - sbch; //m2 m.rvec.z = fixmul(sinp,sbch) - cbsh; //m7 } //computes a matrix from a set of three angles. returns ptr to matrix void vm_angles_2_matrix(vms_matrix &m,const vms_angvec &a) { const auto al = a; sincos_2_matrix(m, al.b, fix_sincos(al.p), fix_sincos(al.h)); } #if DXX_USE_EDITOR //computes a matrix from a forward vector and an angle void vm_vec_ang_2_matrix(vms_matrix &m,const vms_vector &v,fixang a) { fix sinp,cosp,sinh,cosh; sinp = -v.y; cosp = fix_sqrt(f1_0 - fixmul(sinp,sinp)); sinh = fixdiv(v.x,cosp); cosh = fixdiv(v.z,cosp); sincos_2_matrix(m, a, {sinp, cosp}, {sinh, cosh}); } #endif //computes a matrix from one or more vectors. The forward vector is required, //with the other two being optional. If both up & right vectors are passed, //the up vector is used. If only the forward vector is passed, a bank of //zero is assumed //returns ptr to matrix void vm_vector_2_matrix(vms_matrix &m,const vms_vector &fvec,const vms_vector *uvec,const vms_vector *rvec) { vms_vector &xvec=m.rvec,&yvec=m.uvec,&zvec=m.fvec; if (!vm_vec_copy_normalize(zvec,fvec)) { Int3(); //forward vec should not be zero-length return; } if (uvec == NULL) { if (rvec == NULL) { //just forward vec bad_vector2: ; if (zvec.x==0 && zvec.z==0) { //forward vec is straight up or down m.rvec.x = f1_0; m.uvec.z = (zvec.y < 0)?f1_0:-f1_0; m.rvec.y = m.rvec.z = m.uvec.x = m.uvec.y = 0; } else { //not straight up or down xvec.x = zvec.z; xvec.y = 0; xvec.z = -zvec.x; vm_vec_normalize(xvec); vm_vec_cross(yvec,zvec,xvec); } } else { //use right vec if (!vm_vec_copy_normalize(xvec,*rvec)) goto bad_vector2; vm_vec_cross(yvec,zvec,xvec); //normalize new perpendicular vector if (!vm_vec_normalize(yvec)) goto bad_vector2; //now recompute right vector, in case it wasn't entirely perpendiclar vm_vec_cross(xvec,yvec,zvec); } } else { //use up vec if (!vm_vec_copy_normalize(yvec,*uvec)) goto bad_vector2; vm_vec_cross(xvec,yvec,zvec); //normalize new perpendicular vector if (!vm_vec_normalize(xvec)) goto bad_vector2; //now recompute up vector, in case it wasn't entirely perpendiclar vm_vec_cross(yvec,zvec,xvec); } } //rotates a vector through a matrix. returns ptr to dest vector //dest CANNOT equal source void vm_vec_rotate(vms_vector &dest,const vms_vector &src,const vms_matrix &m) { dest.x = vm_vec_dot(src,m.rvec); dest.y = vm_vec_dot(src,m.uvec); dest.z = vm_vec_dot(src,m.fvec); } //mulitply 2 matrices, fill in dest. returns ptr to dest //dest CANNOT equal either source void _vm_matrix_x_matrix(vms_matrix &dest,const vms_matrix &src0,const vms_matrix &src1) { dest.rvec.x = vm_vec_dot3(src0.rvec.x,src0.uvec.x,src0.fvec.x, src1.rvec); dest.uvec.x = vm_vec_dot3(src0.rvec.x,src0.uvec.x,src0.fvec.x, src1.uvec); dest.fvec.x = vm_vec_dot3(src0.rvec.x,src0.uvec.x,src0.fvec.x, src1.fvec); dest.rvec.y = vm_vec_dot3(src0.rvec.y,src0.uvec.y,src0.fvec.y, src1.rvec); dest.uvec.y = vm_vec_dot3(src0.rvec.y,src0.uvec.y,src0.fvec.y, src1.uvec); dest.fvec.y = vm_vec_dot3(src0.rvec.y,src0.uvec.y,src0.fvec.y, src1.fvec); dest.rvec.z = vm_vec_dot3(src0.rvec.z,src0.uvec.z,src0.fvec.z, src1.rvec); dest.uvec.z = vm_vec_dot3(src0.rvec.z,src0.uvec.z,src0.fvec.z, src1.uvec); dest.fvec.z = vm_vec_dot3(src0.rvec.z,src0.uvec.z,src0.fvec.z, src1.fvec); } //extract angles from a matrix void vm_extract_angles_matrix(vms_angvec &a,const vms_matrix &m) { fix cosp; if (m.fvec.x==0 && m.fvec.z==0) //zero head a.h = 0; else a.h = fix_atan2(m.fvec.z,m.fvec.x); const auto &&ah = fix_sincos(a.h); const auto &sinh = ah.sin; const auto &cosh = ah.cos; if (abs(sinh) > abs(cosh)) //sine is larger, so use it cosp = fixdiv(m.fvec.x,sinh); else //cosine is larger, so use it cosp = fixdiv(m.fvec.z,cosh); if (cosp==0 && m.fvec.y==0) a.p = 0; else a.p = fix_atan2(cosp,-m.fvec.y); if (cosp == 0) //the cosine of pitch is zero. we're pitched straight up. say no bank a.b = 0; else { fix sinb,cosb; sinb = fixdiv(m.rvec.y,cosp); cosb = fixdiv(m.uvec.y,cosp); if (sinb==0 && cosb==0) a.b = 0; else a.b = fix_atan2(cosb,sinb); } } //extract heading and pitch from a vector, assuming bank==0 static vms_angvec &vm_extract_angles_vector_normalized(vms_angvec &a,const vms_vector &v) { a.b = 0; //always zero bank a.p = fix_asin(-v.y); if (v.x == 0 && v.z == 0) a.h = 0; else a.h = fix_atan2(v.z,v.x); return a; } //extract heading and pitch from a vector, assuming bank==0 void vm_extract_angles_vector(vms_angvec &a,const vms_vector &v) { vms_vector t; if (vm_vec_copy_normalize(t,v)) vm_extract_angles_vector_normalized(a,t); else a = {}; } //compute the distance from a point to a plane. takes the normalized normal //of the plane (ebx), a point on the plane (edi), and the point to check (esi). //returns distance in eax //distance is signed, so negative dist is on the back of the plane fix vm_dist_to_plane(const vms_vector &checkp,const vms_vector &norm,const vms_vector &planep) { return vm_vec_dot(vm_vec_sub(checkp,planep),norm); } // convert vms_matrix to vms_quaternion void vms_quaternion_from_matrix(vms_quaternion &rq, const vms_matrix &m) { const auto rx = m.rvec.x; const auto ry = m.rvec.y; const auto rz = m.rvec.z; const auto ux = m.uvec.x; const auto uy = m.uvec.y; const auto uz = m.uvec.z; const auto fx = m.fvec.x; const auto fy = m.fvec.y; const auto fz = m.fvec.z; const fix tr = rx + uy + fz; fix qw, qx, qy, qz; if (tr > 0) { fix s = fixmul(fix_sqrt(tr + fl2f(1.0)), fl2f(2.0)); qw = fixmul(fl2f(0.25), s); qx = fixdiv(fy - uz, s); qy = fixdiv(rz - fx, s); qz = fixdiv(ux - ry, s); } else if ((rx > uy) & (rx > fz)) { fix s = fixmul(fix_sqrt(fl2f(1.0) + rx - uy - fz), fl2f(2.0)); qw = fixdiv(fy - uz, s); qx = fixmul(fl2f(0.25), s); qy = fixdiv(ry + ux, s); qz = fixdiv(rz + fx, s); } else if (uy > fz) { fix s = fixmul(fix_sqrt(fl2f(1.0) + uy - rx - fz), fl2f(2.0)); qw = fixdiv(rz - fx, s); qx = fixdiv(ry + ux, s); qy = fixmul(fl2f(0.25), s); qz = fixdiv(uz + fy, s); } else { fix s = fixmul(fix_sqrt(fl2f(1.0) + fz - rx - uy), fl2f(2.0)); qw = fixdiv(ux - ry, s); qx = fixdiv(rz + fx, s); qy = fixdiv(uz + fy, s); qz = fixmul(fl2f(0.25), s); } rq.w = qw / 2; rq.x = qx / 2; rq.y = qy / 2; rq.z = qz / 2; } // convert vms_quaternion to vms_matrix void vms_matrix_from_quaternion(vms_matrix &m, const vms_quaternion &q) { const fix qw2 = q.w * 2; const fix qx2 = q.x * 2; const fix qy2 = q.y * 2; const fix qz2 = q.z * 2; const fix sqw = fixmul(qw2, qw2); const fix sqx = fixmul(qx2, qx2); const fix sqy = fixmul(qy2, qy2); const fix sqz = fixmul(qz2, qz2); const fix invs = fixdiv(fl2f(1.0), (sqw + sqx + sqy + sqz)); m.rvec.x = fixmul(sqx - sqy - sqz + sqw, invs); m.uvec.y = fixmul(-sqx + sqy - sqz + sqw, invs); m.fvec.z = fixmul(-sqx - sqy + sqz + sqw, invs); const fix qxy = fixmul(qx2, qy2); const fix qzw = fixmul(qz2, qw2); m.uvec.x = fixmul(fixmul(fl2f(2.0), (qxy + qzw)), invs); m.rvec.y = fixmul(fixmul(fl2f(2.0), (qxy - qzw)), invs); const fix qxz = fixmul(qx2, qz2); const fix qyw = fixmul(qy2, qw2); m.fvec.x = fixmul(fixmul(fl2f(2.0), (qxz - qyw)), invs); m.rvec.z = fixmul(fixmul(fl2f(2.0), (qxz + qyw)), invs); const fix qyz = fixmul(qy2, qz2); const fix qxw = fixmul(qx2, qw2); m.fvec.y = fixmul(fixmul(fl2f(2.0), (qyz + qxw)), invs); m.uvec.z = fixmul(fixmul(fl2f(2.0), (qyz - qxw)), invs); } }