| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include "TangentSpaceHelper.h"
- #include <AzCore/std/containers/array.h>
- #include <CryCommon/Cry_Math.h>
- namespace WhiteBox
- {
- void AZTangentSpaceCalculation::Calculate(
- const AZStd::vector<AZ::Vector3>& vertices, const AZStd::vector<uint32_t>& indices,
- const AZStd::vector<AZ::Vector2>& uvs)
- {
- AZ_Error(
- "AZTangentSpaceCalculation", (indices.size() % 3) == 0,
- "Size of list of indices (%d) is not a multiple of 3.", indices.size());
- const size_t triangleCount = indices.size() / 3;
- const size_t vertexCount = vertices.size();
- // Reset results with the right number of elements
- m_baseVectors = AZStd::vector<Base33>(vertexCount);
- using TriangleIndices = AZStd::array<uint32_t, 3>;
- using TrianglePositions = AZStd::array<AZ::Vector3, 3>;
- using TriangleUVs = AZStd::array<AZ::Vector3, 3>;
- using TriangleEdges = AZStd::array<AZ::Vector3, 2>;
- AZStd::vector<TriangleIndices> trianglesIndices;
- AZStd::vector<TrianglePositions> trianglesPositions;
- AZStd::vector<TriangleUVs> trianglesUVs;
- AZStd::vector<TriangleEdges> trianglesEdges;
- trianglesIndices.reserve(triangleCount);
- trianglesPositions.reserve(triangleCount);
- trianglesUVs.reserve(triangleCount);
- trianglesEdges.reserve(triangleCount);
- // Precalculate triangles' indices, positions, UVs and edges.
- for (AZ::u32 i = 0; i < triangleCount; ++i)
- {
- const TriangleIndices triangleIndices = {{indices[i * 3 + 0], indices[i * 3 + 1], indices[i * 3 + 2]}};
- const TrianglePositions trianglePositions = {
- {(vertices[triangleIndices[0]]), (vertices[triangleIndices[1]]), (vertices[triangleIndices[2]])}};
- const TriangleUVs triangleUVs = {
- {AZ::Vector3(uvs[triangleIndices[0]].GetX(), uvs[triangleIndices[0]].GetY(), 0.0f),
- AZ::Vector3(uvs[triangleIndices[1]].GetX(), uvs[triangleIndices[1]].GetY(), 0.0f),
- AZ::Vector3(uvs[triangleIndices[2]].GetX(), uvs[triangleIndices[2]].GetY(), 0.0f)}};
- const TriangleEdges triangleEdges = {
- {trianglePositions[1] - trianglePositions[0], trianglePositions[2] - trianglePositions[0]}};
- trianglesIndices.push_back(AZStd::move(triangleIndices));
- trianglesPositions.push_back(AZStd::move(trianglePositions));
- trianglesUVs.push_back(AZStd::move(triangleUVs));
- trianglesEdges.push_back(AZStd::move(triangleEdges));
- }
- // base vectors per triangle
- AZStd::vector<Base33> triangleBases;
- triangleBases.reserve(triangleCount);
- // calculate the base vectors per triangle
- {
- const float identityInfluence = 0.01f;
- const Base33 identityBase(
- AZ::Vector3(identityInfluence, 0.0f, 0.0f), AZ::Vector3(0.0f, identityInfluence, 0.0f),
- AZ::Vector3(0.0f, 0.0f, identityInfluence));
- for (AZ::u32 i = 0; i < triangleCount; ++i)
- {
- #if defined(AZ_ENABLE_TRACING)
- const auto& trianglePositions = trianglesPositions[i];
- #endif
- const auto& triangleUVs = trianglesUVs[i];
- const auto& triangleEdges = trianglesEdges[i];
- // calculate tangent vectors
- AZ::Vector3 normal = triangleEdges[0].Cross(triangleEdges[1]);
- // Avoid situations where the edges are parallel resulting in an invalid normal.
- // This can happen if the simulation moves particles of triangle to the same spot or very far away.
- if (normal.IsZero(0.0001f))
- {
- // Use the identity base with low influence to leave other valid triangles to
- // affect these vertices. In case no other triangle affects the vertices the base
- // will still be valid with identity values as it gets normalized later.
- triangleBases.push_back(identityBase);
- continue;
- }
- normal.Normalize();
- const float deltaU1 = triangleUVs[1].GetX() - triangleUVs[0].GetX();
- const float deltaU2 = triangleUVs[2].GetX() - triangleUVs[0].GetX();
- const float deltaV1 = triangleUVs[1].GetY() - triangleUVs[0].GetY();
- const float deltaV2 = triangleUVs[2].GetY() - triangleUVs[0].GetY();
- const float div = (deltaU1 * deltaV2 - deltaU2 * deltaV1);
- if (_isnan(div))
- {
- AZ_Error(
- "AZTangentSpaceCalculation", false,
- "Vertices 0,1,2 have broken texture coordinates v0:(%f : %f : %f) v1:(%f : %f : %f) v2:(%f : "
- "%f : %f)",
- float(trianglePositions[0].GetX()), float(trianglePositions[0].GetY()),
- float(trianglePositions[0].GetZ()), float(trianglePositions[1].GetX()),
- float(trianglePositions[1].GetY()), float(trianglePositions[1].GetZ()),
- float(trianglePositions[2].GetX()), float(trianglePositions[2].GetY()),
- float(trianglePositions[2].GetZ()));
- return;
- }
- AZ::Vector3 tangent, bitangent;
- if (div != 0.0f)
- {
- // 2D triangle area = (u1*v2-u2*v1)/2
- const float a = deltaV2; // /div was removed - no required because of normalize()
- const float b = -deltaV1;
- const float c = -deltaU2;
- const float d = deltaU1;
- // /fAreaMul2*fAreaMul2 was optimized away -> small triangles in UV should contribute less and
- // less artifacts (no divide and multiply)
- tangent = (triangleEdges[0] * a + triangleEdges[1] * b) * fsgnf(div);
- bitangent = (triangleEdges[0] * c + triangleEdges[1] * d) * fsgnf(div);
- }
- else
- {
- tangent = AZ::Vector3(1.0f, 0.0f, 0.0f);
- bitangent = AZ::Vector3(0.0f, 1.0f, 0.0f);
- }
- triangleBases.push_back(Base33(tangent, bitangent, normal));
- }
- }
- // distribute the normals and uv vectors to the vertices
- {
- // we create a new tangent base for every vertex index that has a different normal (later we split further
- // for mirrored use) and sum the base vectors (weighted by angle and mirrored if necessary)
- for (AZ::u32 i = 0; i < triangleCount; ++i)
- {
- const auto& triangleIndices = trianglesIndices[i];
- const auto& trianglePositions = trianglesPositions[i];
- Base33& triBase = triangleBases[i];
- // for each triangle vertex
- for (AZ::u32 e = 0; e < 3; ++e)
- {
- // weight by angle to fix the L-Shape problem
- const float weight = CalcAngleBetween(
- trianglePositions[(e + 2) % 3] - trianglePositions[e],
- trianglePositions[(e + 1) % 3] - trianglePositions[e]);
- triBase.m_normal *= AZStd::max(weight, 0.0001f);
- triBase.m_tangent *= weight;
- triBase.m_bitangent *= weight;
- AddNormalToBase(triangleIndices[e], triBase.m_normal);
- AddUVToBase(triangleIndices[e], triBase.m_tangent, triBase.m_bitangent);
- }
- }
- }
- // adjust the base vectors per vertex
- {
- for (auto& ref : m_baseVectors)
- {
- // rotate u and v in n plane
- AZ::Vector3 nOut = ref.m_normal;
- nOut.Normalize();
- // project u in n plane
- // project v in n plane
- AZ::Vector3 uOut = ref.m_tangent - nOut * (nOut.Dot(ref.m_tangent));
- AZ::Vector3 vOut = ref.m_bitangent - nOut * (nOut.Dot(ref.m_bitangent));
- ref.m_tangent = uOut;
- ref.m_tangent.Normalize();
- ref.m_bitangent = vOut;
- ref.m_bitangent.Normalize();
- ref.m_normal = nOut;
- }
- }
- AZ_Error(
- "AZTangentSpaceCalculation", GetBaseCount() == vertices.size(),
- "Number of tangent spaces (%d) doesn't match with the number of input vertices (%d).", GetBaseCount(),
- vertices.size());
- }
- size_t AZTangentSpaceCalculation::GetBaseCount() const
- {
- return m_baseVectors.size();
- }
- void AZTangentSpaceCalculation::GetBase(
- AZ::u32 index, AZ::Vector3& tangent, AZ::Vector3& bitangent, AZ::Vector3& normal) const
- {
- tangent = GetTangent(index);
- bitangent = GetBitangent(index);
- normal = GetNormal(index);
- }
- AZ::Vector3 AZTangentSpaceCalculation::GetTangent(AZ::u32 index) const
- {
- return m_baseVectors[index].m_tangent;
- }
- AZ::Vector3 AZTangentSpaceCalculation::GetBitangent(AZ::u32 index) const
- {
- return m_baseVectors[index].m_bitangent;
- }
- AZ::Vector3 AZTangentSpaceCalculation::GetNormal(AZ::u32 index) const
- {
- return m_baseVectors[index].m_normal;
- }
- void AZTangentSpaceCalculation::AddNormalToBase(AZ::u32 index, const AZ::Vector3& normal)
- {
- m_baseVectors[index].m_normal += normal;
- }
- void AZTangentSpaceCalculation::AddUVToBase(AZ::u32 index, const AZ::Vector3& u, const AZ::Vector3& v)
- {
- m_baseVectors[index].m_tangent += u;
- m_baseVectors[index].m_bitangent += v;
- }
- float AZTangentSpaceCalculation::CalcAngleBetween(const AZ::Vector3& a, const AZ::Vector3& b)
- {
- double lengthQ = sqrt(a.GetLengthSq() * b.GetLengthSq());
- // to prevent division by zero
- lengthQ = AZStd::max(lengthQ, 1e-8);
- double cosAngle = a.Dot(b) / lengthQ;
- // acosf is not available on every platform. acos_tpl clamps cosAngle to [-1,1].
- return static_cast<float>(acos_tpl(cosAngle));
- }
- AZTangentSpaceCalculation::Base33::Base33(
- const AZ::Vector3& tangent, const AZ::Vector3& bitangent, const AZ::Vector3& normal)
- : m_tangent(tangent)
- , m_bitangent(bitangent)
- , m_normal(normal)
- {
- }
- } // namespace WhiteBox
|