#include "instancemode.hpp" #include #include #include #include #include #include #include #include #include #include "../../model/prefs/state.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../model/prefs/shortcut.hpp" #include "../../model/world/commandmacro.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" #include "../../model/world/tablemimedata.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" #include "mask.hpp" #include "instancemovemode.hpp" #include "instanceselectionmode.hpp" #include "object.hpp" #include "pagedworldspacewidget.hpp" #include "worldspacewidget.hpp" int CSVRender::InstanceMode::getSubModeFromId(const std::string& id) const { return id == "move" ? 0 : (id == "rotate" ? 1 : 2); } osg::Vec3f CSVRender::InstanceMode::quatToEuler(const osg::Quat& rot) const { float x, y, z; float test = 2 * (rot.w() * rot.y() + rot.x() * rot.z()); if (std::abs(test) >= 1.f) { x = atan2(rot.x(), rot.w()); y = (test > 0) ? (osg::PI / 2) : (-osg::PI / 2); z = 0; } else { x = std::atan2(2 * (rot.w() * rot.x() - rot.y() * rot.z()), 1 - 2 * (rot.x() * rot.x() + rot.y() * rot.y())); y = std::asin(test); z = std::atan2(2 * (rot.w() * rot.z() - rot.x() * rot.y()), 1 - 2 * (rot.y() * rot.y() + rot.z() * rot.z())); } return osg::Vec3f(-x, -y, -z); } osg::Quat CSVRender::InstanceMode::eulerToQuat(const osg::Vec3f& euler) const { osg::Quat xr = osg::Quat(-euler[0], osg::Vec3f(1, 0, 0)); osg::Quat yr = osg::Quat(-euler[1], osg::Vec3f(0, 1, 0)); osg::Quat zr = osg::Quat(-euler[2], osg::Vec3f(0, 0, 1)); return zr * yr * xr; } float CSVRender::InstanceMode::roundFloatToMult(const float val, const double mult) const { if (mult == 0) return val; return round(val / mult) * mult; } osg::Vec3 CSVRender::InstanceMode::calculateSnapPositionRelativeToTarget(osg::Vec3 initalPosition, osg::Vec3 targetPosition, osg::Vec3 targetRotation, osg::Vec3 translation, double snap) const { auto quatTargetRotation = osg::Quat(targetRotation[0], osg::X_AXIS, targetRotation[1], osg::Y_AXIS, targetRotation[2], osg::Z_AXIS); // Break object world coords into snap target space auto localWorld = osg::Matrix::translate(initalPosition) * osg::Matrix::inverse(osg::Matrix::translate(targetPosition)) * osg::Matrix::rotate(quatTargetRotation); osg::Vec3 localPosition = localWorld.getTrans(); osg::Vec3 newTranslation; newTranslation[0] = CSVRender::InstanceMode::roundFloatToMult(localPosition[0] + translation[0], snap); newTranslation[1] = CSVRender::InstanceMode::roundFloatToMult(localPosition[1] + translation[1], snap); newTranslation[2] = CSVRender::InstanceMode::roundFloatToMult(localPosition[2] + translation[2], snap); // rebuild object's world coordinates (note: inverse operations from local construction) auto newObjectWorld = osg::Matrix::translate(newTranslation) * osg::Matrix::inverse(osg::Matrix::rotate(quatTargetRotation)) * osg::Matrix::translate(targetPosition); osg::Vec3 newObjectPosition = newObjectWorld.getTrans(); return newObjectPosition; } osg::Vec3f CSVRender::InstanceMode::getSelectionCenter(const std::vector>& selection) const { osg::Vec3f center = osg::Vec3f(0, 0, 0); int objectCount = 0; for (std::vector>::const_iterator iter(selection.begin()); iter != selection.end(); ++iter) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) { const ESM::Position& position = objectTag->mObject->getPosition(); center += osg::Vec3f(position.pos[0], position.pos[1], position.pos[2]); ++objectCount; } } if (objectCount > 0) center /= objectCount; return center; } osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos) { osg::Matrix viewMatrix = getWorldspaceWidget().getCamera()->getViewMatrix(); osg::Matrix projMatrix = getWorldspaceWidget().getCamera()->getProjectionMatrix(); osg::Matrix windowMatrix = getWorldspaceWidget().getCamera()->getViewport()->computeWindowMatrix(); osg::Matrix combined = viewMatrix * projMatrix * windowMatrix; return pos * combined; } osg::Vec3f CSVRender::InstanceMode::getProjectionSpaceCoords(const osg::Vec3f& pos) { osg::Matrix viewMatrix = getWorldspaceWidget().getCamera()->getViewMatrix(); osg::Matrix projMatrix = getWorldspaceWidget().getCamera()->getProjectionMatrix(); osg::Matrix combined = viewMatrix * projMatrix; return pos * combined; } osg::Vec3f CSVRender::InstanceMode::getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart) { osg::Matrix viewMatrix; viewMatrix.invert(getWorldspaceWidget().getCamera()->getViewMatrix()); osg::Matrix projMatrix; projMatrix.invert(getWorldspaceWidget().getCamera()->getProjectionMatrix()); osg::Matrix combined = projMatrix * viewMatrix; /* calculate viewport normalized coordinates note: is there a reason to use getCamera()->getViewport()->computeWindowMatrix() instead? */ float x = (point.x() * 2) / getWorldspaceWidget().getCamera()->getViewport()->width() - 1.0f; float y = 1.0f - (point.y() * 2) / getWorldspaceWidget().getCamera()->getViewport()->height(); osg::Vec3f mousePlanePoint = osg::Vec3f(x, y, dragStart.z()) * combined; return mousePlanePoint; } void CSVRender::InstanceMode::saveSelectionGroup(const int group) { QStringList strings; QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); QVariant selectionObjects; CSMWorld::CommandMacro macro(undoStack, "Replace Selection Group"); std::string groupName = "project::" + std::to_string(group); const auto& selection = getWorldspaceWidget().getSelection(Mask_Reference); const int selectionObjectsIndex = mSelectionGroups->findColumnIndex(CSMWorld::Columns::ColumnId_SelectionGroupObjects); if (dynamic_cast(&getWorldspaceWidget())) groupName += "-ext"; else groupName += "-" + getWorldspaceWidget().getCellId(osg::Vec3f(0, 0, 0)); CSMWorld::CreateCommand* newGroup = new CSMWorld::CreateCommand(*mSelectionGroups, groupName); newGroup->setType(CSMWorld::UniversalId::Type_SelectionGroup); for (const auto& object : selection) if (const CSVRender::ObjectTag* objectTag = dynamic_cast(object.get())) strings << QString::fromStdString(objectTag->mObject->getReferenceId()); selectionObjects.setValue(strings); newGroup->addValue(selectionObjectsIndex, selectionObjects); if (mSelectionGroups->getModelIndex(groupName, 0).row() != -1) macro.push(new CSMWorld::DeleteCommand(*mSelectionGroups, groupName)); macro.push(newGroup); getWorldspaceWidget().clearSelection(Mask_Reference); } void CSVRender::InstanceMode::getSelectionGroup(const int group) { std::string groupName = "project::" + std::to_string(group); std::vector targets; const auto& selection = getWorldspaceWidget().getSelection(Mask_Reference); const int selectionObjectsIndex = mSelectionGroups->findColumnIndex(CSMWorld::Columns::ColumnId_SelectionGroupObjects); if (dynamic_cast(&getWorldspaceWidget())) groupName += "-ext"; else groupName += "-" + getWorldspaceWidget().getCellId(osg::Vec3f(0, 0, 0)); const QModelIndex groupSearch = mSelectionGroups->getModelIndex(groupName, selectionObjectsIndex); if (groupSearch.row() == -1) return; for (const QString& target : groupSearch.data().toStringList()) targets.push_back(target.toStdString()); if (!selection.empty()) getWorldspaceWidget().clearSelection(Mask_Reference); getWorldspaceWidget().selectGroup(targets); } CSVRender::InstanceMode::InstanceMode( WorldspaceWidget* worldspaceWidget, osg::ref_ptr parentNode, QWidget* parent) : EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing", parent) , mSubMode(nullptr) , mSubModeId("move") , mSelectionMode(nullptr) , mDragMode(DragMode_None) , mDragAxis(-1) , mLocked(false) , mUnitScaleDist(1) , mParentNode(std::move(parentNode)) { mSelectionGroups = dynamic_cast( worldspaceWidget->getDocument().getData().getTableModel(CSMWorld::UniversalId::Type_SelectionGroup)); connect(this, &InstanceMode::requestFocus, worldspaceWidget, &WorldspaceWidget::requestFocus); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget); connect(deleteShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::deleteSelectedInstances); CSMPrefs::Shortcut* duplicateShortcut = new CSMPrefs::Shortcut("scene-duplicate", worldspaceWidget); connect( duplicateShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::cloneSelectedInstances); // Following classes could be simplified by using QSignalMapper, which is obsolete in Qt5.10, but not in Qt4.8 and // Qt5.14 CSMPrefs::Shortcut* dropToCollisionShortcut = new CSMPrefs::Shortcut("scene-instance-drop-collision", worldspaceWidget); connect(dropToCollisionShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::dropSelectedInstancesToCollision); CSMPrefs::Shortcut* dropToTerrainLevelShortcut = new CSMPrefs::Shortcut("scene-instance-drop-terrain", worldspaceWidget); connect(dropToTerrainLevelShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::dropSelectedInstancesToTerrain); CSMPrefs::Shortcut* dropToCollisionShortcut2 = new CSMPrefs::Shortcut("scene-instance-drop-collision-separately", worldspaceWidget); connect(dropToCollisionShortcut2, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::dropSelectedInstancesToCollisionSeparately); CSMPrefs::Shortcut* dropToTerrainLevelShortcut2 = new CSMPrefs::Shortcut("scene-instance-drop-terrain-separately", worldspaceWidget); connect(dropToTerrainLevelShortcut2, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::dropSelectedInstancesToTerrainSeparately); for (short i = 0; i <= 9; i++) { connect(new CSMPrefs::Shortcut("scene-group-" + std::to_string(i), worldspaceWidget), qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, i] { this->getSelectionGroup(i); }); connect(new CSMPrefs::Shortcut("scene-save-" + std::to_string(i), worldspaceWidget), qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, i] { this->saveSelectionGroup(i); }); } } void CSVRender::InstanceMode::activate(CSVWidget::SceneToolbar* toolbar) { if (!mSubMode) { mSubMode = new CSVWidget::SceneToolMode(toolbar, "Edit Sub-Mode"); mSubMode->addButton(new InstanceMoveMode(this), "move"); mSubMode->addButton(":scenetoolbar/transform-rotate", "rotate", "Rotate selected instances" "
  • Use {scene-edit-primary} to rotate instances freely
  • " "
  • Use {scene-edit-secondary} to rotate instances within the grid
  • " "
  • The center of the view acts as the axis of rotation
  • " "
