diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp
index 7e413d03cd..2d82289592 100644
--- a/apps/openmw_test_suite/nif/node.hpp
+++ b/apps/openmw_test_suite/nif/node.hpp
@@ -13,7 +13,7 @@ namespace Nif::Testing
 
     inline void init(Extra& value)
     {
-        value.next = ExtraPtr(nullptr);
+        value.mNext = ExtraPtr(nullptr);
     }
 
     inline void init(Named& value)
diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp
index 25df366d23..49030a8902 100644
--- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp
+++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp
@@ -996,7 +996,7 @@ namespace
     TEST_F(TestBulletNifLoader,
         for_tri_shape_child_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision)
     {
-        mNiStringExtraData.string = "NCC__";
+        mNiStringExtraData.mData = "NCC__";
         mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
         mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
         mNiTriShape.parents.push_back(&mNiNode);
@@ -1024,8 +1024,8 @@ namespace
     TEST_F(TestBulletNifLoader,
         for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision)
     {
-        mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2);
-        mNiStringExtraData2.string = "NCC__";
+        mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2);
+        mNiStringExtraData2.mData = "NCC__";
         mNiStringExtraData2.recType = Nif::RC_NiStringExtraData;
         mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
         mNiTriShape.parents.push_back(&mNiNode);
@@ -1052,7 +1052,7 @@ namespace
     TEST_F(TestBulletNifLoader,
         for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision)
     {
-        mNiStringExtraData.string = "NC___";
+        mNiStringExtraData.mData = "NC___";
         mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
         mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
         mNiTriShape.parents.push_back(&mNiNode);
@@ -1079,8 +1079,8 @@ namespace
     TEST_F(TestBulletNifLoader,
         for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision)
     {
-        mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2);
-        mNiStringExtraData2.string = "NC___";
+        mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2);
+        mNiStringExtraData2.mData = "NC___";
         mNiStringExtraData2.recType = Nif::RC_NiStringExtraData;
         mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
         mNiTriShape.parents.push_back(&mNiNode);
@@ -1141,7 +1141,7 @@ namespace
     TEST_F(TestBulletNifLoader,
         for_tri_shape_child_node_with_extra_data_string_mrk_should_return_shape_with_null_collision_shape)
     {
-        mNiStringExtraData.string = "MRK";
+        mNiStringExtraData.mData = "MRK";
         mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
         mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
         mNiTriShape.parents.push_back(&mNiNode);
@@ -1160,7 +1160,7 @@ namespace
 
     TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision_for_markers)
     {
-        mNiIntegerExtraData.data = 32; // BSX flag "editor marker"
+        mNiIntegerExtraData.mData = 32; // BSX flag "editor marker"
         mNiIntegerExtraData.recType = Nif::RC_BSXFlags;
         mNiTriShape.extralist.push_back(Nif::ExtraPtr(&mNiIntegerExtraData));
         mNiTriShape.parents.push_back(&mNiNode);
@@ -1181,7 +1181,7 @@ namespace
     TEST_F(TestBulletNifLoader,
         for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes)
     {
-        mNiStringExtraData.string = "MRK";
+        mNiStringExtraData.mData = "MRK";
         mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
         mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
         mNiTriShape.parents.push_back(&mNiNode2);
diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt
index 71e508e882..6e79c2a060 100644
--- a/components/CMakeLists.txt
+++ b/components/CMakeLists.txt
@@ -111,7 +111,7 @@ add_component_dir (sceneutil
     )
 
 add_component_dir (nif
-    controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream physics
+    base controller data effect extra niffile nifkey nifstream niftypes node particle physics property record record_ptr texture
     )
 
 add_component_dir (nifosg
diff --git a/components/nif/base.cpp b/components/nif/base.cpp
index ed440cd96d..af98cfa16d 100644
--- a/components/nif/base.cpp
+++ b/components/nif/base.cpp
@@ -5,11 +5,11 @@ namespace Nif
     void Extra::read(NIFStream* nif)
     {
         if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0))
-            name = nif->getString();
+            nif->read(mName);
         else if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0))
         {
-            next.read(nif);
-            recordSize = nif->getUInt();
+            mNext.read(nif);
+            nif->read(mRecordSize);
         }
     }
 
diff --git a/components/nif/base.hpp b/components/nif/base.hpp
index 2cdbdec77f..69094ffc51 100644
--- a/components/nif/base.hpp
+++ b/components/nif/base.hpp
@@ -13,12 +13,12 @@ namespace Nif
     // An extra data record. All the extra data connected to an object form a linked list.
     struct Extra : public Record
     {
-        std::string name;
-        ExtraPtr next; // Next extra data record in the list
-        unsigned int recordSize{ 0u };
+        std::string mName;
+        ExtraPtr mNext; // Next extra data record in the list
+        uint32_t mRecordSize{ 0u };
 
         void read(NIFStream* nif) override;
-        void post(Reader& nif) override { next.post(nif); }
+        void post(Reader& nif) override { mNext.post(nif); }
     };
 
     struct Controller : public Record
diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp
deleted file mode 100644
index bacba07d5f..0000000000
--- a/components/nif/controlled.cpp
+++ /dev/null
@@ -1,140 +0,0 @@
-#include "controlled.hpp"
-
-#include "data.hpp"
-
-namespace Nif
-{
-
-    void NiSourceTexture::read(NIFStream* nif)
-    {
-        Named::read(nif);
-
-        external = nif->getChar() != 0;
-        bool internal = false;
-        if (external)
-            filename = nif->getString();
-        else
-        {
-            if (nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 3))
-                internal = nif->getChar();
-            if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
-                filename = nif->getString(); // Original file path of the internal texture
-        }
-        if (nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 3))
-        {
-            if (!external && internal)
-                data.read(nif);
-        }
-        else
-        {
-            data.read(nif);
-        }
-
-        pixel = nif->getUInt();
-        mipmap = nif->getUInt();
-        alpha = nif->getUInt();
-
-        // Renderer hints, typically of no use for us
-        /* bool mIsStatic = */ nif->getChar();
-        if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 103))
-            /* bool mDirectRendering = */ nif->getBoolean();
-        if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 4))
-            /* bool mPersistRenderData = */ nif->getBoolean();
-    }
-
-    void NiSourceTexture::post(Reader& nif)
-    {
-        Named::post(nif);
-        data.post(nif);
-    }
-
-    void BSShaderTextureSet::read(NIFStream* nif)
-    {
-        nif->getSizedStrings(textures, nif->getUInt());
-    }
-
-    void NiParticleModifier::read(NIFStream* nif)
-    {
-        next.read(nif);
-        controller.read(nif);
-    }
-
-    void NiParticleModifier::post(Reader& nif)
-    {
-        next.post(nif);
-        controller.post(nif);
-    }
-
-    void NiParticleGrowFade::read(NIFStream* nif)
-    {
-        NiParticleModifier::read(nif);
-        growTime = nif->getFloat();
-        fadeTime = nif->getFloat();
-    }
-
-    void NiParticleColorModifier::read(NIFStream* nif)
-    {
-        NiParticleModifier::read(nif);
-        data.read(nif);
-    }
-
-    void NiParticleColorModifier::post(Reader& nif)
-    {
-        NiParticleModifier::post(nif);
-        data.post(nif);
-    }
-
-    void NiGravity::read(NIFStream* nif)
-    {
-        NiParticleModifier::read(nif);
-
-        mDecay = nif->getFloat();
-        mForce = nif->getFloat();
-        mType = nif->getUInt();
-        mPosition = nif->getVector3();
-        mDirection = nif->getVector3();
-    }
-
-    void NiParticleCollider::read(NIFStream* nif)
-    {
-        NiParticleModifier::read(nif);
-
-        mBounceFactor = nif->getFloat();
-        if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 0, 2))
-        {
-            // Unused in NifSkope. Need to figure out what these do.
-            /*bool mSpawnOnCollision = */ nif->getBoolean();
-            /*bool mDieOnCollision = */ nif->getBoolean();
-        }
-    }
-
-    void NiPlanarCollider::read(NIFStream* nif)
-    {
-        NiParticleCollider::read(nif);
-
-        mExtents = nif->getVector2();
-        mPosition = nif->getVector3();
-        mXVector = nif->getVector3();
-        mYVector = nif->getVector3();
-        mPlaneNormal = nif->getVector3();
-        mPlaneDistance = nif->getFloat();
-    }
-
-    void NiParticleRotation::read(NIFStream* nif)
-    {
-        NiParticleModifier::read(nif);
-
-        /* bool mRandomInitialAxis = */ nif->getChar();
-        /* osg::Vec3f mInitialAxis = */ nif->getVector3();
-        /* float mRotationSpeed = */ nif->getFloat();
-    }
-
-    void NiSphericalCollider::read(NIFStream* nif)
-    {
-        NiParticleCollider::read(nif);
-
-        mRadius = nif->getFloat();
-        mCenter = nif->getVector3();
-    }
-
-}
diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp
deleted file mode 100644
index 09bab3cbfd..0000000000
--- a/components/nif/controlled.hpp
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
-  OpenMW - The completely unofficial reimplementation of Morrowind
-  Copyright (C) 2008-2010  Nicolay Korslund
-  Email: < korslund@gmail.com >
-  WWW: https://openmw.org/
-
-  This file (controlled.h) is part of the OpenMW package.
-
-  OpenMW is distributed as free software: you can redistribute it
-  and/or modify it under the terms of the GNU General Public License
-  version 3, as published by the Free Software Foundation.
-
-  This program is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-  General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  version 3 along with this program. If not, see
-  https://www.gnu.org/licenses/ .
-
- */
-
-#ifndef OPENMW_COMPONENTS_NIF_CONTROLLED_HPP
-#define OPENMW_COMPONENTS_NIF_CONTROLLED_HPP
-
-#include "base.hpp"
-
-namespace Nif
-{
-
-    struct NiSourceTexture : public Named
-    {
-        // Is this an external (references a separate texture file) or
-        // internal (data is inside the nif itself) texture?
-        bool external;
-
-        std::string filename; // In case of external textures
-        NiPixelDataPtr data; // In case of internal textures
-
-        /* Pixel layout
-            0 - Palettised
-            1 - High color 16
-            2 - True color 32
-            3 - Compressed
-            4 - Bumpmap
-            5 - Default */
-        unsigned int pixel;
-
-        /* Mipmap format
-            0 - no
-            1 - yes
-            2 - default */
-        unsigned int mipmap;
-
-        /* Alpha
-            0 - none
-            1 - binary
-            2 - smooth
-            3 - default (use material alpha, or multiply material with texture if present)
-        */
-        unsigned int alpha;
-
-        void read(NIFStream* nif) override;
-        void post(Reader& nif) override;
-    };
-
-    struct BSShaderTextureSet : public Record
-    {
-        enum TextureType
-        {
-            TextureType_Base = 0,
-            TextureType_Normal = 1,
-            TextureType_Glow = 2,
-            TextureType_Parallax = 3,
-            TextureType_Env = 4,
-            TextureType_EnvMask = 5,
-            TextureType_Subsurface = 6,
-            TextureType_BackLighting = 7
-        };
-        std::vector<std::string> textures;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiParticleModifier : public Record
-    {
-        NiParticleModifierPtr next;
-        ControllerPtr controller;
-
-        void read(NIFStream* nif) override;
-        void post(Reader& nif) override;
-    };
-
-    struct NiParticleGrowFade : public NiParticleModifier
-    {
-        float growTime;
-        float fadeTime;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiParticleColorModifier : public NiParticleModifier
-    {
-        NiColorDataPtr data;
-
-        void read(NIFStream* nif) override;
-        void post(Reader& nif) override;
-    };
-
-    struct NiGravity : public NiParticleModifier
-    {
-        float mForce;
-        /* 0 - Wind (fixed direction)
-         * 1 - Point (fixed origin)
-         */
-        int mType;
-        float mDecay;
-        osg::Vec3f mPosition;
-        osg::Vec3f mDirection;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiParticleCollider : public NiParticleModifier
-    {
-        float mBounceFactor;
-        void read(NIFStream* nif) override;
-    };
-
-    // NiPinaColada
-    struct NiPlanarCollider : public NiParticleCollider
-    {
-        osg::Vec2f mExtents;
-        osg::Vec3f mPosition;
-        osg::Vec3f mXVector, mYVector;
-        osg::Vec3f mPlaneNormal;
-        float mPlaneDistance;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiSphericalCollider : public NiParticleCollider
-    {
-        float mRadius;
-        osg::Vec3f mCenter;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiParticleRotation : public NiParticleModifier
-    {
-        void read(NIFStream* nif) override;
-    };
-
-} // Namespace
-#endif
diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp
index 9e93b31162..1355ce21d3 100644
--- a/components/nif/controller.cpp
+++ b/components/nif/controller.cpp
@@ -1,9 +1,9 @@
 #include "controller.hpp"
 
-#include "controlled.hpp"
 #include "data.hpp"
 #include "node.hpp"
-#include "recordptr.hpp"
+#include "particle.hpp"
+#include "texture.hpp"
 
 namespace Nif
 {
diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp
index 3b90d50564..36407f8dc2 100644
--- a/components/nif/effect.cpp
+++ b/components/nif/effect.cpp
@@ -1,7 +1,7 @@
 #include "effect.hpp"
 
-#include "controlled.hpp"
 #include "node.hpp"
+#include "texture.hpp"
 
 namespace Nif
 {
@@ -9,55 +9,61 @@ namespace Nif
     void NiDynamicEffect::read(NIFStream* nif)
     {
         Node::read(nif);
-        if (nif->getVersion() >= nif->generateVersion(10, 1, 0, 106)
-            && nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4)
-            nif->getBoolean(); // Switch state
-        if (nif->getVersion() <= NIFFile::VER_MW
-            || (nif->getVersion() >= nif->generateVersion(10, 1, 0, 0)
-                && nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4))
-        {
-            size_t numAffectedNodes = nif->get<uint32_t>();
-            nif->skip(numAffectedNodes * 4);
-        }
+
+        if (nif->getVersion() > NIFFile::VER_MW && nif->getVersion() < nif->generateVersion(10, 1, 0, 0))
+            return;
+
+        if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4)
+            return;
+
+        if (nif->getVersion() >= nif->generateVersion(10, 1, 0, 106))
+            nif->read(mSwitchState);
+        size_t numAffectedNodes = nif->get<uint32_t>();
+        nif->skip(numAffectedNodes * 4);
     }
 
     void NiLight::read(NIFStream* nif)
     {
         NiDynamicEffect::read(nif);
 
-        dimmer = nif->getFloat();
-        ambient = nif->getVector3();
-        diffuse = nif->getVector3();
-        specular = nif->getVector3();
+        mDimmer = nif->getFloat();
+        mAmbient = nif->getVector3();
+        mDiffuse = nif->getVector3();
+        mSpecular = nif->getVector3();
+    }
+
+    void NiPointLight::read(NIFStream* nif)
+    {
+        NiLight::read(nif);
+
+        mConstantAttenuation = nif->getFloat();
+        mLinearAttenuation = nif->getFloat();
+        mQuadraticAttenuation = nif->getFloat();
+    }
+
+    void NiSpotLight::read(NIFStream* nif)
+    {
+        NiPointLight::read(nif);
+
+        mCutoff = nif->getFloat();
+        mExponent = nif->getFloat();
     }
 
     void NiTextureEffect::read(NIFStream* nif)
     {
         NiDynamicEffect::read(nif);
 
-        // Model Projection Matrix
-        nif->skip(3 * 3 * sizeof(float));
-
-        // Model Projection Transform
-        nif->skip(3 * sizeof(float));
-
-        // Texture Filtering
-        nif->skip(4);
-
-        // Max anisotropy samples
+        nif->read(mProjectionRotation);
+        nif->read(mProjectionPosition);
+        nif->read(mFilterMode);
         if (nif->getVersion() >= NIFStream::generateVersion(20, 5, 0, 4))
-            nif->skip(2);
-
-        clamp = nif->getUInt();
-
-        textureType = (TextureType)nif->getUInt();
-
-        coordGenType = (CoordGenType)nif->getUInt();
-
-        texture.read(nif);
-
-        nif->skip(1); // Use clipping plane
-        nif->skip(16); // Clipping plane dimensions vector
+            nif->read(mMaxAnisotropy);
+        nif->read(mClampMode);
+        mTextureType = static_cast<TextureType>(nif->get<uint32_t>());
+        mCoordGenType = static_cast<CoordGenType>(nif->get<uint32_t>());
+        mTexture.read(nif);
+        nif->read(mEnableClipPlane);
+        mClipPlane = osg::Plane(nif->get<osg::Vec4f>());
         if (nif->getVersion() <= NIFStream::generateVersion(10, 2, 0, 0))
             nif->skip(4); // PS2-specific shorts
         if (nif->getVersion() <= NIFStream::generateVersion(4, 1, 0, 12))
@@ -67,24 +73,8 @@ namespace Nif
     void NiTextureEffect::post(Reader& nif)
     {
         NiDynamicEffect::post(nif);
-        texture.post(nif);
-    }
 
-    void NiPointLight::read(NIFStream* nif)
-    {
-        NiLight::read(nif);
-
-        constantAttenuation = nif->getFloat();
-        linearAttenuation = nif->getFloat();
-        quadraticAttenuation = nif->getFloat();
-    }
-
-    void NiSpotLight::read(NIFStream* nif)
-    {
-        NiPointLight::read(nif);
-
-        cutoff = nif->getFloat();
-        exponent = nif->getFloat();
+        mTexture.post(nif);
     }
 
 }
diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp
index ea9e21f003..06f85cd5d5 100644
--- a/components/nif/effect.hpp
+++ b/components/nif/effect.hpp
@@ -29,67 +29,75 @@
 namespace Nif
 {
 
+    // Abstract
     struct NiDynamicEffect : public Node
     {
+        bool mSwitchState{ true };
         void read(NIFStream* nif) override;
     };
 
-    // Used as base for NiAmbientLight, NiDirectionalLight, NiPointLight and NiSpotLight.
+    // Abstract light source
     struct NiLight : NiDynamicEffect
     {
-        float dimmer;
-        osg::Vec3f ambient;
-        osg::Vec3f diffuse;
-        osg::Vec3f specular;
+        float mDimmer;
+        osg::Vec3f mAmbient;
+        osg::Vec3f mDiffuse;
+        osg::Vec3f mSpecular;
 
         void read(NIFStream* nif) override;
     };
 
     struct NiPointLight : public NiLight
     {
-        float constantAttenuation;
-        float linearAttenuation;
-        float quadraticAttenuation;
+        float mConstantAttenuation;
+        float mLinearAttenuation;
+        float mQuadraticAttenuation;
 
         void read(NIFStream* nif) override;
     };
 
     struct NiSpotLight : public NiPointLight
     {
-        float cutoff;
-        float exponent;
+        float mCutoff;
+        float mExponent;
         void read(NIFStream* nif) override;
     };
 
     struct NiTextureEffect : NiDynamicEffect
     {
-        NiSourceTexturePtr texture;
-        unsigned int clamp;
-
-        enum TextureType
+        enum class TextureType : uint32_t
         {
-            Projected_Light = 0,
-            Projected_Shadow = 1,
-            Environment_Map = 2,
-            Fog_Map = 3
+            ProjectedLight = 0,
+            ProjectedShadow = 1,
+            EnvironmentMap = 2,
+            FogMap = 3,
         };
-        TextureType textureType;
 
-        enum CoordGenType
+        enum class CoordGenType : uint32_t
         {
-            World_Parallel = 0,
-            World_Perspective,
-            Sphere_Map,
-            Specular_Cube_Map,
-            Diffuse_Cube_Map
+            WorldParallel = 0,
+            WorldPerspective = 1,
+            SphereMap = 2,
+            SpecularCubeMap = 3,
+            DiffuseCubeMap = 4,
         };
-        CoordGenType coordGenType;
+
+        Matrix3 mProjectionRotation;
+        osg::Vec3f mProjectionPosition;
+        uint32_t mFilterMode;
+        NiSourceTexturePtr mTexture;
+        uint16_t mMaxAnisotropy{ 0 };
+        uint32_t mClampMode;
+        TextureType mTextureType;
+        CoordGenType mCoordGenType;
+        uint8_t mEnableClipPlane;
+        osg::Plane mClipPlane;
 
         void read(NIFStream* nif) override;
         void post(Reader& nif) override;
 
-        bool wrapT() const { return clamp & 1; }
-        bool wrapS() const { return (clamp >> 1) & 1; }
+        bool wrapT() const { return mClampMode & 1; }
+        bool wrapS() const { return mClampMode & 2; }
     };
 
 } // Namespace
diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp
index 4384289f9e..e289ae626e 100644
--- a/components/nif/extra.cpp
+++ b/components/nif/extra.cpp
@@ -6,25 +6,21 @@ namespace Nif
     void NiExtraData::read(NIFStream* nif)
     {
         Extra::read(nif);
-        nif->readVector(data, recordSize);
-    }
 
-    void NiStringExtraData::read(NIFStream* nif)
-    {
-        Extra::read(nif);
-        string = nif->getString();
+        nif->readVector(mData, mRecordSize);
     }
 
     void NiTextKeyExtraData::read(NIFStream* nif)
     {
         Extra::read(nif);
 
-        int keynum = nif->getInt();
-        list.resize(keynum);
-        for (int i = 0; i < keynum; i++)
+        uint32_t numKeys;
+        nif->read(numKeys);
+        mList.resize(numKeys);
+        for (TextKey& key : mList)
         {
-            list[i].time = nif->getFloat();
-            list[i].text = nif->getString();
+            nif->read(key.mTime);
+            nif->read(key.mText);
         }
     }
 
@@ -32,81 +28,39 @@ namespace Nif
     {
         Extra::read(nif);
 
-        nif->skip(nif->getUShort() * sizeof(float)); // vertex weights I guess
-    }
-
-    void NiIntegerExtraData::read(NIFStream* nif)
-    {
-        Extra::read(nif);
-
-        data = nif->getUInt();
-    }
-
-    void NiIntegersExtraData::read(NIFStream* nif)
-    {
-        Extra::read(nif);
-
-        nif->readVector(data, nif->getUInt());
-    }
-
-    void NiBinaryExtraData::read(NIFStream* nif)
-    {
-        Extra::read(nif);
-        nif->readVector(data, nif->getUInt());
-    }
-
-    void NiBooleanExtraData::read(NIFStream* nif)
-    {
-        Extra::read(nif);
-        data = nif->getBoolean();
-    }
-
-    void NiVectorExtraData::read(NIFStream* nif)
-    {
-        Extra::read(nif);
-        data = nif->getVector4();
-    }
-
-    void NiFloatExtraData::read(NIFStream* nif)
-    {
-        Extra::read(nif);
-
-        data = nif->getFloat();
-    }
-
-    void NiFloatsExtraData::read(NIFStream* nif)
-    {
-        Extra::read(nif);
-        nif->readVector(data, nif->getUInt());
+        nif->skip(nif->get<uint16_t>() * sizeof(float)); // vertex weights I guess
     }
 
     void BSBound::read(NIFStream* nif)
     {
         Extra::read(nif);
-        center = nif->getVector3();
-        halfExtents = nif->getVector3();
+
+        nif->read(mCenter);
+        nif->read(mExtents);
     }
 
     void BSFurnitureMarker::LegacyFurniturePosition::read(NIFStream* nif)
     {
-        mOffset = nif->getVector3();
-        mOrientation = nif->getUShort();
-        mPositionRef = nif->getChar();
+        nif->read(mOffset);
+        nif->read(mOrientation);
+        nif->read(mPositionRef);
         nif->skip(1); // Position ref 2
     }
 
     void BSFurnitureMarker::FurniturePosition::read(NIFStream* nif)
     {
-        mOffset = nif->getVector3();
-        mHeading = nif->getFloat();
-        mType = nif->getUShort();
-        mEntryPoint = nif->getUShort();
+        nif->read(mOffset);
+        nif->read(mHeading);
+        nif->read(mType);
+        nif->read(mEntryPoint);
     }
 
     void BSFurnitureMarker::read(NIFStream* nif)
     {
         Extra::read(nif);
-        unsigned int num = nif->getUInt();
+
+        uint32_t num;
+        nif->read(num);
         if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3)
         {
             mLegacyMarkers.resize(num);
@@ -124,19 +78,20 @@ namespace Nif
     void BSInvMarker::read(NIFStream* nif)
     {
         Extra::read(nif);
-        float rotX = nif->getUShort() / 1000.0;
-        float rotY = nif->getUShort() / 1000.0;
-        float rotZ = nif->getUShort() / 1000.0;
-        mScale = nif->getFloat();
 
+        float rotX = nif->get<uint16_t>() / 1000.f;
+        float rotY = nif->get<uint16_t>() / 1000.f;
+        float rotZ = nif->get<uint16_t>() / 1000.f;
         mRotation = osg::Quat(rotX, osg::X_AXIS, rotY, osg::Y_AXIS, rotZ, osg::Z_AXIS);
+        nif->read(mScale);
     }
 
     void BSBehaviorGraphExtraData::read(NIFStream* nif)
     {
         Extra::read(nif);
-        mFile = nif->getString();
-        mControlsBaseSkeleton = nif->getBoolean();
+
+        nif->read(mFile);
+        nif->read(mControlsBaseSkeleton);
     }
 
 }
diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp
index b59fb2f76a..dfe4539138 100644
--- a/components/nif/extra.hpp
+++ b/components/nif/extra.hpp
@@ -1,26 +1,3 @@
-/*
-  OpenMW - The completely unofficial reimplementation of Morrowind
-  Copyright (C) 2008-2010  Nicolay Korslund
-  Email: < korslund@gmail.com >
-  WWW: https://openmw.org/
-
-  This file (extra.h) is part of the OpenMW package.
-
-  OpenMW is distributed as free software: you can redistribute it
-  and/or modify it under the terms of the GNU General Public License
-  version 3, as published by the Free Software Foundation.
-
-  This program is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-  General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  version 3 along with this program. If not, see
-  https://www.gnu.org/licenses/ .
-
- */
-
 #ifndef OPENMW_COMPONENTS_NIF_EXTRA_HPP
 #define OPENMW_COMPONENTS_NIF_EXTRA_HPP
 
@@ -29,9 +6,46 @@
 namespace Nif
 {
 
+    template <typename T>
+    struct TypedExtra : public Extra
+    {
+        T mData;
+
+        void read(NIFStream* nif) override
+        {
+            Extra::read(nif);
+
+            nif->read(mData);
+        }
+    };
+
+    template <typename T>
+    struct TypedVectorExtra : public Extra
+    {
+        std::vector<T> mData;
+
+        void read(NIFStream* nif) override
+        {
+            Extra::read(nif);
+
+            nif->readVector(mData, nif->get<uint32_t>());
+        }
+    };
+
+    using NiBooleanExtraData = TypedExtra<bool>;
+    using NiFloatExtraData = TypedExtra<float>;
+    using NiIntegerExtraData = TypedExtra<uint32_t>;
+    using NiStringExtraData = TypedExtra<std::string>;
+    using NiVectorExtraData = TypedExtra<osg::Vec4f>;
+
+    using NiBinaryExtraData = TypedVectorExtra<uint8_t>;
+    using NiFloatsExtraData = TypedVectorExtra<float>;
+    using NiIntegersExtraData = TypedVectorExtra<uint32_t>;
+
+    // Distinct from NiBinaryExtraData, uses mRecordSize as its size
     struct NiExtraData : public Extra
     {
-        std::vector<char> data;
+        std::vector<uint8_t> mData;
 
         void read(NIFStream* nif) override;
     };
@@ -45,78 +59,17 @@ namespace Nif
     {
         struct TextKey
         {
-            float time;
-            std::string text;
+            float mTime;
+            std::string mText;
         };
-        std::vector<TextKey> list;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiStringExtraData : public Extra
-    {
-        /* Known meanings:
-           "MRK" - marker, only visible in the editor, not rendered in-game
-           "NCC" - no collision except with the camera
-           Anything else starting with "NC" - no collision
-        */
-        std::string string;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiIntegerExtraData : public Extra
-    {
-        unsigned int data;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiIntegersExtraData : public Extra
-    {
-        std::vector<unsigned int> data;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiBinaryExtraData : public Extra
-    {
-        std::vector<char> data;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiBooleanExtraData : public Extra
-    {
-        bool data;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiVectorExtraData : public Extra
-    {
-        osg::Vec4f data;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiFloatExtraData : public Extra
-    {
-        float data;
-
-        void read(NIFStream* nif) override;
-    };
-
-    struct NiFloatsExtraData : public Extra
-    {
-        std::vector<float> data;
+        std::vector<TextKey> mList;
 
         void read(NIFStream* nif) override;
     };
 
     struct BSBound : public Extra
     {
-        osg::Vec3f center, halfExtents;
+        osg::Vec3f mCenter, mExtents;
 
         void read(NIFStream* nif) override;
     };
@@ -149,7 +102,7 @@ namespace Nif
     struct BSInvMarker : public Extra
     {
         osg::Quat mRotation;
-        float mScale = 1.0f;
+        float mScale;
 
         void read(NIFStream* nif) override;
     };
@@ -162,5 +115,5 @@ namespace Nif
         void read(NIFStream* nif) override;
     };
 
-} // Namespace
+}
 #endif
diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp
index f806c61bc1..e147ee3528 100644
--- a/components/nif/niffile.cpp
+++ b/components/nif/niffile.cpp
@@ -10,15 +10,16 @@
 #include <sstream>
 #include <stdexcept>
 
-#include "controlled.hpp"
 #include "controller.hpp"
 #include "data.hpp"
 #include "effect.hpp"
 #include "exception.hpp"
 #include "extra.hpp"
 #include "node.hpp"
+#include "particle.hpp"
 #include "physics.hpp"
 #include "property.hpp"
+#include "texture.hpp"
 
 namespace Nif
 {
diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp
new file mode 100644
index 0000000000..ae391c59e4
--- /dev/null
+++ b/components/nif/particle.cpp
@@ -0,0 +1,95 @@
+#include "particle.hpp"
+
+#include "data.hpp"
+
+namespace Nif
+{
+
+    void NiParticleModifier::read(NIFStream* nif)
+    {
+        mNext.read(nif);
+        if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13))
+            mController.read(nif);
+    }
+
+    void NiParticleModifier::post(Reader& nif)
+    {
+        mNext.post(nif);
+        mController.post(nif);
+    }
+
+    void NiParticleGrowFade::read(NIFStream* nif)
+    {
+        NiParticleModifier::read(nif);
+        nif->read(mGrowTime);
+        nif->read(mFadeTime);
+    }
+
+    void NiParticleColorModifier::read(NIFStream* nif)
+    {
+        NiParticleModifier::read(nif);
+
+        mData.read(nif);
+    }
+
+    void NiParticleColorModifier::post(Reader& nif)
+    {
+        NiParticleModifier::post(nif);
+
+        mData.post(nif);
+    }
+
+    void NiGravity::read(NIFStream* nif)
+    {
+        NiParticleModifier::read(nif);
+
+        if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13))
+            nif->read(mDecay);
+        nif->read(mForce);
+        mType = static_cast<ForceType>(nif->get<uint32_t>());
+        nif->read(mPosition);
+        nif->read(mDirection);
+    }
+
+    void NiParticleCollider::read(NIFStream* nif)
+    {
+        NiParticleModifier::read(nif);
+
+        nif->read(mBounceFactor);
+        if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 0, 2))
+        {
+            nif->read(mSpawnOnCollision);
+            nif->read(mDieOnCollision);
+        }
+    }
+
+    void NiPlanarCollider::read(NIFStream* nif)
+    {
+        NiParticleCollider::read(nif);
+
+        nif->read(mExtents);
+        nif->read(mPosition);
+        nif->read(mXVector);
+        nif->read(mYVector);
+        nif->read(mPlaneNormal);
+        nif->read(mPlaneDistance);
+    }
+
+    void NiSphericalCollider::read(NIFStream* nif)
+    {
+        NiParticleCollider::read(nif);
+
+        nif->read(mRadius);
+        nif->read(mCenter);
+    }
+
+    void NiParticleRotation::read(NIFStream* nif)
+    {
+        NiParticleModifier::read(nif);
+
+        nif->read(mRandomInitialAxis);
+        nif->read(mInitialAxis);
+        nif->read(mRotationSpeed);
+    }
+
+}
diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp
new file mode 100644
index 0000000000..5590877294
--- /dev/null
+++ b/components/nif/particle.hpp
@@ -0,0 +1,90 @@
+#ifndef OPENMW_COMPONENTS_NIF_PARTICLE_HPP
+#define OPENMW_COMPONENTS_NIF_PARTICLE_HPP
+
+#include "base.hpp"
+
+namespace Nif
+{
+
+    struct NiParticleModifier : public Record
+    {
+        NiParticleModifierPtr mNext;
+        ControllerPtr mController;
+
+        void read(NIFStream* nif) override;
+        void post(Reader& nif) override;
+    };
+
+    struct NiParticleGrowFade : public NiParticleModifier
+    {
+        float mGrowTime;
+        float mFadeTime;
+
+        void read(NIFStream* nif) override;
+    };
+
+    struct NiParticleColorModifier : public NiParticleModifier
+    {
+        NiColorDataPtr mData;
+
+        void read(NIFStream* nif) override;
+        void post(Reader& nif) override;
+    };
+
+    struct NiGravity : public NiParticleModifier
+    {
+        enum class ForceType : uint32_t
+        {
+            Wind = 0, // Fixed direction
+            Point = 1, // Fixed origin
+        };
+
+        float mDecay{ 0.f };
+        float mForce;
+        ForceType mType;
+        osg::Vec3f mPosition;
+        osg::Vec3f mDirection;
+
+        void read(NIFStream* nif) override;
+    };
+
+    struct NiParticleCollider : public NiParticleModifier
+    {
+        float mBounceFactor;
+        bool mSpawnOnCollision{ false };
+        bool mDieOnCollision{ false };
+
+        void read(NIFStream* nif) override;
+    };
+
+    // NiPinaColada
+    struct NiPlanarCollider : public NiParticleCollider
+    {
+        osg::Vec2f mExtents;
+        osg::Vec3f mPosition;
+        osg::Vec3f mXVector, mYVector;
+        osg::Vec3f mPlaneNormal;
+        float mPlaneDistance;
+
+        void read(NIFStream* nif) override;
+    };
+
+    struct NiSphericalCollider : public NiParticleCollider
+    {
+        float mRadius;
+        osg::Vec3f mCenter;
+
+        void read(NIFStream* nif) override;
+    };
+
+    struct NiParticleRotation : public NiParticleModifier
+    {
+        uint8_t mRandomInitialAxis;
+        osg::Vec3f mInitialAxis;
+        float mRotationSpeed;
+
+        void read(NIFStream* nif) override;
+    };
+
+}
+#endif
diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp
index daa4f90c2a..95786cb247 100644
--- a/components/nif/physics.cpp
+++ b/components/nif/physics.cpp
@@ -12,15 +12,15 @@ namespace Nif
 
     void bhkWorldObjCInfoProperty::read(NIFStream* nif)
     {
-        mData = nif->getUInt();
-        mSize = nif->getUInt();
-        mCapacityAndFlags = nif->getUInt();
+        nif->read(mData);
+        nif->read(mSize);
+        nif->read(mCapacityAndFlags);
     }
 
     void bhkWorldObjectCInfo::read(NIFStream* nif)
     {
         nif->skip(4); // Unused
-        mPhaseType = static_cast<BroadPhaseType>(nif->getChar());
+        mPhaseType = static_cast<BroadPhaseType>(nif->get<uint8_t>());
         nif->skip(3); // Unused
         mProperty.read(nif);
     }
@@ -29,47 +29,47 @@ namespace Nif
     {
         if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD)
             nif->skip(4); // Unknown
-        mMaterial = nif->getUInt();
+        nif->read(mMaterial);
     }
 
     void HavokFilter::read(NIFStream* nif)
     {
-        mLayer = nif->getChar();
-        mFlags = nif->getChar();
-        mGroup = nif->getUShort();
+        nif->read(mLayer);
+        nif->read(mFlags);
+        nif->read(mGroup);
     }
 
     void hkSubPartData::read(NIFStream* nif)
     {
         mHavokFilter.read(nif);
-        mNumVertices = nif->getUInt();
+        nif->read(mNumVertices);
         mHavokMaterial.read(nif);
     }
 
-    void hkpMoppCode::read(NIFStream* nif)
-    {
-        unsigned int size = nif->getUInt();
-        if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
-            mOffset = nif->getVector4();
-        if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3)
-            nif->getChar(); // MOPP data build type
-        nif->readVector(mData, size);
-    }
-
     void bhkEntityCInfo::read(NIFStream* nif)
     {
-        mResponseType = static_cast<hkResponseType>(nif->getChar());
+        mResponseType = static_cast<hkResponseType>(nif->get<uint8_t>());
         nif->skip(1); // Unused
-        mProcessContactDelay = nif->getUShort();
+        nif->read(mProcessContactDelay);
+    }
+
+    void hkpMoppCode::read(NIFStream* nif)
+    {
+        uint32_t dataSize;
+        nif->read(dataSize);
+        if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
+            nif->read(mOffset);
+        if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3)
+            nif->read(mBuildType);
+        nif->readVector(mData, dataSize);
     }
 
     void TriangleData::read(NIFStream* nif)
     {
-        for (int i = 0; i < 3; i++)
-            mTriangle[i] = nif->getUShort();
-        mWeldingInfo = nif->getUShort();
+        nif->readArray(mTriangle);
+        nif->read(mWeldingInfo);
         if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
-            mNormal = nif->getVector3();
+            nif->read(mNormal);
     }
 
     void bhkMeshMaterial::read(NIFStream* nif)
@@ -80,28 +80,27 @@ namespace Nif
 
     void bhkQsTransform::read(NIFStream* nif)
     {
-        mTranslation = nif->getVector4();
-        mRotation = nif->getQuaternion();
+        nif->read(mTranslation);
+        nif->read(mRotation);
     }
 
     void bhkCMSBigTri::read(NIFStream* nif)
     {
-        for (int i = 0; i < 3; i++)
-            mTriangle[i] = nif->getUShort();
-        mMaterial = nif->getUInt();
-        mWeldingInfo = nif->getUShort();
+        nif->readArray(mTriangle);
+        nif->read(mMaterial);
+        nif->read(mWeldingInfo);
     }
 
     void bhkCMSChunk::read(NIFStream* nif)
     {
-        mTranslation = nif->getVector4();
-        mMaterialIndex = nif->getUInt();
-        mReference = nif->getUShort();
-        mTransformIndex = nif->getUShort();
-        nif->readVector(mVertices, nif->getUInt());
-        nif->readVector(mIndices, nif->getUInt());
-        nif->readVector(mStrips, nif->getUInt());
-        nif->readVector(mWeldingInfos, nif->getUInt());
+        nif->read(mTranslation);
+        nif->read(mMaterialIndex);
+        nif->read(mReference);
+        nif->read(mTransformIndex);
+        nif->readVector(mVertices, nif->get<uint32_t>());
+        nif->readVector(mIndices, nif->get<uint32_t>());
+        nif->readVector(mStrips, nif->get<uint32_t>());
+        nif->readVector(mWeldingInfos, nif->get<uint32_t>());
     }
 
     void bhkRigidBodyCInfo::read(NIFStream* nif)
@@ -115,64 +114,67 @@ namespace Nif
             {
                 if (nif->getBethVersion() >= 83)
                     nif->skip(4); // Unused
-                mResponseType = static_cast<hkResponseType>(nif->getChar());
+                mResponseType = static_cast<hkResponseType>(nif->get<uint8_t>());
                 nif->skip(1); // Unused
-                mProcessContactDelay = nif->getUShort();
+                nif->read(mProcessContactDelay);
             }
         }
         if (nif->getBethVersion() < 83)
             nif->skip(4); // Unused
-        mTranslation = nif->getVector4();
-        mRotation = nif->getQuaternion();
-        mLinearVelocity = nif->getVector4();
-        mAngularVelocity = nif->getVector4();
+        nif->read(mTranslation);
+        nif->read(mRotation);
+        nif->read(mLinearVelocity);
+        nif->read(mAngularVelocity);
+        // A bit hacky, but this is the only instance where a 3x3 matrix has padding.
         for (int i = 0; i < 3; i++)
-            for (int j = 0; j < 4; j++)
-                mInertiaTensor[i][j] = nif->getFloat();
-        mCenter = nif->getVector4();
-        mMass = nif->getFloat();
-        mLinearDamping = nif->getFloat();
-        mAngularDamping = nif->getFloat();
+        {
+            nif->read(mInertiaTensor.mValues[i], 3);
+            nif->skip(4); // Padding
+        }
+        nif->read(mCenter);
+        nif->read(mMass);
+        nif->read(mLinearDamping);
+        nif->read(mAngularDamping);
         if (nif->getBethVersion() >= 83)
         {
             if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4)
-                mTimeFactor = nif->getFloat();
-            mGravityFactor = nif->getFloat();
+                nif->read(mTimeFactor);
+            nif->read(mGravityFactor);
         }
-        mFriction = nif->getFloat();
+        nif->read(mFriction);
         if (nif->getBethVersion() >= 83)
-            mRollingFrictionMult = nif->getFloat();
-        mRestitution = nif->getFloat();
+            nif->read(mRollingFrictionMult);
+        nif->read(mRestitution);
         if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
         {
-            mMaxLinearVelocity = nif->getFloat();
-            mMaxAngularVelocity = nif->getFloat();
+            nif->read(mMaxLinearVelocity);
+            nif->read(mMaxAngularVelocity);
             if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4)
-                mPenetrationDepth = nif->getFloat();
+                nif->read(mPenetrationDepth);
         }
