ITriangle2D.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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 = min(rX1, rX2, rX3) - 1;
  36. int topBound = min(rY1, rY2, rY3) - 1;
  37. int rightBound = max(rX1, rX2, rX3) + 1;
  38. int bottomBound = 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. inline static void cutRight(int32_t& rightBound, int32_t value) {
  56. rightBound = min(rightBound, value);
  57. }
  58. inline static void cutLeft(int32_t& leftBound, int32_t value) {
  59. leftBound = max(leftBound, value);
  60. }
  61. IRect ITriangle2D::getAlignedRasterBound(const IRect& clipBound, int alignX, int alignY) const {
  62. IRect unaligned = IRect::cut(this->wholeBound, clipBound);
  63. int alignedTop = roundDown(unaligned.top(), 2);
  64. int alignedBottom = roundUp(unaligned.bottom(), 2);
  65. return IRect(unaligned.left(), alignedTop, unaligned.width(), alignedBottom - alignedTop);
  66. }
  67. int ITriangle2D::getBufferSize(const IRect& clipBound, int alignX, int alignY) const {
  68. if (IRect::overlaps(this->wholeBound, clipBound)) {
  69. IRect rasterBound = this->getAlignedRasterBound(clipBound, alignX, alignY);
  70. return rasterBound.bottom() - rasterBound.top();
  71. } else {
  72. return 0;
  73. }
  74. }
  75. static void cutConvexEdge(const LVector2D& startPoint, const LVector2D& endPoint, RowInterval* rows, const IRect& clipBound) {
  76. int leftBound = clipBound.left();
  77. int topBound = clipBound.top();
  78. int rightBound = clipBound.right();
  79. int bottomBound = clipBound.bottom();
  80. // Get origin in units
  81. int64_t originX = constants::unitsPerHalfPixel + clipBound.left() * constants::unitsPerPixel;
  82. int64_t originY = constants::unitsPerHalfPixel + clipBound.top() * constants::unitsPerPixel;
  83. // To turn x > 0 into x >= 0 without branching, just compare to -1 instead as it is equivalent for integers.
  84. int64_t threshold = (startPoint.x > endPoint.x || (startPoint.x == endPoint.x && startPoint.y > endPoint.y)) ? -1 : 0;
  85. // Get normals for each edge
  86. int64_t normalX = endPoint.y - startPoint.y;
  87. int64_t normalY = startPoint.x - endPoint.x;
  88. // Get partial derivatives along edge directions in screen space
  89. int64_t offsetX = normalX * constants::unitsPerPixel;
  90. int64_t offsetY = normalY * constants::unitsPerPixel;
  91. // Take the dot product to get an initial weight without normalization.
  92. int64_t valueOrigin = ((originX - startPoint.x) * normalX) + ((originY - startPoint.y) * normalY);
  93. // Get vertical bound
  94. if (normalX != 0) {
  95. int64_t valueRow = valueOrigin;
  96. // Proof for the limit variable:
  97. // Find the highest x for the left side where offsetX < 0 or the lowest x for the right side where offsetX > 0
  98. // x must satisfy the equation valueRow + (offsetX * (x - leftBound)) > threshold
  99. // offsetX * (x - leftBound) > threshold - valueRow
  100. // (offsetX * x) - (offsetX * leftBound) > threshold - valueRow
  101. // offsetX * x > threshold - valueRow + (offsetX * leftBound)
  102. // offsetX * x > limit
  103. int64_t limit = threshold - valueRow + (offsetX * leftBound);
  104. if (normalX < 0) { // Left
  105. for (int32_t y = topBound; y < bottomBound; y++) {
  106. int32_t rowIndex = y - topBound;
  107. // Find the highest x where offsetX * x > limit
  108. int32_t leftSide = min(max(leftBound, (int32_t)((limit + 1) / offsetX + 1)), rightBound);
  109. cutLeft(rows[rowIndex].left, leftSide);
  110. limit -= offsetY;
  111. }
  112. } else { // Right
  113. for (int32_t y = topBound; y < bottomBound; y++) {
  114. int32_t rowIndex = y - topBound;
  115. // Find the lowest x where offsetX * x > limit
  116. int32_t rightSide = min(max(leftBound, (int32_t)(limit / offsetX + 1)), rightBound);
  117. cutRight(rows[rowIndex].right, rightSide);
  118. limit -= offsetY;
  119. }
  120. }
  121. } else if (normalY != 0) {
  122. // Remove pixel rows that are outside of a fully horizontal edge
  123. int64_t valueRow = valueOrigin + (offsetY * (topBound - topBound));
  124. for (int32_t y = topBound; y < bottomBound; y++) {
  125. int32_t rowIndex = y - topBound;
  126. if (valueRow > threshold) { // If outside of the current edge
  127. rows[rowIndex].left = rightBound;
  128. rows[rowIndex].right = leftBound;
  129. }
  130. valueRow = valueRow + offsetY;
  131. }
  132. }
  133. // Zero length edges will make the whole triangle invisible because the two other edges must be exact opposites removing all remaining pixels
  134. }
  135. void dsr::rasterizeTriangle(const LVector2D& cornerA, const LVector2D& cornerB, const LVector2D& cornerC, RowInterval* rows, const IRect& clipBound) {
  136. LVector2D position[3] = {cornerA, cornerB, cornerC};
  137. if (cornerA == cornerB || cornerB == cornerC || cornerC == cornerA) {
  138. // Empty case with less than three separate corners
  139. for (int64_t rowIndex = 0; rowIndex < clipBound.height(); rowIndex++) {
  140. rows[rowIndex].left = clipBound.right();
  141. rows[rowIndex].right = clipBound.left();
  142. }
  143. } else {
  144. // Start with a full bounding box
  145. for (int64_t rowIndex = 0; rowIndex < clipBound.height(); rowIndex++) {
  146. rows[rowIndex].left = clipBound.left();
  147. rows[rowIndex].right = clipBound.right();
  148. }
  149. // Cut away pixels from each side
  150. for (int64_t i = 0; i < 3; i++) {
  151. cutConvexEdge(position[i], position[(i + 1) % 3], rows, clipBound);
  152. }
  153. }
  154. }
  155. void ITriangle2D::getShape(int& startRow, RowInterval* rows, const IRect& clipBound, int alignX, int alignY) const {
  156. // TODO: Move alignment to the render core where it belongs so that all alignX and alignY arguments are removed from the triangle
  157. IRect alignedBound = this->getAlignedRasterBound(clipBound, alignX, alignY);
  158. startRow = alignedBound.top();
  159. rasterizeTriangle(this->position[0].flat, this->position[1].flat, this->position[2].flat, rows, alignedBound);
  160. }
  161. Projection ITriangle2D::getProjection(bool perspective) const {
  162. return this->getProjection(FVector3D(0.0f, 1.0f, 0.0f), FVector3D(0.0f, 0.0f, 1.0f), perspective);
  163. }
  164. Projection ITriangle2D::getProjection(const FVector3D& subB, const FVector3D& subC, bool perspective) const {
  165. /*
  166. TODO: Find out why this implementation gives crap precision
  167. FVector2D pointA = FVector2D(this->position[0].is.x, this->position[0].is.y);
  168. FVector2D pointB = FVector2D(this->position[1].is.x, this->position[1].is.y);
  169. FVector2D pointC = FVector2D(this->position[2].is.x, this->position[2].is.y);
  170. FVector3D targetWeight = getAffineWeight(pointA, pointB, pointC, FVector2D(0.0f, 0.0f));
  171. FVector3D affineWeightDx = getAffineWeight(pointA, pointB, pointC, FVector2D(1.0f, 0.0f)) - targetWeight;
  172. FVector3D affineWeightDy = getAffineWeight(pointA, pointB, pointC, FVector2D(0.0f, 1.0f)) - targetWeight;
  173. */
  174. // Get offsets
  175. FVector3D offsetX, offsetY;
  176. for (int i = 0; i < 3; i++) {
  177. int j = (i + 1) % 3; // End
  178. FVector2D posI = this->position[i].is;
  179. FVector2D posJ = this->position[j].is;
  180. // Get offsets for each edge
  181. offsetX[i] = posJ.y - posI.y;
  182. offsetY[i] = posI.x - posJ.x;
  183. }
  184. // Get the maximum values along the offsets for normalization
  185. FVector3D weightMultiplier;
  186. for (int32_t i = 0; i < 3; i++) {
  187. int o = (i + 2) % 3;
  188. // Take the same kind of dot product from the point that is furthest away from the edge for normalization.
  189. float otherSideValue = ((this->position[o].is.x - this->position[i].is.x) * offsetX[i])
  190. + ((this->position[o].is.y - this->position[i].is.y) * offsetY[i]);
  191. if (otherSideValue == 0.0f) {
  192. weightMultiplier[o] = 0.0f;
  193. } else {
  194. weightMultiplier[o] = 1.0f / otherSideValue;
  195. }
  196. }
  197. // Get normal from weight multiplier and offset
  198. FVector3D normalX, normalY;
  199. for (int i = 0; i < 3; i++) {
  200. normalX[i] = offsetX[i] * weightMultiplier[i];
  201. normalY[i] = offsetY[i] * weightMultiplier[i];
  202. }
  203. // Sample the weight of each corner at the upper left corner of the target image
  204. FVector3D targetWeight;
  205. for (int32_t i = 0; i < 3; i++) {
  206. int o = (i + 2) % 3;
  207. // Take the dot product to get a normalized weight
  208. targetWeight[o] = this->position[i].is.x * -normalX[i] + this->position[i].is.y * -normalY[i];
  209. }
  210. // In order to calculate the perspective corrected vertex weights, we must first linearly iterate over the affine weights.
  211. // Calculate affine weight derivatives for vertex indices from edge indices.
  212. FVector3D affineWeightDx, affineWeightDy;
  213. affineWeightDx.x = normalX.y;
  214. affineWeightDx.y = normalX.z;
  215. affineWeightDx.z = normalX.x;
  216. affineWeightDy.x = normalY.y;
  217. affineWeightDy.y = normalY.z;
  218. affineWeightDy.z = normalY.x;
  219. if (!perspective) {
  220. // Get the linear depth
  221. FVector3D W(this->position[0].cs.z, this->position[1].cs.z, this->position[2].cs.z);
  222. // Get the affine weights of the first pixel
  223. FVector3D pTargetWeight;
  224. pTargetWeight.x = W.x * targetWeight.x + W.y * targetWeight.y + W.z * targetWeight.z;
  225. pTargetWeight.y = targetWeight.x * subB.x + targetWeight.y * subB.y + targetWeight.z * subB.z;
  226. pTargetWeight.z = targetWeight.x * subC.x + targetWeight.y * subC.y + targetWeight.z * subC.z;
  227. // Do the same for derivatives
  228. FVector3D pWeightDx, pWeightDy;
  229. // TODO: Use interpolateUsingAffineWeight
  230. pWeightDx.x = W.x * affineWeightDx.x + W.y * affineWeightDx.y + W.z * affineWeightDx.z;
  231. pWeightDx.y = affineWeightDx.x * subB.x + affineWeightDx.y * subB.y + affineWeightDx.z * subB.z;
  232. pWeightDx.z = affineWeightDx.x * subC.x + affineWeightDx.y * subC.y + affineWeightDx.z * subC.z;
  233. pWeightDy.x = W.x * affineWeightDy.x + W.y * affineWeightDy.y + W.z * affineWeightDy.z;
  234. pWeightDy.y = affineWeightDy.x * subB.x + affineWeightDy.y * subB.y + affineWeightDy.z * subB.z;
  235. pWeightDy.z = affineWeightDy.x * subC.x + affineWeightDy.y * subC.y + affineWeightDy.z * subC.z;
  236. // Store the orthogonal vertex weight projection in the shape head
  237. return Projection(true, pTargetWeight, pWeightDx, pWeightDy);
  238. } else {
  239. // Calculate 1 / W for each corner so that depth and vertex weights can be interpolated in a scale where everything is divided by W.
  240. // This is because a linear interpolation in screen space can only produce affine projections that does not work for multiple depths with perspective.
  241. FVector3D IW(1.0f / this->position[0].cs.z, 1.0f / this->position[1].cs.z, 1.0f / this->position[2].cs.z);
  242. // Calculate the first depth divided weights needed for perspective correction.
  243. // Default W is the linear depth in camera space which everything in the space is divided by.
  244. // Default U is 1 for the second corner and 0 for all others.
  245. // Default V is 1 for the third corner and 0 for all others.
  246. // The first corner's weight can be calculated from the other weights as 1 - (U + V).
  247. // 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.
  248. // |1, U1, V1| |1, 0, 0|
  249. // |1, U2, V2| = |1, 1, 0|
  250. // |1, U3, V3| |1, 0, 1|
  251. // Create a matrix containing (1/W, U/W, V/W) for each corner.
  252. // Rows represent corners and columns represent the different weights.
  253. // |1/W1| |1, 0, 0| |1/W1, 0, 0 |
  254. // P = |1/W2| x |1, 1, 0| = |1/W2, 1/W2, 0 |
  255. // |1/W3| |1, 0, 1| |1/W3, 0, 1/W3|
  256. // 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.
  257. // To get the first pixel's depth divided weights, we multiply the matrix P with the affine vertex weights for each corner.
  258. // 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.
  259. // Calculate P * affineWeight to get the depth divided weights of the first pixel
  260. FVector3D pTargetWeight;
  261. pTargetWeight.x = IW.x * targetWeight.x + IW.y * targetWeight.y + IW.z * targetWeight.z;
  262. pTargetWeight.y = IW.x * targetWeight.x * subB.x + IW.y * targetWeight.y * subB.y + IW.z * targetWeight.z * subB.z;
  263. pTargetWeight.z = IW.x * targetWeight.x * subC.x + IW.y * targetWeight.y * subC.y + IW.z * targetWeight.z * subC.z;
  264. // Do the depth division on derivatives too
  265. FVector3D pWeightDx, pWeightDy;
  266. // TODO: Reuse multiplications by multiplying IW with affineWeightDx and affineWeightDy first
  267. // TODO: Use 3x3 matrices to make the code less error prone
  268. pWeightDx.x = IW.x * affineWeightDx.x + IW.y * affineWeightDx.y + IW.z * affineWeightDx.z;
  269. pWeightDx.y = IW.x * affineWeightDx.x * subB.x + IW.y * affineWeightDx.y * subB.y + IW.z * affineWeightDx.z * subB.z;
  270. pWeightDx.z = IW.x * affineWeightDx.x * subC.x + IW.y * affineWeightDx.y * subC.y + IW.z * affineWeightDx.z * subC.z;
  271. pWeightDy.x = IW.x * affineWeightDy.x + IW.y * affineWeightDy.y + IW.z * affineWeightDy.z;
  272. pWeightDy.y = IW.x * affineWeightDy.x * subB.x + IW.y * affineWeightDy.y * subB.y + IW.z * affineWeightDy.z * subB.z;
  273. pWeightDy.z = IW.x * affineWeightDy.x * subC.x + IW.y * affineWeightDy.y * subC.y + IW.z * affineWeightDy.z * subC.z;
  274. // Store the perspective vertex weight projection in the shape head
  275. return Projection(false, pTargetWeight, pWeightDx, pWeightDy);
  276. }
  277. }