diff --git a/docs/readme.md b/docs/readme.md index f6f804a0b4..ad3f7557f4 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -3,6 +3,7 @@ * [3rd party libraries](3rdparty.md) # Engine +* [Transform](transform.md) * [Job System](job_system.md) * [Profiler](profiler.md) diff --git a/docs/transform.md b/docs/transform.md new file mode 100644 index 0000000000..a546f78e4e --- /dev/null +++ b/docs/transform.md @@ -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. \ No newline at end of file diff --git a/src/core/math.cpp b/src/core/math.cpp index 6e56cebc07..8d3f581563 100644 --- a/src/core/math.cpp +++ b/src/core/math.cpp @@ -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) @@ -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 { diff --git a/src/core/math.h b/src/core/math.h index 31cf51ae1a..31d68a240e 100644 --- a/src/core/math.h +++ b/src/core/math.h @@ -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); @@ -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; diff --git a/src/editor/world_editor.cpp b/src/editor/world_editor.cpp index 91ac46d568..43d283b343 100644 --- a/src/editor/world_editor.cpp +++ b/src/editor/world_editor.cpp @@ -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); @@ -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; } } @@ -3195,10 +3187,6 @@ struct PasteEntityCommand final : IEditorCommand { return false; } - - const Array& getEntities() { return m_entities; } - - private: OutputMemoryStream m_copy_buffer; WorldEditor& m_editor; diff --git a/src/engine/world.cpp b/src/engine/world.cpp index 88372bdd87..4cf287802a 100644 --- a/src/engine/world.cpp +++ b/src/engine/world.cpp @@ -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); @@ -320,7 +319,7 @@ 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; @@ -328,7 +327,7 @@ void World::setTransformKeepChildren(EntityRef entity, const Transform& transfor { 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; } } @@ -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; } @@ -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); } diff --git a/src/navigation/navigation_module.cpp b/src/navigation/navigation_module.cpp index 1a9d1756f9..045b9d4ea9 100644 --- a/src/navigation/navigation_module.cpp +++ b/src/navigation/navigation_module.cpp @@ -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); @@ -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)); @@ -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; @@ -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 @@ -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); @@ -277,8 +276,6 @@ struct NavigationModuleImpl final : NavigationModule { PROFILE_FUNCTION(); - const Transform inv_zone_tr = zone_tr.inverted(); - auto render_module = static_cast(m_world.getModule("renderer")); if (!render_module) return; @@ -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& ims = render_module->getInstancedModels(); @@ -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); } } } @@ -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) { @@ -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; @@ -991,7 +988,7 @@ 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; @@ -999,7 +996,7 @@ struct NavigationModuleImpl final : NavigationModule 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) { @@ -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; @@ -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)); @@ -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; @@ -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) { diff --git a/src/physics/editor/physics_plugins.cpp b/src/physics/editor/physics_plugins.cpp index ca104846d8..559149bfc8 100644 --- a/src/physics/editor/physics_plugins.cpp +++ b/src/physics/editor/physics_plugins.cpp @@ -717,12 +717,12 @@ struct PhysicsUIPlugin final : StudioApp::GUIPlugin for (int i = 0; i < model->getBoneCount(); ++i) { const Model::Bone& bone = model->getBone(i); - const Transform tr = root_tr * bone.transform; + const Transform tr = root_tr.compose(bone.transform); if (bone.parent_idx >= 0) { const Model::Bone& parent_bone = model->getBone(bone.parent_idx); const Vec3 parent_pos = parent_bone.transform.pos; - const DVec3 pos = (root_tr * parent_bone.transform).pos; + const DVec3 pos = (root_tr.compose(parent_bone.transform)).pos; Quat rot = Quat::IDENTITY; if (squaredLength(parent_pos - bone.transform.pos) > 0.01f) { diff --git a/src/physics/physics_module.cpp b/src/physics/physics_module.cpp index b2c8e9f16d..5c3b5933cc 100644 --- a/src/physics/physics_module.cpp +++ b/src/physics/physics_module.cpp @@ -1913,7 +1913,7 @@ struct PhysicsModuleImpl final : PhysicsModule wheels[idx].mWidth = w.width; const Transform& wheel_tr = m_world.getTransform((EntityRef)e); - offsets[idx] = toPhysx((chassis_tr.inverted() * wheel_tr).pos - vehicle.center_of_mass); + offsets[idx] = toPhysx(Transform::computeLocal(chassis_tr, wheel_tr).pos - vehicle.center_of_mass); wheel_sim_data->setTireData(idx, tire); wheel_sim_data->setSuspTravelDirection(idx, PxVec3(0, -1, 0)); diff --git a/src/renderer/editor/render_plugins.cpp b/src/renderer/editor/render_plugins.cpp index 4656d5befa..3f30e240d2 100644 --- a/src/renderer/editor/render_plugins.cpp +++ b/src/renderer/editor/render_plugins.cpp @@ -4169,7 +4169,6 @@ struct InstancedModelPlugin final : PropertyGrid::IPlugin, StudioApp::MousePlugi case Brush::TERRAIN: { const EntityRef terrain = *hit.entity; const Transform terrain_tr = editor.getWorld()->getTransform(terrain); - const Transform inv_terrain_tr = terrain_tr.inverted(); const bool remove = ImGui::GetIO().KeyCtrl; // TODO @@ -4190,7 +4189,7 @@ struct InstancedModelPlugin final : PropertyGrid::IPlugin, StudioApp::MousePlugi const float angle = randFloat(0, PI * 2); const float dist = randFloat(0, 1.0f) * m_brush_radius; DVec3 pos(hit_pos.x + cosf(angle) * dist, 0, hit_pos.z + sinf(angle) * dist); - const Vec3 terrain_pos = Vec3(inv_terrain_tr.transform(pos)); + const Vec3 terrain_pos = Vec3(pos - terrain_tr.pos); pos.y = cmp.module->getTerrainHeightAt(terrain, terrain_pos.x, terrain_pos.z) + terrain_tr.pos.y; pos.y += randFloat(m_y_spread.x, m_y_spread.y); @@ -4477,7 +4476,7 @@ struct ProceduralGeomPlugin final : PropertyGrid::IPlugin, StudioApp::MousePlugi // TODO undo/redo const Transform tr = world.getTransform(entity); - const Vec3 center(tr.inverted().transform(pos)); + const Vec3 center(tr.invTransform(pos)); const float R2 = m_brush_size * m_brush_size; @@ -4567,7 +4566,7 @@ struct ProceduralGeomPlugin final : PropertyGrid::IPlugin, StudioApp::MousePlugi const float R2 = m_brush_size * m_brush_size; const Transform tr = module.getWorld().getTransform(entity); - const Vec3 center_local = Vec3(tr.inverted().transform(center)); + const Vec3 center_local = Vec3(tr.invTransform(center)); for (u32 i = 0, c = pg.getVertexCount(); i < c; ++i) { Vec3 p; @@ -5502,14 +5501,14 @@ struct StudioAppPlugin : StudioApp::IPlugin Transform p0_tr = { pos0, Quat::IDENTITY, Vec3(1) }; WorldEditor& editor = view.getEditor(); if (Gizmo::manipulate((u64(1) << 32) | cmp.entity.index, view, p0_tr, cfg)) { - const Vec2 p0 = Vec2(tr.inverted().transform(p0_tr.pos).xz()); + const Vec2 p0 = Vec2(tr.invTransform(p0_tr.pos).xz()); editor.setProperty(CURVE_DECAL_TYPE, "", 0, "Bezier P0", Span(&e, 1), p0); } const DVec3 pos2 = tr.transform(DVec3(decal.bezier_p2.x, 0, decal.bezier_p2.y)); Transform p2_tr = { pos2, Quat::IDENTITY, Vec3(1) }; if (Gizmo::manipulate((u64(2) << 32) | cmp.entity.index, view, p2_tr, cfg)) { - const Vec2 p2 = Vec2(tr.inverted().transform(p2_tr.pos).xz()); + const Vec2 p2 = Vec2(tr.invTransform(p2_tr.pos).xz()); editor.setProperty(CURVE_DECAL_TYPE, "", 0, "Bezier P2", Span(&e, 1), p2); } diff --git a/src/renderer/editor/scene_view.cpp b/src/renderer/editor/scene_view.cpp index 179467203c..15edc6a470 100644 --- a/src/renderer/editor/scene_view.cpp +++ b/src/renderer/editor/scene_view.cpp @@ -1034,10 +1034,13 @@ void SceneView::manipulate() { Array rots(m_app.getAllocator()); rots.resize(filtered_selection.size()); poss.resize(filtered_selection.size()); + const Quat rot_diff = new_pivot_tr.rot * old_pivot_tr.rot.conjugated(); + const DVec3 pivot_pos = old_pivot_tr.pos; for (u32 i = 0, c = filtered_selection.size(); i < c; ++i) { - const Transform t = new_pivot_tr * old_pivot_tr.inverted() * world.getTransform(filtered_selection[i]); - poss[i] = t.pos; - rots[i] = normalize(t.rot); + const Transform old_tr = world.getTransform(filtered_selection[i]); + + poss[i] = rot_diff.rotate(old_tr.pos - pivot_pos) + pivot_pos; + rots[i] = normalize(rot_diff * old_tr.rot); } m_editor.setEntitiesPositionsAndRotations(filtered_selection.begin(), poss.begin(), rots.begin(), rots.size()); break; diff --git a/src/renderer/editor/terrain_editor.cpp b/src/renderer/editor/terrain_editor.cpp index dfa61265f0..d1fc455a50 100644 --- a/src/renderer/editor/terrain_editor.cpp +++ b/src/renderer/editor/terrain_editor.cpp @@ -172,9 +172,9 @@ struct PaintTerrainCommand final : IEditorCommand m_width = m_height = m_x = m_y = -1; World& world = *editor.getWorld(); - const Transform entity_transform = world.getTransform(terrain).inverted(); + const Transform terrain_tr = world.getTransform(terrain); RenderModule* module = (RenderModule*)world.getModule(TERRAIN_TYPE); - DVec3 local_pos = entity_transform.transform(hit_pos); + DVec3 local_pos = hit_pos - terrain_tr.pos; float terrain_size = module->getTerrainSize(terrain).x; local_pos = local_pos / terrain_size; local_pos.y = -1; @@ -814,25 +814,24 @@ void TerrainEditor::drawCursor(RenderModule& module, EntityRef entity, const DVe } float brush_size = m_terrain_brush_size; - const Vec3 local_center = Vec3(getRelativePosition(center, entity, module.getWorld())); - const Transform terrain_transform = module.getWorld().getTransform(entity); + const DVec3 terrain_origin = module.getWorld().getPosition(entity); + const Vec3 rel_pos = Vec3(center - terrain_origin); for (int i = 0; i < SLICE_COUNT + 1; ++i) { const float angle = i * angle_step; const float next_angle = i * angle_step + angle_step; - Vec3 local_from = local_center + Vec3(cosf(angle), 0, sinf(angle)) * brush_size; + Vec3 local_from = rel_pos + Vec3(cosf(angle), 0, sinf(angle)) * brush_size; local_from.y = terrain->getHeight(local_from.x, local_from.z); local_from.y += 0.25f; - Vec3 local_to = local_center + Vec3(cosf(next_angle), 0, sinf(next_angle)) * brush_size; + Vec3 local_to = rel_pos + Vec3(cosf(next_angle), 0, sinf(next_angle)) * brush_size; local_to.y = terrain->getHeight(local_to.x, local_to.z); local_to.y += 0.25f; - const DVec3 from = terrain_transform.transform(local_from); - const DVec3 to = terrain_transform.transform(local_to); + const DVec3 from = terrain_origin + local_from; + const DVec3 to = terrain_origin + local_to; module.addDebugLine(from, to, Color::RED); } - const Vec3 rel_pos = Vec3(terrain_transform.inverted().transform(center)); const float scale = terrain->getXZScale(); const IVec3 p = IVec3(rel_pos / scale); const i32 half_extents = i32(1 + brush_size / scale); @@ -848,10 +847,10 @@ void TerrainEditor::drawCursor(RenderModule& module, EntityRef entity, const DVe p11.y = terrain->getHeight(i + 1, j + 1); p01.y = terrain->getHeight(i, j + 1); - p00 = terrain_transform.transform(p00); - p10 = terrain_transform.transform(p10); - p01 = terrain_transform.transform(p01); - p11 = terrain_transform.transform(p11); + p00 = terrain_origin + p00; + p10 = terrain_origin + p10; + p01 = terrain_origin + p01; + p11 = terrain_origin + p11; module.addDebugLine(p10, p01, Color(0x80, 0, 0, 0xff)); module.addDebugLine(p10, p11, Color(0x80, 0, 0, 0xff)); @@ -863,18 +862,10 @@ void TerrainEditor::drawCursor(RenderModule& module, EntityRef entity, const DVe } -DVec3 TerrainEditor::getRelativePosition(const DVec3& world_pos, EntityRef terrain, World& world) const -{ - const Transform transform = world.getTransform(terrain); - const Transform inv_transform = transform.inverted(); - - return inv_transform.transform(world_pos); -} - - u16 TerrainEditor::getHeight(const DVec3& world_pos, RenderModule* module, EntityRef terrain) const { - const DVec3 rel_pos = getRelativePosition(world_pos, terrain, module->getWorld()); + const DVec3 terrain_pos = module->getWorld().getPosition(terrain); + const DVec3 rel_pos = world_pos - terrain_pos; ComponentUID cmp; cmp.entity = terrain; cmp.module = module; @@ -1022,7 +1013,7 @@ static bool isOBBCollision(RenderModule& module, World& world = module.getWorld(); Span model_instances = module.getModelInstances(); const Transform* transforms = world.getTransforms(); - while(meshes) { + while (meshes) { const EntityRef* entities = meshes->entities; for (u32 i = 0, c = meshes->header.count; i < c; ++i) { const EntityRef mesh = entities[i]; @@ -1035,10 +1026,9 @@ static bool isOBBCollision(RenderModule& module, const float radius_b = model_instance.model->getOriginBoundingRadius() * maximum(tr_b.scale.x, tr_b.scale.y, tr_b.scale.z); const float radius_squared = radius_a_squared + radius_b * radius_b; if (squaredLength(model_tr.pos - tr_b.pos) < radius_squared) { - const Transform rel_tr = model_tr.inverted() * tr_b; - Matrix mtx = rel_tr.rot.toMatrix(); + const Transform rel_tr = Transform::computeLocal(model_tr, tr_b); + Matrix mtx(Vec3(rel_tr.pos), rel_tr.rot); mtx.multiply3x3(rel_tr.scale); - mtx.setTranslation(Vec3(rel_tr.pos)); if (testOBBCollision(model->getAABB(), mtx, model_instance.model->getAABB())) { if (ignore_not_in_folder && folders.getFolder(EntityRef{mesh.index}) != folder) { @@ -1071,8 +1061,7 @@ void TerrainEditor::paintEntities(const DVec3& hit_pos, WorldEditor& editor, Ent { World& world = *editor.getWorld(); RenderModule* module = static_cast(world.getModule(TERRAIN_TYPE)); - const Transform terrain_tr = world.getTransform(terrain); - const Transform inv_terrain_tr = terrain_tr.inverted(); + const DVec3 terrain_pos = world.getPosition(terrain); ShiftedFrustum frustum; frustum.computeOrtho(hit_pos, @@ -1098,15 +1087,15 @@ void TerrainEditor::paintEntities(const DVec3& hit_pos, WorldEditor& editor, Ent const float dist = randFloat(0, 1.0f) * m_terrain_brush_size; const float y = randFloat(m_y_spread.x, m_y_spread.y); DVec3 pos(hit_pos.x + cosf(angle) * dist, 0, hit_pos.z + sinf(angle) * dist); - const Vec3 terrain_pos = Vec3(inv_terrain_tr.transform(pos)); - if (terrain_pos.x >= 0 && terrain_pos.z >= 0 && terrain_pos.x <= terrain_size.x && terrain_pos.z <= terrain_size.y) + const Vec3 rel_pos = Vec3(pos - terrain_pos); + if (rel_pos.x >= 0 && rel_pos.z >= 0 && rel_pos.x <= terrain_size.x && rel_pos.z <= terrain_size.y) { - pos.y = module->getTerrainHeightAt(terrain, terrain_pos.x, terrain_pos.z) + y; - pos.y += terrain_tr.pos.y; + pos.y = module->getTerrainHeightAt(terrain, rel_pos.x, rel_pos.z) + y; + pos.y += terrain_pos.y; Quat rot(0, 0, 0, 1); if(m_is_align_with_normal) { - Vec3 normal = module->getTerrainNormalAt(terrain, terrain_pos.x, terrain_pos.z); + Vec3 normal = module->getTerrainNormalAt(terrain, rel_pos.x, rel_pos.z); Vec3 dir = normalize(cross(normal, Vec3(1, 0, 0))); Matrix mtx = Matrix::IDENTITY; mtx.setXVector(cross(normal, dir)); diff --git a/src/renderer/editor/terrain_editor.h b/src/renderer/editor/terrain_editor.h index 1dc66a84e7..dc77088205 100644 --- a/src/renderer/editor/terrain_editor.h +++ b/src/renderer/editor/terrain_editor.h @@ -72,7 +72,6 @@ struct LUMIX_RENDERER_API TerrainEditor final : StudioApp::MousePlugin { void increaseBrushSize(); void decreaseBrushSize(); u16 getHeight(const DVec3& world_pos, RenderModule* module, EntityRef terrain) const; - DVec3 getRelativePosition(const DVec3& world_pos, EntityRef terrain, World& world) const; void exportToOBJ(ComponentUID cmp) const; Renderer& getRenderer(); diff --git a/src/renderer/particle_system.cpp b/src/renderer/particle_system.cpp index 61b35ea33d..2d37f2e83d 100644 --- a/src/renderer/particle_system.cpp +++ b/src/renderer/particle_system.cpp @@ -1068,7 +1068,7 @@ void ParticleSystem::applyTransform(const Transform& new_tr) { if (m_total_time == 0) { m_prev_frame_transform = new_tr; } - const Transform delta_tr = new_tr.inverted() * m_prev_frame_transform; + const Transform delta_tr = Transform::computeLocal(new_tr, m_prev_frame_transform); for (i32 emitter_idx = 0; emitter_idx < m_emitters.size(); ++emitter_idx) { Emitter& emitter = m_emitters[emitter_idx]; if ((u32)m_resource->getFlags() & (u32)ParticleSystemResource::Flags::WORLD_SPACE) { diff --git a/src/renderer/render_module.cpp b/src/renderer/render_module.cpp index 9019d8438f..b97fccff00 100644 --- a/src/renderer/render_module.cpp +++ b/src/renderer/render_module.cpp @@ -406,7 +406,7 @@ struct RenderModuleImpl final : RenderModule { Vec3 original_scale = m_world.getScale(bone_attachment.entity); const LocalRigidTransform bone_transform = {parent_pose->positions[idx], parent_pose->rotations[idx] }; const LocalRigidTransform relative_transform = { bone_attachment.relative_transform.pos, bone_attachment.relative_transform.rot }; - Transform result = parent_entity_transform * bone_transform * relative_transform; + Transform result = parent_entity_transform.compose(bone_transform * relative_transform); result.scale = original_scale; m_world.setTransform(bone_attachment.entity, result); unlockPose(model_instance, false); @@ -438,10 +438,9 @@ struct RenderModuleImpl final : RenderModule { const LocalRigidTransform bone_transform = {pose->positions[attachment.bone_index], pose->rotations[attachment.bone_index]}; const EntityRef parent = (EntityRef)attachment.parent_entity; - Transform inv_parent_transform = m_world.getTransform(parent) * bone_transform; - inv_parent_transform = inv_parent_transform.inverted(); + Transform parent_transform = m_world.getTransform(parent).compose(bone_transform); const Transform child_transform = m_world.getTransform(attachment.entity); - const Transform res = inv_parent_transform * child_transform; + const Transform res = Transform::computeLocal(parent_transform, child_transform); attachment.relative_transform = {Vec3(res.pos), res.rot}; unlockPose(model_instance, false); } @@ -2640,10 +2639,10 @@ struct RenderModuleImpl final : RenderModule { Vec3 a, b, c; RayCastModelHit pg_hit; - const DVec3& pos = m_world.getPosition(iter.key()); - const Quat rot = m_world.getRotation(iter.key()).conjugated(); - const Vec3 rd = rot.rotate(ray.dir); - Vec3 ro = Vec3(ray.origin - pos); + const Transform& tr = m_world.getTransform(iter.key()); + + const Vec3 rd = tr.invTransformVector(ray.dir); + Vec3 ro = Vec3(tr.invTransform(ray.origin)); Vec3 dummy; if (!pg.aabb.contains(ro) && !getRayAABBIntersection(ro, rd, pg.aabb.min, pg.aabb.max - pg.aabb.min, dummy)) continue; @@ -2710,9 +2709,8 @@ struct RenderModuleImpl final : RenderModule { const double dist = length(tr.pos - ray.origin); if (dist - radius * maximum(tr.scale.x, tr.scale.y, tr.scale.z) > cur_dist) continue; - const Transform& inv_tr = tr.inverted(); - const Vec3 ray_origin_model_space = Vec3(inv_tr.transform(ray.origin)); - const Vec3 ray_dir_model_space = normalize(inv_tr.transformVector(ray.dir)); + const Vec3 ray_origin_model_space = Vec3(tr.invTransform(ray.origin)); + const Vec3 ray_dir_model_space = normalize(tr.invTransformVector(ray.dir)); float intersection_t; if (getRaySphereIntersection(ray_origin_model_space, ray_dir_model_space, Vec3::ZERO, radius, intersection_t) && intersection_t >= 0) { diff --git a/src/renderer/terrain.cpp b/src/renderer/terrain.cpp index b374476001..5007b98bab 100644 --- a/src/renderer/terrain.cpp +++ b/src/renderer/terrain.cpp @@ -483,26 +483,23 @@ RayCastModelHit Terrain::castRay(const Ray& ray) if (!m_heightmap || !m_heightmap->isReady()) return hit; const World& world = m_module.getWorld(); - const Quat rot = world.getRotation(m_entity); const DVec3 pos = world.getPosition(m_entity); - const Vec3 rel_dir = rot.rotate(ray.dir); - const Vec3 terrain_to_ray = Vec3(ray.origin - pos); - const Vec3 rel_origin = rot.conjugated().rotate(terrain_to_ray); + const Vec3 rel_origin = Vec3(ray.origin - pos); Vec3 start; const Vec3 size(m_width * m_scale.x, m_scale.y * 65535.0f, m_height * m_scale.x); - if (!getRayAABBIntersection(rel_origin, rel_dir, Vec3::ZERO, size, start)) return hit; + if (!getRayAABBIntersection(rel_origin, ray.dir, Vec3::ZERO, size, start)) return hit; int hx = (int)(start.x / m_scale.x); int hz = (int)(start.z / m_scale.x); - float next_x = fabs(rel_dir.x) < 0.01f ? hx : ((hx + (rel_dir.x < 0 ? 0 : 1)) * m_scale.x - rel_origin.x) / rel_dir.x; - float next_z = fabs(rel_dir.z) < 0.01f ? hz : ((hz + (rel_dir.z < 0 ? 0 : 1)) * m_scale.x - rel_origin.z) / rel_dir.z; + float next_x = fabs(ray.dir.x) < 0.01f ? hx : ((hx + (ray.dir.x < 0 ? 0 : 1)) * m_scale.x - rel_origin.x) / ray.dir.x; + float next_z = fabs(ray.dir.z) < 0.01f ? hz : ((hz + (ray.dir.z < 0 ? 0 : 1)) * m_scale.x - rel_origin.z) / ray.dir.z; - float delta_x = fabsf(rel_dir.x) < 0.01f ? 0 : m_scale.x / fabsf(rel_dir.x); - float delta_z = fabsf(rel_dir.z) < 0.01f ? 0 : m_scale.z / fabsf(rel_dir.z); - int step_x = (int)signum(rel_dir.x); - int step_z = (int)signum(rel_dir.z); + float delta_x = fabsf(ray.dir.x) < 0.01f ? 0 : m_scale.x / fabsf(ray.dir.x); + float delta_z = fabsf(ray.dir.z) < 0.01f ? 0 : m_scale.z / fabsf(ray.dir.z); + int step_x = (int)signum(ray.dir.x); + int step_z = (int)signum(ray.dir.z); while (hx >= 0 && hz >= 0 && hx + step_x < m_width && hz + step_z < m_height) { float t; @@ -512,14 +509,14 @@ RayCastModelHit Terrain::castRay(const Ray& ray) Vec3 p1(x + m_scale.x, getHeight(x + m_scale.x, z), z); Vec3 p2(x + m_scale.x, getHeight(x + m_scale.x, z + m_scale.x), z + m_scale.x); Vec3 p3(x, getHeight(x, z + m_scale.x), z + m_scale.x); - if (getRayTriangleIntersection(rel_origin, rel_dir, p0, p1, p2, &t)) { + if (getRayTriangleIntersection(rel_origin, ray.dir, p0, p1, p2, &t)) { hit.is_hit = true; hit.origin = ray.origin; hit.dir = ray.dir; hit.t = t; return hit; } - if (getRayTriangleIntersection(rel_origin, rel_dir, p0, p2, p3, &t)) { + if (getRayTriangleIntersection(rel_origin, ray.dir, p0, p2, p3, &t)) { hit.is_hit = true; hit.origin = ray.origin; hit.dir = ray.dir;