"); mSubMode->addButton(":scenetoolbar/transform-scale", "scale", "Scale selected instances" "
  • Use {scene-edit-primary} to scale instances freely
  • " "
  • Use {scene-edit-secondary} to scale instances along the grid
  • " "
  • The scaling rate is based on how close the start of a drag is to the center of the screen
  • " "
"); mSubMode->setButton(mSubModeId); connect(mSubMode, &CSVWidget::SceneToolMode::modeChanged, this, &InstanceMode::subModeChanged); } if (!mSelectionMode) mSelectionMode = new InstanceSelectionMode(toolbar, getWorldspaceWidget(), mParentNode); mDragMode = DragMode_None; EditMode::activate(toolbar); toolbar->addTool(mSubMode); toolbar->addTool(mSelectionMode); std::string subMode = mSubMode->getCurrentId(); getWorldspaceWidget().setSubMode(getSubModeFromId(subMode), Mask_Reference); } void CSVRender::InstanceMode::deactivate(CSVWidget::SceneToolbar* toolbar) { mDragMode = DragMode_None; getWorldspaceWidget().reset(Mask_Reference); if (mSelectionMode) { toolbar->removeTool(mSelectionMode); delete mSelectionMode; mSelectionMode = nullptr; } if (mSubMode) { toolbar->removeTool(mSubMode); delete mSubMode; mSubMode = nullptr; } EditMode::deactivate(toolbar); } void CSVRender::InstanceMode::setEditLock(bool locked) { mLocked = locked; if (mLocked) getWorldspaceWidget().abortDrag(); } void CSVRender::InstanceMode::primaryEditPressed(const WorldspaceHitResult& hit) { if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) primarySelectPressed(hit); } void CSVRender::InstanceMode::primaryOpenPressed(const WorldspaceHitResult& hit) { if (hit.tag) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) { const std::string refId = objectTag->mObject->getReferenceId(); emit requestFocus(refId); } } } void CSVRender::InstanceMode::secondaryEditPressed(const WorldspaceHitResult& hit) { if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) secondarySelectPressed(hit); } void CSVRender::InstanceMode::primarySelectPressed(const WorldspaceHitResult& hit) { getWorldspaceWidget().clearSelection(Mask_Reference); if (hit.tag) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) { // hit an Object, select it CSVRender::Object* object = objectTag->mObject; object->setSelected(true); return; } } } void CSVRender::InstanceMode::secondarySelectPressed(const WorldspaceHitResult& hit) { if (hit.tag) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) { // hit an Object, toggle its selection state CSVRender::Object* object = objectTag->mObject; object->setSelected(!object->getSelected()); return; } } } void CSVRender::InstanceMode::tertiarySelectPressed(const WorldspaceHitResult& hit) { auto* snapTarget = dynamic_cast(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()); if (snapTarget) { snapTarget->mObject->setSnapTarget(false); } if (hit.tag) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) { // hit an Object, toggle its selection state CSVRender::Object* object = objectTag->mObject; object->setSnapTarget(!object->getSnapTarget()); return; } } } bool CSVRender::InstanceMode::primaryEditStartDrag(const QPoint& pos) { if (mDragMode != DragMode_None || mLocked) return false; WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) { // Only change selection at the start of drag if no object is already selected if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) { getWorldspaceWidget().clearSelection(Mask_Reference); if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) { CSVRender::Object* object = objectTag->mObject; object->setSelected(true); } } selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) return false; } mObjectsAtDragStart.clear(); for (std::vector>::iterator iter(selection.begin()); iter != selection.end(); ++iter) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) { if (mSubModeId == "move") { objectTag->mObject->setEdited(Object::Override_Position); float x = objectTag->mObject->getPosition().pos[0]; float y = objectTag->mObject->getPosition().pos[1]; float z = objectTag->mObject->getPosition().pos[2]; osg::Vec3f thisPoint(x, y, z); mDragStart = getMousePlaneCoords(pos, getProjectionSpaceCoords(thisPoint)); mObjectsAtDragStart.emplace_back(thisPoint); mDragMode = DragMode_Move; } else if (mSubModeId == "rotate") { objectTag->mObject->setEdited(Object::Override_Rotation); mDragMode = DragMode_Rotate; } else if (mSubModeId == "scale") { objectTag->mObject->setEdited(Object::Override_Scale); mDragMode = DragMode_Scale; // Calculate scale factor std::vector> editedSelection = getWorldspaceWidget().getEdited(Mask_Reference); osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection)); int widgetHeight = getWorldspaceWidget().height(); float dx = pos.x() - center.x(); float dy = (widgetHeight - pos.y()) - center.y(); mUnitScaleDist = std::sqrt(dx * dx + dy * dy); } } } if (CSVRender::ObjectMarkerTag* objectTag = dynamic_cast(hit.tag.get())) { mDragAxis = objectTag->mAxis; } else mDragAxis = -1; return true; } bool CSVRender::InstanceMode::secondaryEditStartDrag(const QPoint& pos) { if (mDragMode != DragMode_None || mLocked) return false; WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) { // Only change selection at the start of drag if no object is already selected if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) { getWorldspaceWidget().clearSelection(Mask_Reference); if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) { CSVRender::Object* object = objectTag->mObject; object->setSelected(true); } } selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) return false; } mObjectsAtDragStart.clear(); for (std::vector>::iterator iter(selection.begin()); iter != selection.end(); ++iter) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) { if (mSubModeId == "move") { objectTag->mObject->setEdited(Object::Override_Position); float x = objectTag->mObject->getPosition().pos[0]; float y = objectTag->mObject->getPosition().pos[1]; float z = objectTag->mObject->getPosition().pos[2]; osg::Vec3f thisPoint(x, y, z); mDragStart = getMousePlaneCoords(pos, getProjectionSpaceCoords(thisPoint)); mObjectsAtDragStart.emplace_back(thisPoint); mDragMode = DragMode_Move_Snap; } else if (mSubModeId == "rotate") { objectTag->mObject->setEdited(Object::Override_Rotation); mDragMode = DragMode_Rotate_Snap; } else if (mSubModeId == "scale") { objectTag->mObject->setEdited(Object::Override_Scale); mDragMode = DragMode_Scale_Snap; // Calculate scale factor std::vector> editedSelection = getWorldspaceWidget().getEdited(Mask_Reference); osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection)); int widgetHeight = getWorldspaceWidget().height(); float dx = pos.x() - center.x(); float dy = (widgetHeight - pos.y()) - center.y(); mUnitScaleDist = std::sqrt(dx * dx + dy * dy); } } } if (CSVRender::ObjectMarkerTag* objectTag = dynamic_cast(hit.tag.get())) { mDragAxis = objectTag->mAxis; } else mDragAxis = -1; return true; } bool CSVRender::InstanceMode::primarySelectStartDrag(const QPoint& pos) { if (mDragMode != DragMode_None || mLocked) return false; std::string primarySelectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); if (primarySelectAction == "Select only") mDragMode = DragMode_Select_Only; else if (primarySelectAction == "Add to selection") mDragMode = DragMode_Select_Add; else if (primarySelectAction == "Remove from selection") mDragMode = DragMode_Select_Remove; else if (primarySelectAction == "Invert selection") mDragMode = DragMode_Select_Invert; WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mSelectionMode->setDragStart(hit.worldPos); return true; } bool CSVRender::InstanceMode::secondarySelectStartDrag(const QPoint& pos) { if (mDragMode != DragMode_None || mLocked) return false; std::string secondarySelectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); if (secondarySelectAction == "Select only") mDragMode = DragMode_Select_Only; else if (secondarySelectAction == "Add to selection") mDragMode = DragMode_Select_Add; else if (secondarySelectAction == "Remove from selection") mDragMode = DragMode_Select_Remove; else if (secondarySelectAction == "Invert selection") mDragMode = DragMode_Select_Invert; WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mSelectionMode->setDragStart(hit.worldPos); return true; } void CSVRender::InstanceMode::drag(const QPoint& pos, int diffX, int diffY, double speedFactor) { osg::Vec3f offset; osg::Quat rotation; std::vector> selection = getWorldspaceWidget().getEdited(Mask_Reference); auto* snapTarget = dynamic_cast(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()); if (mDragMode == DragMode_Move || mDragMode == DragMode_Move_Snap) { } else if (mDragMode == DragMode_Rotate || mDragMode == DragMode_Rotate_Snap) { osg::Vec3f eye, centre, up; getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt(eye, centre, up); float angle; osg::Vec3f axis; if (mDragAxis == -1) { // Free rotate float rotationFactor = CSMPrefs::get()["3D Scene Input"]["rotate-factor"].toDouble() * speedFactor; osg::Quat cameraRotation = getWorldspaceWidget().getCamera()->getInverseViewMatrix().getRotate(); osg::Vec3f camForward = centre - eye; osg::Vec3f screenDir = cameraRotation * osg::Vec3f(diffX, diffY, 0); screenDir.normalize(); angle = std::sqrt(diffX * diffX + diffY * diffY) * rotationFactor; axis = screenDir ^ camForward; } else { // Global axis rotation osg::Vec3f camBack = eye - centre; for (int i = 0; i < 3; ++i) { if (i == mDragAxis) axis[i] = 1; else axis[i] = 0; } // Flip axis if facing opposite side if (camBack * axis < 0) axis *= -1; // Convert coordinate system osg::Vec3f screenCenter = getScreenCoords(getSelectionCenter(selection)); int widgetHeight = getWorldspaceWidget().height(); float newX = pos.x() - screenCenter.x(); float newY = (widgetHeight - pos.y()) - screenCenter.y(); float oldX = newX - diffX; float oldY = newY - diffY; // diffY appears to already be flipped osg::Vec3f oldVec = osg::Vec3f(oldX, oldY, 0); oldVec.normalize(); osg::Vec3f newVec = osg::Vec3f(newX, newY, 0); newVec.normalize(); // Find angle and axis of rotation angle = std::acos(oldVec * newVec) * speedFactor; if (((oldVec ^ newVec) * camBack < 0) ^ (camBack.z() < 0)) angle *= -1; } rotation = osg::Quat(angle, axis); } else if (mDragMode == DragMode_Scale || mDragMode == DragMode_Scale_Snap) { osg::Vec3f center = getScreenCoords(getSelectionCenter(selection)); // Calculate scaling distance/rate int widgetHeight = getWorldspaceWidget().height(); float dx = pos.x() - center.x(); float dy = (widgetHeight - pos.y()) - center.y(); float dist = std::sqrt(dx * dx + dy * dy); float scale = dist / mUnitScaleDist; // Only uniform scaling is currently supported offset = osg::Vec3f(scale, scale, scale); } else if (mSelectionMode->getCurrentId() == "cube-centre") { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); mSelectionMode->drawSelectionCubeCentre(mousePlanePoint); return; } else if (mSelectionMode->getCurrentId() == "cube-corner") { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); mSelectionMode->drawSelectionCubeCorner(mousePlanePoint); return; } else if (mSelectionMode->getCurrentId() == "sphere") { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); mSelectionMode->drawSelectionSphere(mousePlanePoint); return; } int i = 0; // Apply for (std::vector>::iterator iter(selection.begin()); iter != selection.end(); ++iter, i++) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) { if (mDragMode == DragMode_Move || mDragMode == DragMode_Move_Snap) { ESM::Position position = objectTag->mObject->getPosition(); osg::Vec3f mousePos = getMousePlaneCoords(pos, getProjectionSpaceCoords(mDragStart)); float addToX = mousePos.x() - mDragStart.x(); float addToY = mousePos.y() - mDragStart.y(); float addToZ = mousePos.z() - mDragStart.z(); position.pos[0] = mObjectsAtDragStart[i].x() + addToX; position.pos[1] = mObjectsAtDragStart[i].y() + addToY; position.pos[2] = mObjectsAtDragStart[i].z() + addToZ; if (mDragMode == DragMode_Move_Snap) { double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble(); if (snapTarget) { osg::Vec3 translation(addToX, addToY, addToZ); auto snapTargetPosition = snapTarget->mObject->getPosition(); auto newPosition = calculateSnapPositionRelativeToTarget(mObjectsAtDragStart[i], snapTargetPosition.asVec3(), snapTargetPosition.asRotationVec3(), translation, snap); position.pos[0] = newPosition[0]; position.pos[1] = newPosition[1]; position.pos[2] = newPosition[2]; } else { position.pos[0] = CSVRender::InstanceMode::roundFloatToMult(position.pos[0], snap); position.pos[1] = CSVRender::InstanceMode::roundFloatToMult(position.pos[1], snap); position.pos[2] = CSVRender::InstanceMode::roundFloatToMult(position.pos[2], snap); } } // XYZ-locking if (mDragAxis != -1) { for (int j = 0; j < 3; ++j) { if (j != mDragAxis) position.pos[j] = mObjectsAtDragStart[i][j]; } } objectTag->mObject->setPosition(position.pos); } else if (mDragMode == DragMode_Rotate || mDragMode == DragMode_Rotate_Snap) { ESM::Position position = objectTag->mObject->getPosition(); osg::Quat currentRot = eulerToQuat(osg::Vec3f(position.rot[0], position.rot[1], position.rot[2])); osg::Quat combined = currentRot * rotation; osg::Vec3f euler = quatToEuler(combined); // There appears to be a very rare rounding error that can cause asin to return NaN if (!euler.isNaN()) { position.rot[0] = euler.x(); position.rot[1] = euler.y(); position.rot[2] = euler.z(); } objectTag->mObject->setRotation(position.rot); } else if (mDragMode == DragMode_Scale || mDragMode == DragMode_Scale_Snap) { // Reset scale objectTag->mObject->setEdited(0); objectTag->mObject->setEdited(Object::Override_Scale); float scale = objectTag->mObject->getScale(); scale *= offset.x(); if (mDragMode == DragMode_Scale_Snap) { scale = CSVRender::InstanceMode::roundFloatToMult( scale, CSMPrefs::get()["3D Scene Editing"]["gridsnap-scale"].toDouble()); } objectTag->mObject->setScale(scale); } } } } void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) { std::vector> selection = getWorldspaceWidget().getEdited(Mask_Reference); auto* snapTarget = dynamic_cast(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()); QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); QString description; switch (mDragMode) { case DragMode_Move: description = "Move Instances"; break; case DragMode_Rotate: description = "Rotate Instances"; break; case DragMode_Scale: description = "Scale Instances"; break; case DragMode_Select_Only: handleSelectDrag(pos); return; break; case DragMode_Select_Add: handleSelectDrag(pos); return; break; case DragMode_Select_Remove: handleSelectDrag(pos); return; break; case DragMode_Select_Invert: handleSelectDrag(pos); return; break; case DragMode_Move_Snap: description = "Move Instances"; break; case DragMode_Rotate_Snap: description = "Rotate Instances"; break; case DragMode_Scale_Snap: description = "Scale Instances"; break; case DragMode_None: break; } CSMWorld::CommandMacro macro(undoStack, description); // Is this even supposed to be here? for (std::vector>::iterator iter(selection.begin()); iter != selection.end(); ++iter) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) { if (mDragMode == DragMode_Rotate_Snap) { ESM::Position position = objectTag->mObject->getPosition(); double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble(); float xOffset = 0; float yOffset = 0; float zOffset = 0; if (snapTarget) { auto snapTargetPosition = snapTarget->mObject->getPosition(); auto rotation = snapTargetPosition.rot; if (rotation) { xOffset = remainder(rotation[0], osg::DegreesToRadians(snap)); yOffset = remainder(rotation[1], osg::DegreesToRadians(snap)); zOffset = remainder(rotation[2], osg::DegreesToRadians(snap)); } } position.rot[0] = CSVRender::InstanceMode::roundFloatToMult(position.rot[0], osg::DegreesToRadians(snap)) + xOffset; position.rot[1] = CSVRender::InstanceMode::roundFloatToMult(position.rot[1], osg::DegreesToRadians(snap)) + yOffset; position.rot[2] = CSVRender::InstanceMode::roundFloatToMult(position.rot[2], osg::DegreesToRadians(snap)) + zOffset; objectTag->mObject->setRotation(position.rot); } objectTag->mObject->apply(macro); } } mObjectsAtDragStart.clear(); mDragMode = DragMode_None; } void CSVRender::InstanceMode::dragAborted() { getWorldspaceWidget().reset(Mask_Reference); mDragMode = DragMode_None; } void CSVRender::InstanceMode::dragWheel(int diff, double speedFactor) { if (mDragMode == DragMode_Move || mDragMode == DragMode_Move_Snap) { osg::Vec3f eye; osg::Vec3f centre; osg::Vec3f up; getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt(eye, centre, up); osg::Vec3f offset = centre - eye; offset.normalize(); offset *= diff * speedFactor; std::vector> selection = getWorldspaceWidget().getEdited(Mask_Reference); auto snapTarget = dynamic_cast(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()); int j = 0; for (std::vector>::iterator iter(selection.begin()); iter != selection.end(); ++iter, j++) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) { ESM::Position position = objectTag->mObject->getPosition(); auto preMovedObjectPosition = position.asVec3(); for (int i = 0; i < 3; ++i) position.pos[i] += offset[i]; if (mDragMode == DragMode_Move_Snap) { double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble(); if (snapTarget) { osg::Vec3 translation(snap, snap, snap); auto snapTargetPosition = snapTarget->mObject->getPosition(); auto newPosition = calculateSnapPositionRelativeToTarget(preMovedObjectPosition, snapTargetPosition.asVec3(), snapTargetPosition.asRotationVec3(), translation, snap); position.pos[0] = newPosition[0]; position.pos[1] = newPosition[1]; position.pos[2] = newPosition[2]; } else { position.pos[0] = CSVRender::InstanceMode::roundFloatToMult(position.pos[0], snap); position.pos[1] = CSVRender::InstanceMode::roundFloatToMult(position.pos[1], snap); position.pos[2] = CSVRender::InstanceMode::roundFloatToMult(position.pos[2], snap); } } objectTag->mObject->setPosition(position.pos); osg::Vec3f thisPoint(position.pos[0], position.pos[1], position.pos[2]); mDragStart = getMousePlaneCoords( getWorldspaceWidget().mapFromGlobal(QCursor::pos()), getProjectionSpaceCoords(thisPoint)); mObjectsAtDragStart[j] = thisPoint; } } } } void CSVRender::InstanceMode::dragEnterEvent(QDragEnterEvent* event) { if (const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData())) { if (!mime->fromDocument(getWorldspaceWidget().getDocument())) return; if (mime->holdsType(CSMWorld::UniversalId::Type_Referenceable)) event->accept(); } } void CSVRender::InstanceMode::dropEvent(QDropEvent* event) { if (const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData())) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); if (!mime->fromDocument(document)) return; WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->pos(), getWorldspaceWidget().getInteractionMask()); std::string cellId = getWorldspaceWidget().getCellId(hit.worldPos); CSMWorld::IdTree& cellTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Cells)); const bool noCell = document.getData().getCells().searchId(ESM::RefId::stringRefId(cellId)) == -1; if (noCell) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-drop"].toString(); // target cell does not exist if (mode == "Discard") return; if (mode == "Create cell and insert") { std::unique_ptr createCommand(new CSMWorld::CreateCommand(cellTable, cellId)); int parentIndex = cellTable.findColumnIndex(CSMWorld::Columns::ColumnId_Cell); int index = cellTable.findNestedColumnIndex(parentIndex, CSMWorld::Columns::ColumnId_Interior); createCommand->addNestedValue(parentIndex, index, false); document.getUndoStack().push(createCommand.release()); if (CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); selection.add(CSMWorld::CellCoordinates::fromId(cellId).first); paged->setCellSelection(selection); } } } else if (CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); if (!selection.has(CSMWorld::CellCoordinates::fromId(cellId).first)) { // target cell exists, but is not shown std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-visible-drop"].toString(); if (mode == "Discard") return; if (mode == "Show cell and insert") { selection.add(CSMWorld::CellCoordinates::fromId(cellId).first); paged->setCellSelection(selection); } } } CSMWorld::IdTable& referencesTable = dynamic_cast( *document.getData().getTableModel(CSMWorld::UniversalId::Type_References)); bool dropped = false; std::vector ids = mime->getData(); for (std::vector::const_iterator iter(ids.begin()); iter != ids.end(); ++iter) if (mime->isReferencable(iter->getType())) { // create reference std::unique_ptr createCommand( new CSMWorld::CreateCommand(referencesTable, document.getData().getReferences().getNewId())); createCommand->addValue(referencesTable.findColumnIndex(CSMWorld::Columns::ColumnId_Cell), QString::fromUtf8(cellId.c_str())); createCommand->addValue( referencesTable.findColumnIndex(CSMWorld::Columns::ColumnId_PositionXPos), hit.worldPos.x()); createCommand->addValue( referencesTable.findColumnIndex(CSMWorld::Columns::ColumnId_PositionYPos), hit.worldPos.y()); createCommand->addValue( referencesTable.findColumnIndex(CSMWorld::Columns::ColumnId_PositionZPos), hit.worldPos.z()); createCommand->addValue(referencesTable.findColumnIndex(CSMWorld::Columns::ColumnId_ReferenceableId), QString::fromUtf8(iter->getId().c_str())); document.getUndoStack().push(createCommand.release()); dropped = true; } if (dropped) event->accept(); } } int CSVRender::InstanceMode::getSubMode() const { return mSubMode ? getSubModeFromId(mSubMode->getCurrentId()) : 0; } void CSVRender::InstanceMode::subModeChanged(const std::string& id) { mSubModeId = id; getWorldspaceWidget().abortDrag(); getWorldspaceWidget().setSubMode(getSubModeFromId(id), Mask_Reference); } void CSVRender::InstanceMode::handleSelectDrag(const QPoint& pos) { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); mSelectionMode->dragEnded(mousePlanePoint, mDragMode); mDragMode = DragMode_None; } void CSVRender::InstanceMode::deleteSelectedInstances() { std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) return; CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& referencesTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_References)); QUndoStack& undoStack = document.getUndoStack(); CSMWorld::CommandMacro macro(undoStack, "Delete Instances"); for (osg::ref_ptr tag : selection) if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) macro.push(new CSMWorld::DeleteCommand(referencesTable, objectTag->mObject->getReferenceId())); getWorldspaceWidget().clearSelection(Mask_Reference); } void CSVRender::InstanceMode::cloneSelectedInstances() { std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) return; CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& referencesTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_References)); QUndoStack& undoStack = document.getUndoStack(); CSMWorld::CommandMacro macro(undoStack, "Clone Instances"); for (osg::ref_ptr tag : selection) if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) { macro.push(new CSMWorld::CloneCommand(referencesTable, objectTag->mObject->getReferenceId(), "ref#" + std::to_string(referencesTable.rowCount()), CSMWorld::UniversalId::Type_Reference)); } // getWorldspaceWidget().clearSelection(Mask_Reference); } void CSVRender::InstanceMode::dropInstance(CSVRender::Object* object, float dropHeight) { object->setEdited(Object::Override_Position); ESM::Position position = object->getPosition(); position.pos[2] -= dropHeight; object->setPosition(position.pos); } float CSVRender::InstanceMode::calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight) { osg::Vec3d point = object->getPosition().asVec3(); osg::Vec3d start = point; start.z() += objectHeight; osg::Vec3d end = point; end.z() = std::numeric_limits::lowest(); osg::ref_ptr intersector( new osgUtil::LineSegmentIntersector(osgUtil::Intersector::MODEL, start, end)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); if (dropMode & Terrain) visitor.setTraversalMask(Mask_Terrain); if (dropMode & Collision) visitor.setTraversalMask(Mask_Terrain | Mask_Reference); mParentNode->accept(visitor); osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); if (it != intersector->getIntersections().end()) { osgUtil::LineSegmentIntersector::Intersection intersection = *it; float collisionLevel = intersection.getWorldIntersectPoint().z(); return point.z() - collisionLevel + objectHeight; } return 0.0f; } void CSVRender::InstanceMode::dropSelectedInstancesToCollision() { handleDropMethod(Collision, "Drop instances to next collision"); } void CSVRender::InstanceMode::dropSelectedInstancesToTerrain() { handleDropMethod(Terrain, "Drop instances to terrain level"); } void CSVRender::InstanceMode::dropSelectedInstancesToCollisionSeparately() { handleDropMethod(CollisionSep, "Drop instances to next collision level separately"); } void CSVRender::InstanceMode::dropSelectedInstancesToTerrainSeparately() { handleDropMethod(TerrainSep, "Drop instances to terrain level separately"); } void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg) { std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) return; CSMDoc::Document& document = getWorldspaceWidget().getDocument(); QUndoStack& undoStack = document.getUndoStack(); CSMWorld::CommandMacro macro(undoStack, commandMsg); DropObjectHeightHandler dropObjectDataHandler(&getWorldspaceWidget()); if (dropMode & Separate) { int counter = 0; for (osg::ref_ptr tag : selection) if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) { float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; float dropHeight = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); dropInstance(objectTag->mObject, dropHeight); objectTag->mObject->apply(macro); counter++; } } else { float smallestDropHeight = std::numeric_limits::max(); int counter = 0; for (osg::ref_ptr tag : selection) if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) { float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; float thisDrop = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); if (thisDrop < smallestDropHeight) smallestDropHeight = thisDrop; counter++; } for (osg::ref_ptr tag : selection) if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) { dropInstance(objectTag->mObject, smallestDropHeight); objectTag->mObject->apply(macro); } } } CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* worldspacewidget) : mWorldspaceWidget(worldspacewidget) { std::vector> selection = mWorldspaceWidget->getSelection(Mask_Reference); for (osg::ref_ptr tag : selection) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) { osg::ref_ptr objectNodeWithGUI = objectTag->mObject->getRootNode(); osg::ref_ptr objectNodeWithoutGUI = objectTag->mObject->getBaseNode(); osg::ComputeBoundsVisitor computeBounds; computeBounds.setTraversalMask(Mask_Reference); objectNodeWithoutGUI->accept(computeBounds); osg::BoundingBox bounds = computeBounds.getBoundingBox(); float boundingBoxOffset = 0.0f; if (bounds.valid()) boundingBoxOffset = bounds.zMin(); mObjectHeights.emplace_back(boundingBoxOffset); mOldMasks.emplace_back(objectNodeWithGUI->getNodeMask()); objectNodeWithGUI->setNodeMask(0); } } } CSVRender::DropObjectHeightHandler::~DropObjectHeightHandler() { std::vector> selection = mWorldspaceWidget->getSelection(Mask_Reference); int counter = 0; for (osg::ref_ptr tag : selection) { if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) { osg::ref_ptr objectNodeWithGUI = objectTag->mObject->getRootNode(); objectNodeWithGUI->setNodeMask(mOldMasks[counter]); counter++; } } }