cnq3/code/im3d/im3d.cpp
2024-07-23 18:24:44 +02:00

3366 lines
92 KiB
C++

#pragma warning(disable : 4100 4189 4456 4505)
/* CHANGE LOG
==========
2020-05-17 (v1.16) - Text API.
- Flip gizmo axes when viewed from behind (AppData::m_flipGizmoWhenBehind).
- Minor gizmo rendering improvements.
2020-01-18 (v1.15) - Added IM3D_API macro.
2018-12-09 (v1.14) - Fixed memory leak in context dtor.
2018-07-29 (v1.13) - Deprecated Draw(), instead use EndFrame() followed by GetDrawListCount() + GetDrawLists() to access draw data directly.
2018-06-07 (v1.12) - Color_ constants are constexpr (fixed issues with static init).
2018-03-20 (v1.11) - Thread-local context ptr (IM3D_THREAD_LOCAL_CONTEXT_PTR).
- MergeContexts API.
2018-01-27 (v1.10) - Added AppData::m_viewDirection (world space), fixed aligned gizmo fadeout in ortho views.
- Gizmo snapping is absolute, not relative.
2018-01-14 (v1.09) - Culling API.
- Moved DrawPrimitiveSize to im3d.cpp, renamed as VertsPerDrawPrimitive.
- Added traits + tag dispatch for math utilities (Min, Max, etc).
2017-10-21 (v1.08) - Added DrawAlignedBoxFilled(), DrawSphereFilled(), fixed clamped ranges for auto LOD in high order primitives.
2017-10-14 (v1.07) - Layers API.
2017-07-03 (v1.06) - Rotation gizmo improvements; avoid selection failure at grazing angles + improved rotation behavior.
2017-04-04 (v1.05) - GetActiveId() returns the gizmo id set by the app instead of an internal id. Added Gizmo* variants which take an Id directly.
2017-03-24 (v1.04) - DrawArrow() interface changed (world space length/pixel thickness instead of head fraction).
2017-03-01 (v1.02) - Configurable VertexData alignment (IM3D_VERTEX_ALIGNMENT).
2017-02-23 (v1.01) - Removed AppData::m_tanHalfFov, replaced with AppData::m_projScaleY. Added AppData::m_projOrtho.
*/
#include "im3d.h"
#include "im3d_math.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cfloat>
#ifndef va_copy
#define va_copy(_dst, _src) (_dst = _src)
#endif
#if defined(IM3D_MALLOC) && !defined(IM3D_FREE)
#error im3d: IM3D_MALLOC defined without IM3D_FREE; define both or neither
#endif
#if defined(IM3D_FREE) && !defined(IM3D_MALLOC)
#error im3d: IM3D_FREE defined without IM3D_MALLOC; define both or neither
#endif
#ifndef IM3D_MALLOC
#define IM3D_MALLOC(size) malloc(size)
#endif
#ifndef IM3D_FREE
#define IM3D_FREE(ptr) free(ptr)
#endif
#ifndef IM3D_CULL_PRIMITIVES
#define IM3D_CULL_PRIMITIVES 0
#endif
#ifndef IM3D_CULL_GIZMOS
#define IM3D_CULL_GIZMOS 0
#endif
// Compiler
#if defined(__GNUC__)
#define IM3D_COMPILER_GNU
#elif defined(_MSC_VER)
#define IM3D_COMPILER_MSVC
#else
#error im3d: Compiler not defined
#endif
#if defined(IM3D_COMPILER_GNU)
#define if_likely(e) if ( __builtin_expect(!!(e), 1) )
#define if_unlikely(e) if ( __builtin_expect(!!(e), 0) )
//#elif defined(IM3D_COMPILER_MSVC)
// not defined for MSVC
#else
#define if_likely(e) if(!!(e))
#define if_unlikely(e) if(!!(e))
#endif
// Internal config/debugging.
#define IM3D_RELATIVE_SNAP 0 // Snap relative to the gizmo stored position/rotation/scale (else snap is absolute).
#define IM3D_GIZMO_DEBUG 0 // Draw debug bounds for gizmo intersections.
using namespace Im3d;
constexpr Color Color_GizmoHighlight = Im3d::Color_Gold;
static const int VertsPerDrawPrimitive[DrawPrimitive_Count] =
{
3, //DrawPrimitive_Triangles,
2, //DrawPrimitive_Lines,
1 //DrawPrimitive_Points,
};
Color::Color(const Vec4& _rgba)
{
v = (U32)(_rgba.x * 255.0f) << 24;
v |= (U32)(_rgba.y * 255.0f) << 16;
v |= (U32)(_rgba.z * 255.0f) << 8;
v |= (U32)(_rgba.w * 255.0f);
}
Color::Color(const Vec3& _rgb, float _alpha)
{
v = (U32)(_rgb.x * 255.0f) << 24;
v |= (U32)(_rgb.y * 255.0f) << 16;
v |= (U32)(_rgb.z * 255.0f) << 8;
v |= (U32)(_alpha * 255.0f);
}
Color::Color(float _r, float _g, float _b, float _a)
{
v = (U32)(_r * 255.0f) << 24;
v |= (U32)(_g * 255.0f) << 16;
v |= (U32)(_b * 255.0f) << 8;
v |= (U32)(_a * 255.0f);
}
void Im3d::MulMatrix(const Mat4& _mat4)
{
Context& ctx = GetContext();
ctx.setMatrix(ctx.getMatrix() * _mat4);
}
void Im3d::Translate(float _x, float _y, float _z)
{
Context& ctx = GetContext();
ctx.setMatrix(ctx.getMatrix() * Translation(Vec3(_x, _y, _z)));
}
void Im3d::Translate(const Vec3& _vec3)
{
Context& ctx = GetContext();
ctx.setMatrix(ctx.getMatrix() * Translation(_vec3));
}
void Im3d::Rotate(const Vec3& _axis, float _angle)
{
Context& ctx = GetContext();
ctx.setMatrix(ctx.getMatrix() * Mat4(Rotation(_axis, _angle)));
}
void Im3d::Rotate(const Mat3& _rotation)
{
Context& ctx = GetContext();
ctx.setMatrix(ctx.getMatrix() * Mat4(_rotation));
}
void Im3d::Scale(float _x, float _y, float _z)
{
Context& ctx = GetContext();
ctx.setMatrix(ctx.getMatrix() * Mat4(Scale(Vec3(_x, _y, _z))));
}
void Im3d::DrawXyzAxes()
{
Context& ctx = GetContext();
ctx.pushColor(ctx.getColor());
ctx.begin(PrimitiveMode_Lines);
ctx.vertex(Vec3(0.0f, 0.0f, 0.0f), ctx.getSize(), Color_Red);
ctx.vertex(Vec3(1.0f, 0.0f, 0.0f), ctx.getSize(), Color_Red);
ctx.vertex(Vec3(0.0f, 0.0f, 0.0f), ctx.getSize(), Color_Green);
ctx.vertex(Vec3(0.0f, 1.0f, 0.0f), ctx.getSize(), Color_Green);
ctx.vertex(Vec3(0.0f, 0.0f, 0.0f), ctx.getSize(), Color_Blue);
ctx.vertex(Vec3(0.0f, 0.0f, 1.0f), ctx.getSize(), Color_Blue);
ctx.end();
ctx.popColor();
}
void Im3d::DrawPoint(const Vec3& _position, float _size, Color _color)
{
Context& ctx = GetContext();
ctx.begin(PrimitiveMode_Points);
ctx.vertex(_position, _size, _color);
ctx.end();
}
void Im3d::DrawLine(const Vec3& _a, const Vec3& _b, float _size, Color _color)
{
Context& ctx = GetContext();
ctx.begin(PrimitiveMode_Lines);
ctx.vertex(_a, _size, _color);
ctx.vertex(_b, _size, _color);
ctx.end();
}
void Im3d::DrawQuad(const Vec3& _a, const Vec3& _b, const Vec3& _c, const Vec3& _d)
{
Context& ctx = GetContext();
ctx.begin(PrimitiveMode_LineLoop);
ctx.vertex(_a);
ctx.vertex(_b);
ctx.vertex(_c);
ctx.vertex(_d);
ctx.end();
}
void Im3d::DrawQuad(const Vec3& _origin, const Vec3& _normal, const Vec2& _size)
{
Context& ctx = GetContext();
ctx.pushMatrix(ctx.getMatrix() * LookAt(_origin, _origin + _normal, ctx.getAppData().m_worldUp));
DrawQuad(
Vec3(-_size.x, _size.y, 0.0f),
Vec3( _size.x, _size.y, 0.0f),
Vec3( _size.x, -_size.y, 0.0f),
Vec3(-_size.x, -_size.y, 0.0f)
);
ctx.popMatrix();
}
void Im3d::DrawQuadFilled(const Vec3& _a, const Vec3& _b, const Vec3& _c, const Vec3& _d)
{
Context& ctx = GetContext();
ctx.begin(PrimitiveMode_Triangles);
ctx.vertex(_a);
ctx.vertex(_b);
ctx.vertex(_c);
ctx.vertex(_a);
ctx.vertex(_c);
ctx.vertex(_d);
ctx.end();
}
void Im3d::DrawQuadFilled(const Vec3& _origin, const Vec3& _normal, const Vec2& _size)
{
Context& ctx = GetContext();
ctx.pushMatrix(ctx.getMatrix() * LookAt(_origin, _origin + _normal, ctx.getAppData().m_worldUp));
DrawQuadFilled(
Vec3(-_size.x, -_size.y, 0.0f),
Vec3( _size.x, -_size.y, 0.0f),
Vec3( _size.x, _size.y, 0.0f),
Vec3(-_size.x, _size.y, 0.0f)
);
ctx.popMatrix();
}
void Im3d::DrawCircle(const Vec3& _origin, const Vec3& _normal, float _radius, int _detail)
{
Context& ctx = GetContext();
#if IM3D_CULL_PRIMITIVES
if (!ctx.isVisible(_origin, _radius))
{
return;
}
#endif
if (_detail < 0)
{
_detail = ctx.estimateLevelOfDetail(_origin, _radius, 8, 48);
}
_detail = Max(_detail, 3);
ctx.pushMatrix(ctx.getMatrix() * LookAt(_origin, _origin + _normal, ctx.getAppData().m_worldUp));
ctx.begin(PrimitiveMode_LineLoop);
for (int i = 0; i < _detail; ++i)
{
float rad = TwoPi * ((float)i / (float)_detail);
ctx.vertex(Vec3(cosf(rad) * _radius, sinf(rad) * _radius, 0.0f));
}
ctx.end();
ctx.popMatrix();
}
void Im3d::DrawCircleFilled(const Vec3& _origin, const Vec3& _normal, float _radius, int _detail)
{
Context& ctx = GetContext();
#if IM3D_CULL_PRIMITIVES
if (!ctx.isVisible(_origin, _radius))
{
return;
}
#endif
if (_detail < 0)
{
_detail = ctx.estimateLevelOfDetail(_origin, _radius, 8, 64);
}
_detail = Max(_detail, 3);
ctx.pushMatrix(ctx.getMatrix() * LookAt(_origin, _origin + _normal, ctx.getAppData().m_worldUp));
ctx.begin(PrimitiveMode_Triangles);
float cp = _radius;
float sp = 0.0f;
for (int i = 1; i <= _detail; ++i)
{
ctx.vertex(Vec3(0.0f, 0.0f, 0.0f));
ctx.vertex(Vec3(cp, sp, 0.0f));
float rad = TwoPi * ((float)i / (float)_detail);
float c = cosf(rad) * _radius;
float s = sinf(rad) * _radius;
ctx.vertex(Vec3(c, s, 0.0f));
cp = c;
sp = s;
}
ctx.end();
ctx.popMatrix();
}
void Im3d::DrawSphere(const Vec3& _origin, float _radius, int _detail)
{
Context& ctx = GetContext();
#if IM3D_CULL_PRIMITIVES
if (!ctx.isVisible(_origin, _radius))
{
return;
}
#endif
if (_detail < 0)
{
_detail = ctx.estimateLevelOfDetail(_origin, _radius, 8, 48);
}
_detail = Max(_detail, 3);
// xy circle
ctx.begin(PrimitiveMode_LineLoop);
for (int i = 0; i < _detail; ++i)
{
float rad = TwoPi * ((float)i / (float)_detail);
ctx.vertex(Vec3(cosf(rad) * _radius + _origin.x, sinf(rad) * _radius + _origin.y, 0.0f + _origin.z));
}
ctx.end();
// xz circle
ctx.begin(PrimitiveMode_LineLoop);
for (int i = 0; i < _detail; ++i)
{
float rad = TwoPi * ((float)i / (float)_detail);
ctx.vertex(Vec3(cosf(rad) * _radius + _origin.x, 0.0f + _origin.y, sinf(rad) * _radius + _origin.z));
}
ctx.end();
// yz circle
ctx.begin(PrimitiveMode_LineLoop);
for (int i = 0; i < _detail; ++i)
{
float rad = TwoPi * ((float)i / (float)_detail);
ctx.vertex(Vec3(0.0f + _origin.x, cosf(rad) * _radius + _origin.y, sinf(rad) * _radius + _origin.z));
}
ctx.end();
}
void Im3d::DrawSphereFilled(const Vec3& _origin, float _radius, int _detail)
{
Context& ctx = GetContext();
#if IM3D_CULL_PRIMITIVES
if (!ctx.isVisible(_origin, _radius))
{
return;
}
#endif
if (_detail < 0)
{
_detail = ctx.estimateLevelOfDetail(_origin, _radius, 12, 32);
}
_detail = Max(_detail, 6);
ctx.begin(PrimitiveMode_Triangles);
float yp = -_radius;
float rp = 0.0f;
for (int i = 1; i <= _detail / 2; ++i)
{
float y = ((float)i / (float)(_detail / 2)) * 2.0f - 1.0f;
float r = cosf(y * HalfPi) * _radius;
y = sinf(y * HalfPi) * _radius;
float xp = 1.0f;
float zp = 0.0f;
for (int j = 1; j <= _detail; ++j)
{
float x = ((float)j / (float)(_detail)) * TwoPi;
float z = sinf(x);
x = cosf(x);
ctx.vertex(Vec3(xp * rp + _origin.x, yp + _origin.y, zp * rp + _origin.z));
ctx.vertex(Vec3(xp * r + _origin.x, y + _origin.y, zp * r + _origin.z));
ctx.vertex(Vec3(x * r + _origin.x, y + _origin.y, z * r + _origin.z));
ctx.vertex(Vec3(xp * rp + _origin.x, yp + _origin.y, zp * rp + _origin.z));
ctx.vertex(Vec3(x * r + _origin.x, y + _origin.y, z * r + _origin.z));
ctx.vertex(Vec3(x * rp + _origin.x, yp + _origin.y, z * rp + _origin.z));
xp = x;
zp = z;
}
yp = y;
rp = r;
}
ctx.end();
}
void Im3d::DrawAlignedBox(const Vec3& _min, const Vec3& _max)
{
Context& ctx = GetContext();
#if IM3D_CULL_PRIMITIVES
if (!ctx.isVisible(_min, _max))
{
return;
}
#endif
ctx.begin(PrimitiveMode_LineLoop);
ctx.vertex(Vec3(_min.x, _min.y, _min.z));
ctx.vertex(Vec3(_max.x, _min.y, _min.z));
ctx.vertex(Vec3(_max.x, _min.y, _max.z));
ctx.vertex(Vec3(_min.x, _min.y, _max.z));
ctx.end();
ctx.begin(PrimitiveMode_LineLoop);
ctx.vertex(Vec3(_min.x, _max.y, _min.z));
ctx.vertex(Vec3(_max.x, _max.y, _min.z));
ctx.vertex(Vec3(_max.x, _max.y, _max.z));
ctx.vertex(Vec3(_min.x, _max.y, _max.z));
ctx.end();
ctx.begin(PrimitiveMode_Lines);
ctx.vertex(Vec3(_min.x, _min.y, _min.z));
ctx.vertex(Vec3(_min.x, _max.y, _min.z));
ctx.vertex(Vec3(_max.x, _min.y, _min.z));
ctx.vertex(Vec3(_max.x, _max.y, _min.z));
ctx.vertex(Vec3(_min.x, _min.y, _max.z));
ctx.vertex(Vec3(_min.x, _max.y, _max.z));
ctx.vertex(Vec3(_max.x, _min.y, _max.z));
ctx.vertex(Vec3(_max.x, _max.y, _max.z));
ctx.end();
}
void Im3d::DrawAlignedBoxFilled(const Vec3& _min, const Vec3& _max)
{
Context& ctx = GetContext();
#if IM3D_CULL_PRIMITIVES
if (!ctx.isVisible(_min, _max))
{
return;
}
#endif
ctx.pushEnableSorting(true);
// x+
DrawQuadFilled(
Vec3(_max.x, _max.y, _min.z),
Vec3(_max.x, _max.y, _max.z),
Vec3(_max.x, _min.y, _max.z),
Vec3(_max.x, _min.y, _min.z)
);
// x-
DrawQuadFilled(
Vec3(_min.x, _min.y, _min.z),
Vec3(_min.x, _min.y, _max.z),
Vec3(_min.x, _max.y, _max.z),
Vec3(_min.x, _max.y, _min.z)
);
// y+
DrawQuadFilled(
Vec3(_min.x, _max.y, _min.z),
Vec3(_min.x, _max.y, _max.z),
Vec3(_max.x, _max.y, _max.z),
Vec3(_max.x, _max.y, _min.z)
);
// y-
DrawQuadFilled(
Vec3(_max.x, _min.y, _min.z),
Vec3(_max.x, _min.y, _max.z),
Vec3(_min.x, _min.y, _max.z),
Vec3(_min.x, _min.y, _min.z)
);
// z+
DrawQuadFilled(
Vec3(_max.x, _min.y, _max.z),
Vec3(_max.x, _max.y, _max.z),
Vec3(_min.x, _max.y, _max.z),
Vec3(_min.x, _min.y, _max.z)
);
// z-
DrawQuadFilled(
Vec3(_min.x, _min.y, _min.z),
Vec3(_min.x, _max.y, _min.z),
Vec3(_max.x, _max.y, _min.z),
Vec3(_max.x, _min.y, _min.z)
);
ctx.popEnableSorting();
}
void Im3d::DrawCylinder(const Vec3& _start, const Vec3& _end, float _radius, int _detail)
{
Context& ctx = GetContext();
#if IM3D_CULL_PRIMITIVES
if (!ctx.isVisible((_start + _end) * 0.5f, Max(Length2(_start - _end), _radius)))
{
return;
}
#endif
Vec3 org = _start + (_end - _start) * 0.5f;
if (_detail < 0)
{
_detail = ctx.estimateLevelOfDetail(org, _radius, 16, 24);
}
_detail = Max(_detail, 3);
float ln = Length(_end - _start) * 0.5f;
ctx.pushMatrix(ctx.getMatrix() * LookAt(org, _end, ctx.getAppData().m_worldUp));
ctx.begin(PrimitiveMode_LineLoop);
for (int i = 0; i <= _detail; ++i)
{
float rad = TwoPi * ((float)i / (float)_detail) - HalfPi;
ctx.vertex(Vec3(0.0f, 0.0f, -ln) + Vec3(cosf(rad), sinf(rad), 0.0f) * _radius);
}
ctx.end();
ctx.begin(PrimitiveMode_LineLoop);
for (int i = 0; i <= _detail; ++i)
{
float rad = TwoPi * ((float)i / (float)_detail) - HalfPi;
ctx.vertex(Vec3(0.0f, 0.0f, ln) + Vec3(cosf(rad), sinf(rad), 0.0f) * _radius);
}
ctx.end();
ctx.begin(PrimitiveMode_Lines);
for (int i = 0; i <= 6; ++i)
{
float rad = TwoPi * ((float)i / 6.0f) - HalfPi;
ctx.vertex(Vec3(0.0f, 0.0f, -ln) + Vec3(cosf(rad), sinf(rad), 0.0f) * _radius);
ctx.vertex(Vec3(0.0f, 0.0f, ln) + Vec3(cosf(rad), sinf(rad), 0.0f) * _radius);
}
ctx.end();
ctx.popMatrix();
}
void Im3d::DrawCapsule(const Vec3& _start, const Vec3& _end, float _radius, int _detail)
{
Context& ctx = GetContext();
#if IM3D_CULL_PRIMITIVES
if (!ctx.isVisible((_start + _end) * 0.5f, Max(Length2(_start - _end), _radius)))
{
return;
}
#endif
Vec3 org = _start + (_end - _start) * 0.5f;
if (_detail < 0)
{
_detail = ctx.estimateLevelOfDetail(org, _radius, 6, 24);
}
_detail = Max(_detail, 3);
float ln = Length(_end - _start) * 0.5f;
int detail2 = _detail * 2; // force cap base detail to match ends
ctx.pushMatrix(ctx.getMatrix() * LookAt(org, _end, ctx.getAppData().m_worldUp));
ctx.begin(PrimitiveMode_LineLoop);
// yz silhoette + cap bases
for (int i = 0; i <= detail2; ++i)
{
float rad = TwoPi * ((float)i / (float)detail2) - HalfPi;
ctx.vertex(Vec3(0.0f, 0.0f, -ln) + Vec3(cosf(rad), sinf(rad), 0.0f) * _radius);
}
for (int i = 0; i < _detail; ++i)
{
float rad = Pi * ((float)i / (float)_detail) + Pi;
ctx.vertex(Vec3(0.0f, 0.0f, -ln) + Vec3(0.0f, cosf(rad), sinf(rad)) * _radius);
}
for (int i = 0; i < _detail; ++i)
{
float rad = Pi * ((float)i / (float)_detail);
ctx.vertex(Vec3(0.0f, 0.0f, ln) + Vec3(0.0f, cosf(rad), sinf(rad)) * _radius);
}
for (int i = 0; i <= detail2; ++i)
{
float rad = TwoPi * ((float)i / (float)detail2) - HalfPi;
ctx.vertex(Vec3(0.0f, 0.0f, ln) + Vec3(cosf(rad), sinf(rad), 0.0f) * _radius);
}
ctx.end();
ctx.begin(PrimitiveMode_LineLoop);
// xz silhoette
for (int i = 0; i < _detail; ++i)
{
float rad = Pi * ((float)i / (float)_detail) + Pi;
ctx.vertex(Vec3(0.0f, 0.0f, -ln) + Vec3(cosf(rad), 0.0f, sinf(rad)) * _radius);
}
for (int i = 0; i < _detail; ++i)
{
float rad = Pi * ((float)i / (float)_detail);
ctx.vertex(Vec3(0.0f, 0.0f, ln) + Vec3(cosf(rad), 0.0f, sinf(rad)) * _radius);
}
ctx.end();
ctx.popMatrix();
}
void Im3d::DrawPrism(const Vec3& _start, const Vec3& _end, float _radius, int _sides)
{
_sides = Max(_sides, 2);
Context& ctx = GetContext();
#if IM3D_CULL_PRIMITIVES
if (!ctx.isVisible((_start + _end) * 0.5f, Max(Length2(_start - _end), _radius)))
{
return;
}
#endif
Vec3 org = _start + (_end - _start) * 0.5f;
float ln = Length(_end - _start) * 0.5f;
ctx.pushMatrix(ctx.getMatrix() * LookAt(org, _end, ctx.getAppData().m_worldUp));
ctx.begin(PrimitiveMode_LineLoop);
for (int i = 0; i <= _sides; ++i)
{
float rad = TwoPi * ((float)i / (float)_sides) - HalfPi;
ctx.vertex(Vec3(0.0f, 0.0f, -ln) + Vec3(cosf(rad), sinf(rad), 0.0f) * _radius);
}
for (int i = 0; i <= _sides; ++i)
{
float rad = TwoPi * ((float)i / (float)_sides) - HalfPi;
ctx.vertex(Vec3(0.0f, 0.0f, ln) + Vec3(cosf(rad), sinf(rad), 0.0f) * _radius);
}
ctx.end();
ctx.begin(PrimitiveMode_Lines);
for (int i = 0; i <= _sides; ++i)
{
float rad = TwoPi * ((float)i / (float)_sides) - HalfPi;
ctx.vertex(Vec3(0.0f, 0.0f, -ln) + Vec3(cosf(rad), sinf(rad), 0.0f) * _radius);
ctx.vertex(Vec3(0.0f, 0.0f, ln) + Vec3(cosf(rad), sinf(rad), 0.0f) * _radius);
}
ctx.end();
ctx.popMatrix();
}
void Im3d::DrawArrow(const Vec3& _start, const Vec3& _end, float _headLength, float _headThickness)
{
Context& ctx = GetContext();
if (_headThickness < 0.0f)
{
_headThickness = ctx.getSize() * 2.0f;
}
Vec3 dir = _end - _start;
float dirlen = Length(dir);
if (_headLength < 0.0f)
{
_headLength = Min(dirlen / 2.0f, ctx.pixelsToWorldSize(_end, _headThickness * 2.0f));
}
dir = dir / dirlen;
Vec3 head = _end - dir * _headLength;
ctx.begin(PrimitiveMode_Lines);
ctx.vertex(_start);
ctx.vertex(head);
ctx.vertex(head, _headThickness, ctx.getColor());
ctx.vertex(_end, 2.0f, ctx.getColor()); // \hack \todo 2.0f here compensates for the shader antialiasing (which reduces alpha when size < 2)
ctx.end();
}
void Im3d::DrawCone(const Vec3& _origin, const Vec3& _normal,float height, float _radius, int _detail){
Context& ctx = GetContext();
#if IM3D_CULL_PRIMITIVES
if (!ctx.isVisible(_origin + _normal * height / 2, height / 2))
{
return;
}
#endif
if (_detail < 0)
{
_detail = ctx.estimateLevelOfDetail(_origin + _normal * height / 2, height / 2, 8, 48);
}
_detail = Max(_detail, 3);
//cone bottom face
DrawCircle(_origin,_normal,_radius,_detail);
//cone side face
ctx.pushMatrix(ctx.getMatrix() * LookAt(_origin, _origin + _normal, ctx.getAppData().m_worldUp));
ctx.begin(PrimitiveMode_LineLoop);
float cp = _radius;
float sp = 0.0f;
for (int i = 1; i <= _detail; ++i)
{
ctx.vertex(Vec3(0,0,1)*height);
ctx.vertex(Vec3(cp, sp, 0.0f));
float rad = TwoPi * ((float)i / (float)_detail);
float c = cosf(rad) * _radius;
float s = sinf(rad) * _radius;
ctx.vertex(Vec3(c, s, 0.0f));
cp = c;
sp = s;
}
ctx.end();
ctx.popMatrix();
}
void Im3d::DrawConeFilled(const Vec3& _origin, const Vec3& _normal,float height, float _radius, int _detail){
Context& ctx = GetContext();
#if IM3D_CULL_PRIMITIVES
if (!ctx.isVisible(_origin + _normal * height / 2, height / 2))
{
return;
}
#endif
if (_detail < 0)
{
_detail = ctx.estimateLevelOfDetail(_origin + _normal * height / 2, height / 2, 8, 48);
}
_detail = Max(_detail, 3);
//cone bottom face
DrawCircleFilled(_origin,_normal,_radius,_detail);
//cone side face
ctx.pushMatrix(ctx.getMatrix() * LookAt(_origin, _origin + _normal, ctx.getAppData().m_worldUp));
ctx.begin(PrimitiveMode_Triangles);
float cp = _radius;
float sp = 0.0f;
for (int i = 1; i <= _detail; ++i)
{
ctx.vertex(Vec3(0,0,1)*height);
ctx.vertex(Vec3(cp, sp, 0.0f));
float rad = TwoPi * ((float)i / (float)_detail);
float c = cosf(rad) * _radius;
float s = sinf(rad) * _radius;
ctx.vertex(Vec3(c, s, 0.0f));
cp = c;
sp = s;
}
ctx.end();
ctx.popMatrix();
}
void Im3d::Text(const Vec3& _position, U32 _textFlags, const char* _text, ...)
{
va_list args;
va_start(args, _text);
GetContext().text(_position, GetSize(), GetColor(), (TextFlags)_textFlags, _text, args);
va_end(args);
}
void Im3d::Text(const Vec3& _position, float _size, Color _color, U32 _textFlags, const char* _text, ...)
{
va_list args;
va_start(args, _text);
GetContext().text(_position, _size, _color, (TextFlags)_textFlags, _text, args);
va_end(args);
}
static constexpr U32 kFnv1aPrime32 = 0x01000193u;
static U32 Hash(const char* _buf, int _buflen, U32 _base)
{
U32 ret = _base;
const char* lim = _buf + _buflen;
while (_buf < lim)
{
ret ^= (U32)*_buf++;
ret *= kFnv1aPrime32;
}
return ret;
}
static U32 HashStr(const char* _str, U32 _base)
{
U32 ret = _base;
while (*_str)
{
ret ^= (U32)*_str++;
ret *= kFnv1aPrime32;
}
return ret;
}
Im3d::Id Im3d::MakeId(const char* _str)
{
return HashStr(_str, GetContext().getId());
}
Im3d::Id Im3d::MakeId(const void* _ptr)
{
return Hash((const char*)&_ptr, sizeof(void*), GetContext().getId());
}
Im3d::Id Im3d::MakeId(int _i)
{
return Hash((const char*)&_i, sizeof(int), GetContext().getId());
}
inline static float Snap(float _val, float _snap)
{
if (_snap > 0.0f)
{
return floorf(_val / _snap) * _snap;
}
return _val;
}
inline static Vec3 Snap(const Vec3& _val, float _snap)
{
if (_snap > 0.0f)
{
return Vec3(floorf(_val.x / _snap) * _snap, floorf(_val.y / _snap) * _snap, floorf(_val.z / _snap) * _snap);
}
return _val;
}
inline static Vec3 Snap(const Vec3& _pos, const Plane& _plane, float _snap)
{
if (_snap > 0.0f)
{
// get basis vectors on the plane
Mat3 basis = AlignZ(_plane.m_normal);
Vec3 i = basis.getCol(0);
Vec3 j = basis.getCol(1);
// decompose _pos in terms of the basis vectors
i = i * Dot(_pos, i);
j = j * Dot(_pos, j);
// snap the vector lengths
float ilen = Length(i);
float jlen = Length(j);
if (ilen < 1e-7f || jlen < 1e-7f) // \hack prevent DBZ when _pos is 0
{
return _pos;
}
i = i / ilen;
ilen = floorf(ilen / _snap) * _snap;
i = i * ilen;
j = j / jlen;
jlen = floorf(jlen / _snap) * _snap;
j = j * jlen;
return i + j;
}
return _pos;
}
bool Im3d::GizmoTranslation(Id _id, float _translation_[3], bool _local)
{
Context& ctx = GetContext();
bool ret = false;
Vec3* outVec3 = (Vec3*)_translation_;
Vec3 drawAt = *outVec3;
const AppData& appData = ctx.getAppData();
float worldHeight = ctx.pixelsToWorldSize(drawAt, ctx.m_gizmoHeightPixels);
#if IM3D_CULL_GIZMOS
if (!ctx.isVisible(drawAt, worldHeight))
{
return false;
}
#endif
ctx.pushId(_id);
ctx.m_appId = _id;
if (_local)
{
Mat4 localMatrix = ctx.getMatrix();
localMatrix.setScale(Vec3(1.0f));
ctx.pushMatrix(localMatrix);
}
float planeSize = worldHeight * (0.5f * 0.5f);
float planeOffset = worldHeight * 0.5f;
float worldSize = ctx.pixelsToWorldSize(drawAt, ctx.m_gizmoSizePixels);
struct AxisG { Id m_id; Vec3 m_axis; Color m_color; };
AxisG axes[] =
{
{ MakeId("axisX"), Vec3(1.0f, 0.0f, 0.0f), Color_Red },
{ MakeId("axisY"), Vec3(0.0f, 1.0f, 0.0f), Color_Green },
{ MakeId("axisZ"), Vec3(0.0f, 0.0f, 1.0f), Color_Blue }
};
struct PlaneG { Id m_id; Vec3 m_origin; };
PlaneG planes[] =
{
{ MakeId("planeYZ"), Vec3(0.0f, planeOffset, planeOffset) },
{ MakeId("planeXZ"), Vec3(planeOffset, 0.0f, planeOffset) },
{ MakeId("planeXY"), Vec3(planeOffset, planeOffset, 0.0f) },
{ MakeId("planeV"), Vec3(0.0f, 0.0f, 0.0f) }
};
// invert axes if viewing from behind
if (appData.m_flipGizmoWhenBehind)
{
const Vec3 viewDir = appData.m_projOrtho
? -appData.m_viewDirection
: Normalize(appData.m_viewOrigin - *outVec3)
;
for (int i = 0; i < 3; ++i)
{
const Vec3 axis = _local ? Vec3(ctx.getMatrix().getCol(i)) : axes[i].m_axis;
if (Dot(axis, viewDir) < 0.0f)
{
axes[i].m_axis = -axes[i].m_axis;
for (int j = 0; j < 3; ++j)
{
planes[j].m_origin[i] = -planes[j].m_origin[i];
}
}
}
}
Sphere boundingSphere(*outVec3, worldHeight * 1.5f); // expand the bs to catch the planar subgizmos
Ray ray(appData.m_cursorRayOrigin, appData.m_cursorRayDirection);
bool intersects = ctx.m_appHotId == ctx.m_appId || Intersects(ray, boundingSphere);
// planes
ctx.pushEnableSorting(true);
if (_local)
{
// local planes need to be drawn with the pushed matrix for correct orientation
for (int i = 0; i < 3; ++i)
{
const PlaneG& plane = planes[i];
ctx.gizmoPlaneTranslation_Draw(plane.m_id, plane.m_origin, axes[i].m_axis, planeSize, Color_GizmoHighlight);
axes[i].m_axis = Mat3(ctx.getMatrix()) * axes[i].m_axis;
if (intersects)
{
ret |= ctx.gizmoPlaneTranslation_Behavior(plane.m_id, ctx.getMatrix() * plane.m_origin, axes[i].m_axis, appData.m_snapTranslation, planeSize, outVec3);
}
}
}
else
{
ctx.pushMatrix(Mat4(1.0f));
for (int i = 0; i < 3; ++i)
{
const PlaneG& plane = planes[i];
ctx.gizmoPlaneTranslation_Draw(plane.m_id, drawAt + plane.m_origin, axes[i].m_axis, planeSize, Color_GizmoHighlight);
if (intersects)
{
ret |= ctx.gizmoPlaneTranslation_Behavior(plane.m_id, drawAt + plane.m_origin, axes[i].m_axis, appData.m_snapTranslation, planeSize, outVec3);
}
}
ctx.popMatrix();
}
ctx.pushMatrix(Mat4(1.0f));
if (intersects)
{
// view plane (store the normal when the gizmo becomes active)
Id currentId = ctx.m_activeId;
Vec3& storedViewNormal= *((Vec3*)ctx.m_gizmoStateMat3.m);
Vec3 viewNormal;
if (planes[3].m_id == ctx.m_activeId)
{
viewNormal = storedViewNormal;
}
else
{
viewNormal = ctx.getAppData().m_viewDirection;
}
ret |= ctx.gizmoPlaneTranslation_Behavior(planes[3].m_id, drawAt, viewNormal, appData.m_snapTranslation, worldSize, outVec3);
if (currentId != ctx.m_activeId)
{
// gizmo became active, store the view normal
storedViewNormal = viewNormal;
}
// highlight axes if the corresponding plane is hot
if (planes[0].m_id == ctx.m_hotId) // YZ
{
axes[1].m_color = axes[2].m_color = Color_GizmoHighlight;
}
else if (planes[1].m_id == ctx.m_hotId) // XZ
{
axes[0].m_color = axes[2].m_color = Color_GizmoHighlight;
}
else if (planes[2].m_id == ctx.m_hotId) // XY
{
axes[0].m_color = axes[1].m_color = Color_GizmoHighlight;
}
else if (planes[3].m_id == ctx.m_hotId) // view plane
{
axes[0].m_color = axes[1].m_color = axes[2].m_color = Color_GizmoHighlight;
}
}
// draw the view plane handle
ctx.begin(PrimitiveMode_Points);
ctx.vertex(drawAt, ctx.m_gizmoSizePixels * 2.0f, planes[3].m_id == ctx.m_hotId ? Color_GizmoHighlight : Color_White);
ctx.end();
// axes
for (int i = 0; i < 3; ++i)
{
AxisG& axis = axes[i];
ctx.gizmoAxisTranslation_Draw(axis.m_id, drawAt, axis.m_axis, worldHeight, worldSize, axis.m_color);
if (intersects)
{
ret |= ctx.gizmoAxisTranslation_Behavior(axis.m_id, drawAt, axis.m_axis, appData.m_snapTranslation, worldHeight, worldSize, outVec3);
}
}
ctx.popMatrix();
ctx.popEnableSorting();
if (_local)
{
ctx.popMatrix();
}
ctx.popId();
return ret;
}
bool Im3d::GizmoRotation(Id _id, float _rotation_[3*3], bool _local)
{
Context& ctx = GetContext();
Vec3 origin = ctx.getMatrix().getTranslation();
float worldRadius = ctx.pixelsToWorldSize(origin, ctx.m_gizmoHeightPixels);
#if IM3D_CULL_GIZMOS
if (!ctx.isVisible(origin, worldRadius))
{
return false;
}
#endif
Id currentId = ctx.m_activeId; // store currentId to detect if the gizmo becomes active during this call
ctx.pushId(_id);
ctx.m_appId = _id;
bool ret = false;
Mat3& storedRotation = ctx.m_gizmoStateMat3;
Mat3* outMat3 = (Mat3*)_rotation_;
Vec3 euler = ToEulerXYZ(*outMat3);
float worldSize = ctx.pixelsToWorldSize(origin, ctx.m_gizmoSizePixels);
struct AxisG { Id m_id; Vec3 m_axis; Color m_color; };
AxisG axes[] =
{
{ MakeId("axisX"), Vec3(1.0f, 0.0f, 0.0f), Color_Red },
{ MakeId("axisY"), Vec3(0.0f, 1.0f, 0.0f), Color_Green },
{ MakeId("axisZ"), Vec3(0.0f, 0.0f, 1.0f), Color_Blue }
};
Id viewId = MakeId("axisV");
Sphere boundingSphere(origin, worldRadius);
Ray ray(ctx.getAppData().m_cursorRayOrigin, ctx.getAppData().m_cursorRayDirection);
bool intersects = ctx.m_appHotId == ctx.m_appId || Intersects(ray, boundingSphere);
const AppData& appData = ctx.getAppData();
if (_local)
{
// extract axes from the pushed matrix
for (int i = 0; i < 3; ++i)
{
if (ctx.m_activeId == axes[i].m_id)
{
// use the stored matrix where the id is active, avoid rotating the axis frame during interaction (cause numerical instability)
axes[i].m_axis = Normalize(Vec3(storedRotation.getCol(i)));
}
else
{
axes[i].m_axis = Normalize(Vec3(ctx.getMatrix().getCol(i)));
}
}
}
ctx.pushMatrix(Mat4(1.0f));
for (int i = 0; i < 3; ++i)
{
if (i == 0 && (ctx.m_activeId == axes[1].m_id || ctx.m_activeId == axes[2].m_id || ctx.m_activeId == viewId))
{
continue;
}
if (i == 1 && (ctx.m_activeId == axes[2].m_id || ctx.m_activeId == axes[0].m_id || ctx.m_activeId == viewId))
{
continue;
}
if (i == 2 && (ctx.m_activeId == axes[0].m_id || ctx.m_activeId == axes[1].m_id || ctx.m_activeId == viewId))
{
continue;
}
AxisG& axis = axes[i];
ctx.gizmoAxislAngle_Draw(axis.m_id, origin, axis.m_axis, worldRadius * 0.9f, euler[i], axis.m_color, 0.0f);
if (intersects && ctx.gizmoAxislAngle_Behavior(axis.m_id, origin, axis.m_axis, appData.m_snapRotation, worldRadius * 0.9f, worldSize, &euler[i]))
{
*outMat3 = Rotation(axis.m_axis, euler[i] - ctx.m_gizmoStateFloat) * storedRotation;
ret = true;
}
}
if (!(ctx.m_activeId == axes[0].m_id || ctx.m_activeId == axes[1].m_id || ctx.m_activeId == axes[2].m_id))
{
Vec3 viewNormal = ctx.getAppData().m_viewDirection;
float angle = 0.0f;
if (intersects && ctx.gizmoAxislAngle_Behavior(viewId, origin, viewNormal, appData.m_snapRotation, worldRadius, worldSize, &angle))
{
*outMat3 = Rotation(viewNormal, angle) * storedRotation;
ret = true;
}
ctx.gizmoAxislAngle_Draw(viewId, origin, viewNormal, worldRadius, angle, viewId == ctx.m_activeId ? Color_GizmoHighlight : Color_White, 1.0f);
}
ctx.popMatrix();
if (currentId != ctx.m_activeId)
{
// gizmo became active, store rotation matrix
storedRotation = *outMat3;
}
ctx.popId();
return ret;
}
bool Im3d::GizmoScale(Id _id, float _scale_[3])
{
Context& ctx = GetContext();
Vec3 origin = ctx.getMatrix().getTranslation();
float worldHeight = ctx.pixelsToWorldSize(origin, ctx.m_gizmoHeightPixels);
#if IM3D_CULL_GIZMOS
if (!ctx.isVisible(origin, worldHeight))
{
return false;
}
#endif
ctx.pushId(_id);
ctx.m_appId = _id;
bool ret = false;
Vec3* outVec3 = (Vec3*)_scale_;
const AppData& appData = ctx.getAppData();
float planeSize = worldHeight * (0.5f * 0.5f);
float planeOffset = worldHeight * 0.5f;
float worldSize = ctx.pixelsToWorldSize(origin, ctx.m_gizmoSizePixels);
struct AxisG { Id m_id; Vec3 m_axis; Color m_color; };
AxisG axes[] =
{
{ MakeId("axisX"), Normalize(ctx.getMatrix().getCol(0)), Color_Red },
{ MakeId("axisY"), Normalize(ctx.getMatrix().getCol(1)), Color_Green },
{ MakeId("axisZ"), Normalize(ctx.getMatrix().getCol(2)), Color_Blue }
};
// invert axes if viewing from behind
if (appData.m_flipGizmoWhenBehind)
{
const Vec3 viewDir = appData.m_projOrtho
? appData.m_viewDirection
: Normalize(appData.m_viewOrigin - origin)
;
for (int i = 0; i < 3; ++i)
{
if (Dot(axes[i].m_axis, viewDir) < 0.0f)
{
axes[i].m_axis = -axes[i].m_axis;
}
}
}
Sphere boundingSphere(origin, worldHeight);
Ray ray(appData.m_cursorRayOrigin, appData.m_cursorRayDirection);
bool intersects = ctx.m_appHotId == ctx.m_appId || Intersects(ray, boundingSphere);
ctx.pushEnableSorting(true);
ctx.pushMatrix(Mat4(1.0f));
{ // uniform scale
Id uniformId = MakeId("uniform");
if (intersects)
{
Sphere handle(origin, ctx.pixelsToWorldSize(origin, ctx.m_gizmoSizePixels * 4.0f));
float t0, t1;
bool intersects = Intersect(ray, handle, t0, t1);
Vec3& storedScale = ctx.m_gizmoStateVec3;
Vec3& storedPosition = *((Vec3*)ctx.m_gizmoStateMat3.m);
if (uniformId == ctx.m_activeId)
{
if (ctx.isKeyDown(Action_Select))
{
Plane plane(Normalize(origin - appData.m_viewOrigin), origin);
Intersect(ray, plane, t0);
Vec3 intersection = ray.m_origin + ray.m_direction * t0;
float sign = Dot(intersection - origin, storedPosition - origin);
float scale= copysignf(Length(intersection - origin), sign) / worldHeight;
scale = Snap(scale, appData.m_snapScale);
*outVec3 = storedScale * Vec3(Max(1.0f + copysignf(scale, sign), 1e-4f));
ret = true;
}
else
{
ctx.makeActive(Id_Invalid);
}
}
else if (uniformId == ctx.m_hotId)
{
if (intersects)
{
if (ctx.isKeyDown(Action_Select))
{
ctx.makeActive(uniformId);
storedScale = *outVec3;
storedPosition = ray.m_origin + ray.m_direction * t0;
}
}
else
{
ctx.resetId();
}
}
else
{
float depth = Length2(origin - appData.m_viewOrigin);
ctx.makeHot(uniformId, depth, intersects);
}
}
bool activeOrHot = ctx.m_activeId == uniformId || ctx.m_hotId == uniformId;
if (activeOrHot)
{
for (int i = 0; i < 3; ++i)
{
axes[i].m_color = Color_GizmoHighlight;
}
ctx.pushColor(Color_GizmoHighlight);
ctx.pushAlpha(1.0f);
ctx.pushSize(2.0f);
DrawCircle(origin, Normalize(origin - appData.m_viewOrigin), worldSize * 2.0f);
ctx.popSize();
ctx.popAlpha();
ctx.popColor();
}
ctx.pushAlpha(ctx.m_hotId == uniformId ? 1.0f : ctx.getAlpha());
ctx.begin(PrimitiveMode_Points);
ctx.vertex(origin, ctx.m_gizmoSizePixels * 2.0f, activeOrHot ? Color_GizmoHighlight : Color_White);
ctx.end();
ctx.popAlpha();
}
for (int i = 0; i < 3; ++i)
{
AxisG& axis = axes[i];
ctx.gizmoAxisScale_Draw(axis.m_id, origin, axis.m_axis, worldHeight, worldSize, axis.m_color);
if (intersects)
{
ret |= ctx.gizmoAxisScale_Behavior(axis.m_id, origin, axis.m_axis, appData.m_snapScale, worldHeight, worldSize, &(*outVec3)[i]);
}
}
ctx.popMatrix();
ctx.popEnableSorting();
ctx.popId();
return ret;
}
bool Im3d::Gizmo(Id _id, float _transform_[4*4])
{
IM3D_ASSERT(_transform_);
Context& ctx = GetContext();
Mat4* outMat4 = (Mat4*)_transform_;
ctx.pushMatrix(*outMat4);
bool ret = false;
switch (ctx.m_gizmoMode)
{
case GizmoMode_Translation:
{
Vec3 translation = outMat4->getTranslation();
if (GizmoTranslation(_id, translation, ctx.m_gizmoLocal))
{
outMat4->setTranslation(translation);
ret = true;
}
break;
}
case GizmoMode_Rotation:
{
Mat3 rotation = outMat4->getRotation();
if (GizmoRotation(_id, rotation, ctx.m_gizmoLocal))
{
outMat4->setRotation(rotation);
ret = true;
}
break;
}
case GizmoMode_Scale:
{
Vec3 scale = outMat4->getScale();
if (GizmoScale(_id, scale))
{
outMat4->setScale(scale);
ret = true;
}
break;
}
default:
break;
};
ctx.popMatrix();
return ret;
}
bool Im3d::Gizmo(Id _id, float _translation_[3], float _rotation_[3*3], float _scale_[3])
{
Context& ctx = GetContext();
Mat4 transform(
_translation_ ? *((Vec3*)_translation_) : Vec3(0.0f),
_rotation_ ? *((Mat3*)_rotation_) : Mat3(1.0f),
_scale_ ? *((Vec3*)_scale_) : Vec3(1.0f)
);
ctx.pushMatrix(transform);
bool ret = false;
switch (ctx.m_gizmoMode)
{
case GizmoMode_Translation:
if (_translation_)
{
if (GizmoTranslation(_id, _translation_, ctx.m_gizmoLocal))
{
ret = true;
}
}
break;
case GizmoMode_Rotation:
if (_rotation_)
{
if (GizmoRotation(_id, _rotation_, ctx.m_gizmoLocal))
{
ret = true;
}
}
break;
case GizmoMode_Scale:
if (_scale_)
{
if (GizmoScale(_id, _scale_))
{
ret = true;
}
}
break;
default:
break;
};
ctx.popMatrix();
return ret;
}
void AppData::setCullFrustum(const Mat4& _viewProj, bool _ndcZNegativeOneToOne)
{
m_cullFrustum[FrustumPlane_Top].x = _viewProj(3, 0) - _viewProj(1, 0);
m_cullFrustum[FrustumPlane_Top].y = _viewProj(3, 1) - _viewProj(1, 1);
m_cullFrustum[FrustumPlane_Top].z = _viewProj(3, 2) - _viewProj(1, 2);
m_cullFrustum[FrustumPlane_Top].w = -(_viewProj(3, 3) - _viewProj(1, 3));
m_cullFrustum[FrustumPlane_Bottom].x = _viewProj(3, 0) + _viewProj(1, 0);
m_cullFrustum[FrustumPlane_Bottom].y = _viewProj(3, 1) + _viewProj(1, 1);
m_cullFrustum[FrustumPlane_Bottom].z = _viewProj(3, 2) + _viewProj(1, 2);
m_cullFrustum[FrustumPlane_Bottom].w = -(_viewProj(3, 3) + _viewProj(1, 3));
m_cullFrustum[FrustumPlane_Right].x = _viewProj(3, 0) - _viewProj(0, 0);
m_cullFrustum[FrustumPlane_Right].y = _viewProj(3, 1) - _viewProj(0, 1);
m_cullFrustum[FrustumPlane_Right].z = _viewProj(3, 2) - _viewProj(0, 2);
m_cullFrustum[FrustumPlane_Right].w = -(_viewProj(3, 3) - _viewProj(0, 3));
m_cullFrustum[FrustumPlane_Left].x = _viewProj(3, 0) + _viewProj(0, 0);
m_cullFrustum[FrustumPlane_Left].y = _viewProj(3, 1) + _viewProj(0, 1);
m_cullFrustum[FrustumPlane_Left].z = _viewProj(3, 2) + _viewProj(0, 2);
m_cullFrustum[FrustumPlane_Left].w = -(_viewProj(3, 3) + _viewProj(0, 3));
m_cullFrustum[FrustumPlane_Far].x = _viewProj(3, 0) - _viewProj(2, 0);
m_cullFrustum[FrustumPlane_Far].y = _viewProj(3, 1) - _viewProj(2, 1);
m_cullFrustum[FrustumPlane_Far].z = _viewProj(3, 2) - _viewProj(2, 2);
m_cullFrustum[FrustumPlane_Far].w = -(_viewProj(3, 3) - _viewProj(2, 3));
if (_ndcZNegativeOneToOne)
{
m_cullFrustum[FrustumPlane_Near].x = _viewProj(3, 0) + _viewProj(2, 0);
m_cullFrustum[FrustumPlane_Near].y = _viewProj(3, 1) + _viewProj(2, 1);
m_cullFrustum[FrustumPlane_Near].z = _viewProj(3, 2) + _viewProj(2, 2);
m_cullFrustum[FrustumPlane_Near].w = -(_viewProj(3, 3) + _viewProj(2, 3));
}
else
{
m_cullFrustum[FrustumPlane_Near].x = _viewProj(2, 0);
m_cullFrustum[FrustumPlane_Near].y = _viewProj(2, 1);
m_cullFrustum[FrustumPlane_Near].z = _viewProj(2, 2);
m_cullFrustum[FrustumPlane_Near].w = -(_viewProj(2, 3));
}
// normalize
for (int i = 0; i < FrustumPlane_Count; ++i)
{
float d = 1.0f / Length(Vec3(m_cullFrustum[i]));
m_cullFrustum[i] = m_cullFrustum[i] * d;
}
}
/*******************************************************************************
Vector
*******************************************************************************/
static void* AlignedMalloc(size_t _size, size_t _align)
{
IM3D_ASSERT(_size > 0);
IM3D_ASSERT(_align > 0);
size_t grow = (_align - 1) + sizeof(void*);
size_t mem = (size_t)IM3D_MALLOC(_size + grow);
if (mem)
{
size_t ret = (mem + grow) & (~(_align - 1));
IM3D_ASSERT(ret % _align == 0); // aligned correctly
IM3D_ASSERT(ret >= mem + sizeof(void*)); // header large enough to store a ptr
*((void**)(ret - sizeof(void*))) = (void*)mem;
return (void*)ret;
}
else
{
return nullptr;
}
}
static void AlignedFree(void* _ptr_)
{
void* mem = *((void**)((size_t)_ptr_ - sizeof(void*)));
IM3D_FREE(mem);
}
template <typename T>
Vector<T>::~Vector()
{
if (m_data)
{
AlignedFree(m_data);
m_data = 0;
}
}
template <typename T>
void Vector<T>::append(const T* _v, U32 _count)
{
if (_count == 0)
{
return;
}
U32 sz = m_size + _count;
reserve(sz);
memcpy(end(), _v, sizeof(T) * _count);
m_size = sz;
}
template <typename T>
void Vector<T>::reserve(U32 _capacity)
{
_capacity = _capacity < 8 ? 8 : _capacity;
if (_capacity <= m_capacity)
{
return;
}
T* data = (T*)AlignedMalloc(sizeof(T) * _capacity, alignof(T));
if (m_data)
{
memcpy(data, m_data, sizeof(T) * m_size);
AlignedFree(m_data);;
}
m_data = data;
m_capacity = _capacity;
}
template <typename T>
void Vector<T>::resize(U32 _size, const T& _val)
{
IM3D_ASSERT(_size >= m_size); // only support growing the vector
reserve(_size);
while (m_size < _size)
{
push_back(_val);
}
m_size = _size;
}
template <typename T>
void Vector<T>::resize(U32 _size)
{
IM3D_ASSERT(_size >= m_size); // only support growing the vector
reserve(_size);
m_size = _size;
}
template <typename T>
void Vector<T>::swap(Vector<T>& _a_, Vector<T>& _b_)
{
T* data = _a_.m_data;
U32 capacity = _a_.m_capacity;
U32 size = _a_.m_size;
_a_.m_data = _b_.m_data;
_a_.m_capacity = _b_.m_capacity;
_a_.m_size = _b_.m_size;
_b_.m_data = data;
_b_.m_capacity = capacity;
_b_.m_size = size;
}
template struct Im3d::Vector<bool>;
template struct Im3d::Vector<char>;
template struct Im3d::Vector<float>;
template struct Im3d::Vector<Id>;
template struct Im3d::Vector<Mat4>;
template struct Im3d::Vector<Color>;
template struct Im3d::Vector<DrawList>;
/*******************************************************************************
Context
*******************************************************************************/
static Context g_DefaultContext;
IM3D_THREAD_LOCAL Context* Im3d::internal::g_CurrentContext = &g_DefaultContext;
void Context::begin(PrimitiveMode _mode)
{
IM3D_ASSERT(!m_endFrameCalled); // Begin*() called after EndFrame() but before NewFrame(), or forgot to call NewFrame()
IM3D_ASSERT(m_primMode == PrimitiveMode_None); // forgot to call End()
m_primMode = _mode;
m_vertCountThisPrim = 0;
switch (m_primMode)
{
case PrimitiveMode_Points:
m_primType = DrawPrimitive_Points;
break;
case PrimitiveMode_Lines:
case PrimitiveMode_LineStrip:
case PrimitiveMode_LineLoop:
m_primType = DrawPrimitive_Lines;
break;
case PrimitiveMode_Triangles:
case PrimitiveMode_TriangleStrip:
m_primType = DrawPrimitive_Triangles;
break;
default:
break;
};
m_firstVertThisPrim = getCurrentVertexList()->size();
}
void Context::end()
{
IM3D_ASSERT(m_primMode != PrimitiveMode_None); // End() called without Begin*()
if (m_vertCountThisPrim > 0)
{
VertexList* vertexList = getCurrentVertexList();
switch (m_primMode)
{
case PrimitiveMode_Points:
break;
case PrimitiveMode_Lines:
IM3D_ASSERT(m_vertCountThisPrim % 2 == 0);
break;
case PrimitiveMode_LineStrip:
IM3D_ASSERT(m_vertCountThisPrim > 1);
break;
case PrimitiveMode_LineLoop:
IM3D_ASSERT(m_vertCountThisPrim > 1);
vertexList->push_back(vertexList->back());
vertexList->push_back((*vertexList)[m_firstVertThisPrim]);
break;
case PrimitiveMode_Triangles:
IM3D_ASSERT(m_vertCountThisPrim % 3 == 0);
break;
case PrimitiveMode_TriangleStrip:
IM3D_ASSERT(m_vertCountThisPrim >= 3);
break;
default:
break;
};
#if IM3D_CULL_PRIMITIVES
// \hack force the bounds to be slightly conservative to account for point/line size
m_minVertThisPrim = m_minVertThisPrim - Vec3(1.0f);
m_maxVertThisPrim = m_maxVertThisPrim + Vec3(1.0f);
if (!isVisible(m_minVertThisPrim, m_maxVertThisPrim))
{
vertexList->resize(m_firstVertThisPrim, VertexData());
}
#endif
}
m_primMode = PrimitiveMode_None;
m_primType = DrawPrimitive_Count;
#if IM3D_CULL_PRIMITIVES
// \debug draw primitive BBs
//if (m_enableCulling)
//{
// m_enableCulling = false;
// pushColor(Im3d::Color_Magenta);
// pushSize(1.0f);
// pushMatrix(Mat4(1.0f));
// DrawAlignedBox(m_minVertThisPrim, m_maxVertThisPrim);
// popMatrix();
// popColor();
// popSize();
// m_enableCulling = true;
//}
#endif
}
void Context::vertex(const Vec3& _position, float _size, Color _color)
{
IM3D_ASSERT(m_primMode != PrimitiveMode_None); // Vertex() called without Begin*()
VertexData vd(_position, _size, _color);
if (m_matrixStack.size() > 1) // optim, skip the matrix multiplication when the stack size is 1
{
vd.m_positionSize = Vec4(m_matrixStack.back() * _position, _size);
}
vd.m_color.setA(vd.m_color.getA() * m_alphaStack.back());
#if IM3D_CULL_PRIMITIVES
Vec3 p = Vec3(vd.m_positionSize);
if (m_vertCountThisPrim == 0) // p is the first vertex
{
m_minVertThisPrim = m_maxVertThisPrim = p;
}
else
{
m_minVertThisPrim = Min(m_minVertThisPrim, p);
m_maxVertThisPrim = Max(m_maxVertThisPrim, p);
}
#endif
VertexList* vertexList = getCurrentVertexList();
switch (m_primMode)
{
case PrimitiveMode_Points:
case PrimitiveMode_Lines:
case PrimitiveMode_Triangles:
vertexList->push_back(vd);
break;
case PrimitiveMode_LineStrip:
case PrimitiveMode_LineLoop:
if (m_vertCountThisPrim >= 2)
{
vertexList->push_back(vertexList->back());
++m_vertCountThisPrim;
}
vertexList->push_back(vd);
break;
case PrimitiveMode_TriangleStrip:
if (m_vertCountThisPrim >= 3)
{
vertexList->push_back(*(vertexList->end() - 2));
vertexList->push_back(*(vertexList->end() - 2));
m_vertCountThisPrim += 2;
}
vertexList->push_back(vd);
break;
default:
break;
};
++m_vertCountThisPrim;
#if 0
// per-vertex primitive culling; this method is generally too expensive to be practical (and can't cull line loops).
// check if the primitive was visible and rewind vertex data if not
switch (m_primMode)
{
case PrimitiveMode_Points:
if (!isVisible(&vertexList->back(), DrawPrimitive_Points))
{
vertexList->pop_back();
--m_vertCountThisPrim;
}
break;
case PrimitiveMode_LineLoop:
break; // can't cull line loops; end() may add an invalid line if any vertices are culled
case PrimitiveMode_Lines:
case PrimitiveMode_LineStrip:
if (m_vertCountThisPrim % 2 == 0)
{
if (!isVisible(&vertexList->back() - 1, DrawPrimitive_Lines))
{
for (int i = 0; i < 2; ++i)
{
vertexList->pop_back();
--m_vertCountThisPrim;
}
}
}
break;
case PrimitiveMode_Triangles:
case PrimitiveMode_TriangleStrip:
if (m_vertCountThisPrim % 3 == 0)
{
if (!isVisible(&vertexList->back() - 2, DrawPrimitive_Triangles))
{
for (int i = 0; i < 3; ++i)
{
vertexList->pop_back();
--m_vertCountThisPrim;
}
}
}
break;
default:
break;
};
#endif
}
void Context::text(const Vec3& _position, float _size, Color _color, TextFlags _flags, const char* _textStart, const char* _textEnd)
{
TextData& td = getCurrentTextList()->push_back();
td.m_positionSize = Vec4(_position, _size);
if (m_matrixStack.size() > 1) // optim, skip the matrix multiplication when the stack size is 1
{
td.m_positionSize = Vec4(m_matrixStack.back() * _position, _size);
}
td.m_color = _color;
td.m_color.setA(td.m_color.getA() * m_alphaStack.back());
td.m_flags = _flags;
td.m_textBufferOffset = m_textBuffer.size();
td.m_textLength = (U32)(_textEnd - _textStart);
const U32 copyOffset = m_textBuffer.size();
m_textBuffer.resize(copyOffset + td.m_textLength + 1);
memcpy(m_textBuffer.data() + copyOffset, _textStart, (size_t)td.m_textLength);
m_textBuffer.back() = '\0';
}
void Context::text(const Vec3& _position, float _size, Color _color, TextFlags _flags, const char* _text, va_list _args)
{
TextData& td = getCurrentTextList()->push_back();
td.m_positionSize = Vec4(_position, _size);
if (m_matrixStack.size() > 1) // optim, skip the matrix multiplication when the stack size is 1
{
td.m_positionSize = Vec4(m_matrixStack.back() * _position, _size);
}
td.m_color = _color;
td.m_color.setA(td.m_color.getA() * m_alphaStack.back());
td.m_flags = _flags;
td.m_textBufferOffset = m_textBuffer.size();
va_list argsCopy;
va_copy(argsCopy, _args);
td.m_textLength = (U32)vsnprintf(nullptr, 0, _text, argsCopy);
const U32 copyOffset = m_textBuffer.size();
m_textBuffer.resize(copyOffset + td.m_textLength + 1);
vsnprintf(m_textBuffer.data() + copyOffset, td.m_textLength + 1, _text, argsCopy);
m_textBuffer.back() = '\0';
}
void Context::reset()
{
// all state stacks should be default here, else there was a mismatched Push*()/Pop*()
IM3D_ASSERT(m_colorStack.size() == 1);
IM3D_ASSERT(m_alphaStack.size() == 1);
IM3D_ASSERT(m_sizeStack.size() == 1);
IM3D_ASSERT(m_enableSortingStack.size() == 1);
IM3D_ASSERT(m_layerIdStack.size() == 1);
IM3D_ASSERT(m_matrixStack.size() == 1);
IM3D_ASSERT(m_idStack.size() == 1);
IM3D_ASSERT(m_primMode == PrimitiveMode_None);
m_primMode = PrimitiveMode_None;
m_primType = DrawPrimitive_Count;
IM3D_ASSERT(m_vertexData[0].size() == m_vertexData[1].size());
for (U32 i = 0; i < m_vertexData[0].size(); ++i)
{
m_vertexData[0][i]->clear();
m_vertexData[1][i]->clear();
}
m_drawLists.clear();
for (U32 i = 0; i < m_textData.size(); ++i)
{
m_textData[i]->clear();
}
m_textDrawLists.clear();
m_textBuffer.clear();
m_sortCalled = false;
m_endFrameCalled = false;
m_appData.m_viewDirection = Normalize(m_appData.m_viewDirection);
// copy keydown array internally so that we can make a delta to detect key presses
memcpy(m_keyDownPrev, m_keyDownCurr, Key_Count); // \todo avoid this copy, use an index
memcpy(m_keyDownCurr, m_appData.m_keyDown, Key_Count); // must copy in case m_keyDown is updated after reset (e.g. by an app callback)
// process cull frustum
m_cullFrustumCount = 0;
for (int i = 0; i < FrustumPlane_Count; ++i)
{
const Vec4& plane = m_appData.m_cullFrustum[i];
if (m_appData.m_projOrtho && i == FrustumPlane_Near) // skip near plane if perspective
{
continue;
}
if (std::isinf(plane.w)) // may be the case e.g. for the far plane if projection is infinite
{
continue;
}
m_cullFrustum[m_cullFrustumCount++] = plane;
}
// update gizmo modes
if (wasKeyPressed(Action_GizmoTranslation))
{
m_gizmoMode = GizmoMode_Translation;
resetId();
}
else if (wasKeyPressed(Action_GizmoRotation))
{
m_gizmoMode = GizmoMode_Rotation;
resetId();
}
else if (wasKeyPressed(Action_GizmoScale))
{
m_gizmoMode = GizmoMode_Scale;
resetId();
}
if (wasKeyPressed(Action_GizmoLocal))
{
m_gizmoLocal = !m_gizmoLocal;
resetId();
}
}
void Context::merge(const Context& _src)
{
IM3D_ASSERT(!m_endFrameCalled && !_src.m_endFrameCalled); // call MergeContexts() before calling EndFrame()
// layer IDs
for (Id id : _src.m_layerIdMap)
{
pushLayerId(id); // add a new layer if id doesn't alrady exist
popLayerId();
}
// vertex data
for (U32 i = 0; i < 2; ++i)
{
const auto& vertexData = _src.m_vertexData[i];
for (U32 j = 0; j < vertexData.size(); ++j)
{
// for each layer in _src, find the matching layer in this
const Id layerId = _src.m_layerIdMap[j / DrawPrimitive_Count];
const int layerIndex = findLayerIndex(layerId);
IM3D_ASSERT(layerIndex >= 0);
U32 k = j % DrawPrimitive_Count;
m_vertexData[i][layerIndex * DrawPrimitive_Count + k]->append(*vertexData[j]);
}
}
// text data
for (U32 i = 0; i < _src.m_textData.size(); ++i)
{
const Id layerId = _src.m_layerIdMap[i];
const int layerIndex = findLayerIndex(layerId);
IM3D_ASSERT(layerIndex >= 0);
const U32 textBufferOffset = m_textBuffer.size();
m_textBuffer.append(_src.m_textBuffer);
const auto& textList = _src.m_textData[i];
for (U32 j = 0; j < textList->size(); ++j)
{
m_textData[i]->push_back((*_src.m_textData[i])[j]);
m_textData[i]->back().m_textBufferOffset += textBufferOffset;
}
}
}
void Context::endFrame()
{
IM3D_ASSERT(!m_endFrameCalled); // EndFrame() was called multiple times for this frame
m_endFrameCalled = true;
// draw unsorted primitives first
for (U32 i = 0; i < m_vertexData[0].size(); ++i)
{
if (m_vertexData[0][i]->size() > 0)
{
DrawList& dl = m_drawLists.push_back();
dl.m_layerId = m_layerIdMap[i / DrawPrimitive_Count];
dl.m_primType = (DrawPrimitiveType)(i % DrawPrimitive_Count);
dl.m_vertexData = m_vertexData[0][i]->data();
dl.m_vertexCount = m_vertexData[0][i]->size();
}
}
// draw sorted primitives second
if (!m_sortCalled)
{
sort();
}
for (U32 i = 0; i < m_textData.size(); ++i) {
if (m_textData[i]->size() > 0)
{
TextDrawList& dl = m_textDrawLists.push_back();
dl.m_layerId = m_layerIdMap[i];
dl.m_textData = m_textData[i]->data();
dl.m_textDataCount = m_textData[i]->size();
dl.m_textBuffer = m_textBuffer.data();
}
}
}
void Context::draw()
{
if (m_drawLists.empty())
{
endFrame();
}
IM3D_ASSERT(m_appData.drawCallback);
for (auto& drawList : m_drawLists)
{
m_appData.drawCallback(drawList);
}
}
void Context::pushEnableSorting(bool _enable)
{
IM3D_ASSERT(m_primMode == PrimitiveMode_None); // can't change sort mode mid-primitive
m_vertexDataIndex = _enable ? 1 : 0;
m_enableSortingStack.push_back(_enable);
}
void Context::popEnableSorting()
{
IM3D_ASSERT(m_primMode == PrimitiveMode_None); // can't change sort mode mid-primitive
m_enableSortingStack.pop_back();
m_vertexDataIndex = m_enableSortingStack.back() ? 1 : 0;
}
void Context::setEnableSorting(bool _enable)
{
IM3D_ASSERT(m_primMode == PrimitiveMode_None); // can't change sort mode mid-primitive
m_vertexDataIndex = _enable ? 1 : 0;
m_enableSortingStack.back() = _enable;
}
void Context::pushLayerId(Id _layer)
{
IM3D_ASSERT(m_primMode == PrimitiveMode_None); // can't change layer mid-primitive
int idx = findLayerIndex(_layer);
if (idx == -1) // not found, push new layer
{
idx = m_layerIdMap.size();
m_layerIdMap.push_back(_layer);
for (int i = 0; i < DrawPrimitive_Count; ++i)
{
m_vertexData[0].push_back((VertexList*)IM3D_MALLOC(sizeof(VertexList)));
*m_vertexData[0].back() = VertexList();
m_vertexData[1].push_back((VertexList*)IM3D_MALLOC(sizeof(VertexList)));
*m_vertexData[1].back() = VertexList();
}
m_textData.push_back((TextList*)IM3D_MALLOC(sizeof(TextList)));
*m_textData.back() = TextList();
}
m_layerIdStack.push_back(_layer);
m_layerIndex = idx;
}
void Context::popLayerId()
{
IM3D_ASSERT(m_layerIdStack.size() > 1);
m_layerIdStack.pop_back();
m_layerIndex = findLayerIndex(m_layerIdStack.back());
}
Context::Context()
{
m_sortCalled = false;
m_endFrameCalled = false;
m_primMode = PrimitiveMode_None;
m_vertexDataIndex = 0; // = sorting disabled
m_layerIndex = 0;
m_firstVertThisPrim = 0;
m_vertCountThisPrim = 0;
m_gizmoLocal = false;
m_gizmoMode = GizmoMode_Translation;
m_hotId = Id_Invalid;
m_activeId = Id_Invalid;
m_appId = Id_Invalid;
m_appActiveId = Id_Invalid;
m_appHotId = Id_Invalid;
m_hotDepth = FLT_MAX;
m_gizmoHeightPixels = 64.0f;
m_gizmoSizePixels = 5.0f;
memset(&m_keyDownCurr, 0, sizeof(m_keyDownCurr));
memset(&m_keyDownPrev, 0, sizeof(m_keyDownPrev));
// init cull frustum to INF effectively disables culling
for (int i = 0; i < FrustumPlane_Count; ++i)
{
m_appData.m_cullFrustum[i] = Vec4(INFINITY);
}
pushMatrix(Mat4(1.0f));
pushColor(Color_White);
pushAlpha(1.0f);
pushSize(1.0f);
pushEnableSorting(false);
pushLayerId(0);
pushId(0x811C9DC5u); // fnv1 hash base
}
Context::~Context()
{
for (int i = 0; i < 2; ++i)
{
while (!m_vertexData[i].empty())
{
m_vertexData[i].back()->~Vector(); // manually call dtor (vector is allocated via IM3D_MALLOC during pushLayerId)
IM3D_FREE(m_vertexData[i].back());
m_vertexData[i].pop_back();
}
}
while (!m_textData.empty())
{
m_textData.back()->~Vector(); // see above
IM3D_FREE(m_textData.back());
m_textData.pop_back();
}
}
namespace {
struct SortData
{
float m_key;
VertexData* m_start;
SortData() {}
SortData(float _key, VertexData* _start): m_key(_key), m_start(_start) {}
};
int SortCmp(const void* _a, const void* _b)
{
float ka = ((SortData*)_a)->m_key;
float kb = ((SortData*)_b)->m_key;
if (ka < kb)
{
return 1;
}
else if (ka > kb)
{
return -1;
}
else
{
return 0;
}
}
void Reorder(Vector<VertexData>& _data_, const SortData* _sort, U32 _sortCount, U32 _primSize)
{
Vector<VertexData> ret;
ret.reserve(_data_.size());
for (U32 i = 0; i < _sortCount; ++i)
{
for (U32 j = 0; j < _primSize; ++j)
{
ret.push_back(*(_sort[i].m_start + j));
}
}
Vector<VertexData>::swap(_data_, ret);
}
}
void Context::sort()
{
static IM3D_THREAD_LOCAL Vector<SortData> sortData[DrawPrimitive_Count]; // reduces # allocs
for (U32 layer = 0; layer < m_layerIdMap.size(); ++layer)
{
Vec3 viewOrigin = m_appData.m_viewOrigin;
// sort each primitive list internally
for (int i = 0 ; i < DrawPrimitive_Count; ++i)
{
Vector<VertexData>& vertexData = *(m_vertexData[1][layer * DrawPrimitive_Count + i]);
sortData[i].clear();
if (!vertexData.empty())
{
sortData[i].reserve(vertexData.size() / VertsPerDrawPrimitive[i]);
for (VertexData* v = vertexData.begin(); v != vertexData.end(); )
{
sortData[i].push_back(SortData(0.0f, v));
IM3D_ASSERT(v < vertexData.end());
for (int j = 0; j < VertsPerDrawPrimitive[i]; ++j, ++v)
{
// sort key is the primitive midpoint distance to view origin
sortData[i].back().m_key += Length2(Vec3(v->m_positionSize) - viewOrigin);
}
sortData[i].back().m_key /= (float)VertsPerDrawPrimitive[i];
}
// qsort is not necessarily stable but it doesn't matter assuming the prims are pushed in roughly the same order each frame
qsort(sortData[i].data(), sortData[i].size(), sizeof(SortData), SortCmp);
Reorder(vertexData, sortData[i].data(), sortData[i].size(), VertsPerDrawPrimitive[i]);
}
}
// construct draw lists - partition sort data into non-overlapping lists
int cprim = 0;
SortData* search[DrawPrimitive_Count];
int emptyCount = 0;
for (int i = 0; i < DrawPrimitive_Count; ++i)
{
if (sortData[i].empty())
{
search[i] = 0;
++emptyCount;
}
else
{
search[i] = sortData[i].begin();
}
}
bool first = true;
#define modinc(v) ((v + 1) % DrawPrimitive_Count)
while (emptyCount != DrawPrimitive_Count)
{
while (search[cprim] == 0)
{
cprim = modinc(cprim);
}
// find the max key at the current position across all sort data
float mxkey = search[cprim]->m_key;
int mxprim = cprim;
for (int p = modinc(cprim); p != cprim; p = modinc(p))
{
if (search[p] != 0 && search[p]->m_key > mxkey)
{
mxkey = search[p]->m_key;
mxprim = p;
}
}
// if draw list is empty or the layer or primitive changed, start a new draw list
if (false
|| first
|| (m_drawLists.back().m_layerId != m_layerIdMap[layer])
|| (m_drawLists.back().m_primType != mxprim)
)
{
cprim = mxprim;
DrawList dl;
dl.m_layerId = m_layerIdMap[layer];
dl.m_primType = (DrawPrimitiveType)cprim;
dl.m_vertexData = m_vertexData[1][layer * DrawPrimitive_Count + cprim]->data() + (search[cprim] - sortData[cprim].data()) * VertsPerDrawPrimitive[cprim];
dl.m_vertexCount = 0;
m_drawLists.push_back(dl);
first = false;
}
// increment the vertex count for the current draw list
m_drawLists.back().m_vertexCount += VertsPerDrawPrimitive[cprim];
++search[cprim];
if (search[cprim] == sortData[cprim].end())
{
search[cprim] = 0;
++emptyCount;
}
}
#undef modinc
}
m_sortCalled = true;
}
int Context::findLayerIndex(Id _id) const
{
for (int i = 0; i < (int)m_layerIdMap.size(); ++i)
{
if (m_layerIdMap[i] == _id)
{
return i;
}
}
return -1;
}
bool Context::isVisible(const VertexData* _vdata, DrawPrimitiveType _prim)
{
Vec3 pos[3];
float size[3];
for (int i = 0; i < VertsPerDrawPrimitive[_prim]; ++i)
{
pos[i] = Vec3(_vdata[i].m_positionSize);
size[i] = _prim == DrawPrimitive_Triangles ? 0.0f : pixelsToWorldSize(pos[i], _vdata[i].m_positionSize.w);
}
for (int i = 0; i < m_cullFrustumCount; ++i)
{
const Vec4& plane = m_cullFrustum[i];
bool isVisible= false;
for (int j = 0; j < VertsPerDrawPrimitive[_prim]; ++j)
{
isVisible |= Distance(plane, pos[j]) > -size[j];
}
if (!isVisible)
{
return false;
}
}
return true;
}
bool Context::isVisible(const Vec3& _origin, float _radius)
{
for (int i = 0; i < m_cullFrustumCount; ++i)
{
const Vec4& plane = m_cullFrustum[i];
if (Distance(plane, _origin) < -_radius)
{
return false;
}
}
return true;
}
bool Context::isVisible(const Vec3& _min, const Vec3& _max)
{
#if 0
const Vec3 points[] =
{
Vec3(_min.x, _min.y, _min.z),
Vec3(_max.x, _min.y, _min.z),
Vec3(_max.x, _max.y, _min.z),
Vec3(_min.x, _max.y, _min.z),
Vec3(_min.x, _min.y, _max.z),
Vec3(_max.x, _min.y, _max.z),
Vec3(_max.x, _max.y, _max.z),
Vec3(_min.x, _max.y, _max.z)
};
for (int i = 0; i < m_cullFrustumCount; ++i)
{
const Vec4& plane = m_cullFrustum[i];
bool inside = false;
for (int j = 0; j < 8; ++j)
{
if (Distance(plane, points[j]) > 0.0f)
{
inside = true;
break;
}
}
if (!inside)
{
return false;
}
}
return true;
#else
for (int i = 0; i < m_cullFrustumCount; ++i)
{
const Vec4& plane = m_cullFrustum[i];
float d =
Max(_min.x * plane.x, _max.x * plane.x) +
Max(_min.y * plane.y, _max.y * plane.y) +
Max(_min.z * plane.z, _max.z * plane.z) -
plane.w
;
if (d < 0.0f)
{
return false;
}
}
return true;
#endif
}
Context::VertexList* Context::getCurrentVertexList()
{
return m_vertexData[m_vertexDataIndex][m_layerIndex * DrawPrimitive_Count + m_primType];
}
Context::TextList* Context::getCurrentTextList()
{
return m_textData[m_layerIndex];
}
float Context::pixelsToWorldSize(const Vec3& _position, float _pixels)
{
float d = m_appData.m_projOrtho ? 1.0f : Length(_position - m_appData.m_viewOrigin);
return m_appData.m_projScaleY * d * (_pixels / m_appData.m_viewportSize.y);
}
float Context::worldSizeToPixels(const Vec3& _position, float _size)
{
float d = m_appData.m_projOrtho ? 1.0f : Length(_position - m_appData.m_viewOrigin);
return (_size * m_appData.m_viewportSize.y) / d / m_appData.m_projScaleY;
}
int Context::estimateLevelOfDetail(const Vec3& _position, float _worldSize, int _min, int _max)
{
if (m_appData.m_projOrtho)
{
return _max;
}
float d = Length(_position - m_appData.m_viewOrigin);
float x = Clamp(2.0f * atanf(_worldSize / (2.0f * d)), 0.0f, 1.0f);
float fmin = (float)_min;
float fmax = (float)_max;
return (int)(fmin + (fmax - fmin) * x);
}
bool Context::gizmoAxisTranslation_Behavior(Id _id, const Vec3& _origin, const Vec3& _axis, float _snap, float _worldHeight, float _worldSize, Vec3* _out_)
{
if (_id != m_hotId)
{
// disable behavior when aligned
Vec3 viewDir = m_appData.m_projOrtho
? m_appData.m_viewDirection
: Normalize(m_appData.m_viewOrigin - _origin)
;
float aligned = 1.0f - fabs(Dot(_axis, viewDir));
if (aligned < 0.01f)
{
return false;
}
}
Ray ray(m_appData.m_cursorRayOrigin, m_appData.m_cursorRayDirection);
Line axisLine(_origin, _axis);
Capsule axisCapsule(_origin + _axis * (0.2f * _worldHeight), _origin + _axis * _worldHeight, _worldSize);
#if IM3D_GIZMO_DEBUG
if (_id == m_hotId)
{
PushDrawState();
EnableSorting(false);
SetColor(Color_Magenta);
SetAlpha(1.0f);
DrawCapsule(axisCapsule.m_start, axisCapsule.m_end, axisCapsule.m_radius);
PopDrawState();
}
#endif
Vec3& storedPosition = m_gizmoStateVec3;
if (_id == m_activeId)
{
if (isKeyDown(Action_Select))
{
float tr, tl;
Nearest(ray, axisLine, tr, tl);
#if IM3D_RELATIVE_SNAP
*_out_ = *_out_ + Snap(_axis * tl - storedPosition, _snap);
#else
*_out_ = Snap(*_out_ + _axis * tl - storedPosition, _snap);
#endif
return true;
}
else
{
makeActive(Id_Invalid);
}
}
else if (_id == m_hotId)
{
if (Intersects(ray, axisCapsule))
{
if (isKeyDown(Action_Select))
{
makeActive(_id);
float tr, tl;
Nearest(ray, axisLine, tr, tl);
storedPosition = _axis * tl;
}
}
else
{
resetId();
}
}
else
{
float t0, t1;
bool intersects = Intersect(ray, axisCapsule, t0, t1);
makeHot(_id, t0, intersects);
}
return false;
}
void Context::gizmoAxisTranslation_Draw(Id _id, const Vec3& _origin, const Vec3& _axis, float _worldHeight, float _worldSize, Color _color)
{
Vec3 viewDir = m_appData.m_projOrtho
? m_appData.m_viewDirection
: Normalize(m_appData.m_viewOrigin - _origin)
;
float aligned = 1.0f - fabs(Dot(_axis, viewDir));
aligned = Remap(aligned, 0.05f, 0.1f);
Color color = _color;
if (_id == m_activeId)
{
color = Color_GizmoHighlight;
pushEnableSorting(false);
begin(PrimitiveMode_Lines);
vertex(_origin - _axis * 999.0f, m_gizmoSizePixels * 0.5f, _color);
vertex(_origin + _axis * 999.0f, m_gizmoSizePixels * 0.5f, _color);
end();
popEnableSorting();
}
else if (_id == m_hotId)
{
color = Color_GizmoHighlight;
aligned = 1.0f;
}
color.setA(color.getA() * aligned);
pushColor(color);
pushSize(m_gizmoSizePixels);
DrawArrow(
_origin + _axis * (0.2f * _worldHeight),
_origin + _axis * _worldHeight
);
popSize();
popColor();
}
bool Context::gizmoPlaneTranslation_Behavior(Id _id, const Vec3& _origin, const Vec3& _normal, float _snap, float _worldSize, Vec3* _out_)
{
Ray ray(m_appData.m_cursorRayOrigin, m_appData.m_cursorRayDirection);
Plane plane(_normal, _origin);
#if IM3D_GIZMO_DEBUG
if (_id == m_hotId)
{
PushDrawState();
EnableSorting(false);
SetColor(Color_Magenta);
SetAlpha(0.1f);
DrawQuadFilled(_origin, _normal, Vec2(2.0f));
SetAlpha(0.75f);
SetSize(1.0f);
DrawQuad(_origin, _normal, Vec2(2.0f));
SetSize(2.0f);
DrawCircle(_origin, _normal, 2.0f);
PopDrawState();
}
#endif
float tr;
bool intersects = Intersect(ray, plane, tr);
if (!intersects)
{
return false;
}
Vec3 intersection = ray.m_origin + ray.m_direction * tr;
intersects &= AllLess(Abs(intersection - _origin), Vec3(_worldSize));
Vec3& storedPosition = m_gizmoStateVec3;
if (_id == m_activeId)
{
if (isKeyDown(Action_Select))
{
#if IM3D_RELATIVE_SNAP
intersection = Snap(intersection, plane, _snap);
*_out_ = intersection + storedPosition;
#else
*_out_ = Snap(intersection + storedPosition, plane, _snap);
#endif
return true;
}
else
{
makeActive(Id_Invalid);
}
}
else if (_id == m_hotId)
{
if (intersects)
{
if (isKeyDown(Action_Select))
{
makeActive(_id);
storedPosition = *_out_ - intersection;
}
}
else
{
resetId();
}
}
else
{
makeHot(_id, tr, intersects);
}
return false;
}
void Context::gizmoPlaneTranslation_Draw(Id _id, const Vec3& _origin, const Vec3& _normal, float _worldSize, Color _color)
{
Vec3 viewDir = m_appData.m_projOrtho
? m_appData.m_viewDirection
: Normalize(m_appData.m_viewOrigin - _origin)
;
Vec3 n = Mat3(m_matrixStack.back()) * _normal; // _normal may be in local space, need to transform to world space for the dot with viewDir to make sense
float aligned = fabs(Dot(n, viewDir));
aligned = Remap(aligned, 0.1f, 0.2f);
Color color = _color;
color.setA(color.getA() * aligned);
pushColor(color);
pushAlpha(_id == m_hotId ? 0.7f : 0.1f * getAlpha());
DrawQuadFilled(_origin, _normal, Vec2(_worldSize));
popAlpha();
DrawQuad(_origin, _normal, Vec2(_worldSize));
popColor();
}
bool Context::gizmoAxislAngle_Behavior(Id _id, const Vec3& _origin, const Vec3& _axis, float _snap, float _worldRadius, float _worldSize, float* _out_)
{
Vec3 viewDir = m_appData.m_projOrtho
? m_appData.m_viewDirection
: Normalize(m_appData.m_viewOrigin - _origin)
;
float aligned = fabs(Dot(_axis, viewDir));
float tr = 0.0f;
Ray ray(m_appData.m_cursorRayOrigin, m_appData.m_cursorRayDirection);
bool intersects = false;
Vec3 intersection;
if (aligned < 0.05f)
{
// ray-plane intersection fails at grazing angles, use capsule interesection
float t1;
Vec3 capsuleAxis = Cross(viewDir, _axis);
Capsule capsule(_origin + capsuleAxis * _worldRadius, _origin - capsuleAxis * _worldRadius, _worldSize * 0.5f);
intersects = Intersect(ray, capsule, tr, t1);
intersection = ray.m_origin + ray.m_direction * tr;
#if IM3D_GIZMO_DEBUG
if (_id == m_hotId)
{
PushDrawState();
SetColor(Im3d::Color_Magenta);
SetSize(3.0f);
DrawCapsule(capsule.m_start, capsule.m_end, capsule.m_radius);
PopDrawState();
}
#endif
}
else
{
Plane plane(_axis, _origin);
intersects = Intersect(ray, plane, tr);
intersection = ray.m_origin + ray.m_direction * tr;
float dist = Length(intersection - _origin);
intersects &= fabs(dist - _worldRadius) < (_worldSize + _worldSize * (1.0f - aligned) * 2.0f);
}
Vec3& storedVec = m_gizmoStateVec3;
float& storedAngle = m_gizmoStateFloat;
bool ret = false;
// use a view-aligned plane intersection to generate the rotation delta
Plane viewPlane(viewDir, _origin);
Intersect(ray, viewPlane, tr);
intersection = ray.m_origin + ray.m_direction * tr;
if (_id == m_activeId)
{
if (isKeyDown(Action_Select))
{
Vec3 delta = Normalize(intersection - _origin);
float sign = Dot(Cross(storedVec, delta), _axis);
float angle = acosf(Clamp(Dot(delta, storedVec), -1.0f, 1.0f));
#if IM3D_RELATIVE_SNAP
*_out_ = storedAngle + copysignf(Snap(angle, _snap), sign);
#else
*_out_ = Snap(storedAngle + copysignf(angle, sign), _snap);
#endif
return true;
}
else
{
makeActive(Id_Invalid);
}
}
else if (_id == m_hotId)
{
if (intersects)
{
if (isKeyDown(Action_Select))
{
makeActive(_id);
storedVec = Normalize(intersection - _origin);
storedAngle = Snap(*_out_, m_appData.m_snapRotation);
}
}
else
{
resetId();
}
}
else
{
makeHot(_id, tr, intersects);
}
return false;
}
void Context::gizmoAxislAngle_Draw(Id _id, const Vec3& _origin, const Vec3& _axis, float _worldRadius, float _angle, Color _color, float _minAlpha)
{
Vec3 viewDir = m_appData.m_projOrtho
? m_appData.m_viewDirection
: Normalize(m_appData.m_viewOrigin - _origin)
;
float aligned = fabs(Dot(_axis, viewDir));
Vec3& storedVec = m_gizmoStateVec3;
Color color = _color;
if (_id == m_activeId)
{
color = Color_GizmoHighlight;
Ray ray(m_appData.m_cursorRayOrigin, m_appData.m_cursorRayDirection);
Plane plane(_axis, _origin);
float tr;
if (Intersect(ray, plane, tr))
{
Vec3 intersection = ray.m_origin + ray.m_direction * tr;
Vec3 delta = Normalize(intersection - _origin);
pushAlpha(Max(_minAlpha, Remap(aligned, 1.0f, 0.99f)));
pushEnableSorting(false);
begin(PrimitiveMode_Lines);
vertex(_origin - _axis * 999.0f, m_gizmoSizePixels * 0.5f, _color);
vertex(_origin + _axis * 999.0f, m_gizmoSizePixels * 0.5f, _color);
//vertex(_origin, m_gizmoSizePixels * 0.5f, Color_GizmoHighlight);
//vertex(_origin + storedVec * _worldRadius, m_gizmoSizePixels * 0.5f, Color_GizmoHighlight);
end();
popEnableSorting();
popAlpha();
pushColor(Color_GizmoHighlight);
pushSize(m_gizmoSizePixels);
DrawArrow(_origin, _origin + delta * _worldRadius);
popSize();
popColor();
begin(PrimitiveMode_Points);
vertex(_origin, m_gizmoSizePixels * 2.0f, Color_GizmoHighlight);
end();
}
}
else if (_id == m_hotId)
{
color = Color_GizmoHighlight;
}
aligned = Max(Remap(aligned, 0.9f, 1.0f), 0.1f);
if (m_activeId == _id)
{
aligned = 1.0f;
}
pushColor(color);
pushSize(m_gizmoSizePixels);
pushMatrix(getMatrix() * LookAt(_origin, _origin + _axis, m_appData.m_worldUp));
begin(PrimitiveMode_LineLoop);
const int detail = estimateLevelOfDetail(_origin, _worldRadius, 32, 128);
for (int i = 0; i < detail; ++i)
{
float rad = TwoPi * ((float)i / (float)detail);
vertex(Vec3(cosf(rad) * _worldRadius, sinf(rad) * _worldRadius, 0.0f));
// post-modify the alpha for parts of the ring occluded by the sphere
VertexData& vd = getCurrentVertexList()->back();
Vec3 v = vd.m_positionSize;
float d = Dot(Normalize(_origin - v), m_appData.m_viewDirection);
d = Max(_minAlpha, Max(Remap(d, 0.1f, 0.2f), aligned));
vd.m_color.setA(vd.m_color.getA() * d);
}
end();
popMatrix();
popSize();
popColor();
}
bool Context::gizmoAxisScale_Behavior(Id _id, const Vec3& _origin, const Vec3& _axis, float _snap, float _worldHeight, float _worldSize, float *_out_)
{
Ray ray(m_appData.m_cursorRayOrigin, m_appData.m_cursorRayDirection);
Line axisLine(_origin, _axis);
Capsule axisCapsule(_origin + _axis * (0.2f * _worldHeight), _origin + _axis * _worldHeight, _worldSize);
#if IM3D_GIZMO_DEBUG
if (_id == m_hotId)
{
PushDrawState();
EnableSorting(false);
SetColor(Color_Magenta);
SetAlpha(1.0f);
DrawCapsule(axisCapsule.m_start, axisCapsule.m_end, axisCapsule.m_radius);
PopDrawState();
}
#endif
Vec3& storedPosition = m_gizmoStateVec3;
float& storedScale = m_gizmoStateFloat;
if (_id == m_activeId)
{
if (isKeyDown(Action_Select))
{
float tr, tl;
Nearest(ray, axisLine, tr, tl);
Vec3 intersection = _axis * tl;
Vec3 delta = intersection - storedPosition;
float sign = Dot(delta, _axis);
#if 1
// relative snap
float scale = Snap(Length(delta) / _worldHeight, _snap);
*_out_ = storedScale * Max(1.0f + copysignf(scale, sign), 1e-3f);
#else
// absolute snap
float scale = Length(delta) / _worldHeight;
*_out_ = Max(Snap(storedScale * (1.0f + copysignf(scale, sign)), _snap), 1e-3f);
#endif
return true;
}
else
{
makeActive(Id_Invalid);
}
}
else if (_id == m_hotId)
{
if (Intersects(ray, axisCapsule))
{
if (isKeyDown(Action_Select))
{
makeActive(_id);
float tr, tl;
Nearest(ray, axisLine, tr, tl);
storedPosition = _axis * tl;
storedScale = *_out_;
}
}
else
{
resetId();
}
}
else
{
float t0, t1;
bool intersects = Intersect(ray, axisCapsule, t0, t1);
makeHot(_id, t0, intersects);
}
return false;
}
void Context::gizmoAxisScale_Draw(Id _id, const Vec3& _origin, const Vec3& _axis, float _worldHeight, float _worldSize, Color _color)
{
Vec3 viewDir = m_appData.m_projOrtho
? m_appData.m_viewDirection
: Normalize(m_appData.m_viewOrigin - _origin)
;
float aligned = 1.0f - fabs(Dot(_axis, viewDir));
aligned = Remap(aligned, 0.05f, 0.1f);
Color color = _color;
if (_id == m_activeId)
{
color = Color_GizmoHighlight;
pushEnableSorting(false);
begin(PrimitiveMode_Lines);
vertex(_origin - _axis * 999.0f, m_gizmoSizePixels * 0.5f, _color);
vertex(_origin + _axis * 999.0f, m_gizmoSizePixels * 0.5f, _color);
end();
popEnableSorting();
}
else if (_id == m_hotId)
{
color = Color_GizmoHighlight;
aligned = 1.0f;
}
color.setA(color.getA() * aligned);
begin(PrimitiveMode_LineLoop);
vertex(_origin + _axis * (0.2f * _worldHeight), m_gizmoSizePixels, color);
vertex(_origin + _axis * _worldHeight, m_gizmoSizePixels, color);
end();
begin(PrimitiveMode_Points);
vertex(_origin + _axis * _worldHeight, m_gizmoSizePixels * 2.0f, color);
end();
}
bool Context::makeHot(Id _id, float _depth, bool _intersects)
{
if (m_activeId == Id_Invalid && _depth < m_hotDepth && _intersects && !isKeyDown(Action_Select))
{
m_hotId = _id;
m_appHotId = m_appId;
m_hotDepth = _depth;
return true;
}
return false;
}
void Context::makeActive(Id _id)
{
m_activeId = _id;
m_appActiveId = _id == Id_Invalid ? Id_Invalid : m_appId;
}
void Context::resetId()
{
m_activeId = m_hotId = m_appActiveId = m_appHotId = Id_Invalid;
m_hotDepth = FLT_MAX;
}
U32 Context::getPrimitiveCount(DrawPrimitiveType _type) const
{
U32 ret = 0;
for (U32 i = 0; i < m_layerIdMap.size(); ++i)
{
U32 j = i * DrawPrimitive_Count + _type;
ret += m_vertexData[0][j]->size() + m_vertexData[1][j]->size();
}
ret /= VertsPerDrawPrimitive[_type];
return ret;
}
U32 Context::getTextCount() const
{
U32 ret = 0;
for (U32 i = 0; i < m_layerIdMap.size(); ++i)
{
ret += m_textData[i]->size();
}
return ret;
}
/******************************************************************************
im3d_math
******************************************************************************/
// Vec3
Vec3::Vec3(const Vec4& _v)
: x(_v.x)
, y(_v.y)
, z(_v.z)
{
}
// Vec4
Vec4::Vec4(Color _rgba)
: x(_rgba.getR())
, y(_rgba.getG())
, z(_rgba.getB())
, w(_rgba.getA())
{
}
// Mat3
Mat3::Mat3(float _diagonal)
{
(*this)(0, 0) = _diagonal; (*this)(0, 1) = 0.0f; (*this)(0, 2) = 0.0f;
(*this)(1, 0) = 0.0f; (*this)(1, 1) = _diagonal; (*this)(1, 2) = 0.0f;
(*this)(2, 0) = 0.0f; (*this)(2, 1) = 0.0f; (*this)(2, 2) = _diagonal;
}
Mat3::Mat3(
float m00, float m01, float m02,
float m10, float m11, float m12,
float m20, float m21, float m22
)
{
(*this)(0, 0) = m00; (*this)(0, 1) = m01; (*this)(0, 2) = m02;
(*this)(1, 0) = m10; (*this)(1, 1) = m11; (*this)(1, 2) = m12;
(*this)(2, 0) = m20; (*this)(2, 1) = m21; (*this)(2, 2) = m22;
}
Mat3::Mat3(const Vec3& _colX, const Vec3& _colY, const Vec3& _colZ)
{
(*this)(0, 0) = _colX.x; (*this)(0, 1) = _colY.x; (*this)(0, 2) = _colZ.x;
(*this)(1, 0) = _colX.y; (*this)(1, 1) = _colY.y; (*this)(1, 2) = _colZ.y;
(*this)(2, 0) = _colX.z; (*this)(2, 1) = _colY.z; (*this)(2, 2) = _colZ.z;
}
Mat3::Mat3(const Mat4& _mat4)
{
(*this)(0, 0) = _mat4(0, 0); (*this)(0, 1) = _mat4(0, 1); (*this)(0, 2) = _mat4(0, 2);
(*this)(1, 0) = _mat4(1, 0); (*this)(1, 1) = _mat4(1, 1); (*this)(1, 2) = _mat4(1, 2);
(*this)(2, 0) = _mat4(2, 0); (*this)(2, 1) = _mat4(2, 1); (*this)(2, 2) = _mat4(2, 2);
}
Vec3 Mat3::getCol(int _i) const
{
return Vec3((*this)(0, _i), (*this)(1, _i), (*this)(2, _i));
}
Vec3 Mat3::getRow(int _i) const
{
return Vec3((*this)(_i, 0), (*this)(_i, 1), (*this)(_i, 2));
}
void Mat3::setCol(int _i, const Vec3& _v)
{
(*this)(0, _i) = _v.x;
(*this)(1, _i) = _v.y;
(*this)(2, _i) = _v.z;
}
void Mat3::setRow(int _i, const Vec3& _v)
{
(*this)(_i, 0) = _v.x;
(*this)(_i, 1) = _v.y;
(*this)(_i, 2) = _v.z;
}
Vec3 Mat3::getScale() const
{
return Vec3(Length(getCol(0)), Length(getCol(1)), Length(getCol(2)));
}
void Mat3::setScale(const Vec3& _scale)
{
Vec3 scale = _scale / getScale();
setCol(0, getCol(0) * scale.x);
setCol(1, getCol(1) * scale.y);
setCol(2, getCol(2) * scale.z);
}
Vec3 Im3d::ToEulerXYZ(const Mat3& _m)
{
// http://www.staff.city.ac.uk/~sbbh653/publications/euler.pdf
Vec3 ret;
if_likely (fabs(_m(2, 0)) < 1.0f)
{
ret.y = -asinf(_m(2, 0));
float c = 1.0f / cosf(ret.y);
ret.x = atan2f(_m(2, 1) * c, _m(2, 2) * c);
ret.z = atan2f(_m(1, 0) * c, _m(0, 0) * c);
}
else
{
ret.z = 0.0f;
if (!(_m(2, 0) > -1.0f))
{
ret.x = ret.z + atan2f(_m(0, 1), _m(0, 2));
ret.y = HalfPi;
}
else
{
ret.x = -ret.z + atan2f(-_m(0, 1), -_m(0, 2));
ret.y = -HalfPi;
}
}
return ret;
}
Mat3 Im3d::FromEulerXYZ(Vec3& _euler)
{
float cx = cosf(_euler.x);
float sx = sinf(_euler.x);
float cy = cosf(_euler.y);
float sy = sinf(_euler.y);
float cz = cosf(_euler.z);
float sz = sinf(_euler.z);
return Mat3(
cy * cz, sz * sy * cz - cx * sz, cx * sy * cz + sx * sz,
cy * sz, sx * sy * sz + cx * cz, cx * sy * sz - sx * cz,
-sz, sx * cy, cx * cy
);
}
Mat3 Im3d::Transpose(const Mat3& _m)
{
return Mat3(
_m(0, 0), _m(1, 0), _m(2, 0),
_m(0, 1), _m(1, 1), _m(2, 1),
_m(0, 2), _m(1, 2), _m(2, 2)
);
}
Mat3 Im3d::Rotation(const Vec3& _axis, float _rads)
{
float c = cosf(_rads);
float rc = 1.0f - c;
float s = sinf(_rads);
return Mat3(
_axis.x * _axis.x + (1.0f - _axis.x * _axis.x) * c, _axis.x * _axis.y * rc - _axis.z * s, _axis.x * _axis.z * rc + _axis.y * s,
_axis.x * _axis.y * rc + _axis.z * s, _axis.y * _axis.y + (1.0f - _axis.y * _axis.y) * c, _axis.y * _axis.z * rc - _axis.x * s,
_axis.x * _axis.z * rc - _axis.y * s, _axis.y * _axis.z * rc + _axis.x * s, _axis.z * _axis.z + (1.0f - _axis.z * _axis.z) * c
);
}
Mat3 Im3d::Scale(const Vec3& _s)
{
return Mat3(
_s.x, 0.0f, 0.0f,
0.0f, _s.y, 0.0f,
0.0f, 0.0f, _s.z
);
}
// Mat4
Mat4::Mat4(float _diagonal)
{
(*this)(0, 0) = _diagonal; (*this)(0, 1) = 0.0f; (*this)(0, 2) = 0.0f; (*this)(0, 3) = 0.0f;
(*this)(1, 0) = 0.0f; (*this)(1, 1) = _diagonal; (*this)(1, 2) = 0.0f; (*this)(1, 3) = 0.0f;
(*this)(2, 0) = 0.0f; (*this)(2, 1) = 0.0f; (*this)(2, 2) = _diagonal; (*this)(2, 3) = 0.0f;
(*this)(3, 0) = 0.0f; (*this)(3, 1) = 0.0f; (*this)(3, 2) = 0.0f; (*this)(3, 3) = _diagonal;
}
Mat4::Mat4(
float m00, float m01, float m02, float m03,
float m10, float m11, float m12, float m13,
float m20, float m21, float m22, float m23,
float m30, float m31, float m32, float m33
)
{
(*this)(0, 0) = m00; (*this)(0, 1) = m01; (*this)(0, 2) = m02; (*this)(0, 3) = m03;
(*this)(1, 0) = m10; (*this)(1, 1) = m11; (*this)(1, 2) = m12; (*this)(1, 3) = m13;
(*this)(2, 0) = m20; (*this)(2, 1) = m21; (*this)(2, 2) = m22; (*this)(2, 3) = m23;
(*this)(3, 0) = m30; (*this)(3, 1) = m31; (*this)(3, 2) = m32; (*this)(3, 3) = m33;
}
Mat4::Mat4(const Mat3& _mat3)
{
(*this)(0, 0) = _mat3(0, 0); (*this)(0, 1) = _mat3(0, 1); (*this)(0, 2) = _mat3(0, 2); (*this)(0, 3) = 0.0f;
(*this)(1, 0) = _mat3(1, 0); (*this)(1, 1) = _mat3(1, 1); (*this)(1, 2) = _mat3(1, 2); (*this)(1, 3) = 0.0f;
(*this)(2, 0) = _mat3(2, 0); (*this)(2, 1) = _mat3(2, 1); (*this)(2, 2) = _mat3(2, 2); (*this)(2, 3) = 0.0f;
(*this)(3, 0) = 0.0f; (*this)(3, 1) = 0.0f; (*this)(3, 2) = 0.0f; (*this)(3, 3) = 1.0f;
}
Mat4::Mat4(const Vec3& _translation, const Mat3& _rotation, const Vec3& _scale)
{
setCol(0, Vec4(_rotation.getCol(0) * _scale.x, 0.0f));
setCol(1, Vec4(_rotation.getCol(1) * _scale.y, 0.0f));
setCol(2, Vec4(_rotation.getCol(2) * _scale.z, 0.0f));
setCol(3, Vec4(_translation, 1.0f));
}
Vec4 Mat4::getCol(int _i) const
{
return Vec4((*this)(0, _i), (*this)(1, _i), (*this)(2, _i), (*this)(3, _i));
}
Vec4 Mat4::getRow(int _i) const
{
return Vec4((*this)(_i, 0), (*this)(_i, 1), (*this)(_i, 2), (*this)(_i, 3));
}
void Mat4::setCol(int _i, const Vec4& _v)
{
(*this)(0, _i) = _v.x;
(*this)(1, _i) = _v.y;
(*this)(2, _i) = _v.z;
(*this)(3, _i) = _v.w;
}
void Mat4::setRow(int _i, const Vec4& _v)
{
(*this)(_i, 0) = _v.x;
(*this)(_i, 1) = _v.y;
(*this)(_i, 2) = _v.z;
(*this)(_i, 3) = _v.w;
}
Vec3 Mat4::getTranslation() const
{
return Vec3((*this)(0, 3), (*this)(1, 3), (*this)(2, 3));
}
void Mat4::setTranslation(const Vec3& _translation)
{
(*this)(0, 3) = _translation.x;
(*this)(1, 3) = _translation.y;
(*this)(2, 3) = _translation.z;
}
Mat3 Mat4::getRotation() const
{
Mat3 ret(*this);
ret.setCol(0, Normalize(ret.getCol(0)));
ret.setCol(1, Normalize(ret.getCol(1)));
ret.setCol(2, Normalize(ret.getCol(2)));
return ret;
}
void Mat4::setRotation(const Mat3& _rotation)
{
Vec3 scale = getScale();
setCol(0, Vec4(_rotation.getCol(0) * scale.x, 0.0f));
setCol(1, Vec4(_rotation.getCol(1) * scale.y, 0.0f));
setCol(2, Vec4(_rotation.getCol(2) * scale.z, 0.0f));
}
Vec3 Mat4::getScale() const
{
return Vec3(Length(getCol(0)), Length(getCol(1)), Length(getCol(2)));
}
void Mat4::setScale(const Vec3& _scale)
{
Vec3 scale = _scale / getScale();
setCol(0, getCol(0) * scale.x);
setCol(1, getCol(1) * scale.y);
setCol(2, getCol(2) * scale.z);
}
inline static float Determinant(const Mat4& _m)
{
return
_m(0, 3) * _m(1, 2) * _m(2, 1) * _m(3, 0) - _m(0, 2) * _m(1, 3) * _m(2, 1) * _m(3, 0) - _m(0, 3) * _m(1, 1) * _m(2, 2) * _m(3, 0) + _m(0, 1) * _m(1, 3) * _m(2, 2) * _m(3, 0) +
_m(0, 2) * _m(1, 1) * _m(2, 3) * _m(3, 0) - _m(0, 1) * _m(1, 2) * _m(2, 3) * _m(3, 0) - _m(0, 3) * _m(1, 2) * _m(2, 0) * _m(3, 1) + _m(0, 2) * _m(1, 3) * _m(2, 0) * _m(3, 1) +
_m(0, 3) * _m(1, 0) * _m(2, 2) * _m(3, 1) - _m(0, 0) * _m(1, 3) * _m(2, 2) * _m(3, 1) - _m(0, 2) * _m(1, 0) * _m(2, 3) * _m(3, 1) + _m(0, 0) * _m(1, 2) * _m(2, 3) * _m(3, 1) +
_m(0, 3) * _m(1, 1) * _m(2, 0) * _m(3, 2) - _m(0, 1) * _m(1, 3) * _m(2, 0) * _m(3, 2) - _m(0, 3) * _m(1, 0) * _m(2, 1) * _m(3, 2) + _m(0, 0) * _m(1, 3) * _m(2, 1) * _m(3, 2) +
_m(0, 1) * _m(1, 0) * _m(2, 3) * _m(3, 2) - _m(0, 0) * _m(1, 1) * _m(2, 3) * _m(3, 2) - _m(0, 2) * _m(1, 1) * _m(2, 0) * _m(3, 3) + _m(0, 1) * _m(1, 2) * _m(2, 0) * _m(3, 3) +
_m(0, 2) * _m(1, 0) * _m(2, 1) * _m(3, 3) - _m(0, 0) * _m(1, 2) * _m(2, 1) * _m(3, 3) - _m(0, 1) * _m(1, 0) * _m(2, 2) * _m(3, 3) + _m(0, 0) * _m(1, 1) * _m(2, 2) * _m(3, 3)
;
}
Mat4 Im3d::Inverse(const Mat4& _m)
{
Mat4 ret;
ret(0, 0) = _m(1, 2) * _m(2, 3) * _m(3, 1) - _m(1, 3) * _m(2, 2) * _m(3, 1) + _m(1, 3) * _m(2, 1) * _m(3, 2) - _m(1, 1) * _m(2, 3) * _m(3, 2) - _m(1, 2) * _m(2, 1) * _m(3, 3) + _m(1, 1) * _m(2, 2) * _m(3, 3);
ret(0, 1) = _m(0, 3) * _m(2, 2) * _m(3, 1) - _m(0, 2) * _m(2, 3) * _m(3, 1) - _m(0, 3) * _m(2, 1) * _m(3, 2) + _m(0, 1) * _m(2, 3) * _m(3, 2) + _m(0, 2) * _m(2, 1) * _m(3, 3) - _m(0, 1) * _m(2, 2) * _m(3, 3);
ret(0, 2) = _m(0, 2) * _m(1, 3) * _m(3, 1) - _m(0, 3) * _m(1, 2) * _m(3, 1) + _m(0, 3) * _m(1, 1) * _m(3, 2) - _m(0, 1) * _m(1, 3) * _m(3, 2) - _m(0, 2) * _m(1, 1) * _m(3, 3) + _m(0, 1) * _m(1, 2) * _m(3, 3);
ret(0, 3) = _m(0, 3) * _m(1, 2) * _m(2, 1) - _m(0, 2) * _m(1, 3) * _m(2, 1) - _m(0, 3) * _m(1, 1) * _m(2, 2) + _m(0, 1) * _m(1, 3) * _m(2, 2) + _m(0, 2) * _m(1, 1) * _m(2, 3) - _m(0, 1) * _m(1, 2) * _m(2, 3);
ret(1, 0) = _m(1, 3) * _m(2, 2) * _m(3, 0) - _m(1, 2) * _m(2, 3) * _m(3, 0) - _m(1, 3) * _m(2, 0) * _m(3, 2) + _m(1, 0) * _m(2, 3) * _m(3, 2) + _m(1, 2) * _m(2, 0) * _m(3, 3) - _m(1, 0) * _m(2, 2) * _m(3, 3);
ret(1, 1) = _m(0, 2) * _m(2, 3) * _m(3, 0) - _m(0, 3) * _m(2, 2) * _m(3, 0) + _m(0, 3) * _m(2, 0) * _m(3, 2) - _m(0, 0) * _m(2, 3) * _m(3, 2) - _m(0, 2) * _m(2, 0) * _m(3, 3) + _m(0, 0) * _m(2, 2) * _m(3, 3);
ret(1, 2) = _m(0, 3) * _m(1, 2) * _m(3, 0) - _m(0, 2) * _m(1, 3) * _m(3, 0) - _m(0, 3) * _m(1, 0) * _m(3, 2) + _m(0, 0) * _m(1, 3) * _m(3, 2) + _m(0, 2) * _m(1, 0) * _m(3, 3) - _m(0, 0) * _m(1, 2) * _m(3, 3);
ret(1, 3) = _m(0, 2) * _m(1, 3) * _m(2, 0) - _m(0, 3) * _m(1, 2) * _m(2, 0) + _m(0, 3) * _m(1, 0) * _m(2, 2) - _m(0, 0) * _m(1, 3) * _m(2, 2) - _m(0, 2) * _m(1, 0) * _m(2, 3) + _m(0, 0) * _m(1, 2) * _m(2, 3);
ret(2, 0) = _m(1, 1) * _m(2, 3) * _m(3, 0) - _m(1, 3) * _m(2, 1) * _m(3, 0) + _m(1, 3) * _m(2, 0) * _m(3, 1) - _m(1, 0) * _m(2, 3) * _m(3, 1) - _m(1, 1) * _m(2, 0) * _m(3, 3) + _m(1, 0) * _m(2, 1) * _m(3, 3);
ret(2, 1) = _m(0, 3) * _m(2, 1) * _m(3, 0) - _m(0, 1) * _m(2, 3) * _m(3, 0) - _m(0, 3) * _m(2, 0) * _m(3, 1) + _m(0, 0) * _m(2, 3) * _m(3, 1) + _m(0, 1) * _m(2, 0) * _m(3, 3) - _m(0, 0) * _m(2, 1) * _m(3, 3);
ret(2, 2) = _m(0, 1) * _m(1, 3) * _m(3, 0) - _m(0, 3) * _m(1, 1) * _m(3, 0) + _m(0, 3) * _m(1, 0) * _m(3, 1) - _m(0, 0) * _m(1, 3) * _m(3, 1) - _m(0, 1) * _m(1, 0) * _m(3, 3) + _m(0, 0) * _m(1, 1) * _m(3, 3);
ret(2, 3) = _m(0, 3) * _m(1, 1) * _m(2, 0) - _m(0, 1) * _m(1, 3) * _m(2, 0) - _m(0, 3) * _m(1, 0) * _m(2, 1) + _m(0, 0) * _m(1, 3) * _m(2, 1) + _m(0, 1) * _m(1, 0) * _m(2, 3) - _m(0, 0) * _m(1, 1) * _m(2, 3);
ret(3, 0) = _m(1, 2) * _m(2, 1) * _m(3, 0) - _m(1, 1) * _m(2, 2) * _m(3, 0) - _m(1, 2) * _m(2, 0) * _m(3, 1) + _m(1, 0) * _m(2, 2) * _m(3, 1) + _m(1, 1) * _m(2, 0) * _m(3, 2) - _m(1, 0) * _m(2, 1) * _m(3, 2);
ret(3, 1) = _m(0, 1) * _m(2, 2) * _m(3, 0) - _m(0, 2) * _m(2, 1) * _m(3, 0) + _m(0, 2) * _m(2, 0) * _m(3, 1) - _m(0, 0) * _m(2, 2) * _m(3, 1) - _m(0, 1) * _m(2, 0) * _m(3, 2) + _m(0, 0) * _m(2, 1) * _m(3, 2);
ret(3, 2) = _m(0, 2) * _m(1, 1) * _m(3, 0) - _m(0, 1) * _m(1, 2) * _m(3, 0) - _m(0, 2) * _m(1, 0) * _m(3, 1) + _m(0, 0) * _m(1, 2) * _m(3, 1) + _m(0, 1) * _m(1, 0) * _m(3, 2) - _m(0, 0) * _m(1, 1) * _m(3, 2);
ret(3, 3) = _m(0, 1) * _m(1, 2) * _m(2, 0) - _m(0, 2) * _m(1, 1) * _m(2, 0) + _m(0, 2) * _m(1, 0) * _m(2, 1) - _m(0, 0) * _m(1, 2) * _m(2, 1) - _m(0, 1) * _m(1, 0) * _m(2, 2) + _m(0, 0) * _m(1, 1) * _m(2, 2);
float det = 1.0f / Determinant(_m);
for (int i = 0; i < 16; ++i)
{
ret[i] *= det;
}
return ret;
}
Mat4 Im3d::Transpose(const Mat4& _m)
{
return Mat4(
_m(0, 0), _m(1, 0), _m(2, 0), _m(3, 0),
_m(0, 1), _m(1, 1), _m(2, 1), _m(3, 1),
_m(0, 2), _m(1, 2), _m(2, 2), _m(3, 2),
_m(0, 3), _m(1, 3), _m(2, 3), _m(3, 3)
);
}
Mat4 Im3d::Translation(const Vec3& _t)
{
return Mat4(
1.0f, 0.0f, 0.0f, _t.x,
0.0f, 1.0f, 0.0f, _t.y,
0.0f, 0.0f, 1.0f, _t.z,
0.0f, 0.0f, 0.0f, 1.0f
);
}
Mat4 Im3d::AlignZ(const Vec3& _axis, const Vec3& _up)
{
Vec3 x, y;
y = _up - _axis * Dot(_up, _axis);
float ylen = Length(y);
if_unlikely (ylen < FLT_EPSILON)
{
Vec3 k = Vec3(1.0f, 0.0f, 0.0f);
y = k - _axis * Dot(k, _axis);
ylen = Length(y);
if_unlikely (ylen < FLT_EPSILON)
{
k = Vec3(0.0f, 0.0f, 1.0f);
y = k - _axis * Dot(k, _axis);
ylen = Length(y);
}
}
y = y / ylen;
x = Cross(y, _axis);
return Mat4(
x.x, y.x, _axis.x, 0.0f,
x.y, y.y, _axis.y, 0.0f,
x.z, y.z, _axis.z, 0.0f
);
}
Mat4 Im3d::LookAt(const Vec3& _from, const Vec3& _to, const Vec3& _up)
{
Mat4 ret = AlignZ(Normalize(_to - _from), _up);
ret.setCol(3, Vec4(_from, 1.0f)); // inject translation
return ret;
}
// Geometry
Line::Line(const Vec3& _origin, const Vec3& _direction)
: m_origin(_origin)
, m_direction(_direction)
{
}
Ray::Ray(const Vec3& _origin, const Vec3& _direction)
: m_origin(_origin)
, m_direction(_direction)
{
}
LineSegment::LineSegment(const Vec3& _start, const Vec3& _end)
: m_start(_start)
, m_end(_end)
{
}
Sphere::Sphere(const Vec3& _origin, float _radius)
: m_origin(_origin)
, m_radius(_radius)
{
}
Plane::Plane(const Vec3& _normal, float _offset)
: m_normal(_normal)
, m_offset(_offset)
{
}
Plane::Plane(const Vec3& _normal, const Vec3& _origin)
: m_normal(_normal)
, m_offset(Dot(_normal, _origin))
{
}
Capsule::Capsule(const Vec3& _start, const Vec3& _end, float _radius)
: m_start(_start)
, m_end(_end)
, m_radius(_radius)
{
}
bool Im3d::Intersects(const Ray& _ray, const Plane& _plane)
{
float x = Dot(_plane.m_normal, _ray.m_direction);
return x <= 0.0f;
}
bool Im3d::Intersect(const Ray& _ray, const Plane& _plane, float& t0_)
{
t0_ = Dot(_plane.m_normal, (_plane.m_normal * _plane.m_offset) - _ray.m_origin) / Dot(_plane.m_normal, _ray.m_direction);
return t0_ >= 0.0f;
}
bool Im3d::Intersects(const Ray& _r, const Sphere& _s)
{
Vec3 p = _s.m_origin - _r.m_origin;
float p2 = Length2(p);
float q = Dot(p, _r.m_direction);
float r2 = _s.m_radius * _s.m_radius;
if (q < 0.0f && p2 > r2)
{
return false;
}
return p2 - (q * q) <= r2;
}
bool Im3d::Intersect(const Ray& _r, const Sphere& _s, float& t0_, float& t1_)
{
Vec3 p = _s.m_origin - _r.m_origin;
float q = Dot(p, _r.m_direction);
if (q < 0.0f)
{
return false;
}
float p2 = Length2(p) - q * q;
float r2 = _s.m_radius * _s.m_radius;
if (p2 > r2)
{
return false;
}
float s = sqrtf(r2 - p2);
t0_ = Max(q - s, 0.0f);
t1_ = q + s;
return true;
}
bool Im3d::Intersects(const Ray& _ray, const Capsule& _capsule)
{
return Distance2(_ray, LineSegment(_capsule.m_start, _capsule.m_end)) < _capsule.m_radius * _capsule.m_radius;
}
bool Im3d::Intersect(const Ray& _ray, const Capsule& _capsule, float& t0_, float& t1_)
{
//IM3D_ASSERT(false); // \todo implement
t0_ = t1_ = 0.0f;
return Intersects(_ray, _capsule);
}
void Im3d::Nearest(const Line& _line0, const Line& _line1, float& t0_, float& t1_)
{
Vec3 p = _line0.m_origin - _line1.m_origin;
float q = Dot(_line0.m_direction, _line1.m_direction);
float s = Dot(_line1.m_direction, p);
float d = 1.0f - q * q;
if (d < FLT_EPSILON) // lines are parallel
{
t0_ = 0.0f;
t1_ = s;
}
else
{
float r = Dot(_line0.m_direction, p);
t0_ = (q * s - r) / d;
t1_ = (s - q * r) / d;
}
}
void Im3d::Nearest(const Ray& _ray, const Line& _line, float& tr_, float& tl_)
{
Nearest(Line(_ray.m_origin, _ray.m_direction), _line, tr_, tl_);
tr_ = Max(tr_, 0.0f);
}
Vec3 Im3d::Nearest(const Ray& _ray, const LineSegment& _segment, float& tr_)
{
Vec3 ldir = _segment.m_end - _segment.m_start;
Vec3 p = _segment.m_start - _ray.m_origin;
float q = Length2(ldir);
float r = Dot(ldir, _ray.m_direction);
float s = Dot(ldir, p);
float t = Dot(_ray.m_direction, p);
float sn, sd, tn, td;
float denom = q - r * r;
if (denom < FLT_EPSILON)
{
sd = td = 1.0f;
sn = 0.0f;
tn = t;
}
else
{
sd = td = denom;
sn = r * t - s;
tn = q * t - r * s;
if (sn < 0.0f)
{
sn = 0.0f;
tn = t;
td = 1.0f;
}
else if (sn > sd)
{
sn = sd;
tn = t + r;
td = 1.0f;
}
}
float ts;
if (tn < 0.0f)
{
tr_ = 0.0f;
if (r >= 0.0f)
{
ts = 0.0f;
}
else if (s <= q)
{
ts = 1.0f;
}
else
{
ts = -s / q;
}
}
else
{
tr_ = tn / td;
ts = sn / sd;
}
return _segment.m_start + ldir * ts;
}
float Im3d::Distance2(const Ray& _ray, const LineSegment& _segment)
{
float tr;
Vec3 p = Nearest(_ray, _segment, tr);
return Length2(_ray.m_origin + _ray.m_direction * tr - p);
}
#define IM3D_STATIC_ASSERT(e) { (void)sizeof(char[(e) ? 1 : -1]); }
static void StaticAsserts()
{
IM3D_STATIC_ASSERT(sizeof (Vec2) == sizeof (float[2]));
IM3D_STATIC_ASSERT(alignof(Vec2) == alignof(float[2]));
IM3D_STATIC_ASSERT(sizeof (Vec3) == sizeof (float[3]));
IM3D_STATIC_ASSERT(alignof(Vec3) == alignof(float[3]));
IM3D_STATIC_ASSERT(sizeof (Vec4) == sizeof (float[4]));
IM3D_STATIC_ASSERT(alignof(Vec4) == alignof(float[4]));
IM3D_STATIC_ASSERT(sizeof (Mat3) == sizeof (float[9]));
IM3D_STATIC_ASSERT(alignof(Mat3) == alignof(float[9]));
IM3D_STATIC_ASSERT(sizeof (Mat4) == sizeof (float[16]));
IM3D_STATIC_ASSERT(alignof(Mat4) == alignof(float[16]));
}