Camera.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2017 to 2022 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. #ifndef DFPSR_RENDER_CAMERA
  24. #define DFPSR_RENDER_CAMERA
  25. #include <cstdint>
  26. #include <cassert>
  27. #include "../../math/FVector.h"
  28. #include "../../math/LVector.h"
  29. #include "../../math/FPlane3D.h"
  30. #include "../../math/Transform3D.h"
  31. #include "../math/scalar.h"
  32. #include "constants.h"
  33. #include "ProjectedPoint.h"
  34. #include <limits>
  35. namespace dsr {
  36. class ViewFrustum {
  37. private:
  38. FPlane3D planes[6];
  39. int planeCount;
  40. public:
  41. // Named indices to the different planes defining a view frustum.
  42. static const int view_left = 0;
  43. static const int view_right = 1;
  44. static const int view_top = 2;
  45. static const int view_bottom = 3;
  46. static const int view_near = 4;
  47. static const int view_far = 5;
  48. ViewFrustum() : planeCount(0) {}
  49. // Orthogonal view frustum in camera space
  50. ViewFrustum(float halfWidth, float halfHeight)
  51. : planeCount(4) {
  52. // Sides
  53. planes[view_left ] = FPlane3D(FVector3D(-1.0f, 0.0f, 0.0f), halfWidth);
  54. planes[view_right ] = FPlane3D(FVector3D(1.0f, 0.0f, 0.0f), halfWidth);
  55. planes[view_top ] = FPlane3D(FVector3D(0.0f, 1.0f, 0.0f), halfHeight);
  56. planes[view_bottom] = FPlane3D(FVector3D(0.0f, -1.0f, 0.0f), halfHeight);
  57. }
  58. // Perspective view frustum in camera space
  59. ViewFrustum(float nearClip, float farClip, float widthSlope, float heightSlope)
  60. : planeCount(farClip == std::numeric_limits<float>::infinity() ? 5 : 6) { // Skip the far clip plane if its distance is infinite.
  61. // Sides
  62. planes[view_left ] = FPlane3D(FVector3D(-1.0f, 0.0f, -widthSlope ), 0.0f);
  63. planes[view_right ] = FPlane3D(FVector3D( 1.0f, 0.0f, -widthSlope ), 0.0f);
  64. planes[view_top ] = FPlane3D(FVector3D( 0.0f, 1.0f, -heightSlope), 0.0f);
  65. planes[view_bottom] = FPlane3D(FVector3D( 0.0f, -1.0f, -heightSlope), 0.0f);
  66. // Near and far clip planes
  67. planes[view_near ] = FPlane3D(FVector3D(0.0f, 0.0f, -1.0f), -nearClip);
  68. planes[view_far ] = FPlane3D(FVector3D(0.0f, 0.0f, 1.0f), farClip);
  69. }
  70. inline int getPlaneCount() const {
  71. return this->planeCount;
  72. }
  73. inline FPlane3D getPlane(int sideIndex) const {
  74. assert(sideIndex >= 0 && sideIndex < this->planeCount);
  75. return planes[sideIndex];
  76. }
  77. // Quick estimation of potential visibility without caring about edges nor details.
  78. // The convex hull points to test are relative to the camera's location.
  79. // Returns 0 if all points are outside of the same plane, so that an object within the convex hull can not be visible.
  80. // Returns 1 if one or more points are outside of the view frustum but they are not all outside of the same plane, so it may or may not be visible.
  81. // Returns 2 if all points are inside of the view frustum, so that it is certainly visible, unless hidden by something else.
  82. int isConvexHullSeen(SafePointer<const FVector3D> cameraSpacePoints, int32_t pointCount) const {
  83. bool anyOutside = false;
  84. for (int s = 0; s < this->getPlaneCount(); s++) {
  85. FPlane3D plane = this->getPlane(s);
  86. // Check if any point is inside of the current plane.
  87. bool anyInside = false;
  88. for (int p = 0; p < pointCount; p++) {
  89. if (plane.inside(cameraSpacePoints[p])) {
  90. anyInside = true;
  91. } else {
  92. anyOutside = true;
  93. }
  94. }
  95. // If none was inside of the plane, then the point clound is not visible.
  96. if (!anyInside) {
  97. // All points were outside of the current side, so the hull is not visible.
  98. return 0;
  99. }
  100. }
  101. // Every side had at least one point inside, so the hull is visible.
  102. return anyOutside ? 1 : 2;
  103. }
  104. };
  105. // How much is the image region magnified for skipping entire triangles.
  106. // A small margin is needed to prevent missing pixels from rounding errors along the borders in high image resolutions.
  107. static const float cullRatio = 1.0001f;
  108. // How much is the image region magnified for clipping triangles.
  109. // The larger you make the clip region, the less triangles you have to apply clipping to.
  110. // The triangle rasterization can handle clipping triangles in integer coordinates,
  111. // but there are limits to how large those integers can become before overflowing.
  112. static const float clipRatio = 2.0f;
  113. // To prevent division by zero, a near clipping distance is slightly above zero to
  114. // clip triangles in 3D camera space before projecting the coordinates to the target image.
  115. static const float defaultNearClip = 0.01f;
  116. static const float defaultFarClip = 1000.0f;
  117. // Just create a new camera on stack memory every time you need to render something.
  118. class Camera {
  119. public: // Do not modify individual settings without assigning whole new cameras.
  120. bool perspective; // When off, widthSlope and heightSlope will be used as halfWidth and halfHeight.
  121. Transform3D location; // Only translation and rotation allowed. Scaling and tilting will obviously not work for cameras.
  122. float widthSlope, heightSlope, invWidthSlope, invHeightSlope, imageWidth, imageHeight, nearClip, farClip;
  123. // The tight view frustum, used for skipping rendering as soon as something is fully out of sight.
  124. ViewFrustum cullFrustum;
  125. // The extra large frustum outside of the visible border, used to clip rendering of partial visibility to prevent integer overflow in perspective projection.
  126. // The clip frustum is much larger than the cull frustum because clipping is expensive and can not be done using exact integers.
  127. ViewFrustum clipFrustum;
  128. Camera() :
  129. perspective(true), location(Transform3D()), widthSlope(0.0f), heightSlope(0.0f),
  130. invWidthSlope(0.0f), invHeightSlope(0.0f), imageWidth(0), imageHeight(0),
  131. nearClip(0.0f), farClip(0.0f), cullFrustum(ViewFrustum()), clipFrustum(ViewFrustum()) {}
  132. Camera(bool perspective, const Transform3D &location, float imageWidth, float imageHeight, float widthSlope, float heightSlope, float nearClip, float farClip, const ViewFrustum &cullFrustum, const ViewFrustum &clipFrustum) :
  133. perspective(perspective), location(location), widthSlope(widthSlope), heightSlope(heightSlope),
  134. invWidthSlope(0.5f / widthSlope), invHeightSlope(0.5f / heightSlope), imageWidth(imageWidth), imageHeight(imageHeight),
  135. nearClip(nearClip), farClip(farClip), cullFrustum(cullFrustum), clipFrustum(clipFrustum) {}
  136. public:
  137. static Camera createPerspective(const Transform3D &location, float imageWidth, float imageHeight, float widthSlope = 1.0f, float nearClip = defaultNearClip, float farClip = defaultFarClip) {
  138. float heightSlope = widthSlope * imageHeight / imageWidth;
  139. return Camera(true, location, imageWidth, imageHeight, widthSlope, heightSlope, nearClip, farClip,
  140. ViewFrustum(nearClip, farClip, widthSlope * cullRatio, heightSlope * cullRatio),
  141. ViewFrustum(nearClip, farClip, widthSlope * clipRatio, heightSlope * clipRatio));
  142. }
  143. // Orthogonal cameras doesn't have any near or far clip planes
  144. static Camera createOrthogonal(const Transform3D &location, float imageWidth, float imageHeight, float halfWidth) {
  145. float halfHeight = halfWidth * imageHeight / imageWidth;
  146. return Camera(false, location, imageWidth, imageHeight, halfWidth, halfHeight, -std::numeric_limits<float>::max(), std::numeric_limits<float>::max(),
  147. ViewFrustum(halfWidth * cullRatio, halfHeight * cullRatio),
  148. ViewFrustum(halfWidth * clipRatio, halfHeight * clipRatio));
  149. }
  150. inline FVector3D worldToCamera(const FVector3D &worldSpace) const {
  151. return this->location.transformPointTransposedInverse(worldSpace);
  152. }
  153. ProjectedPoint cameraToScreen(const FVector3D &cameraSpace) const {
  154. // Camera to image space
  155. if (this->perspective) {
  156. float invDepth;
  157. if (cameraSpace.z > 0.0f) {
  158. invDepth = 1.0f / cameraSpace.z;
  159. } else {
  160. invDepth = 0.0f;
  161. }
  162. float centerShear = cameraSpace.z * 0.5f;
  163. FVector2D preProjection = FVector2D(
  164. ( cameraSpace.x * this->invWidthSlope + centerShear) * this->imageWidth,
  165. (-cameraSpace.y * this->invHeightSlope + centerShear) * this->imageHeight
  166. );
  167. FVector2D projectedFloat = preProjection * invDepth;
  168. FVector2D subPixel = projectedFloat * constants::unitsPerPixel;
  169. LVector2D rounded = LVector2D(int64_t(subPixel.x), int64_t(subPixel.y));
  170. return ProjectedPoint(cameraSpace, projectedFloat, rounded);
  171. } else {
  172. FVector2D projectedFloat = FVector2D(
  173. ( cameraSpace.x * this->invWidthSlope + 0.5f) * this->imageWidth,
  174. (-cameraSpace.y * this->invHeightSlope + 0.5f) * this->imageHeight
  175. );
  176. FVector2D subPixel = projectedFloat * constants::unitsPerPixel;
  177. LVector2D rounded = LVector2D(int64_t(subPixel.x), int64_t(subPixel.y));
  178. return ProjectedPoint(cameraSpace, projectedFloat, rounded);
  179. }
  180. }
  181. inline ProjectedPoint worldToScreen(const FVector3D &worldSpace) const {
  182. return this->cameraToScreen(this->worldToCamera(worldSpace));
  183. }
  184. // Get the number of planes in the clipping or culling frustum.
  185. inline int getFrustumPlaneCount(bool clipping = false) const {
  186. return clipping ? this->clipFrustum.getPlaneCount() : this->cullFrustum.getPlaneCount();
  187. }
  188. // Get a certain plane from the clipping or culling frustum.
  189. // The plane is expressed in camera space.
  190. inline FPlane3D getFrustumPlane(int sideIndex, bool clipping = false) const {
  191. return clipping ? this->clipFrustum.getPlane(sideIndex) : this->cullFrustum.getPlane(sideIndex);
  192. }
  193. // Returns 0 iff the model inside of the bound can clearly not be visible, 1 if it intersects with the view frustum, or 2 if fully in view.
  194. // by having all corners outside of the same side in the camera's culling frustum.
  195. int isBoxSeen(const FVector3D& minModelSpaceBound, const FVector3D& maxModelSpaceBound, const Transform3D &modelToWorld) const {
  196. // Allocate memory for the corners.
  197. FVector3D cornerBuffer[8];
  198. SafePointer<FVector3D> corners = SafePointer<FVector3D>("corners in Camera::isBoxSeen", cornerBuffer, sizeof(cornerBuffer));
  199. // Convert from model space bounds to camera space point cloud.
  200. corners[0] = this->worldToCamera(modelToWorld.transformPoint(FVector3D(minModelSpaceBound.x, minModelSpaceBound.y, minModelSpaceBound.z)));
  201. corners[1] = this->worldToCamera(modelToWorld.transformPoint(FVector3D(maxModelSpaceBound.x, minModelSpaceBound.y, minModelSpaceBound.z)));
  202. corners[2] = this->worldToCamera(modelToWorld.transformPoint(FVector3D(minModelSpaceBound.x, maxModelSpaceBound.y, minModelSpaceBound.z)));
  203. corners[3] = this->worldToCamera(modelToWorld.transformPoint(FVector3D(maxModelSpaceBound.x, maxModelSpaceBound.y, minModelSpaceBound.z)));
  204. corners[4] = this->worldToCamera(modelToWorld.transformPoint(FVector3D(minModelSpaceBound.x, minModelSpaceBound.y, maxModelSpaceBound.z)));
  205. corners[5] = this->worldToCamera(modelToWorld.transformPoint(FVector3D(maxModelSpaceBound.x, minModelSpaceBound.y, maxModelSpaceBound.z)));
  206. corners[6] = this->worldToCamera(modelToWorld.transformPoint(FVector3D(minModelSpaceBound.x, maxModelSpaceBound.y, maxModelSpaceBound.z)));
  207. corners[7] = this->worldToCamera(modelToWorld.transformPoint(FVector3D(maxModelSpaceBound.x, maxModelSpaceBound.y, maxModelSpaceBound.z)));
  208. // Apply a fast visibility test, which might return true even when the object is not visible.
  209. return this->cullFrustum.isConvexHullSeen(corners, 8);
  210. }
  211. };
  212. }
  213. #endif