-        mMotionType = static_cast<hkMotionType>(nif->getChar());
+        mMotionType = static_cast<hkMotionType>(nif->get<uint8_t>());
         if (nif->getBethVersion() < 83)
-            mDeactivatorType = static_cast<hkDeactivatorType>(nif->getChar());
+            mDeactivatorType = static_cast<hkDeactivatorType>(nif->get<uint8_t>());
         else
-            mEnableDeactivation = nif->getBoolean();
-        mSolverDeactivation = static_cast<hkSolverDeactivation>(nif->getChar());
+            nif->read(mEnableDeactivation);
+        mSolverDeactivation = static_cast<hkSolverDeactivation>(nif->get<uint8_t>());
         if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4)
         {
             nif->skip(1);
-            mPenetrationDepth = nif->getFloat();
-            mTimeFactor = nif->getFloat();
+            nif->read(mPenetrationDepth);
+            nif->read(mTimeFactor);
             nif->skip(4);
-            mResponseType = static_cast<hkResponseType>(nif->getChar());
+            mResponseType = static_cast<hkResponseType>(nif->get<uint8_t>());
             nif->skip(1); // Unused
-            mProcessContactDelay = nif->getUShort();
+            nif->read(mProcessContactDelay);
         }
-        mQualityType = static_cast<hkQualityType>(nif->getChar());
+        mQualityType = static_cast<hkQualityType>(nif->get<uint8_t>());
         if (nif->getBethVersion() >= 83)
         {
-            mAutoRemoveLevel = nif->getChar();
-            mResponseModifierFlags = nif->getChar();
-            mNumContactPointShapeKeys = nif->getChar();
-            mForceCollidedOntoPPU = nif->getBoolean();
+            nif->read(mAutoRemoveLevel);
+            nif->read(mResponseModifierFlags);
+            nif->read(mNumContactPointShapeKeys);
+            nif->read(mForceCollidedOntoPPU);
         }
         if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4)
             nif->skip(3); // Unused
