/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #include "OdaCommon.h" #include "SrTriangleRenderer.h" #include "rendering/SrFrameContext.h" #include "SrTransformationHolder.h" #include #include #include namespace TriangleRendering { OdSrTriangleRenderer::OdSrTriangleRenderer(OdSrTriangleRendererData& data, OdSrFrameContext& frameContext) : m_rendererData(data), m_frameContext(frameContext) {} void OdSrTriangleRenderer::renderTriangle( const OdGePoint3d& pt1, const OdGePoint3d& pt2, const OdGePoint3d& pt3, const OdSrTransformationHolder& transformationHolder, const RenderSettings& settings) { const auto& fb = m_rendererData.frameBuffer(); if (!fb.initialized()) return; const auto& zClipPlanes = m_rendererData.zClipPlanes(); bool zClippingEnabled = zClipPlanes.frontClippingEnabled || zClipPlanes.backClippingEnabled; if (!zClippingEnabled) { renderTriangleNoClip(pt1, pt2, pt3, transformationHolder, settings); return; } const OdGeMatrix3d& worldToEye = transformationHolder.worldToEye(); OdGePoint3d eyePts[3] = { worldToEye * pt1, worldToEye * pt2, worldToEye * pt3 }; OdSrZClipper::ClipVertex clipVerts[3]; for (int i = 0; i < 3; i++) { clipVerts[i].pos = eyePts[i]; const OdGePoint3d* origPts[3] = { &pt1, &pt2, &pt3 }; OdGePoint3d texPt = settings.skipTransform ? *origPts[i] : m_rendererData.textureTransform().transform(worldToEye, *origPts[i]); clipVerts[i].u = texPt.x; clipVerts[i].v = texPt.y; } OdSrZClipper::ClipVertex clippedVerts[6]; int vertCount = OdSrZClipper::clipTriangleZ(clipVerts, clippedVerts, zClipPlanes); if (vertCount == 0) return; const OdGeMatrix3d& eyeToDevice = transformationHolder.eyeToDevice(); for (int i = 1; i < vertCount - 1; i++) { OdGePoint3d screenVerts[3]; screenVerts[0] = clippedVerts[0].pos; screenVerts[1] = clippedVerts[i].pos; screenVerts[2] = clippedVerts[i + 1].pos; for (int j = 0; j < 3; j++) { screenVerts[j].transformBy(eyeToDevice); screenVerts[j].y = fb.invertY((OdInt32)OdRound(screenVerts[j].y)); } OdGePoint3d textureVertices[3] = { OdGePoint3d(clippedVerts[0].u, clippedVerts[0].v, 0), OdGePoint3d(clippedVerts[i].u, clippedVerts[i].v, 0), OdGePoint3d(clippedVerts[i + 1].u, clippedVerts[i + 1].v, 0) }; renderClippedTriangle(screenVerts, textureVertices, settings); } } void OdSrTriangleRenderer::renderClippedTriangle( const OdGePoint3d screenVertices[3], const OdGePoint3d textureVertices[3], const RenderSettings& settings) { TriangleVertex v1((OdUInt32)OdRound(screenVertices[0].x), (OdUInt32)OdRound(screenVertices[0].y), textureVertices[0]); TriangleVertex v2((OdUInt32)OdRound(screenVertices[1].x), (OdUInt32)OdRound(screenVertices[1].y), textureVertices[1]); TriangleVertex v3((OdUInt32)OdRound(screenVertices[2].x), (OdUInt32)OdRound(screenVertices[2].y), textureVertices[2]); TriangleVertex vertices[3] = { v1, v2, v3 }; if (vertices[0].y > vertices[1].y) std::swap(vertices[0], vertices[1]); if (vertices[0].y > vertices[2].y) std::swap(vertices[0], vertices[2]); if (vertices[1].y > vertices[2].y) std::swap(vertices[1], vertices[2]); if (!clipTriangle(vertices)) return; void (OdSrTriangleRenderer::*renderTriangleImpl)(const TriangleVertex[], const RenderSettings&, int); renderTriangleImpl = settings.isPerspectiveProjection ? &OdSrTriangleRenderer::renderTrianglePerspective : &OdSrTriangleRenderer::renderTriangleOrtho; switch (settings.qualityMode) { case RenderQuality::Low: (this->*renderTriangleImpl)(vertices, settings, 2); break; case RenderQuality::Regular: (this->*renderTriangleImpl)(vertices, settings, 1); break; } } /** * Main method for rendering a triangle */ void OdSrTriangleRenderer::renderTriangleNoClip( const OdGePoint3d& pt1, const OdGePoint3d& pt2, const OdGePoint3d& pt3, const OdSrTransformationHolder& transformationHolder, const RenderSettings& settings) { // Check for null buffers const auto& fb = m_rendererData.frameBuffer(); OdGePoint3d screenVertices[3] = { pt1, pt2, pt3 }; for (int i = 0; i < 3; i++) { screenVertices[i].transformBy(transformationHolder.worldToDevice()); screenVertices[i].y = fb.invertY((OdInt32)OdRound(screenVertices[i].y)); } OdGePoint3d textureVertices[3] = { pt1, pt2, pt3 }; // Transform texture coordinates if needed if (!settings.skipTransform) for (int i = 0; i < 3; i++) textureVertices[i] = m_rendererData.textureTransform().transform(transformationHolder.worldToEye(), textureVertices[i]); // Copy vertices for processing TriangleVertex v1((OdUInt32)OdRound(screenVertices[0].x), (OdUInt32)OdRound(screenVertices[0].y), textureVertices[0]); TriangleVertex v2((OdUInt32)OdRound(screenVertices[1].x), (OdUInt32)OdRound(screenVertices[1].y), textureVertices[1]); TriangleVertex v3((OdUInt32)OdRound(screenVertices[2].x), (OdUInt32)OdRound(screenVertices[2].y), textureVertices[2]); TriangleVertex vertices[3] = { v1, v2, v3 }; // Sort vertices by Y for simpler checking if (vertices[0].y > vertices[1].y) std::swap(vertices[0], vertices[1]); if (vertices[0].y > vertices[2].y) std::swap(vertices[0], vertices[2]); if (vertices[1].y > vertices[2].y) std::swap(vertices[1], vertices[2]); // Clip triangle against screen boundaries if (!clipTriangle(vertices)) return; void (OdSrTriangleRenderer::*renderTriangleImpl)(const TriangleVertex[], const RenderSettings&, int); renderTriangleImpl = settings.isPerspectiveProjection ? &OdSrTriangleRenderer::renderTrianglePerspective : &OdSrTriangleRenderer::renderTriangleOrtho; switch (settings.qualityMode) { case RenderQuality::Low: (this->*renderTriangleImpl)(vertices, settings, 2); break; case RenderQuality::Regular: (this->*renderTriangleImpl)(vertices, settings, 1); break; } } /** * Clip triangle against rendering area boundaries * Returns true if triangle is visible * !NB Vertices must be sorted */ bool OdSrTriangleRenderer::clipTriangle(TriangleVertex vertices[3]) { const auto& fb = m_rendererData.frameBuffer(); const auto& clipRegion = m_rendererData.clipRegion(); // Determine effective clipping bounds OdInt32 minClipX, minClipY, maxClipX, maxClipY; if (clipRegion.clipRect.m_min.x != 0 || clipRegion.clipRect.m_max.x != 0 || clipRegion.clipRect.m_min.y != 0 || clipRegion.clipRect.m_max.y != 0) { // Use custom clip region minClipX = clipRegion.clipRect.m_min.x; minClipY = clipRegion.clipRect.m_min.y; maxClipX = clipRegion.clipRect.m_max.x; maxClipY = clipRegion.clipRect.m_max.y; } else { // Use framebuffer bounds as default minClipX = 0; minClipY = 0; maxClipX = fb.m_width - 1; maxClipY = fb.m_height - 1; } // Fast check if triangle is completely outside the clipping area if (vertices[2].y < minClipY || vertices[0].y > maxClipY || (vertices[0].x < minClipX && vertices[1].x < minClipX && vertices[2].x < minClipX) || (vertices[0].x > maxClipX && vertices[1].x > maxClipX && vertices[2].x > maxClipX)) return false; return true; } //============================================================================= // Updated bounding box calculation with clip region support /*static*/ void OdSrTriangleRenderer::getBoundingBoxWithClipping(const TriangleVertex vertices[3], const OdSrTriangleRendererData& rendererData, OdInt32& minX, OdInt32& minY, OdInt32& maxX, OdInt32& maxY) { const auto& frameBuffer = rendererData.frameBuffer(); const auto& clipRegion = rendererData.clipRegion(); // Calculate triangle bounding box OdInt32 triMinX = odmin(odmin(vertices[0].x, vertices[1].x), vertices[2].x); OdInt32 triMinY = odmin(odmin(vertices[0].y, vertices[1].y), vertices[2].y); OdInt32 triMaxX = odmax(odmax(vertices[0].x, vertices[1].x), vertices[2].x); OdInt32 triMaxY = odmax(odmax(vertices[0].y, vertices[1].y), vertices[2].y); // Determine effective clipping bounds OdInt32 clipMinX, clipMinY, clipMaxX, clipMaxY; if (clipRegion.clipRect.m_min.x != 0 || clipRegion.clipRect.m_max.x != 0 || clipRegion.clipRect.m_min.y != 0 || clipRegion.clipRect.m_max.y != 0) { // Use custom clip region clipMinX = clipRegion.clipRect.m_min.x; clipMinY = clipRegion.clipRect.m_min.y; clipMaxX = clipRegion.clipRect.m_max.x; clipMaxY = clipRegion.clipRect.m_max.y; } else { // Use framebuffer bounds clipMinX = 0; clipMinY = 0; clipMaxX = frameBuffer.m_width - 1; clipMaxY = frameBuffer.m_height - 1; } // Intersect triangle bbox with clip bounds minX = odmax(triMinX, clipMinX); minY = odmax(triMinY, clipMinY); maxX = odmin(triMaxX, clipMaxX); maxY = odmin(triMaxY, clipMaxY); } // Edge function - tests if point (x,y) is on the right side of the line passing through a and b template inline float edgeFunction(int ax, int ay, int bx, int by, T x, T y, bool applyTopLeftRule = true) { float edge = float((bx - ax) * (y - ay) - (by - ay) * (x - ax)); const float EDGE_TOLERANCE = 0.5f; // Half-pixel tolerance if (applyTopLeftRule && std::abs(edge) < EDGE_TOLERANCE) { // Use tolerance for boundary detection // Apply top-left rule for boundary pixels // Top edge: horizontal edge going left to right bool isTopEdge = (ay == by) && (ax < bx); // Left edge: edge going downward (not horizontal) bool isLeftEdge = (ay < by); edge = (isTopEdge || isLeftEdge) ? -EDGE_TOLERANCE : EDGE_TOLERANCE; } return edge; } // Updated pixel inside triangle check with top-left rule inline bool isPixelInsideTriangle(float w0, float w1, float w2, bool counterClockwise) { // For CCW triangles: pixel is inside if all edge functions are >= 0 // With top-left rule: boundary pixels (edge == 0) are handled by bias in edgeFunction // For CW triangles: pixel is inside if all edge functions are <= 0 return counterClockwise ? (w0 >= 0 && w1 >= 0 && w2 >= 0) : (w0 <= 0 && w1 <= 0 && w2 <= 0); } static inline bool getRBGA(float lambda0, float lambda1, float lambda2, const ODCOLORREF* colorBuffer, OdUInt32 colorLength, OdUInt8& r, OdUInt8& g, OdUInt8& b, OdUInt8& a); void OdSrTriangleRenderer::renderTrianglePerspective(const TriangleVertex vertices[3], const RenderSettings& settings, int pixelStep) { auto& frameBuffer = m_rendererData.frameBuffer(); const auto& textureBuffer = m_rendererData.textureBuffer(); const auto& depthBuffer = m_rendererData.depthBuffer(); // 1. Calculate the bounding box with clipping support OdInt32 minX, minY, maxX, maxY; getBoundingBoxWithClipping(vertices, m_rendererData, minX, minY, maxX, maxY); // Early rejection for triangles outside the viewport if (minX > maxX || minY > maxY) return; // 2. Preparation for perspective-correct interpolation // Premultiply texture coordinates by w for perspective correction float u0w = vertices[0].u * vertices[0].w; float v0w = vertices[0].v * vertices[0].w; float u1w = vertices[1].u * vertices[1].w; float v1w = vertices[1].v * vertices[1].w; float u2w = vertices[2].u * vertices[2].w; float v2w = vertices[2].v * vertices[2].w; // 3. Optimization: precomputation for edge function int A01 = vertices[0].y - vertices[1].y; int B01 = vertices[1].x - vertices[0].x; int A12 = vertices[1].y - vertices[2].y; int B12 = vertices[2].x - vertices[1].x; int A20 = vertices[2].y - vertices[0].y; int B20 = vertices[0].x - vertices[2].x; // 4. Calculate the total triangle area float area = edgeFunction(vertices[0].x, vertices[0].y, vertices[1].x, vertices[1].y, vertices[2].x, vertices[2].y, false); // the rule is not applied to calc the area // Check if triangle is too small (degenerate) if (std::abs(area) < 0.00001f) return; // If area is negative, flip winding order bool counterClockwise = area > 0; float absArea = std::abs(area); // 5. Check if texture is power-of-two for faster texture coordinate calculation bool isPow2Texture = false; int uMask = 0, vMask = 0; if (textureBuffer.initialized()) { isPow2Texture = (textureBuffer.m_width > 0 && (textureBuffer.m_width & (textureBuffer.m_width - 1)) == 0) && (textureBuffer.m_height > 0 && (textureBuffer.m_height & (textureBuffer.m_height - 1)) == 0); uMask = textureBuffer.m_width - 1; vMask = textureBuffer.m_height - 1; } //// Increase step for larger triangles //if ((maxX - minX) > 20 || (maxY - minY) > 20) pixelStep++; // Adjust starting position to align with step grid minX += (minX % pixelStep); minY += (minY % pixelStep); // 7. Calculate initial values for the top-left corner float w0_row = edgeFunction(vertices[1].x, vertices[1].y, vertices[2].x, vertices[2].y, (float)minX + 0.5f, (float)minY + 0.5f); float w1_row = edgeFunction(vertices[2].x, vertices[2].y, vertices[0].x, vertices[0].y, (float)minX + 0.5f, (float)minY + 0.5f); float w2_row = edgeFunction(vertices[0].x, vertices[0].y, vertices[1].x, vertices[1].y, (float)minX + 0.5f, (float)minY + 0.5f); // 8. Iterate through rows and columns of pixels, with pixel stepping for (int y = minY; y <= maxY; y += pixelStep) { // Copy initial values for current row float w0 = w0_row; float w1 = w1_row; float w2 = w2_row; for (int x = minX; x <= maxX; x += pixelStep, w0 += A12 * pixelStep, w1 += A20 * pixelStep, w2 += A01 * pixelStep) { // Check if pixel is inside triangle based on winding order if (!((counterClockwise && w0 >= 0 && w1 >= 0 && w2 >= 0) || (!counterClockwise && w0 <= 0 && w1 <= 0 && w2 <= 0))) continue; // Normalize barycentric coordinates float lambda0 = std::abs(w0) / absArea; float lambda1 = std::abs(w1) / absArea; float lambda2 = std::abs(w2) / absArea; // 9. Calculate perspective-correct interpolation factors // Calculate 1/w at this point using barycentric coordinates float invW = lambda0 * vertices[0].w + lambda1 * vertices[1].w + lambda2 * vertices[2].w; float depth = 1.0f / invW; // This is the actual depth (z-value) // 10. Z-testing if enabled if (settings.enableZTest()) { int pixelIndex = y * frameBuffer.m_width + x; OdUInt8 depthValue = static_cast(depth * 255.0f); if (depthValue > depthBuffer.scanLines[pixelIndex]) continue; // Fail depth test // Store new depth value depthBuffer.scanLines[pixelIndex] = depthValue; } // 11. Perspective-correct texture coordinate interpolation // Use premultiplied coordinates and then divide by interpolated W float u = (lambda0 * u0w + lambda1 * u1w + lambda2 * u2w) * depth; float v = (lambda0 * v0w + lambda1 * v1w + lambda2 * v2w) * depth; // 12. Determine pixel color OdUInt8 r, g, b, a; if (textureBuffer.initialized()) { int tx, ty; // Fast texture coordinate calculation for power-of-two textures if (isPow2Texture) { tx = ((int)(u * textureBuffer.m_width)) & uMask; ty = ((int)(v * textureBuffer.m_height)) & vMask; } else { tx = (int)(u * textureBuffer.m_width); ty = (int)(v * textureBuffer.m_height); // Clamp to texture bounds odmin(textureBuffer.m_width - 1, tx); //tx = odmax(0, odmin(textureBuffer.m_width - 1, tx)); //ty = std::max(0, std::min(textureBuffer.m_height - 1, ty)); } // Direct texture access textureBuffer.getPixel(tx, ty, r, g, b, a); } else { OdUInt32 colorLength; const ODCOLORREF* colorBuffer = m_rendererData.triangleColorBuffer(colorLength); getRBGA(lambda0, lambda1, lambda2, colorBuffer, colorLength, r, g, b, a); } // Optimized pixel block filling fillPixelBlockSmart(x, y, maxX, maxY, pixelStep, r, g, b, a); } // Incrementally update for next row with pixel stepping w0_row += B12 * pixelStep; w1_row += B20 * pixelStep; w2_row += B01 * pixelStep; } } static inline bool getRBGA(float lambda0, float lambda1, float lambda2, const ODCOLORREF* colorBuffer, OdUInt32 colorLength, OdUInt8& r, OdUInt8& g, OdUInt8& b, OdUInt8& a) { if (colorLength == 1) { r = ODGETRED(colorBuffer[0]); g = ODGETGREEN(colorBuffer[0]); b = ODGETBLUE(colorBuffer[0]); a = ODGETALPHA(colorBuffer[0]); return true; } else if (colorLength == 3) { r = static_cast((lambda0)*ODGETRED(colorBuffer[0]) + (lambda1)*ODGETRED(colorBuffer[1]) + (lambda2)*ODGETRED(colorBuffer[2])); g = static_cast((lambda0)*ODGETGREEN(colorBuffer[0]) + (lambda1)*ODGETGREEN(colorBuffer[1]) + (lambda2)*ODGETGREEN(colorBuffer[2])); b = static_cast((lambda0)*ODGETBLUE(colorBuffer[0]) + (lambda1)*ODGETBLUE(colorBuffer[1]) + (lambda2)*ODGETBLUE(colorBuffer[2])); // Interpolate alpha components OdUInt8 a0 = ODGETALPHA(colorBuffer[0]); OdUInt8 a1 = ODGETALPHA(colorBuffer[1]); OdUInt8 a2 = ODGETALPHA(colorBuffer[2]); a = static_cast((lambda0)*a0 + (lambda1)*a1 + (lambda2)*a2); return true; } else { ODA_FAIL(); return false; } } void OdSrTriangleRenderer::renderTriangleOrtho(const TriangleVertex vertices[3], const RenderSettings&, int pixelStep) { const auto& textureBuffer = m_rendererData.textureBuffer(); OdUInt32 colorLength; const ODCOLORREF* colorBuffer = m_rendererData.triangleColorBuffer(colorLength); bool useFastMethod = !textureBuffer.initialized() && colorBuffer && colorLength == 1 && ODGETALPHA(colorBuffer[0]) == 0xFF; if (useFastMethod) { renderTriangleScanline(vertices[0].x, vertices[0].y, vertices[1].x, vertices[1].y, vertices[2].x, vertices[2].y, colorBuffer[0]); return; } // 1. Calculate the bounding box with clipping support OdInt32 minX, minY, maxX, maxY; getBoundingBoxWithClipping(vertices, m_rendererData, minX, minY, maxX, maxY); // Early rejection for triangles outside the viewport if (minX > maxX || minY > maxY) return; // 2. Preparation for texture coordinates float u0 = vertices[0].u, v0 = vertices[0].v; float u1 = vertices[1].u, v1 = vertices[1].v; float u2 = vertices[2].u, v2 = vertices[2].v; // 3. Optimization: precomputation for edge function int A01 = vertices[0].y - vertices[1].y; int B01 = vertices[1].x - vertices[0].x; int A12 = vertices[1].y - vertices[2].y; int B12 = vertices[2].x - vertices[1].x; int A20 = vertices[2].y - vertices[0].y; int B20 = vertices[0].x - vertices[2].x; // 4. Calculate the total triangle area float area = edgeFunction(vertices[0].x, vertices[0].y, vertices[1].x, vertices[1].y, vertices[2].x, vertices[2].y, false); // Check if triangle is too small (degenerate) if (fabs(area) < 0.00001f) return; // If area is negative, flip winding order bool counterClockwise = area > 0; float absArea = std::abs(area); // 5. Check if texture is power-of-two for faster texture coordinate calculation bool isPow2Texture = false; int uMask = 0, vMask = 0; if (textureBuffer.initialized()) { isPow2Texture = (textureBuffer.m_width > 0 && (textureBuffer.m_width & (textureBuffer.m_width - 1)) == 0) && (textureBuffer.m_height > 0 && (textureBuffer.m_height & (textureBuffer.m_height - 1)) == 0); uMask = textureBuffer.m_width - 1; vMask = textureBuffer.m_height - 1; } //// Increase step for larger triangles //if ((maxX - minX) > 20 || (maxY - minY) > 20) pixelStep++; // Adjust starting position to align with step grid minX += (minX % pixelStep); minY += (minY % pixelStep); // 7. Calculate initial values for the top-left corner float w0_row = edgeFunction(vertices[1].x, vertices[1].y, vertices[2].x, vertices[2].y, (float)minX + 0.5f, (float)minY + 0.5f); float w1_row = edgeFunction(vertices[2].x, vertices[2].y, vertices[0].x, vertices[0].y, (float)minX + 0.5f, (float)minY + 0.5f); float w2_row = edgeFunction(vertices[0].x, vertices[0].y, vertices[1].x, vertices[1].y, (float)minX + 0.5f, (float)minY + 0.5f); // 8. Iterate through rows and columns of pixels, with pixel stepping for (int y = minY; y <= maxY; y += pixelStep) { // Copy initial values for current row float w0 = w0_row; float w1 = w1_row; float w2 = w2_row; for (int x = minX; x <= maxX; x += pixelStep, w0 += A12 * pixelStep, w1 += A20 * pixelStep, w2 += A01 * pixelStep) { // Check if pixel is inside triangle based on winding order if (!((counterClockwise && w0 >= 0 && w1 >= 0 && w2 >= 0) || (!counterClockwise && w0 <= 0 && w1 <= 0 && w2 <= 0))) continue; // Normalize barycentric coordinates float lambda0 = std::abs(w0) / absArea; float lambda1 = std::abs(w1) / absArea; float lambda2 = std::abs(w2) / absArea; // 9. Linear interpolation of texture coordinates (no perspective correction for speed) float u = lambda0 * u0 + lambda1 * u1 + lambda2 * u2; float v = lambda0 * v0 + lambda1 * v1 + lambda2 * v2; // 10. Determine pixel color OdUInt8 r, g, b, a; if (textureBuffer.initialized()) { int tx, ty; // Fast texture coordinate calculation for power-of-two textures if (isPow2Texture) { tx = ((int)(u * textureBuffer.m_width)) & uMask; ty = ((int)(v * textureBuffer.m_height)) & vMask; } else { tx = (int)(u * textureBuffer.m_width); ty = (int)(v * textureBuffer.m_height); // Clamp to texture bounds tx = odmax(0, odmin(textureBuffer.m_width - 1, tx)); ty = odmax(0, odmin(textureBuffer.m_height - 1, ty)); } textureBuffer.getPixel(tx, ty, r, g, b, a); } else getRBGA(lambda0, lambda1, lambda2, colorBuffer, colorLength, r, g, b, a); // Optimized pixel block filling fillPixelBlockSmart(x, y, maxX, maxY, pixelStep, r, g, b, a); } // Incrementally update for next row with pixel stepping w0_row += B12 * pixelStep; w1_row += B20 * pixelStep; w2_row += B01 * pixelStep; } } void OdSrTriangleRenderer::renderTriangleScanline(OdInt32 x1, OdInt32 y1, OdInt32 x2, OdInt32 y2, OdInt32 x3, OdInt32 y3, ODCOLORREF col) { OdInt32 h = m_rendererData.frameBuffer().getHeight(); //to be refactored //workaround to fix non-inclusion of a triangle's boundary OdInt32 y1inv = (h - 1) - y1; OdInt32 y2inv = (h - 1) - y2; OdInt32 y3inv = (h - 1) - y3; m_frameContext.draw_general_line( x1, y1inv, x2, y2inv, 0, col); m_frameContext.draw_general_line( x2, y2inv, x3, y3inv, 0, col); m_frameContext.draw_general_line( x1, y1inv, x3, y3inv, 0, col); // must be y1 = min(y), y3 = max(y) if (y2 < y1) { std::swap(x1, x2); std::swap(y1, y2); } if (y3 < y1) { std::swap(x3, x1); std::swap(y3, y1); } if (y3 < y2) { std::swap(x3, x2); std::swap(y3, y2); } if (y1 == y3) { int minX(x1), maxX(x2); if (minX > x2) minX = x2; if (minX > x3) minX = x3; if (maxX < x2) maxX = x2; if (maxX < x3) maxX = x3; drawScanline(y1, minX, maxX, col); } else { for (int sy = y1; sy <= y3; sy++) { int xx1, xx2; xx1 = x1 + (sy - y1) * (x3 - x1) / (y3 - y1); if (sy < y2) xx2 = x1 + (sy - y1) * (x2 - x1) / (y2 - y1); else { if (y3 == y2) xx2 = x2; else xx2 = x2 + (sy - y2) * (x3 - x2) / (y3 - y2); } drawScanline(sy, xx1, xx2, col); } } } };