Asset API updates, parallel blake3, inline AABB extender, IO policies, runtime tunning, mesh loaders & writers#1000
Asset API updates, parallel blake3, inline AABB extender, IO policies, runtime tunning, mesh loaders & writers#1000AnastaZIuk wants to merge 42 commits intomasterfrom
Conversation
| double stlNormalizeColorComponentToUnit(double value) | ||
| { | ||
| if (!std::isfinite(value)) | ||
| return 0.0; | ||
| if (value > 1.0) | ||
| value /= 255.0; | ||
| return std::clamp(value, 0.0, 1.0); | ||
| } | ||
|
|
||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, "endsolid ", context->fileOffset, 9); | ||
| uint16_t stlPackViscamColorFromB8G8R8A8(const uint32_t color) | ||
| { | ||
| const void* src[4] = { &color, nullptr, nullptr, nullptr }; | ||
| uint16_t packed = 0u; | ||
| convertColor<EF_B8G8R8A8_UNORM, EF_A1R5G5B5_UNORM_PACK16>(src, &packed, 0u, 0u); | ||
| packed |= 0x8000u; | ||
| return packed; | ||
| } | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| } | ||
| const ICPUPolygonGeometry::SDataView* stlFindColorView(const ICPUPolygonGeometry* geom, const size_t vertexCount) | ||
| { | ||
| if (!geom) | ||
| return nullptr; | ||
|
|
||
| const auto& auxViews = geom->getAuxAttributeViews(); | ||
| const ICPUPolygonGeometry::SDataView* fallback = nullptr; | ||
| for (const auto& view : auxViews) | ||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, headerTxt, context->fileOffset, sizeof(headerTxt) - 1); | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| if (!view || view.getElementCount() != vertexCount) | ||
| continue; | ||
| const uint32_t channels = getFormatChannelCount(view.composed.format); | ||
| if (channels < 3u) | ||
| continue; | ||
| if (view.composed.format == EF_B8G8R8A8_UNORM) | ||
| return &view; | ||
| if (!fallback) | ||
| fallback = &view; | ||
| } | ||
| return fallback; | ||
| } | ||
|
|
||
| bool stlDecodeColorB8G8R8A8(const ICPUPolygonGeometry::SDataView& colorView, const uint32_t ix, uint32_t& outColor) | ||
| { | ||
| if (colorView.composed.format == EF_B8G8R8A8_UNORM && colorView.composed.getStride() == sizeof(uint32_t)) | ||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, name.c_str(), context->fileOffset, name.size()); | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| const auto* const ptr = reinterpret_cast<const uint8_t*>(colorView.getPointer()); | ||
| if (!ptr) | ||
| return false; | ||
| std::memcpy(&outColor, ptr + static_cast<size_t>(ix) * sizeof(uint32_t), sizeof(outColor)); | ||
| return true; | ||
| } | ||
|
|
||
| hlsl::float64_t4 decoded = {}; | ||
| if (!colorView.decodeElement(ix, decoded)) | ||
| return false; | ||
| const double rgbaUnit[4] = { | ||
| stlNormalizeColorComponentToUnit(decoded.x), | ||
| stlNormalizeColorComponentToUnit(decoded.y), | ||
| stlNormalizeColorComponentToUnit(decoded.z), | ||
| stlNormalizeColorComponentToUnit(decoded.w) | ||
| }; | ||
| encodePixels<EF_B8G8R8A8_UNORM, double>(&outColor, rgbaUnit); | ||
| return true; | ||
| } | ||
|
|
||
| void CSTLMeshWriter::getVectorAsStringLine(const core::vectorSIMDf& v, std::string& s) const | ||
| void stlDecodeColorUnitRGBAFromB8G8R8A8(const uint32_t color, double (&out)[4]) | ||
| { | ||
| std::ostringstream tmp; | ||
| tmp << v.X << " " << v.Y << " " << v.Z << "\n"; | ||
| s = std::string(tmp.str().c_str()); | ||
| const void* src[4] = { &color, nullptr, nullptr, nullptr }; | ||
| decodePixels<EF_B8G8R8A8_UNORM, double>(src, out, 0u, 0u); | ||
| } | ||
|
|
||
| void CSTLMeshWriter::writeFaceText( | ||
| const core::vectorSIMDf& v1, | ||
| const core::vectorSIMDf& v2, | ||
| const core::vectorSIMDf& v3, | ||
| SContext* context) | ||
| bool writeMeshBinary(const asset::ICPUPolygonGeometry* geom, SContext* context) | ||
| { | ||
| core::vectorSIMDf vertex1 = v3; | ||
| core::vectorSIMDf vertex2 = v2; | ||
| core::vectorSIMDf vertex3 = v1; | ||
| core::vectorSIMDf normal = core::plane3dSIMDf(vertex1, vertex2, vertex3).getNormal(); | ||
| std::string tmp; | ||
| if (!geom || !context || !context->writeContext.outputFile) | ||
| return false; | ||
|
|
||
| auto flipVectors = [&]() | ||
| { | ||
| vertex1.X = -vertex1.X; | ||
| vertex2.X = -vertex2.X; | ||
| vertex3.X = -vertex3.X; | ||
| normal = core::plane3dSIMDf(vertex1, vertex2, vertex3).getNormal(); | ||
| }; | ||
|
|
||
| if (!(context->writeContext.params.flags & E_WRITER_FLAGS::EWF_MESH_IS_RIGHT_HANDED)) | ||
| flipVectors(); | ||
|
|
||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, "facet normal ", context->fileOffset, 13); | ||
| const auto& posView = geom->getPositionView(); | ||
| if (!posView) | ||
| return false; | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| } | ||
| const bool flipHandedness = !(context->writeContext.params.flags & E_WRITER_FLAGS::EWF_MESH_IS_RIGHT_HANDED); | ||
| const size_t vertexCount = posView.getElementCount(); | ||
| if (vertexCount == 0ull) | ||
| return false; | ||
|
|
||
| getVectorAsStringLine(normal, tmp); | ||
| core::vector<uint32_t> indexData; | ||
| const uint32_t* indices = nullptr; | ||
| uint32_t facenum = 0u; | ||
| if (!decodeTriangleIndices(geom, posView, indexData, indices, facenum)) | ||
| return false; | ||
|
|
||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, tmp.c_str(), context->fileOffset, tmp.size()); | ||
| const size_t outputSize = stl_writer_detail::BinaryPrefixBytes + static_cast<size_t>(facenum) * stl_writer_detail::BinaryTriangleRecordBytes; | ||
| std::unique_ptr<uint8_t[]> output(new (std::nothrow) uint8_t[outputSize]); | ||
| if (!output) | ||
| return false; | ||
| uint8_t* dst = output.get(); | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| } | ||
| std::memset(dst, 0, stl_writer_detail::BinaryHeaderBytes); | ||
| dst += stl_writer_detail::BinaryHeaderBytes; | ||
|
|
||
| std::memcpy(dst, &facenum, sizeof(facenum)); | ||
| dst += sizeof(facenum); | ||
|
|
||
| const auto& normalView = geom->getNormalView(); | ||
| const bool hasNormals = static_cast<bool>(normalView); | ||
| const auto* const colorView = stlFindColorView(geom, vertexCount); | ||
| const hlsl::float32_t3* const tightPositions = getTightFloat3View(posView); | ||
| const hlsl::float32_t3* const tightNormals = hasNormals ? getTightFloat3View(normalView) : nullptr; | ||
| const float handednessSign = flipHandedness ? -1.f : 1.f; | ||
|
|
||
| auto decodePosition = [&](const uint32_t ix, hlsl::float32_t3& out)->bool | ||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, " outer loop\n", context->fileOffset, 13); | ||
| if (tightPositions) | ||
| { | ||
| out = tightPositions[ix]; | ||
| return true; | ||
| } | ||
| return posView.decodeElement(ix, out); | ||
| }; | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| } | ||
| auto decodeNormal = [&](const uint32_t ix, hlsl::float32_t3& out)->bool | ||
| { | ||
| if (!hasNormals) | ||
| return false; | ||
| if (tightNormals) | ||
| { | ||
| out = tightNormals[ix]; | ||
| return true; | ||
| } | ||
| return normalView.decodeElement(ix, out); | ||
| }; | ||
| auto computeFaceColor = [&](const uint32_t i0, const uint32_t i1, const uint32_t i2, uint16_t& outColor)->bool | ||
| { | ||
| outColor = 0u; | ||
| if (!colorView) | ||
| return true; | ||
| uint32_t c0 = 0u, c1 = 0u, c2 = 0u; | ||
| if (!stlDecodeColorB8G8R8A8(*colorView, i0, c0)) | ||
| return false; | ||
| if (!stlDecodeColorB8G8R8A8(*colorView, i1, c1)) | ||
| return false; | ||
| if (!stlDecodeColorB8G8R8A8(*colorView, i2, c2)) | ||
| return false; | ||
| double rgba0[4] = {}; | ||
| double rgba1[4] = {}; | ||
| double rgba2[4] = {}; | ||
| stlDecodeColorUnitRGBAFromB8G8R8A8(c0, rgba0); | ||
| stlDecodeColorUnitRGBAFromB8G8R8A8(c1, rgba1); | ||
| stlDecodeColorUnitRGBAFromB8G8R8A8(c2, rgba2); | ||
| const double rgbaAvg[4] = { | ||
| (rgba0[0] + rgba1[0] + rgba2[0]) / 3.0, | ||
| (rgba0[1] + rgba1[1] + rgba2[1]) / 3.0, | ||
| (rgba0[2] + rgba1[2] + rgba2[2]) / 3.0, | ||
| 1.0 | ||
| }; | ||
| uint32_t avgColor = 0u; | ||
| encodePixels<EF_B8G8R8A8_UNORM, double>(&avgColor, rgbaAvg); | ||
| outColor = stlPackViscamColorFromB8G8R8A8(avgColor); | ||
| return true; | ||
| }; | ||
| auto writeRecord = [&dst](const float nx, const float ny, const float nz, const float v1x, const float v1y, const float v1z, const float v2x, const float v2y, const float v2z, const float v3x, const float v3y, const float v3z, const uint16_t attribute)->void | ||
| { | ||
| const float payload[stl_writer_detail::BinaryTriangleFloatCount] = { | ||
| nx, ny, nz, | ||
| v1x, v1y, v1z, | ||
| v2x, v2y, v2z, | ||
| v3x, v3y, v3z | ||
| }; | ||
| std::memcpy(dst, payload, stl_writer_detail::BinaryTriangleFloatBytes); | ||
| dst += stl_writer_detail::BinaryTriangleFloatBytes; | ||
| std::memcpy(dst, &attribute, stl_writer_detail::BinaryTriangleAttributeBytes); | ||
| dst += stl_writer_detail::BinaryTriangleAttributeBytes; | ||
| }; | ||
|
|
||
| const bool hasFastTightPath = (indices == nullptr) && (tightPositions != nullptr) && (!hasNormals || (tightNormals != nullptr)); | ||
| if (hasFastTightPath && hasNormals) | ||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, " vertex ", context->fileOffset, 11); | ||
| bool allFastNormalsNonZero = true; | ||
| const size_t normalCount = static_cast<size_t>(facenum) * 3ull; | ||
| for (size_t i = 0ull; i < normalCount; ++i) | ||
| { | ||
| const auto& n = tightNormals[i]; | ||
| if (n.x == 0.f && n.y == 0.f && n.z == 0.f) | ||
| { | ||
| allFastNormalsNonZero = false; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| const hlsl::float32_t3* posTri = tightPositions; | ||
| const hlsl::float32_t3* nrmTri = tightNormals; | ||
| if (allFastNormalsNonZero) | ||
| { | ||
| for (uint32_t primIx = 0u; primIx < facenum; ++primIx, posTri += 3u, nrmTri += 3u) | ||
| { | ||
| uint16_t faceColor = 0u; | ||
| if (!computeFaceColor(primIx * 3u + 0u, primIx * 3u + 1u, primIx * 3u + 2u, faceColor)) | ||
| return false; | ||
|
|
||
| const hlsl::float32_t3 vertex1 = posTri[2u]; | ||
| const hlsl::float32_t3 vertex2 = posTri[1u]; | ||
| const hlsl::float32_t3 vertex3 = posTri[0u]; | ||
| const float vertex1x = vertex1.x * handednessSign; | ||
| const float vertex2x = vertex2.x * handednessSign; | ||
| const float vertex3x = vertex3.x * handednessSign; | ||
|
|
||
| hlsl::float32_t3 attrNormal = nrmTri[0u]; | ||
| if (flipHandedness) | ||
| attrNormal.x = -attrNormal.x; | ||
|
|
||
| writeRecord( | ||
| attrNormal.x, attrNormal.y, attrNormal.z, | ||
| vertex1x, vertex1.y, vertex1.z, | ||
| vertex2x, vertex2.y, vertex2.z, | ||
| vertex3x, vertex3.y, vertex3.z, | ||
| faceColor); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| for (uint32_t primIx = 0u; primIx < facenum; ++primIx, posTri += 3u, nrmTri += 3u) | ||
| { | ||
| uint16_t faceColor = 0u; | ||
| if (!computeFaceColor(primIx * 3u + 0u, primIx * 3u + 1u, primIx * 3u + 2u, faceColor)) | ||
| return false; | ||
|
|
||
| const hlsl::float32_t3 vertex1 = posTri[2u]; | ||
| const hlsl::float32_t3 vertex2 = posTri[1u]; | ||
| const hlsl::float32_t3 vertex3 = posTri[0u]; | ||
| const float vertex1x = vertex1.x * handednessSign; | ||
| const float vertex2x = vertex2.x * handednessSign; | ||
| const float vertex3x = vertex3.x * handednessSign; | ||
|
|
||
| float normalX = 0.f; | ||
| float normalY = 0.f; | ||
| float normalZ = 0.f; | ||
| hlsl::float32_t3 attrNormal = nrmTri[0u]; | ||
| if (attrNormal.x == 0.f && attrNormal.y == 0.f && attrNormal.z == 0.f) | ||
| attrNormal = nrmTri[1u]; | ||
| if (attrNormal.x == 0.f && attrNormal.y == 0.f && attrNormal.z == 0.f) | ||
| attrNormal = nrmTri[2u]; | ||
| if (!(attrNormal.x == 0.f && attrNormal.y == 0.f && attrNormal.z == 0.f)) | ||
| { | ||
| if (flipHandedness) | ||
| attrNormal.x = -attrNormal.x; | ||
| normalX = attrNormal.x; | ||
| normalY = attrNormal.y; | ||
| normalZ = attrNormal.z; | ||
| } | ||
|
|
||
| if (normalX == 0.f && normalY == 0.f && normalZ == 0.f) | ||
| { | ||
| const float edge21x = vertex2x - vertex1x; | ||
| const float edge21y = vertex2.y - vertex1.y; | ||
| const float edge21z = vertex2.z - vertex1.z; | ||
| const float edge31x = vertex3x - vertex1x; | ||
| const float edge31y = vertex3.y - vertex1.y; | ||
| const float edge31z = vertex3.z - vertex1.z; | ||
|
|
||
| normalX = edge21y * edge31z - edge21z * edge31y; | ||
| normalY = edge21z * edge31x - edge21x * edge31z; | ||
| normalZ = edge21x * edge31y - edge21y * edge31x; | ||
| const float planeNormalLen2 = normalX * normalX + normalY * normalY + normalZ * normalZ; | ||
| if (planeNormalLen2 > 0.f) | ||
| { | ||
| const float invLen = 1.f / std::sqrt(planeNormalLen2); | ||
| normalX *= invLen; | ||
| normalY *= invLen; | ||
| normalZ *= invLen; | ||
| } | ||
| } | ||
|
|
||
| writeRecord( | ||
| normalX, normalY, normalZ, | ||
| vertex1x, vertex1.y, vertex1.z, | ||
| vertex2x, vertex2.y, vertex2.z, | ||
| vertex3x, vertex3.y, vertex3.z, | ||
| faceColor); | ||
| } | ||
| } | ||
| } | ||
| else if (hasFastTightPath) | ||
| { | ||
| const hlsl::float32_t3* posTri = tightPositions; | ||
| for (uint32_t primIx = 0u; primIx < facenum; ++primIx, posTri += 3u) | ||
| { | ||
| uint16_t faceColor = 0u; | ||
| if (!computeFaceColor(primIx * 3u + 0u, primIx * 3u + 1u, primIx * 3u + 2u, faceColor)) | ||
| return false; | ||
|
|
||
| const hlsl::float32_t3 vertex1 = posTri[2u]; | ||
| const hlsl::float32_t3 vertex2 = posTri[1u]; | ||
| const hlsl::float32_t3 vertex3 = posTri[0u]; | ||
| const float vertex1x = vertex1.x * handednessSign; | ||
| const float vertex2x = vertex2.x * handednessSign; | ||
| const float vertex3x = vertex3.x * handednessSign; | ||
|
|
||
| const float edge21x = vertex2x - vertex1x; | ||
| const float edge21y = vertex2.y - vertex1.y; | ||
| const float edge21z = vertex2.z - vertex1.z; | ||
| const float edge31x = vertex3x - vertex1x; | ||
| const float edge31y = vertex3.y - vertex1.y; | ||
| const float edge31z = vertex3.z - vertex1.z; | ||
|
|
||
| float normalX = edge21y * edge31z - edge21z * edge31y; | ||
| float normalY = edge21z * edge31x - edge21x * edge31z; | ||
| float normalZ = edge21x * edge31y - edge21y * edge31x; | ||
| const float planeNormalLen2 = normalX * normalX + normalY * normalY + normalZ * normalZ; | ||
| if (planeNormalLen2 > 0.f) | ||
| { | ||
| const float invLen = 1.f / std::sqrt(planeNormalLen2); | ||
| normalX *= invLen; | ||
| normalY *= invLen; | ||
| normalZ *= invLen; | ||
| } | ||
|
|
||
| writeRecord( | ||
| normalX, normalY, normalZ, | ||
| vertex1x, vertex1.y, vertex1.z, | ||
| vertex2x, vertex2.y, vertex2.z, | ||
| vertex3x, vertex3.y, vertex3.z, | ||
| faceColor); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| for (uint32_t primIx = 0u; primIx < facenum; ++primIx) | ||
| { | ||
| const uint32_t i0 = indices ? indices[primIx * 3u + 0u] : (primIx * 3u + 0u); | ||
| const uint32_t i1 = indices ? indices[primIx * 3u + 1u] : (primIx * 3u + 1u); | ||
| const uint32_t i2 = indices ? indices[primIx * 3u + 2u] : (primIx * 3u + 2u); | ||
| if (i0 >= vertexCount || i1 >= vertexCount || i2 >= vertexCount) | ||
| return false; | ||
| uint16_t faceColor = 0u; | ||
| if (!computeFaceColor(i0, i1, i2, faceColor)) | ||
| return false; | ||
|
|
||
| hlsl::float32_t3 p0 = {}; | ||
| hlsl::float32_t3 p1 = {}; | ||
| hlsl::float32_t3 p2 = {}; | ||
| if (!decodePosition(i0, p0) || !decodePosition(i1, p1) || !decodePosition(i2, p2)) | ||
| return false; | ||
|
|
||
| hlsl::float32_t3 vertex1 = p2; | ||
| hlsl::float32_t3 vertex2 = p1; | ||
| hlsl::float32_t3 vertex3 = p0; | ||
|
|
||
| if (flipHandedness) | ||
| { | ||
| vertex1.x = -vertex1.x; | ||
| vertex2.x = -vertex2.x; | ||
| vertex3.x = -vertex3.x; | ||
| } | ||
|
|
||
| const hlsl::float32_t3 planeNormal = hlsl::cross(vertex2 - vertex1, vertex3 - vertex1); | ||
| const float planeNormalLen2 = hlsl::dot(planeNormal, planeNormal); | ||
| hlsl::float32_t3 normal = hlsl::float32_t3(0.f, 0.f, 0.f); | ||
| if (!hasNormals) | ||
| { | ||
| if (planeNormalLen2 > 0.f) | ||
| normal = hlsl::normalize(planeNormal); | ||
| } | ||
|
|
||
| if (hasNormals) | ||
| { | ||
| hlsl::float32_t3 n0 = {}; | ||
| if (!decodeNormal(i0, n0)) | ||
| return false; | ||
|
|
||
| hlsl::float32_t3 attrNormal = n0; | ||
| if (hlsl::dot(attrNormal, attrNormal) <= 0.f) | ||
| { | ||
| hlsl::float32_t3 n1 = {}; | ||
| if (!decodeNormal(i1, n1)) | ||
| return false; | ||
| attrNormal = n1; | ||
| } | ||
| if (hlsl::dot(attrNormal, attrNormal) <= 0.f) | ||
| { | ||
| hlsl::float32_t3 n2 = {}; | ||
| if (!decodeNormal(i2, n2)) | ||
| return false; | ||
| attrNormal = n2; | ||
| } | ||
|
|
||
| if (hlsl::dot(attrNormal, attrNormal) > 0.f) | ||
| { | ||
| if (flipHandedness) | ||
| attrNormal.x = -attrNormal.x; | ||
| if (planeNormalLen2 > 0.f && hlsl::dot(attrNormal, planeNormal) < 0.f) | ||
| attrNormal = -attrNormal; | ||
| normal = attrNormal; | ||
| } | ||
| else if (planeNormalLen2 > 0.f) | ||
| { | ||
| normal = hlsl::normalize(planeNormal); | ||
| } | ||
| } | ||
|
|
||
| writeRecord( | ||
| normal.x, normal.y, normal.z, | ||
| vertex1.x, vertex1.y, vertex1.z, | ||
| vertex2.x, vertex2.y, vertex2.z, | ||
| vertex3.x, vertex3.y, vertex3.z, | ||
| faceColor); | ||
| } | ||
| } | ||
|
|
||
| getVectorAsStringLine(vertex1, tmp); | ||
| const bool writeOk = writeFileWithPolicy(context->writeContext.outputFile, context->ioPlan, output.get(), outputSize, &context->writeTelemetry); | ||
| if (writeOk) | ||
| context->fileOffset += outputSize; | ||
| return writeOk; | ||
| } | ||
|
|
||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, tmp.c_str(), context->fileOffset, tmp.size()); | ||
| bool writeMeshASCII(const asset::ICPUPolygonGeometry* geom, SContext* context) | ||
| { | ||
| if (!geom) | ||
| return false; | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| } | ||
| const auto* indexing = geom->getIndexingCallback(); | ||
| if (!indexing || indexing->degree() != 3u) | ||
| return false; | ||
|
|
||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, " vertex ", context->fileOffset, 11); | ||
| const auto& posView = geom->getPositionView(); | ||
| if (!posView) | ||
| return false; | ||
| const auto& normalView = geom->getNormalView(); | ||
| const bool flipHandedness = !(context->writeContext.params.flags & E_WRITER_FLAGS::EWF_MESH_IS_RIGHT_HANDED); | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| } | ||
| const std::string name = context->writeContext.outputFile->getFileName().filename().replace_extension().string(); | ||
| const std::string_view solidName = name.empty() ? std::string_view(stl_writer_detail::AsciiDefaultName) : std::string_view(name); | ||
|
|
||
| getVectorAsStringLine(vertex2, tmp); | ||
| if (!writeBytes(context, stl_writer_detail::AsciiSolidPrefix, sizeof(stl_writer_detail::AsciiSolidPrefix) - 1ull)) | ||
| return false; | ||
|
|
||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, tmp.c_str(), context->fileOffset, tmp.size()); | ||
| if (!writeBytes(context, solidName.data(), solidName.size())) | ||
| return false; | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| } | ||
| if (!writeBytes(context, "\n", sizeof("\n") - 1ull)) | ||
| return false; | ||
|
|
||
| const uint32_t faceCount = static_cast<uint32_t>(geom->getPrimitiveCount()); | ||
| for (uint32_t primIx = 0u; primIx < faceCount; ++primIx) | ||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, " vertex ", context->fileOffset, 11); | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| core::vectorSIMDf v0; | ||
| core::vectorSIMDf v1; | ||
| core::vectorSIMDf v2; | ||
| uint32_t idx[3] = {}; | ||
| if (!decodeTriangle(geom, indexing, posView, primIx, v0, v1, v2, idx)) | ||
| return false; | ||
| if (!writeFaceText(v0, v1, v2, idx, normalView, flipHandedness, context)) | ||
| return false; | ||
| if (!writeBytes(context, "\n", sizeof("\n") - 1ull)) | ||
| return false; | ||
| } | ||
|
|
||
| getVectorAsStringLine(vertex3, tmp); | ||
| if (!writeBytes(context, stl_writer_detail::AsciiEndSolidPrefix, sizeof(stl_writer_detail::AsciiEndSolidPrefix) - 1ull)) | ||
| return false; | ||
|
|
||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, tmp.c_str(), context->fileOffset, tmp.size()); | ||
| if (!writeBytes(context, solidName.data(), solidName.size())) | ||
| return false; | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, " endloop\n", context->fileOffset, 10); | ||
| bool writeFaceText( | ||
| const core::vectorSIMDf& v1, | ||
| const core::vectorSIMDf& v2, | ||
| const core::vectorSIMDf& v3, | ||
| const uint32_t* idx, | ||
| const asset::ICPUPolygonGeometry::SDataView& normalView, | ||
| const bool flipHandedness, | ||
| SContext* context) | ||
| { | ||
| core::vectorSIMDf vertex1 = v3; | ||
| core::vectorSIMDf vertex2 = v2; | ||
| core::vectorSIMDf vertex3 = v1; | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| if (flipHandedness) | ||
| { | ||
| vertex1.X = -vertex1.X; | ||
| vertex2.X = -vertex2.X; | ||
| vertex3.X = -vertex3.X; | ||
| } | ||
|
|
||
| core::vectorSIMDf normal = core::plane3dSIMDf(vertex1, vertex2, vertex3).getNormal(); | ||
| core::vectorSIMDf attrNormal; | ||
| if (decodeTriangleNormal(normalView, idx, attrNormal)) | ||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, "endfacet\n", context->fileOffset, 9); | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| if (flipHandedness) | ||
| attrNormal.X = -attrNormal.X; | ||
| if (core::dot(attrNormal, normal).X < 0.f) | ||
| attrNormal = -attrNormal; | ||
| normal = attrNormal; | ||
| } | ||
|
|
||
| std::array<char, stl_writer_detail::AsciiFaceTextMaxBytes> faceText = {}; | ||
| char* cursor = faceText.data(); | ||
| char* const end = faceText.data() + faceText.size(); | ||
| if (!appendLiteral(cursor, end, "facet normal ", sizeof("facet normal ") - 1ull)) | ||
| return false; | ||
| if (!appendVectorAsAsciiLine(cursor, end, normal)) | ||
| return false; | ||
| if (!appendLiteral(cursor, end, " outer loop\n", sizeof(" outer loop\n") - 1ull)) | ||
| return false; | ||
| if (!appendLiteral(cursor, end, " vertex ", sizeof(" vertex ") - 1ull)) | ||
| return false; | ||
| if (!appendVectorAsAsciiLine(cursor, end, vertex1)) | ||
| return false; | ||
| if (!appendLiteral(cursor, end, " vertex ", sizeof(" vertex ") - 1ull)) | ||
| return false; | ||
| if (!appendVectorAsAsciiLine(cursor, end, vertex2)) | ||
| return false; | ||
| if (!appendLiteral(cursor, end, " vertex ", sizeof(" vertex ") - 1ull)) | ||
| return false; | ||
| if (!appendVectorAsAsciiLine(cursor, end, vertex3)) | ||
| return false; | ||
| if (!appendLiteral(cursor, end, " endloop\n", sizeof(" endloop\n") - 1ull)) | ||
| return false; | ||
| if (!appendLiteral(cursor, end, "endfacet\n", sizeof("endfacet\n") - 1ull)) | ||
| return false; | ||
|
|
||
| return writeBytes(context, faceText.data(), static_cast<size_t>(cursor - faceText.data())); | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
fun fact when dealing with mesh LDR colors, they're supposed to be SRGB
whenever they were UNORM before, that was wrong.
There was a problem hiding this comment.
at least when the semantics were clear that they were RGB per-vertex colors
| core::vector<uint32_t> indexData; | ||
| const uint32_t* indices = nullptr; | ||
| uint32_t facenum = 0u; | ||
| if (!decodeTriangleIndices(geom, posView, indexData, indices, facenum)) | ||
| return false; |
There was a problem hiding this comment.
make a common util for all polygon geo writers ?
| const size_t outputSize = stl_writer_detail::BinaryPrefixBytes + static_cast<size_t>(facenum) * stl_writer_detail::BinaryTriangleRecordBytes; | ||
| std::unique_ptr<uint8_t[]> output(new (std::nothrow) uint8_t[outputSize]); | ||
| if (!output) | ||
| return false; | ||
| uint8_t* dst = output.get(); |
There was a problem hiding this comment.
does this make sense if the output file is memory mapped anyway ?
| auto computeFaceColor = [&](const uint32_t i0, const uint32_t i1, const uint32_t i2, uint16_t& outColor)->bool | ||
| { | ||
| outColor = 0u; | ||
| if (!colorView) | ||
| return true; | ||
| uint32_t c0 = 0u, c1 = 0u, c2 = 0u; | ||
| if (!stlDecodeColorB8G8R8A8(*colorView, i0, c0)) | ||
| return false; | ||
| if (!stlDecodeColorB8G8R8A8(*colorView, i1, c1)) | ||
| return false; | ||
| if (!stlDecodeColorB8G8R8A8(*colorView, i2, c2)) | ||
| return false; | ||
| double rgba0[4] = {}; | ||
| double rgba1[4] = {}; | ||
| double rgba2[4] = {}; | ||
| stlDecodeColorUnitRGBAFromB8G8R8A8(c0, rgba0); | ||
| stlDecodeColorUnitRGBAFromB8G8R8A8(c1, rgba1); | ||
| stlDecodeColorUnitRGBAFromB8G8R8A8(c2, rgba2); | ||
| const double rgbaAvg[4] = { | ||
| (rgba0[0] + rgba1[0] + rgba2[0]) / 3.0, | ||
| (rgba0[1] + rgba1[1] + rgba2[1]) / 3.0, | ||
| (rgba0[2] + rgba1[2] + rgba2[2]) / 3.0, | ||
| 1.0 | ||
| }; | ||
| uint32_t avgColor = 0u; | ||
| encodePixels<EF_B8G8R8A8_UNORM, double>(&avgColor, rgbaAvg); | ||
| outColor = stlPackViscamColorFromB8G8R8A8(avgColor); | ||
| return true; | ||
| }; |
There was a problem hiding this comment.
I think you need better SRGB handling
| outColor = stlPackViscamColorFromB8G8R8A8(avgColor); | ||
| return true; | ||
| }; | ||
| auto writeRecord = [&dst](const float nx, const float ny, const float nz, const float v1x, const float v1y, const float v1z, const float v2x, const float v2y, const float v2z, const float v3x, const float v3y, const float v3z, const uint16_t attribute)->void |
There was a problem hiding this comment.
why are we passing stuff as individual scalars and not hlsl::float32_t3 ?
| const hlsl::float32_t3 vertex1 = posTri[2u]; | ||
| const hlsl::float32_t3 vertex2 = posTri[1u]; | ||
| const hlsl::float32_t3 vertex3 = posTri[0u]; | ||
| const float vertex1x = vertex1.x * handednessSign; | ||
| const float vertex2x = vertex2.x * handednessSign; | ||
| const float vertex3x = vertex3.x * handednessSign; | ||
|
|
||
| float normalX = 0.f; | ||
| float normalY = 0.f; | ||
| float normalZ = 0.f; | ||
| hlsl::float32_t3 attrNormal = nrmTri[0u]; | ||
| if (attrNormal.x == 0.f && attrNormal.y == 0.f && attrNormal.z == 0.f) | ||
| attrNormal = nrmTri[1u]; | ||
| if (attrNormal.x == 0.f && attrNormal.y == 0.f && attrNormal.z == 0.f) | ||
| attrNormal = nrmTri[2u]; | ||
| if (!(attrNormal.x == 0.f && attrNormal.y == 0.f && attrNormal.z == 0.f)) | ||
| { | ||
| if (flipHandedness) | ||
| attrNormal.x = -attrNormal.x; | ||
| normalX = attrNormal.x; | ||
| normalY = attrNormal.y; | ||
| normalZ = attrNormal.z; | ||
| } | ||
|
|
||
| if (normalX == 0.f && normalY == 0.f && normalZ == 0.f) | ||
| { | ||
| const float edge21x = vertex2x - vertex1x; | ||
| const float edge21y = vertex2.y - vertex1.y; | ||
| const float edge21z = vertex2.z - vertex1.z; | ||
| const float edge31x = vertex3x - vertex1x; | ||
| const float edge31y = vertex3.y - vertex1.y; | ||
| const float edge31z = vertex3.z - vertex1.z; | ||
|
|
||
| normalX = edge21y * edge31z - edge21z * edge31y; | ||
| normalY = edge21z * edge31x - edge21x * edge31z; | ||
| normalZ = edge21x * edge31y - edge21y * edge31x; | ||
| const float planeNormalLen2 = normalX * normalX + normalY * normalY + normalZ * normalZ; | ||
| if (planeNormalLen2 > 0.f) | ||
| { | ||
| const float invLen = 1.f / std::sqrt(planeNormalLen2); | ||
| normalX *= invLen; | ||
| normalY *= invLen; | ||
| normalZ *= invLen; | ||
| } | ||
| } |
There was a problem hiding this comment.
the code could be so much smaller if you used vector types and HLSL lib
| if (planeNormalLen2 > 0.f) | ||
| { | ||
| const float invLen = 1.f / std::sqrt(planeNormalLen2); | ||
| normalX *= invLen; | ||
| normalY *= invLen; | ||
| normalZ *= invLen; | ||
| } |
There was a problem hiding this comment.
its okay to write NaN in this case
| if (planeNormalLen2 > 0.f) | ||
| { | ||
| const float invLen = 1.f / std::sqrt(planeNormalLen2); | ||
| normalX *= invLen; | ||
| normalY *= invLen; | ||
| normalZ *= invLen; | ||
| } |
| hlsl::float32_t3 p0 = {}; | ||
| hlsl::float32_t3 p1 = {}; | ||
| hlsl::float32_t3 p2 = {}; | ||
| if (!decodePosition(i0, p0) || !decodePosition(i1, p1) || !decodePosition(i2, p2)) | ||
| return false; | ||
|
|
||
| hlsl::float32_t3 vertex1 = p2; | ||
| hlsl::float32_t3 vertex2 = p1; | ||
| hlsl::float32_t3 vertex3 = p0; | ||
|
|
||
| if (flipHandedness) | ||
| { | ||
| vertex1.x = -vertex1.x; | ||
| vertex2.x = -vertex2.x; | ||
| vertex3.x = -vertex3.x; | ||
| } |
There was a problem hiding this comment.
use arrays instead of separate vals
| hlsl::float32_t3 normal = hlsl::float32_t3(0.f, 0.f, 0.f); | ||
| if (!hasNormals) | ||
| { | ||
| if (planeNormalLen2 > 0.f) |
| if (hasNormals) | ||
| { | ||
| hlsl::float32_t3 n0 = {}; | ||
| if (!decodeNormal(i0, n0)) | ||
| return false; | ||
|
|
||
| hlsl::float32_t3 attrNormal = n0; | ||
| if (hlsl::dot(attrNormal, attrNormal) <= 0.f) | ||
| { | ||
| hlsl::float32_t3 n1 = {}; | ||
| if (!decodeNormal(i1, n1)) | ||
| return false; | ||
| attrNormal = n1; | ||
| } | ||
| if (hlsl::dot(attrNormal, attrNormal) <= 0.f) | ||
| { | ||
| hlsl::float32_t3 n2 = {}; | ||
| if (!decodeNormal(i2, n2)) | ||
| return false; | ||
| attrNormal = n2; | ||
| } | ||
|
|
||
| if (hlsl::dot(attrNormal, attrNormal) > 0.f) | ||
| { | ||
| if (flipHandedness) | ||
| attrNormal.x = -attrNormal.x; | ||
| if (planeNormalLen2 > 0.f && hlsl::dot(attrNormal, planeNormal) < 0.f) | ||
| attrNormal = -attrNormal; | ||
| normal = attrNormal; | ||
| } | ||
| else if (planeNormalLen2 > 0.f) | ||
| { | ||
| normal = hlsl::normalize(planeNormal); | ||
| } | ||
| } |
There was a problem hiding this comment.
what's all this code ?
| bool writeFaceText( | ||
| const core::vectorSIMDf& v1, | ||
| const core::vectorSIMDf& v2, | ||
| const core::vectorSIMDf& v3, | ||
| const uint32_t* idx, | ||
| const asset::ICPUPolygonGeometry::SDataView& normalView, | ||
| const bool flipHandedness, | ||
| SContext* context) | ||
| { | ||
| core::vectorSIMDf vertex1 = v3; | ||
| core::vectorSIMDf vertex2 = v2; | ||
| core::vectorSIMDf vertex3 = v1; | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| if (flipHandedness) | ||
| { | ||
| vertex1.X = -vertex1.X; | ||
| vertex2.X = -vertex2.X; | ||
| vertex3.X = -vertex3.X; | ||
| } | ||
|
|
||
| core::vectorSIMDf normal = core::plane3dSIMDf(vertex1, vertex2, vertex3).getNormal(); | ||
| core::vectorSIMDf attrNormal; | ||
| if (decodeTriangleNormal(normalView, idx, attrNormal)) | ||
| { | ||
| system::IFile::success_t success;; | ||
| context->writeContext.outputFile->write(success, "endfacet\n", context->fileOffset, 9); | ||
|
|
||
| context->fileOffset += success.getBytesProcessed(); | ||
| if (flipHandedness) | ||
| attrNormal.X = -attrNormal.X; | ||
| if (core::dot(attrNormal, normal).X < 0.f) | ||
| attrNormal = -attrNormal; | ||
| normal = attrNormal; | ||
| } |
There was a problem hiding this comment.
get rid of old vector types
| core::vectorSIMDf v0; | ||
| core::vectorSIMDf v1; | ||
| core::vectorSIMDf v2; |
| constexpr size_t ApproxObjBytesPerVertex = 96ull; | ||
| constexpr size_t ApproxObjBytesPerFace = 48ull; | ||
| constexpr size_t MaxUInt32Chars = 10ull; | ||
| constexpr size_t MaxFloatFixed6Chars = 48ull; | ||
| constexpr size_t MaxIndexTokenBytes = MaxUInt32Chars * 3ull + 2ull; |
There was a problem hiding this comment.
express this with sizeof so we know where all that stuff comes from / assumptions
| bool decodeVec4(const ICPUPolygonGeometry::SDataView& view, const size_t ix, hlsl::float64_t4& out) | ||
| { | ||
| out = hlsl::float64_t4(0.0, 0.0, 0.0, 0.0); | ||
| return view.decodeElement(ix, out); | ||
| } | ||
|
|
||
| char* appendUIntToBuffer(char* dst, char* const end, const uint32_t value) | ||
| { | ||
| if (!dst || dst >= end) | ||
| return end; | ||
|
|
||
| const auto result = std::to_chars(dst, end, value); | ||
| if (result.ec == std::errc()) | ||
| return result.ptr; | ||
|
|
||
| const int written = std::snprintf(dst, static_cast<size_t>(end - dst), "%u", value); | ||
| if (written <= 0) | ||
| return dst; | ||
| const size_t writeLen = static_cast<size_t>(written); | ||
| return (writeLen < static_cast<size_t>(end - dst)) ? (dst + writeLen) : end; | ||
| } |
| void appendVec3Line(std::string& out, const char* prefix, const size_t prefixSize, const float x, const float y, const float z) | ||
| { | ||
| const size_t oldSize = out.size(); | ||
| out.resize(oldSize + prefixSize + (3ull * MaxFloatFixed6Chars) + 3ull); | ||
| char* const lineBegin = out.data() + oldSize; | ||
| char* cursor = lineBegin; | ||
| char* const lineEnd = out.data() + out.size(); | ||
|
|
||
| std::memcpy(cursor, prefix, prefixSize); | ||
| cursor += prefixSize; | ||
|
|
||
| cursor = appendFloatFixed6ToBuffer(cursor, lineEnd, x); | ||
| if (cursor < lineEnd) | ||
| *(cursor++) = ' '; | ||
| cursor = appendFloatFixed6ToBuffer(cursor, lineEnd, y); | ||
| if (cursor < lineEnd) | ||
| *(cursor++) = ' '; | ||
| cursor = appendFloatFixed6ToBuffer(cursor, lineEnd, z); | ||
| if (cursor < lineEnd) | ||
| *(cursor++) = '\n'; | ||
|
|
||
| out.resize(oldSize + static_cast<size_t>(cursor - lineBegin)); | ||
| } | ||
|
|
||
| void appendVec2Line(std::string& out, const char* prefix, const size_t prefixSize, const float x, const float y) | ||
| { | ||
| const size_t oldSize = out.size(); | ||
| out.resize(oldSize + prefixSize + (2ull * MaxFloatFixed6Chars) + 2ull); | ||
| char* const lineBegin = out.data() + oldSize; | ||
| char* cursor = lineBegin; | ||
| char* const lineEnd = out.data() + out.size(); | ||
|
|
||
| std::memcpy(cursor, prefix, prefixSize); | ||
| cursor += prefixSize; | ||
|
|
||
| cursor = appendFloatFixed6ToBuffer(cursor, lineEnd, x); | ||
| if (cursor < lineEnd) | ||
| *(cursor++) = ' '; | ||
| cursor = appendFloatFixed6ToBuffer(cursor, lineEnd, y); | ||
| if (cursor < lineEnd) | ||
| *(cursor++) = '\n'; | ||
|
|
||
| out.resize(oldSize + static_cast<size_t>(cursor - lineBegin)); | ||
| } | ||
|
|
||
| void appendFaceLine(std::string& out, const std::string& storage, const core::vector<SIndexStringRef>& refs, const uint32_t i0, const uint32_t i1, const uint32_t i2) | ||
| { | ||
| const auto& ref0 = refs[i0]; | ||
| const auto& ref1 = refs[i1]; | ||
| const auto& ref2 = refs[i2]; | ||
| const size_t oldSize = out.size(); | ||
| const size_t lineSize = 2ull + static_cast<size_t>(ref0.length) + 1ull + static_cast<size_t>(ref1.length) + 1ull + static_cast<size_t>(ref2.length) + 1ull; | ||
| out.resize(oldSize + lineSize); | ||
| char* cursor = out.data() + oldSize; | ||
| *(cursor++) = 'f'; | ||
| *(cursor++) = ' '; | ||
| std::memcpy(cursor, storage.data() + ref0.offset, ref0.length); | ||
| cursor += ref0.length; | ||
| *(cursor++) = ' '; | ||
| std::memcpy(cursor, storage.data() + ref1.offset, ref1.length); | ||
| cursor += ref1.length; | ||
| *(cursor++) = ' '; | ||
| std::memcpy(cursor, storage.data() + ref2.offset, ref2.length); | ||
| cursor += ref2.length; | ||
| *(cursor++) = '\n'; | ||
| } |
There was a problem hiding this comment.
why not take hlsl::float32_tN and hlsl::uint32_t3 instead of individual scalar arguments?
Also you have this out state that could really be wrapped up in a context struct with methods
| const auto* geom = IAsset::castDown<const ICPUPolygonGeometry>(_params.rootAsset); | ||
| if (!geom || !geom->valid()) | ||
| return false; | ||
|
|
There was a problem hiding this comment.
btw OBJ writer should be an ICPUScene writer
| inline bool parseObjFloat(const char*& ptr, const char* const end, float& out) | ||
| { | ||
| SContext ctx( | ||
| asset::IAssetLoader::SAssetLoadContext{ | ||
| _params, | ||
| _file | ||
| }, | ||
| _hierarchyLevel, | ||
| _override | ||
| ); | ||
| const char* const start = ptr; | ||
| if (start >= end) | ||
| return false; | ||
|
|
||
| if (_params.meshManipulatorOverride == nullptr) | ||
| { | ||
| _NBL_DEBUG_BREAK_IF(true); | ||
| assert(false); | ||
| } | ||
| const char* p = start; | ||
| bool negative = false; | ||
| if (*p == '-' || *p == '+') | ||
| { | ||
| negative = (*p == '-'); | ||
| ++p; | ||
| if (p >= end) | ||
| return false; | ||
| } | ||
|
|
||
| CQuantNormalCache* const quantNormalCache = _params.meshManipulatorOverride->getQuantNormalCache(); | ||
| if (*p == '.' || !isObjDigit(*p)) | ||
| { | ||
| const auto parseResult = fast_float::from_chars(start, end, out); | ||
| if (parseResult.ec == std::errc() && parseResult.ptr != start) | ||
| { | ||
| ptr = parseResult.ptr; | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| const long filesize = _file->getSize(); | ||
| if (!filesize) | ||
| return {}; | ||
| uint64_t integerPart = 0ull; | ||
| while (p < end && isObjDigit(*p)) | ||
| { | ||
| integerPart = integerPart * 10ull + static_cast<uint64_t>(*p - '0'); | ||
| ++p; | ||
| } | ||
|
|
||
| const uint32_t WORD_BUFFER_LENGTH = 512u; | ||
| char tmpbuf[WORD_BUFFER_LENGTH]{}; | ||
|
|
||
| uint32_t smoothingGroup=0; | ||
|
|
||
| const std::filesystem::path fullName = _file->getFileName(); | ||
| const std::string relPath = [&fullName]() -> std::string | ||
| { | ||
| auto dir = fullName.parent_path().string(); | ||
| return dir; | ||
| }(); | ||
|
|
||
| //value_type: directory from which .mtl (pipeline) was loaded and the pipeline | ||
| using pipeline_meta_pair_t = std::pair<core::smart_refctd_ptr<ICPURenderpassIndependentPipeline>,const CMTLMetadata::CRenderpassIndependentPipeline*>; | ||
| struct hash_t | ||
| { | ||
| inline auto operator()(const pipeline_meta_pair_t& item) const | ||
| { | ||
| return std::hash<std::string>()(item.second->m_name); | ||
| } | ||
| }; | ||
| struct key_equal_t | ||
| { | ||
| inline bool operator()(const pipeline_meta_pair_t& lhs, const pipeline_meta_pair_t& rhs) const | ||
| { | ||
| return lhs.second->m_name==rhs.second->m_name; | ||
| } | ||
| }; | ||
| core::unordered_multiset<pipeline_meta_pair_t,hash_t,key_equal_t> pipelines; | ||
|
|
||
| // TODO: map the file whenever possible | ||
| std::string fileContents; | ||
| fileContents.resize(filesize); | ||
| char* const buf = fileContents.data(); | ||
|
|
||
| system::IFile::success_t success; | ||
| _file->read(success, buf, 0, filesize); | ||
| if (!success) | ||
| return {}; | ||
|
|
||
| const char* const bufEnd = buf+filesize; | ||
| // Process obj information | ||
| const char* bufPtr = buf; | ||
| std::string grpName, mtlName; | ||
|
|
||
| auto performActionBasedOnOrientationSystem = [&](auto performOnRightHanded, auto performOnLeftHanded) | ||
| { | ||
| if (_params.loaderFlags & E_LOADER_PARAMETER_FLAGS::ELPF_RIGHT_HANDED_MESHES) | ||
| performOnRightHanded(); | ||
| else | ||
| performOnLeftHanded(); | ||
| }; | ||
|
|
||
|
|
||
| struct vec3 { | ||
| float data[3]; | ||
| }; | ||
| struct vec2 { | ||
| float data[2]; | ||
| }; | ||
| core::vector<vec3> vertexBuffer; | ||
| core::vector<vec3> normalsBuffer; | ||
| core::vector<vec2> textureCoordBuffer; | ||
|
|
||
| core::vector<core::smart_refctd_ptr<ICPUMeshBuffer>> submeshes; | ||
| core::vector<core::vector<uint32_t>> indices; | ||
| core::vector<SObjVertex> vertices; | ||
| core::map<SObjVertex, uint32_t> map_vtx2ix; | ||
| core::vector<bool> recalcNormals; | ||
| core::vector<bool> submeshWasLoadedFromCache; | ||
| core::vector<std::string> submeshCacheKeys; | ||
| core::vector<std::string> submeshMaterialNames; | ||
| core::vector<uint32_t> vtxSmoothGrp; | ||
|
|
||
| // TODO: handle failures much better! | ||
| constexpr const char* NO_MATERIAL_MTL_NAME = "#"; | ||
| bool noMaterial = true; | ||
| bool dummyMaterialCreated = false; | ||
| while(bufPtr != bufEnd) | ||
| { | ||
| switch(bufPtr[0]) | ||
| { | ||
| case 'm': // mtllib (material) | ||
| { | ||
| if (ctx.useMaterials) | ||
| { | ||
| bufPtr = goAndCopyNextWord(tmpbuf, bufPtr, WORD_BUFFER_LENGTH, bufEnd); | ||
| _params.logger.log("Reading material _file %s", system::ILogger::ELL_DEBUG, tmpbuf); | ||
|
|
||
| std::string mtllib = tmpbuf; | ||
| std::replace(mtllib.begin(), mtllib.end(), '\\', '/'); | ||
| SAssetLoadParams loadParams(_params); | ||
| loadParams.workingDirectory = _file->getFileName().parent_path(); | ||
| auto bundle = interm_getAssetInHierarchy(AssetManager, mtllib, loadParams, _hierarchyLevel+ICPUMesh::PIPELINE_HIERARCHYLEVELS_BELOW, _override); | ||
|
|
||
| if (bundle.getContents().empty()) | ||
| break; | ||
|
|
||
| if (bundle.getMetadata()) | ||
| { | ||
| auto meta = bundle.getMetadata()->selfCast<const CMTLMetadata>(); | ||
| if (bundle.getAssetType()==IAsset::ET_RENDERPASS_INDEPENDENT_PIPELINE) | ||
| for (auto ass : bundle.getContents()) | ||
| { | ||
| auto ppln = core::smart_refctd_ptr_static_cast<ICPURenderpassIndependentPipeline>(ass); | ||
| const auto pplnMeta = meta->getAssetSpecificMetadata(ppln.get()); | ||
| if (!pplnMeta) | ||
| continue; | ||
|
|
||
| pipelines.emplace(std::move(ppln),pplnMeta); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| break; | ||
|
|
||
| case 'v': // v, vn, vt | ||
| //reset flags | ||
| noMaterial = true; | ||
| dummyMaterialCreated = false; | ||
| switch(bufPtr[1]) | ||
| { | ||
| case ' ': // vertex | ||
| { | ||
| vec3 vec; | ||
| bufPtr = readVec3(bufPtr, vec.data, bufEnd); | ||
| performActionBasedOnOrientationSystem([&]() {vec.data[0] = -vec.data[0];}, [&]() {}); | ||
| vertexBuffer.push_back(vec); | ||
| } | ||
| break; | ||
|
|
||
| case 'n': // normal | ||
| { | ||
| vec3 vec; | ||
| bufPtr = readVec3(bufPtr, vec.data, bufEnd); | ||
| performActionBasedOnOrientationSystem([&]() {vec.data[0] = -vec.data[0]; }, [&]() {}); | ||
| normalsBuffer.push_back(vec); | ||
| } | ||
| break; | ||
|
|
||
| case 't': // texcoord | ||
| { | ||
| vec2 vec; | ||
| bufPtr = readUV(bufPtr, vec.data, bufEnd); | ||
| textureCoordBuffer.push_back(vec); | ||
| } | ||
| break; | ||
| } | ||
| break; | ||
|
|
||
| case 'g': // group name | ||
| bufPtr = goAndCopyNextWord(tmpbuf, bufPtr, WORD_BUFFER_LENGTH, bufEnd); | ||
| grpName = tmpbuf; | ||
| break; | ||
| case 's': // smoothing can be a group or off (equiv. to 0) | ||
| { | ||
| bufPtr = goAndCopyNextWord(tmpbuf, bufPtr, WORD_BUFFER_LENGTH, bufEnd); | ||
| _params.logger.log("Loaded smoothing group start %s",system::ILogger::ELL_DEBUG, tmpbuf); | ||
| if (strcmp("off", tmpbuf)==0) | ||
| smoothingGroup=0u; | ||
| else | ||
| sscanf(tmpbuf,"%u",&smoothingGroup); | ||
| } | ||
| break; | ||
|
|
||
| case 'u': // usemtl | ||
| // get name of material | ||
| { | ||
| noMaterial = false; | ||
| bufPtr = goAndCopyNextWord(tmpbuf, bufPtr, WORD_BUFFER_LENGTH, bufEnd); | ||
| _params.logger.log("Loaded material start %s", system::ILogger::ELL_DEBUG, tmpbuf); | ||
| mtlName=tmpbuf; | ||
|
|
||
| if (ctx.useMaterials && !ctx.useGroups) | ||
| { | ||
| asset::IAsset::E_TYPE types[] {asset::IAsset::ET_SUB_MESH, (asset::IAsset::E_TYPE)0u }; | ||
| auto mb_bundle = _override->findCachedAsset(genKeyForMeshBuf(ctx, _file->getFileName().string(), mtlName, grpName), types, ctx.inner, _hierarchyLevel+ICPUMesh::MESHBUFFER_HIERARCHYLEVELS_BELOW); | ||
| auto mbs = mb_bundle.getContents(); | ||
| bool notempty = mbs.size()!=0ull; | ||
| { | ||
| auto mb = notempty ? core::smart_refctd_ptr_static_cast<ICPUMeshBuffer>(*mbs.begin()) : core::make_smart_refctd_ptr<ICPUMeshBuffer>(); | ||
| submeshes.push_back(std::move(mb)); | ||
| } | ||
| indices.emplace_back(); | ||
| recalcNormals.push_back(false); | ||
| submeshWasLoadedFromCache.push_back(notempty); | ||
| //if submesh was loaded from cache - insert empty "cache key" (submesh loaded from cache won't be added to cache again) | ||
| submeshCacheKeys.push_back(submeshWasLoadedFromCache.back() ? "" : genKeyForMeshBuf(ctx, _file->getFileName().string(), mtlName, grpName)); | ||
| submeshMaterialNames.push_back(mtlName); | ||
| } | ||
| } | ||
| break; | ||
| case 'f': // face | ||
| { | ||
| if (noMaterial && !dummyMaterialCreated) | ||
| { | ||
| dummyMaterialCreated = true; | ||
|
|
||
| submeshes.push_back(core::make_smart_refctd_ptr<ICPUMeshBuffer>()); | ||
| indices.emplace_back(); | ||
| recalcNormals.push_back(false); | ||
| submeshWasLoadedFromCache.push_back(false); | ||
| submeshCacheKeys.push_back(genKeyForMeshBuf(ctx, _file->getFileName().string(), NO_MATERIAL_MTL_NAME, grpName)); | ||
| submeshMaterialNames.push_back(NO_MATERIAL_MTL_NAME); | ||
| } | ||
|
|
||
| SObjVertex v; | ||
|
|
||
| // get all vertices data in this face (current line of obj _file) | ||
| const std::string wordBuffer = copyLine(bufPtr, bufEnd); | ||
| const char* linePtr = wordBuffer.c_str(); | ||
| const char* const endPtr = linePtr + wordBuffer.size(); | ||
|
|
||
| core::vector<uint32_t> faceCorners; | ||
| faceCorners.reserve(32ull); | ||
|
|
||
| // read in all vertices | ||
| linePtr = goNextWord(linePtr, endPtr); | ||
| while (0 != linePtr[0]) | ||
| { | ||
| // Array to communicate with retrieveVertexIndices() | ||
| // sends the buffer sizes and gets the actual indices | ||
| // if index not set returns -1 | ||
| int32_t Idx[3]; | ||
| Idx[1] = Idx[2] = -1; | ||
|
|
||
| // read in next vertex's data | ||
| uint32_t wlength = copyWord(tmpbuf, linePtr, WORD_BUFFER_LENGTH, endPtr); | ||
| // this function will also convert obj's 1-based index to c++'s 0-based index | ||
| retrieveVertexIndices(tmpbuf, Idx, tmpbuf+wlength+1, vertexBuffer.size(), textureCoordBuffer.size(), normalsBuffer.size()); | ||
| v.pos[0] = vertexBuffer[Idx[0]].data[0]; | ||
| v.pos[1] = vertexBuffer[Idx[0]].data[1]; | ||
| v.pos[2] = vertexBuffer[Idx[0]].data[2]; | ||
| //set texcoord | ||
| if ( -1 != Idx[1] ) | ||
| { | ||
| v.uv[0] = textureCoordBuffer[Idx[1]].data[0]; | ||
| v.uv[1] = textureCoordBuffer[Idx[1]].data[1]; | ||
| } | ||
| else | ||
| { | ||
| v.uv[0] = core::nan<float>(); | ||
| v.uv[1] = core::nan<float>(); | ||
| } | ||
| //set normal | ||
| if ( -1 != Idx[2] ) | ||
| { | ||
| core::vectorSIMDf simdNormal; | ||
| simdNormal.set(normalsBuffer[Idx[2]].data); | ||
| simdNormal.makeSafe3D(); | ||
| v.normal32bit = quantNormalCache->quantize<EF_A2B10G10R10_SNORM_PACK32>(simdNormal); | ||
| } | ||
| else | ||
| { | ||
| v.normal32bit = core::vectorSIMDu32(0u); | ||
| recalcNormals.back() = true; | ||
| } | ||
|
|
||
| uint32_t ix; | ||
| auto vtx_ix = map_vtx2ix.find(v); | ||
| if (vtx_ix != map_vtx2ix.end() && smoothingGroup==vtxSmoothGrp[vtx_ix->second]) | ||
| ix = vtx_ix->second; | ||
| else | ||
| { | ||
| ix = vertices.size(); | ||
| vertices.push_back(v); | ||
| vtxSmoothGrp.push_back(smoothingGroup); | ||
| map_vtx2ix.insert({v, ix}); | ||
| } | ||
|
|
||
| faceCorners.push_back(ix); | ||
|
|
||
| // go to next vertex | ||
| linePtr = goNextWord(linePtr, endPtr); | ||
| } | ||
|
|
||
| // triangulate the face | ||
| for (uint32_t i = 1u; i < faceCorners.size()-1u; ++i) | ||
| double value = static_cast<double>(integerPart); | ||
| if (p < end && *p == '.') | ||
| { | ||
| const char* const dot = p; | ||
| if ((dot + 7) <= end) | ||
| { | ||
| const char d0 = dot[1]; | ||
| const char d1 = dot[2]; | ||
| const char d2 = dot[3]; | ||
| const char d3 = dot[4]; | ||
| const char d4 = dot[5]; | ||
| const char d5 = dot[6]; | ||
| if ( | ||
| isObjDigit(d0) && isObjDigit(d1) && isObjDigit(d2) && | ||
| isObjDigit(d3) && isObjDigit(d4) && isObjDigit(d5) | ||
| ) |
There was a problem hiding this comment.
did you have to write your own float parser ?
| const auto createAdoptedView = [](auto&& data, const E_FORMAT format) -> IGeometry<ICPUBuffer>::SDataView | ||
| { | ||
| using T = typename std::decay_t<decltype(data)>::value_type; | ||
| if (data.empty()) | ||
| return {}; | ||
|
|
||
| IMeshManipulator::recalculateBoundingBox(mesh.get()); | ||
| if (mesh->getMeshBuffers().empty()) | ||
| auto backer = core::make_smart_refctd_ptr<core::adoption_memory_resource<core::vector<T>>>(std::move(data)); | ||
| auto& storage = backer->getBacker(); | ||
| auto* const ptr = storage.data(); | ||
| const size_t byteCount = storage.size() * sizeof(T); | ||
| auto buffer = ICPUBuffer::create({ { byteCount }, ptr, core::smart_refctd_ptr<core::refctd_memory_resource>(std::move(backer)), alignof(T) }, core::adopt_memory); | ||
| if (!buffer) | ||
| return {}; | ||
|
|
||
| // | ||
| auto meta = core::make_smart_refctd_ptr<COBJMetadata>(usedPipelines.size()); | ||
| uint32_t metaOffset = 0u; | ||
| for (auto pipeAndMeta : usedPipelines) | ||
| meta->placeMeta(metaOffset++,pipeAndMeta.first.get(),*pipeAndMeta.second); | ||
|
|
||
| //at the very end, insert submeshes into cache | ||
| uint32_t i = 0u; | ||
| for (auto meshbuffer : mesh->getMeshBuffers()) | ||
| { | ||
| auto bundle = SAssetBundle(meta,{ core::smart_refctd_ptr<ICPUMeshBuffer>(meshbuffer) }); | ||
| _override->insertAssetIntoCache(bundle, submeshCacheKeys[i++], ctx.inner, _hierarchyLevel+ICPUMesh::MESHBUFFER_HIERARCHYLEVELS_BELOW); | ||
| } | ||
|
|
||
| return SAssetBundle(std::move(meta),{std::move(mesh)}); | ||
| } | ||
|
|
||
| IGeometry<ICPUBuffer>::SDataView view = { | ||
| .composed = { | ||
| .stride = sizeof(T), | ||
| .format = format, | ||
| .rangeFormat = IGeometryBase::getMatchingAABBFormat(format) | ||
| }, | ||
| .src = { | ||
| .offset = 0u, | ||
| .size = byteCount, | ||
| .buffer = std::move(buffer) | ||
| } | ||
| }; | ||
| return view; | ||
| }; |
There was a problem hiding this comment.
common with other loaders
| inline bool parseUnsignedObjIndex(const char*& ptr, const char* const end, uint32_t& out) | ||
| { | ||
| const uint32_t WORD_BUFFER_LENGTH = 256; | ||
| char wordBuffer[WORD_BUFFER_LENGTH]; | ||
| if (ptr >= end || !isObjDigit(*ptr)) | ||
| return false; | ||
|
|
||
| bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd); | ||
| sscanf(wordBuffer,"%f",vec); | ||
| bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd); | ||
| sscanf(wordBuffer,"%f",vec+1); | ||
| uint64_t value = 0ull; | ||
| while (ptr < end && isObjDigit(*ptr)) | ||
| { | ||
| value = value * 10ull + static_cast<uint64_t>(*ptr - '0'); | ||
| ++ptr; | ||
| } | ||
| if (value == 0ull || value > static_cast<uint64_t>(std::numeric_limits<int32_t>::max())) | ||
| return false; | ||
|
|
||
| vec[1] = 1.f-vec[1]; // change handedness | ||
| return bufPtr; | ||
| out = static_cast<uint32_t>(value); | ||
| return true; | ||
| } |
There was a problem hiding this comment.
again, own parser needed ?
| inline char toObjLowerAscii(const char c) | ||
| { | ||
| if (c >= 'A' && c <= 'Z') | ||
| return static_cast<char>(c - 'A' + 'a'); | ||
| return c; | ||
| } |
There was a problem hiding this comment.
what was wrong with std::to_lower ?
| uint64_t value = 0ull; | ||
| bool sawDigit = false; | ||
| for (const char* it = tokenStart; it < linePtr; ++it) | ||
| { | ||
| if (!isObjDigit(*it)) | ||
| { | ||
| outGroup = 0u; | ||
| return; | ||
| } | ||
| sawDigit = true; | ||
| value = value * 10ull + static_cast<uint64_t>(*it - '0'); | ||
| if (value > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) | ||
| { | ||
| outGroup = 0u; | ||
| return; | ||
| } | ||
| } |
There was a problem hiding this comment.
this is an integer parser again, duplicate code as well
| while (ptr < lineEnd && isObjDigit(*ptr)) | ||
| { | ||
| const uint32_t digit = static_cast<uint32_t>(*ptr - '0'); | ||
| if (value > 429496729u) |
There was a problem hiding this comment.
numeric_limits harmed you?
| while (ptr < lineEnd && isObjDigit(*ptr)) | ||
| { | ||
| const uint32_t digit = static_cast<uint32_t>(*ptr - '0'); | ||
| if (value > 429496729u) | ||
| return false; | ||
| value = value * 10u + digit; | ||
| ++ptr; | ||
| } |
There was a problem hiding this comment.
this is a 32bit uint parser, can you somehow make that with STD or other functions
| uint32_t value = 0u; | ||
| if (ptr >= lineEnd || !isObjDigit(*ptr)) | ||
| return false; | ||
| while (ptr < lineEnd && isObjDigit(*ptr)) | ||
| { | ||
| const uint32_t digit = static_cast<uint32_t>(*ptr - '0'); | ||
| if (value > 429496729u) | ||
| return false; | ||
| value = value * 10u + digit; | ||
| ++ptr; | ||
| } |
| uint32_t value = 0u; | ||
| if (ptr >= lineEnd || !isObjDigit(*ptr)) | ||
| return false; | ||
| while (ptr < lineEnd && isObjDigit(*ptr)) | ||
| { | ||
| const uint32_t digit = static_cast<uint32_t>(*ptr - '0'); | ||
| if (value > 429496729u) | ||
| return false; | ||
| value = value * 10u + digit; | ||
| ++ptr; | ||
| } |
| bool negative = false; | ||
| if (*ptr == '-') | ||
| { | ||
| negative = true; | ||
| ++ptr; | ||
| } | ||
| else if (*ptr == '+') | ||
| { | ||
| ++ptr; | ||
| } | ||
|
|
||
| if (ptr >= end || !isObjDigit(*ptr)) | ||
| return false; | ||
|
|
||
| int64_t value = 0; | ||
| while (ptr < end && isObjDigit(*ptr)) | ||
| { | ||
| value = value * 10ll + static_cast<int64_t>(*ptr - '0'); | ||
| ++ptr; | ||
| } | ||
| if (negative) | ||
| value = -value; | ||
|
|
||
| if (value == 0) | ||
| return false; | ||
| if (value < static_cast<int64_t>(std::numeric_limits<int32_t>::min()) || value > static_cast<int64_t>(std::numeric_limits<int32_t>::max())) | ||
| return false; | ||
|
|
||
| out = static_cast<int32_t>(value); |
There was a problem hiding this comment.
and this is a 64bit signed int parser
| SFileReadTelemetry ioTelemetry = {}; | ||
|
|
||
| const long filesize = _file->getSize(); | ||
| if (filesize <= 0) | ||
| return {}; | ||
| const auto ioPlan = resolveFileIOPolicy(_params.ioPolicy, static_cast<uint64_t>(filesize), true); | ||
| if (!ioPlan.valid) | ||
| { | ||
| _params.logger.log("OBJ loader: invalid io policy for %s reason=%s", system::ILogger::ELL_ERROR, _file->getFileName().string().c_str(), ioPlan.reason); | ||
| return {}; | ||
| } | ||
|
|
||
| std::string fileContents = {}; | ||
| const char* buf = nullptr; | ||
| if (ioPlan.strategy == SResolvedFileIOPolicy::Strategy::WholeFile) | ||
| { | ||
| const auto* constFile = static_cast<const system::IFile*>(_file); | ||
| const auto* mapped = reinterpret_cast<const char*>(constFile->getMappedPointer()); | ||
| if (mapped) | ||
| { | ||
| buf = mapped; | ||
| ioTelemetry.account(static_cast<uint64_t>(filesize)); | ||
| } | ||
| } | ||
| if (!buf) | ||
| { | ||
| fileContents.resize(static_cast<size_t>(filesize)); | ||
| if (!readTextFileWithPolicy(_file, fileContents.data(), fileContents.size(), ioPlan, ioTelemetry)) | ||
| return {}; | ||
| buf = fileContents.data(); | ||
| } |
There was a problem hiding this comment.
common to all loaders
| struct SDedupHotEntry | ||
| { | ||
| int32_t pos = -1; | ||
| int32_t uv = -1; | ||
| int32_t normal = -1; | ||
| uint32_t outIndex = 0u; | ||
| }; | ||
| const size_t hw = resolveLoaderHardwareThreads(); | ||
| const size_t hardMaxWorkers = resolveLoaderHardMaxWorkers(hw, _params.ioPolicy.runtimeTuning.workerHeadroom); | ||
| SLoaderRuntimeTuningRequest dedupTuningRequest = {}; | ||
| dedupTuningRequest.inputBytes = static_cast<uint64_t>(filesize); | ||
| dedupTuningRequest.totalWorkUnits = estimatedOutVertexCount; | ||
| dedupTuningRequest.hardwareThreads = static_cast<uint32_t>(hw); | ||
| dedupTuningRequest.hardMaxWorkers = static_cast<uint32_t>(hardMaxWorkers); | ||
| dedupTuningRequest.targetChunksPerWorker = _params.ioPolicy.runtimeTuning.targetChunksPerWorker; | ||
| dedupTuningRequest.sampleData = reinterpret_cast<const uint8_t*>(buf); | ||
| dedupTuningRequest.sampleBytes = resolveLoaderRuntimeSampleBytes(_params.ioPolicy, static_cast<uint64_t>(filesize)); | ||
| const auto dedupTuning = tuneLoaderRuntime(_params.ioPolicy, dedupTuningRequest); | ||
| const size_t dedupHotSeed = std::max<size_t>( | ||
| 16ull, | ||
| estimatedOutVertexCount / std::max<size_t>(1ull, dedupTuning.workerCount * 8ull)); | ||
| const size_t dedupHotEntryCount = std::bit_ceil(dedupHotSeed); | ||
| core::vector<SDedupHotEntry> dedupHotCache(dedupHotEntryCount); | ||
| const size_t dedupHotMask = dedupHotEntryCount - 1ull; |
There was a problem hiding this comment.
what is this for?
| if (needsNormalGeneration) | ||
| { | ||
| core::vector<Float3> generatedNormals(outVertexWriteCount, Float3(0.f, 0.f, 0.f)); | ||
| const size_t triangleCount = indices.size() / 3ull; | ||
| for (size_t triIx = 0ull; triIx < triangleCount; ++triIx) | ||
| { | ||
| const uint32_t i0 = indices[triIx * 3ull + 0ull]; | ||
| const uint32_t i1 = indices[triIx * 3ull + 1ull]; | ||
| const uint32_t i2 = indices[triIx * 3ull + 2ull]; | ||
| if (i0 >= outVertexWriteCount || i1 >= outVertexWriteCount || i2 >= outVertexWriteCount) | ||
| continue; | ||
|
|
||
| } // end namespace scene | ||
| } // end namespace nbl | ||
| const auto& p0 = outPositions[static_cast<size_t>(i0)]; | ||
| const auto& p1 = outPositions[static_cast<size_t>(i1)]; | ||
| const auto& p2 = outPositions[static_cast<size_t>(i2)]; | ||
|
|
||
| const float e10x = p1.x - p0.x; | ||
| const float e10y = p1.y - p0.y; | ||
| const float e10z = p1.z - p0.z; | ||
| const float e20x = p2.x - p0.x; | ||
| const float e20y = p2.y - p0.y; | ||
| const float e20z = p2.z - p0.z; | ||
|
|
||
| const Float3 faceNormal( | ||
| e10y * e20z - e10z * e20y, | ||
| e10z * e20x - e10x * e20z, | ||
| e10x * e20y - e10y * e20x); | ||
|
|
||
| const float faceLenSq = faceNormal.x * faceNormal.x + faceNormal.y * faceNormal.y + faceNormal.z * faceNormal.z; | ||
| if (faceLenSq <= 1e-20f) | ||
| continue; | ||
|
|
||
| auto accumulateIfNeeded = [&](const uint32_t vertexIx)->void | ||
| { | ||
| if (outNormalNeedsGeneration[static_cast<size_t>(vertexIx)] == 0u) | ||
| return; | ||
| auto& dstNormal = generatedNormals[static_cast<size_t>(vertexIx)]; | ||
| dstNormal.x += faceNormal.x; | ||
| dstNormal.y += faceNormal.y; | ||
| dstNormal.z += faceNormal.z; | ||
| }; | ||
|
|
||
| accumulateIfNeeded(i0); | ||
| accumulateIfNeeded(i1); | ||
| accumulateIfNeeded(i2); | ||
| } | ||
|
|
||
| for (size_t i = 0ull; i < outVertexWriteCount; ++i) | ||
| { | ||
| if (outNormalNeedsGeneration[i] == 0u) | ||
| continue; | ||
|
|
||
| auto normal = generatedNormals[i]; | ||
| const float lenSq = normal.x * normal.x + normal.y * normal.y + normal.z * normal.z; | ||
| if (lenSq > 1e-20f) | ||
| { | ||
| const float invLen = 1.f / std::sqrt(lenSq); | ||
| normal.x *= invLen; | ||
| normal.y *= invLen; | ||
| normal.z *= invLen; | ||
| } | ||
| else | ||
| { | ||
| normal = Float3(0.f, 0.f, 1.f); | ||
| } | ||
| outNormals[i] = normal; | ||
| } | ||
| } |
There was a problem hiding this comment.
don't bother generating normals, I have a polygon geo manipulator fir this I can use later
No description provided.