@@ -182,7 +184,7 @@ namespace Nif
 
     void bhkConstraintCInfo::read(NIFStream* nif)
     {
-        nif->get<unsigned int>(); // Number of entities, unused
+        nif->get<uint32_t>(); // Number of entities, unused
         mEntityA.read(nif);
         mEntityB.read(nif);
 
@@ -203,7 +205,7 @@ namespace Nif
         nif->read(mDamping);
         nif->read(mProportionalRecoveryVelocity);
         nif->read(mConstantRecoveryVelocity);
-        mEnabled = nif->getBoolean();
+        nif->read(mEnabled);
     }
 
     void bhkVelocityConstraintMotor::read(NIFStream* nif)
@@ -212,8 +214,8 @@ namespace Nif
         nif->read(mMaxForce);
         nif->read(mTau);
         nif->read(mTargetVelocity);
-        mUseVelocityTarget = nif->getBoolean();
-        mEnabled = nif->getBoolean();
+        nif->read(mUseVelocityTarget);
+        nif->read(mEnabled);
     }
 
     void bhkSpringDamperConstraintMotor::read(NIFStream* nif)
@@ -222,7 +224,7 @@ namespace Nif
         nif->read(mMaxForce);
         nif->read(mSpringConstant);
         nif->read(mSpringDamping);
-        mEnabled = nif->getBoolean();
+        nif->read(mEnabled);
     }
 
     void bhkConstraintMotorCInfo::read(NIFStream* nif)
@@ -335,7 +337,8 @@ namespace Nif
     void bhkCollisionObject::read(NIFStream* nif)
     {
         NiCollisionObject::read(nif);
-        mFlags = nif->getUShort();
+
+        nif->read(mFlags);
         mBody.read(nif);
     }
 
@@ -356,6 +359,7 @@ namespace Nif
     void bhkEntity::read(NIFStream* nif)
     {
         bhkWorldObject::read(nif);
+
         mInfo.read(nif);
     }
 
@@ -372,21 +376,26 @@ namespace Nif
     void bhkMoppBvTreeShape::read(NIFStream* nif)
     {
         bhkBvTreeShape::read(nif);
+
         nif->skip(12); // Unused
-        mScale = nif->getFloat();
+        nif->read(mScale);
         mMopp.read(nif);
     }
 
     void bhkNiTriStripsShape::read(NIFStream* nif)
     {
         mHavokMaterial.read(nif);
-        mRadius = nif->getFloat();
+        nif->read(mRadius);
         nif->skip(20); // Unused
-        mGrowBy = nif->getUInt();
+        nif->read(mGrowBy);
         if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
-            mScale = nif->getVector4();
+            nif->read(mScale);
         readRecordList(nif, mData);
-        nif->readVector(mFilters, nif->getUInt());
+        uint32_t numFilters;
+        nif->read(numFilters);
+        mHavokFilters.resize(numFilters);
+        for (HavokFilter& filter : mHavokFilters)
+            filter.read(nif);
     }
 
     void bhkNiTriStripsShape::post(Reader& nif)
