modelAPI.h 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2018 to 2025 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. // A reference API for 3D models, designed to make it easy to get started with 3D rendering.
  24. // Once you are too limited by the model API, you can call the renderer API directly with triangles from your own representation.
  25. #ifndef DFPSR_API_MODEL
  26. #define DFPSR_API_MODEL
  27. #include "rendererAPI.h"
  28. namespace dsr {
  29. // A handle to a model.
  30. class ModelImpl;
  31. using Model = Handle<ModelImpl>;
  32. }
  33. // This header depends on defining Model ahead of including it to avoid cyclic dependencies.
  34. #include "../implementation/render/model/format/dmf1.h"
  35. // TODO: Create a folder with types.
  36. namespace dsr {
  37. // Normalized texture coordinates:
  38. // (0.0f, 0.0f) is the texture coordinate for the upper left corner of the upper left pixel in the 2D texture.
  39. // (1.0f, 0.0f) is the texture coordinate for the upper right corner of the upper right pixel in the 2D texture.
  40. // (0.0f, 1.0f) is the texture coordinate for the lower left corner of the lower left pixel in the 2D texture.
  41. // (1.0f, 1.0f) is the texture coordinate for the lower right corner of the lower right pixel in the 2D texture.
  42. // (0.5f, 0.5f) is the texture coordinate for the center of the 2D texture.
  43. // Texture sampling:
  44. // By default, texture sampling is looped around the edges with bilinear with mip-maps for diffuse textures when available.
  45. // In bi-linear interpolation, the center of the pixel has the full weight of the color while the sides may show pixels from the other end of the texture.
  46. // When getting further away or viewing from the side, a lower resolution version of the texture will be taken from the pyramid if it was generated.
  47. // Seams between resolutions will be hard seams, so avoid using hard lines in textures if you want it to look natural.
  48. // Loading textures from a ResourcePool will automatically call image_generatePyramid.
  49. // Images without the power of two dimensions needed to generate a pyramid can not be used as textures in models.
  50. // image_isTexture can be used to know if an image has the supported dimensions for the current version of the renderer.
  51. // Side-effect: Creates a new empty model.
  52. // Post-condition: Returns a reference counted handle to the new model.
  53. // The model will be deleted automatically when all handles are gone.
  54. Model model_create();
  55. // Clones the geometry but refers to the same textures to save memory.
  56. // Pre-condition: model must refer to an existing model.
  57. // Post-condition: Returns a reference counted handle to the clone of model.
  58. Model model_clone(const Model& model);
  59. // Assign a filter to the whole model.
  60. // Assigning filters per model makes it easier to draw solid models before filtered models.
  61. // Two separate models can be used if you need both solid and filtered geometry.
  62. // Filters:
  63. // Filter::Alpha uses the alpha channel from the shader as opacity.
  64. // Filter::Solid is the default setting for newly created models.
  65. // Pre-condition: model must refer to an existing model.
  66. // Side-effect: Sets the given model's filter to the given filter argument.
  67. void model_setFilter(const Model& model, Filter filter);
  68. // Get back the filter enumeration, which was assigned to the model using model_setFilter.
  69. // This is useful for knowing in which pass to render your model.
  70. // Pre-condition: model must refer to an existing model.
  71. // Post-condition: Returns the model's filter enumeration
  72. Filter model_getFilter(const Model& model);
  73. // Post-condition: Returns true iff the model exists.
  74. bool model_exists(const Model& model);
  75. // Each part contains material settings and a list of polygons.
  76. // Each polygon contains 3 or 4 vertices. (triangles and quads)
  77. // Each vertex has with its own color, texture coordinates and position index.
  78. // Position indices refers to the model's list of points,
  79. // which is shared across multiple parts to avoid gaps between materials.
  80. // Pre-condition: model must refer to an existing model.
  81. // Side-effect: Adds an empty part without any polygons and returns its new local part index.
  82. // The returned part index is relative to the model and goes from 0 to model_getNumberOfParts(model) - 1.
  83. int model_addEmptyPart(Model& model, const String &name);
  84. // Pre-condition: model must refer to an existing model.
  85. // Post-condition: Returns the number of parts in model.
  86. int model_getNumberOfParts(const Model& model);
  87. // Pre-condition: model must refer to an existing model.
  88. // Side-effect: Sets the part at partIndex in model to the new name.
  89. void model_setPartName(Model& model, int partIndex, const String &name);
  90. // Pre-condition: model must refer to an existing model.
  91. // Post-condition: Returns the name of the part at partIndex in model.
  92. String model_getPartName(const Model& model, int partIndex);
  93. // Pre-condition: model must refer to an existing model.
  94. // Post-condition: Returns the number of points in model.
  95. int model_getNumberOfPoints(const Model& model);
  96. // Pre-condition: model must refer to an existing model.
  97. // Post-condition: Returns the 3D position of the point at pointIndex in model.
  98. FVector3D model_getPoint(const Model& model, int pointIndex);
  99. // Pre-condition: model must refer to an existing model.
  100. void model_setPoint(Model& model, int pointIndex, const FVector3D& position);
  101. // Pre-condition: model must refer to an existing model.
  102. // Post-condition:
  103. // Returns an index to the closest point in model relative to position in euclidean distance.
  104. // Returns -1 if none was inside of threshold.
  105. // A point p is inside of threshold iff |p - position| < threshold.
  106. // If multiple points have the same distance approximated, the point with the lowest index will be preferred.
  107. int model_findPoint(const Model& model, const FVector3D &position, float threshold);
  108. // Add a point even if it overlaps an existing point.
  109. // Can be used for animation where the initial position might not always be the same.
  110. // Pre-condition: model must refer to an existing model.
  111. // Side-effect: Adds a new point to model at position.
  112. // Post-condition: Returns a local index to the new point.
  113. int model_addPoint(const Model& model, const FVector3D &position);
  114. // Add a point, only if it does not overlap.
  115. // Can be used to seal small gaps and reduce the time needed to transform vertex positions.
  116. // Pre-condition: model must refer to an existing model.
  117. // Side-effect: Adds a new point to model at position unless another point already exists within threshold so that model_findPoint(model, position, threshold) > -1.
  118. // Post-condition:
  119. // If a new point was created then its new index is returned.
  120. // Otherwise, if any existing point was within threshold,
  121. // then the index of the closest existing point in euclidean distance is returned.
  122. // If multiple existing points are within the same distance,
  123. // then the point with the lowest index is preferred, just like in model_findPoint.
  124. int model_addPointIfNeeded(Model& model, const FVector3D &position, float threshold);
  125. // Get the bounding box, which expands automatically when adding or moving points in the model.
  126. // Side-effect: Writes model's bounding box to minimum and maximum by reference.
  127. void model_getBoundingBox(const Model& model, FVector3D& minimum, FVector3D& maximum);
  128. // Get the vertex position's index, which refers to a shared point in the model.
  129. // Pre-condition: model must refer to an existing model.
  130. // Post-condition: Returns the position index of the vertex. (At vertexIndex in the polygon at polygonIndex in the part at partIndex in model.)
  131. int model_getVertexPointIndex(const Model& model, int partIndex, int polygonIndex, int vertexIndex);
  132. // Pre-condition: model must refer to an existing model.
  133. // Side-effect: Sets the position index of the vertex to pointIndex. (At vertexIndex in the polygon at polygonIndex in the part at partIndex in model.)
  134. void model_setVertexPointIndex(Model& model, int partIndex, int polygonIndex, int vertexIndex, int pointIndex);
  135. // Get the vertex position directly, without having to look it up by index using model_getPoint.
  136. // Pre-condition: model must refer to an existing model.
  137. // Post-condition: Returns the position of the vertex. (At vertexIndex in the polygon at polygonIndex in the part at partIndex in model.)
  138. FVector3D model_getVertexPosition(const Model& model, int partIndex, int polygonIndex, int vertexIndex);
  139. // Get the vertex color, which is not shared with any other polygons. (Red, green, blue, alpha) channels are packed as (x, y, z, w) in FVector4D.
  140. // Vertex colors use a normalized scale from 0.0f to 1.0f.
  141. // Transparent black is FVector4D(0.0f, 0.0f, 0.0f, 0.0f).
  142. // Solid red is FVector4D(1.0f, 0.0f, 0.0f, 1.0f).
  143. // Solid green is FVector4D(0.0f, 1.0f, 0.0f, 1.0f).
  144. // Solid blue is FVector4D(0.0f, 0.0f, 1.0f, 1.0f).
  145. // Half opaque orange is FVector4D(1.0f, 0.5f, 0.0f, 0.5f).
  146. // Pre-condition: model must refer to an existing model.
  147. // Post-condition: Returns the color of the vertex. (At vertexIndex in the polygon at polygonIndex in the part at partIndex in model.)
  148. FVector4D model_getVertexColor(const Model& model, int partIndex, int polygonIndex, int vertexIndex);
  149. // Set the vertex color using the same system as model_getVertexColor.
  150. // Pre-condition: model must refer to an existing model.
  151. // Side-effect: Sets the color of the vertex to color. (At vertexIndex in the polygon at polygonIndex in the part at partIndex in model.)
  152. void model_setVertexColor(Model& model, int partIndex, int polygonIndex, int vertexIndex, const FVector4D& color);
  153. // Get (U1, V1, U2, V2) texture coordinates packed as (x, y, z, w) in FVector4D.
  154. // UV1 coordinates (x, y) refers to normalized texture sampling coordinates for the diffuse-map.
  155. // UV2 coordinates (z, w) refers to normalized texture sampling coordinates for the light-map.
  156. // Light-maps do not use mip-map layers, which allow generating light-maps dynamically.
  157. // Pre-condition: model must refer to an existing model.
  158. FVector4D model_getTexCoord(const Model& model, int partIndex, int polygonIndex, int vertexIndex);
  159. // Pre-condition: model must refer to an existing model.
  160. // Side-effect: Sets the texture coordinates of the vertex to texCoord for both UV1 and UV2.
  161. // (At vertexIndex in the polygon at polygonIndex in the part at partIndex in model.)
  162. void model_setTexCoord(Model& model, int partIndex, int polygonIndex, int vertexIndex, const FVector4D& texCoord);
  163. // Create a triangle surface at given position indices.
  164. // The fourth vertex is used as padding, so quads and triangles take the same amount of memory per polygon.
  165. // Using two triangles instead of one quad would use twice as much memory.
  166. // Pre-condition: model must refer to an existing model.
  167. // Side-effect:
  168. // Adds a new polygon in the model's part at partIndex.
  169. // The new polygon contains three vertices.
  170. // Each new vertex has texture coordinates set to the upper left corner using (0.0f, 0.0f, 0.0f, 0.0f).
  171. // Each new vertex has the color set to solid white using (1.0f, 1.0f, 1.0f, 1.0f).
  172. // Post-condition:
  173. // Returns the new polygon's local index within the part at partIndex in model.
  174. int model_addTriangle(Model& model, int partIndex, int pointA, int pointB, int pointC);
  175. // Create a quad surface at given position indices.
  176. // Pre-condition: model must refer to an existing model.
  177. // Side-effect:
  178. // Adds a new polygon in the model's part at partIndex.
  179. // The new polygon contains four vertices.
  180. // Each new vertex has texture coordinates set to the upper left corner using (0.0f, 0.0f, 0.0f, 0.0f).
  181. // Each new vertex has the color set to solid white using (1.0f, 1.0f, 1.0f, 1.0f).
  182. // Post-condition:
  183. // Returns the new polygon's local index within the part at partIndex in model.
  184. int model_addQuad(Model& model, int partIndex, int pointA, int pointB, int pointC, int pointD);
  185. // Pre-condition: model must refer to an existing model.
  186. // Post-condition: Returns the number of polygons (triangles + quads) in the part at partIndex in model.
  187. int model_getNumberOfPolygons(const Model& model, int partIndex);
  188. // Pre-condition: model must refer to an existing model.
  189. // Post-condition: Returns the number of vertices in the polygon at polygonIndex in the part at partIndex in model.
  190. int model_getPolygonVertexCount(const Model& model, int partIndex, int polygonIndex);
  191. // Get the part's diffuse texture.
  192. // Pre-condition: model must refer to an existing model.
  193. // Post-condition:
  194. // Returns an image handle to the diffuse texture in the part at partIndex in model.
  195. // If the part has no diffuse image then an empth handle is returned.
  196. TextureRgbaU8 model_getDiffuseMap(const Model& model, int partIndex);
  197. // Set the part's diffuse texture.
  198. // A texture is just an image fulfilling the criterias of image_isTexture to allow fast texture sampling and pyramid generation.
  199. // Pre-condition:
  200. // model must refer to an existing model.
  201. // diffuseMap must be either empty or have power-of-two dimensions accepted by image_isTexture.
  202. // Side-effect:
  203. // Sets the diffuse texture in the part at partIndex in model to diffuseMap.
  204. // If diffuseMap is an empty image handle, then the diffuse texture will be replaced by the default solid white color.
  205. void model_setDiffuseMap(Model& model, int partIndex, const TextureRgbaU8 &diffuseMap);
  206. // Automatically find the diffuse texture by name in the resource pool and assign it.
  207. // Pre-condition:
  208. // model must refer to an existing model.
  209. // pool must refer to an existing resource pool.
  210. // filename must be the image's filename without any extension nor path.
  211. // "Car" is accepted.
  212. // "Car.png" is rejected for having an extension.
  213. // "myFolder/Car" is rejected for having a path.
  214. // "myFolder\\Car" is rejected for having a path.
  215. // "Car_1.2" is rejected for using a dot in the actual name, just to catch more mistakes with file extensions.
  216. // Side-effect:
  217. // Sets the diffuse texture in the part at partIndex in model to the image looked up by filename in pool.
  218. void model_setDiffuseMapByName(Model& model, int partIndex, ResourcePool &pool, const String &filename);
  219. // Get the part's light texture.
  220. // Pre-condition:
  221. // model must refer to an existing model.
  222. // Post-condition:
  223. // Returns an image handle to the light texture in the part at partIndex in model.
  224. // If the part has no light image then an empth handle is returned.
  225. TextureRgbaU8 model_getLightMap(Model& model, int partIndex);
  226. // Set the part's light texture.
  227. // A texture is just an image fulfilling the criterias of image_isTexture to allow fast texture sampling.
  228. // Even though no texture-pyramid is used for light-maps, it still has to look up
  229. // pixels quickly using bit-shifts with base two logarithms of power of two widths.
  230. // Pre-condition:
  231. // model must refer to an existing model.
  232. // lightMap must be either empty or have power-of-two dimensions accepted by image_isTexture.
  233. // Side-effect:
  234. // Sets the diffuse texture in the part at partIndex in model to diffuseMap.
  235. // If diffuseMap is an empty image handle, then the diffuse texture will be replaced by the default solid white color.
  236. void model_setLightMap(Model& model, int partIndex, const TextureRgbaU8 &lightMap);
  237. // Automatically find the light texture by name in the resource pool and assign it.
  238. // Pre-condition:
  239. // model must refer to an existing model.
  240. // pool must refer to an existing resource pool.
  241. // filename must be the image's filename without any extension nor path.
  242. // Side-effect:
  243. // Sets the light texture in the part at partIndex in model to the image looked up by filename in pool.
  244. void model_setLightMapByName(Model& model, int partIndex, ResourcePool &pool, const String &filename);
  245. // In order to draw two adjacent polygons without any missing pixels along the seam, they must:
  246. // * Share two position indices in opposite directions.
  247. // (Rounding the same value to integers twice can be rounded differently,
  248. // even though it's highly unlikely to actually happen.)
  249. // * Have each vertex position inside of the camera's clipping frustum.
  250. // (Far outside of the view frustum, triangles must be clipped in
  251. // floating-point 3D space to prevent integer overflows when converted
  252. // to sub-pixel integer coordinates.)
  253. // * Avoid colliding with near or far clip planes.
  254. // (This would also cause clipping in floating-point 3D space, because a
  255. // location behind the camera cannot be represented as a screen coordinate)
  256. // If your clipped polygons are fully outside of the view-frustum,
  257. // then you will not see the seam nor the polygons.
  258. // To solve this:
  259. // * use model_addPointIfNeeded instead of model_addPoint when adding points.
  260. // * Split polygons that are way too big and use them to produce more details.
  261. // (This will also increase precision for texture coordinates by splitting up seemingly infinite planes.)
  262. // If this does not hold true then there is either an exception missing
  263. // or a bug in the renderer, which should be reported as soon as possible.
  264. // Single-threaded rendering (Easy to use directly, ideal for rendering in background threads)
  265. // Can be executed on different threads if targetImage and depthBuffer doesn't have overlapping memory lines between the threads
  266. // Pre-condition: colorBuffer and depthBuffer must have the same dimensions.
  267. // Side-effect: Render any model transformed by modelToWorldTransform, seen from camera, to any colorBuffer using any depthBuffer.
  268. // An empty model handle will be skipped silently, which can be used instead of an model with zero polygons.
  269. void model_render(const Model& model, const Transform3D &modelToWorldTransform, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer, const Camera &camera);
  270. // Simpler rendering without colorBuffer, for shadows and other depth effects
  271. // Equivalent to model_render with a non-existing colorBuffer and filter forced to solid.
  272. // Skip this call conditionally for filtered models (using model_getFilter) if you want full equivalence with model_render.
  273. // Side-effect: Render any model transformed by modelToWorldTransform, seen from camera, to any depthBuffer.
  274. // An empty model handle will be skipped silently, which can be used instead of an model with zero polygons.
  275. void model_renderDepth(const Model& model, const Transform3D &modelToWorldTransform, ImageF32& depthBuffer, const Camera &camera);
  276. // Multi-threader rendering (Splits the target rows into separate threads for faster rendering)
  277. // The render job will be performed during the next call to renderer_end with the same renderer.
  278. // Pre-condition: renderer must refer to an existing renderer.
  279. // An empty model handle will be skipped silently, which can be used instead of an model with zero polygons.
  280. // Side-effect: The visible triangles are queued up in the renderer.
  281. void model_render_threaded(const Model& model, const Transform3D &modelToWorldTransform, Renderer& renderer, const Camera &camera);
  282. // Extending the renderer API with an alias for model_render_threaded with different argument order.
  283. static inline void renderer_giveTask(Renderer& renderer, const Model& model, const Transform3D &modelToWorldTransform, const Camera &camera) {
  284. model_render_threaded(model, modelToWorldTransform, renderer, camera);
  285. }
  286. // Imports a DMF model from file content.
  287. // Use in combination with string_load or your own system for storing files.
  288. // Example:
  289. // Model crateModel = importFromContent_DMF1(string_load(mediaPath + U"Model_Crate.dmf"), pool);
  290. // Pre-condition:
  291. // fileContent must be the content of a DMF 1.0 model file.
  292. // pool must refer to an existing resource pool.
  293. // 0 <= detailLevel <= 2 (0 = low, 1 = medium, 2 = high)
  294. // Post-condition:
  295. // Returns a handle to a model imported from fileContent, using pool to access resources, with parts not visible in detailLevel excluded.
  296. // How to import from the DMF1 format:
  297. // * Only use M_Diffuse_0Tex, M_Diffuse_1Tex or M_Diffuse_2Tex as shaders.
  298. // Place any diffuse texture in texture slot 0 and any lightmap in slot 1.
  299. // Remove any textures that are not used by the shaders.
  300. // The fixed pipeline only checks which textures are used.
  301. // * Make sure that texture names are spelled case sensitive or they might not be found on some operating systems like Linux.
  302. Model importFromContent_DMF1(const String &fileContent, ResourcePool &pool, int detailLevel = 2);
  303. }
  304. #endif