modelAPI.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  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. #include "modelAPI.h"
  24. #include "imageAPI.h"
  25. #include "drawAPI.h"
  26. #include "../render/model/Model.h"
  27. #include <limits>
  28. #define MUST_EXIST(OBJECT, METHOD) if (OBJECT.get() == nullptr) { throwError("The " #OBJECT " handle was null in " #METHOD "\n"); }
  29. namespace dsr {
  30. Model model_create() {
  31. return std::make_shared<ModelImpl>();
  32. }
  33. Model model_clone(const Model& model) {
  34. MUST_EXIST(model,model_clone);
  35. return std::make_shared<ModelImpl>(model->filter, model->partBuffer, model->positionBuffer);
  36. }
  37. void model_setFilter(const Model& model, Filter filter) {
  38. MUST_EXIST(model,model_setFilter);
  39. model->filter = filter;
  40. }
  41. Filter model_getFilter(const Model& model) {
  42. MUST_EXIST(model,model_getFilter);
  43. return model->filter;
  44. }
  45. bool model_exists(const Model& model) {
  46. return model.get() != nullptr;
  47. }
  48. int model_addEmptyPart(Model& model, const String &name) {
  49. MUST_EXIST(model,model_addEmptyPart);
  50. return model->addEmptyPart(name);
  51. }
  52. int model_getNumberOfParts(const Model& model) {
  53. MUST_EXIST(model,model_getNumberOfParts);
  54. return model->getNumberOfParts();
  55. }
  56. void model_setPartName(Model& model, int partIndex, const String &name) {
  57. MUST_EXIST(model,model_setPartName);
  58. model->setPartName(partIndex, name);
  59. }
  60. String model_getPartName(const Model& model, int partIndex) {
  61. MUST_EXIST(model,model_getPartName);
  62. return model->getPartName(partIndex);
  63. }
  64. int model_getNumberOfPoints(const Model& model) {
  65. MUST_EXIST(model,model_getNumberOfPoints);
  66. return model->getNumberOfPoints();
  67. }
  68. FVector3D model_getPoint(const Model& model, int pointIndex) {
  69. MUST_EXIST(model,model_getPoint);
  70. return model->getPoint(pointIndex);
  71. }
  72. void model_setPoint(Model& model, int pointIndex, const FVector3D& position) {
  73. MUST_EXIST(model,model_setPoint);
  74. model->setPoint(pointIndex, position);
  75. }
  76. int model_findPoint(const Model& model, const FVector3D &position, float threshold) {
  77. MUST_EXIST(model,model_findPoint);
  78. return model->findPoint(position, threshold);
  79. }
  80. int model_addPoint(const Model& model, const FVector3D &position) {
  81. MUST_EXIST(model,model_addPoint);
  82. return model->addPoint(position);
  83. }
  84. int model_addPointIfNeeded(Model& model, const FVector3D &position, float threshold) {
  85. MUST_EXIST(model,model_addPointIfNeeded);
  86. return model->addPointIfNeeded(position, threshold);
  87. }
  88. int model_getVertexPointIndex(const Model& model, int partIndex, int polygonIndex, int vertexIndex) {
  89. MUST_EXIST(model,model_getVertexPointIndex);
  90. return model->getVertexPointIndex(partIndex, polygonIndex, vertexIndex);
  91. }
  92. void model_setVertexPointIndex(Model& model, int partIndex, int polygonIndex, int vertexIndex, int pointIndex) {
  93. MUST_EXIST(model,model_setVertexPointIndex);
  94. model->setVertexPointIndex(partIndex, polygonIndex, vertexIndex, pointIndex);
  95. }
  96. FVector3D model_getVertexPosition(const Model& model, int partIndex, int polygonIndex, int vertexIndex) {
  97. MUST_EXIST(model,model_getVertexPosition);
  98. return model->getVertexPosition(partIndex, polygonIndex, vertexIndex);
  99. }
  100. FVector4D model_getVertexColor(const Model& model, int partIndex, int polygonIndex, int vertexIndex) {
  101. MUST_EXIST(model,model_getVertexColor);
  102. return model->getVertexColor(partIndex, polygonIndex, vertexIndex);
  103. }
  104. void model_setVertexColor(Model& model, int partIndex, int polygonIndex, int vertexIndex, const FVector4D& color) {
  105. MUST_EXIST(model,model_setVertexColor);
  106. model->setVertexColor(partIndex, polygonIndex, vertexIndex, color);
  107. }
  108. FVector4D model_getTexCoord(const Model& model, int partIndex, int polygonIndex, int vertexIndex) {
  109. MUST_EXIST(model,model_getTexCoord);
  110. return model->getTexCoord(partIndex, polygonIndex, vertexIndex);
  111. }
  112. void model_setTexCoord(Model& model, int partIndex, int polygonIndex, int vertexIndex, const FVector4D& texCoord) {
  113. MUST_EXIST(model,model_setTexCoord);
  114. model->setTexCoord(partIndex, polygonIndex, vertexIndex, texCoord);
  115. }
  116. int model_addTriangle(Model& model, int partIndex, int pointA, int pointB, int pointC) {
  117. MUST_EXIST(model,model_addTriangle);
  118. return model->addPolygon(Polygon(pointA, pointB, pointC), partIndex);
  119. }
  120. int model_addQuad(Model& model, int partIndex, int pointA, int pointB, int pointC, int pointD) {
  121. MUST_EXIST(model,model_addQuad);
  122. return model->addPolygon(Polygon(pointA, pointB, pointC, pointD), partIndex);
  123. }
  124. int model_getNumberOfPolygons(const Model& model, int partIndex) {
  125. MUST_EXIST(model,model_getNumberOfPolygons);
  126. return model->getNumberOfPolygons(partIndex);
  127. }
  128. int model_getPolygonVertexCount(const Model& model, int partIndex, int polygonIndex) {
  129. MUST_EXIST(model,model_getPolygonVertexCount);
  130. return model->getPolygonVertexCount(partIndex, polygonIndex);
  131. }
  132. ImageRgbaU8 model_getDiffuseMap(const Model& model, int partIndex) {
  133. MUST_EXIST(model,model_getDiffuseMap);
  134. return model->getDiffuseMap(partIndex);
  135. }
  136. void model_setDiffuseMap(Model& model, int partIndex, const ImageRgbaU8 &diffuseMap) {
  137. MUST_EXIST(model,model_setDiffuseMap);
  138. model->setDiffuseMap(diffuseMap, partIndex);
  139. }
  140. void model_setDiffuseMapByName(Model& model, int partIndex, ResourcePool &pool, const String &filename) {
  141. MUST_EXIST(model,model_setDiffuseMapByName);
  142. model->setDiffuseMapByName(pool, filename, partIndex);
  143. }
  144. ImageRgbaU8 model_getLightMap(Model& model, int partIndex) {
  145. MUST_EXIST(model,model_getLightMap);
  146. return model->getLightMap(partIndex);
  147. }
  148. void model_setLightMap(Model& model, int partIndex, const ImageRgbaU8 &lightMap) {
  149. MUST_EXIST(model,model_setLightMap);
  150. model->setLightMap(lightMap, partIndex);
  151. }
  152. void model_setLightMapByName(Model& model, int partIndex, ResourcePool &pool, const String &filename) {
  153. MUST_EXIST(model,model_setLightMapByName);
  154. model->setLightMapByName(pool, filename, partIndex);
  155. }
  156. // Single-threaded rendering for the simple cases where you just want it to work
  157. void model_render(const Model& model, const Transform3D &modelToWorldTransform, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer, const Camera &camera) {
  158. if (model.get() != nullptr) {
  159. model->render((CommandQueue*)nullptr, colorBuffer, depthBuffer, modelToWorldTransform, camera);
  160. }
  161. }
  162. void model_renderDepth(const Model& model, const Transform3D &modelToWorldTransform, ImageF32& depthBuffer, const Camera &camera) {
  163. if (model.get() != nullptr) {
  164. model->renderDepth(depthBuffer, modelToWorldTransform, camera);
  165. }
  166. }
  167. void model_getBoundingBox(const Model& model, FVector3D& minimum, FVector3D& maximum) {
  168. MUST_EXIST(model,model_getBoundingBox);
  169. minimum = model->minBound;
  170. maximum = model->maxBound;
  171. }
  172. static const int cellSize = 16;
  173. struct DebugLine {
  174. int64_t x1, y1, x2, y2;
  175. ColorRgbaI32 color;
  176. DebugLine(int64_t x1, int64_t y1, int64_t x2, int64_t y2, const ColorRgbaI32& color)
  177. : x1(x1), y1(y1), x2(x2), y2(y2), color(color) {}
  178. };
  179. // Context for rendering multiple models at the same time for improved speed
  180. class RendererImpl {
  181. private:
  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. public:
  191. RendererImpl() {}
  192. void beginFrame(ImageRgbaU8& colorBuffer, ImageF32& depthBuffer) {
  193. if (this->receiving) {
  194. throwError("Called renderer_begin on the same renderer twice without ending the previous batch!\n");
  195. }
  196. this->receiving = true;
  197. this->colorBuffer = colorBuffer;
  198. this->depthBuffer = depthBuffer;
  199. if (image_exists(this->colorBuffer)) {
  200. this->width = image_getWidth(this->colorBuffer);
  201. this->height = image_getHeight(this->colorBuffer);
  202. } else if (image_exists(this->depthBuffer)) {
  203. this->width = image_getWidth(this->depthBuffer);
  204. this->height = image_getHeight(this->depthBuffer);
  205. }
  206. this->gridWidth = (this->width + (cellSize - 1)) / cellSize;
  207. this->gridHeight = (this->height + (cellSize - 1)) / cellSize;
  208. this->occluded = false;
  209. }
  210. void giveTask(const Model& model, const Transform3D &modelToWorldTransform, const Camera &camera) {
  211. if (!this->receiving) {
  212. throwError("Cannot call renderer_giveTask before renderer_begin!\n");
  213. }
  214. // TODO: Make an algorithm for selecting if the model should be queued as an instance or triangulated at once
  215. // An extra argument may choose to force an instance directly into the command queue
  216. // Because the model is being borrowed for vertex animation
  217. // To prevent the command queue from getting full hold as much as possible in a sorted list of instances
  218. // When the command queue is full, the solid instances will be drawn front to back before filtered is drawn back to front
  219. model->render(&this->commandQueue, this->colorBuffer, this->depthBuffer, modelToWorldTransform, camera);
  220. }
  221. bool pointInsideOfEdge(const LVector2D &edgeA, const LVector2D &edgeB, const LVector2D &point) {
  222. LVector2D edgeDirection = LVector2D(edgeB.y - edgeA.y, edgeA.x - edgeB.x);
  223. LVector2D relativePosition = point - edgeA;
  224. int64_t dotProduct = (edgeDirection.x * relativePosition.x) + (edgeDirection.y * relativePosition.y);
  225. return dotProduct <= 0;
  226. }
  227. // Returns true iff the point is inside of the hull
  228. // convexHullCorners from 0 to cornerCount-1 must be sorted clockwise and may not include any concave corners
  229. bool pointInsideOfHull(const ProjectedPoint* convexHullCorners, int cornerCount, const LVector2D &point) {
  230. for (int c = 0; c < cornerCount; c++) {
  231. int nc = c + 1;
  232. if (nc == cornerCount) {
  233. nc = 0;
  234. }
  235. if (!pointInsideOfEdge(convexHullCorners[c].flat, convexHullCorners[nc].flat, point)) {
  236. // Outside of one edge, not inside
  237. return false;
  238. }
  239. }
  240. // Passed all edge tests
  241. return true;
  242. }
  243. // Returns true iff all cornets of the rectangle are inside of the hull
  244. bool rectangleInsideOfHull(const ProjectedPoint* convexHullCorners, int cornerCount, const IRect &rectangle) {
  245. return pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.left(), rectangle.top()))
  246. && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.right(), rectangle.top()))
  247. && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.left(), rectangle.bottom()))
  248. && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.right(), rectangle.bottom()));
  249. }
  250. IRect getOuterCellBound(const IRect &pixelBound) {
  251. int minCellX = pixelBound.left() / cellSize;
  252. int maxCellX = pixelBound.right() / cellSize + 1;
  253. int minCellY = pixelBound.top() / cellSize;
  254. int maxCellY = pixelBound.bottom() / cellSize + 1;
  255. if (minCellX < 0) { minCellX = 0; }
  256. if (minCellY < 0) { minCellY = 0; }
  257. if (maxCellX > this->gridWidth) { maxCellX = this->gridWidth; }
  258. if (maxCellY > this->gridHeight) { maxCellY = this->gridHeight; }
  259. return IRect(minCellX, minCellY, maxCellX - minCellX, maxCellY - minCellY);
  260. }
  261. // Called before occluding so that the grid is initialized once when used and skipped when not used
  262. void prepareForOcclusion() {
  263. if (!this->occluded) {
  264. // Allocate the grid if a sufficiently large one does not already exist
  265. if (!(image_exists(this->depthGrid) && image_getWidth(this->depthGrid) >= gridWidth && image_getHeight(this->depthGrid) >= gridHeight)) {
  266. this->depthGrid = image_create_F32(gridWidth, gridHeight);
  267. }
  268. // Use inifnite depth in camera space
  269. image_fill(this->depthGrid, std::numeric_limits<float>::infinity());
  270. }
  271. this->occluded = true;
  272. }
  273. // If any occluder has been used during this pass, all triangles in the buffer will be filtered based using depthGrid
  274. void completeOcclusion() {
  275. if (this->occluded) {
  276. for (int t = this->commandQueue.buffer.length() - 1; t >= 0; t--) {
  277. bool anyVisible = false;
  278. ITriangle2D triangle = this->commandQueue.buffer[t].triangle;
  279. IRect outerBound = getOuterCellBound(triangle.wholeBound);
  280. for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
  281. for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
  282. // TODO: Optimize access using SafePointer iteration
  283. float backgroundDepth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
  284. float triangleDepth = triangle.position[0].cs.z;
  285. replaceWithSmaller(triangleDepth, triangle.position[1].cs.z);
  286. replaceWithSmaller(triangleDepth, triangle.position[2].cs.z);
  287. if (triangleDepth < backgroundDepth + 0.001) {
  288. anyVisible = true;
  289. }
  290. }
  291. }
  292. if (!anyVisible) {
  293. // TODO: Make triangle swapping work so that the list can be sorted
  294. this->commandQueue.buffer[t].occluded = true;
  295. }
  296. }
  297. }
  298. }
  299. void occludeFromSortedHull(const ProjectedPoint* convexHullCorners, int cornerCount, const IRect& pixelBound) {
  300. // Loop over the outer bound
  301. if (pixelBound.width() > cellSize && pixelBound.height() > cellSize) {
  302. float distance = 0.0f;
  303. for (int c = 0; c < cornerCount; c++) {
  304. replaceWithLarger(distance, convexHullCorners[c].cs.z);
  305. }
  306. // Loop over all cells within the bound
  307. IRect outerBound = getOuterCellBound(pixelBound);
  308. for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
  309. for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
  310. IRect pixelRegion = IRect(cellX * cellSize, cellY * cellSize, cellSize, cellSize);
  311. IRect subPixelRegion = pixelRegion * constants::unitsPerPixel;
  312. if (rectangleInsideOfHull(convexHullCorners, cornerCount, subPixelRegion)) {
  313. float oldDepth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
  314. if (distance < oldDepth) {
  315. image_writePixel(this->depthGrid, cellX, cellY, distance);
  316. }
  317. }
  318. }
  319. }
  320. }
  321. }
  322. void occludeFromSortedHull(const ProjectedPoint* convexHullCorners, int cornerCount) {
  323. IRect pixelBound = IRect(convexHullCorners[0].flat.x / constants::unitsPerPixel, convexHullCorners[0].flat.y / constants::unitsPerPixel, 1, 1);
  324. for (int p = 1; p < cornerCount; p++) {
  325. pixelBound = IRect::merge(pixelBound, IRect(convexHullCorners[p].flat.x / constants::unitsPerPixel, convexHullCorners[p].flat.y / constants::unitsPerPixel, 1, 1));
  326. }
  327. occludeFromSortedHull(convexHullCorners, cornerCount, pixelBound);
  328. }
  329. void occludeFromExistingTriangles() {
  330. prepareForOcclusion();
  331. // Generate a depth grid to remove many small triangles behind larger triangles
  332. // This will leave triangles along seams but at least begin to remove the worst unwanted drawing
  333. for (int t = 0; t < this->commandQueue.buffer.length(); t++) {
  334. // Get the current triangle from the queue
  335. Filter filter = this->commandQueue.buffer[t].filter;
  336. if (filter == Filter::Solid) {
  337. ITriangle2D triangle = this->commandQueue.buffer[t].triangle;
  338. occludeFromSortedHull(triangle.position, 3, triangle.wholeBound);
  339. }
  340. }
  341. }
  342. bool counterClockwise(const ProjectedPoint& p, const ProjectedPoint& q, const ProjectedPoint& r) {
  343. 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;
  344. }
  345. // outputHullCorners must be at least as big as inputHullCorners, so that it can hold the worst case output size.
  346. // Instead of not allowing less than three points, it copies the input as output when it happens to reduce pre-conditions.
  347. void jarvisConvexHullAlgorithm(ProjectedPoint* outputHullCorners, int& outputCornerCount, const ProjectedPoint* inputHullCorners, int inputCornerCount) {
  348. if (inputCornerCount < 3) {
  349. outputCornerCount = inputCornerCount;
  350. for (int p = 0; p < inputCornerCount; p++) {
  351. outputHullCorners[p] = inputHullCorners[p];
  352. }
  353. } else {
  354. int l = 0;
  355. outputCornerCount = 0;
  356. for (int i = 1; i < inputCornerCount; i++) {
  357. if (inputHullCorners[i].flat.x < inputHullCorners[l].flat.x) {
  358. l = i;
  359. }
  360. }
  361. int p = l;
  362. do {
  363. if (outputCornerCount >= inputCornerCount) {
  364. // Prevent getting stuck in an infinite loop from overflow
  365. return;
  366. }
  367. outputHullCorners[outputCornerCount] = inputHullCorners[p]; outputCornerCount++;
  368. int q = (p + 1) % inputCornerCount;
  369. for (int i = 0; i < inputCornerCount; i++) {
  370. if (counterClockwise(inputHullCorners[p], inputHullCorners[i], inputHullCorners[q])) {
  371. q = i;
  372. }
  373. }
  374. p = q;
  375. } while (p != l);
  376. }
  377. }
  378. // Transform and project the corners of a hull, so that the output can be given to the convex hull algorithm and used for occluding
  379. // Returns true if occluder culling passed, which may skip occluders that could have been visible
  380. bool projectHull(ProjectedPoint* outputHullCorners, const FVector3D* inputHullCorners, int cornerCount, const Transform3D &modelToWorldTransform, const Camera &camera) {
  381. int outsideCount = 0;
  382. for (int p = 0; p < cornerCount; p++) {
  383. FVector3D worldPoint = modelToWorldTransform.transformPoint(inputHullCorners[p]);
  384. FVector3D cameraPoint = camera.worldToCamera(worldPoint);
  385. if (cameraPoint.z < camera.nearClip || cameraPoint.z > camera.farClip) {
  386. // Too close or far away to make a difference
  387. return false;
  388. }
  389. for (int s = 0; s < camera.cullFrustum.getPlaneCount(); s++) {
  390. FPlane3D plane = camera.cullFrustum.getPlane(s);
  391. if (!plane.inside(cameraPoint)) {
  392. outsideCount++;
  393. }
  394. }
  395. outputHullCorners[p] = camera.cameraToScreen(cameraPoint);
  396. }
  397. return outsideCount < 8;
  398. }
  399. void occludeFromBox(const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette) {
  400. prepareForOcclusion();
  401. static const int pointCount = 8;
  402. FVector3D localPoints[pointCount];
  403. ProjectedPoint projections[pointCount];
  404. ProjectedPoint edgeCorners[pointCount];
  405. localPoints[0] = FVector3D(minimum.x, minimum.y, minimum.z);
  406. localPoints[1] = FVector3D(minimum.x, minimum.y, maximum.z);
  407. localPoints[2] = FVector3D(minimum.x, maximum.y, minimum.z);
  408. localPoints[3] = FVector3D(minimum.x, maximum.y, maximum.z);
  409. localPoints[4] = FVector3D(maximum.x, minimum.y, minimum.z);
  410. localPoints[5] = FVector3D(maximum.x, minimum.y, maximum.z);
  411. localPoints[6] = FVector3D(maximum.x, maximum.y, minimum.z);
  412. localPoints[7] = FVector3D(maximum.x, maximum.y, maximum.z);
  413. if (projectHull(projections, localPoints, 8, modelToWorldTransform, camera)) {
  414. // Get a 2D convex hull from the projected corners
  415. int edgeCornerCount = 0;
  416. jarvisConvexHullAlgorithm(edgeCorners, edgeCornerCount, projections, 8);
  417. occludeFromSortedHull(edgeCorners, edgeCornerCount);
  418. // Allow saving the 2D silhouette for debugging
  419. if (debugSilhouette) {
  420. for (int p = 0; p < edgeCornerCount; p++) {
  421. int q = (p + 1) % edgeCornerCount;
  422. if (projections[p].cs.z > camera.nearClip) {
  423. this->debugLines.pushConstruct(
  424. edgeCorners[p].flat.x / constants::unitsPerPixel, edgeCorners[p].flat.y / constants::unitsPerPixel,
  425. edgeCorners[q].flat.x / constants::unitsPerPixel, edgeCorners[q].flat.y / constants::unitsPerPixel,
  426. ColorRgbaI32(64, 128, 128, 255)
  427. );
  428. }
  429. }
  430. }
  431. }
  432. }
  433. void endFrame(bool debugWireframe) {
  434. if (!this->receiving) {
  435. throwError("Called renderer_end without renderer_begin!\n");
  436. }
  437. this->receiving = false;
  438. completeOcclusion();
  439. this->commandQueue.execute(IRect::FromSize(this->width, this->height));
  440. if (image_exists(this->colorBuffer)) {
  441. // Debug drawn triangles
  442. if (debugWireframe) {
  443. if (image_exists(this->depthGrid)) {
  444. for (int cellY = 0; cellY < this->gridHeight; cellY++) {
  445. for (int cellX = 0; cellX < this->gridWidth; cellX++) {
  446. float depth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
  447. if (depth < std::numeric_limits<float>::infinity()) {
  448. int intensity = depth;
  449. draw_rectangle(this->colorBuffer, IRect(cellX * cellSize + 4, cellY * cellSize + 4, cellSize - 8, cellSize - 8), ColorRgbaI32(intensity, intensity, 0, 255));
  450. }
  451. }
  452. }
  453. }
  454. for (int t = 0; t < this->commandQueue.buffer.length(); t++) {
  455. if (!this->commandQueue.buffer[t].occluded) {
  456. ITriangle2D *triangle = &(this->commandQueue.buffer[t].triangle);
  457. draw_line(this->colorBuffer,
  458. triangle->position[0].flat.x / constants::unitsPerPixel, triangle->position[0].flat.y / constants::unitsPerPixel,
  459. triangle->position[1].flat.x / constants::unitsPerPixel, triangle->position[1].flat.y / constants::unitsPerPixel,
  460. ColorRgbaI32(255, 255, 255, 255)
  461. );
  462. draw_line(this->colorBuffer,
  463. triangle->position[1].flat.x / constants::unitsPerPixel, triangle->position[1].flat.y / constants::unitsPerPixel,
  464. triangle->position[2].flat.x / constants::unitsPerPixel, triangle->position[2].flat.y / constants::unitsPerPixel,
  465. ColorRgbaI32(255, 255, 255, 255)
  466. );
  467. draw_line(this->colorBuffer,
  468. triangle->position[2].flat.x / constants::unitsPerPixel, triangle->position[2].flat.y / constants::unitsPerPixel,
  469. triangle->position[0].flat.x / constants::unitsPerPixel, triangle->position[0].flat.y / constants::unitsPerPixel,
  470. ColorRgbaI32(255, 255, 255, 255)
  471. );
  472. }
  473. }
  474. }
  475. // Debug anything else added to debugLines
  476. for (int l = 0; l < this->debugLines.length(); l++) {
  477. draw_line(this->colorBuffer, this->debugLines[l].x1, this->debugLines[l].y1, this->debugLines[l].x2, this->debugLines[l].y2, this->debugLines[l].color);
  478. }
  479. this->debugLines.clear();
  480. }
  481. this->commandQueue.clear();
  482. }
  483. };
  484. Renderer renderer_create() {
  485. return std::make_shared<RendererImpl>();
  486. }
  487. bool renderer_exists(const Renderer& renderer) {
  488. return renderer.get() != nullptr;
  489. }
  490. void renderer_begin(Renderer& renderer, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer) {
  491. MUST_EXIST(renderer,renderer_begin);
  492. renderer->beginFrame(colorBuffer, depthBuffer);
  493. }
  494. // TODO: Synchronous setting
  495. // * Asynchronous (default)
  496. // Only works on models that are locked from further editing
  497. // Locked models can also be safely pooled for reuse then (ResourcePool)
  498. // * Synced (for animation)
  499. // Dispatch triangles directly to the command queue so that the current state of the model is captured
  500. // This allow rendering many instances using the same model at different times
  501. // Enabling vertex light, reflection maps and bone animation
  502. void renderer_giveTask(Renderer& renderer, const Model& model, const Transform3D &modelToWorldTransform, const Camera &camera) {
  503. MUST_EXIST(renderer,renderer_giveTask);
  504. if (model.get() != nullptr) {
  505. renderer->giveTask(model, modelToWorldTransform, camera);
  506. }
  507. }
  508. void renderer_occludeFromBox(Renderer& renderer, const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette) {
  509. MUST_EXIST(renderer,renderer_occludeFromBox);
  510. renderer->occludeFromBox(minimum, maximum, modelToWorldTransform, camera, debugSilhouette);
  511. }
  512. void renderer_occludeFromExistingTriangles(Renderer& renderer) {
  513. MUST_EXIST(renderer,renderer_optimize);
  514. renderer->occludeFromExistingTriangles();
  515. }
  516. void renderer_end(Renderer& renderer, bool debugWireframe) {
  517. MUST_EXIST(renderer,renderer_end);
  518. renderer->endFrame(debugWireframe);
  519. }
  520. }