Skip to content

Commit

Permalink
transform fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
nem0 committed Nov 17, 2024
1 parent ff4cc74 commit daaf11a
Show file tree
Hide file tree
Showing 16 changed files with 146 additions and 139 deletions.
1 change: 1 addition & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* [3rd party libraries](3rdparty.md)

# Engine
* [Transform](transform.md)
* [Job System](job_system.md)
* [Profiler](profiler.md)

Expand Down
14 changes: 14 additions & 0 deletions docs/transform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Transform

Various types of transforms are defined in [math.h](../src/core/math.h):

* `LocalTransform` - single precision position, quaternion rotation, and uniform scale.
* `LocalRigidTransform` - similar to `LocalTransform`, but without scale.
* `Transform` - double precision position, quaternion rotation, and nonuniform scale.
* `RigidTransform` - similar to `Transform`, but without scale.

"Local" indicates single precision position, while "Rigid" indicates the absence of scale.

## Entity transform

Entities utilize `Transform`. Note that `Transform` is not equivalent to a matrix. Its components are applied in **SRT** order: scale, rotation, position: `pos + rot.rotate(value * scale)`. Consequently, it cannot represent skew. Additionally, the composition of two transforms does not behave like matrix multiplication if the scale is nonuniform. The scale is "lossy", i.e. when composing multiple transforms, "direction" of the original scale is lost.
39 changes: 26 additions & 13 deletions src/core/math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -757,30 +757,43 @@ Transform::Transform(const DVec3& pos, const Quat& rot, Vec3 scale)
, rot(rot)
, scale(scale) {}

Transform Transform::operator*(const LocalRigidTransform& rhs) const { return {pos + rot.rotate(rhs.pos) * scale, rot * rhs.rot, scale}; }
Transform Transform::compose(const LocalRigidTransform& rhs) const { return {pos + rot.rotate(rhs.pos * scale), rot * rhs.rot, scale}; }

DVec3 Transform::transform(const Vec3& value) const { return pos + rot.rotate(value) * scale; }
DVec3 Transform::transform(const Vec3& value) const { return pos + rot.rotate(value * scale); }

DVec3 Transform::transform(const DVec3& value) const { return pos + rot.rotate(value) * scale; }
DVec3 Transform::invTransform(const DVec3& value) const {
return rot.conjugated().rotate(value - pos) / scale;
}

DVec3 Transform::invTransform(const Vec3& value) const {
return rot.conjugated().rotate(DVec3(value) - pos) / scale;
}

Vec3 Transform::transformVector(const Vec3& value) const { return rot.rotate(value) * scale; }
DVec3 Transform::transform(const DVec3& value) const { return pos + rot.rotate(value * scale); }

Vec3 Transform::transformVector(const Vec3& value) const { return rot.rotate(value * scale); }

Vec3 Transform::invTransformVector(const Vec3& value) const {
return rot.conjugated().rotate(value) / scale;
}

RigidTransform Transform::getRigidPart() const { return {pos, rot}; }