@@ -398,15 +407,17 @@ namespace Nif
     {
         if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
         {
-            mSubshapes.resize(nif->getUShort());
+            uint16_t numSubshapes;
+            nif->read(numSubshapes);
+            mSubshapes.resize(numSubshapes);
             for (hkSubPartData& subshape : mSubshapes)
                 subshape.read(nif);
         }
-        mUserData = nif->getUInt();
+        nif->read(mUserData);
         nif->skip(4); // Unused
-        mRadius = nif->getFloat();
+        nif->read(mRadius);
         nif->skip(4); // Unused
-        mScale = nif->getVector4();
+        nif->read(mScale);
         nif->skip(20); // Duplicates of the two previous fields
         mData.read(nif);
     }
@@ -418,22 +429,26 @@ namespace Nif
 
     void hkPackedNiTriStripsData::read(NIFStream* nif)
     {
-        unsigned int numTriangles = nif->getUInt();
+        uint32_t numTriangles;
+        nif->read(numTriangles);
         mTriangles.resize(numTriangles);
-        for (unsigned int i = 0; i < numTriangles; i++)
+        for (uint32_t i = 0; i < numTriangles; i++)
             mTriangles[i].read(nif);
 
-        unsigned int numVertices = nif->getUInt();
+        uint32_t numVertices;
+        nif->read(numVertices);
         bool compressed = false;
         if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS)
-            compressed = nif->getBoolean();
+            nif->read(compressed);
         if (!compressed)
             nif->readVector(mVertices, numVertices);
         else
             nif->skip(6 * numVertices); // Half-precision vectors are not currently supported
         if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS)
         {
-            mSubshapes.resize(nif->getUShort());
+            uint16_t numSubshapes;
+            nif->read(numSubshapes);
+            mSubshapes.resize(numSubshapes);
             for (hkSubPartData& subshape : mSubshapes)
                 subshape.read(nif);
         }
@@ -447,23 +462,25 @@ namespace Nif
     void bhkConvexShape::read(NIFStream* nif)
     {
         bhkSphereRepShape::read(nif);
-        mRadius = nif->getFloat();
+
+        nif->read(mRadius);
     }
 
     void bhkConvexVerticesShape::read(NIFStream* nif)
     {
         bhkConvexShape::read(nif);
+
         mVerticesProperty.read(nif);
         mNormalsProperty.read(nif);
-        nif->readVector(mVertices, nif->getUInt());
-        nif->readVector(mNormals, nif->getUInt());
+        nif->readVector(mVertices, nif->get<uint32_t>());
+        nif->readVector(mNormals, nif->get<uint32_t>());
     }
 
     void bhkConvexTransformShape::read(NIFStream* nif)
     {
         mShape.read(nif);
         mHavokMaterial.read(nif);
-        mRadius = nif->getFloat();
+        nif->read(mRadius);
         nif->skip(8); // Unused
         std::array<float, 16> mat;
         nif->readArray(mat);
@@ -478,19 +495,21 @@ namespace Nif
     void bhkBoxShape::read(NIFStream* nif)
     {
         bhkConvexShape::read(nif);
+
         nif->skip(8); // Unused
-        mExtents = nif->getVector3();
+        nif->read(mExtents);
         nif->skip(4); // Unused
     }
 
     void bhkCapsuleShape::read(NIFStream* nif)
     {
         bhkConvexShape::read(nif);
+
         nif->skip(8); // Unused
-        mPoint1 = nif->getVector3();
-        mRadius1 = nif->getFloat();
-        mPoint2 = nif->getVector3();
-        mRadius2 = nif->getFloat();
+        nif->read(mPoint1);
+        nif->read(mRadius1);
+        nif->read(mPoint2);
+        nif->read(mRadius2);
     }
 
     void bhkListShape::read(NIFStream* nif)
@@ -499,7 +518,8 @@ namespace Nif
         mHavokMaterial.read(nif);
         mChildShapeProperty.read(nif);
         mChildFilterProperty.read(nif);
-        unsigned int numFilters = nif->getUInt();
+        uint32_t numFilters;
+        nif->read(numFilters);
         mHavokFilters.resize(numFilters);
         for (HavokFilter& filter : mHavokFilters)
             filter.read(nif);
@@ -508,12 +528,12 @@ namespace Nif
     void bhkCompressedMeshShape::read(NIFStream* nif)
     {
         mTarget.read(nif);
-        mUserData = nif->getUInt();
-        mRadius = nif->getFloat();
-        nif->getFloat(); // Unknown
-        mScale = nif->getVector4();
-        nif->getFloat(); // Radius
-        nif->getVector4(); // Scale
+        nif->read(mUserData);
+        nif->read(mRadius);
+        nif->skip(4); // Unknown
+        nif->read(mScale);
+        nif->skip(4); // Radius
+        nif->skip(16); // Scale
         mData.read(nif);
     }
 
@@ -525,60 +545,66 @@ namespace Nif
 
     void bhkCompressedMeshShapeData::read(NIFStream* nif)
     {
-        mBitsPerIndex = nif->getUInt();
-        mBitsPerWIndex = nif->getUInt();
-        mMaskWIndex = nif->getUInt();
-        mMaskIndex = nif->getUInt();
-        mError = nif->getFloat();
-        mAabbMin = nif->getVector4();
-        mAabbMax = nif->getVector4();
-        mWeldingType = nif->getChar();
-        mMaterialType = nif->getChar();
-        nif->skip(nif->getUInt() * 4); // Unused
-        nif->skip(nif->getUInt() * 4); // Unused
-        nif->skip(nif->getUInt() * 4); // Unused
+        nif->read(mBitsPerIndex);
+        nif->read(mBitsPerWIndex);
+        nif->read(mMaskWIndex);
+        nif->read(mMaskIndex);
+        nif->read(mError);
+        nif->read(mAabbMin);
+        nif->read(mAabbMax);
+        nif->read(mWeldingType);
+        nif->read(mMaterialType);
+        nif->skip(nif->get<uint32_t>() * 4); // Unused
+        nif->skip(nif->get<uint32_t>() * 4); // Unused
+        nif->skip(nif->get<uint32_t>() * 4); // Unused
 
-        size_t numMaterials = nif->getUInt();
+        uint32_t numMaterials;
+        nif->read(numMaterials);
         mMaterials.resize(numMaterials);
         for (bhkMeshMaterial& material : mMaterials)
             material.read(nif);
 
-        nif->getUInt(); // Unused
-        size_t numTransforms = nif->getUInt();
+        nif->skip(4); // Unused
 
+        uint32_t numTransforms;
+        nif->read(numTransforms);
         mChunkTransforms.resize(numTransforms);
         for (bhkQsTransform& transform : mChunkTransforms)
             transform.read(nif);
 
-        nif->readVector(mBigVerts, nif->getUInt());
+        nif->readVector(mBigVerts, nif->get<uint32_t>());
 
-        size_t numBigTriangles = nif->getUInt();
+        uint32_t numBigTriangles;
+        nif->read(numBigTriangles);
         mBigTris.resize(numBigTriangles);
         for (bhkCMSBigTri& tri : mBigTris)
             tri.read(nif);
 
-        size_t numChunks = nif->getUInt();
+        uint32_t numChunks;
+        nif->read(numChunks);
         mChunks.resize(numChunks);
         for (bhkCMSChunk& chunk : mChunks)
             chunk.read(nif);
 
-        nif->getUInt(); // Unused
+        nif->skip(4); // Unused
     }
 
     void bhkRigidBody::read(NIFStream* nif)
     {
         bhkEntity::read(nif);
+
         mInfo.read(nif);
         readRecordList(nif, mConstraints);
         if (nif->getBethVersion() < 76)
-            mBodyFlags = nif->getUInt();
+            nif->read(mBodyFlags);
         else
-            mBodyFlags = nif->getUShort();
+            mBodyFlags = nif->get<uint16_t>();
     }
 
     void bhkSimpleShapePhantom::read(NIFStream* nif)
     {
         bhkWorldObject::read(nif);
+
         nif->skip(8); // Unused
         std::array<float, 16> mat;
         nif->readArray(mat);
@@ -598,18 +624,21 @@ namespace Nif
     void bhkRagdollConstraint::read(NIFStream* nif)
     {
         bhkConstraint::read(nif);
+
         mConstraint.read(nif);
     }
 
     void bhkHingeConstraint::read(NIFStream* nif)
     {
         bhkConstraint::read(nif);
+
         mConstraint.read(nif);
     }
 
     void bhkLimitedHingeConstraint::read(NIFStream* nif)
     {
         bhkConstraint::read(nif);
+
         mConstraint.read(nif);
     }
 
diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp
index cc1a3ba755..a7bfa1425d 100644
--- a/components/nif/physics.hpp
+++ b/components/nif/physics.hpp
@@ -1,6 +1,7 @@
 #ifndef OPENMW_COMPONENTS_NIF_PHYSICS_HPP
 #define OPENMW_COMPONENTS_NIF_PHYSICS_HPP
 
+#include "niftypes.hpp"
 #include "record.hpp"
 #include "recordptr.hpp"
 
@@ -23,9 +24,10 @@ namespace Nif
 
     struct bhkWorldObjCInfoProperty
     {
-        unsigned int mData;
-        unsigned int mSize;
-        unsigned int mCapacityAndFlags;
+        uint32_t mData;
+        uint32_t mSize;
+        uint32_t mCapacityAndFlags;
+
         void read(NIFStream* nif);
     };
 
@@ -41,28 +43,32 @@ namespace Nif
     {
         BroadPhaseType mPhaseType;
         bhkWorldObjCInfoProperty mProperty;
+
         void read(NIFStream* nif);
     };
 
     struct HavokMaterial
     {
-        unsigned int mMaterial;
+        uint32_t mMaterial;
+
         void read(NIFStream* nif);
     };
 
     struct HavokFilter
     {
-        unsigned char mLayer;
-        unsigned char mFlags;
-        unsigned short mGroup;
+        uint8_t mLayer;
+        uint8_t mFlags;
+        uint16_t mGroup;
+
         void read(NIFStream* nif);
     };
 
     struct hkSubPartData
     {
         HavokMaterial mHavokMaterial;
-        unsigned int mNumVertices;
+        uint32_t mNumVertices;
         HavokFilter mHavokFilter;
+
         void read(NIFStream* nif);
     };
 
@@ -77,22 +83,26 @@ namespace Nif
     struct bhkEntityCInfo
     {
         hkResponseType mResponseType;
-        unsigned short mProcessContactDelay;
+        uint16_t mProcessContactDelay;
+
         void read(NIFStream* nif);
     };
 
     struct hkpMoppCode
     {
         osg::Vec4f mOffset;
-        std::vector<char> mData;
+        uint8_t mBuildType;
+        std::vector<uint8_t> mData;
+
         void read(NIFStream* nif);
     };
 
     struct TriangleData
     {
-        unsigned short mTriangle[3];
-        unsigned short mWeldingInfo;
+        std::array<uint16_t, 3> mTriangle;
+        uint16_t mWeldingInfo;
         osg::Vec3f mNormal;
+
         void read(NIFStream* nif);
     };
 
@@ -100,6 +110,7 @@ namespace Nif
     {
         HavokMaterial mHavokMaterial;
         HavokFilter mHavokFilter;
+
         void read(NIFStream* nif);
     };
 
@@ -107,27 +118,30 @@ namespace Nif
     {
         osg::Vec4f mTranslation;
         osg::Quat mRotation;
+
         void read(NIFStream* nif);
     };
 
     struct bhkCMSBigTri
     {
-        unsigned short mTriangle[3];
-        unsigned int mMaterial;
-        unsigned short mWeldingInfo;
+        std::array<uint16_t, 3> mTriangle;
+        uint32_t mMaterial;
+        uint16_t mWeldingInfo;
+
         void read(NIFStream* nif);
     };
 
     struct bhkCMSChunk
     {
         osg::Vec4f mTranslation;
-        unsigned int mMaterialIndex;
-        unsigned short mReference;
-        unsigned short mTransformIndex;
-        std::vector<unsigned short> mVertices;
-        std::vector<unsigned short> mIndices;
-        std::vector<unsigned short> mStrips;
-        std::vector<unsigned short> mWeldingInfos;
+        uint32_t mMaterialIndex;
+        uint16_t mReference;
+        uint16_t mTransformIndex;
+        std::vector<uint16_t> mVertices;
+        std::vector<uint16_t> mIndices;
+        std::vector<uint16_t> mStrips;
+        std::vector<uint16_t> mWeldingInfos;
+
         void read(NIFStream* nif);
     };
 
@@ -180,12 +194,12 @@ namespace Nif
     {
         HavokFilter mHavokFilter;
         hkResponseType mResponseType;
-        unsigned short mProcessContactDelay;
+        uint16_t mProcessContactDelay;
         osg::Vec4f mTranslation;
         osg::Quat mRotation;
         osg::Vec4f mLinearVelocity;
         osg::Vec4f mAngularVelocity;
-        float mInertiaTensor[3][4];
+        Matrix3 mInertiaTensor;
         osg::Vec4f mCenter;
         float mMass;
         float mLinearDamping;
@@ -203,10 +217,11 @@ namespace Nif
         bool mEnableDeactivation{ true };
         hkSolverDeactivation mSolverDeactivation;
         hkQualityType mQualityType;
-        unsigned char mAutoRemoveLevel;
-        unsigned char mResponseModifierFlags;
-        unsigned char mNumContactPointShapeKeys;
+        uint8_t mAutoRemoveLevel;
+        uint8_t mResponseModifierFlags;
+        uint8_t mNumContactPointShapeKeys;
         bool mForceCollidedOntoPPU;
+
         void read(NIFStream* nif);
     };
 
@@ -222,6 +237,7 @@ namespace Nif
         bhkEntityPtr mEntityA;
         bhkEntityPtr mEntityB;
         ConstraintPriority mPriority;
+
         void read(NIFStream* nif);
         void post(Reader& nif);
     };
@@ -242,6 +258,7 @@ namespace Nif
         float mProportionalRecoveryVelocity;
         float mConstantRecoveryVelocity;
         bool mEnabled;
+
         void read(NIFStream* nif);
     };
 
