diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp
index 4eb9ea388..80687359e 100644
--- a/apps/opencs/view/render/instancemode.cpp
+++ b/apps/opencs/view/render/instancemode.cpp
@@ -27,6 +27,38 @@ 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
+{
+ const float Pi = 3.14159265f;
+
+ 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) ? (Pi / 2) : (-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;
+}
+
CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent)
: EditMode (worldspaceWidget, QIcon (":placeholder"), Mask_Reference, "Instance editing",
parent), mSubMode (0), mSubModeId ("move"), mSelectionMode (0), mDragMode (DragMode_None),
@@ -44,14 +76,16 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar)
"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
"
"
"
- "Not implemented yet");
+ "Grid rotate not implemented yet");
mSubMode->addButton (":placeholder", "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
"
"
"
- "Not implemented yet");
+ "Grid scale not implemented yet");
mSubMode->setButton (mSubModeId);
@@ -151,6 +185,7 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos)
if (mDragMode!=DragMode_None || mLocked)
return false;
+
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue())
{
@@ -173,12 +208,33 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos)
{
if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get()))
{
- objectTag->mObject->setEdited (Object::Override_Position);
+ if (mSubModeId == "move")
+ {
+ objectTag->mObject->setEdited (Object::Override_Position);
+ 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
+ int widgetWidth = getWorldspaceWidget().width();
+ int widgetHeight = getWorldspaceWidget().height();
+
+ int x = pos.x() - widgetWidth / 2;
+ int y = pos.y() - widgetHeight / 2;
+
+ mUnitScaleDist = std::sqrt(x * x + y * y);
+ }
}
}
- // \todo check for sub-mode
-
if (CSVRender::ObjectMarkerTag *objectTag = dynamic_cast (hit.tag.get()))
{
mDragAxis = objectTag->mAxis;
@@ -186,8 +242,6 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos)
else
mDragAxis = -1;
- mDragMode = DragMode_Move;
-
return true;
}
@@ -201,48 +255,139 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos)
void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor)
{
- osg::Vec3f eye;
- osg::Vec3f centre;
- osg::Vec3f up;
-
- getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up);
-
osg::Vec3f offset;
+ osg::Quat rotation;
- if (diffY)
- offset += up * diffY * speedFactor;
-
- if (diffX)
- offset += ((centre-eye) ^ up) * diffX * speedFactor;
-
- switch (mDragMode)
+ if (mDragMode == DragMode_Move)
{
- case DragMode_Move:
+ osg::Vec3f eye, centre, up;
+ getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up);
+
+ if (diffY)
{
- if (mDragAxis!=-1)
- for (int i=0; i<3; ++i)
- if (i!=mDragAxis)
- offset[i] = 0;
-
- std::vector > selection =
- getWorldspaceWidget().getEdited (Mask_Reference);
-
- for (std::vector >::iterator iter (selection.begin());
- iter!=selection.end(); ++iter)
- {
- if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get()))
- {
- ESM::Position position = objectTag->mObject->getPosition();
- for (int i=0; i<3; ++i)
- position.pos[i] += offset[i];
- objectTag->mObject->setPosition (position.pos);
- }
- }
-
- break;
+ offset += up * diffY * speedFactor;
+ }
+ if (diffX)
+ {
+ offset += ((centre-eye) ^ up) * diffX * speedFactor;
}
- case DragMode_None: break;
+ if (mDragAxis!=-1)
+ {
+ for (int i=0; i<3; ++i)
+ {
+ if (i!=mDragAxis)
+ offset[i] = 0;
+ }
+ }
+ }
+ else if (mDragMode == DragMode_Rotate)
+ {
+ osg::Vec3f eye, centre, up;
+ getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up);
+
+ osg::Vec3f camBack = eye - centre;
+
+ // Convert coordinate system
+ int widgetWidth = getWorldspaceWidget().width();
+ int widgetHeight = getWorldspaceWidget().height();
+
+ int newX = pos.x() - widgetWidth / 2;
+ int newY = -pos.y() + widgetHeight / 2;
+
+ int oldX = newX - diffX;
+ int 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
+ float angle = std::acos(oldVec * newVec) * speedFactor;
+ if (((oldVec ^ newVec) * camBack < 0) ^ (camBack.z() < 0))
+ angle *= -1;
+
+ osg::Vec3f axis;
+ if (mDragAxis != -1)
+ {
+ for (int i = 0; i < 3; ++i)
+ {
+ if (i == mDragAxis)
+ axis[i] = 1;
+ else
+ axis[i] = 0;
+ }
+
+ if (camBack * axis < 0)
+ axis *= -1;
+ }
+ else
+ axis = camBack;
+
+ rotation = osg::Quat(angle, axis);
+ }
+ else if (mDragMode == DragMode_Scale)
+ {
+ int widgetWidth = getWorldspaceWidget().width();
+ int widgetHeight = getWorldspaceWidget().height();
+
+ int x = pos.x() - widgetWidth / 2;
+ int y = pos.y() - widgetHeight / 2;
+
+ float dist = std::sqrt(x * x + y * y);
+ float scale = dist / mUnitScaleDist;
+
+ // Only uniform scaling is currently supported
+ offset = osg::Vec3f(scale, scale, scale);
+ }
+
+ std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference);
+ for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter)
+ {
+ if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get()))
+ {
+ if (mDragMode == DragMode_Move)
+ {
+ ESM::Position position = objectTag->mObject->getPosition();
+ for (int i=0; i<3; ++i)
+ {
+ position.pos[i] += offset[i];
+ }
+
+ objectTag->mObject->setPosition(position.pos);
+ }
+ else if (mDragMode == DragMode_Rotate)
+ {
+ 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)
+ {
+ // Reset scale
+ objectTag->mObject->setEdited(0);
+ objectTag->mObject->setEdited(Object::Override_Scale);
+
+ float scale = objectTag->mObject->getScale();
+ scale *= offset.x();
+
+ objectTag->mObject->setScale (scale);
+ }
+ }
}
}
@@ -258,6 +403,8 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos)
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_None: break;
}
diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp
index 4f7937b51..dd2755237 100644
--- a/apps/opencs/view/render/instancemode.hpp
+++ b/apps/opencs/view/render/instancemode.hpp
@@ -1,6 +1,9 @@
#ifndef CSV_RENDER_INSTANCEMODE_H
#define CSV_RENDER_INSTANCEMODE_H
+#include
+#include
+
#include "editmode.hpp"
namespace CSVWidget
@@ -19,7 +22,9 @@ namespace CSVRender
enum DragMode
{
DragMode_None,
- DragMode_Move
+ DragMode_Move,
+ DragMode_Rotate,
+ DragMode_Scale
};
CSVWidget::SceneToolMode *mSubMode;
@@ -28,9 +33,13 @@ namespace CSVRender
DragMode mDragMode;
int mDragAxis;
bool mLocked;
+ float mUnitScaleDist;
int getSubModeFromId (const std::string& id) const;
+ osg::Vec3f quatToEuler(const osg::Quat& quat) const;
+ osg::Quat eulerToQuat(const osg::Vec3f& euler) const;
+
public:
InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent = 0);
diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp
index cc151ead9..3a6640c54 100644
--- a/apps/opencs/view/render/object.cpp
+++ b/apps/opencs/view/render/object.cpp
@@ -29,6 +29,13 @@
#include "mask.hpp"
+
+const float CSVRender::Object::MarkerShaftWidth = 30;
+const float CSVRender::Object::MarkerShaftBaseLength = 70;
+const float CSVRender::Object::MarkerHeadWidth = 50;
+const float CSVRender::Object::MarkerHeadLength = 50;
+
+
namespace
{
@@ -179,7 +186,21 @@ void CSVRender::Object::updateMarker()
{
if (mSubMode==0)
{
- mMarker[i] = makeMarker (i);
+ mMarker[i] = makeMoveOrScaleMarker (i);
+ mMarker[i]->setUserData(new ObjectMarkerTag (this, i));
+
+ mRootNode->addChild (mMarker[i]);
+ }
+ else if (mSubMode==1)
+ {
+ mMarker[i] = makeRotateMarker (i);
+ mMarker[i]->setUserData(new ObjectMarkerTag (this, i));
+
+ mRootNode->addChild (mMarker[i]);
+ }
+ else if (mSubMode==2)
+ {
+ mMarker[i] = makeMoveOrScaleMarker (i);
mMarker[i]->setUserData(new ObjectMarkerTag (this, i));
mRootNode->addChild (mMarker[i]);
@@ -188,16 +209,11 @@ void CSVRender::Object::updateMarker()
}
}
-osg::ref_ptr CSVRender::Object::makeMarker (int axis)
+osg::ref_ptr CSVRender::Object::makeMoveOrScaleMarker (int axis)
{
osg::ref_ptr geometry (new osg::Geometry);
- const float shaftWidth = 10;
- const float shaftBaseLength = 50;
- const float headWidth = 30;
- const float headLength = 30;
-
- float shaftLength = shaftBaseLength + mBaseNode->getBound().radius();
+ float shaftLength = MarkerShaftBaseLength + mBaseNode->getBound().radius();
// shaft
osg::Vec3Array *vertices = new osg::Vec3Array;
@@ -206,20 +222,20 @@ osg::ref_ptr CSVRender::Object::makeMarker (int axis)
{
float length = i ? shaftLength : 0;
- vertices->push_back (getMarkerPosition (-shaftWidth/2, -shaftWidth/2, length, axis));
- vertices->push_back (getMarkerPosition (-shaftWidth/2, shaftWidth/2, length, axis));
- vertices->push_back (getMarkerPosition (shaftWidth/2, shaftWidth/2, length, axis));
- vertices->push_back (getMarkerPosition (shaftWidth/2, -shaftWidth/2, length, axis));
+ vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis));
+ vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis));
+ vertices->push_back (getMarkerPosition (MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis));
+ vertices->push_back (getMarkerPosition (MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis));
}
// head backside
- vertices->push_back (getMarkerPosition (-headWidth/2, -headWidth/2, shaftLength, axis));
- vertices->push_back (getMarkerPosition (-headWidth/2, headWidth/2, shaftLength, axis));
- vertices->push_back (getMarkerPosition (headWidth/2, headWidth/2, shaftLength, axis));
- vertices->push_back (getMarkerPosition (headWidth/2, -headWidth/2, shaftLength, axis));
+ vertices->push_back (getMarkerPosition (-MarkerHeadWidth/2, -MarkerHeadWidth/2, shaftLength, axis));
+ vertices->push_back (getMarkerPosition (-MarkerHeadWidth/2, MarkerHeadWidth/2, shaftLength, axis));
+ vertices->push_back (getMarkerPosition (MarkerHeadWidth/2, MarkerHeadWidth/2, shaftLength, axis));
+ vertices->push_back (getMarkerPosition (MarkerHeadWidth/2, -MarkerHeadWidth/2, shaftLength, axis));
// head
- vertices->push_back (getMarkerPosition (0, 0, shaftLength+headLength, axis));
+ vertices->push_back (getMarkerPosition (0, 0, shaftLength+MarkerHeadLength, axis));
geometry->setVertexArray (vertices);
@@ -285,6 +301,87 @@ osg::ref_ptr CSVRender::Object::makeMarker (int axis)
return geode;
}
+osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis)
+{
+ const float Pi = 3.14159265f;
+
+ const float InnerRadius = mBaseNode->getBound().radius();
+ const float OuterRadius = InnerRadius + MarkerShaftWidth;
+
+ const float SegmentDistance = 100.f;
+ const size_t SegmentCount = std::min(64, std::max(8, (int)(OuterRadius * 2 * Pi / SegmentDistance)));
+ const size_t VerticesPerSegment = 4;
+ const size_t IndicesPerSegment = 24;
+
+ const size_t VertexCount = SegmentCount * VerticesPerSegment;
+ const size_t IndexCount = SegmentCount * IndicesPerSegment;
+
+ const float Angle = 2 * Pi / SegmentCount;
+
+ const unsigned short IndexPattern[IndicesPerSegment] =
+ {
+ 0, 4, 5, 0, 5, 1,
+ 2, 6, 4, 2, 4, 0,
+ 3, 7, 6, 3, 6, 2,
+ 1, 5, 7, 1, 7, 3
+ };
+
+
+ osg::ref_ptr geometry = new osg::Geometry();
+
+ osg::ref_ptr vertices = new osg::Vec3Array(VertexCount);
+ osg::ref_ptr colors = new osg::Vec4Array(1);
+ osg::ref_ptr primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES,
+ IndexCount);
+
+ for (size_t i = 0; i < SegmentCount; ++i)
+ {
+ size_t index = i * VerticesPerSegment;
+
+ float innerX = InnerRadius * std::cos(i * Angle);
+ float innerY = InnerRadius * std::sin(i * Angle);
+
+ float outerX = OuterRadius * std::cos(i * Angle);
+ float outerY = OuterRadius * std::sin(i * Angle);
+
+ vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis);
+ vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis);
+ vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis);
+ vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis);
+ }
+
+ colors->at(0) = osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, axis==2 ? 1.0f : 0.2f, 1.0f);
+
+ for (size_t i = 0; i < SegmentCount; ++i)
+ {
+ size_t indices[IndicesPerSegment];
+ for (size_t j = 0; j < IndicesPerSegment; ++j)
+ {
+ indices[j] = i * VerticesPerSegment + j;
+
+ if (indices[j] >= VertexCount)
+ indices[j] -= VertexCount;
+ }
+
+ size_t offset = i * IndicesPerSegment;
+ for (size_t j = 0; j < IndicesPerSegment; ++j)
+ {
+ primitives->setElement(offset++, indices[IndexPattern[j]]);
+ }
+ }
+
+ geometry->setVertexArray(vertices);
+ geometry->setColorArray(colors, osg::Array::BIND_OVERALL);
+ geometry->addPrimitiveSet(primitives);
+
+ geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF);
+
+ osg::ref_ptr geode = new osg::Geode();
+ geode->addDrawable (geometry);
+
+ return geode;
+}
+
osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int axis)
{
switch (axis)
@@ -494,7 +591,7 @@ ESM::Position CSVRender::Object::getPosition() const
float CSVRender::Object::getScale() const
{
- return mOverrideFlags & Override_Scale ? mScaleOverride : getReference().mScale;
+ return (mOverrideFlags & Override_Scale) ? mScaleOverride : getReference().mScale;
}
void CSVRender::Object::setPosition (const float position[3])
diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp
index 54a284fa3..d3cba6c55 100644
--- a/apps/opencs/view/render/object.hpp
+++ b/apps/opencs/view/render/object.hpp
@@ -78,6 +78,11 @@ namespace CSVRender
private:
+ static const float MarkerShaftWidth;
+ static const float MarkerShaftBaseLength;
+ static const float MarkerHeadWidth;
+ static const float MarkerHeadLength;
+
CSMWorld::Data& mData;
std::string mReferenceId;
std::string mReferenceableId;
@@ -89,7 +94,7 @@ namespace CSVRender
Resource::ResourceSystem* mResourceSystem;
bool mForceBaseToZero;
ESM::Position mPositionOverride;
- int mScaleOverride;
+ float mScaleOverride;
int mOverrideFlags;
osg::ref_ptr mMarker[3];
int mSubMode;
@@ -115,7 +120,8 @@ namespace CSVRender
void updateMarker();
- osg::ref_ptr makeMarker (int axis);
+ osg::ref_ptr makeMoveOrScaleMarker (int axis);
+ osg::ref_ptr makeRotateMarker (int axis);
osg::Vec3f getMarkerPosition (float x, float y, float z, int axis);