modelAPI.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. // zlib open source license
  2. //
  3. // Copyright (c) 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. #define DFPSR_INTERNAL_ACCESS
  24. #include "modelAPI.h"
  25. #include "imageAPI.h"
  26. #include "drawAPI.h"
  27. #include "../render/model/Model.h"
  28. #include <limits>
  29. #define MUST_EXIST(OBJECT, METHOD) if (OBJECT.get() == nullptr) { throwError("The " #OBJECT " handle was null in " #METHOD "\n"); }
  30. namespace dsr {
  31. Model model_create() {
  32. return std::make_shared<ModelImpl>();
  33. }
  34. Model model_clone(const Model& model) {
  35. MUST_EXIST(model,model_clone);
  36. return std::make_shared<ModelImpl>(model->filter, model->partBuffer, model->positionBuffer);
  37. }
  38. void model_setFilter(const Model& model, Filter filter) {
  39. MUST_EXIST(model,model_setFilter);
  40. model->filter = filter;
  41. }
  42. Filter model_getFilter(const Model& model) {
  43. MUST_EXIST(model,model_getFilter);
  44. return model->filter;
  45. }
  46. bool model_exists(const Model& model) {
  47. return model.get() != nullptr;
  48. }
  49. int model_addEmptyPart(Model& model, const String &name) {
  50. MUST_EXIST(model,model_addEmptyPart);
  51. return model->addEmptyPart(name);
  52. }
  53. int model_getNumberOfParts(const Model& model) {
  54. MUST_EXIST(model,model_getNumberOfParts);
  55. return model->getNumberOfParts();
  56. }
  57. void model_setPartName(Model& model, int partIndex, const String &name) {
  58. MUST_EXIST(model,model_setPartName);
  59. model->setPartName(partIndex, name);
  60. }
  61. String model_getPartName(const Model& model, int partIndex) {
  62. MUST_EXIST(model,model_getPartName);
  63. return model->getPartName(partIndex);
  64. }
  65. int model_getNumberOfPoints(const Model& model) {
  66. MUST_EXIST(model,model_getNumberOfPoints);
  67. return model->getNumberOfPoints();
  68. }
  69. FVector3D model_getPoint(const Model& model, int pointIndex) {
  70. MUST_EXIST(model,model_getPoint);
  71. return model->getPoint(pointIndex);
  72. }
  73. void model_setPoint(Model& model, int pointIndex, const FVector3D& position) {
  74. MUST_EXIST(model,model_setPoint);
  75. model->setPoint(pointIndex, position);
  76. }
  77. int model_findPoint(const Model& model, const FVector3D &position, float threshold) {
  78. MUST_EXIST(model,model_findPoint);
  79. return model->findPoint(position, threshold);
  80. }
  81. int model_addPoint(const Model& model, const FVector3D &position) {
  82. MUST_EXIST(model,model_addPoint);
  83. return model->addPoint(position);
  84. }
  85. int model_addPointIfNeeded(Model& model, const FVector3D &position, float threshold) {
  86. MUST_EXIST(model,model_addPointIfNeeded);
  87. return model->addPointIfNeeded(position, threshold);
  88. }
  89. int model_getVertexPointIndex(const Model& model, int partIndex, int polygonIndex, int vertexIndex) {
  90. MUST_EXIST(model,model_getVertexPointIndex);
  91. return model->getVertexPointIndex(partIndex, polygonIndex, vertexIndex);
  92. }
  93. void model_setVertexPointIndex(Model& model, int partIndex, int polygonIndex, int vertexIndex, int pointIndex) {
  94. MUST_EXIST(model,model_setVertexPointIndex);
  95. model->setVertexPointIndex(partIndex, polygonIndex, vertexIndex, pointIndex);
  96. }
  97. FVector3D model_getVertexPosition(const Model& model, int partIndex, int polygonIndex, int vertexIndex) {
  98. MUST_EXIST(model,model_getVertexPosition);
  99. return model->getVertexPosition(partIndex, polygonIndex, vertexIndex);
  100. }
  101. FVector4D model_getVertexColor(const Model& model, int partIndex, int polygonIndex, int vertexIndex) {
  102. MUST_EXIST(model,model_getVertexColor);
  103. return model->getVertexColor(partIndex, polygonIndex, vertexIndex);
  104. }
  105. void model_setVertexColor(Model& model, int partIndex, int polygonIndex, int vertexIndex, const FVector4D& color) {
  106. MUST_EXIST(model,model_setVertexColor);
  107. model->setVertexColor(partIndex, polygonIndex, vertexIndex, color);
  108. }
  109. FVector4D model_getTexCoord(const Model& model, int partIndex, int polygonIndex, int vertexIndex) {
  110. MUST_EXIST(model,model_getTexCoord);
  111. return model->getTexCoord(partIndex, polygonIndex, vertexIndex);
  112. }
  113. void model_setTexCoord(Model& model, int partIndex, int polygonIndex, int vertexIndex, const FVector4D& texCoord) {
  114. MUST_EXIST(model,model_setTexCoord);
  115. model->setTexCoord(partIndex, polygonIndex, vertexIndex, texCoord);
  116. }
  117. int model_addTriangle(Model& model, int partIndex, int pointA, int pointB, int pointC) {
  118. MUST_EXIST(model,model_addTriangle);
  119. return model->addPolygon(Polygon(pointA, pointB, pointC), partIndex);
  120. }
  121. int model_addQuad(Model& model, int partIndex, int pointA, int pointB, int pointC, int pointD) {
  122. MUST_EXIST(model,model_addQuad);
  123. return model->addPolygon(Polygon(pointA, pointB, pointC, pointD), partIndex);
  124. }
  125. int model_getNumberOfPolygons(const Model& model, int partIndex) {
  126. MUST_EXIST(model,model_getNumberOfPolygons);
  127. return model->getNumberOfPolygons(partIndex);
  128. }
  129. int model_getPolygonVertexCount(const Model& model, int partIndex, int polygonIndex) {
  130. MUST_EXIST(model,model_getPolygonVertexCount);
  131. return model->getPolygonVertexCount(partIndex, polygonIndex);
  132. }
  133. ImageRgbaU8 model_getDiffuseMap(const Model& model, int partIndex) {
  134. MUST_EXIST(model,model_getDiffuseMap);
  135. return model->getDiffuseMap(partIndex);
  136. }
  137. void model_setDiffuseMap(Model& model, int partIndex, const ImageRgbaU8 &diffuseMap) {
  138. MUST_EXIST(model,model_setDiffuseMap);
  139. model->setDiffuseMap(diffuseMap, partIndex);
  140. }
  141. void model_setDiffuseMapByName(Model& model, int partIndex, ResourcePool &pool, const String &filename) {
  142. MUST_EXIST(model,model_setDiffuseMapByName);
  143. model->setDiffuseMapByName(pool, filename, partIndex);
  144. }
  145. ImageRgbaU8 model_getLightMap(Model& model, int partIndex) {
  146. MUST_EXIST(model,model_getLightMap);
  147. return model->getLightMap(partIndex);
  148. }
  149. void model_setLightMap(Model& model, int partIndex, const ImageRgbaU8 &lightMap) {
  150. MUST_EXIST(model,model_setLightMap);
  151. model->setLightMap(lightMap, partIndex);
  152. }
  153. void model_setLightMapByName(Model& model, int partIndex, ResourcePool &pool, const String &filename) {
  154. MUST_EXIST(model,model_setLightMapByName);
  155. model->setLightMapByName(pool, filename, partIndex);
  156. }
  157. // Single-threaded rendering for the simple cases where you just want it to work
  158. void model_render(const Model& model, const Transform3D &modelToWorldTransform, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer, const Camera &camera) {
  159. if (model.get() != nullptr) {
  160. model->render((CommandQueue*)nullptr, colorBuffer, depthBuffer, modelToWorldTransform, camera);
  161. }
  162. }
  163. void model_renderDepth(const Model& model, const Transform3D &modelToWorldTransform, ImageF32& depthBuffer, const Camera &camera) {
  164. if (model.get() != nullptr) {
  165. model->renderDepth(depthBuffer, modelToWorldTransform, camera);
  166. }
  167. }
  168. void model_getBoundingBox(const Model& model, FVector3D& minimum, FVector3D& maximum) {
  169. MUST_EXIST(model,model_getBoundingBox);
  170. minimum = model->minBound;
  171. maximum = model->maxBound;
  172. }
  173. static const int cellSize = 16;
  174. struct DebugLine {
  175. int64_t x1, y1, x2, y2;
  176. ColorRgbaI32 color;
  177. DebugLine(int64_t x1, int64_t y1, int64_t x2, int64_t y2, const ColorRgbaI32& color)
  178. : x1(x1), y1(y1), x2(x2), y2(y2), color(color) {}
  179. };
  180. // Context for multi-threaded rendering of triangles in a command queue
  181. struct RendererImpl {
  182. bool receiving = false; // Preventing version dependency by only allowing calls in the expected order
  183. ImageRgbaU8 colorBuffer; // The color image being rendered to
  184. ImageF32 depthBuffer; // Linear depth for isometric cameras, 1 / depth for perspective cameras
  185. ImageF32 depthGrid; // An occlusion grid of cellSize² cells representing the longest linear depth where something might be visible
  186. CommandQueue commandQueue; // Triangles to be drawn
  187. List<DebugLine> debugLines; // Additional lines to be drawn as an overlay for debugging occlusion
  188. int width = 0, height = 0, gridWidth = 0, gridHeight = 0;
  189. bool occluded = false;
  190. RendererImpl() {}
  191. void beginFrame(ImageRgbaU8& colorBuffer, ImageF32& depthBuffer) {
  192. if (this->receiving) {
  193. throwError("Called renderer_begin on the same renderer twice without ending the previous batch!\n");
  194. }
  195. this->receiving = true;
  196. this->colorBuffer = colorBuffer;
  197. this->depthBuffer = depthBuffer;
  198. if (image_exists(this->colorBuffer)) {
  199. this->width = image_getWidth(this->colorBuffer);
  200. this->height = image_getHeight(this->colorBuffer);
  201. } else if (image_exists(this->depthBuffer)) {
  202. this->width = image_getWidth(this->depthBuffer);
  203. this->height = image_getHeight(this->depthBuffer);
  204. }
  205. this->gridWidth = (this->width + (cellSize - 1)) / cellSize;
  206. this->gridHeight = (this->height + (cellSize - 1)) / cellSize;
  207. this->occluded = false;
  208. }
  209. bool pointInsideOfEdge(const LVector2D &edgeA, const LVector2D &edgeB, const LVector2D &point) {
  210. LVector2D edgeDirection = LVector2D(edgeB.y - edgeA.y, edgeA.x - edgeB.x);
  211. LVector2D relativePosition = point - edgeA;
  212. return (edgeDirection.x * relativePosition.x) + (edgeDirection.y * relativePosition.y) <= 0;
  213. }
  214. // Returns true iff the point is inside of the hull
  215. // convexHullCorners from 0 to cornerCount-1 must be sorted clockwise and may not include any concave corners
  216. bool pointInsideOfHull(const ProjectedPoint* convexHullCorners, int cornerCount, const LVector2D &point) {
  217. for (int c = 0; c < cornerCount; c++) {
  218. int nc = c + 1;
  219. if (nc == cornerCount) {
  220. nc = 0;
  221. }
  222. if (!pointInsideOfEdge(convexHullCorners[c].flat, convexHullCorners[nc].flat, point)) {
  223. // Outside of one edge, not inside
  224. return false;
  225. }
  226. }
  227. // Passed all edge tests
  228. return true;
  229. }
  230. // Returns true iff all corners of the rectangle are inside of the hull
  231. bool rectangleInsideOfHull(const ProjectedPoint* convexHullCorners, int cornerCount, const IRect &rectangle) {
  232. return pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.left(), rectangle.top()))
  233. && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.right(), rectangle.top()))
  234. && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.left(), rectangle.bottom()))
  235. && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.right(), rectangle.bottom()));
  236. }
  237. IRect getOuterCellBound(const IRect &pixelBound) {
  238. int minCellX = pixelBound.left() / cellSize;
  239. int maxCellX = pixelBound.right() / cellSize + 1;
  240. int minCellY = pixelBound.top() / cellSize;
  241. int maxCellY = pixelBound.bottom() / cellSize + 1;
  242. if (minCellX < 0) { minCellX = 0; }
  243. if (minCellY < 0) { minCellY = 0; }
  244. if (maxCellX > this->gridWidth) { maxCellX = this->gridWidth; }
  245. if (maxCellY > this->gridHeight) { maxCellY = this->gridHeight; }
  246. return IRect(minCellX, minCellY, maxCellX - minCellX, maxCellY - minCellY);
  247. }
  248. // Called before occluding so that the grid is initialized once when used and skipped when not used
  249. void prepareForOcclusion() {
  250. if (!this->occluded) {
  251. // Allocate the grid if a sufficiently large one does not already exist
  252. if (!(image_exists(this->depthGrid) && image_getWidth(this->depthGrid) >= gridWidth && image_getHeight(this->depthGrid) >= gridHeight)) {
  253. this->depthGrid = image_create_F32(gridWidth, gridHeight);
  254. }
  255. // Use inifnite depth in camera space
  256. image_fill(this->depthGrid, std::numeric_limits<float>::infinity());
  257. }
  258. this->occluded = true;
  259. }
  260. // If any occluder has been used during this pass, all triangles in the buffer will be filtered based using depthGrid
  261. void completeOcclusion() {
  262. if (this->occluded) {
  263. for (int t = this->commandQueue.buffer.length() - 1; t >= 0; t--) {
  264. bool anyVisible = false;
  265. ITriangle2D triangle = this->commandQueue.buffer[t].triangle;
  266. IRect outerBound = getOuterCellBound(triangle.wholeBound);
  267. for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
  268. for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
  269. // TODO: Optimize access using SafePointer iteration
  270. float backgroundDepth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
  271. float triangleDepth = triangle.position[0].cs.z;
  272. replaceWithSmaller(triangleDepth, triangle.position[1].cs.z);
  273. replaceWithSmaller(triangleDepth, triangle.position[2].cs.z);
  274. if (triangleDepth < backgroundDepth + 0.001) {
  275. anyVisible = true;
  276. }
  277. }
  278. }
  279. if (!anyVisible) {
  280. // TODO: Make triangle swapping work so that the list can be sorted
  281. this->commandQueue.buffer[t].occluded = true;
  282. }
  283. }
  284. }
  285. }
  286. void occludeFromSortedHull(const ProjectedPoint* convexHullCorners, int cornerCount, const IRect& pixelBound) {
  287. // Loop over the outer bound
  288. if (pixelBound.width() > cellSize && pixelBound.height() > cellSize) {
  289. float distance = 0.0f;
  290. for (int c = 0; c < cornerCount; c++) {
  291. replaceWithLarger(distance, convexHullCorners[c].cs.z);
  292. }
  293. // Loop over all cells within the bound
  294. IRect outerBound = getOuterCellBound(pixelBound);
  295. for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
  296. for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
  297. IRect pixelRegion = IRect(cellX * cellSize, cellY * cellSize, cellSize, cellSize);
  298. IRect subPixelRegion = pixelRegion * constants::unitsPerPixel;
  299. if (rectangleInsideOfHull(convexHullCorners, cornerCount, subPixelRegion)) {
  300. float oldDepth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
  301. if (distance < oldDepth) {
  302. image_writePixel(this->depthGrid, cellX, cellY, distance);
  303. }
  304. }
  305. }
  306. }
  307. }
  308. }
  309. IRect getPixelBoundFromProjection(const ProjectedPoint* convexHullCorners, int cornerCount) {
  310. IRect result = IRect(convexHullCorners[0].flat.x / constants::unitsPerPixel, convexHullCorners[0].flat.y / constants::unitsPerPixel, 1, 1);
  311. for (int p = 1; p < cornerCount; p++) {
  312. result = IRect::merge(result, IRect(convexHullCorners[p].flat.x / constants::unitsPerPixel, convexHullCorners[p].flat.y / constants::unitsPerPixel, 1, 1));
  313. }
  314. return result;
  315. }
  316. void occludeFromSortedHull(const ProjectedPoint* convexHullCorners, int cornerCount) {
  317. occludeFromSortedHull(convexHullCorners, cornerCount, getPixelBoundFromProjection(convexHullCorners, cornerCount));
  318. }
  319. void occludeFromExistingTriangles() {
  320. if (!this->receiving) {
  321. throwError("Cannot call renderer_occludeFromExistingTriangles without first calling renderer_begin!\n");
  322. }
  323. prepareForOcclusion();
  324. // Generate a depth grid to remove many small triangles behind larger triangles
  325. // This will leave triangles along seams but at least begin to remove the worst unwanted drawing
  326. for (int t = 0; t < this->commandQueue.buffer.length(); t++) {
  327. // Get the current triangle from the queue
  328. Filter filter = this->commandQueue.buffer[t].filter;
  329. if (filter == Filter::Solid) {
  330. ITriangle2D triangle = this->commandQueue.buffer[t].triangle;
  331. occludeFromSortedHull(triangle.position, 3, triangle.wholeBound);
  332. }
  333. }
  334. }
  335. bool counterClockwise(const ProjectedPoint& p, const ProjectedPoint& q, const ProjectedPoint& r) {
  336. return (q.flat.y - p.flat.y) * (r.flat.x - q.flat.x) - (q.flat.x - p.flat.x) * (r.flat.y - q.flat.y) < 0;
  337. }
  338. // outputHullCorners must be at least as big as inputHullCorners, so that it can hold the worst case output size.
  339. // Instead of not allowing less than three points, it copies the input as output when it happens to reduce pre-conditions.
  340. void jarvisConvexHullAlgorithm(ProjectedPoint* outputHullCorners, int& outputCornerCount, const ProjectedPoint* inputHullCorners, int inputCornerCount) {
  341. if (inputCornerCount < 3) {
  342. outputCornerCount = inputCornerCount;
  343. for (int p = 0; p < inputCornerCount; p++) {
  344. outputHullCorners[p] = inputHullCorners[p];
  345. }
  346. } else {
  347. int l = 0;
  348. outputCornerCount = 0;
  349. for (int i = 1; i < inputCornerCount; i++) {
  350. if (inputHullCorners[i].flat.x < inputHullCorners[l].flat.x) {
  351. l = i;
  352. }
  353. }
  354. int p = l;
  355. do {
  356. if (outputCornerCount >= inputCornerCount) {
  357. // Prevent getting stuck in an infinite loop from overflow
  358. return;
  359. }
  360. outputHullCorners[outputCornerCount] = inputHullCorners[p]; outputCornerCount++;
  361. int q = (p + 1) % inputCornerCount;
  362. for (int i = 0; i < inputCornerCount; i++) {
  363. if (counterClockwise(inputHullCorners[p], inputHullCorners[i], inputHullCorners[q])) {
  364. q = i;
  365. }
  366. }
  367. p = q;
  368. } while (p != l);
  369. }
  370. }
  371. // Transform and project the corners of a hull, so that the output can be given to the convex hull algorithm and used for occluding
  372. // Returns true if occluder culling passed, which may skip occluders that could have been visible
  373. bool projectHull(ProjectedPoint* outputHullCorners, const FVector3D* inputHullCorners, int cornerCount, const Transform3D &modelToWorldTransform, const Camera &camera) {
  374. for (int p = 0; p < cornerCount; p++) {
  375. FVector3D worldPoint = modelToWorldTransform.transformPoint(inputHullCorners[p]);
  376. FVector3D cameraPoint = camera.worldToCamera(worldPoint);
  377. FVector3D narrowPoint = cameraPoint * FVector3D(0.5f, 0.5f, 1.0f);
  378. for (int s = 0; s < camera.cullFrustum.getPlaneCount(); s++) {
  379. FPlane3D plane = camera.cullFrustum.getPlane(s);
  380. if (!plane.inside(narrowPoint)) {
  381. return false;
  382. }
  383. }
  384. outputHullCorners[p] = camera.cameraToScreen(cameraPoint);
  385. }
  386. return true;
  387. }
  388. #define GENERATE_BOX_CORNERS(TARGET, MIN, MAX) \
  389. TARGET[0] = FVector3D(MIN.x, MIN.y, MIN.z); \
  390. TARGET[1] = FVector3D(MIN.x, MIN.y, MAX.z); \
  391. TARGET[2] = FVector3D(MIN.x, MAX.y, MIN.z); \
  392. TARGET[3] = FVector3D(MIN.x, MAX.y, MAX.z); \
  393. TARGET[4] = FVector3D(MAX.x, MIN.y, MIN.z); \
  394. TARGET[5] = FVector3D(MAX.x, MIN.y, MAX.z); \
  395. TARGET[6] = FVector3D(MAX.x, MAX.y, MIN.z); \
  396. TARGET[7] = FVector3D(MAX.x, MAX.y, MAX.z);
  397. // Fills the occlusion grid using the box, so that things behind it can skip rendering
  398. void occludeFromBox(const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette) {
  399. if (!this->receiving) {
  400. throwError("Cannot call renderer_occludeFromBox without first calling renderer_begin!\n");
  401. }
  402. prepareForOcclusion();
  403. static const int pointCount = 8;
  404. FVector3D localPoints[pointCount];
  405. ProjectedPoint projections[pointCount];
  406. ProjectedPoint edgeCorners[pointCount];
  407. GENERATE_BOX_CORNERS(localPoints, minimum, maximum)
  408. if (projectHull(projections, localPoints, 8, modelToWorldTransform, camera)) {
  409. // Get a 2D convex hull from the projected corners
  410. int edgeCornerCount = 0;
  411. jarvisConvexHullAlgorithm(edgeCorners, edgeCornerCount, projections, 8);
  412. occludeFromSortedHull(edgeCorners, edgeCornerCount);
  413. // Allow saving the 2D silhouette for debugging
  414. if (debugSilhouette) {
  415. for (int p = 0; p < edgeCornerCount; p++) {
  416. int q = (p + 1) % edgeCornerCount;
  417. if (projections[p].cs.z > camera.nearClip) {
  418. this->debugLines.pushConstruct(
  419. edgeCorners[p].flat.x / constants::unitsPerPixel, edgeCorners[p].flat.y / constants::unitsPerPixel,
  420. edgeCorners[q].flat.x / constants::unitsPerPixel, edgeCorners[q].flat.y / constants::unitsPerPixel,
  421. ColorRgbaI32(0, 255, 255, 255)
  422. );
  423. }
  424. }
  425. }
  426. }
  427. }
  428. // Occlusion test for whole model bounds.
  429. // Returns false if the convex hull of the corners has a chance to be seen from the camera.
  430. bool isHullOccluded(ProjectedPoint* outputHullCorners, const FVector3D* inputHullCorners, int cornerCount, const Transform3D &modelToWorldTransform, const Camera &camera) {
  431. FVector3D cameraPoints[cornerCount];
  432. for (int p = 0; p < cornerCount; p++) {
  433. cameraPoints[p] = camera.worldToCamera(modelToWorldTransform.transformPoint(inputHullCorners[p]));
  434. outputHullCorners[p] = camera.cameraToScreen(cameraPoints[p]);
  435. }
  436. // Culling test to see if all points are outside of the same plane of the view frustum.
  437. for (int s = 0; s < camera.cullFrustum.getPlaneCount(); s++) {
  438. bool allOutside = true; // True until prooven false.
  439. FPlane3D plane = camera.cullFrustum.getPlane(s);
  440. for (int p = 0; p < cornerCount; p++) {
  441. if (plane.inside(cameraPoints[p])) {
  442. // One point was inside of this plane, so it can not guarantee that all interpolated points between the corners are outside.
  443. allOutside = false;
  444. break;
  445. }
  446. }
  447. // If all points are outside of the same plane in the view frustum...
  448. if (allOutside) {
  449. // ...then we know that all interpolated points in between are also outside of this plane.
  450. return true; // Occluded due to failing culling test.
  451. }
  452. }
  453. IRect pixelBound = getPixelBoundFromProjection(outputHullCorners, cornerCount);
  454. float closestDistance = std::numeric_limits<float>::infinity();
  455. for (int c = 0; c < cornerCount; c++) {
  456. replaceWithSmaller(closestDistance, outputHullCorners[c].cs.z);
  457. }
  458. // Loop over all cells within the bound
  459. IRect outerBound = getOuterCellBound(pixelBound);
  460. for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
  461. for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
  462. if (closestDistance < image_readPixel_clamp(this->depthGrid, cellX, cellY)) {
  463. return false; // Visible because one cell had a more distant maximum depth.
  464. }
  465. }
  466. }
  467. return true; // Occluded, because none of the cells had a more distant depth.
  468. }
  469. // Checks if the box from minimum to maximum in object space is fully occluded when seen by the camera
  470. // Must be the same camera as when occluders filled the grid with occlusion depth
  471. bool isBoxOccluded(const FVector3D &minimum, const FVector3D &maximum, const Transform3D &modelToWorldTransform, const Camera &camera) {
  472. if (!this->receiving) {
  473. throwError("Cannot call renderer_isBoxVisible without first calling renderer_begin and giving occluder shapes to the pass!\n");
  474. }
  475. FVector3D corners[8];
  476. GENERATE_BOX_CORNERS(corners, minimum, maximum)
  477. ProjectedPoint projections[8];
  478. return isHullOccluded(projections, corners, 8, modelToWorldTransform, camera);
  479. }
  480. void giveTask(const Model& model, const Transform3D &modelToWorldTransform, const Camera &camera) {
  481. if (!this->receiving) {
  482. throwError("Cannot call renderer_giveTask before renderer_begin!\n");
  483. }
  484. // If occluders are present, check if the model's bound is visible
  485. if (this->occluded) {
  486. FVector3D minimum, maximum;
  487. model_getBoundingBox(model, minimum, maximum);
  488. if (isBoxOccluded(minimum, maximum, modelToWorldTransform, camera)) {
  489. // Skip projection of triangles if the whole bounding box is already behind occluders
  490. return;
  491. }
  492. }
  493. // TODO: Make an algorithm for selecting if the model should be queued as an instance or triangulated at once
  494. // An extra argument may choose to force an instance directly into the command queue
  495. // Because the model is being borrowed for vertex animation
  496. // To prevent the command queue from getting full hold as much as possible in a sorted list of instances
  497. // When the command queue is full, the solid instances will be drawn front to back before filtered is drawn back to front
  498. model->render(&this->commandQueue, this->colorBuffer, this->depthBuffer, modelToWorldTransform, camera);
  499. }
  500. void endFrame(bool debugWireframe) {
  501. if (!this->receiving) {
  502. throwError("Called renderer_end without renderer_begin!\n");
  503. }
  504. this->receiving = false;
  505. // Mark occluded triangles to prevent them from being rendered
  506. completeOcclusion();
  507. this->commandQueue.execute(IRect::FromSize(this->width, this->height));
  508. if (image_exists(this->colorBuffer)) {
  509. // Debug drawn triangles
  510. if (debugWireframe) {
  511. /*if (image_exists(this->depthGrid)) {
  512. for (int cellY = 0; cellY < this->gridHeight; cellY++) {
  513. for (int cellX = 0; cellX < this->gridWidth; cellX++) {
  514. float depth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
  515. if (depth < std::numeric_limits<float>::infinity()) {
  516. int intensity = depth;
  517. draw_rectangle(this->colorBuffer, IRect(cellX * cellSize + 4, cellY * cellSize + 4, cellSize - 8, cellSize - 8), ColorRgbaI32(intensity, intensity, 0, 255));
  518. }
  519. }
  520. }
  521. }*/
  522. for (int t = 0; t < this->commandQueue.buffer.length(); t++) {
  523. if (!this->commandQueue.buffer[t].occluded) {
  524. ITriangle2D *triangle = &(this->commandQueue.buffer[t].triangle);
  525. draw_line(this->colorBuffer,
  526. triangle->position[0].flat.x / constants::unitsPerPixel, triangle->position[0].flat.y / constants::unitsPerPixel,
  527. triangle->position[1].flat.x / constants::unitsPerPixel, triangle->position[1].flat.y / constants::unitsPerPixel,
  528. ColorRgbaI32(255, 255, 255, 255)
  529. );
  530. draw_line(this->colorBuffer,
  531. triangle->position[1].flat.x / constants::unitsPerPixel, triangle->position[1].flat.y / constants::unitsPerPixel,
  532. triangle->position[2].flat.x / constants::unitsPerPixel, triangle->position[2].flat.y / constants::unitsPerPixel,
  533. ColorRgbaI32(255, 255, 255, 255)
  534. );
  535. draw_line(this->colorBuffer,
  536. triangle->position[2].flat.x / constants::unitsPerPixel, triangle->position[2].flat.y / constants::unitsPerPixel,
  537. triangle->position[0].flat.x / constants::unitsPerPixel, triangle->position[0].flat.y / constants::unitsPerPixel,
  538. ColorRgbaI32(255, 255, 255, 255)
  539. );
  540. }
  541. }
  542. }
  543. // Debug anything else added to debugLines
  544. for (int l = 0; l < this->debugLines.length(); l++) {
  545. draw_line(this->colorBuffer, this->debugLines[l].x1, this->debugLines[l].y1, this->debugLines[l].x2, this->debugLines[l].y2, this->debugLines[l].color);
  546. }
  547. this->debugLines.clear();
  548. }
  549. this->commandQueue.clear();
  550. }
  551. void occludeFromTopRows(const Camera &camera) {
  552. // Make sure that the depth grid exists with the correct dimensions.
  553. this->prepareForOcclusion();
  554. if (!this->receiving) {
  555. throwError("Cannot call renderer_occludeFromTopRows without first calling renderer_begin!\n");
  556. }
  557. if (!image_exists(this->depthBuffer)) {
  558. throwError("Cannot call renderer_occludeFromTopRows without having given a depth buffer in renderer_begin!\n");
  559. }
  560. SafePointer<float> depthRow = image_getSafePointer(this->depthBuffer);
  561. int depthStride = image_getStride(this->depthBuffer);
  562. SafePointer<float> gridRow = image_getSafePointer(this->depthGrid);
  563. int gridStride = image_getStride(this->depthGrid);
  564. if (camera.perspective) {
  565. // Perspective case using 1/depth for the depth buffer.
  566. for (int y = 0; y < this->height; y += cellSize) {
  567. SafePointer<float> gridPixel = gridRow;
  568. SafePointer<float> depthPixel = depthRow;
  569. int x = 0;
  570. int right = cellSize - 1;
  571. float maxInvDistance;
  572. // Scan bottom row of whole cell width
  573. for (int gridX = 0; gridX < this->gridWidth; gridX++) {
  574. maxInvDistance = std::numeric_limits<float>::infinity();
  575. if (right >= this->width) { right = this->width; }
  576. while (x < right) {
  577. float newInvDistance = *depthPixel;
  578. if (newInvDistance < maxInvDistance) { maxInvDistance = newInvDistance; }
  579. depthPixel += 1;
  580. x += 1;
  581. }
  582. float maxDistance = 1.0f / maxInvDistance;
  583. float oldDistance = *gridPixel;
  584. if (maxDistance < oldDistance) {
  585. *gridPixel = maxDistance;
  586. }
  587. gridPixel += 1;
  588. right += cellSize;
  589. }
  590. // Go to the next grid row
  591. depthRow.increaseBytes(depthStride * cellSize);
  592. gridRow.increaseBytes(gridStride);
  593. }
  594. } else {
  595. // Orthogonal case where linear depth is used for both grid and depth buffer.
  596. // TODO: Create test cases for many ways to use occlusion, even these strange cases like isometric occlusion where plain culling does not leave many occluded models.
  597. for (int y = 0; y < this->height; y += cellSize) {
  598. SafePointer<float> gridPixel = gridRow;
  599. SafePointer<float> depthPixel = depthRow;
  600. int x = 0;
  601. int right = cellSize - 1;
  602. float maxDistance;
  603. // Scan bottom row of whole cell width
  604. for (int gridX = 0; gridX < this->gridWidth; gridX++) {
  605. maxDistance = 0.0f;
  606. if (right >= this->width) { right = this->width; }
  607. while (x < right) {
  608. float newDistance = *depthPixel;
  609. if (newDistance > maxDistance) { maxDistance = newDistance; }
  610. depthPixel += 1;
  611. x += 1;
  612. }
  613. float oldDistance = *gridPixel;
  614. if (maxDistance < oldDistance) {
  615. *gridPixel = maxDistance;
  616. }
  617. gridPixel += 1;
  618. right += cellSize;
  619. }
  620. // Go to the next grid row
  621. depthRow.increaseBytes(depthStride * cellSize);
  622. gridRow.increaseBytes(gridStride);
  623. }
  624. }
  625. }
  626. };
  627. Renderer renderer_create() {
  628. return std::make_shared<RendererImpl>();
  629. }
  630. bool renderer_exists(const Renderer& renderer) {
  631. return renderer.get() != nullptr;
  632. }
  633. void renderer_begin(Renderer& renderer, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer) {
  634. MUST_EXIST(renderer,renderer_begin);
  635. renderer->beginFrame(colorBuffer, depthBuffer);
  636. }
  637. // TODO: Synchronous setting
  638. // * Asynchronous (default)
  639. // Only works on models that are locked from further editing
  640. // Locked models can also be safely pooled for reuse then (ResourcePool)
  641. // * Synced (for animation)
  642. // Dispatch triangles directly to the command queue so that the current state of the model is captured
  643. // This allow rendering many instances using the same model at different times
  644. // Enabling vertex light, reflection maps and bone animation
  645. void renderer_giveTask(Renderer& renderer, const Model& model, const Transform3D &modelToWorldTransform, const Camera &camera) {
  646. MUST_EXIST(renderer,renderer_giveTask);
  647. if (model.get() != nullptr) {
  648. renderer->giveTask(model, modelToWorldTransform, camera);
  649. }
  650. }
  651. void renderer_giveTask_triangle(Renderer& renderer,
  652. const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC,
  653. const FVector4D &colorA, const FVector4D &colorB, const FVector4D &colorC,
  654. const FVector4D &texCoordA, const FVector4D &texCoordB, const FVector4D &texCoordC,
  655. const ImageRgbaU8& diffuseMap, const ImageRgbaU8& lightMap,
  656. Filter filter, const Camera &camera) {
  657. #ifndef NDEBUG
  658. if (image_exists(diffuseMap) && !image_isTexture(diffuseMap)) {
  659. throwError("If renderer_addTriangle is given a diffuse map, it must be a valid texture according to the criterias of image_isTexture!");
  660. }
  661. if (image_exists(lightMap) && !image_isTexture(lightMap)) {
  662. throwError("If renderer_addTriangle is given a light map, it must be a valid texture according to the criterias of image_isTexture!");
  663. }
  664. MUST_EXIST(renderer,renderer_addTriangle);
  665. #endif
  666. renderTriangleFromData(
  667. &(renderer->commandQueue), renderer->colorBuffer.get(), renderer->depthBuffer.get(), camera,
  668. posA, posB, posC,
  669. filter, diffuseMap.get(), lightMap.get(),
  670. TriangleTexCoords(texCoordA, texCoordB, texCoordC),
  671. TriangleColors(colorA, colorB, colorC)
  672. );
  673. }
  674. void renderer_occludeFromBox(Renderer& renderer, const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette) {
  675. MUST_EXIST(renderer,renderer_occludeFromBox);
  676. renderer->occludeFromBox(minimum, maximum, modelToWorldTransform, camera, debugSilhouette);
  677. }
  678. void renderer_occludeFromExistingTriangles(Renderer& renderer) {
  679. MUST_EXIST(renderer,renderer_optimize);
  680. renderer->occludeFromExistingTriangles();
  681. }
  682. void renderer_occludeFromTopRows(Renderer& renderer, const Camera &camera) {
  683. MUST_EXIST(renderer,renderer_occludeFromTopRows);
  684. renderer->occludeFromTopRows(camera);
  685. }
  686. bool renderer_isBoxVisible(Renderer& renderer, const FVector3D &minimum, const FVector3D &maximum, const Transform3D &modelToWorldTransform, const Camera &camera) {
  687. MUST_EXIST(renderer,renderer_isBoxVisible);
  688. return !(renderer->isBoxOccluded(minimum, maximum, modelToWorldTransform, camera));
  689. }
  690. void renderer_end(Renderer& renderer, bool debugWireframe) {
  691. MUST_EXIST(renderer,renderer_end);
  692. renderer->endFrame(debugWireframe);
  693. }
  694. }