orthoAPI.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #ifndef DFPSR_ORTHO
  2. #define DFPSR_ORTHO
  3. #include <assert.h>
  4. #include "../../../DFPSR/includeFramework.h"
  5. namespace dsr {
  6. // TODO: Give an ortho_ prefix
  7. using Direction = int32_t;
  8. static const Direction dir360 = 8;
  9. static const Direction dir315 = 7;
  10. static const Direction dir270 = 6;
  11. static const Direction dir225 = 5;
  12. static const Direction dir180 = 4;
  13. static const Direction dir135 = 3;
  14. static const Direction dir90 = 2;
  15. static const Direction dir45 = 1;
  16. static const Direction dir0 = 0;
  17. inline int correctDirection(Direction direction) {
  18. return (int32_t)((uint32_t)((int32_t)direction + (dir360 * 1024)) % dir360);
  19. }
  20. // World 3D units
  21. // Tile = Diameter from one side to another along a standard tile
  22. // Used for expressing exact tile indices in games so that information can be stored efficiently
  23. // Mini-Tile = Tile / miniUnitsPerTile
  24. // Used to express locations in 3D without relying too much on non-deterministic floats
  25. static constexpr int ortho_miniUnitsPerTile = 1024;
  26. static constexpr float ortho_tilesPerMiniUnit = 1.0f / (float)ortho_miniUnitsPerTile;
  27. int ortho_roundToTile(int miniCoordinate);
  28. IVector3D ortho_roundToTile(const IVector3D& miniPosition);
  29. float ortho_miniToFloatingTile(int miniCoordinate);
  30. FVector3D ortho_miniToFloatingTile(const IVector3D& miniPosition);
  31. int ortho_floatingTileToMini(float tileCoordinate);
  32. IVector3D ortho_floatingTileToMini(const FVector3D& tilePosition);
  33. // TODO: Make sure that every conversion is derived from a single pixel-rounded world-to-screen transform
  34. // Do this by letting it be the only argument for construction using integers
  35. // Everything else will simply be derived from it on construction
  36. struct OrthoView {
  37. public:
  38. // Unique integer for identifying the view
  39. int id = -1;
  40. // Direction for rotating sprites
  41. Direction worldDirection = dir0; // How are sprites in the world rotated relative to the camera's point of view
  42. // The rotating transform from normal-space to world-space.
  43. // Light-space is a superset of normal-space with the origin around the camera. (Almost like camera-space but with Y straight up)
  44. FMatrix3x3 normalToWorldSpace;
  45. // Pixel aligned space (To ensure that moving one tile has the same number of pixels each time)
  46. IVector2D pixelOffsetPerTileX; // How many pixels does a sprite move per tile in X.
  47. IVector2D pixelOffsetPerTileZ; // How many pixels does a sprite move per tile in Z.
  48. int yPixelsPerTile = 0;
  49. // How pixels in the depth buffer maps to world-space coordinates in whole floating tiles.
  50. FMatrix3x3 screenDepthToWorldSpace;
  51. FMatrix3x3 worldSpaceToScreenDepth;
  52. // How pixels in the depth buffer maps to light-space coordinates in whole floating tiles.
  53. // The origin is at the center of the image.
  54. // The X and Y axis gives tile offsets in light space along the screen without depth information.
  55. // The Z axis gives tile offset per mini-tile unit of height in the depth buffer.
  56. FMatrix3x3 screenDepthToLightSpace;
  57. FMatrix3x3 lightSpaceToScreenDepth;
  58. // Conversion systems between rounded pixels and XZ tiles along Y = 0
  59. FMatrix2x2 roundedScreenPixelsToWorldTiles; // TODO: Replace with a screenToTile sub-set
  60. public:
  61. // TODO: Find a way to avoid the default constructor
  62. OrthoView() {}
  63. OrthoView(int id, const IVector2D roundedXAxis, const IVector2D roundedZAxis, int yPixelsPerTile, const FMatrix3x3 &normalToWorldSpace, Direction worldDirection)
  64. : id(id), worldDirection(worldDirection), normalToWorldSpace(normalToWorldSpace),
  65. pixelOffsetPerTileX(roundedXAxis), pixelOffsetPerTileZ(roundedZAxis), yPixelsPerTile(yPixelsPerTile) {
  66. // Pixel aligned 3D transformation matrix from tile (x, y, z) to screen (x, y, h)
  67. FMatrix3x3 tileToScreen = FMatrix3x3(
  68. FVector3D(roundedXAxis.x, roundedXAxis.y, 0),
  69. FVector3D(0, -this->yPixelsPerTile, 1.0f),
  70. FVector3D(roundedZAxis.x, roundedZAxis.y, 0)
  71. );
  72. // Back from deep screen pixels to world tile coordinates
  73. FMatrix3x3 screenToTile = inverse(tileToScreen);
  74. // TODO: Obsolete
  75. this->roundedScreenPixelsToWorldTiles = inverse(FMatrix2x2(FVector2D(roundedXAxis.x, roundedXAxis.y), FVector2D(roundedZAxis.x, roundedZAxis.y)));
  76. // Save the conversion from screen-space to world-space in tile units
  77. this->screenDepthToWorldSpace = screenToTile;
  78. this->worldSpaceToScreenDepth = tileToScreen;
  79. // Save the conversion from screen-space to light-space in tile units
  80. this->screenDepthToLightSpace = FMatrix3x3(
  81. this->normalToWorldSpace.transformTransposed(screenToTile.xAxis),
  82. this->normalToWorldSpace.transformTransposed(screenToTile.yAxis),
  83. this->normalToWorldSpace.transformTransposed(screenToTile.zAxis)
  84. );
  85. this->lightSpaceToScreenDepth = inverse(this->screenDepthToLightSpace);
  86. }
  87. public:
  88. IVector2D miniTileOffsetToScreenPixel(const IVector3D& miniTileOffset) const {
  89. IVector2D centeredPixelLocation = this->pixelOffsetPerTileX * miniTileOffset.x + this->pixelOffsetPerTileZ * miniTileOffset.z;
  90. centeredPixelLocation.y -= miniTileOffset.y * this->yPixelsPerTile;
  91. return centeredPixelLocation / ortho_miniUnitsPerTile;
  92. }
  93. // Position is expressed in world space using mini units
  94. IVector2D miniTilePositionToScreenPixel(const IVector3D& position, const IVector2D& worldCenter) const {
  95. return this->miniTileOffsetToScreenPixel(position) + worldCenter;
  96. }
  97. // Returns the 3D mini-tile units moved along the ground for the pixel offset
  98. // Only rotation and scaling for pixel offsets
  99. FVector3D pixelToTileOffset(const IVector2D& pixelOffset) const {
  100. FVector2D xzTiles = this->roundedScreenPixelsToWorldTiles.transform(FVector2D(pixelOffset.x, pixelOffset.y));
  101. return FVector3D(xzTiles.x, 0.0f, xzTiles.y);
  102. }
  103. IVector3D pixelToMiniOffset(const IVector2D& pixelOffset) const {
  104. FVector3D tiles = this->pixelToTileOffset(pixelOffset);
  105. return IVector3D(ortho_floatingTileToMini(tiles.x), 0, ortho_floatingTileToMini(tiles.z));
  106. }
  107. // Returns the 3D mini-tile location for a certain pixel on the screen intersecting with the ground
  108. // Full transform for pixel locations
  109. IVector3D pixelToMiniPosition(const IVector2D& pixelLocation, const IVector2D& worldCenter) const {
  110. return this->pixelToMiniOffset(pixelLocation - worldCenter);
  111. }
  112. };
  113. // How to use the orthogonal system
  114. // * Place tiles in whole tile integer units
  115. // Multiply directly with pixelOffsetPerTileX and pixelOffsetPerTileZ to get deterministic pixel offsets
  116. // * Define sprites in mini units (1 tile = ortho_miniUnitsPerTile mini units)
  117. // First multiply mini units with yPixelsPerTile, pixelOffsetPerTileX and pixelOffsetPerTileZ for each 3D coordinate
  118. // Then divide by ortho_miniUnitsPerTile, which most processors should have custom instructions for handling quickly
  119. // With enough bits in the integers, the result should be steady and not shake around randomly
  120. struct OrthoSystem {
  121. public:
  122. static constexpr int maxCameraAngles = 8;
  123. static constexpr float diag = 0.707106781f; // cos(45 degrees) = Sqrt(2) / 2
  124. // Persistent settings
  125. float cameraTilt; // Camera coefficient. (-inf is straight down, -1 is diagonal down, 0 is horizontal)
  126. int pixelsPerTile; // The sideway length of a tile in pixels when seen from straight ahead.
  127. // Generated views
  128. OrthoView view[maxCameraAngles];
  129. private:
  130. // Update generated settings from persistent settings
  131. // Enforces a valid orthogonal camera system
  132. void update() {
  133. // Calculate y offset rounded to whole tiles to prevent random gaps in grids
  134. int yPixelsPerTile = (float)this->pixelsPerTile / sqrt(this->cameraTilt * this->cameraTilt + 1);
  135. // Define sprite directions
  136. FVector3D upAxis = FVector3D(0.0f, 1.0f, 0.0f);
  137. Direction worldDirections[8] = {dir315, dir45, dir135, dir225, dir0, dir90, dir180, dir270};
  138. // Define approximate camera systems just to get something axis aligned
  139. FMatrix3x3 cameraSystems[8];
  140. cameraSystems[0] = FMatrix3x3::makeAxisSystem(FVector3D(diag, this->cameraTilt, diag), upAxis);
  141. cameraSystems[1] = FMatrix3x3::makeAxisSystem(FVector3D(-diag, this->cameraTilt, diag), upAxis);
  142. cameraSystems[2] = FMatrix3x3::makeAxisSystem(FVector3D(-diag, this->cameraTilt, -diag), upAxis);
  143. cameraSystems[3] = FMatrix3x3::makeAxisSystem(FVector3D(diag, this->cameraTilt, -diag), upAxis);
  144. cameraSystems[4] = FMatrix3x3::makeAxisSystem(FVector3D( 0, this->cameraTilt, 1), upAxis);
  145. cameraSystems[5] = FMatrix3x3::makeAxisSystem(FVector3D(-1, this->cameraTilt, 0), upAxis);
  146. cameraSystems[6] = FMatrix3x3::makeAxisSystem(FVector3D( 0, this->cameraTilt,-1), upAxis);
  147. cameraSystems[7] = FMatrix3x3::makeAxisSystem(FVector3D( 1, this->cameraTilt, 0), upAxis);
  148. for (int a = 0; a < maxCameraAngles; a++) {
  149. // Define the coordinate system for normals
  150. FVector3D normalSystemDirection = cameraSystems[a].zAxis;
  151. normalSystemDirection.y = 0.0f;
  152. FMatrix3x3 normalToWorldSpace = FMatrix3x3::makeAxisSystem(normalSystemDirection, FVector3D(0.0f, 1.0f, 0.0f));
  153. // Create an axis system truncated inwards to whole pixels to prevent creating empty seams between tile aligned sprites
  154. Camera approximateCamera = Camera::createOrthogonal(Transform3D(FVector3D(), cameraSystems[a]), this->pixelsPerTile, this->pixelsPerTile, 0.5f);
  155. float halfTile = (float)this->pixelsPerTile * 0.5f;
  156. FVector2D XAxis = approximateCamera.worldToScreen(FVector3D(1.0f, 0.0f, 0.0f)).is - halfTile;
  157. FVector2D ZAxis = approximateCamera.worldToScreen(FVector3D(0.0f, 0.0f, 1.0f)).is - halfTile;
  158. this->view[a] = OrthoView(
  159. a,
  160. IVector2D((int)XAxis.x, (int)XAxis.y),
  161. IVector2D((int)ZAxis.x, (int)ZAxis.y),
  162. yPixelsPerTile,
  163. normalToWorldSpace,
  164. worldDirections[a]
  165. );
  166. }
  167. }
  168. public:
  169. OrthoSystem() : cameraTilt(0), pixelsPerTile(0) {}
  170. OrthoSystem(float cameraTilt, int pixelsPerTile) : cameraTilt(cameraTilt), pixelsPerTile(pixelsPerTile) {
  171. this->update();
  172. }
  173. explicit OrthoSystem(const ReadableString& content) {
  174. config_parse_ini(content, [this](const ReadableString& block, const ReadableString& key, const ReadableString& value) {
  175. if (block.length() == 0) {
  176. if (string_caseInsensitiveMatch(key, U"DownTiltPerThousand")) {
  177. this->cameraTilt = (float)string_parseInteger(value) * -0.001f;
  178. } else if (string_caseInsensitiveMatch(key, U"PixelsPerTile")) {
  179. this->pixelsPerTile = string_parseInteger(value);
  180. } else {
  181. printText("Unrecognized key \"", key, "\" in orthogonal camera configuration file.\n");
  182. }
  183. } else {
  184. printText("Unrecognized block \"", block, "\" in orthogonal camera configuration file.\n");
  185. }
  186. });
  187. this->update();
  188. }
  189. public:
  190. IVector2D miniTileOffsetToScreenPixel(const IVector3D& miniTileOffset, int cameraIndex) const {
  191. return this->view[cameraIndex].miniTileOffsetToScreenPixel(miniTileOffset);
  192. }
  193. // Position is expressed in world space using mini units
  194. IVector2D miniTilePositionToScreenPixel(const IVector3D& position, int cameraIndex, const IVector2D& worldCenter) const {
  195. return this->view[cameraIndex].miniTilePositionToScreenPixel(position, worldCenter);
  196. }
  197. public:
  198. // Returns the 3D mini-tile units moved along the ground for the pixel offset
  199. // Only rotation and scaling for pixel offsets
  200. FVector3D pixelToTileOffset(const IVector2D& pixelOffset, int cameraIndex) const {
  201. return this->view[cameraIndex].pixelToTileOffset(pixelOffset);
  202. }
  203. IVector3D pixelToMiniOffset(const IVector2D& pixelOffset, int cameraIndex) const {
  204. return this->view[cameraIndex].pixelToMiniOffset(pixelOffset);
  205. }
  206. // Returns the 3D mini-tile location for a certain pixel on the screen intersecting with the ground
  207. // Full transform for pixel locations
  208. IVector3D pixelToMiniPosition(const IVector2D& pixelLocation, int cameraIndex, const IVector2D& worldCenter) const {
  209. return this->view[cameraIndex].pixelToMiniPosition(pixelLocation, worldCenter);
  210. }
  211. };
  212. }
  213. #endif