Tiny Fast Math (tinyfm) is a small C++17 math library for real-time graphics,
simulations, and games. It is distributed as a single public header,
src/tinyfm.h, and exposed by CMake as the tinyfm
INTERFACE target. You can integrate it with CMake or simply download
tinyfm.h and place it in your project's include path if you only need the
library header.
The API lives in the tfm namespace and includes:
- integer and floating-point vectors:
vec2i,vec3i,vec4i,vec2f,vec3f,vec4f - quaternions:
quatf - matrices:
mat3f,mat4f - common operations such as
dot,cross,length,normalize,distance,project,reflect,lerp,mix,min,max,clamp,transpose,inverse, and transformation helpers - camera and projection helpers, including perspective, orthographic, frustum, look-at, and look-to matrix builders
- optional code paths that use intrinsics when
TFM_FORCE_INTRINSICSis defined on supported SSE/AVX or NEON platforms
- C++17-compatible compiler
- CMake 3.14 or newer
- Git, when cloning the repository or using CMake
FetchContent
The library target itself is header-only. Tests, benchmarks, and graphics examples have additional dependencies that are downloaded or detected only when those components are enabled.
src/tinyfm.h Public single-header library
src/test/ GoogleTest-based scalar and SIMD tests
src/benchmark/ Google Benchmark comparisons
src/examples/ Cube transformation examples for graphics APIs
3rdparty/ CMake integration for optional dependencies
CMakeLists.txt Root CMake project
git clone https://github.com/arabasso/tinyfm.git
cd tinyfmNo Git submodules are required. Optional dependencies are handled by CMake
FetchContent when the corresponding project options are enabled.
#include <tinyfm.h>
int main() {
tfm::vec3f position(1.0f, 2.0f, 3.0f);
tfm::vec3f up = tfm::vec3f::unit_y();
float height = tfm::dot(position, up);
tfm::mat4f model = tfm::mat4f::identity()
.translate(0.0f, 1.0f, 0.0f)
.rotate_y(tfm::radians(45.0f));
}The public header includes all standard library headers required by its API, so it can be included directly.
TinyFM follows the same composition convention used by the graphics examples in
this repository: build the model, view, and projection matrices separately,
then compose them as model * view * projection.
Perspective projection with right-handed coordinates and a zero-to-one clip depth range:
#include <tinyfm.h>
tfm::mat4f make_perspective_mvp(float aspect_ratio) {
const tfm::mat4f model = tfm::mat4f::identity()
.translate(0.0f, 0.0f, 0.0f)
.rotate_y(tfm::radians(45.0f));
const tfm::mat4f view = tfm::mat4f::look_at_rh(
tfm::vec3f(0.0f, 0.0f, 5.0f),
tfm::vec3f(0.0f, 0.0f, 0.0f),
tfm::vec3f::unit_y());
const tfm::mat4f projection =
tfm::mat4f::perspective_fov_rh_zo(tfm::radians(60.0f), aspect_ratio, 0.1f, 100.0f);
return model * view * projection;
}Orthographic projection with right-handed coordinates and a negative-one-to-one clip depth range:
#include <tinyfm.h>
tfm::mat4f make_orthographic_mvp(float width, float height) {
const tfm::mat4f model = tfm::mat4f::identity()
.translate(0.0f, 0.0f, 0.0f);
const tfm::mat4f view = tfm::mat4f::look_at_rh(
tfm::vec3f(0.0f, 0.0f, 5.0f),
tfm::vec3f(0.0f, 0.0f, 0.0f),
tfm::vec3f::unit_y());
const tfm::mat4f projection =
tfm::mat4f::ortho_rh_no(width, height, 0.1f, 100.0f);
return model * view * projection;
}For interactive scenes, a small free-look camera can own the view and projection state and expose a reusable view-projection matrix:
#include <tinyfm.h>
class freelook_camera {
public:
tfm::vec3f position;
tfm::vec3f front = tfm::vec3f(0.0f, 0.0f, 1.0f);
tfm::vec3f up{};
tfm::vec3f right{};
tfm::vec3f world_up = tfm::vec3f(0.0f, 1.0f, 0.0f);
float yaw;
float pitch;
float movement_speed = 2.5f;
float mouse_sensitivity = 0.05f;
float zoom = 45.0f;
float fov;
float znear;
float zfar;
float zclip_range;
float aspect_ratio;
freelook_camera(float x, float y, float z, float pitch = 0.0f, float yaw = -90.0f, float movement_speed = 2.5f, float mouse_sensitivity = 0.05f, float fov = 45.0f, float znear = 0.1f, float zfar = 100.0f, float aspect_ratio = 16.0f / 9.0f)
: position(tfm::vec3f(x, y, z)), pitch(pitch), yaw(yaw), movement_speed(movement_speed), mouse_sensitivity(mouse_sensitivity), fov(fov), znear(znear), zfar(zfar), aspect_ratio(aspect_ratio), zclip_range(zfar - znear) {
update_vectors();
}
void move_dxy(int dx, int dy, float max_pitch = 89.0f) {
yaw += dx * mouse_sensitivity;
pitch -= dy * mouse_sensitivity;
if (pitch > max_pitch) pitch = max_pitch;
if (pitch < -max_pitch) pitch = -max_pitch;
update_vectors();
}
void move_front(float frame_time) { position += front * (frame_time * movement_speed); }
void move_back(float frame_time) { position -= front * (frame_time * movement_speed); }
void move_left(float frame_time) { position -= right * (frame_time * movement_speed); }
void move_right(float frame_time) { position += right * (frame_time * movement_speed); }
tfm::mat4f view_matrix() const {
return tfm::mat4f::look_at_rh(position, position + front, up);
}
tfm::mat4f projection_matrix() const {
return tfm::mat4f::perspective_fov_rh_zo(tfm::radians(fov), aspect_ratio, znear, zfar);
}
tfm::mat4f view_projection_matrix() const {
return projection_matrix() * view_matrix();
}
private:
void update_vectors() {
const tfm::vec3f direction(
std::cos(tfm::radians(yaw)) * std::cos(tfm::radians(pitch)),
std::sin(tfm::radians(pitch)),
std::sin(tfm::radians(yaw)) * std::cos(tfm::radians(pitch)));
front = direction.normalize();
right = tfm::cross(front, world_up).normalize();
up = tfm::cross(right, front).normalize();
}
};
tfm::mat4f make_camera_mvp(float aspect_ratio, float dt, int mouse_dx, int mouse_dy) {
freelook_camera camera(0.0f, 0.0f, 5.0f, 0.0f, -90.0f, 2.5f, 0.05f, 45.0f, 0.1f, 100.0f, aspect_ratio);
camera.move_dxy(mouse_dx, mouse_dy);
camera.move_front(dt);
const tfm::mat4f model = tfm::mat4f::identity()
.rotate_y(tfm::radians(45.0f));
return model * camera.view_projection_matrix();
}The suffixes describe both the handedness and the normalized depth range. Use
rh_zo for right-handed projections that target zero-to-one clip depth, which
is the usual convention for Direct3D and Vulkan-style pipelines. Use rh_no
for right-handed projections that target negative-one-to-one clip depth, which
is OpenGL's default convention. If a backend, shader, viewport transform, or
extension changes the expected clip-space depth range, choose the matching
projection variant; mixing zo and no typically results in incorrect depth
testing, clipping, or object placement along the Z-axis.
When TinyFM is fetched as a dependency, tests, benchmarks, and examples are
disabled by default. No explicit cache overrides are needed; fetch the
repository and link against the tinyfm target.
cmake_minimum_required(VERSION 3.14)
project(my_app LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
tinyfm
GIT_REPOSITORY https://github.com/arabasso/tinyfm.git
GIT_TAG master
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(tinyfm)
add_executable(my_app src/main.cpp)
target_link_libraries(my_app PRIVATE tinyfm)
# Optional: compile the intrinsic code paths guarded by tinyfm.h.
# target_compile_definitions(my_app PRIVATE TFM_FORCE_INTRINSICS)The target name is tinyfm, not tinyfm::tinyfm. The target adds the public
include directory and CPU-specific compiler options through CMake INTERFACE
properties.
| Option | Default | Effect |
|---|---|---|
ENABLE_TESTS |
ON for the root project; OFF as a dependency |
Fetches GoogleTest, builds tinyfm_test_scalar and tinyfm_test_simd, and enables CTest. |
ENABLE_BENCHMARKS |
ON for the root project; OFF as a dependency |
Fetches Google Benchmark, RTM, GLM, Eigen, and SimpleMath on Windows, then builds tinyfm_benchmark. |
ENABLE_EXAMPLES |
ON for the root project; OFF as a dependency |
Builds available graphics examples and fetches their dependencies such as fmt, tinywm, GLAD, vk-bootstrap, DirectX-Headers, or Metal C++ support. |
To configure the repository directly with the root project's defaults:
cmake -S . -B build
cmake --build build --config ReleaseWhen TinyFM is added through FetchContent, its dependency defaults are
selected automatically; the three -D...=OFF options are not necessary in the
consumer project.
Because tinyfm is header-only, a library-only build mainly validates the CMake
configuration and creates the INTERFACE target. It may not produce a standalone
library file when tests, benchmarks, and examples are disabled.
Install:
- Git
- CMake
- Visual Studio 2022 or Visual Studio Build Tools with the Desktop development with C++ workload
- Windows SDK
From a Developer PowerShell or Developer Command Prompt:
git clone https://github.com/arabasso/tinyfm.git
cd tinyfm
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config ReleaseGraphics examples are enabled by default when building the repository directly.
CMake enables Direct3D 11, Direct3D 12, OpenGL, and Vulkan examples only when
the matching SDKs or headers are available. Example targets include
cube_transform_d3d11, cube_transform_d3d12, cube_transform_gl4, and
cube_transform_vk1.
Install a C++17 compiler, CMake, and Git. Ninja is optional but recommended.
git clone https://github.com/arabasso/tinyfm.git
cd tinyfm
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallelGraphics examples are enabled by default when building the repository directly. Install the platform packages needed by the APIs you want to build. OpenGL examples require OpenGL development packages, and Vulkan examples require a Vulkan SDK or development package discoverable by CMake.
Install Xcode command line tools and CMake:
xcode-select --installThen build with the Xcode generator:
git clone https://github.com/arabasso/tinyfm.git
cd tinyfm
cmake -S . -B build -G Xcode
cmake --build build --config ReleaseYou can also use Ninja or Unix Makefiles:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallelWhen examples are enabled on macOS, CMake can build Metal examples and any
OpenGL or Vulkan examples whose dependencies are available. Example targets
include cube_transform_mtl, cube_transform_gl4, and cube_transform_vk1.
Tests and benchmarks are optional. They are enabled by default when building this repository directly and disabled by default when TinyFM is a dependency.
To build and run tests without benchmarks or examples:
cmake -S . -B build-tests -DENABLE_TESTS=ON -DENABLE_BENCHMARKS=OFF -DENABLE_EXAMPLES=OFF
cmake --build build-tests --config Release
ctest --test-dir build-tests -C Release --output-on-failureTo build benchmarks without tests or examples:
cmake -S . -B build-bench -DENABLE_TESTS=OFF -DENABLE_BENCHMARKS=ON -DENABLE_EXAMPLES=OFF
cmake --build build-bench --config Release --target tinyfm_benchmarkThe CMake target selects CPU-specific compiler options based on the detected processor:
- x86/x64: AVX2, FMA, and fast floating-point options where supported
- ARM64: NEON-compatible fast floating-point options
- other processors: no explicit SIMD option
The header compiles the intrinsic implementations only when
TFM_FORCE_INTRINSICS is defined. Without that definition, the scalar
implementation is used.
Tiny Fast Math is released under the MIT License. See LICENSE.