Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524#include "object.hpp"
#include <algorithm>#include <cmath>#include <exception>#include <stddef.h>#include <stdexcept>#include <string>#include <utility>
#include <osg/PolygonMode>#include <osg/PolygonOffset>#include <osg/ShapeDrawable>
#include <apps/opencs/model/prefs/category.hpp>#include <apps/opencs/model/prefs/setting.hpp>#include <apps/opencs/model/world/columns.hpp>#include <apps/opencs/model/world/record.hpp>#include <apps/opencs/model/world/ref.hpp>#include <apps/opencs/model/world/refcollection.hpp>#include <apps/opencs/model/world/refidcollection.hpp>#include <apps/opencs/model/world/universalid.hpp>#include <apps/opencs/view/render/tagbase.hpp>
#include "../../model/prefs/state.hpp"#include "../../model/world/cellcoordinates.hpp"#include "../../model/world/commandmacro.hpp"#include "../../model/world/commands.hpp"#include "../../model/world/data.hpp"
#include <components/debug/debuglog.hpp>#include <components/esm/esmbridge.hpp>#include <components/resource/resourcesystem.hpp>#include <components/resource/scenemanager.hpp>#include <components/sceneutil/lightcommon.hpp>#include <components/sceneutil/lightmanager.hpp>#include <components/sceneutil/lightutil.hpp>#include <components/shader/shadermanager.hpp>
#include "actor.hpp"#include "mask.hpp"
namespace CSVRender{ struct WorldspaceHitResult;}
namespace ESM{ struct Light;}
namespace{
osg::ref_ptr<osg::Group> createErrorCube() { osg::ref_ptr<osg::Box> shape(new osg::Box(osg::Vec3f(0, 0, 0), 50.f)); osg::ref_ptr<osg::ShapeDrawable> shapedrawable(new osg::ShapeDrawable); shapedrawable->setShape(shape);
osg::ref_ptr<osg::Group> group(new osg::Group); group->addChild(shapedrawable); return group; }
}
CSVRender::ObjectTag::ObjectTag(Object* object) : TagBase(Mask_Reference) , mObject(object){}
QString CSVRender::ObjectTag::getToolTip(bool /*hideBasics*/, const WorldspaceHitResult& /*hit*/) const{ return QString::fromUtf8(mObject->getReferenceableId().c_str());}
void CSVRender::Object::clear() {}
void CSVRender::Object::update(){ clear();
const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); const int typeIndex = referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); const int modelIndex = referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_Model);
int index = referenceables.searchId(mReferenceableId); const ESM::Light* light = nullptr;
mBaseNode->removeChildren(0, mBaseNode->getNumChildren());
if (index == -1) { mBaseNode->addChild(createErrorCube()); return; }
/// \todo check for Deleted state (error 1)
int recordType = referenceables.getData(index, typeIndex).toInt(); std::string model = referenceables.getData(index, modelIndex).toString().toUtf8().constData();
if (recordType == CSMWorld::UniversalId::Type_Light) { light = &dynamic_cast<const CSMWorld::Record<ESM::Light>&>(referenceables.getRecord(index)).get(); if (model.empty()) model = "marker_light.nif"; }
if (recordType == CSMWorld::UniversalId::Type_CreatureLevelledList) { if (model.empty()) model = "marker_creature.nif"; }
try { if (recordType == CSMWorld::UniversalId::Type_Npc || recordType == CSMWorld::UniversalId::Type_Creature) { if (!mActor) mActor = std::make_unique<Actor>(mReferenceableId, mData); mActor->update(); mBaseNode->addChild(mActor->getBaseNode()); } else if (!model.empty()) { constexpr VFS::Path::NormalizedView meshes("meshes"); VFS::Path::Normalized path(meshes); path /= model; mResourceSystem->getSceneManager()->getInstance(path, mBaseNode); } else { throw std::runtime_error(mReferenceableId.getRefIdString() + " has no model"); } } catch (std::exception& e) { // TODO: use error marker mesh Log(Debug::Error) << e.what(); }
if (light) { bool isExterior = false; // FIXME SceneUtil::addLight(mBaseNode, SceneUtil::LightCommon(*light), Mask_Lighting, isExterior); }}
void CSVRender::Object::adjustTransform(){ if (mReferenceId.empty()) return;
ESM::Position position = getPosition();
// position mRootNode->setPosition( mForceBaseToZero ? osg::Vec3() : osg::Vec3f(position.pos[0], position.pos[1], position.pos[2]));
// orientation osg::Quat xr(-position.rot[0], osg::Vec3f(1, 0, 0)); osg::Quat yr(-position.rot[1], osg::Vec3f(0, 1, 0)); osg::Quat zr(-position.rot[2], osg::Vec3f(0, 0, 1)); mBaseNode->setAttitude(zr * yr * xr);
float scale = getScale();
mBaseNode->setScale(osg::Vec3(scale, scale, scale));}
const CSMWorld::CellRef& CSVRender::Object::getReference() const{ if (mReferenceId.empty()) throw std::logic_error("object does not represent a reference");
return mData.getReferences().getRecord(mReferenceId).get();}
CSVRender::Object::Object( CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero) : mData(data) , mParentNode(parentNode) , mResourceSystem(data.getResourceSystem().get()) , mForceBaseToZero(forceBaseToZero){ mRootNode = new osg::PositionAttitudeTransform;
mBaseNode = new osg::PositionAttitudeTransform; mBaseNode->addCullCallback(new SceneUtil::LightListCallback);
mOutline = new osg::Group; mOutline->setNodeMask(0); osg::ref_ptr<osg::PolygonOffset> offset = new osg::PolygonOffset(1, 1); osg::ref_ptr<osg::PolygonMode> mode = new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); mOutline->getOrCreateStateSet()->addUniform(new osg::Uniform("color", osg::Vec4f(1, 1, 1, 1))); mOutline->getStateSet()->setAttributeAndModes(mode); mOutline->getStateSet()->setAttributeAndModes(offset); mOutline->getStateSet()->setAttributeAndModes( mResourceSystem->getSceneManager()->getShaderManager().getProgram("outline"), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); mOutline->addChild(mBaseNode);
mBaseNode->setUserData(new ObjectTag(this));
mRootNode->addChild(mBaseNode); mRootNode->addChild(mOutline);
parentNode->addChild(mRootNode);
mRootNode->setNodeMask(Mask_Reference); ESM::RefId refId = ESM::RefId::stringRefId(id); if (referenceable) { mReferenceableId = refId; } else { mReferenceId = refId; mReferenceableId = getReference().mRefID; }
adjustTransform(); update();}
CSVRender::Object::~Object(){ clear();
mParentNode->removeChild(mRootNode);}
void CSVRender::Object::setSelected(bool selected, const osg::Vec4f& color){ mSelected = selected;
if (mSnapTarget) { setSnapTarget(false); }
mOutline->setNodeMask(selected ? ~0u : 0);
if (selected) mOutline->getStateSet()->getUniform("color")->set(color);}
bool CSVRender::Object::getSelected() const{ return mSelected;}
void CSVRender::Object::setSnapTarget(bool isSnapTarget){ mSnapTarget = isSnapTarget;
if (mSelected) { setSelected(false); }
mOutline->setNodeMask(isSnapTarget ? ~0u : 0);
if (isSnapTarget) mOutline->getStateSet()->getUniform("color")->set(osg::Vec4f(1, 1, 0, 1));}
bool CSVRender::Object::getSnapTarget() const{ return mSnapTarget;}
osg::ref_ptr<osg::Group> CSVRender::Object::getRootNode(){ return mRootNode;}
osg::ref_ptr<osg::Group> CSVRender::Object::getBaseNode(){ return mBaseNode;}
bool CSVRender::Object::referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight){ const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables();
int index = referenceables.searchId(mReferenceableId);
if (index != -1 && index >= topLeft.row() && index <= bottomRight.row()) { adjustTransform(); update(); return true; }
return false;}
bool CSVRender::Object::referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end){ const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables();
int index = referenceables.searchId(mReferenceableId);
if (index != -1 && index >= start && index <= end) { // Deletion of referenceable-type objects is handled outside of Object. if (!mReferenceId.empty()) { adjustTransform(); update(); return true; } }
return false;}
bool CSVRender::Object::referenceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight){ if (mReferenceId.empty()) return false;
const CSMWorld::RefCollection& references = mData.getReferences();
int index = references.searchId(mReferenceId);
if (index != -1 && index >= topLeft.row() && index <= bottomRight.row()) { int columnIndex = references.findColumnIndex(CSMWorld::Columns::ColumnId_ReferenceableId);
adjustTransform();
if (columnIndex >= topLeft.column() && columnIndex <= bottomRight.row()) { mReferenceableId = ESM::RefId::stringRefId(references.getData(index, columnIndex).toString().toUtf8().constData());
update(); }
return true; }
return false;}
void CSVRender::Object::reloadAssets(){ update();}
std::string CSVRender::Object::getReferenceId() const{ return mReferenceId.getRefIdString();}
std::string CSVRender::Object::getReferenceableId() const{ return mReferenceableId.getRefIdString();}
osg::ref_ptr<CSVRender::TagBase> CSVRender::Object::getTag() const{ return static_cast<CSVRender::TagBase*>(mBaseNode->getUserData());}
bool CSVRender::Object::isEdited() const{ return mOverrideFlags;}
void CSVRender::Object::setEdited(int flags){ bool discard = mOverrideFlags & ~flags; int added = flags & ~mOverrideFlags;
mOverrideFlags = flags;
if (added & Override_Position) for (int i = 0; i < 3; ++i) mPositionOverride.pos[i] = getReference().mPos.pos[i];
if (added & Override_Rotation) for (int i = 0; i < 3; ++i) mPositionOverride.rot[i] = getReference().mPos.rot[i];
if (added & Override_Scale) mScaleOverride = getReference().mScale;
if (discard) adjustTransform();}
ESM::Position CSVRender::Object::getPosition() const{ ESM::Position position = getReference().mPos;
if (mOverrideFlags & Override_Position) for (int i = 0; i < 3; ++i) position.pos[i] = mPositionOverride.pos[i];
if (mOverrideFlags & Override_Rotation) for (int i = 0; i < 3; ++i) position.rot[i] = mPositionOverride.rot[i];
return position;}
float CSVRender::Object::getScale() const{ return (mOverrideFlags & Override_Scale) ? mScaleOverride : getReference().mScale;}
void CSVRender::Object::setPosition(const float position[3]){ mOverrideFlags |= Override_Position;
for (int i = 0; i < 3; ++i) mPositionOverride.pos[i] = position[i];
adjustTransform();}
void CSVRender::Object::setRotation(const float rotation[3]){ mOverrideFlags |= Override_Rotation;
for (int i = 0; i < 3; ++i) mPositionOverride.rot[i] = rotation[i];
adjustTransform();}
void CSVRender::Object::setScale(float scale){ mOverrideFlags |= Override_Scale;
mScaleOverride = std::clamp(scale, 0.5f, 2.0f);
adjustTransform();}
void CSVRender::Object::apply(CSMWorld::CommandMacro& commands){ const CSMWorld::RefCollection& collection = mData.getReferences(); QAbstractItemModel* model = mData.getTableModel(CSMWorld::UniversalId::Type_References);
int recordIndex = collection.getIndex(mReferenceId);
if (mOverrideFlags & Override_Position) { // Do cell check first so positions can be compared const CSMWorld::CellRef& ref = collection.getRecord(recordIndex).get();
if (CSMWorld::CellCoordinates::isExteriorCell(ref.mCell)) { // Find cell index at new position std::pair<int, int> cellIndex = CSMWorld::CellCoordinates::coordinatesToCellIndex(mPositionOverride.pos[0], mPositionOverride.pos[1]); std::pair<int, int> originalIndex = ref.getCellIndex();
int cellColumn = collection.findColumnIndex( static_cast<CSMWorld::Columns::ColumnId>(CSMWorld::Columns::ColumnId_Cell)); int origCellColumn = collection.findColumnIndex( static_cast<CSMWorld::Columns::ColumnId>(CSMWorld::Columns::ColumnId_OriginalCell));
if (cellIndex != originalIndex) { /// \todo figure out worldspace (not important until multiple worldspaces are supported) std::string origCellId = CSMWorld::CellCoordinates(originalIndex).getId(""); std::string cellId = CSMWorld::CellCoordinates(cellIndex).getId("");
commands.push(new CSMWorld::ModifyCommand( *model, model->index(recordIndex, origCellColumn), QString::fromUtf8(origCellId.c_str()))); commands.push(new CSMWorld::ModifyCommand( *model, model->index(recordIndex, cellColumn), QString::fromUtf8(cellId.c_str()))); // NOTE: refnum is not modified for moving a reference to another cell } }
for (int i = 0; i < 3; ++i) { int column = collection.findColumnIndex( static_cast<CSMWorld::Columns::ColumnId>(CSMWorld::Columns::ColumnId_PositionXPos + i));
commands.push( new CSMWorld::ModifyCommand(*model, model->index(recordIndex, column), mPositionOverride.pos[i])); } }
if (mOverrideFlags & Override_Rotation) { for (int i = 0; i < 3; ++i) { int column = collection.findColumnIndex( static_cast<CSMWorld::Columns::ColumnId>(CSMWorld::Columns::ColumnId_PositionXRot + i));
commands.push(new CSMWorld::ModifyCommand( *model, model->index(recordIndex, column), osg::RadiansToDegrees(mPositionOverride.rot[i]))); } }
if (mOverrideFlags & Override_Scale) { int column = collection.findColumnIndex(CSMWorld::Columns::ColumnId_Scale);
commands.push(new CSMWorld::ModifyCommand(*model, model->index(recordIndex, column), mScaleOverride)); }
mOverrideFlags = 0;}
void CSVRender::Object::reset(){ mOverrideFlags = 0; adjustTransform();}