| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- // zlib open source license
- //
- // Copyright (c) 2017 to 2019 David Forsgren Piuva
- //
- // This software is provided 'as-is', without any express or implied
- // warranty. In no event will the authors be held liable for any damages
- // arising from the use of this software.
- //
- // Permission is granted to anyone to use this software for any purpose,
- // including commercial applications, and to alter it and redistribute it
- // freely, subject to the following restrictions:
- //
- // 1. The origin of this software must not be misrepresented; you must not
- // claim that you wrote the original software. If you use this software
- // in a product, an acknowledgment in the product documentation would be
- // appreciated but is not required.
- //
- // 2. Altered source versions must be plainly marked as such, and must not be
- // misrepresented as being the original software.
- //
- // 3. This notice may not be removed or altered from any source
- // distribution.
- #include "ITriangle2D.h"
- #include "../math/scalar.h"
- #include "../../math/FMatrix2x2.h"
- #include <algorithm>
- using namespace dsr;
- IRect dsr::getTriangleBound(LVector2D a, LVector2D b, LVector2D c) {
- int32_t rX1 = (a.x + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
- int32_t rY1 = (a.y + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
- int32_t rX2 = (b.x + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
- int32_t rY2 = (b.y + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
- int32_t rX3 = (c.x + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
- int32_t rY3 = (c.y + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
- int leftBound = min(rX1, rX2, rX3) - 1;
- int topBound = min(rY1, rY2, rY3) - 1;
- int rightBound = max(rX1, rX2, rX3) + 1;
- int bottomBound = max(rY1, rY2, rY3) + 1;
- return IRect(leftBound, topBound, rightBound - leftBound, bottomBound - topBound);
- }
- FVector3D dsr::getAffineWeight(const FVector2D& cornerA, const FVector2D& cornerB, const FVector2D& cornerC, const FVector2D& point) {
- FMatrix2x2 offsetToWeight = inverse(FMatrix2x2(cornerB - cornerA, cornerC - cornerA));
- FVector2D weightBC = offsetToWeight.transform(point - cornerA);
- return FVector3D(1.0f - (weightBC.x + weightBC.y), weightBC.x, weightBC.y);
- }
- ITriangle2D::ITriangle2D(ProjectedPoint posA, ProjectedPoint posB, ProjectedPoint posC)
- : position{posA, posB, posC}, wholeBound(getTriangleBound(this->position[0].flat, this->position[1].flat, this->position[2].flat)) {}
- // Will produce weird results if called on a triangle that needs clipping against the near plane
- bool ITriangle2D::isFrontfacing() const {
- LVector2D flatA = this->position[0].flat;
- LVector2D flatB = this->position[1].flat;
- LVector2D flatC = this->position[2].flat;
- return ((flatC.x - flatA.x) * (flatB.y - flatA.y)) + ((flatC.y - flatA.y) * (flatA.x - flatB.x)) < 0;
- }
- inline static void cutRight(int32_t& rightBound, int32_t value) {
- rightBound = min(rightBound, value);
- }
- inline static void cutLeft(int32_t& leftBound, int32_t value) {
- leftBound = max(leftBound, value);
- }
- IRect ITriangle2D::getAlignedRasterBound(const IRect& clipBound, int alignX, int alignY) const {
- IRect unaligned = IRect::cut(this->wholeBound, clipBound);
- int alignedTop = roundDown(unaligned.top(), 2);
- int alignedBottom = roundUp(unaligned.bottom(), 2);
- return IRect(unaligned.left(), alignedTop, unaligned.width(), alignedBottom - alignedTop);
- }
- int ITriangle2D::getBufferSize(const IRect& clipBound, int alignX, int alignY) const {
- if (IRect::overlaps(this->wholeBound, clipBound)) {
- IRect rasterBound = this->getAlignedRasterBound(clipBound, alignX, alignY);
- return rasterBound.bottom() - rasterBound.top();
- } else {
- return 0;
- }
- }
- static void cutConvexEdge(const LVector2D& startPoint, const LVector2D& endPoint, RowInterval* rows, const IRect& clipBound) {
- int leftBound = clipBound.left();
- int topBound = clipBound.top();
- int rightBound = clipBound.right();
- int bottomBound = clipBound.bottom();
- // Get origin in units
- int64_t originX = constants::unitsPerHalfPixel + clipBound.left() * constants::unitsPerPixel;
- int64_t originY = constants::unitsPerHalfPixel + clipBound.top() * constants::unitsPerPixel;
- // To turn x > 0 into x >= 0 without branching, just compare to -1 instead as it is equivalent for integers.
- int64_t threshold = (startPoint.x > endPoint.x || (startPoint.x == endPoint.x && startPoint.y > endPoint.y)) ? -1 : 0;
- // Get normals for each edge
- int64_t normalX = endPoint.y - startPoint.y;
- int64_t normalY = startPoint.x - endPoint.x;
- // Get partial derivatives along edge directions in screen space
- int64_t offsetX = normalX * constants::unitsPerPixel;
- int64_t offsetY = normalY * constants::unitsPerPixel;
- // Take the dot product to get an initial weight without normalization.
- int64_t valueOrigin = ((originX - startPoint.x) * normalX) + ((originY - startPoint.y) * normalY);
- // Get vertical bound
- if (normalX != 0) {
- int64_t valueRow = valueOrigin;
- // Proof for the limit variable:
- // Find the highest x for the left side where offsetX < 0 or the lowest x for the right side where offsetX > 0
- // x must satisfy the equation valueRow + (offsetX * (x - leftBound)) > threshold
- // offsetX * (x - leftBound) > threshold - valueRow
- // (offsetX * x) - (offsetX * leftBound) > threshold - valueRow
- // offsetX * x > threshold - valueRow + (offsetX * leftBound)
- // offsetX * x > limit
- int64_t limit = threshold - valueRow + (offsetX * leftBound);
- if (normalX < 0) { // Left
- for (int32_t y = topBound; y < bottomBound; y++) {
- int32_t rowIndex = y - topBound;
- // Find the highest x where offsetX * x > limit
- int32_t leftSide = min(max(leftBound, (int32_t)((limit + 1) / offsetX + 1)), rightBound);
- cutLeft(rows[rowIndex].left, leftSide);
- limit -= offsetY;
- }
- } else { // Right
- for (int32_t y = topBound; y < bottomBound; y++) {
- int32_t rowIndex = y - topBound;
- // Find the lowest x where offsetX * x > limit
- int32_t rightSide = min(max(leftBound, (int32_t)(limit / offsetX + 1)), rightBound);
- cutRight(rows[rowIndex].right, rightSide);
- limit -= offsetY;
- }
- }
- } else if (normalY != 0) {
- // Remove pixel rows that are outside of a fully horizontal edge
- int64_t valueRow = valueOrigin + (offsetY * (topBound - topBound));
- for (int32_t y = topBound; y < bottomBound; y++) {
- int32_t rowIndex = y - topBound;
- if (valueRow > threshold) { // If outside of the current edge
- rows[rowIndex].left = rightBound;
- rows[rowIndex].right = leftBound;
- }
- valueRow = valueRow + offsetY;
- }
- }
- // Zero length edges will make the whole triangle invisible because the two other edges must be exact opposites removing all remaining pixels
- }
- void dsr::rasterizeTriangle(const LVector2D& cornerA, const LVector2D& cornerB, const LVector2D& cornerC, RowInterval* rows, const IRect& clipBound) {
- LVector2D position[3] = {cornerA, cornerB, cornerC};
- if (cornerA == cornerB || cornerB == cornerC || cornerC == cornerA) {
- // Empty case with less than three separate corners
- for (int64_t rowIndex = 0; rowIndex < clipBound.height(); rowIndex++) {
- rows[rowIndex].left = clipBound.right();
- rows[rowIndex].right = clipBound.left();
- }
- } else {
- // Start with a full bounding box
- for (int64_t rowIndex = 0; rowIndex < clipBound.height(); rowIndex++) {
- rows[rowIndex].left = clipBound.left();
- rows[rowIndex].right = clipBound.right();
- }
- // Cut away pixels from each side
- for (int64_t i = 0; i < 3; i++) {
- cutConvexEdge(position[i], position[(i + 1) % 3], rows, clipBound);
- }
- }
- }
- void ITriangle2D::getShape(int& startRow, RowInterval* rows, const IRect& clipBound, int alignX, int alignY) const {
- // TODO: Move alignment to the render core where it belongs so that all alignX and alignY arguments are removed from the triangle
- IRect alignedBound = this->getAlignedRasterBound(clipBound, alignX, alignY);
- startRow = alignedBound.top();
- rasterizeTriangle(this->position[0].flat, this->position[1].flat, this->position[2].flat, rows, alignedBound);
- }
- Projection ITriangle2D::getProjection(bool perspective) const {
- return this->getProjection(FVector3D(0.0f, 1.0f, 0.0f), FVector3D(0.0f, 0.0f, 1.0f), perspective);
- }
- Projection ITriangle2D::getProjection(const FVector3D& subB, const FVector3D& subC, bool perspective) const {
- /*
- TODO: Find out why this implementation gives crap precision
- FVector2D pointA = FVector2D(this->position[0].is.x, this->position[0].is.y);
- FVector2D pointB = FVector2D(this->position[1].is.x, this->position[1].is.y);
- FVector2D pointC = FVector2D(this->position[2].is.x, this->position[2].is.y);
- FVector3D targetWeight = getAffineWeight(pointA, pointB, pointC, FVector2D(0.0f, 0.0f));
- FVector3D affineWeightDx = getAffineWeight(pointA, pointB, pointC, FVector2D(1.0f, 0.0f)) - targetWeight;
- FVector3D affineWeightDy = getAffineWeight(pointA, pointB, pointC, FVector2D(0.0f, 1.0f)) - targetWeight;
- */
- // Get offsets
- FVector3D offsetX, offsetY;
- for (int i = 0; i < 3; i++) {
- int j = (i + 1) % 3; // End
- FVector2D posI = this->position[i].is;
- FVector2D posJ = this->position[j].is;
- // Get offsets for each edge
- offsetX[i] = posJ.y - posI.y;
- offsetY[i] = posI.x - posJ.x;
- }
- // Get the maximum values along the offsets for normalization
- FVector3D weightMultiplier;
- for (int32_t i = 0; i < 3; i++) {
- int o = (i + 2) % 3;
- // Take the same kind of dot product from the point that is furthest away from the edge for normalization.
- float otherSideValue = ((this->position[o].is.x - this->position[i].is.x) * offsetX[i])
- + ((this->position[o].is.y - this->position[i].is.y) * offsetY[i]);
- if (otherSideValue == 0.0f) {
- weightMultiplier[o] = 0.0f;
- } else {
- weightMultiplier[o] = 1.0f / otherSideValue;
- }
- }
- // Get normal from weight multiplier and offset
- FVector3D normalX, normalY;
- for (int i = 0; i < 3; i++) {
- normalX[i] = offsetX[i] * weightMultiplier[i];
- normalY[i] = offsetY[i] * weightMultiplier[i];
- }
- // Sample the weight of each corner at the upper left corner of the target image
- FVector3D targetWeight;
- for (int32_t i = 0; i < 3; i++) {
- int o = (i + 2) % 3;
- // Take the dot product to get a normalized weight
- targetWeight[o] = this->position[i].is.x * -normalX[i] + this->position[i].is.y * -normalY[i];
- }
- // In order to calculate the perspective corrected vertex weights, we must first linearly iterate over the affine weights.
- // Calculate affine weight derivatives for vertex indices from edge indices.
- FVector3D affineWeightDx, affineWeightDy;
- affineWeightDx.x = normalX.y;
- affineWeightDx.y = normalX.z;
- affineWeightDx.z = normalX.x;
- affineWeightDy.x = normalY.y;
- affineWeightDy.y = normalY.z;
- affineWeightDy.z = normalY.x;
- if (!perspective) {
- // Get the linear depth
- FVector3D W(this->position[0].cs.z, this->position[1].cs.z, this->position[2].cs.z);
- // Get the affine weights of the first pixel
- FVector3D pTargetWeight;
- pTargetWeight.x = W.x * targetWeight.x + W.y * targetWeight.y + W.z * targetWeight.z;
- pTargetWeight.y = targetWeight.x * subB.x + targetWeight.y * subB.y + targetWeight.z * subB.z;
- pTargetWeight.z = targetWeight.x * subC.x + targetWeight.y * subC.y + targetWeight.z * subC.z;
- // Do the same for derivatives
- FVector3D pWeightDx, pWeightDy;
- // TODO: Use interpolateUsingAffineWeight
- pWeightDx.x = W.x * affineWeightDx.x + W.y * affineWeightDx.y + W.z * affineWeightDx.z;
- pWeightDx.y = affineWeightDx.x * subB.x + affineWeightDx.y * subB.y + affineWeightDx.z * subB.z;
- pWeightDx.z = affineWeightDx.x * subC.x + affineWeightDx.y * subC.y + affineWeightDx.z * subC.z;
- pWeightDy.x = W.x * affineWeightDy.x + W.y * affineWeightDy.y + W.z * affineWeightDy.z;
- pWeightDy.y = affineWeightDy.x * subB.x + affineWeightDy.y * subB.y + affineWeightDy.z * subB.z;
- pWeightDy.z = affineWeightDy.x * subC.x + affineWeightDy.y * subC.y + affineWeightDy.z * subC.z;
- // Store the orthogonal vertex weight projection in the shape head
- return Projection(true, pTargetWeight, pWeightDx, pWeightDy);
- } else {
- // Calculate 1 / W for each corner so that depth and vertex weights can be interpolated in a scale where everything is divided by W.
- // This is because a linear interpolation in screen space can only produce affine projections that does not work for multiple depths with perspective.
- FVector3D IW(1.0f / this->position[0].cs.z, 1.0f / this->position[1].cs.z, 1.0f / this->position[2].cs.z);
- // Calculate the first depth divided weights needed for perspective correction.
- // Default W is the linear depth in camera space which everything in the space is divided by.
- // Default U is 1 for the second corner and 0 for all others.
- // Default V is 1 for the third corner and 0 for all others.
- // The first corner's weight can be calculated from the other weights as 1 - (U + V).
- // The U and V vertex weights are locked to a pre-defined pattern because texture coordinates and colors can later be interpolated from them using any values.
- // |1, U1, V1| |1, 0, 0|
- // |1, U2, V2| = |1, 1, 0|
- // |1, U3, V3| |1, 0, 1|
- // Create a matrix containing (1/W, U/W, V/W) for each corner.
- // Rows represent corners and columns represent the different weights.
- // |1/W1| |1, 0, 0| |1/W1, 0, 0 |
- // P = |1/W2| x |1, 1, 0| = |1/W2, 1/W2, 0 |
- // |1/W3| |1, 0, 1| |1/W3, 0, 1/W3|
- // In order to efficiently loop over (1/W, U/W, V/W) for each pixel, we need to calculate the values for the first pixels and getting their derivatives.
- // To get the first pixel's depth divided weights, we multiply the matrix P with the affine vertex weights for each corner.
- // It is okay to linearly interpolate in the depth divided space because the projected 2D coordinate on the screen is also divided by the linear depth W.
- // Calculate P * affineWeight to get the depth divided weights of the first pixel
- FVector3D pTargetWeight;
- pTargetWeight.x = IW.x * targetWeight.x + IW.y * targetWeight.y + IW.z * targetWeight.z;
- pTargetWeight.y = IW.x * targetWeight.x * subB.x + IW.y * targetWeight.y * subB.y + IW.z * targetWeight.z * subB.z;
- pTargetWeight.z = IW.x * targetWeight.x * subC.x + IW.y * targetWeight.y * subC.y + IW.z * targetWeight.z * subC.z;
- // Do the depth division on derivatives too
- FVector3D pWeightDx, pWeightDy;
- // TODO: Reuse multiplications by multiplying IW with affineWeightDx and affineWeightDy first
- // TODO: Use 3x3 matrices to make the code less error prone
- pWeightDx.x = IW.x * affineWeightDx.x + IW.y * affineWeightDx.y + IW.z * affineWeightDx.z;
- pWeightDx.y = IW.x * affineWeightDx.x * subB.x + IW.y * affineWeightDx.y * subB.y + IW.z * affineWeightDx.z * subB.z;
- pWeightDx.z = IW.x * affineWeightDx.x * subC.x + IW.y * affineWeightDx.y * subC.y + IW.z * affineWeightDx.z * subC.z;
- pWeightDy.x = IW.x * affineWeightDy.x + IW.y * affineWeightDy.y + IW.z * affineWeightDy.z;
- pWeightDy.y = IW.x * affineWeightDy.x * subB.x + IW.y * affineWeightDy.y * subB.y + IW.z * affineWeightDy.z * subB.z;
- pWeightDy.z = IW.x * affineWeightDy.x * subC.x + IW.y * affineWeightDy.y * subC.y + IW.z * affineWeightDy.z * subC.z;
- // Store the perspective vertex weight projection in the shape head
- return Projection(false, pTargetWeight, pWeightDx, pWeightDy);
- }
- }
|