From 2d190ea30b203782e8dd322c70ba9bfb38ea03a8 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Tue, 19 Sep 2023 15:20:40 +0800 Subject: [PATCH] finish BuildNormals --- CodeGen/VxVectors.py | 32 +++++++++++- LibCmo/CK2/ObjImpls/CKMesh.cpp | 43 +++++++++++++++- LibCmo/VxMath/VxMath.cpp | 27 ++++++++++ LibCmo/VxMath/VxMath.hpp | 13 +++++ LibCmo/VxMath/VxTypes.hpp | 92 ++++++++++++++++++++++++++++++++-- Tools/README.md | 17 +++++++ Unvirt/StructFormatter.cpp | 16 +++--- 7 files changed, 225 insertions(+), 15 deletions(-) diff --git a/CodeGen/VxVectors.py b/CodeGen/VxVectors.py index 33d782a..3bf0c5d 100644 --- a/CodeGen/VxVectors.py +++ b/CodeGen/VxVectors.py @@ -35,6 +35,11 @@ def GetTmplOperOffset(sname: str, svars: tuple[str]) -> str: \t\t\t{sp.join(map(lambda x: f'case {x}: return {svars[x]};', range(len(svars))))} \t\t\tdefault: return {svars[0]}; \t\t}} +\t}}\tconst CKFLOAT& operator[](size_t i) const {{ +\t\tswitch (i) {{ +\t\t\t{sp.join(map(lambda x: f'case {x}: return {svars[x]};', range(len(svars))))} +\t\t\tdefault: return {svars[0]}; +\t\t}} \t}}""" def GetTmplOperAddMinus(sname: str, svars: tuple[str], oper: str) -> str: @@ -58,6 +63,9 @@ def GetTmplOperMul(sname: str, svars: tuple[str]) -> str: \t}} \tfriend {sname} operator*(CKFLOAT lhs, const {sname}& rhs) {{ \t\treturn {sname}({', '.join(map(lambda x: f'lhs * rhs.{x}', svars))}); +\t}} +\tfriend CKFLOAT operator*(const {sname}& lhs, const {sname}& rhs) {{ +\t\treturn ({' + '.join(map(lambda x: f'lhs.{x} * rhs.{x}', svars))}); \t}}""" def GetTmplOperDiv(sname: str, svars: tuple[str]) -> str: @@ -73,7 +81,6 @@ def GetTmplOperDiv(sname: str, svars: tuple[str]) -> str: \t}}""" def GetTmplOperEqual(sname: str, svars: tuple[str]) -> str: - sp: str = '\n\t\t' return f"""\tbool operator==(const {sname}& rhs) const {{ \t\treturn ({' && '.join(map(lambda x: f'{x} == rhs.{x}', svars))}); \t}} @@ -81,6 +88,27 @@ def GetTmplOperEqual(sname: str, svars: tuple[str]) -> str: \t\treturn !(*this == rhs); \t}}""" +def GetTmplLength(sname: str, svars: tuple[str]) -> str: + return f"""\tCKFLOAT SquaredLength() const {{ +\t\treturn ({' + '.join(map(lambda x: f'{x} * {x}', svars))}); +\t}} +\tCKFLOAT Length() const {{ +\t\treturn std::sqrt(SquaredLength()); +\t}}""" + +def GetTmplNormalize(sname: str, svars: tuple[str]) -> str: + sp: str = '\n\t\t' + return f"""\tvoid Normalized() {{ +\t\tCKFLOAT len = Length(); +\t\tif (len == 0.0f) return; +\t\t{sp.join(map(lambda x: f'{x} /= len;', svars))} +\t}} +\t{sname} Normalize() const {{ +\t\tCKFLOAT len = Length(); +\t\tif (len == 0.0f) return {sname}(); +\t\treturn {sname}({', '.join(map(lambda x: f'{x} / len', svars))}); +\t}}""" + def GetTmplVector(sname: str, svars: tuple[str]) -> str: return f""" struct {sname} {{ @@ -94,6 +122,8 @@ struct {sname} {{ {GetTmplOperMul(sname, svars)} {GetTmplOperDiv(sname, svars)} {GetTmplOperEqual(sname, svars)} +{GetTmplLength(sname, svars)} +{GetTmplNormalize(sname, svars)} }}; """ diff --git a/LibCmo/CK2/ObjImpls/CKMesh.cpp b/LibCmo/CK2/ObjImpls/CKMesh.cpp index 231f8db..64b4db5 100644 --- a/LibCmo/CK2/ObjImpls/CKMesh.cpp +++ b/LibCmo/CK2/ObjImpls/CKMesh.cpp @@ -226,6 +226,8 @@ namespace LibCmo::CK2::ObjImpls { return true; } +#pragma region Misc Section + void CKMesh::CleanMesh() { // clear material channel first SetMtlChannelCount(0); @@ -236,9 +238,46 @@ namespace LibCmo::CK2::ObjImpls { SetLineCount(0); } - void CKMesh::BuildNormals() {} + void CKMesh::BuildNormals() { + if (m_FaceCount == 0 || m_VertexCount == 0) return; - void CKMesh::BuildFaceNormals() {} + // build face normal first + BuildFaceNormals(); + + // iterate all face and add face normal to each point's normal + for (CKDWORD fid = 0; fid < m_FaceCount; ++fid) { + m_VertexNormal[m_FaceIndices[fid * 3]] += m_Faces[fid].m_Normal; + m_VertexNormal[m_FaceIndices[fid * 3 + 1]] += m_Faces[fid].m_Normal; + m_VertexNormal[m_FaceIndices[fid * 3 + 2]] += m_Faces[fid].m_Normal; + } + + // then normalize all vertex normal + for (auto& nml : m_VertexNormal) { + nml.Normalized(); + } + } + + void CKMesh::BuildFaceNormals() { + if (m_FaceCount == 0 || m_VertexCount == 0) return; + + // iertate all face to build face normal according to position data + for (CKDWORD fid = 0; fid < m_FaceCount; ++fid) { + VxMath::VxVector3 *p0 = &m_VertexPosition[m_FaceIndices[fid * 3]]; + + VxMath::VxVector3 p0_p1 = m_VertexPosition[m_FaceIndices[fid * 3 + 1]] - *p0, + p0_p2 = m_VertexPosition[m_FaceIndices[fid * 3 + 2]] - *p0; + + // cross product to get normal + // and normalize it + VxMath::VxVector3 nml = VxMath::NSVxVector::CrossProduct(p0_p1, p0_p2); + nml.Normalized(); + + // assign it + m_Faces[fid].m_Normal = nml; + } + } + +#pragma endregion #pragma region Vertex Section diff --git a/LibCmo/VxMath/VxMath.cpp b/LibCmo/VxMath/VxMath.cpp index d208765..f1b45aa 100644 --- a/LibCmo/VxMath/VxMath.cpp +++ b/LibCmo/VxMath/VxMath.cpp @@ -124,6 +124,33 @@ namespace LibCmo::VxMath { #pragma endregion +#pragma region Patched + + namespace NSVxVector { + + float LibCmo::VxMath::NSVxVector::DotProduct(const VxVector2& lhs, const VxVector2& rhs) { + return lhs * rhs; + } + + float LibCmo::VxMath::NSVxVector::DotProduct(const VxVector3& lhs, const VxVector3& rhs) { + return lhs * rhs; + } + + float LibCmo::VxMath::NSVxVector::DotProduct(const VxVector4& lhs, const VxVector4& rhs) { + return lhs * rhs; + } + + VxVector3 CrossProduct(const VxVector3& lhs, const VxVector3& rhs) { + return VxVector3( + lhs.y * rhs.z - lhs.z * rhs.y, + lhs.z * rhs.x - lhs.x * rhs.z, + lhs.x * rhs.y - lhs.y * rhs.x + ); + } + + } + +#pragma endregion } diff --git a/LibCmo/VxMath/VxMath.hpp b/LibCmo/VxMath/VxMath.hpp index 5eeb2e5..573b31e 100644 --- a/LibCmo/VxMath/VxMath.hpp +++ b/LibCmo/VxMath/VxMath.hpp @@ -83,5 +83,18 @@ namespace LibCmo::VxMath { */ void VxDoAlphaBlit(VxImageDescEx* dst_desc, const CKBYTE* AlphaValues); + + // ========== Patch Section ========== + + namespace NSVxVector { + + CKFLOAT DotProduct(const VxVector2& lhs, const VxVector2& rhs); + CKFLOAT DotProduct(const VxVector3& lhs, const VxVector3& rhs); + CKFLOAT DotProduct(const VxVector4& lhs, const VxVector4& rhs); + + VxVector3 CrossProduct(const VxVector3& lhs, const VxVector3& rhs); + + } + } diff --git a/LibCmo/VxMath/VxTypes.hpp b/LibCmo/VxMath/VxTypes.hpp index 6ce40a6..ca17276 100644 --- a/LibCmo/VxMath/VxTypes.hpp +++ b/LibCmo/VxMath/VxTypes.hpp @@ -7,6 +7,7 @@ #include #include #include +#include /** * @brief The VxMath part of LibCmo. @@ -35,6 +36,12 @@ namespace LibCmo::VxMath { case 1: return y; default: return x; } + } const CKFLOAT& operator[](size_t i) const { + switch (i) { + case 0: return x; + case 1: return y; + default: return x; + } } VxVector2& operator+=(const VxVector2& rhs) { x += rhs.x; @@ -63,6 +70,9 @@ namespace LibCmo::VxMath { friend VxVector2 operator*(CKFLOAT lhs, const VxVector2& rhs) { return VxVector2(lhs * rhs.x, lhs * rhs.y); } + friend CKFLOAT operator*(const VxVector2& lhs, const VxVector2& rhs) { + return (lhs.x * rhs.x + lhs.y * rhs.y); + } VxVector2& operator/=(CKFLOAT rhs) { if (rhs == 0.0f) return *this; x /= rhs; @@ -79,6 +89,23 @@ namespace LibCmo::VxMath { bool operator!=(const VxVector2& rhs) const { return !(*this == rhs); } + CKFLOAT SquaredLength() const { + return (x * x + y * y); + } + CKFLOAT Length() const { + return std::sqrt(SquaredLength()); + } + void Normalized() { + CKFLOAT len = Length(); + if (len == 0.0f) return; + x /= len; + y /= len; + } + VxVector2 Normalize() const { + CKFLOAT len = Length(); + if (len == 0.0f) return VxVector2(); + return VxVector2(x / len, y / len); + } }; struct VxVector3 { @@ -93,6 +120,13 @@ namespace LibCmo::VxMath { case 2: return z; default: return x; } + } const CKFLOAT& operator[](size_t i) const { + switch (i) { + case 0: return x; + case 1: return y; + case 2: return z; + default: return x; + } } VxVector3& operator+=(const VxVector3& rhs) { x += rhs.x; @@ -124,6 +158,9 @@ namespace LibCmo::VxMath { friend VxVector3 operator*(CKFLOAT lhs, const VxVector3& rhs) { return VxVector3(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z); } + friend CKFLOAT operator*(const VxVector3& lhs, const VxVector3& rhs) { + return (lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z); + } VxVector3& operator/=(CKFLOAT rhs) { if (rhs == 0.0f) return *this; x /= rhs; @@ -141,6 +178,24 @@ namespace LibCmo::VxMath { bool operator!=(const VxVector3& rhs) const { return !(*this == rhs); } + CKFLOAT SquaredLength() const { + return (x * x + y * y + z * z); + } + CKFLOAT Length() const { + return std::sqrt(SquaredLength()); + } + void Normalized() { + CKFLOAT len = Length(); + if (len == 0.0f) return; + x /= len; + y /= len; + z /= len; + } + VxVector3 Normalize() const { + CKFLOAT len = Length(); + if (len == 0.0f) return VxVector3(); + return VxVector3(x / len, y / len, z / len); + } }; struct VxVector4 { @@ -156,6 +211,14 @@ namespace LibCmo::VxMath { case 3: return w; default: return x; } + } const CKFLOAT& operator[](size_t i) const { + switch (i) { + case 0: return x; + case 1: return y; + case 2: return z; + case 3: return w; + default: return x; + } } VxVector4& operator+=(const VxVector4& rhs) { x += rhs.x; @@ -190,6 +253,9 @@ namespace LibCmo::VxMath { friend VxVector4 operator*(CKFLOAT lhs, const VxVector4& rhs) { return VxVector4(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z, lhs * rhs.w); } + friend CKFLOAT operator*(const VxVector4& lhs, const VxVector4& rhs) { + return (lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z + lhs.w * rhs.w); + } VxVector4& operator/=(CKFLOAT rhs) { if (rhs == 0.0f) return *this; x /= rhs; @@ -208,6 +274,25 @@ namespace LibCmo::VxMath { bool operator!=(const VxVector4& rhs) const { return !(*this == rhs); } + CKFLOAT SquaredLength() const { + return (x * x + y * y + z * z + w * w); + } + CKFLOAT Length() const { + return std::sqrt(SquaredLength()); + } + void Normalized() { + CKFLOAT len = Length(); + if (len == 0.0f) return; + x /= len; + y /= len; + z /= len; + w /= len; + } + VxVector4 Normalize() const { + CKFLOAT len = Length(); + if (len == 0.0f) return VxVector4(); + return VxVector4(x / len, y / len, z / len, w / len); + } }; struct VxQuaternion { @@ -259,7 +344,7 @@ namespace LibCmo::VxMath { if (r > 1.0f) r = 1.0f; else if (r < 0.0f) r = 0.0f; if (g > 1.0f) g = 1.0f; - else if (g < 0.0f) g= 0.0f; + else if (g < 0.0f) g = 0.0f; if (b > 1.0f) b = 1.0f; else if (b < 0.0f) b = 0.0f; if (a > 1.0f) a = 1.0f; @@ -309,8 +394,7 @@ namespace LibCmo::VxMath { public: VxStridedData(_Ty ptr, CKDWORD stride) : m_Ptr(reinterpret_cast(m_Ptr)), - m_Stride(stride) - {} + m_Stride(stride) {} ~VxStridedData() {} _Ty operator[](size_t idx) { @@ -433,7 +517,7 @@ namespace LibCmo::VxMath { m_Width != 0u && m_Height != 0u && m_Image != nullptr - ); + ); } bool IsHWEqual(const VxImageDescEx& rhs) const { return (m_Width == rhs.m_Width && m_Height == rhs.m_Height); diff --git a/Tools/README.md b/Tools/README.md index 6415d1a..f19cb3a 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -1,3 +1,20 @@ # Tools +The developer need to know the loaded data whether is correct when testing LibCmo. So we create this folder and you can use Unvirt and the tools located in this folder to test the correction of loaded data. +Unvirt can show the data of each CKObject, such as Texture, Mesh and etc. For example, Unvirt can provide vertex's position, normal, UV, even the face's indices data for CKMesh. You can use tools to broswer memory to get them, but you couldn't evaluate them how they shape a mesh. This is the reason why this folder existed and in this README I will tell you how to debug the loaded data. + +I suggest you to use HxD to broswer memory, but if you have other softwares, use it freely. + +## CKTexture + +* Install [PixelViewer](https://github.com/carina-studio/PixelViewer) first. +* Change profile to `BGRA_8888` (actually is little-endian RGBA8888, but I think the developer of PixelViewer get confused). +* The image resolution can be gotten from Uvirt. Set it in PixelViewer. +* The image address also can be gotten from Unvirt. Save the memory image data to local file and open it by PixelViewer. + +## CKMesh + +* Have a executable Python. +* Save VertexPosition, VertexNormal, VertexUV, FaceIndices data into file according to the given memory address by Unvirt. +* Call `MeshConv.py`, set the argument properly, then you will get a converted Wavefront OBJ file. diff --git a/Unvirt/StructFormatter.cpp b/Unvirt/StructFormatter.cpp index 212e44b..45b8966 100644 --- a/Unvirt/StructFormatter.cpp +++ b/Unvirt/StructFormatter.cpp @@ -123,32 +123,32 @@ namespace Unvirt::StructFormatter { fputs("VertexPositions: ", stdout); PrintPointer(obj->GetVertexPositions()); - fputc('\n', stdout); + fprintf(stdout, " (0x%" PRIxCKDWORD " bytes)\n", obj->GetVertexCount() * CKSizeof(LibCmo::VxMath::VxVector3)); fputs("VertexNormals: ", stdout); PrintPointer(obj->GetVertexNormals()); - fputc('\n', stdout); + fprintf(stdout, " (0x%" PRIxCKDWORD " bytes)\n", obj->GetVertexCount() * CKSizeof(LibCmo::VxMath::VxVector3)); fputs("VertexUVs: ", stdout); PrintPointer(obj->GetVertexUVs()); - fputc('\n', stdout); + fprintf(stdout, " (0x%" PRIxCKDWORD " bytes)\n", obj->GetVertexCount() * CKSizeof(LibCmo::VxMath::VxVector2)); fputs("VertexColors: ", stdout); PrintPointer(obj->GetVertexColors()); - fputc('\n', stdout); + fprintf(stdout, " (0x%" PRIxCKDWORD " bytes)\n", obj->GetVertexCount() * CKSizeof(LibCmo::CKDWORD)); fputs("VertexSpecularColors: ", stdout); PrintPointer(obj->GetVertexSpecularColors()); - fputc('\n', stdout); + fprintf(stdout, " (0x%" PRIxCKDWORD " bytes)\n", obj->GetVertexCount() * CKSizeof(LibCmo::CKDWORD)); fputs("VertexWeights: ", stdout); PrintPointer(obj->GetVertexWeights()); - fputc('\n', stdout); + fprintf(stdout, " (0x%" PRIxCKDWORD " bytes)\n", obj->GetVertexCount() * CKSizeof(LibCmo::CKFLOAT)); fputs("Face:\n", stdout); fprintf(stdout, "Face Count: %" PRIuCKDWORD "\n", obj->GetFaceCount()); fputs("FaceIndices: ", stdout); PrintPointer(obj->GetFaceIndices()); - fputc('\n', stdout); + fprintf(stdout, " (0x%" PRIxCKDWORD " bytes)\n", obj->GetFaceCount() * 3 * CKSizeof(LibCmo::CKWORD)); fputs("FaceMaterialSlotIndexs: ", stdout); PrintPointer(obj->GetFaceMaterialSlotIndexs()); - fputc('\n', stdout); + fprintf(stdout, " (0x%" PRIxCKDWORD " bytes)\n", obj->GetFaceCount() * CKSizeof(LibCmo::CKWORD)); }