Transform Transform::operator*(const Transform& rhs) const {
Transform Transform::compose(const Transform& rhs) const {
return {
rot.rotate(rhs.pos) * scale + pos,
rot.rotate(rhs.pos * scale) + pos,
rot * rhs.rot,
scale * rhs.scale
};
}

Transform Transform::inverted() const {
Transform result;
result.rot = rot.conjugated();
result.pos = result.rot.rotate(-pos) / scale;
result.scale = Vec3(1.0f) / scale;
return result;
Transform Transform::computeLocal(const Transform& parent, const Transform& child) {
const DVec3 inv_parent_pos = parent.rot.conjugated().rotate(-parent.pos) / parent.scale;
return {
parent.rot.conjugated().rotate(child.pos) / parent.scale + inv_parent_pos,
parent.rot.conjugated() * child.rot,
child.scale / parent.scale
};
}

LocalTransform::LocalTransform(const Vec3& pos, const Quat& rot, float scale)
Expand All @@ -798,7 +811,7 @@ LocalTransform LocalTransform::inverted() const {
}

LocalTransform LocalTransform::operator*(const LocalTransform& rhs) const {
return {pos + rot.rotate(rhs.pos) * scale, rot * rhs.rot, scale};
return {pos + rot.rotate(rhs.pos * scale), rot * rhs.rot, scale};
}

LocalRigidTransform LocalRigidTransform::inverted() const {
Expand Down
20 changes: 15 additions & 5 deletions src/core/math.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ struct LUMIX_CORE_API RigidTransform {
};


// single precision position, uniform scale
struct LUMIX_CORE_API LocalTransform {
LocalTransform() {}
LocalTransform(const Vec3& pos, const Quat& rot, float scale);
Expand All @@ -297,17 +298,26 @@ struct LUMIX_CORE_API LocalTransform {
};


// double precision position, quaternion rotation, nonuniform scale
// this is NOT the same as Matrix
// * it behaves like a matrix only when the scale is uniform
// * when composing multiple transforms with nonuniform scale, there's no skew
// * it behaves like transforms in most other engines (e.g. Unreal)
// * scale is lossy, i.e. when composing multiple transforms, "direction" of the original scale is lost
struct LUMIX_CORE_API Transform {
Transform() {}
Transform(const DVec3& pos, const Quat& rot, Vec3 scale);

static Transform computeLocal(const Transform& parent, const Transform& child);

Transform inverted() const;

Transform operator*(const Transform& rhs) const;
Transform operator*(const LocalRigidTransform& rhs) const;
Transform compose(const Transform& rhs) const;
Transform compose(const LocalRigidTransform& rhs) const;
DVec3 transform(const DVec3& value) const;
DVec3 transform(const Vec3& value) const;
DVec3 invTransform(const DVec3& value) const;
DVec3 invTransform(const Vec3& value) const;
Vec3 transformVector(const Vec3& value) const;
DVec3 transform(const DVec3& value) const;
Vec3 invTransformVector(const Vec3& value) const;
RigidTransform getRigidPart() const;

DVec3 pos;
Expand Down
26 changes: 7 additions & 19 deletions src/editor/world_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3117,16 +3117,14 @@ struct PasteEntityCommand final : IEditorCommand {
}
m_editor.selectEntities(m_entities, false);

Transform base_tr;
base_tr.pos = m_position;
base_tr.scale = Vec3(1);
base_tr.rot = Quat(0, 0, 0, 1);
DVec3 diff_pos(0, 0, 0);
m_map.reserve(entity_count);
for (int i = 0; i < entity_count; ++i) {
EntityRef orig_e;
blob.read(orig_e);
if (!is_redo) m_map.insert(orig_e, i);
}

for (int i = 0; i < entity_count; ++i) {
Transform tr;
blob.read(tr);
Expand All @@ -3138,19 +3136,13 @@ struct PasteEntityCommand final : IEditorCommand {
auto iter = m_map.find(parent);
if (iter.isValid()) parent = m_entities[iter.value()];

if (!m_identity)
{
if (i == 0)
{
const Transform inv = tr.inverted();
base_tr.rot = tr.rot;
base_tr.scale = tr.scale;
base_tr = base_tr * inv;
if (!m_identity) {
if (i == 0) {
diff_pos = m_position - tr.pos;
tr.pos = m_position;
}
else
{
tr = base_tr * tr;
else {
tr.pos += diff_pos;
}
}

Expand Down Expand Up @@ -3195,10 +3187,6 @@ struct PasteEntityCommand final : IEditorCommand {
return false;
}


const Array<EntityRef>& getEntities() { return m_entities; }


private:
OutputMemoryStream m_copy_buffer;
WorldEditor& m_editor;
Expand Down
15 changes: 7 additions & 8 deletions src/engine/world.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,13 @@ void World::transformEntity(EntityRef entity, bool update_local)
const Transform my_transform = getTransform(entity);
if (update_local && h.parent.isValid()) {
const Transform parent_tr = getTransform((EntityRef)h.parent);
h.local_transform = (parent_tr.inverted() * my_transform);
h.local_transform = Transform::computeLocal(parent_tr, my_transform);
}

EntityPtr child = h.first_child;
while (child.isValid())
{
while (child.isValid()) {
const Hierarchy& child_h = m_hierarchy[m_entities[child.index].hierarchy];
const Transform abs_tr = my_transform * child_h.local_transform;
const Transform abs_tr = my_transform.compose(child_h.local_transform);
Transform& child_data = m_transforms[child.index];
child_data = abs_tr;
transformEntity((EntityRef)child, false);
Expand Down Expand Up @@ -320,15 +319,15 @@ void World::setTransformKeepChildren(EntityRef entity, const Transform& transfor
if (h.parent.isValid())
{
Transform parent_tr = getTransform((EntityRef)h.parent);
h.local_transform = parent_tr.inverted() * my_transform;
h.local_transform = Transform::computeLocal(parent_tr, my_transform);
}

EntityPtr child = h.first_child;
while (child.isValid())
{
Hierarchy& child_h = m_hierarchy[m_entities[child.index].hierarchy];

child_h.local_transform = my_transform.inverted() * getTransform((EntityRef)child);
child_h.local_transform = Transform::computeLocal(my_transform, getTransform((EntityRef)child));
child = child_h.next_sibling;
}
}
Expand Down Expand Up @@ -687,7 +686,7 @@ void World::setParent(EntityPtr new_parent, EntityRef child)
m_hierarchy[child_idx].parent = new_parent;
Transform parent_tr = getTransform((EntityRef)new_parent);
Transform child_tr = getTransform(child);
m_hierarchy[child_idx].local_transform = parent_tr.inverted() * child_tr;
m_hierarchy[child_idx].local_transform = Transform::computeLocal(parent_tr, child_tr);
m_hierarchy[child_idx].next_sibling = m_hierarchy[new_parent_idx].first_child;
m_hierarchy[new_parent_idx].first_child = child;
}
Expand All @@ -704,7 +703,7 @@ void World::updateGlobalTransform(EntityRef entity)
ASSERT(h.parent.isValid());
Transform parent_tr = getTransform((EntityRef)h.parent);

Transform new_tr = parent_tr * h.local_transform;
Transform new_tr = parent_tr.compose(h.local_transform);
setTransform(entity, new_tr);
}

Expand Down
41 changes: 19 additions & 22 deletions src/navigation/navigation_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ struct NavigationModuleImpl final : NavigationModule
const DVec3 agent_pos = m_world.getPosition(iter.key());
const dtCrowdAgent* dt_agent = zone.crowd->getAgent(agent.agent);
const Transform zone_tr = m_world.getTransform((EntityRef)agent.zone);
const Vec3 pos = Vec3(zone_tr.inverted().transform(agent_pos));
const Vec3 pos = Vec3(zone_tr.invTransform(agent_pos));
if (squaredLength(pos.xz() - (*(Vec3*)dt_agent->npos).xz()) > 0.1f) {
const Transform old_zone_tr = m_world.getTransform(zone.entity);
const DVec3 target_pos = old_zone_tr.transform(*(Vec3*)dt_agent->targetPos);
Expand Down Expand Up @@ -161,8 +161,7 @@ struct NavigationModuleImpl final : NavigationModule
}


void rasterizeTerrains(const Transform& zone_tr, const AABB& tile_aabb, rcContext& ctx, rcConfig& cfg, rcHeightfield& solid)
{
void rasterizeTerrains(const Transform& zone_tr, const AABB& tile_aabb, rcContext& ctx, rcConfig& cfg, rcHeightfield& solid) {
PROFILE_FUNCTION();
const float walkable_threshold = cosf(degreesToRadians(60));

Expand All @@ -172,10 +171,10 @@ struct NavigationModuleImpl final : NavigationModule
EntityPtr entity_ptr = render_module->getFirstTerrain();
while (entity_ptr.isValid()) {
const EntityRef entity = (EntityRef)entity_ptr;
const Transform terrain_tr = m_world.getTransform(entity);
const Transform to_zone = zone_tr.inverted() * terrain_tr;
const DVec3 terrain_pos = m_world.getPosition(entity);
const Transform to_zone = Transform::computeLocal(zone_tr, Transform(terrain_pos, Quat::IDENTITY, {1, 1, 1}));
const Transform to_terrain = Transform::computeLocal(Transform(terrain_pos, Quat::IDENTITY, {1, 1, 1}), zone_tr);
float scaleXZ = render_module->getTerrainXZScale(entity);
const Transform to_terrain = to_zone.inverted();
Matrix mtx = to_terrain.rot.toMatrix();
mtx.setTranslation(Vec3(to_terrain.pos));
AABB aabb = tile_aabb;
Expand Down Expand Up @@ -221,7 +220,7 @@ struct NavigationModuleImpl final : NavigationModule
LUMIX_FORCE_INLINE void rasterizeModel(Model* model
, const Transform& tr
, const AABB& zone_aabb
, const Transform& inv_zone_tr
, const Transform& zone_tr
, u32 no_navigation_flag
, u32 nonwalkable_flag
, rcContext& ctx
Expand All @@ -230,7 +229,7 @@ struct NavigationModuleImpl final : NavigationModule
ASSERT(model->isReady());

AABB model_aabb = model->getAABB();
const Transform rel_tr = inv_zone_tr * tr;
const Transform rel_tr = Transform::computeLocal(zone_tr, tr);
Matrix mtx = rel_tr.rot.toMatrix();
mtx.setTranslation(Vec3(rel_tr.pos));
mtx.multiply3x3(rel_tr.scale);
Expand Down Expand Up @@ -277,8 +276,6 @@ struct NavigationModuleImpl final : NavigationModule
{
PROFILE_FUNCTION();

const Transform inv_zone_tr = zone_tr.inverted();

auto render_module = static_cast<RenderModule*>(m_world.getModule("renderer"));
if (!render_module) return;

Expand All @@ -293,7 +290,7 @@ struct NavigationModuleImpl final : NavigationModule
if (!model) return;

const Transform tr = m_world.getTransform(entity);
rasterizeModel(model, tr, aabb, inv_zone_tr, no_navigation_flag, nonwalkable_flag, ctx, solid);
rasterizeModel(model, tr, aabb, zone_tr, no_navigation_flag, nonwalkable_flag, ctx, solid);
}

const HashMap<EntityRef, InstancedModel>& ims = render_module->getInstancedModels();
Expand Down Expand Up @@ -324,8 +321,8 @@ struct NavigationModuleImpl final : NavigationModule
tr.rot = Quat(i.rot_quat.x, i.rot_quat.y, i.rot_quat.z, 0);
tr.rot.w = sqrtf(1 - dot(i.rot_quat, i.rot_quat));
tr.scale = Vec3(i.scale);
tr = im_tr * tr;
rasterizeModel(im.model, tr, aabb, inv_zone_tr, no_navigation_flag, nonwalkable_flag, ctx, solid);
tr = im_tr.compose(tr);
rasterizeModel(im.model, tr, aabb, zone_tr, no_navigation_flag, nonwalkable_flag, ctx, solid);
}
}
}
Expand Down Expand Up @@ -432,7 +429,7 @@ struct NavigationModuleImpl final : NavigationModule
}
}
else {
*(Vec3*)dt_agent->npos = Vec3(zone_tr.inverted().transform(m_world.getPosition(agent.entity)));
*(Vec3*)dt_agent->npos = Vec3(zone_tr.invTransform(m_world.getPosition(agent.entity)));
}

if (dt_agent->ncorners == 0 && dt_agent->targetState != DT_CROWDAGENT_TARGET_REQUESTING) {
Expand Down Expand Up @@ -924,7 +921,7 @@ struct NavigationModuleImpl final : NavigationModule
if (!zone.navmesh) return;

const Transform tr = m_world.getTransform(zone_entity);
const Vec3 pos(tr.inverted().transform(world_pos));
const Vec3 pos(tr.invTransform(world_pos));

const Vec3 min = -zone.zone.extents;
const Vec3 max = zone.zone.extents;
Expand Down Expand Up @@ -991,15 +988,15 @@ struct NavigationModuleImpl final : NavigationModule
return false;
}

const Transform inv_zone_tr = m_world.getTransform(zone.entity).inverted();
const Transform zone_tr = m_world.getTransform(zone.entity);
const Vec3 min = -zone.zone.extents;
const Vec3 max = zone.zone.extents;

for (auto iter : m_agents.iterated()) {
Agent& agent = iter.value();
if (agent.zone.isValid() && agent.agent >= 0) continue;

const Vec3 pos = Vec3(inv_zone_tr.transform(m_world.getPosition(agent.entity)));
const Vec3 pos = Vec3(zone_tr.invTransform(m_world.getPosition(agent.entity)));
if (pos.x > min.x && pos.y > min.y && pos.z > min.z
&& pos.x < max.x && pos.y < max.y && pos.z < max.z)
{
Expand Down Expand Up @@ -1065,7 +1062,7 @@ struct NavigationModuleImpl final : NavigationModule
static const float ext[] = { 1.0f, 20.0f, 1.0f };

const Transform zone_tr = m_world.getTransform(zone.entity);
const Vec3 dest = Vec3(zone_tr.inverted().transform(world_dest));
const Vec3 dest = Vec3(zone_tr.invTransform(world_dest));

zone.navquery->findNearestPoly(&dest.x, ext, &filter, &end_poly_ref, 0);
dtCrowdAgentParams params = zone.crowd->getAgent(agent.agent)->params;
Expand All @@ -1087,7 +1084,7 @@ struct NavigationModuleImpl final : NavigationModule
if (!zone.navmesh) return false;

const Transform tr = m_world.getTransform(zone_entity);
const Vec3 pos = Vec3(tr.inverted().transform(world_pos));
const Vec3 pos = Vec3(tr.invTransform(world_pos));
const Vec3 min = -zone.zone.extents;
const int x = int((pos.x - min.x + (1 + zone.getBorderSize()) * zone.zone.cell_size) / (CELLS_PER_TILE_SIDE * zone.zone.cell_size));
const int z = int((pos.z - min.z + (1 + zone.getBorderSize()) * zone.zone.cell_size) / (CELLS_PER_TILE_SIDE * zone.zone.cell_size));
Expand Down Expand Up @@ -1405,7 +1402,7 @@ struct NavigationModuleImpl final : NavigationModule
ASSERT(zone.crowd);

const Transform zone_tr = m_world.getTransform(zone.entity);
const Vec3 pos = Vec3(zone_tr.inverted().transform(m_world.getPosition(agent.entity)));
const Vec3 pos = Vec3(zone_tr.invTransform(m_world.getPosition(agent.entity)));
dtCrowdAgentParams params = {};
params.radius = agent.radius;
params.height = agent.height;
Expand Down Expand Up @@ -1453,10 +1450,10 @@ struct NavigationModuleImpl final : NavigationModule
void assignZone(Agent& agent) {
const DVec3 agent_pos = m_world.getPosition(agent.entity);
for (RecastZone& zone : m_zones) {
const Transform inv_zone_tr = m_world.getTransform(zone.entity).inverted();
const Transform zone_tr = m_world.getTransform(zone.entity);
const Vec3 min = -zone.zone.extents;
const Vec3 max = zone.zone.extents;
const Vec3 pos = Vec3(inv_zone_tr.transform(agent_pos));
const Vec3 pos = Vec3(zone_tr.invTransform(agent_pos));
if (pos.x > min.x && pos.y > min.y && pos.z > min.z
&& pos.x < max.x && pos.y < max.y && pos.z < max.z)
{
Expand Down
Loading

0 comments on commit daaf11a

Please sign in to comment.