ITriangle2D.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2017 to 2019 David Forsgren Piuva
  4. //
  5. // This software is provided 'as-is', without any express or implied
  6. // warranty. In no event will the authors be held liable for any damages
  7. // arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it
  11. // freely, subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented; you must not
  14. // claim that you wrote the original software. If you use this software
  15. // in a product, an acknowledgment in the product documentation would be
  16. // appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such, and must not be
  19. // misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source
  22. // distribution.
  23. #include "ITriangle2D.h"
  24. #include "../math/scalar.h"
  25. #include "../math/FMatrix2x2.h"
  26. #include <algorithm>
  27. using namespace dsr;
  28. IRect dsr::getTriangleBound(LVector2D a, LVector2D b, LVector2D c) {
  29. int32_t rX1 = (a.x + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
  30. int32_t rY1 = (a.y + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
  31. int32_t rX2 = (b.x + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
  32. int32_t rY2 = (b.y + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
  33. int32_t rX3 = (c.x + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
  34. int32_t rY3 = (c.y + constants::unitsPerHalfPixel) / constants::unitsPerPixel;
  35. int leftBound = std::min(std::min(rX1, rX2), rX3) - 1;
  36. int topBound = std::min(std::min(rY1, rY2), rY3) - 1;
  37. int rightBound = std::max(std::max(rX1, rX2), rX3) + 1;
  38. int bottomBound = std::max(std::max(rY1, rY2), rY3) + 1;
  39. return IRect(leftBound, topBound, rightBound - leftBound, bottomBound - topBound);
  40. }
  41. FVector3D dsr::getAffineWeight(const FVector2D& cornerA, const FVector2D& cornerB, const FVector2D& cornerC, const FVector2D& point) {
  42. FMatrix2x2 offsetToWeight = inverse(FMatrix2x2(cornerB - cornerA, cornerC - cornerA));
  43. FVector2D weightBC = offsetToWeight.transform(point - cornerA);
  44. return FVector3D(1.0f - (weightBC.x + weightBC.y), weightBC.x, weightBC.y);
  45. }
  46. ITriangle2D::ITriangle2D(ProjectedPoint posA, ProjectedPoint posB, ProjectedPoint posC)
  47. : position{posA, posB, posC}, wholeBound(getTriangleBound(this->position[0].flat, this->position[1].flat, this->position[2].flat)) {}
  48. // Will produce weird results if called on a triangle that needs clipping against the near plane
  49. bool ITriangle2D::isFrontfacing() const {
  50. LVector2D flatA = this->position[0].flat;
  51. LVector2D flatB = this->position[1].flat;
  52. LVector2D flatC = this->position[2].flat;
  53. return ((flatC.x - flatA.x) * (flatB.y - flatA.y)) + ((flatC.y - flatA.y) * (flatA.x - flatB.x)) < 0;
  54. }
  55. #define INSIDE(VALUE,TRESH) !((VALUE)[0] > (TRESH)[0] || (VALUE)[1] > (TRESH)[1] || (VALUE)[2] > (TRESH)[2])
  56. inline static void cutRight(int32_t& rightBound, int32_t value) {
  57. rightBound = std::min(rightBound, value);
  58. }
  59. inline static void cutLeft(int32_t& leftBound, int32_t value) {
  60. leftBound = std::max(leftBound, value);
  61. }
  62. IRect ITriangle2D::getAlignedRasterBound(const IRect& clipBound, int alignX, int alignY) const {
  63. IRect unaligned = IRect::cut(this->wholeBound, clipBound);
  64. int alignedTop = roundDown(unaligned.top(), 2);
  65. int alignedBottom = roundUp(unaligned.bottom(), 2);
  66. return IRect(unaligned.left(), alignedTop, unaligned.width(), alignedBottom - alignedTop);
  67. }
  68. int ITriangle2D::getBufferSize(const IRect& clipBound, int alignX, int alignY) const {
  69. if (IRect::overlaps(this->wholeBound, clipBound)) {
  70. IRect rasterBound = this->getAlignedRasterBound(clipBound, alignX, alignY);
  71. return rasterBound.bottom() - rasterBound.top();
  72. } else {
  73. return 0;
  74. }
  75. }
  76. static void cutConvexEdge(const LVector2D& startPoint, const LVector2D& endPoint, RowInterval* rows, const IRect& clipBound) {
  77. int leftBound = clipBound.left();
  78. int topBound = clipBound.top();
  79. int rightBound = clipBound.right();
  80. int bottomBound = clipBound.bottom();
  81. // Get origin in units
  82. int64_t originX = constants::unitsPerHalfPixel + clipBound.left() * constants::unitsPerPixel;
  83. int64_t originY = constants::unitsPerHalfPixel + clipBound.top() * constants::unitsPerPixel;
  84. // To turn x > 0 into x >= 0 without branching, just compare to -1 instead as it is equivalent for integers.
  85. int64_t threshold = (startPoint.x > endPoint.x || (startPoint.x == endPoint.x && startPoint.y > endPoint.y)) ? -1 : 0;
  86. // Get normals for each edge
  87. int64_t normalX = endPoint.y - startPoint.y;
  88. int64_t normalY = startPoint.x - endPoint.x;
  89. // Get partial derivatives along edge directions in screen space
  90. int64_t offsetX = normalX * constants::unitsPerPixel;
  91. int64_t offsetY = normalY * constants::unitsPerPixel;
  92. // Take the dot product to get an initial weight without normalization.
  93. int64_t valueOrigin = ((originX - startPoint.x) * normalX) + ((originY - startPoint.y) * normalY);
  94. // Get vertical bound
  95. if (normalX != 0) {
  96. int64_t valueRow = valueOrigin;
  97. // Proof for the limit variable:
  98. // Find the highest x for the left side where offsetX < 0 or the lowest x for the right side where offsetX > 0
  99. // x must satisfy the equation valueRow + (offsetX * (x - leftBound)) > threshold
  100. // offsetX * (x - leftBound) > threshold - valueRow
  101. // (offsetX * x) - (offsetX * leftBound) > threshold - valueRow
  102. // offsetX * x > threshold - valueRow + (offsetX * leftBound)
  103. // offsetX * x > limit
  104. int64_t limit = threshold - valueRow + (offsetX * leftBound);
  105. if (normalX < 0) { // Left
  106. for (int32_t y = topBound; y < bottomBound; y++) {
  107. int32_t rowIndex = y - topBound;
  108. // Find the highest x where offsetX * x > limit
  109. int32_t leftSide = std::min(std::max(leftBound, (int32_t)((limit + 1) / offsetX + 1)), rightBound);
  110. cutLeft(rows[rowIndex].left, leftSide);
  111. limit -= offsetY;
  112. }
  113. } else { // Right
  114. for (int32_t y = topBound; y < bottomBound; y++) {
  115. int32_t rowIndex = y - topBound;
  116. // Find the lowest x where offsetX * x > limit
  117. int32_t rightSide = std::min(std::max(leftBound, (int32_t)(limit / offsetX + 1)), rightBound);
  118. cutRight(rows[rowIndex].right, rightSide);
  119. limit -= offsetY;
  120. }
  121. }
  122. } else if (normalY != 0) {
  123. // Remove pixel rows that are outside of a fully horizontal edge
  124. int64_t valueRow = valueOrigin + (offsetY * (topBound - topBound));
  125. for (int32_t y = topBound; y < bottomBound; y++) {
  126. int32_t rowIndex = y - topBound;
  127. if (valueRow > threshold) { // If outside of the current edge
  128. rows[rowIndex].left = rightBound;
  129. rows[rowIndex].right = leftBound;
  130. }
  131. valueRow = valueRow + offsetY;
  132. }
  133. }
  134. // Zero length edges will make the whole triangle invisible because the two other edges must be exact opposites removing all remaining pixels
  135. }
  136. void dsr::rasterizeTriangle(const LVector2D& cornerA, const LVector2D& cornerB, const LVector2D& cornerC, RowInterval* rows, const IRect& clipBound) {
  137. LVector2D position[3] = {cornerA, cornerB, cornerC};
  138. if (cornerA == cornerB || cornerB == cornerC || cornerC == cornerA) {
  139. // Empty case with less than three separate corners
  140. for (int64_t rowIndex = 0; rowIndex < clipBound.height(); rowIndex++) {
  141. rows[rowIndex].left = clipBound.right();
  142. rows[rowIndex].right = clipBound.left();
  143. }
  144. } else {
  145. // Start with a full bounding box
  146. for (int64_t rowIndex = 0; rowIndex < clipBound.height(); rowIndex++) {
  147. rows[rowIndex].left = clipBound.left();
  148. rows[rowIndex].right = clipBound.right();
  149. }
  150. // Cut away pixels from each side
  151. for (int64_t i = 0; i < 3; i++) {
  152. cutConvexEdge(position[i], position[(i + 1) % 3], rows, clipBound);
  153. }
  154. }
  155. }
  156. void ITriangle2D::getShape(int& startRow, RowInterval* rows, const IRect& clipBound, int alignX, int alignY) const {
  157. // TODO: Move alignment to the render core where it belongs so that all alignX and alignY arguments are removed from the triangle
  158. IRect alignedBound = this->getAlignedRasterBound(clipBound, alignX, alignY);
  159. startRow = alignedBound.top();
  160. rasterizeTriangle(this->position[0].flat, this->position[1].flat, this->position[2].flat, rows, alignedBound);
  161. }
  162. Projection ITriangle2D::getProjection(bool perspective) const {
  163. return this->getProjection(FVector3D(0.0f, 1.0f, 0.0f), FVector3D(0.0f, 0.0f, 1.0f), perspective);
  164. }
  165. Projection ITriangle2D::getProjection(const FVector3D& subB, const FVector3D& subC, bool perspective) const {
  166. /*
  167. TODO: Find out why this implementation gives crap precision
  168. FVector2D pointA = FVector2D(this->position[0].is.x, this->position[0].is.y);
  169. FVector2D pointB = FVector2D(this->position[1].is.x, this->position[1].is.y);
  170. FVector2D pointC = FVector2D(this->position[2].is.x, this->position[2].is.y);
  171. FVector3D targetWeight = getAffineWeight(pointA, pointB, pointC, FVector2D(0.0f, 0.0f));
  172. FVector3D affineWeightDx = getAffineWeight(pointA, pointB, pointC, FVector2D(1.0f, 0.0f)) - targetWeight;
  173. FVector3D affineWeightDy = getAffineWeight(pointA, pointB, pointC, FVector2D(0.0f, 1.0f)) - targetWeight;
  174. */
  175. // Get offsets
  176. FVector3D offsetX, offsetY;
  177. for (int i = 0; i < 3; i++) {
  178. int j = (i + 1) % 3; // End
  179. FVector2D posI = this->position[i].is;
  180. FVector2D posJ = this->position[j].is;
  181. // Get offsets for each edge
  182. offsetX[i] = posJ.y - posI.y;
  183. offsetY[i] = posI.x - posJ.x;
  184. }
  185. // Get the maximum values along the offsets for normalization
  186. FVector3D weightMultiplier;
  187. for (int32_t i = 0; i < 3; i++) {
  188. int o = (i + 2) % 3;
  189. // Take the same kind of dot product from the point that is furthest away from the edge for normalization.
  190. float otherSideValue = ((this->position[o].is.x - this->position[i].is.x) * offsetX[i])
  191. + ((this->position[o].is.y - this->position[i].is.y) * offsetY[i]);
  192. if (otherSideValue == 0.0f) {
  193. weightMultiplier[o] = 0.0f;
  194. } else {
  195. weightMultiplier[o] = 1.0f / otherSideValue;
  196. }
  197. }
  198. // Get normal from weight multiplier and offset
  199. FVector3D normalX, normalY;
  200. for (int i = 0; i < 3; i++) {
  201. normalX[i] = offsetX[i] * weightMultiplier[i];
  202. normalY[i] = offsetY[i] * weightMultiplier[i];
  203. }
  204. // Sample the weight of each corner at the upper left corner of the target image
  205. FVector3D targetWeight;
  206. for (int32_t i = 0; i < 3; i++) {
  207. int o = (i + 2) % 3;
  208. // Take the dot product to get a normalized weight
  209. targetWeight[o] = this->position[i].is.x * -normalX[i] + this->position[i].is.y * -normalY[i];
  210. }
  211. // In order to calculate the perspective corrected vertex weights, we must first linearly iterate over the affine weights.
  212. // Calculate affine weight derivatives for vertex indices from edge indices.
  213. FVector3D affineWeightDx, affineWeightDy;
  214. affineWeightDx.x = normalX.y;
  215. affineWeightDx.y = normalX.z;
  216. affineWeightDx.z = normalX.x;
  217. affineWeightDy.x = normalY.y;
  218. affineWeightDy.y = normalY.z;
  219. affineWeightDy.z = normalY.x;
  220. if (!perspective) {
  221. // Get the linear depth
  222. FVector3D W(this->position[0].cs.z, this->position[1].cs.z, this->position[2].cs.z);
  223. // Get the affine weights of the first pixel
  224. FVector3D pTargetWeight;
  225. pTargetWeight.x = W.x * targetWeight.x + W.y * targetWeight.y + W.z * targetWeight.z;
  226. pTargetWeight.y = targetWeight.x * subB.x + targetWeight.y * subB.y + targetWeight.z * subB.z;
  227. pTargetWeight.z = targetWeight.x * subC.x + targetWeight.y * subC.y + targetWeight.z * subC.z;
  228. // Do the same for derivatives
  229. FVector3D pWeightDx, pWeightDy;
  230. // TODO: Use interpolateUsingAffineWeight
  231. pWeightDx.x = W.x * affineWeightDx.x + W.y * affineWeightDx.y + W.z * affineWeightDx.z;
  232. pWeightDx.y = affineWeightDx.x * subB.x + affineWeightDx.y * subB.y + affineWeightDx.z * subB.z;
  233. pWeightDx.z = affineWeightDx.x * subC.x + affineWeightDx.y * subC.y + affineWeightDx.z * subC.z;
  234. pWeightDy.x = W.x * affineWeightDy.x + W.y * affineWeightDy.y + W.z * affineWeightDy.z;
  235. pWeightDy.y = affineWeightDy.x * subB.x + affineWeightDy.y * subB.y + affineWeightDy.z * subB.z;
  236. pWeightDy.z = affineWeightDy.x * subC.x + affineWeightDy.y * subC.y + affineWeightDy.z * subC.z;
  237. // Store the orthogonal vertex weight projection in the shape head
  238. return Projection(true, pTargetWeight, pWeightDx, pWeightDy);
  239. } else {
  240. // Calculate 1 / W for each corner so that depth and vertex weights can be interpolated in a scale where everything is divided by W.
  241. // This is because a linear interpolation in screen space can only produce affine projections that does not work for multiple depths with perspective.
  242. FVector3D IW(1.0f / this->position[0].cs.z, 1.0f / this->position[1].cs.z, 1.0f / this->position[2].cs.z);
  243. // Calculate the first depth divided weights needed for perspective correction.
  244. // Default W is the linear depth in camera space which everything in the space is divided by.
  245. // Default U is 1 for the second corner and 0 for all others.
  246. // Default V is 1 for the third corner and 0 for all others.
  247. // The first corner's weight can be calculated from the other weights as 1 - (U + V).
  248. // 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.
  249. // |1, U1, V1| |1, 0, 0|
  250. // |1, U2, V2| = |1, 1, 0|
  251. // |1, U3, V3| |1, 0, 1|
  252. // Create a matrix containing (1/W, U/W, V/W) for each corner.
  253. // Rows represent corners and columns represent the different weights.
  254. // |1/W1| |1, 0, 0| |1/W1, 0, 0 |
  255. // P = |1/W2| x |1, 1, 0| = |1/W2, 1/W2, 0 |
  256. // |1/W3| |1, 0, 1| |1/W3, 0, 1/W3|
  257. // 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.
  258. // To get the first pixel's depth divided weights, we multiply the matrix P with the affine vertex weights for each corner.
  259. // 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.
  260. // Calculate P * affineWeight to get the depth divided weights of the first pixel
  261. FVector3D pTargetWeight;
  262. pTargetWeight.x = IW.x * targetWeight.x + IW.y * targetWeight.y + IW.z * targetWeight.z;
  263. pTargetWeight.y = IW.x * targetWeight.x * subB.x + IW.y * targetWeight.y * subB.y + IW.z * targetWeight.z * subB.z;
  264. pTargetWeight.z = IW.x * targetWeight.x * subC.x + IW.y * targetWeight.y * subC.y + IW.z * targetWeight.z * subC.z;
  265. // Do the depth division on derivatives too
  266. FVector3D pWeightDx, pWeightDy;
  267. // TODO: Reuse multiplications by multiplying IW with affineWeightDx and affineWeightDy first
  268. // TODO: Use 3x3 matrices to make the code less error prone
  269. pWeightDx.x = IW.x * affineWeightDx.x + IW.y * affineWeightDx.y + IW.z * affineWeightDx.z;
  270. pWeightDx.y = IW.x * affineWeightDx.x * subB.x + IW.y * affineWeightDx.y * subB.y + IW.z * affineWeightDx.z * subB.z;
  271. pWeightDx.z = IW.x * affineWeightDx.x * subC.x + IW.y * affineWeightDx.y * subC.y + IW.z * affineWeightDx.z * subC.z;
  272. pWeightDy.x = IW.x * affineWeightDy.x + IW.y * affineWeightDy.y + IW.z * affineWeightDy.z;
  273. pWeightDy.y = IW.x * affineWeightDy.x * subB.x + IW.y * affineWeightDy.y * subB.y + IW.z * affineWeightDy.z * subB.z;
  274. pWeightDy.z = IW.x * affineWeightDy.x * subC.x + IW.y * affineWeightDy.y * subC.y + IW.z * affineWeightDy.z * subC.z;
  275. // Store the perspective vertex weight projection in the shape head
  276. return Projection(false, pTargetWeight, pWeightDx, pWeightDy);
  277. }
  278. }