okinawaengine GitHub

Math

The math types wrap GLM behind a small, explicit API. OkPoint is a 3D point or vector, OkRotation holds Euler angles (and the matrix they produce), and OkMath is a static helper for direction/angle conversions and look-at.

Coordinate system

Okinawa uses a right-handed coordinate system: X points right, Y points up, Z points towards the viewer (out of the screen). The default camera sits at the origin looking down negative Z, with up along positive Y. Rotations are Euler angles in radians: pitch (X), yaw (Y), roll (Z), with pitch clamped to avoid gimbal lock. See src/okinawa/math/readme.md in the engine for the full conventions.

OkPoint methods

Method Purpose
OkPoint(float x, float y, float z) Construct from components.
float x() / y() / z() const Component getters.
float magnitude() const Vector length.
OkPoint normalize() const Unit vector.
float distance(const OkPoint &other) const Distance to another point.
float dot(const OkPoint &other) const Dot product.
OkPoint cross(const OkPoint &other) const Cross product.
static OkPoint Forward() / Right() / Up() Basis vectors.

OkPoint also supports +, -, * (scalar), and the compound assignment operators.

OkRotation methods

Method Purpose
OkRotation(float pitch, float yaw, float roll) Construct from Euler angles (radians).
void setRotation(float x, float y, float z) Replace the angles.
void rotate(float dx, float dy, float dz) Apply a delta rotation.
OkPoint getForwardVector() const Forward direction.
OkPoint getRightVector() const Right direction.
OkPoint getUpVector() const Up direction.
OkPoint transformPoint(const OkPoint &p) const Rotate a point.

OkMath methods

Method Purpose
static void directionVectorToAngles(const OkPoint &dir, float &outPitch, float &outYaw) Decompose a direction into pitch/yaw.
static OkRotation lookAt(const OkPoint &eye, const OkPoint &target, const OkPoint &up = OkPoint(0,1,0)) Build a rotation that looks from eye to target.

OkRay

A half-line through the world — an origin and a direction — with the intersection tests worth having against it. It is what a cursor becomes once OkCamera::rayThroughPixel has turned a pixel into a direction, and what OkItem::intersectRay is asked with.

Method Purpose
OkRay(const OkPoint &origin, const OkPoint &direction) Construct. The direction need not be a unit vector; see below.
OkPoint pointAt(float distance) const The point that far along the ray.
bool intersectsBox(const OkPoint &low, const OkPoint &high, float *outDistance) const Axis-aligned box, by the slab method. 0 when the ray starts inside.
bool intersectsSphere(const OkPoint &centre, float radius, float *outDistance) const Sphere. 0 when the ray starts inside.
bool intersectsTriangle(const OkPoint &a, const OkPoint &b, const OkPoint &c, float *outDistance) const Triangle, by Möller–Trumbore, counting both faces.
OkRay transformed(const glm::mat4 &matrix) const The same ray seen from another space.

Distances are measured in units of the direction

Every distance a ray reports is in units of its own direction, not in world units. With a unit direction the two are the same and nobody has to think about it, which is the ordinary case — rayThroughPixel returns one.

The distinction is there for transformed. Multiplying a ray by the inverse of an object's model matrix puts it in that object's local space, where the object's vertices already are, so a mesh test transforms one ray instead of every vertex. Under a scaling the transformed direction is no longer a unit vector, and that is deliberate: the scaling lives in the direction's length, which is what makes a hit found in local space come back with a distance measured in world units. Normalizing after the transform throws that conversion away and reports the wrong distance for any scaling but 1.

What it does not decide

OkRay holds no policy about what may be hit. Which objects are worth testing, whether a hidden one counts, and which of several hits wins are questions for whoever is asking — and applications answer them differently: an editor's cursor wants the nearest selectable thing, a projectile wants the first solid one, a line-of-sight check only wants to know whether anything is in the way. An engine that answered any of them here would be answering it for all of them.

OkFrustum

The view frustum as six planes, extracted from a combined projection * view matrix (Gribb-Hartmann), used for bounding-sphere culling. OkCore builds one per frame from the current camera and activates it for the world pass: OkItem::drawSelf skips any item whose bounding sphere (bbox centre + half-diagonal radius, transformed by the item's matrix) falls fully outside — in a dense scene over half the scene's items are skipped every frame. The GUI and camera-attached passes run with no active frustum (their calibrated cameras are not the world camera), and the skybox dome is camera-centred so it always intersects. get_state (MCP) reports the per-frame skipped count as scene.frustum_culled.

Method Purpose
void setFromMatrix(const glm::mat4 &projView) Extract and normalize the six planes.
bool containsSphere(float x, float y, float z, float r) const Sphere-vs-frustum test (true = at least partially inside).
static void setActive(const OkFrustum *) / static const OkFrustum *getActive() The frame's culling frustum (null = no culling).
static void setViewer(x, y, z, maxDistance) Viewer position and draw distance for the frame.
static bool isBeyondDrawDistance(x, y, z, r) Whether a bounding sphere lies entirely out of range.
static long getCulledCount() / static void resetStats() Draws skipped since the last reset.
static long getDrawCalls() / static long getTriangles() What the frame actually submitted.

Draw distance

render.drawdistance (world units, 0 disables) skips anything whose bounding sphere lies entirely beyond it. It is a single comparison and in an open world it rejects far more than the frustum test does, so the draw path tries it first. Set it where the project's distance fog has already swallowed the world: past that point the draws change nothing on screen.

Opaque geometry is also drawn nearest first (see OkScene), so the depth buffer rejects hidden fragments early — the cheapest defence against overdraw in a scene full of occluders. The order is refreshed periodically rather than every frame, since it only has to be roughly right.

Example

OkPoint eye(0.0f, 100.0f, 200.0f);
OkPoint target(0.0f, 0.0f, 0.0f);
OkPoint direction = (target - eye).normalize();

float pitch, yaw;
OkMath::directionVectorToAngles(direction, pitch, yaw);
camera->setRotation(pitch, yaw, 0.0f);