@@ -252,6 +269,7 @@ namespace Nif
         float mTargetVelocity;
         bool mUseVelocityTarget;
         bool mEnabled;
+
         void read(NIFStream* nif);
     };
 
@@ -261,6 +279,7 @@ namespace Nif
         float mSpringConstant;
         float mSpringDamping;
         bool mEnabled;
+
         void read(NIFStream* nif);
     };
 
@@ -270,6 +289,7 @@ namespace Nif
         bhkPositionConstraintMotor mPositionMotor;
         bhkVelocityConstraintMotor mVelocityMotor;
         bhkSpringDamperConstraintMotor mSpringDamperMotor;
+
         void read(NIFStream* nif);
     };
 
@@ -289,6 +309,7 @@ namespace Nif
         float mTwistMinAngle, mTwistMaxAngle;
         float mMaxFriction;
         bhkConstraintMotorCInfo mMotor;
+
         void read(NIFStream* nif);
     };
 
@@ -301,8 +322,10 @@ namespace Nif
             osg::Vec4f mPerpAxis1;
             osg::Vec4f mPerpAxis2;
         };
+
         HingeData mDataA;
         HingeData mDataB;
+
         void read(NIFStream* nif);
     };
 
@@ -315,11 +338,13 @@ namespace Nif
             osg::Vec4f mPerpAxis1;
             osg::Vec4f mPerpAxis2;
         };
+
         HingeData mDataA;
         HingeData mDataB;
         float mMinAngle, mMaxAngle;
         float mMaxFriction;
         bhkConstraintMotorCInfo mMotor;
+
         void read(NIFStream* nif);
     };
 
@@ -358,7 +383,7 @@ namespace Nif
     // Bethesda Havok-specific collision object
     struct bhkCollisionObject : public NiCollisionObject
     {
-        unsigned short mFlags;
+        uint16_t mFlags;
         bhkWorldObjectPtr mBody;
 
         void read(NIFStream* nif) override;
@@ -375,6 +400,7 @@ namespace Nif
         bhkShapePtr mShape;
         HavokFilter mHavokFilter;
         bhkWorldObjectCInfo mWorldObjectInfo;
+
         void read(NIFStream* nif) override;
         void post(Reader& nif) override;
     };
@@ -383,6 +409,7 @@ namespace Nif
     struct bhkEntity : public bhkWorldObject
     {
         bhkEntityCInfo mInfo;
+
         void read(NIFStream* nif) override;
     };
 
@@ -391,6 +418,7 @@ namespace Nif
     struct bhkBvTreeShape : public bhkShape
     {
         bhkShapePtr mShape;
+
         void read(NIFStream* nif) override;
         void post(Reader& nif) override;
     };
@@ -400,6 +428,7 @@ namespace Nif
     {
         float mScale;
         hkpMoppCode mMopp;
+
         void read(NIFStream* nif) override;
     };
 
@@ -408,10 +437,11 @@ namespace Nif
     {
         HavokMaterial mHavokMaterial;
         float mRadius;
-        unsigned int mGrowBy;
+        uint32_t mGrowBy;
         osg::Vec4f mScale{ 1.f, 1.f, 1.f, 0.f };
         NiTriStripsDataList mData;
-        std::vector<unsigned int> mFilters;
+        std::vector<HavokFilter> mHavokFilters;
+
         void read(NIFStream* nif) override;
         void post(Reader& nif) override;
     };
@@ -420,7 +450,7 @@ namespace Nif
     struct bhkPackedNiTriStripsShape : public bhkShapeCollection
     {
         std::vector<hkSubPartData> mSubshapes;
-        unsigned int mUserData;
+        uint32_t mUserData;
         float mRadius;
         osg::Vec4f mScale;
         hkPackedNiTriStripsDataPtr mData;
@@ -435,6 +465,7 @@ namespace Nif
         std::vector<TriangleData> mTriangles;
         std::vector<osg::Vec3f> mVertices;
         std::vector<hkSubPartData> mSubshapes;
+
         void read(NIFStream* nif) override;
     };
 
@@ -442,6 +473,7 @@ namespace Nif
     struct bhkSphereRepShape : public bhkShape
     {
         HavokMaterial mHavokMaterial;
+
         void read(NIFStream* nif) override;
     };
 
@@ -449,6 +481,7 @@ namespace Nif
     struct bhkConvexShape : public bhkSphereRepShape
     {
         float mRadius;
+
         void read(NIFStream* nif) override;
     };
 
@@ -459,6 +492,7 @@ namespace Nif
         bhkWorldObjCInfoProperty mNormalsProperty;
         std::vector<osg::Vec4f> mVertices;
         std::vector<osg::Vec4f> mNormals;
+
         void read(NIFStream* nif) override;
     };
 
@@ -468,6 +502,7 @@ namespace Nif
         HavokMaterial mHavokMaterial;
         float mRadius;
         osg::Matrixf mTransform;
+
         void read(NIFStream* nif) override;
         void post(Reader& nif) override;
     };
@@ -476,6 +511,7 @@ namespace Nif
     struct bhkBoxShape : public bhkConvexShape
     {
         osg::Vec3f mExtents;
+
         void read(NIFStream* nif) override;
     };
 
@@ -499,28 +535,30 @@ namespace Nif
         bhkWorldObjCInfoProperty mChildShapeProperty;
         bhkWorldObjCInfoProperty mChildFilterProperty;
         std::vector<HavokFilter> mHavokFilters;
+
         void read(NIFStream* nif) override;
     };
 
     struct bhkCompressedMeshShape : public bhkShape
     {
         NodePtr mTarget;
-        unsigned int mUserData;
+        uint32_t mUserData;
         float mRadius;
         osg::Vec4f mScale;
         bhkCompressedMeshShapeDataPtr mData;
+
         void read(NIFStream* nif) override;
         void post(Reader& nif) override;
     };
 
     struct bhkCompressedMeshShapeData : public bhkRefObject
     {
-        unsigned int mBitsPerIndex, mBitsPerWIndex;
-        unsigned int mMaskWIndex, mMaskIndex;
+        uint32_t mBitsPerIndex, mBitsPerWIndex;
+        uint32_t mMaskWIndex, mMaskIndex;
         float mError;
         osg::Vec4f mAabbMin, mAabbMax;
-        char mWeldingType;
-        char mMaterialType;
+        uint8_t mWeldingType;
+        uint8_t mMaterialType;
         std::vector<bhkMeshMaterial> mMaterials;
         std::vector<bhkQsTransform> mChunkTransforms;
         std::vector<osg::Vec4f> mBigVerts;
@@ -534,7 +572,7 @@ namespace Nif
     {
         bhkRigidBodyCInfo mInfo;
         bhkSerializableList mConstraints;
-        unsigned int mBodyFlags;
+        uint32_t mBodyFlags;
 
         void read(NIFStream* nif) override;
     };
@@ -542,6 +580,7 @@ namespace Nif
     struct bhkSimpleShapePhantom : public bhkWorldObject
     {
         osg::Matrixf mTransform;
+
         void read(NIFStream* nif) override;
     };
 
@@ -549,6 +588,7 @@ namespace Nif
     struct bhkConstraint : public bhkSerializable
     {
         bhkConstraintCInfo mInfo;
+
         void read(NIFStream* nif) override;
         void post(Reader& nif) override;
     };
@@ -556,18 +596,21 @@ namespace Nif
     struct bhkRagdollConstraint : public bhkConstraint
     {
         bhkRagdollConstraintCInfo mConstraint;
+
         void read(NIFStream* nif) override;
     };
 
     struct bhkHingeConstraint : public bhkConstraint
     {
         bhkHingeConstraintCInfo mConstraint;
+
         void read(NIFStream* nif) override;
     };
 
     struct bhkLimitedHingeConstraint : public bhkConstraint
     {
         bhkLimitedHingeConstraintCInfo mConstraint;
+
         void read(NIFStream* nif) override;
     };
 
diff --git a/components/nif/property.cpp b/components/nif/property.cpp
index 3c1c424de9..39a7cbff23 100644
--- a/components/nif/property.cpp
+++ b/components/nif/property.cpp
@@ -1,7 +1,7 @@
 #include "property.hpp"
 
-#include "controlled.hpp"
 #include "data.hpp"
+#include "texture.hpp"
 
 namespace Nif
 {
diff --git a/components/nif/texture.cpp b/components/nif/texture.cpp
new file mode 100644
index 0000000000..116ded6f7e
--- /dev/null
+++ b/components/nif/texture.cpp
@@ -0,0 +1,47 @@
+#include "texture.hpp"
+
+#include "data.hpp"
+
+namespace Nif
+{
+
+    void NiSourceTexture::read(NIFStream* nif)
+    {
+        NiTexture::read(nif);
+
+        nif->read(mExternal);
+        if (mExternal || nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
+            nif->read(mFile);
+
+        bool hasData = nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 4);
+        if (!hasData && !mExternal)
+            nif->read(hasData);
+
+        if (hasData)
+            mData.read(nif);
+
+        mPrefs.mPixelLayout = static_cast<PixelLayout>(nif->get<uint32_t>());
+        mPrefs.mUseMipMaps = static_cast<MipMapFormat>(nif->get<uint32_t>());
+        mPrefs.mAlphaFormat = static_cast<AlphaFormat>(nif->get<uint32_t>());
+
+        nif->read(mIsStatic);
+        if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 103))
+        {
+            nif->read(mDirectRendering);
+            if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 4))
+                nif->read(mPersistRenderData);
+        }
+    }
+
+    void NiSourceTexture::post(Reader& nif)
+    {
+        NiTexture::post(nif);
+        mData.post(nif);
+    }
+
+    void BSShaderTextureSet::read(NIFStream* nif)
+    {
+        nif->getSizedStrings(mTextures, nif->get<uint32_t>());
+    }
+
+}
diff --git a/components/nif/texture.hpp b/components/nif/texture.hpp
new file mode 100644
index 0000000000..a326b47f14
--- /dev/null
+++ b/components/nif/texture.hpp
@@ -0,0 +1,81 @@
+#ifndef OPENMW_COMPONENTS_NIF_TEXTURE_HPP
+#define OPENMW_COMPONENTS_NIF_TEXTURE_HPP
+
+#include "base.hpp"
+
+namespace Nif
+{
+
+    struct NiTexture : public Named
+    {
+    };
+
+    struct NiSourceTexture : public NiTexture
+    {
+        enum class PixelLayout : uint32_t
+        {
+            Palette = 0,
+            HighColor = 1,
+            TrueColor = 2,
+            Compressed = 3,
+            BumpMap = 4,
+            Default = 5,
+        };
+
+        enum class MipMapFormat : uint32_t
+        {
+            No = 0,
+            Yes = 1,
+            Default = 2,
+        };
+
+        enum class AlphaFormat : uint32_t
+        {
+            None = 0,
+            Binary = 1,
+            Smooth = 2,
+            Default = 3,
+        };
+
+        struct FormatPrefs
+        {
+            PixelLayout mPixelLayout;
+            MipMapFormat mUseMipMaps;
+            AlphaFormat mAlphaFormat;
+        };
+
+        char mExternal; // References external file
+
+        std::string mFile;
+        NiPixelDataPtr mData;
+
+        FormatPrefs mPrefs;
+
+        char mIsStatic{ 1 };
+        bool mDirectRendering{ true };
+        bool mPersistRenderData{ false };
+
+        void read(NIFStream* nif) override;
+        void post(Reader& nif) override;
+    };
+
+    struct BSShaderTextureSet : public Record
+    {
+        enum class TextureType : uint32_t
+        {
+            Base = 0,
+            Normal = 1,
+            Glow = 2,
+            Parallax = 3,
+            Environment = 4,
+            EnvironmentMask = 5,
+            Subsurface = 6,
+            BackLighting = 7,
+        };
+        std::vector<std::string> mTextures;
+
+        void read(NIFStream* nif) override;
+    };
+
+}
+#endif
diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp
index 251795eb21..0c85949a53 100644
--- a/components/nifbullet/bulletnifloader.cpp
+++ b/components/nifbullet/bulletnifloader.cpp
@@ -305,7 +305,7 @@ namespace NifBullet
 
         // Check for extra data
         std::vector<Nif::ExtraPtr> extraCollection;
