ezResult Mesh::ComputeNormals() { ezStopwatch timer; const VertexDataStream* positionStreamRaw = GetDataStream(ezGALVertexAttributeSemantic::Position); if (positionStreamRaw == nullptr) { ezLog::Error("Can't compute vertex normals for the mesh '{0}', because it doesn't have vertex positions.", m_Name); return EZ_FAILURE; } const TypedVertexDataStreamView<ezVec3> positionStream(*positionStreamRaw); VertexDataStream* normalStreamRaw = AddDataStream(ezGALVertexAttributeSemantic::Normal, 3); TypedVertexDataStreamView_ReadWrite<ezVec3> normalStream(*normalStreamRaw); // Normals have same mapping as positions. normalStream->m_IndexToData = positionStreamRaw->m_IndexToData; // Reset all normals to zero. normalStream->m_Data.SetCountUninitialized(positionStreamRaw->m_Data.GetCount()); ezMemoryUtils::ZeroFill<char>(normalStream->m_Data.GetData(), normalStream->m_Data.GetCount()); // Compute unnormalized triangle normals and add them to all vertices. // This way large triangles have an higher influence on the vertex normal. for (const Triangle& triangle : m_Triangles) { const VertexIndex v0 = triangle.m_Vertices[0]; const VertexIndex v1 = triangle.m_Vertices[1]; const VertexIndex v2 = triangle.m_Vertices[2]; const ezVec3 p0 = positionStream.GetValue(v0); const ezVec3 p1 = positionStream.GetValue(v1); const ezVec3 p2 = positionStream.GetValue(v2); const ezVec3 d01 = p1 - p0; const ezVec3 d02 = p2 - p0; const ezVec3 triNormal = d01.CrossRH(d02); normalStream.SetValue(v0, normalStream.GetValue(v0) + triNormal); // (possible optimization: have a special addValue to avoid unnecessary lookup) normalStream.SetValue(v1, normalStream.GetValue(v1) + triNormal); normalStream.SetValue(v2, normalStream.GetValue(v2) + triNormal); } // Normalize normals. for (ezUInt32 n = 0; n < normalStream->m_Data.GetCount(); n += sizeof(ezVec3)) reinterpret_cast<ezVec3*>(&normalStream->m_Data[n])->NormalizeIfNotZero(); ezLog::Debug("Computed mesh normals ('{0}') in '{1}'s", m_Name, ezArgF(timer.GetRunningTotal().GetSeconds(), 2)); return EZ_SUCCESS; }
ezSharedPtr<Scene> Importer::ImportScene(const char* szFileName, ezBitflags<ImportFlags> importFlags, bool addToCache) { ezLogBlock logBlock("Scene Import", szFileName); ezSharedPtr<Scene> scene; if (m_cachedScenes.TryGetValue(szFileName, scene)) return scene; ezString fileExtension = ezPathUtils::GetFileExtension(szFileName); if (fileExtension.IsEmpty()) { ezLog::Error("Unable to choose model importer since file '{0}' has no file extension.", szFileName); return nullptr; } // Newer implementations have higher priority. for (int i = m_ImporterImplementations.GetCount() - 1; i >= 0; --i) { auto supportedFormats = m_ImporterImplementations[i]->GetSupportedFileFormats(); if (std::any_of(cbegin(supportedFormats), cend(supportedFormats), [fileExtension](const char* ext) { return ezStringUtils::IsEqual_NoCase(ext, fileExtension); })) { ezStopwatch timer; scene = m_ImporterImplementations[i]->ImportScene(szFileName, importFlags); #if EZ_ENABLED(EZ_COMPILE_FOR_DEBUG) ValidateSceneGraph(scene); #endif if (scene) { scene->RefreshRootList(); scene->CreateUniqueNames(); } ezLog::Success("Imported scene '{0}' in {1} seconds", szFileName, ezArgF(timer.GetRunningTotal().GetSeconds(), 2)); if (addToCache) m_cachedScenes.Insert(szFileName, scene); return scene; } } return nullptr; }
ezResult Mesh::ComputeTangents() { ezStopwatch timer; struct MikkInterfaceImpl { MikkInterfaceImpl(Mesh& mesh, const VertexDataStream& position, const VertexDataStream& normal, const VertexDataStream& tex) : triangles(mesh.m_Triangles) , positionStream(position) , normalStream(normal) , texStream(tex) , tangentStream(*mesh.AddDataStream(ezGALVertexAttributeSemantic::Tangent, 3)) // Make sure tangent stream exists. , bitangentStream(*mesh.AddDataStream(ezGALVertexAttributeSemantic::BiTangent, 1)) , bitangentIndexNegative(0) , bitangentIndexPositive(sizeof(float)) { float biTangentSignValues[] = {-1.0f, 1.0f}; bitangentStream.AddValues(ezMakeArrayPtr(biTangentSignValues)); } ezArrayPtr<Triangle> triangles; const TypedVertexDataStreamView<ezVec3> positionStream; const TypedVertexDataStreamView<ezVec3> normalStream; const TypedVertexDataStreamView<ezVec2> texStream; TypedVertexDataStreamView_ReadWrite<ezVec3> tangentStream; TypedVertexDataStreamView_ReadWrite<float> bitangentStream; VertexDataIndex bitangentIndexPositive; VertexDataIndex bitangentIndexNegative; ezHashTable<ezVec3, VertexDataIndex> tangentDataMap; int GetNumFaces() const { return triangles.GetCount(); } int GetNumVerticesOfFace(const int iFace) const { return 3; } void GetPosition(float fvPosOut[], const int iFace, const int iVert) const { ezVec3 p = positionStream.GetValue(triangles[iFace].m_Vertices[iVert]); fvPosOut[0] = p.x; fvPosOut[1] = p.y; fvPosOut[2] = p.z; } void GetNormal(float fvNormOut[], const int iFace, const int iVert) const { ezVec3 n = normalStream.GetValue(triangles[iFace].m_Vertices[iVert]); fvNormOut[0] = n.x; fvNormOut[1] = n.y; fvNormOut[2] = n.z; } void GetTexCoord(float fvTexcOut[], const int iFace, const int iVert) const { ezVec2 uv = texStream.GetValue(triangles[iFace].m_Vertices[iVert]); fvTexcOut[0] = uv.x; fvTexcOut[1] = uv.y; } void SetTSpaceBasic(const float fvTangent[], const float fSign, const int iFace, const int iVert) { // Need to reconstruct indexing using hashmap lookups. // (will get a call to SetTSpaceBasic for every triangle vertex, not for every data vertex.) VertexIndex v = triangles[iFace].m_Vertices[iVert]; ezVec3 key = ezVec3(fvTangent[0], fvTangent[1], fvTangent[2]); VertexDataIndex existingTangentIndex; if (tangentDataMap.TryGetValue(key, existingTangentIndex)) { tangentStream->SetDataIndex(v, existingTangentIndex); } else { tangentStream.SetValue(v, ezVec3(fvTangent[0], fvTangent[1], fvTangent[2])); tangentDataMap.Insert(key, tangentStream->GetDataIndex(v)); } // For bitangent sign its easy: There are only 2 signs and we've set the data already. if (fSign >= 1.0f) bitangentStream->SetDataIndex(v, bitangentIndexPositive); else bitangentStream->SetDataIndex(v, bitangentIndexNegative); } }; // If there is already a data stream with 3 component bitangents, remove it. { VertexDataStream* bitangents = GetDataStream(ezGALVertexAttributeSemantic::BiTangent); if (bitangents && bitangents->GetNumElementsPerVertex() != 1) RemoveDataStream(ezGALVertexAttributeSemantic::BiTangent); } const VertexDataStream* positionStream = GetDataStream(ezGALVertexAttributeSemantic::Position); if (positionStream == nullptr) { ezLog::Error("Can't compute vertex tangents for the mesh '{0}', because it doesn't have vertex positions.", m_Name); return EZ_FAILURE; } const VertexDataStream* normalStream = GetDataStream(ezGALVertexAttributeSemantic::Normal); if (normalStream == nullptr) { ezLog::Error("Can't compute tangents for the mesh '{0}', because it doesn't have vertex normals.", m_Name); return EZ_FAILURE; } const VertexDataStream* texStream = GetDataStream(ezGALVertexAttributeSemantic::TexCoord0); if (texStream == nullptr || texStream->GetNumElementsPerVertex() != 2) { ezLog::Error("Can't compute tangents for the mesh '{0}', because it doesn't have TexCoord0 stream with two components.", m_Name); return EZ_FAILURE; } MikkInterfaceImpl mikkInterface(*this, *positionStream, *normalStream, *texStream); // Use Morton S. Mikkelsen's tangent calculation. SMikkTSpaceContext context; SMikkTSpaceInterface functions; context.m_pUserData = &mikkInterface; context.m_pInterface = &functions; functions.m_getNumFaces = [](const SMikkTSpaceContext* pContext) { return static_cast<MikkInterfaceImpl*>(pContext->m_pUserData)->GetNumFaces(); }; functions.m_getNumVerticesOfFace = [](const SMikkTSpaceContext* pContext, const int iFace) { return static_cast<MikkInterfaceImpl*>(pContext->m_pUserData)->GetNumVerticesOfFace(iFace); }; functions.m_getPosition = [](const SMikkTSpaceContext* pContext, float fvPosOut[], const int iFace, const int iVert) { return static_cast<MikkInterfaceImpl*>(pContext->m_pUserData)->GetPosition(fvPosOut, iFace, iVert); }; functions.m_getNormal = [](const SMikkTSpaceContext* pContext, float fvPosOut[], const int iFace, const int iVert) { return static_cast<MikkInterfaceImpl*>(pContext->m_pUserData)->GetNormal(fvPosOut, iFace, iVert); }; functions.m_getTexCoord = [](const SMikkTSpaceContext* pContext, float fvPosOut[], const int iFace, const int iVert) { return static_cast<MikkInterfaceImpl*>(pContext->m_pUserData)->GetTexCoord(fvPosOut, iFace, iVert); }; functions.m_setTSpaceBasic = [](const SMikkTSpaceContext* pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert) { return static_cast<MikkInterfaceImpl*>(pContext->m_pUserData)->SetTSpaceBasic(fvTangent, fSign, iFace, iVert); }; functions.m_setTSpace = nullptr; if (!genTangSpaceDefault(&context)) { ezLog::Error("Failed to compute compute tangents for the mesh {0}.", m_Name); return EZ_FAILURE; } ezLog::Debug("Computed mesh normals ('{0}') in '{1}'s", m_Name, ezArgF(timer.GetRunningTotal().GetSeconds(), 2)); return EZ_SUCCESS; }
void ezQGridBarWidget::paintEvent(QPaintEvent* e) { if (!MapFromSceneFunc.IsValid()) { QWidget::paintEvent(e); return; } QPainter Painter(this); QPainter* painter = &Painter; painter->setRenderHint(QPainter::Antialiasing); painter->setRenderHint(QPainter::TextAntialiasing); painter->setRenderHint(QPainter::HighQualityAntialiasing); QRect areaRect = rect(); // background painter->fillRect(areaRect, palette().button()); painter->translate(0.5, 0.5); // render fine grid stop lines { double fSceneMinX, fSceneMaxX; ezWidgetUtils::ComputeGridExtentsX(m_viewportSceneRect, m_fFineGridStops, fSceneMinX, fSceneMaxX); fSceneMinX = ezMath::Max(fSceneMinX, 0.0); painter->setPen(palette().buttonText().color()); ezHybridArray<QLine, 100> lines; // some overcompensation for the case that the GraphicsView displays a scrollbar at the side for (double x = fSceneMinX; x <= fSceneMaxX + m_fTextGridStops; x += m_fFineGridStops) { const QPoint pos = MapFromSceneFunc(QPointF(x, 0)); QLine& l = lines.ExpandAndGetRef(); l.setLine(pos.x(), areaRect.bottom() - 3, pos.x(), areaRect.bottom()); } painter->drawLines(lines.GetData(), lines.GetCount()); } // Grid Stop Value Text { double fSceneMinX, fSceneMaxX; ezWidgetUtils::ComputeGridExtentsX(m_viewportSceneRect, m_fTextGridStops, fSceneMinX, fSceneMaxX); fSceneMinX = ezMath::Max(fSceneMinX, 0.0); QTextOption textOpt(Qt::AlignCenter); QRectF textRect; painter->setPen(palette().buttonText().color()); ezStringBuilder tmp; for (double x = fSceneMinX; x <= fSceneMaxX; x += m_fTextGridStops) { const QPoint pos = MapFromSceneFunc(QPointF(x, 0)); textRect.setRect(pos.x() - 50, areaRect.top(), 99, areaRect.height()); tmp.Format("{0}", ezArgF(x)); painter->drawText(textRect, tmp.GetData(), textOpt); } } }