From 2b528b7bc7c7e29dab64a8382d43269106e284c2 Mon Sep 17 00:00:00 2001 From: gavrant Date: Sat, 22 Jun 2024 20:46:45 +0300 Subject: [PATCH 1/3] Fixed animation (sub)toolbar always disabled The bug was introduced in one of my own UI refactors. --- src/ui/nifskope.ui | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index 15749e97..f858a242 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -92,9 +92,6 @@ - - false - Animation From 3dc2b35be464e93aa049052d93bbdb5b744d476e Mon Sep 17 00:00:00 2001 From: fo76utils <87907510+fo76utils@users.noreply.github.com> Date: Sun, 23 Jun 2024 15:44:38 +0200 Subject: [PATCH 2/3] New value types: ShortVector3, ByteVector4, UDecVector4, ByteColor4BGRA --- src/data/niftypes.h | 83 +++++++++++++++++++++++++ src/data/nifvalue.cpp | 34 +++++++++++ src/data/nifvalue.h | 77 +++++++++++++++++++++--- src/io/nifstream.cpp | 79 ++++++++++++++++++++++++ src/model/basemodel.cpp | 13 ++-- src/model/nifmodel.cpp | 25 +++----- src/spells/color.cpp | 3 + src/ui/widgets/valueedit.cpp | 113 +++++++++++++++++++++++++---------- 8 files changed, 361 insertions(+), 66 deletions(-) diff --git a/src/data/niftypes.h b/src/data/niftypes.h index b0c76006..f70b8f6e 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -482,6 +482,24 @@ class HalfVector3 : public Vector3 } }; +class ShortVector3 : public Vector3 +{ +public: + //! Default constructor + ShortVector3() + { + xyz[0] = xyz[1] = xyz[2] = 0.0; + } + //! Constructor + ShortVector3( float x, float y, float z ) : Vector3( x, y, z ) + { + } + + ShortVector3( Vector3 v ) : Vector3( v ) + { + } +}; + class UshortVector3 : public Vector3 { public: @@ -570,6 +588,14 @@ class Vector4 xyzw[2] = v3[2]; xyzw[3] = w; } + inline Vector4 & operator=( const FloatVector4 & v ) + { + xyzw[0] = v[0]; + xyzw[1] = v[1]; + xyzw[2] = v[2]; + xyzw[3] = v[3]; + return *this; + } //! Add-equals operator Vector4 & operator+=( const Vector4 & v ) { @@ -768,6 +794,44 @@ inline Vector3::Vector3( const Vector4 & v4 ) xyz[2] = v4[2]; } +class ByteVector4 : public Vector4 +{ +public: + ByteVector4() : Vector4( FloatVector4( 0.0f, 0.0f, 1.0f, 1.0f ) ) + { + } + + ByteVector4( const std::uint32_t & v ) : Vector4( ( FloatVector4(v) - 127.5f ) / 127.5f ) + { + } + + inline operator std::uint32_t() const + { + return std::uint32_t( FloatVector4( *this ) * 127.5f + 127.5f ); + } +}; + +class UDecVector4 : public Vector4 +{ +public: + UDecVector4() : Vector4( FloatVector4( 0.0f, 0.0f, 1.0f, 1.0f ) ) + { + } + + UDecVector4( const std::uint32_t & v ) : Vector4( FloatVector4::convertX10Y10Z10( v ) ) + { + xyzw[3] = ( !(v & 0x80000000U) ? 1.0f : -1.0f ); + } + + inline operator std::uint32_t() const + { + std::uint32_t v = FloatVector4( *this ).convertToX10Y10Z10(); + if ( xyzw[3] < 0.0f ) + v = v | 0x80000000U; + return v; + } +}; + //! A quaternion class Quat { @@ -1535,6 +1599,21 @@ class ByteColor4 : public Color4 } }; +class ByteColor4BGRA : public Color4 +{ +public: + //! Default constructor + ByteColor4BGRA() { rgba[0] = rgba[1] = rgba[2] = rgba[3] = 1.0; } + ByteColor4BGRA( const std::uint32_t & c ) + : Color4( FloatVector4(c).shuffleValues(0xC6) / 255.0f ) + { + } + inline operator std::uint32_t() const + { + return std::uint32_t( FloatVector4(*this).shuffleValues(0xC6) * 255.0f ); + } +}; + inline Color3::Color3( const Color4 & c4 ) { @@ -1971,12 +2050,16 @@ inline QDataStream & operator>>( QDataStream & ds, BSVertexDesc & d ) Q_DECLARE_TYPEINFO( Vector2, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( Vector3, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( HalfVector3, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( ShortVector3, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( UshortVector3, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( ByteVector3, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( Vector4, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( ByteVector4, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( UDecVector4, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( Color3, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( Color4, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( ByteColor4, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( ByteColor4BGRA, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( Triangle, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( Quat, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( Matrix, Q_MOVABLE_TYPE ); diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index 984a30e7..8d2aa822 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -96,6 +96,8 @@ void NifValue::initialize() typeMap.insert( "Color3", NifValue::tColor3 ); typeMap.insert( "Color4", NifValue::tColor4 ); typeMap.insert( "Vector4", NifValue::tVector4 ); + typeMap.insert( "ByteVector4", NifValue::tByteVector4 ); + typeMap.insert( "UDecVector4", NifValue::tUDecVector4 ); typeMap.insert( "Vector3", NifValue::tVector3 ); typeMap.insert( "TBC", NifValue::tVector3 ); typeMap.insert( "Quaternion", NifValue::tQuat ); @@ -122,11 +124,13 @@ void NifValue::initialize() typeMap.insert( "blob", NifValue::tBlob ); typeMap.insert( "hfloat", NifValue::tHfloat ); typeMap.insert( "HalfVector3", NifValue::tHalfVector3 ); + typeMap.insert( "ShortVector3", NifValue::tShortVector3 ); typeMap.insert( "UshortVector3", NifValue::tUshortVector3 ); typeMap.insert( "ByteVector3", NifValue::tByteVector3 ); typeMap.insert( "HalfVector2", NifValue::tHalfVector2 ); typeMap.insert( "HalfTexCoord", NifValue::tHalfVector2 ); typeMap.insert( "ByteColor4", NifValue::tByteColor4 ); + typeMap.insert( "ByteColor4BGRA", NifValue::tByteColor4BGRA ); //typeMap.insert( "BSVertexDesc", NifValue::tBSVertexDesc ); enumMap.clear(); @@ -361,10 +365,13 @@ void NifValue::clear() { switch ( typ ) { case tVector4: + case tByteVector4: + case tUDecVector4: delete static_cast( val.data ); break; case tVector3: case tHalfVector3: + case tShortVector3: case tUshortVector3: case tByteVector3: delete static_cast( val.data ); @@ -408,6 +415,7 @@ void NifValue::clear() break; case tColor4: case tByteColor4: + case tByteColor4BGRA: delete static_cast( val.data ); break; case tBSVertexDesc: @@ -439,6 +447,7 @@ void NifValue::changeType( Type t ) return; case tVector3: case tHalfVector3: + case tShortVector3: case tUshortVector3: case tByteVector3: val.data = new Vector3(); @@ -446,6 +455,10 @@ void NifValue::changeType( Type t ) case tVector4: val.data = new Vector4(); return; + case tByteVector4: + case tUDecVector4: + val.data = new ByteVector4(); + return; case tMatrix: val.data = new Matrix(); return; @@ -478,6 +491,7 @@ void NifValue::changeType( Type t ) return; case tColor4: case tByteColor4: + case tByteColor4BGRA: val.data = new Color4(); return; case tByteArray: @@ -511,11 +525,14 @@ void NifValue::operator=( const NifValue & other ) switch ( typ ) { case tVector3: case tHalfVector3: + case tShortVector3: case tUshortVector3: case tByteVector3: *static_cast( val.data ) = *static_cast( other.val.data ); return; case tVector4: + case tByteVector4: + case tUDecVector4: *static_cast( val.data ) = *static_cast( other.val.data ); return; case tMatrix: @@ -547,6 +564,7 @@ void NifValue::operator=( const NifValue & other ) return; case tColor4: case tByteColor4: + case tByteColor4BGRA: *static_cast( val.data ) = *static_cast( other.val.data ); return; case tByteArray: @@ -637,6 +655,7 @@ bool NifValue::operator==( const NifValue & other ) const case tColor4: case tByteColor4: + case tByteColor4BGRA: { Color4 * c1 = static_cast(val.data); Color4 * c2 = static_cast(other.val.data); @@ -661,6 +680,7 @@ bool NifValue::operator==( const NifValue & other ) const case tVector3: case tHalfVector3: + case tShortVector3: case tUshortVector3: case tByteVector3: { @@ -674,6 +694,8 @@ bool NifValue::operator==( const NifValue & other ) const } case tVector4: + case tByteVector4: + case tUDecVector4: { Vector4 * vec1 = static_cast(val.data); Vector4 * vec2 = static_cast(other.val.data); @@ -865,6 +887,7 @@ bool NifValue::setFromString( const QString & s, const BaseModel * model, const break; case tColor4: case tByteColor4: + case tByteColor4BGRA: static_cast( val.data )->fromQColor( QColor( s ) ); ok = true; break; @@ -882,6 +905,8 @@ bool NifValue::setFromString( const QString & s, const BaseModel * model, const ok = true; break; case tVector4: + case tByteVector4: + case tUDecVector4: static_cast( val.data )->fromString( s ); ok = true; break; @@ -957,6 +982,7 @@ QString NifValue::toString() const } case tColor4: case tByteColor4: + case tByteColor4BGRA: { Color4 * col = static_cast( val.data ); float r = col->red(), g = col->green(), b = col->blue(), a = col->alpha(); @@ -985,6 +1011,7 @@ QString NifValue::toString() const } case tVector3: case tHalfVector3: + case tShortVector3: case tUshortVector3: case tByteVector3: { @@ -996,6 +1023,8 @@ QString NifValue::toString() const .arg( NumOrMinMax( (*v)[2], 'f', VECTOR_DECIMALS ) ); } case tVector4: + case tByteVector4: + case tUDecVector4: { Vector4 * v = static_cast( val.data ); @@ -1151,6 +1180,8 @@ QString NifValue::getTypeDebugStr( NifValue::Type t ) case tMatrix4: typeStr = "Matrix4"; break; case tVector2: typeStr = "Vector2"; break; case tVector4: typeStr = "Vector4"; break; + case tByteVector4: typeStr = "ByteVector4"; break; + case tUDecVector4: typeStr = "UDecVector4"; break; case tTriangle: typeStr = "Triangle"; break; case tFileVersion: typeStr = "FileVersion"; break; case tByteArray: typeStr = "ByteArray"; break; @@ -1161,10 +1192,12 @@ QString NifValue::getTypeDebugStr( NifValue::Type t ) case tBlob: typeStr = "Blob"; break; case tHfloat: typeStr = "Hfloat"; break; case tHalfVector3: typeStr = "HalfVector3"; break; + case tShortVector3: typeStr = "ShortVector3"; break; case tUshortVector3: typeStr = "UshortVector3"; break; case tByteVector3: typeStr = "ByteVector3"; break; case tHalfVector2: typeStr = "HalfVector2"; break; case tByteColor4: typeStr = "ByteColor4"; break; + case tByteColor4BGRA: typeStr = "ByteColor4BGRA"; break; case tBSVertexDesc: typeStr = "BSVertexDesc"; break; case tNone: typeStr = "None"; break; default: typeStr = "UNKNOWN"; break; @@ -1180,6 +1213,7 @@ QColor NifValue::toColor( const BaseModel * model, const NifItem * item ) const return static_cast( val.data )->toQColor(); case tColor4: case tByteColor4: + case tByteColor4BGRA: return static_cast( val.data )->toQColor(); default: if ( model ) diff --git a/src/data/nifvalue.h b/src/data/nifvalue.h index 3813450c..37a20062 100644 --- a/src/data/nifvalue.h +++ b/src/data/nifvalue.h @@ -108,15 +108,26 @@ class NifValue tLineString, tChar8String, // all string types should come between tSizedString and tChar8String + // all color types should come between tColor3 and tByteColor4BGRA tColor3, tColor4, + tByteColor4, + tByteColor4BGRA, + // all vector3 types should come between tVector3 and tByteVector3 tVector3, + tHalfVector3, + tShortVector3, + tUshortVector3, + tByteVector3, tQuat, tQuatXYZW, tMatrix, tMatrix4, tVector2, + // all vector4 types should come between tVector4 and tUDecVector4 tVector4, + tByteVector4, + tUDecVector4, tTriangle, tFileVersion, tByteArray, @@ -126,11 +137,7 @@ class NifValue tByteMatrix, tBlob, tHfloat, - tHalfVector3, - tUshortVector3, - tByteVector3, tHalfVector2, - tByteColor4, tBSVertexDesc, tNormbyte, tNone= 0xff @@ -227,7 +234,7 @@ class NifValue static QString enumOptionText( const QString & eid, quint32 oval ); /*! Get an option from an option string. - * + * * @param eid The name of the enum type. * @param oid The name of the option. * @param ok Is set to true if successful, is set to false if the option string was not found. @@ -250,7 +257,7 @@ class NifValue //! Check if the type of the data is not tNone. bool isValid() const { return typ != tNone; } //! Check if the type of the data is a color type (Color3 or Color4 in xml). - bool isColor() const { return typ == tColor3 || typ == tColor4 || typ == tByteColor4; } + bool isColor() const { return typ >= tColor3 && typ <= tByteColor4BGRA; } //! Check if the type of the data is a count. bool isCount() const { return (typ >= tBool && typ <= tUInt); } //! Check if the type of the data is a flag type (Flags in xml). @@ -269,12 +276,18 @@ class NifValue bool isString() const { return (typ >= tSizedString && typ <= tChar8String) || typ == tString; } //! Check if the type of the data is a Vector 4. bool isVector4() const { return typ == tVector4; } + //! Check if the type of the data is ByteVector4. + bool isByteVector4() const { return typ == tByteVector4; } + //! Check if the type of the data is UDecVector4. + bool isUDecVector4() const { return typ == tUDecVector4; } + bool isAVector4() const { return typ >= tVector4 && typ <= tUDecVector4; } //! Check if the type of the data is a Vector 3. bool isVector3() const { return typ == tVector3; } //! Check if the type of the data is a Half Vector3. bool isHalfVector3() const { return typ == tHalfVector3; } //! Check if the type of the data is a Byte Vector3. bool isByteVector3() const { return typ == tByteVector3; } + bool isAVector3() const { return typ >= tVector3 && typ <= tByteVector3; } //! Check if the type of the data is a HalfVector2. bool isHalfVector2() const { return typ == tHalfVector2; } //! Check if the type of the data is a Vector 2. @@ -580,11 +593,24 @@ template <> inline Matrix4 NifValue::get( const BaseModel * model, const NifItem } template <> inline Vector4 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tVector4, model, item ); + if ( typ >= tVector4 && typ <= tUDecVector4 ) + return *static_cast(val.data); + + if ( model ) + reportConvertToError( model, item, "a Vector4" ); + return Vector4(); +} +template <> inline ByteVector4 NifValue::get( const BaseModel * model, const NifItem * item ) const +{ + return getType( tByteVector4, model, item ); +} +template <> inline UDecVector4 NifValue::get( const BaseModel * model, const NifItem * item ) const +{ + return getType( tUDecVector4, model, item ); } template <> inline Vector3 NifValue::get( const BaseModel * model, const NifItem * item ) const { - if ( typ == tVector3 || typ == tHalfVector3 ) + if ( typ >= tVector3 && typ <= tByteVector3 ) return *static_cast(val.data); if ( model ) @@ -595,6 +621,10 @@ template <> inline HalfVector3 NifValue::get( const BaseModel * model, const Nif { return getType( tHalfVector3, model, item ); } +template <> inline ShortVector3 NifValue::get( const BaseModel * model, const NifItem * item ) const +{ + return getType( tShortVector3, model, item ); +} template <> inline UshortVector3 NifValue::get( const BaseModel * model, const NifItem * item ) const { return getType( tUshortVector3, model, item ); @@ -624,9 +654,18 @@ template <> inline ByteColor4 NifValue::get( const BaseModel * model, const NifI { return getType( tByteColor4, model, item ); } +template <> inline ByteColor4BGRA NifValue::get( const BaseModel * model, const NifItem * item ) const +{ + return getType( tByteColor4BGRA, model, item ); +} template <> inline Color4 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tColor4, model, item ); + if ( typ >= tColor4 && typ <= tByteColor4BGRA ) + return *static_cast(val.data); + + if ( model ) + reportConvertToError( model, item, "a Color4" ); + return Color4(); } template <> inline Triangle NifValue::get( const BaseModel * model, const NifItem * item ) const { @@ -737,6 +776,16 @@ template <> inline bool NifValue::set( const Vector4 & x, const BaseModel * mode { return setType( tVector4, x, model, item ); } +//! Set the data from a ByteVector4. Return true if successful. +template <> inline bool NifValue::set( const ByteVector4 & x, const BaseModel * model, const NifItem * item ) +{ + return setType( tByteVector4, x, model, item ); +} +//! Set the data from a UDecVector4. Return true if successful. +template <> inline bool NifValue::set( const UDecVector4 & x, const BaseModel * model, const NifItem * item ) +{ + return setType( tUDecVector4, x, model, item ); +} //! Set the data from a Vector3. Return true if successful. template <> inline bool NifValue::set( const Vector3 & x, const BaseModel * model, const NifItem * item ) { @@ -747,6 +796,11 @@ template <> inline bool NifValue::set( const HalfVector3 & x, const BaseModel * { return setType( tHalfVector3, x, model, item ); } +//! Set the data from a ShortVector3. Return true if successful. +template <> inline bool NifValue::set( const ShortVector3 & x, const BaseModel * model, const NifItem * item ) +{ + return setType( tShortVector3, x, model, item ); +} //! Set the data from a UshortVector3. Return true if successful. template <> inline bool NifValue::set( const UshortVector3 & x, const BaseModel * model, const NifItem * item ) { @@ -777,6 +831,11 @@ template <> inline bool NifValue::set( const ByteColor4 & x, const BaseModel * m { return setType( tByteColor4, x, model, item ); } +//! Set the data from a ByteColor4BGRA. Return true if successful. +template <> inline bool NifValue::set( const ByteColor4BGRA & x, const BaseModel * model, const NifItem * item ) +{ + return setType( tByteColor4BGRA, x, model, item ); +} //! Set the data from a Color4. Return true if successful. template <> inline bool NifValue::set( const Color4 & x, const BaseModel * model, const NifItem * item ) { diff --git a/src/io/nifstream.cpp b/src/io/nifstream.cpp index 60bef5a6..e1c287d8 100644 --- a/src/io/nifstream.cpp +++ b/src/io/nifstream.cpp @@ -176,6 +176,24 @@ bool NifIStream::read( NifValue & val ) Vector3 * v = static_cast(val.val.data); v->xyz[0] = xf; v->xyz[1] = yf; v->xyz[2] = zf; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tShortVector3: + { + int16_t x, y, z; + float xf, yf, zf; + + *dataStream >> x; + *dataStream >> y; + *dataStream >> z; + + xf = (float) x; + yf = (float) y; + zf = (float) z; + + Vector3 * v = static_cast(val.val.data); + v->xyz[0] = xf; v->xyz[1] = yf; v->xyz[2] = zf; + return (dataStream->status() == QDataStream::Ok); } case NifValue::tUshortVector3: @@ -265,6 +283,20 @@ bool NifIStream::read( NifValue & val ) *dataStream >> *v; return (dataStream->status() == QDataStream::Ok); } + case NifValue::tByteVector4: + { + std::uint32_t v; + *dataStream >> v; + (void) new( static_cast(val.val.data) ) ByteVector4( v ); + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tUDecVector4: + { + std::uint32_t v; + *dataStream >> v; + (void) new( static_cast(val.val.data) ) UDecVector4( v ); + return (dataStream->status() == QDataStream::Ok); + } case NifValue::tTriangle: { Triangle * t = static_cast(val.val.data); @@ -301,6 +333,13 @@ bool NifIStream::read( NifValue & val ) (void) new( static_cast(val.val.data) ) ByteColor4( rgba ); return (dataStream->status() == QDataStream::Ok); } + case NifValue::tByteColor4BGRA: + { + std::uint32_t bgra; + *dataStream >> bgra; + (void) new( static_cast(val.val.data) ) ByteColor4BGRA( bgra ); + return (dataStream->status() == QDataStream::Ok); + } case NifValue::tColor4: { Color4 * c = static_cast(val.val.data); @@ -659,6 +698,19 @@ bool NifOStream::write( const NifValue & val ) return device->write( (char*)v, 3 ) == 3; } + case NifValue::tShortVector3: + { + Vector3 * vec = static_cast(val.val.data); + if ( !vec ) + return false; + + int16_t v[3]; + v[0] = (int16_t) round(vec->xyz[0]); + v[1] = (int16_t) round(vec->xyz[1]); + v[2] = (int16_t) round(vec->xyz[2]); + + return device->write( (char*)v, 6 ) == 6; + } case NifValue::tUshortVector3: { Vector3 * vec = static_cast(val.val.data); @@ -720,6 +772,24 @@ bool NifOStream::write( const NifValue & val ) return device->write( (char *)static_cast(val.val.data)->xyz, 12 ) == 12; case NifValue::tVector4: return device->write( (char *)static_cast(val.val.data)->xyzw, 16 ) == 16; + case NifValue::tByteVector4: + { + ByteVector4 * vec = static_cast(val.val.data); + if ( !vec ) + return false; + char v[4]; + FileBuffer::writeUInt32Fast( v, std::uint32_t( *vec ) ); + return device->write( v, 4 ) == 4; + } + case NifValue::tUDecVector4: + { + UDecVector4 * vec = static_cast(val.val.data); + if ( !vec ) + return false; + char v[4]; + FileBuffer::writeUInt32Fast( v, std::uint32_t( *vec ) ); + return device->write( v, 4 ) == 4; + } case NifValue::tTriangle: return device->write( (char *)static_cast(val.val.data)->v, 6 ) == 6; case NifValue::tQuat: @@ -746,6 +816,15 @@ bool NifOStream::write( const NifValue & val ) FileBuffer::writeUInt32Fast( c, std::uint32_t(*color) ); return device->write( c, 4 ) == 4; } + case NifValue::tByteColor4BGRA: + { + ByteColor4BGRA * color = static_cast(val.val.data); + if ( !color ) + return false; + char c[4]; + FileBuffer::writeUInt32Fast( c, std::uint32_t(*color) ); + return device->write( c, 4 ) == 4; + } case NifValue::tColor4: return device->write( (char *)static_cast(val.val.data)->rgba, 16 ) == 16; case NifValue::tSizedString: diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index eecf9a5b..5d496b1f 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -383,13 +383,11 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const return QString( "dec: %1
hex: 0x%2
bin: 0b%3" ).arg( f ).arg( f, 4, 16, QChar( '0' ) ).arg( f, 16, 2, QChar( '0' ) ); } case NifValue::tVector3: - return item->get().toHtml(); + case NifValue::tShortVector3: case NifValue::tUshortVector3: - return item->get().toHtml(); case NifValue::tHalfVector3: - return item->get().toHtml(); case NifValue::tByteVector3: - return item->get().toHtml(); + return item->get().toHtml(); case NifValue::tMatrix: return item->get().toHtml(); case NifValue::tQuat: @@ -400,12 +398,9 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const Color3 c = item->get(); return QString( "R %1
G %2
B %3" ).arg( c[0] ).arg( c[1] ).arg( c[2] ); } - case NifValue::tByteColor4: - { - Color4 c = item->get(); - return QString( "R %1
G %2
B %3
A %4" ).arg( c[0] ).arg( c[1] ).arg( c[2] ).arg( c[3] ); - } case NifValue::tColor4: + case NifValue::tByteColor4: + case NifValue::tByteColor4BGRA: { Color4 c = item->get(); return QString( "R %1
G %2
B %3
A %4" ).arg( c[0] ).arg( c[1] ).arg( c[2] ).arg( c[3] ); diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 85301c2d..990593d4 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -1476,13 +1476,11 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const return QString( "0x%1" ) .arg( item->getCountValue(), 8, 16, QChar( '0' ) ); case NifValue::tVector3: - return item->get().toHtml(); case NifValue::tHalfVector3: - return item->get().toHtml(); + case NifValue::tShortVector3: case NifValue::tUshortVector3: - return item->get().toHtml(); case NifValue::tByteVector3: - return item->get().toHtml(); + return item->get().toHtml(); case NifValue::tMatrix: return item->get().toHtml(); case NifValue::tMatrix4: @@ -1498,23 +1496,16 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const .arg( c[1] ) .arg( c[2] ); } - case NifValue::tByteColor4: - { - Color4 c = item->get(); - return QString( "R %1\nG %2\nB %3\nA %4" ) - .arg( c[0] ) - .arg( c[1] ) - .arg( c[2] ) - .arg( c[3] ); - } case NifValue::tColor4: + case NifValue::tByteColor4: + case NifValue::tByteColor4BGRA: { Color4 c = item->get(); return QString( "R %1\nG %2\nB %3\nA %4" ) - .arg( c[0] ) - .arg( c[1] ) - .arg( c[2] ) - .arg( c[3] ); + .arg( c[0] ) + .arg( c[1] ) + .arg( c[2] ) + .arg( c[3] ); } default: break; diff --git a/src/spells/color.cpp b/src/spells/color.cpp index 18d24348..8557d91c 100644 --- a/src/spells/color.cpp +++ b/src/spells/color.cpp @@ -35,6 +35,9 @@ class spChooseColor final : public Spell } else if ( typ == NifValue::tByteColor4 ) { auto col = ColorWheel::choose( nif->get( index ) ); nif->set( index, *static_cast(&col) ); + } else if ( typ == NifValue::tByteColor4BGRA ) { + auto col = ColorWheel::choose( nif->get( index ) ); + nif->set( index, *static_cast(&col) ); } diff --git a/src/ui/widgets/valueedit.cpp b/src/ui/widgets/valueedit.cpp index ed578170..81b10369 100644 --- a/src/ui/widgets/valueedit.cpp +++ b/src/ui/widgets/valueedit.cpp @@ -56,16 +56,50 @@ ValueEdit::ValueEdit( QWidget * parent ) : QWidget( parent ), typ( NifValue::tNo bool ValueEdit::canEdit( NifValue::Type t ) { - return t == NifValue::tByte || t == NifValue::tWord || t == NifValue::tInt || t == NifValue::tFlags - || t == NifValue::tLink || t == NifValue::tUpLink || t == NifValue::tFloat || t == NifValue::tText - || t == NifValue::tSizedString || t == NifValue::tSizedString16 || t == NifValue::tLineString - || t == NifValue::tChar8String || t == NifValue::tShortString || t == NifValue::tStringIndex - || t == NifValue::tString || t == NifValue::tVector4 || t == NifValue::tVector3 || t == NifValue::tVector2 - || t == NifValue::tColor3 || t == NifValue::tColor4 || t == NifValue::tByteColor4 - || t == NifValue::tMatrix || t == NifValue::tQuat || t == NifValue::tQuatXYZW - || t == NifValue::tTriangle || t == NifValue::tShort || t == NifValue::tUInt || t == NifValue::tULittle32 - || t == NifValue::tHfloat || t == NifValue::tHalfVector3 || t == NifValue::tByteVector3 - || t == NifValue::tHalfVector2 || t == NifValue::tNormbyte; + switch ( t ) { + case NifValue::tByte: + case NifValue::tWord: + case NifValue::tInt: + case NifValue::tFlags: + case NifValue::tLink: + case NifValue::tUpLink: + case NifValue::tFloat: + case NifValue::tText: + case NifValue::tSizedString: + case NifValue::tSizedString16: + case NifValue::tLineString: + case NifValue::tChar8String: + case NifValue::tShortString: + case NifValue::tStringIndex: + case NifValue::tString: + case NifValue::tVector4: + case NifValue::tByteVector4: + case NifValue::tUDecVector4: + case NifValue::tVector3: + case NifValue::tVector2: + case NifValue::tColor3: + case NifValue::tColor4: + case NifValue::tByteColor4: + case NifValue::tByteColor4BGRA: + case NifValue::tMatrix: + case NifValue::tQuat: + case NifValue::tQuatXYZW: + case NifValue::tTriangle: + case NifValue::tShort: + case NifValue::tUInt: + case NifValue::tULittle32: + case NifValue::tHfloat: + case NifValue::tShortVector3: + case NifValue::tUshortVector3: + case NifValue::tHalfVector3: + case NifValue::tByteVector3: + case NifValue::tHalfVector2: + case NifValue::tNormbyte: + return true; + default: + break; + } + return false; } class CenterLabel final : public QLabel @@ -258,14 +292,9 @@ void ValueEdit::setValue( const NifValue & v ) // te->setBaseSize( width(), height() * 5); // edit = te; //} break; - case NifValue::tByteColor4: - { - ColorEdit * ce = new ColorEdit( this ); - ce->setColor4( v.get( nullptr, nullptr ) ); - edit = ce; - } - break; case NifValue::tColor4: + case NifValue::tByteColor4: + case NifValue::tByteColor4BGRA: { ColorEdit * ce = new ColorEdit( this ); ce->setColor4( v.get( nullptr, nullptr ) ); @@ -280,27 +309,19 @@ void ValueEdit::setValue( const NifValue & v ) } break; case NifValue::tVector4: + case NifValue::tByteVector4: + case NifValue::tUDecVector4: { VectorEdit * ve = new VectorEdit( this ); ve->setVector4( v.get( nullptr, nullptr ) ); edit = ve; } break; - case NifValue::tByteVector3: - { - VectorEdit * ve = new VectorEdit( this ); - ve->setVector3( v.get( nullptr, nullptr ) ); - edit = ve; - } - break; - case NifValue::tHalfVector3: - { - VectorEdit * ve = new VectorEdit( this ); - ve->setVector3( v.get( nullptr, nullptr ) ); - edit = ve; - } - break; case NifValue::tVector3: + case NifValue::tHalfVector3: + case NifValue::tShortVector3: + case NifValue::tUshortVector3: + case NifValue::tByteVector3: { VectorEdit * ve = new VectorEdit( this ); ve->setVector3( v.get( nullptr, nullptr ) ); @@ -422,6 +443,12 @@ NifValue ValueEdit::getValue() const case NifValue::tText: val.setFromString( qobject_cast( edit )->toPlainText(), nullptr, nullptr ); break; + case NifValue::tByteColor4BGRA: + { + auto col = qobject_cast(edit)->getColor4(); + val.set( *static_cast(&col), nullptr, nullptr ); + break; + } case NifValue::tByteColor4: { auto col = qobject_cast(edit)->getColor4(); @@ -437,6 +464,30 @@ NifValue ValueEdit::getValue() const case NifValue::tVector4: val.set( qobject_cast( edit )->getVector4(), nullptr, nullptr ); break; + case NifValue::tByteVector4: + { + auto vec = qobject_cast(edit)->getVector4(); + val.set( *static_cast(&vec), nullptr, nullptr ); + break; + } + case NifValue::tUDecVector4: + { + auto vec = qobject_cast(edit)->getVector4(); + val.set( *static_cast(&vec), nullptr, nullptr ); + break; + } + case NifValue::tShortVector3: + { + auto vec = qobject_cast(edit)->getVector3(); + val.set( *static_cast(&vec), nullptr, nullptr ); + break; + } + case NifValue::tUshortVector3: + { + auto vec = qobject_cast(edit)->getVector3(); + val.set( *static_cast(&vec), nullptr, nullptr ); + break; + } case NifValue::tByteVector3: { auto vec = qobject_cast(edit)->getVector3(); From 65b7723cb2f7c7824621e79f443f08e1af6e2e01 Mon Sep 17 00:00:00 2001 From: fo76utils <87907510+fo76utils@users.noreply.github.com> Date: Sun, 23 Jun 2024 15:46:27 +0200 Subject: [PATCH 3/3] Implementing support for Starfield NIF files with internal geometry data --- build/nif.xml | 89 ++++++- src/gl/BSMesh.cpp | 41 +--- src/gl/BSMesh.h | 1 - src/io/MeshFile.cpp | 505 +++++++++++++++++++++++++++----------- src/io/MeshFile.h | 38 +-- src/lib/importex/gltf.cpp | 57 +++-- src/spells/mesh.cpp | 6 +- src/ui/widgets/uvedit.cpp | 12 +- 8 files changed, 527 insertions(+), 222 deletions(-) diff --git a/build/nif.xml b/build/nif.xml index 34d4aabe..89177232 100644 --- a/build/nif.xml +++ b/build/nif.xml @@ -1728,6 +1728,14 @@ Alpha color component. + + A color with alpha (blue, green, red, alpha). + Blue color component. + Green color component. + Red color component. + Alpha color component. + + A string that contains the path to a file. The normal string. @@ -1767,6 +1775,13 @@ Third coordinate. + + A vector in 3D space (x,y,z). + First coordinate. + Second coordinate. + Third coordinate. + + A vector in 3D space (x,y,z). First coordinate. @@ -1789,6 +1804,21 @@ Fourth coordinate. + + A 4-dimensional vector. + First coordinate. + Second coordinate. + Third coordinate. + Fourth coordinate. + + + + First coordinate. + Second coordinate. + Third coordinate. + Fourth coordinate. + + A quaternion. The w-coordinate. @@ -9184,16 +9214,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Vertex coordinate scale * 32767.0 + + + + + + + + + + + + + + + + + + + + + + + - + + - + @@ -9202,7 +9285,7 @@ - + diff --git a/src/gl/BSMesh.cpp b/src/gl/BSMesh.cpp index 8712ae40..44f16448 100644 --- a/src/gl/BSMesh.cpp +++ b/src/gl/BSMesh.cpp @@ -194,25 +194,6 @@ QString BSMesh::textStats() const return QString(); } -void BSMesh::forMeshIndex(const NifModel* nif, std::function& f) -{ - for ( int i = 0; i < 4; i++ ) { - auto meshArray = QModelIndex_child( iMeshes, i ); - bool hasMesh = nif->get( QModelIndex_child( meshArray ) ); - auto mesh = QModelIndex_child( meshArray, 1 ); - if ( hasMesh ) { - auto meshPath = nif->get(mesh, "Mesh Path"); - if ( !meshPath.startsWith("geometries", Qt::CaseInsensitive) ) { - meshPath = "geometries\\" + meshPath; - } - if ( !meshPath.endsWith(".mesh") ) { - meshPath += ".mesh"; - } - f(meshPath, i); - } - } -} - int BSMesh::meshCount() { return meshes.size(); @@ -250,16 +231,18 @@ void BSMesh::updateImpl(const NifModel* nif, const QModelIndex& index) iData = index; iMeshes = nif->getIndex(index, "Meshes"); meshes.clear(); - std::function createMeshFile = [&](const QString& meshPath, int lodLevel) { - auto mesh = std::make_shared(meshPath, nif); - if ( mesh->isValid() ) { - meshes.append(mesh); - if ( lodLevel > 0 || mesh->lods.size() > 0 ) - emit nif->lodSliderChanged(true); + for ( int i = 0; i < 4; i++ ) { + auto meshArray = QModelIndex_child( iMeshes, i ); + bool hasMesh = nif->get( QModelIndex_child( meshArray ) ); + if ( hasMesh ) { + auto mesh = std::make_shared( nif, QModelIndex_child( meshArray, 1 ) ); + if ( mesh->isValid() ) { + meshes.append(mesh); + if ( lodLevel > 0 || mesh->lods.size() > 0 ) + emit nif->lodSliderChanged(true); + } } - }; - - forMeshIndex(nif, createMeshFile); + } } void BSMesh::updateData(const NifModel* nif) @@ -310,8 +293,8 @@ void BSMesh::updateData(const NifModel* nif) transColors = mesh->colors; hasVertexColors = !transColors.empty(); transNorms = mesh->normals; - transTangents = mesh->bitangents; transBitangents = mesh->tangents; + mesh->calculateBitangents( transTangents ); weightsUNORM = mesh->weights; gpuLODs = mesh->lods; diff --git a/src/gl/BSMesh.h b/src/gl/BSMesh.h index 16158867..da984ee3 100644 --- a/src/gl/BSMesh.h +++ b/src/gl/BSMesh.h @@ -32,7 +32,6 @@ class BSMesh : public Shape QString textStats() const override; // TODO (Gavrant): move to Shape - void forMeshIndex(const NifModel* nif, std::function& f); int meshCount(); // end Node diff --git a/src/io/MeshFile.cpp b/src/io/MeshFile.cpp index 9b89c041..dfec79fd 100644 --- a/src/io/MeshFile.cpp +++ b/src/io/MeshFile.cpp @@ -1,9 +1,10 @@ #include "io/MeshFile.h" #include "model/nifmodel.h" +#include "qtcompat.h" +#include +#include #include -#include -#include #include "fp32vec4.hpp" #if 0 @@ -11,187 +12,401 @@ double snormToDouble(int16_t x) { return x < 0 ? x / double(32768) : x / double(32767); } #endif -MeshFile::MeshFile( const QString & filepath, const NifModel * nif ) +MeshFile::MeshFile( const void * data, size_t size ) { - QString path( QDir::fromNativeSeparators(filepath.toLower()) ); - if ( path.isEmpty() || !nif ) - return; + update( data, size ); +} - if ( nif->getResourceFile( data, path, "geometries", ".mesh" ) && readMesh() > 0 ) { - qDebug() << "MeshFile created for" << filepath; - } else { - qWarning() << "MeshFile creation failed for" << filepath; - } +MeshFile::MeshFile( const NifModel * nif, const QString & path ) +{ + update( nif, path ); } -bool MeshFile::isValid() +MeshFile::MeshFile( const NifModel * nif, const QModelIndex & index ) { - return !data.isNull(); + update( nif, index ); } -quint32 MeshFile::readMesh() +void MeshFile::clear() { - if ( data.isEmpty() ) - return 0; + if ( !haveData ) + return; - QBuffer f(&data); - if ( f.open(QIODevice::ReadOnly) ) { - in.setDevice(&f); - in.setByteOrder(QDataStream::LittleEndian); - in.setFloatingPointPrecision(QDataStream::SinglePrecision); + positions.clear(); + normals.clear(); + colors.clear(); + tangents.clear(); + bitangentsBasis.clear(); + haveTexCoord2 = false; + coords.clear(); + weights.clear(); + weightsPerVertex = 0; + triangles.clear(); + lods.clear(); + + haveData = false; +} - quint32 magic; - in >> magic; - if ( magic > 2U ) - return 0; +void MeshFile::update( const void * data, size_t size ) +{ + clear(); + if ( !( data && size > 0 ) ) + return; - quint32 indicesSize; - in >> indicesSize; - triangles.resize(indicesSize / 3); + QBuffer f; + f.setData( reinterpret_cast< const char * >( data ), qsizetype( size ) ); + if ( !f.open(QIODevice::ReadOnly) ) + return; - for ( quint32 i = 0; i < indicesSize / 3; i++ ) { - Triangle tri; - in >> tri; - triangles[i] = tri; + QDataStream in; + in.setDevice(&f); + in.setByteOrder(QDataStream::LittleEndian); + in.setFloatingPointPrecision(QDataStream::SinglePrecision); - } + quint32 magic; + in >> magic; + if ( magic > 2U ) + return; - float scale; - in >> scale; - if ( scale <= 0.0 ) - return 0; // From RE + quint32 indicesSize; + in >> indicesSize; + triangles.resize( indicesSize / 3 ); + haveData = true; - quint32 numWeightsPerVertex; - in >> numWeightsPerVertex; - weightsPerVertex = numWeightsPerVertex; + for ( quint32 i = 0; i < indicesSize / 3; i++ ) { + Triangle tri; + in >> tri; + triangles[i] = tri; + } - quint32 numPositions; - in >> numPositions; - positions.resize(numPositions + positions.count()); + float scale; + in >> scale; + if ( scale <= 0.0f ) { + clear(); + return; // From RE + } - for ( int i = 0; i < positions.count(); i++ ) { - uint32_t xy; - uint16_t z; + quint32 numWeightsPerVertex; + in >> numWeightsPerVertex; + weightsPerVertex = numWeightsPerVertex; - in >> xy; - in >> z; - FloatVector4 xyz(FloatVector4::convertInt16((std::uint64_t(z) << 32) | xy)); - xyz /= 32767.0f; - xyz *= scale; + quint32 numPositions; + in >> numPositions; + if ( !numPositions ) { + clear(); + return; + } + positions.resize( numPositions ); - positions[i] = Vector3(xyz[0], xyz[1], xyz[2]); - } + for ( int i = 0; i < int(numPositions); i++ ) { + uint32_t xy; + uint16_t z; - quint32 numCoord1; - in >> numCoord1; - coords.resize(numCoord1); + in >> xy; + in >> z; + FloatVector4 xyz(FloatVector4::convertInt16((std::uint64_t(z) << 32) | xy)); + xyz /= 32767.0f; + xyz *= scale; - for ( int i = 0; i < coords.count(); i++ ) { - uint32_t uv; + positions[i] = Vector3(xyz[0], xyz[1], xyz[2]); + } - in >> uv; + quint32 numCoord1; + in >> numCoord1; + coords.resize( numCoord1 ); - coords[i] = Vector4(FloatVector4::convertFloat16(uv)); - } + for ( int i = 0; i < int(numCoord1); i++ ) { + uint32_t uv; - quint32 numCoord2; - in >> numCoord2; - numCoord2 = std::min(numCoord2, numCoord1); - haveTexCoord2 = bool(numCoord2); + in >> uv; - for ( quint32 i = 0; i < numCoord2; i++ ) { - uint32_t uv; + coords[i] = Vector4(FloatVector4::convertFloat16(uv)); + } - in >> uv; - FloatVector4 uv_f(FloatVector4::convertFloat16(uv)); + quint32 numCoord2; + in >> numCoord2; + numCoord2 = std::min( numCoord2, numCoord1 ); + haveTexCoord2 = bool( numCoord2 ); - coords[i][2] = uv_f[0]; - coords[i][3] = uv_f[1]; - } + for ( quint32 i = 0; i < numCoord2; i++ ) { + uint32_t uv; - quint32 numColor; - in >> numColor; - if ( numColor > 0 ) { - colors.resize(numColor + colors.count()); - } - for ( quint32 i = 0; i < numColor; i++ ) { - uint32_t bgra; - in >> bgra; - colors[i] = Color4( ( FloatVector4(bgra) / 255.0f ).shuffleValues( 0xC6 ) ); // 2, 1, 0, 3 - } + in >> uv; + FloatVector4 uv_f(FloatVector4::convertFloat16(uv)); - quint32 numNormal; - in >> numNormal; - if ( numNormal > 0 ) { - normals.resize(numNormal + normals.count()); - } - for ( int i = 0; i < normals.count(); i++ ) { - quint32 n; - in >> n; - FloatVector4 v(FloatVector4::convertX10Y10Z10(n)); - normals[i] = Vector3(v[0], v[1], v[2]); - } + coords[i][2] = uv_f[0]; + coords[i][3] = uv_f[1]; + } - quint32 numTangent; - in >> numTangent; - if ( numTangent > 0 ) { - tangents.resize(numTangent + tangents.count()); - tangentsBasis.resize(numTangent + tangentsBasis.count()); - bitangents.resize(numTangent + bitangents.count()); - } - for ( int i = 0; i < tangents.count(); i++ ) { - quint32 n; - in >> n; - bool b = bool(n & 0x80000000U); - FloatVector4 v(FloatVector4::convertX10Y10Z10(n)); - tangents[i] = Vector3(v[0], v[1], v[2]); - // For export - tangentsBasis[i] = Vector4(v[0], v[1], v[2], (b) ? 1.0 : -1.0); - if (b) - v *= -1.0f; - v = v.crossProduct3(FloatVector4(normals[i][0], normals[i][1], normals[i][2], 0.0f)); - bitangents[i] = Vector3(v[0], v[1], v[2]); - } + quint32 numColor; + in >> numColor; + if ( numColor > 0 ) { + colors.resize( numColor ); + } + for ( quint32 i = 0; i < numColor; i++ ) { + uint32_t bgra; + in >> bgra; + colors[i] = Color4( ( FloatVector4(bgra) / 255.0f ).shuffleValues( 0xC6 ) ); // 2, 1, 0, 3 + } + + quint32 numNormal; + in >> numNormal; + if ( numNormal > 0 ) { + normals.resize( numNormal ); + } + for ( int i = 0; i < int(numNormal); i++ ) { + std::uint32_t n; + in >> n; + normals[i] = Vector3( UDecVector4(n) ); + } - quint32 numWeights; - in >> numWeights; - if ( numWeights > 0 && numWeightsPerVertex > 0 ) { - weights.resize(numWeights / numWeightsPerVertex); + quint32 numTangent; + in >> numTangent; + if ( numTangent > 0 ) { + tangents.resize( numTangent ); + bitangentsBasis.resize( numTangent ); + } + for ( int i = 0; i < int(numTangent); i++ ) { + std::uint32_t n; + in >> n; + UDecVector4 v( n ); + tangents[i] = Vector3( v ); + bitangentsBasis[i] = v[3]; + } + + quint32 numWeights; + in >> numWeights; + if ( numWeights > 0 && numWeightsPerVertex > 0 ) { + weights.resize(numWeights / numWeightsPerVertex); + } + for ( int i = 0; i < weights.count(); i++ ) { + QVector> weightsUNORM; + for ( quint32 j = 0; j < 8; j++ ) { + if ( j < numWeightsPerVertex ) { + quint16 b, w; + in >> b; + in >> w; + weightsUNORM.append({ b, w }); + } else { + weightsUNORM.append({0, 0}); + } } - for ( int i = 0; i < weights.count(); i++ ) { - QVector> weightsUNORM; - for ( quint32 j = 0; j < 8; j++ ) { - if ( j < numWeightsPerVertex ) { - quint16 b, w; - in >> b; - in >> w; - weightsUNORM.append({ b, w }); - } else { - weightsUNORM.append({0, 0}); - } + weights[i] = BoneWeightsUNorm(weightsUNORM, i); + } + + if ( magic ) { + quint32 numLODs; + in >> numLODs; + lods.resize(numLODs); + for ( quint32 i = 0; i < numLODs; i++ ) { + quint32 indicesSize2; + in >> indicesSize2; + lods[i].resize(indicesSize2 / 3); + + for ( quint32 j = 0; j < indicesSize2 / 3; j++ ) { + Triangle tri; + in >> tri; + lods[i][j] = tri; } - weights[i] = BoneWeightsUNorm(weightsUNORM, i); } + } +} - if ( magic ) { - quint32 numLODs; - in >> numLODs; - lods.resize(numLODs); - for ( quint32 i = 0; i < numLODs; i++ ) { - quint32 indicesSize2; - in >> indicesSize2; - lods[i].resize(indicesSize2 / 3); - - for ( quint32 j = 0; j < indicesSize2 / 3; j++ ) { - Triangle tri; - in >> tri; - lods[i][j] = tri; - } +void MeshFile::update( const NifModel * nif, const QString & path ) +{ + clear(); + + if ( path.isEmpty() || !nif ) + return; + + QByteArray data; + if ( nif->getResourceFile( data, path, "geometries", ".mesh" ) ) + update( data.data(), size_t(data.size()) ); + if ( haveData ) + qDebug() << "MeshFile created for" << path; + else + qWarning() << "MeshFile creation failed for" << path; +} + +void MeshFile::update( const NifModel * nif, const QModelIndex & index ) +{ + clear(); + if ( !( nif && index.isValid() ) ) + return; + auto meshPath = nif->getIndex( index, "Mesh Path" ); + if ( meshPath.isValid() ) { + update( nif, nif->get( meshPath ) ); + return; + } + auto meshData = nif->getIndex( index, "Mesh Data" ); + if ( !meshData.isValid() ) + return; + + quint32 magic = nif->get( meshData, "Version" ); + if ( magic > 2U ) + return; + + quint32 indicesSize = nif->get( meshData, "Indices Size" ); + auto trianglesIndex = nif->getIndex( meshData, "Triangles" ); + if ( !trianglesIndex.isValid() ) + indicesSize = 0; + triangles.resize( indicesSize / 3 ); + haveData = true; + + for ( int i = 0; i < int(indicesSize / 3); i++ ) + triangles[i] = nif->get( QModelIndex_child( trianglesIndex, i ) ); + + float scale = nif->get( meshData, "Scale" ); + if ( scale <= 0.0f ) { + clear(); + return; // From RE + } + + quint32 numWeightsPerVertex = nif->get( meshData, "Weights Per Vertex" ); + weightsPerVertex = numWeightsPerVertex; + + quint32 numPositions = nif->get( meshData, "Num Verts" ); + auto verticesIndex = nif->getIndex( meshData, "Vertices" ); + if ( !( numPositions && verticesIndex.isValid() ) ) { + clear(); + return; + } + positions.resize( numPositions ); + + for ( int i = 0; i < int(numPositions); i++ ) { + Vector3 xyz = nif->get( QModelIndex_child( verticesIndex, i ) ); + xyz /= 32767.0f; + xyz *= scale; + + positions[i] = xyz; + } + + quint32 numCoord1 = nif->get( meshData, "Num UVs" ); + auto uvIndex1 = nif->getIndex( meshData, "UVs" ); + if ( !uvIndex1.isValid() ) + numCoord1 = 0; + coords.resize( numCoord1 ); + + for ( int i = 0; i < int(numCoord1); i++ ) { + HalfVector2 uv = nif->get( QModelIndex_child( uvIndex1, i ) ); + + coords[i] = Vector4( uv[0], uv[1], 0.0f, 0.0f ); + } + + quint32 numCoord2 = nif->get( meshData, "Num UVs 2" ); + auto uvIndex2 = nif->getIndex( meshData, "UVs 2" ); + if ( !uvIndex2.isValid() ) + numCoord2 = 0; + numCoord2 = std::min( numCoord2, numCoord1 ); + haveTexCoord2 = bool( numCoord2 ); + + for ( quint32 i = 0; i < numCoord2; i++ ) { + HalfVector2 uv = nif->get( QModelIndex_child( uvIndex2, i ) ); + + coords[i][2] = uv[0]; + coords[i][3] = uv[1]; + } + + quint32 numColor = nif->get( meshData, "Num Vertex Colors" ); + auto colorsIndex = nif->getIndex( meshData, "Vertex Colors" ); + if ( !colorsIndex.isValid() ) + numColor = 0; + if ( numColor > 0 ) + colors.resize( numColor ); + for ( int i = 0; i < int(numColor); i++ ) + colors[i] = nif->get( QModelIndex_child( colorsIndex, i ) ); + + quint32 numNormal = nif->get( meshData, "Num Normals" ); + auto normalsIndex = nif->getIndex( meshData, "Normals" ); + if ( !normalsIndex.isValid() ) + numNormal = 0; + if ( numNormal > 0 ) + normals.resize( numNormal ); + for ( int i = 0; i < int(numNormal); i++ ) + normals[i] = Vector3( nif->get( QModelIndex_child( normalsIndex, i ) ) ); + + quint32 numTangent = nif->get( meshData, "Num Tangents" ); + auto tangentsIndex = nif->getIndex( meshData, "Tangents" ); + if ( !tangentsIndex.isValid() ) + numTangent = 0; + if ( numTangent > 0 ) { + tangents.resize( numTangent ); + bitangentsBasis.resize( numTangent ); + } + for ( int i = 0; i < int(numTangent); i++ ) { + Vector4 v = nif->get( QModelIndex_child( tangentsIndex, i ) ); + tangents[i] = Vector3( v ); + bitangentsBasis[i] = v[3]; + } + + quint32 numWeights = nif->get( meshData, "Num Weights" ); + auto weightsIndex = nif->getIndex( meshData, "Weights" ); + if ( !weightsIndex.isValid() ) + numWeights = 0; + if ( numWeights > 0 && numWeightsPerVertex > 0 ) { + weights.resize(numWeights / numWeightsPerVertex); + } + int k = 0; + for ( int i = 0; i < weights.count(); i++ ) { + QVector> weightsUNORM; + for ( quint32 j = 0; j < 8; j++ ) { + if ( j < numWeightsPerVertex ) { + quint16 b = nif->get( QModelIndex_child( weightsIndex, k, 0 ) ); + quint16 w = nif->get( QModelIndex_child( weightsIndex, k, 1 ) ); + weightsUNORM.append({ b, w }); + k++; + } else { + weightsUNORM.append({0, 0}); } } + weights[i] = BoneWeightsUNorm(weightsUNORM, i); + } - return numPositions; + quint32 numLODs = 0; + auto lodsIndex = nif->getIndex( meshData, "LODs" ); + if ( lodsIndex.isValid() ) + numLODs = nif->get( meshData, "Num LODs" ); + lods.resize(numLODs); + for ( quint32 i = 0; i < numLODs; i++ ) { + auto lodIndex = QModelIndex_child( lodsIndex, int(i) ); + if ( !lodIndex.isValid() ) + continue; + auto trianglesIndex2 = nif->getIndex( lodIndex, "Triangles" ); + if ( !trianglesIndex2.isValid() ) + continue; + quint32 indicesSize2 = nif->get( lodIndex, "Indices Size" ); + lods[i].resize(indicesSize2 / 3); + + for ( quint32 j = 0; j < indicesSize2 / 3; j++ ) + lods[i][j] = nif->get( QModelIndex_child( trianglesIndex2, int(j) ) ); } +} - return 0; +void MeshFile::calculateBitangents( QVector & bitangents ) const +{ + bitangents.clear(); + qsizetype n = tangents.size(); + bitangents.resize( n ); + const Vector3 * srcN = normals.data(); + const Vector3 * srcT = tangents.data(); + const float * srcB = bitangentsBasis.data(); + Vector3 * dstB = bitangents.data(); + qsizetype m = std::min< qsizetype >( n, normals.size() ); + m = std::min< qsizetype >( m, bitangentsBasis.size() ); + qsizetype i = 0; + for ( ; (i + 1) < m; i++ ) { + FloatVector4 t( &(srcT[i][0]) ); + t = ( t * srcB[i] ).crossProduct3( FloatVector4( &(srcN[i][0]) ) ); + dstB[i] = Vector3( t[0], t[1], t[2] ); + } + for ( ; i < n; i++ ) { + FloatVector4 t( srcT[i][0], srcT[i][1], srcT[i][2], 0.0f ); + FloatVector4 normal( 0.0f, 0.0f, 1.0f, 0.0f ); + if ( i < normals.size() ) + normal = FloatVector4( srcN[i][0], srcN[i][1], srcN[i][2], 0.0f ); + if ( i < bitangentsBasis.size() ) + t *= srcB[i]; + t = t.crossProduct3( normal ); + dstB[i] = Vector3( t[0], t[1], t[2] ); + } } diff --git a/src/io/MeshFile.h b/src/io/MeshFile.h index c7eb3c82..e23857b2 100644 --- a/src/io/MeshFile.h +++ b/src/io/MeshFile.h @@ -1,10 +1,9 @@ -#pragma once +#ifndef MESHFILE_H_INCLUDED +#define MESHFILE_H_INCLUDED #include "data/niftypes.h" #include "gl/gltools.h" -#include -#include #include class NifModel; @@ -12,11 +11,19 @@ class NifModel; class MeshFile { - public: - MeshFile( const QString & path, const NifModel * nif ); + MeshFile( const void * data, size_t size ); + MeshFile( const NifModel * nif, const QString & path ); + // construct from BSMesh structure index, can load .mesh file or internal geometry data + MeshFile( const NifModel * nif, const QModelIndex & index ); + + void clear(); + + void update( const void * data, size_t size ); + void update( const NifModel * nif, const QString & path ); + void update( const NifModel * nif, const QModelIndex & index ); - bool isValid(); + void calculateBitangents( QVector & bitangents ) const; //! Vertices QVector positions; @@ -26,23 +33,26 @@ class MeshFile QVector colors; //! Tangents QVector tangents; - //! Tangents with bitangent basis (1.0, -1.0) - QVector tangentsBasis; - //! Bitangents - QVector bitangents; + //! Bitangents basis (bitangents[i] = cross(tangents[i] * bitangentsBasis[i], normals[i])) + QVector bitangentsBasis; //! UV coordinate sets bool haveTexCoord2 = false; QVector coords; //! Weights QVector weights; - quint8 weightsPerVertex; + quint8 weightsPerVertex = 0; //! Triangles QVector triangles; //! Skeletal Mesh LOD QVector> lods; private: - QByteArray data; - QDataStream in; - quint32 readMesh(); + bool haveData = false; +public: + inline bool isValid() const + { + return haveData; + } }; + +#endif diff --git a/src/lib/importex/gltf.cpp b/src/lib/importex/gltf.cpp index 92fe1319..f07c9fd9 100644 --- a/src/lib/importex/gltf.cpp +++ b/src/lib/importex/gltf.cpp @@ -7,6 +7,7 @@ #include "io/MeshFile.h" #include "model/nifmodel.h" #include "message.h" +#include "libfo76utils/src/filebuf.hpp" #define TINYGLTF_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION @@ -285,6 +286,32 @@ bool exportCreateNodes(const NifModel* nif, const Scene* scene, tinygltf::Model& return true; } +static inline void exportFloats( QByteArray & bin, const float * data, size_t n ) +{ + char buf[16]; + qsizetype nBytes = 0; + + switch ( n ) { + case 4: + FileBuffer::writeUInt32Fast( &(buf[12]), std::bit_cast< std::uint32_t >(data[3]) ); + nBytes += 4; + [[fallthrough]]; + case 3: + FileBuffer::writeUInt32Fast( &(buf[8]), std::bit_cast< std::uint32_t >(data[2]) ); + nBytes += 4; + [[fallthrough]]; + case 2: + FileBuffer::writeUInt32Fast( &(buf[4]), std::bit_cast< std::uint32_t >(data[1]) ); + nBytes += 4; + [[fallthrough]]; + case 1: + FileBuffer::writeUInt32Fast( &(buf[0]), std::bit_cast< std::uint32_t >(data[0]) ); + nBytes += 4; + break; + } + + bin.append( buf, nBytes ); +} void exportCreatePrimitive(tinygltf::Model& model, QByteArray& bin, std::shared_ptr mesh, tinygltf::Primitive& prim, std::string attr, int count, int componentType, int type, quint32& attributeIndex, GltfStore& gltf) @@ -363,39 +390,29 @@ void exportCreatePrimitive(tinygltf::Model& model, QByteArray& bin, std::shared_ // So, do this for now. if ( attr == "POSITION" ) { for ( const auto& v : mesh->positions ) { - bin.append(reinterpret_cast(&v[0]), sizeof(v[0])); - bin.append(reinterpret_cast(&v[1]), sizeof(v[1])); - bin.append(reinterpret_cast(&v[2]), sizeof(v[2])); + exportFloats( bin, &(v[0]), 3 ); } } else if ( attr == "NORMAL" ) { for ( const auto& v : mesh->normals ) { - bin.append(reinterpret_cast(&v[0]), sizeof(v[0])); - bin.append(reinterpret_cast(&v[1]), sizeof(v[1])); - bin.append(reinterpret_cast(&v[2]), sizeof(v[2])); + exportFloats( bin, &(v[0]), 3 ); } } else if ( attr == "TANGENT" ) { - for ( const auto& v : mesh->tangentsBasis ) { - bin.append(reinterpret_cast(&v[0]), sizeof(v[0])); - bin.append(reinterpret_cast(&v[1]), sizeof(v[1])); - bin.append(reinterpret_cast(&v[2]), sizeof(v[2])); - bin.append(reinterpret_cast(&v[3]), sizeof(v[3])); + for ( const auto& v : mesh->tangents ) { + Vector4 tmp( v ); + tmp[3] = mesh->bitangentsBasis.at( qsizetype(&v - mesh->tangents.data()) ) * -1.0f; + exportFloats( bin, &(tmp[0]), 4 ); } } else if ( attr == "TEXCOORD_0" ) { for ( const auto& v : mesh->coords ) { - bin.append(reinterpret_cast(&v[0]), sizeof(v[0])); - bin.append(reinterpret_cast(&v[1]), sizeof(v[1])); + exportFloats( bin, &(v[0]), 2 ); } } else if ( attr == "TEXCOORD_1" && mesh->haveTexCoord2 ) { for ( const auto& v : mesh->coords ) { - bin.append(reinterpret_cast(&v[2]), sizeof(v[2])); - bin.append(reinterpret_cast(&v[3]), sizeof(v[3])); + exportFloats( bin, &(v[2]), 2 ); } } else if ( attr == "COLOR_0" ) { for ( const auto& v : mesh->colors ) { - bin.append(reinterpret_cast(&v[0]), sizeof(v[0])); - bin.append(reinterpret_cast(&v[1]), sizeof(v[1])); - bin.append(reinterpret_cast(&v[2]), sizeof(v[2])); - bin.append(reinterpret_cast(&v[3]), sizeof(v[3])); + exportFloats( bin, &(v[0]), 4 ); } } else if ( attr == "WEIGHTS_0" ) { for ( const auto& v : mesh->weights ) { @@ -450,7 +467,7 @@ bool exportCreatePrimitives(tinygltf::Model& model, QByteArray& bin, const BSMes exportCreatePrimitive(model, bin, mesh, prim, "POSITION", mesh->positions.size(), TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC3, attributeIndex, gltf); exportCreatePrimitive(model, bin, mesh, prim, "NORMAL", mesh->normals.size(), TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC3, attributeIndex, gltf); - exportCreatePrimitive(model, bin, mesh, prim, "TANGENT", mesh->tangentsBasis.size(), TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC4, attributeIndex, gltf); + exportCreatePrimitive(model, bin, mesh, prim, "TANGENT", mesh->tangents.size(), TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC4, attributeIndex, gltf); if ( mesh->coords.size() > 0 ) { exportCreatePrimitive(model, bin, mesh, prim, "TEXCOORD_0", mesh->coords.size(), TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC2, attributeIndex, gltf); } diff --git a/src/spells/mesh.cpp b/src/spells/mesh.cpp index e2bf30f5..6d64a7a7 100644 --- a/src/spells/mesh.cpp +++ b/src/spells/mesh.cpp @@ -883,10 +883,7 @@ QModelIndex spUpdateBounds::cast_Starfield( NifModel * nif, const QModelIndex & mesh = nif->getIndex( mesh, "Mesh" ); if ( !mesh.isValid() ) continue; - QString meshPath( nif->get( mesh, "Mesh Path" ) ); - if ( meshPath.isEmpty() ) - continue; - MeshFile meshFile( meshPath, nif ); + MeshFile meshFile( nif, mesh ); quint32 indicesSize = 0; quint32 numVerts = 0; if ( meshFile.isValid() ) { @@ -901,6 +898,7 @@ QModelIndex spUpdateBounds::cast_Starfield( NifModel * nif, const QModelIndex & bounds = BoundSphere( meshFile.positions, true ); calculateBoundingBox( bndCenter, bndDims, meshFile.positions ); boundsCalculated = true; + // TODO: update bounds in internal geometry data } } diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index 3c07dd3c..183c6aab 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -974,6 +974,7 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) if ( !meshes.isValid() ) return false; + QModelIndex sfMeshIndex; int lodDiff = 255; for ( int i = 0; i <= 3; i++ ) { auto mesh = QModelIndex_child( meshes, i ); @@ -985,17 +986,16 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) mesh = nif->getIndex( mesh, "Mesh" ); if ( !mesh.isValid() ) continue; - QString meshPath( nif->findResourceFile( nif->get( mesh, "Mesh Path" ), "geometries/", ".mesh" ) ); - if ( meshPath.isEmpty() ) - continue; if ( std::abs( i - sfMeshLOD ) < lodDiff ) { lodDiff = std::abs( i - sfMeshLOD ); - sfMeshPath = meshPath; + if ( nif->getIndex( mesh, "Mesh Path" ).isValid() ) + sfMeshPath = nif->findResourceFile( nif->get( mesh, "Mesh Path" ), "geometries/", ".mesh" ); + sfMeshIndex = mesh; } } - if ( sfMeshPath.isEmpty() ) + if ( !sfMeshIndex.isValid() ) return false; - MeshFile meshFile( sfMeshPath, nif ); + MeshFile meshFile( nif, sfMeshIndex ); if ( !( meshFile.isValid() && meshFile.coords.size() > 0 && meshFile.triangles.size() > 0 ) ) return false; for ( qsizetype i = 0; i < meshFile.coords.size(); i++ )