-        for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->next)
+        for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->mNext)
             extraCollection.emplace_back(e);
         for (const auto& extraNode : node.extralist)
             if (!extraNode.empty())
@@ -316,29 +316,30 @@ namespace NifBullet
             {
                 // String markers may contain important information
                 // affecting the entire subtree of this node
-                Nif::NiStringExtraData* sd = (Nif::NiStringExtraData*)e.getPtr();
+                auto sd = static_cast<const Nif::NiStringExtraData*>(e.getPtr());
 
-                if (Misc::StringUtils::ciStartsWith(sd->string, "NC"))
+                if (Misc::StringUtils::ciStartsWith(sd->mData, "NC"))
                 {
                     // NCC flag in vanilla is partly case sensitive: prefix NC is case insensitive but second C needs be
                     // uppercase
-                    if (sd->string.length() > 2 && sd->string[2] == 'C')
+                    if (sd->mData.length() > 2 && sd->mData[2] == 'C')
                         // Collide only with camera.
                         visualCollisionType = Resource::VisualCollisionType::Camera;
                     else
                         // No collision.
                         visualCollisionType = Resource::VisualCollisionType::Default;
                 }
-                else if (sd->string == "MRK" && args.mAutogenerated)
+                // Don't autogenerate collision if MRK is set.
+                // FIXME: verify if this covers the entire subtree
+                else if (sd->mData == "MRK" && args.mAutogenerated)
                 {
-                    // Marker can still have collision if the model explicitely specifies it via a RootCollisionNode.
                     return;
                 }
             }
             else if (e->recType == Nif::RC_BSXFlags)
             {
                 auto bsxFlags = static_cast<const Nif::NiIntegerExtraData*>(e.getPtr());
-                if (bsxFlags->data & 32) // Editor marker flag
+                if (bsxFlags->mData & 32) // Editor marker flag
                     args.mHasMarkers = true;
             }
         }
diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp
index 2e2d0293e6..dde4f261e2 100644
--- a/components/nifosg/nifloader.cpp
+++ b/components/nifosg/nifloader.cpp
@@ -42,13 +42,14 @@
 #include <osg/TexEnvCombine>
 #include <osg/Texture2D>
 
-#include <components/nif/controlled.hpp>
 #include <components/nif/effect.hpp>
 #include <components/nif/exception.hpp>
 #include <components/nif/extra.hpp>
 #include <components/nif/niffile.hpp>
 #include <components/nif/node.hpp>
+#include <components/nif/particle.hpp>
 #include <components/nif/property.hpp>
+#include <components/nif/texture.hpp>
 #include <components/sceneutil/depth.hpp>
 #include <components/sceneutil/morphgeometry.hpp>
 #include <components/sceneutil/riggeometry.hpp>
@@ -174,16 +175,16 @@ namespace
 
     void extractTextKeys(const Nif::NiTextKeyExtraData* tk, SceneUtil::TextKeyMap& textkeys)
     {
-        for (size_t i = 0; i < tk->list.size(); i++)
+        for (const Nif::NiTextKeyExtraData::TextKey& key : tk->mList)
         {
             std::vector<std::string> results;
-            Misc::StringUtils::split(tk->list[i].text, results, "\r\n");
+            Misc::StringUtils::split(key.mText, results, "\r\n");
             for (std::string& result : results)
             {
                 Misc::StringUtils::trim(result);
                 Misc::StringUtils::lowerCaseInPlace(result);
                 if (!result.empty())
-                    textkeys.emplace(tk->list[i].time, std::move(result));
+                    textkeys.emplace(key.mTime, std::move(result));
             }
         }
     }
@@ -285,9 +286,9 @@ namespace NifOsg
 
             extractTextKeys(static_cast<const Nif::NiTextKeyExtraData*>(extra.getPtr()), target.mTextKeys);
 
-            extra = extra->next;
+            extra = extra->mNext;
             Nif::ControllerPtr ctrl = seq->controller;
-            for (; !extra.empty() && !ctrl.empty(); (extra = extra->next), (ctrl = ctrl->next))
+            for (; !extra.empty() && !ctrl.empty(); (extra = extra->mNext), (ctrl = ctrl->next))
             {
                 if (extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController)
                 {
@@ -315,8 +316,8 @@ namespace NifOsg
                 osg::ref_ptr<SceneUtil::KeyframeController> callback = new NifOsg::KeyframeController(key);
                 setupController(key, callback, /*animflags*/ 0);
 
-                if (!target.mKeyframeControllers.emplace(strdata->string, callback).second)
-                    Log(Debug::Verbose) << "Controller " << strdata->string << " present more than once in "
+                if (!target.mKeyframeControllers.emplace(strdata->mData, callback).second)
+                    Log(Debug::Verbose) << "Controller " << strdata->mData << " present more than once in "
                                         << nif.getFilename() << ", ignoring later version";
             }
         }
@@ -509,15 +510,15 @@ namespace NifOsg
                 return nullptr;
 
             osg::ref_ptr<osg::Image> image;
-            if (!st->external && !st->data.empty())
+            if (st->mExternal)
             {
-                image = handleInternalTexture(st->data.getPtr());
-            }
-            else
-            {
-                std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS());
+                std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, imageManager->getVFS());
                 image = imageManager->getImage(filename);
             }
+            else if (!st->mData.empty())
+            {
+                image = handleInternalTexture(st->mData.getPtr());
+            }
             return image;
         }
 
@@ -536,38 +537,41 @@ namespace NifOsg
             }
 
             const Nif::NiTextureEffect* textureEffect = static_cast<const Nif::NiTextureEffect*>(nifNode);
-            if (textureEffect->textureType != Nif::NiTextureEffect::Environment_Map)
+            if (!textureEffect->mSwitchState)
+                return false;
+
+            if (textureEffect->mTextureType != Nif::NiTextureEffect::TextureType::EnvironmentMap)
             {
-                Log(Debug::Info) << "Unhandled NiTextureEffect type " << textureEffect->textureType << " in "
-                                 << mFilename;
+                Log(Debug::Info) << "Unhandled NiTextureEffect type "
+                                 << static_cast<uint32_t>(textureEffect->mTextureType) << " in " << mFilename;
                 return false;
             }
 
-            if (textureEffect->texture.empty())
+            if (textureEffect->mTexture.empty())
             {
                 Log(Debug::Info) << "NiTextureEffect missing source texture in " << mFilename;
                 return false;
             }
 
             osg::ref_ptr<osg::TexGen> texGen(new osg::TexGen);
-            switch (textureEffect->coordGenType)
+            switch (textureEffect->mCoordGenType)
             {
-                case Nif::NiTextureEffect::World_Parallel:
+                case Nif::NiTextureEffect::CoordGenType::WorldParallel:
                     texGen->setMode(osg::TexGen::OBJECT_LINEAR);
                     break;
-                case Nif::NiTextureEffect::World_Perspective:
+                case Nif::NiTextureEffect::CoordGenType::WorldPerspective:
                     texGen->setMode(osg::TexGen::EYE_LINEAR);
                     break;
-                case Nif::NiTextureEffect::Sphere_Map:
+                case Nif::NiTextureEffect::CoordGenType::SphereMap:
                     texGen->setMode(osg::TexGen::SPHERE_MAP);
                     break;
                 default:
-                    Log(Debug::Info) << "Unhandled NiTextureEffect coordGenType " << textureEffect->coordGenType
-                                     << " in " << mFilename;
+                    Log(Debug::Info) << "Unhandled NiTextureEffect CoordGenType "
+                                     << static_cast<uint32_t>(textureEffect->mCoordGenType) << " in " << mFilename;
                     return false;
             }
 
-            osg::ref_ptr<osg::Image> image(handleSourceTexture(textureEffect->texture.getPtr(), imageManager));
+            osg::ref_ptr<osg::Image> image(handleSourceTexture(textureEffect->mTexture.getPtr(), imageManager));
             osg::ref_ptr<osg::Texture2D> texture2d(new osg::Texture2D(image));
             if (image)
                 texture2d->setTextureSize(image->s(), image->t());
@@ -644,7 +648,7 @@ namespace NifOsg
 
             std::vector<Nif::ExtraPtr> extraCollection;
 
-            for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next)
+            for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->mNext)
                 extraCollection.emplace_back(e);
 
             for (const auto& extraNode : nifNode->extralist)
@@ -666,25 +670,25 @@ namespace NifOsg
 
                     // String markers may contain important information
                     // affecting the entire subtree of this obj
-                    if (sd->string == "MRK" && !Loader::getShowMarkers())
+                    if (sd->mData == "MRK" && !Loader::getShowMarkers())
                     {
                         // Marker objects. These meshes are only visible in the editor.
                         args.mHasMarkers = true;
                     }
-                    else if (sd->string == "BONE")
+                    else if (sd->mData == "BONE")
                     {
                         node->getOrCreateUserDataContainer()->addDescription("CustomBone");
                     }
