From 7aca18f92b2bd91f8e22a41c89dd4376f3e262dd Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 30 May 2020 17:59:36 +0300 Subject: [PATCH] Handle NiLines (feature #5445) --- CHANGELOG.md | 1 + components/nif/data.cpp | 26 ++++++++++++++ components/nif/data.hpp | 8 +++++ components/nif/niffile.cpp | 2 ++ components/nif/node.hpp | 20 +++++++++++ components/nif/record.hpp | 2 ++ components/nif/recordptr.hpp | 2 ++ components/nifosg/nifloader.cpp | 64 ++++++++++++++++++++------------- 8 files changed, 101 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9f293cb2..48cbe2ea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Bug #5427: GetDistance unknown ID error is misleading Bug #5441: Enemies can't push a player character when in critical strike stance Feature #5362: Show the soul gems' trapped soul in count dialog + Feature #5445: Handle NiLines 0.46.0 ------ diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 8ae49476b..82865faa7 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -110,6 +110,32 @@ void NiTriStripsData::read(NIFStream *nif) nif->getUShorts(strips[i], lengths[i]); } +void NiLinesData::read(NIFStream *nif) +{ + NiGeometryData::read(nif); + size_t num = vertices.size(); + std::vector flags; + nif->getChars(flags, num); + // Can't construct a line from a single vertex. + if (num < 2) + return; + // Convert connectivity flags into usable geometry. The last element needs special handling. + for (size_t i = 0; i < num-1; ++i) + { + if (flags[i] & 1) + { + lines.emplace_back(i); + lines.emplace_back(i+1); + } + } + // If there are just two vertices, they can be connected twice. Probably isn't critical. + if (flags[num-1] & 1) + { + lines.emplace_back(num-1); + lines.emplace_back(0); + } +} + void NiAutoNormalParticlesData::read(NIFStream *nif) { NiGeometryData::read(nif); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 33818810a..66b3f693a 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -62,6 +62,14 @@ public: void read(NIFStream *nif); }; +struct NiLinesData : public NiGeometryData +{ + // Lines, series of indices that correspond to connected vertices. + std::vector lines; + + void read(NIFStream *nif); +}; + class NiAutoNormalParticlesData : public NiGeometryData { public: diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 13c9ced60..b33f0c051 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -54,6 +54,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiBillboardNode", &construct , RC_NiBillboardNode )); newFactory.insert(makeEntry("NiTriShape", &construct , RC_NiTriShape )); newFactory.insert(makeEntry("NiTriStrips", &construct , RC_NiTriStrips )); + newFactory.insert(makeEntry("NiLines", &construct , RC_NiLines )); newFactory.insert(makeEntry("NiRotatingParticles", &construct , RC_NiRotatingParticles )); newFactory.insert(makeEntry("NiAutoNormalParticles", &construct , RC_NiAutoNormalParticles )); newFactory.insert(makeEntry("NiCamera", &construct , RC_NiCamera )); @@ -97,6 +98,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiFloatData", &construct , RC_NiFloatData )); newFactory.insert(makeEntry("NiTriShapeData", &construct , RC_NiTriShapeData )); newFactory.insert(makeEntry("NiTriStripsData", &construct , RC_NiTriStripsData )); + newFactory.insert(makeEntry("NiLinesData", &construct , RC_NiLinesData )); newFactory.insert(makeEntry("NiVisData", &construct , RC_NiVisData )); newFactory.insert(makeEntry("NiColorData", &construct , RC_NiColorData )); newFactory.insert(makeEntry("NiPixelData", &construct , RC_NiPixelData )); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 06a1a3b76..e605df32a 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -182,6 +182,26 @@ struct NiTriStrips : NiGeometry } }; +struct NiLines : NiGeometry +{ + NiLinesDataPtr data; + + void read(NIFStream *nif) + { + Node::read(nif); + data.read(nif); + skin.read(nif); + } + + void post(NIFFile *nif) + { + Node::post(nif); + data.post(nif); + skin.post(nif); + if (!skin.empty()) + nif->setUseSkinning(true); + } +}; struct NiCamera : Node { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 074dea9cf..67202d2fe 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -43,6 +43,7 @@ enum RecordType RC_NiCollisionSwitch, RC_NiTriShape, RC_NiTriStrips, + RC_NiLines, RC_NiRotatingParticles, RC_NiAutoNormalParticles, RC_NiBSParticleNode, @@ -83,6 +84,7 @@ enum RecordType RC_NiFloatData, RC_NiTriShapeData, RC_NiTriStripsData, + RC_NiLinesData, RC_NiVisData, RC_NiColorData, RC_NiPixelData, diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 478ecfdbb..57607cb6a 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -142,6 +142,7 @@ class NiRotatingParticlesData; class NiAutoNormalParticlesData; class NiPalette; struct NiParticleModifier; +struct NiLinesData; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -158,6 +159,7 @@ using NiColorDataPtr = RecordPtrT; using NiKeyframeDataPtr = RecordPtrT; using NiTriShapeDataPtr = RecordPtrT; using NiTriStripsDataPtr = RecordPtrT; +using NiLinesDataPtr = RecordPtrT; using NiSkinInstancePtr = RecordPtrT; using NiSourceTexturePtr = RecordPtrT; using NiRotatingParticlesDataPtr = RecordPtrT; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 131f0c470..bff414707 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -612,7 +612,9 @@ namespace NifOsg applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); - if ((nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips) && !skipMeshes) + const bool isGeometry = nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines; + + if (isGeometry && !skipMeshes) { const std::string nodeName = Misc::StringUtils::lowerCase(nifNode->name); static const std::string markerName = "tri editormarker"; @@ -624,9 +626,9 @@ namespace NifOsg Nif::NiSkinInstancePtr skin = static_cast(nifNode)->skin; if (skin.empty()) - handleTriShape(nifNode, node, composite, boundTextures, animflags); + handleGeometry(nifNode, node, composite, boundTextures, animflags); else - handleSkinnedTriShape(nifNode, node, composite, boundTextures, animflags); + handleSkinnedGeometry(nifNode, node, composite, boundTextures, animflags); if (!nifNode->controller.empty()) handleMeshControllers(nifNode, node, composite, boundTextures, animflags); @@ -1099,8 +1101,11 @@ namespace NifOsg partsys->getOrCreateStateSet(); } - void triCommonToGeometry(osg::Geometry *geometry, const std::vector& vertices, const std::vector& normals, const std::vector>& uvlist, const std::vector& colors, const std::vector& boundTextures, const std::string& name) + void handleNiGeometryData(osg::Geometry *geometry, const Nif::NiGeometryData* data, const std::vector& boundTextures, const std::string& name) { + const auto& vertices = data->vertices; + const auto& normals = data->normals; + const auto& colors = data->colors; if (!vertices.empty()) geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data())); if (!normals.empty()) @@ -1108,6 +1113,7 @@ namespace NifOsg if (!colors.empty()) geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); + const auto& uvlist = data->uvlist; int textureStage = 0; for (const unsigned int uvSet : boundTextures) { @@ -1124,43 +1130,53 @@ namespace NifOsg } } - void triShapeToGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void handleNiGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - bool vertexColorsPresent = false; + const Nif::NiGeometryData* niGeometryData = nullptr; if (nifNode->recType == Nif::RC_NiTriShape) { const Nif::NiTriShape* triShape = static_cast(nifNode); if (!triShape->data.empty()) { const Nif::NiTriShapeData* data = triShape->data.getPtr(); - vertexColorsPresent = !data->colors.empty(); - triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triShape->name); + niGeometryData = static_cast(data); if (!data->triangles.empty()) geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, data->triangles.size(), (unsigned short*)data->triangles.data())); } } - else + else if (nifNode->recType == Nif::RC_NiTriStrips) { const Nif::NiTriStrips* triStrips = static_cast(nifNode); if (!triStrips->data.empty()) { const Nif::NiTriStripsData* data = triStrips->data.getPtr(); - vertexColorsPresent = !data->colors.empty(); - triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triStrips->name); + niGeometryData = static_cast(data); if (!data->strips.empty()) { - for (const std::vector& strip : data->strips) + for (const auto& strip : data->strips) { - // Can't make a triangle from less than three vertices. - if (strip.size() < 3) - continue; - geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), - (unsigned short*)strip.data())); + if (strip.size() >= 3) + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), + (unsigned short*)strip.data())); } } } } + else if (nifNode->recType == Nif::RC_NiLines) + { + const Nif::NiLines* lines = static_cast(nifNode); + if (!lines->data.empty()) + { + const Nif::NiLinesData* data = lines->data.getPtr(); + niGeometryData = static_cast(data); + const auto& line = data->lines; + if (!line.empty()) + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), (unsigned short*)line.data())); + } + } + if (niGeometryData) + handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->name); // osg::Material properties are handled here for two reasons: // - if there are no vertex colors, we need to disable colorMode. @@ -1168,15 +1184,15 @@ namespace NifOsg // above the actual renderable would be tedious. std::vector drawableProps; collectDrawableProperties(nifNode, drawableProps); - applyDrawableProperties(parentNode, drawableProps, composite, vertexColorsPresent, animflags); + applyDrawableProperties(parentNode, drawableProps, composite, niGeometryData && !niGeometryData->colors.empty(), animflags); } - void handleTriShape(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips); + assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines); osg::ref_ptr drawable; osg::ref_ptr geom (new osg::Geometry); - triShapeToGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); + handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) @@ -1215,12 +1231,12 @@ namespace NifOsg return morphGeom; } - void handleSkinnedTriShape(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, + void handleSkinnedGeometry(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips); + assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines); osg::ref_ptr geometry (new osg::Geometry); - triShapeToGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); + handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); rig->setName(nifNode->name);