Merge remote-tracking branch 'aesylwinn/InstanceRotateAndScale'

This commit is contained in:
Marc Zinnschlag 2016-08-18 09:13:43 +02:00
commit 2fef8948f1
5 changed files with 397 additions and 76 deletions

View file

@ -27,6 +27,68 @@ 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;
}
osg::Vec3f CSVRender::InstanceMode::getSelectionCenter(const std::vector<osg::ref_ptr<TagBase> >& selection) const
{
osg::Vec3f center = osg::Vec3f(0, 0, 0);
int objectCount = 0;
for (std::vector<osg::ref_ptr<TagBase> >::const_iterator iter (selection.begin()); iter!=selection.end(); ++iter)
{
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (iter->get()))
{
const ESM::Position& position = objectTag->mObject->getPosition();
center += osg::Vec3f(position.pos[0], position.pos[1], position.pos[2]);
++objectCount;
}
}
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;
}
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 +106,16 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar)
"Rotate selected instances"
"<ul><li>Use {scene-edit-primary} to rotate instances freely</li>"
"<li>Use {scene-edit-secondary} to rotate instances within the grid</li>"
"<li>The center of the view acts as the axis of rotation</li>"
"</ul>"
"<font color=Red>Not implemented yet</font color>");
"<font color=Red>Grid rotate not implemented yet</font color>");
mSubMode->addButton (":placeholder", "scale",
"Scale selected instances"
"<ul><li>Use {scene-edit-primary} to scale instances freely</li>"
"<li>Use {scene-edit-secondary} to scale instances along the grid</li>"
"<li>The scaling rate is based on how close the start of a drag is to the center of the screen</li>"
"</ul>"
"<font color=Red>Not implemented yet</font color>");
"<font color=Red>Grid scale not implemented yet</font color>");
mSubMode->setButton (mSubModeId);
@ -152,33 +216,60 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos)
return false;
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue())
{
getWorldspaceWidget().clearSelection (Mask_Reference);
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (hit.tag.get()))
{
CSVRender::Object* object = objectTag->mObject;
object->setSelected (true);
}
}
std::vector<osg::ref_ptr<TagBase> > selection =
getWorldspaceWidget().getSelection (Mask_Reference);
std::vector<osg::ref_ptr<TagBase> > selection = getWorldspaceWidget().getSelection (Mask_Reference);
if (selection.empty())
return false;
{
// 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<CSVRender::ObjectTag *> (hit.tag.get()))
{
CSVRender::Object* object = objectTag->mObject;
object->setSelected (true);
}
}
selection = getWorldspaceWidget().getSelection (Mask_Reference);
if (selection.empty())
return false;
}
for (std::vector<osg::ref_ptr<TagBase> >::iterator iter (selection.begin());
iter!=selection.end(); ++iter)
{
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (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 factor
std::vector<osg::ref_ptr<TagBase> > selection = getWorldspaceWidget().getEdited (Mask_Reference);
osg::Vec3f center = getScreenCoords(getSelectionCenter(selection));
int widgetHeight = getWorldspaceWidget().height();
float dx = pos.x() - center.x();
float dy = (widgetHeight - pos.y()) - center.y();
mUnitScaleDist = std::sqrt(dx * dx + dy * dy);
}
}
}
// \todo check for sub-mode
if (CSVRender::ObjectMarkerTag *objectTag = dynamic_cast<CSVRender::ObjectMarkerTag *> (hit.tag.get()))
{
mDragAxis = objectTag->mAxis;
@ -186,8 +277,6 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos)
else
mDragAxis = -1;
mDragMode = DragMode_Move;
return true;
}
@ -201,48 +290,160 @@ 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;
std::vector<osg::ref_ptr<TagBase> > selection = getWorldspaceWidget().getEdited (Mask_Reference);
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<osg::ref_ptr<TagBase> > selection =
getWorldspaceWidget().getEdited (Mask_Reference);
for (std::vector<osg::ref_ptr<TagBase> >::iterator iter (selection.begin());
iter!=selection.end(); ++iter)
{
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (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);
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)
{
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);
}
// Apply
for (std::vector<osg::ref_ptr<TagBase> >::iterator iter (selection.begin()); iter!=selection.end(); ++iter)
{
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (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 +459,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;
}