-                    else if (sd->string.rfind(extraDataIdentifer, 0) == 0)
+                    else if (sd->mData.rfind(extraDataIdentifer, 0) == 0)
                     {
                         node->setUserValue(
-                            Misc::OsgUserValues::sExtraData, sd->string.substr(extraDataIdentifer.length()));
+                            Misc::OsgUserValues::sExtraData, sd->mData.substr(extraDataIdentifer.length()));
                     }
                 }
                 else if (e->recType == Nif::RC_BSXFlags)
                 {
                     auto bsxFlags = static_cast<const Nif::NiIntegerExtraData*>(e.getPtr());
-                    if (bsxFlags->data & 32) // Editor marker flag
+                    if (bsxFlags->mData & 32) // Editor marker flag
                         args.mHasMarkers = true;
                 }
             }
@@ -895,7 +899,7 @@ namespace NifOsg
                     if (!key->mInterpolator.empty() && key->mInterpolator->recType != Nif::RC_NiTransformInterpolator)
                     {
                         Log(Debug::Error) << "Unsupported interpolator type for NiKeyframeController " << key->recIndex
-                                          << " in " << mFilename;
+                                          << " in " << mFilename << ": " << key->mInterpolator->recName;
                         continue;
                     }
                     osg::ref_ptr<KeyframeController> callback = new KeyframeController(key);
@@ -922,7 +926,7 @@ namespace NifOsg
                         && visctrl->mInterpolator->recType != Nif::RC_NiBoolInterpolator)
                     {
                         Log(Debug::Error) << "Unsupported interpolator type for NiVisController " << visctrl->recIndex
-                                          << " in " << mFilename;
+                                          << " in " << mFilename << ": " << visctrl->mInterpolator->recName;
                         continue;
                     }
                     osg::ref_ptr<VisController> callback(new VisController(visctrl, Loader::getHiddenNodeMask()));
@@ -938,7 +942,7 @@ namespace NifOsg
                         && rollctrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator)
                     {
                         Log(Debug::Error) << "Unsupported interpolator type for NiRollController " << rollctrl->recIndex
-                                          << " in " << mFilename;
+                                          << " in " << mFilename << ": " << rollctrl->mInterpolator->recName;
                         continue;
                     }
                     osg::ref_ptr<RollController> callback = new RollController(rollctrl);
@@ -973,8 +977,9 @@ namespace NifOsg
                     if (!alphactrl->mInterpolator.empty()
                         && alphactrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator)
                     {
-                        Log(Debug::Error) << "Unsupported interpolator type for NiAlphaController "
-                                          << alphactrl->recIndex << " in " << mFilename;
+                        Log(Debug::Error)
+                            << "Unsupported interpolator type for NiAlphaController " << alphactrl->recIndex << " in "
+                            << mFilename << ": " << alphactrl->mInterpolator->recName;
                         continue;
                     }
                     osg::ref_ptr<AlphaController> osgctrl = new AlphaController(alphactrl, baseMaterial);
@@ -994,8 +999,9 @@ namespace NifOsg
                     if (!matctrl->mInterpolator.empty()
                         && matctrl->mInterpolator->recType != Nif::RC_NiPoint3Interpolator)
                     {
-                        Log(Debug::Error) << "Unsupported interpolator type for NiMaterialColorController "
-                                          << matctrl->recIndex << " in " << mFilename;
+                        Log(Debug::Error)
+                            << "Unsupported interpolator type for NiMaterialColorController " << matctrl->recIndex
+                            << " in " << mFilename << ": " << matctrl->mInterpolator->recName;
                         continue;
                     }
                     osg::ref_ptr<MaterialColorController> osgctrl = new MaterialColorController(matctrl, baseMaterial);
@@ -1021,7 +1027,7 @@ namespace NifOsg
                         && flipctrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator)
                     {
                         Log(Debug::Error) << "Unsupported interpolator type for NiFlipController " << flipctrl->recIndex
-                                          << " in " << mFilename;
+                                          << " in " << mFilename << ": " << flipctrl->mInterpolator->recName;
                         continue;
                     }
                     std::vector<osg::ref_ptr<osg::Texture2D>> textures;
@@ -1067,12 +1073,12 @@ namespace NifOsg
             attachTo->addChild(program);
             program->setParticleSystem(partsys);
             program->setReferenceFrame(rf);
-            for (; !affectors.empty(); affectors = affectors->next)
+            for (; !affectors.empty(); affectors = affectors->mNext)
             {
                 if (affectors->recType == Nif::RC_NiParticleGrowFade)
                 {
                     const Nif::NiParticleGrowFade* gf = static_cast<const Nif::NiParticleGrowFade*>(affectors.getPtr());
-                    program->addOperator(new GrowFadeAffector(gf->growTime, gf->fadeTime));
+                    program->addOperator(new GrowFadeAffector(gf->mGrowTime, gf->mFadeTime));
                 }
                 else if (affectors->recType == Nif::RC_NiGravity)
                 {
@@ -1083,9 +1089,9 @@ namespace NifOsg
                 {
                     const Nif::NiParticleColorModifier* cl
                         = static_cast<const Nif::NiParticleColorModifier*>(affectors.getPtr());
-                    if (cl->data.empty())
+                    if (cl->mData.empty())
                         continue;
-                    const Nif::NiColorData* clrdata = cl->data.getPtr();
+                    const Nif::NiColorData* clrdata = cl->mData.getPtr();
                     program->addOperator(new ParticleColorAffector(clrdata));
                 }
                 else if (affectors->recType == Nif::RC_NiParticleRotation)
@@ -1095,7 +1101,7 @@ namespace NifOsg
                 else
                     Log(Debug::Info) << "Unhandled particle modifier " << affectors->recName << " in " << mFilename;
             }
-            for (; !colliders.empty(); colliders = colliders->next)
+            for (; !colliders.empty(); colliders = colliders->mNext)
             {
                 if (colliders->recType == Nif::RC_NiPlanarCollider)
                 {
@@ -2008,15 +2014,15 @@ namespace NifOsg
 
             const unsigned int uvSet = 0;
 
-            for (size_t i = 0; i < textureSet->textures.size(); ++i)
+            for (size_t i = 0; i < textureSet->mTextures.size(); ++i)
             {
-                if (textureSet->textures[i].empty())
+                if (textureSet->mTextures[i].empty())
                     continue;
-                switch (i)
+                switch (static_cast<Nif::BSShaderTextureSet::TextureType>(i))
                 {
-                    case Nif::BSShaderTextureSet::TextureType_Base:
-                    case Nif::BSShaderTextureSet::TextureType_Normal:
-                    case Nif::BSShaderTextureSet::TextureType_Glow:
+                    case Nif::BSShaderTextureSet::TextureType::Base:
+                    case Nif::BSShaderTextureSet::TextureType::Normal:
+                    case Nif::BSShaderTextureSet::TextureType::Glow:
                         break;
                     default:
                     {
@@ -2026,7 +2032,7 @@ namespace NifOsg
                     }
                 }
                 std::string filename
-                    = Misc::ResourceHelpers::correctTexturePath(textureSet->textures[i], imageManager->getVFS());
+                    = Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], imageManager->getVFS());
                 osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
                 osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
                 if (image)
@@ -2035,17 +2041,19 @@ namespace NifOsg
                 unsigned int texUnit = boundTextures.size();
                 stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
                 // BSShaderTextureSet presence means there's no need for FFP support for the affected node
-                switch (i)
+                switch (static_cast<Nif::BSShaderTextureSet::TextureType>(i))
                 {
-                    case Nif::BSShaderTextureSet::TextureType_Base:
+                    case Nif::BSShaderTextureSet::TextureType::Base:
                         texture2d->setName("diffuseMap");
                         break;
-                    case Nif::BSShaderTextureSet::TextureType_Normal:
+                    case Nif::BSShaderTextureSet::TextureType::Normal:
                         texture2d->setName("normalMap");
                         break;
-                    case Nif::BSShaderTextureSet::TextureType_Glow:
+                    case Nif::BSShaderTextureSet::TextureType::Glow:
                         texture2d->setName("emissiveMap");
                         break;
+                    default:
+                        break;
                 }
                 boundTextures.emplace_back(uvSet);
             }
diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp
index 2700b7eb93..8be0d4ba4f 100644
--- a/components/nifosg/particle.cpp
+++ b/components/nifosg/particle.cpp
@@ -9,7 +9,6 @@
 
 #include <components/debug/debuglog.hpp>
 #include <components/misc/rng.hpp>
-#include <components/nif/controlled.hpp>
 #include <components/nif/data.hpp>
 #include <components/sceneutil/morphgeometry.hpp>
 #include <components/sceneutil/riggeometry.hpp>
@@ -281,20 +280,13 @@ namespace NifOsg
 
     GravityAffector::GravityAffector(const Nif::NiGravity* gravity)
         : mForce(gravity->mForce)
-        , mType(static_cast<ForceType>(gravity->mType))
+        , mType(gravity->mType)
         , mPosition(gravity->mPosition)
         , mDirection(gravity->mDirection)
         , mDecay(gravity->mDecay)
     {
     }
 
-    GravityAffector::GravityAffector()
-        : mForce(0)
-        , mType(Type_Wind)
-        , mDecay(0.f)
-    {
-    }
-
     GravityAffector::GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop)
         : osgParticle::Operator(copy, copyop)
     {
@@ -311,8 +303,8 @@ namespace NifOsg
     {
         bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF);
 
-        if (mType == Type_Point
-            || mDecay != 0.f) // we don't need the position for Wind gravity, except if decay is being applied
+        // We don't need the position for Wind gravity, except if decay is being applied
+        if (mType == Nif::NiGravity::ForceType::Point || mDecay != 0.f)
             mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition;
 
         mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection;
@@ -324,7 +316,7 @@ namespace NifOsg
         const float magic = 1.6f;
         switch (mType)
         {
-            case Type_Wind:
+            case Nif::NiGravity::ForceType::Wind:
             {
                 float decayFactor = 1.f;
                 if (mDecay != 0.f)
@@ -338,7 +330,7 @@ namespace NifOsg
 
                 break;
             }
-            case Type_Point:
+            case Nif::NiGravity::ForceType::Point:
             {
                 osg::Vec3f diff = mCachedWorldPosition - particle->getPosition();
 
diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp
index 3424512208..a9b628f695 100644
--- a/components/nifosg/particle.hpp
+++ b/components/nifosg/particle.hpp
@@ -10,15 +10,14 @@
 #include <osgParticle/Placer>
 #include <osgParticle/Shooter>
 
+#include <components/nif/particle.hpp> // NiGravity::ForceType
+
 #include <components/sceneutil/nodecallback.hpp>
 
 #include "controller.hpp" // ValueInterpolator
 
 namespace Nif
 {
-    struct NiGravity;
-    struct NiPlanarCollider;
-    struct NiSphericalCollider;
     struct NiColorData;
 }
 
@@ -180,7 +179,7 @@ namespace NifOsg
     {
     public:
         GravityAffector(const Nif::NiGravity* gravity);
-        GravityAffector();
+        GravityAffector() = default;
         GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);
 
         GravityAffector& operator=(const GravityAffector&) = delete;
@@ -191,16 +190,11 @@ namespace NifOsg
         void beginOperate(osgParticle::Program*) override;
 
     private:
-        float mForce;
-        enum ForceType
-        {
-            Type_Wind,
-            Type_Point
-        };
-        ForceType mType;
+        float mForce{ 0.f };
+        Nif::NiGravity::ForceType mType{ Nif::NiGravity::ForceType::Wind };
         osg::Vec3f mPosition;
         osg::Vec3f mDirection;
-        float mDecay;
+        float mDecay{ 0.f };
         osg::Vec3f mCachedWorldPosition;
         osg::Vec3f mCachedWorldDirection;
     };