A small, header-only SIMD vector math library with one portable API across x86 (SSE2/SSSE3/SSE4.1) and ARM (NEON / AArch64). Write your math once; it compiles to good vector code on desktop, consoles, and mobile.
vecmath is the math core of the Dagor Engine and powers its transforms, physics, culling, and animation. This repository is the standalone, dependency-free version of those headers.
- One API, many CPUs. You call
v_add,v_mat44_mul,v_norm3. The header selects the SSE or NEON implementation for whatever you build for. No#ifdefsoup in your own code. - Header-only, no dependencies. Add the include path and go. Nothing to build or link.
- Zero-overhead. Types are the native SIMD registers (
__m128/float32x4_t), passed in registers. Almost everything is force-inlined, so unused results melt away and there is no wrapper-object cost. - Batteries included. Vectors, 3x3 / 4x3 / 4x4 matrices, quaternions, planes, bounding boxes and spheres, frustum culling, ray/triangle intersection, and fast trig/exp approximations.
- C++11 or later.
- An x86 target with at least SSE2, or an AArch64 (ARMv8) target with NEON.
- MSVC, Clang, or GCC.
The target ISA is auto-detected from the usual compiler macros (__SSE4_1__,
__ARM_NEON, ...). To pin it explicitly, define one of these before including:
#define _TARGET_SIMD_SSE 4 // 2 = SSE2, 3 = SSSE3, 4 = SSE4.1
// or
#define _TARGET_SIMD_NEON 1This is a header-only library. Copy the headers somewhere on your include path
(a vecmath/ subfolder is the convention) and include the API:
#include <vecmath/dag_vecMath.h> // full API
#include <vecmath/dag_vecMathDecl.h> // types only (forward declarations)With CMake you can expose it as an interface target:
add_library(vecmath INTERFACE)
target_include_directories(vecmath INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
# consumers: target_link_libraries(my_app PRIVATE vecmath)| Type | Meaning |
|---|---|
vec4f / vec3f |
128-bit float vector. vec3f is a vec4f whose .w lane is ignored. |
vec4i |
128-bit integer vector (four 32-bit ints) |
mat33f |
3x3 matrix, column-major (col0..col2) |
mat44f |
4x4 matrix, column-major (col0..col3) |
mat43f |
4x3 matrix, row-major (row0..row2); each row's .w is the translation |
quat4f |
quaternion (stored as a vec4f) |
plane3f |
plane: .xyz = normal, .w = D |
bbox3f |
axis-aligned bounding box {bmin, bmax} |
bsph3f |
bounding sphere: .xyz = center, .w = radius |
Build the frame's world-to-clip matrix, then collect the visible objects and their LOD in one sweep:
void cullScene(vec3f camPos, vec3f camDir, vec3f camUp, float wk, float hk, float zn, float zf,
const SceneObj *objs, int count, VisibleObj *out, int &outCount)
{
mat44f view, proj, globtm;
v_mat44_make_look_at(view, camPos, v_add(camPos, camDir), camUp);
v_mat44_make_persp_reverse(proj, wk, hk, zn, zf); // reverse-Z
v_mat44_mul(globtm, proj, view);
outCount = 0;
for (int i = 0; i < count; i++)
{
const bbox3f &b = objs[i].worldBox;
if (v_test_vec_x_eqi_0(v_is_visible(b.bmin, b.bmax, globtm)))
continue; // fully outside the frustum
float d2 = v_extract_x(v_length3_sq_x(v_sub(v_bbox3_center(b), camPos)));
out[outCount++] = { i, d2 > objs[i].lodSwitchDistSq }; // pick LOD from squared distance
}
}Distance falloff plus an occlusion raycast against world geometry:
void applyBlast(vec3f center, float radius, float maxDmg,
Actor *actors, int actorCount, const bbox3f *world, int worldCount)
{
for (int a = 0; a < actorCount; a++)
{
vec3f to = v_sub(actors[a].pos, center);
float dist = v_extract_x(v_length3_x(to));
if (dist >= radius)
continue; // out of range
vec3f dir = v_norm3(to);
bool blocked = false;
for (int w = 0; w < worldCount && !blocked; w++)
{
vec3f t;
blocked = v_ray_box_intersection(center, dir, t, world[w]) && v_extract_x(t) < dist;
}
if (!blocked)
actors[a].hp -= maxDmg * (1.f - dist / radius); // linear falloff
}
}Shortest-arc aiming slerped by this frame's turn rate, composed straight into a world matrix:
void updateTurret(Turret &t, vec3f target, float turnFrac)
{
vec3f facing = v_quat_mul_vec3(t.rot, t.restDir); // current muzzle direction
vec3f wanted = v_norm3(v_sub(target, t.pos));
quat4f goal = v_quat_mul_quat(v_quat_from_unit_arc(facing, wanted), t.rot);
t.rot = v_quat_slerp(v_splats(turnFrac), t.rot, goal);
v_mat44_compose(t.tm, t.pos, t.rot, V_C_ONE); // pos + rotation, unit scale
}_x- result kept in the.xlane only; the other lanes may hold anything. Cheaper than the all-lanes (broadcast) form._est- fast hardware estimate refined toward useful precision._unprecise- raw hardware estimate; fastest, least precise._safe- tolerates edge inputs such as zero divisors.v_ld/v_stare aligned;v_ldu/v_stuare unaligned; a trailingiis the integer variant.v_cast_*reinterprets the bits (free);v_cvt_*converts the value (int <-> float).
| File | Contents |
|---|---|
dag_vecMathDecl.h |
Type declarations, platform detection, alignment macros |
dag_vecMath.h |
Full API declarations with comments |
dag_vecMath_const.h |
Named constants (V_C_HALF, V_C_ONE, V_C_PI, lane masks, ...) |
dag_vecMath_pc_sse.h |
SSE implementation |
dag_vecMath_neon.h |
NEON (AArch64) implementation |
dag_vecMath_common.h |
Portable implementations built on the core intrinsics |
dag_vecMath_trig.h |
Polynomial sin / cos / tan / atan / asin / acos approximations |
BSD-3-Clause. See LICENSE. Copyright (C) Gaijin Games KFT.