4#if defined(MXVK_USE_EIGEN_MATH)
26 class SurfaceDeleter {
31 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
33 [[nodiscard]] SurfacePtr create_frame_surface(
int width,
int height) {
34 SurfacePtr
surface(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
36 throw mxvk::Exception(std::format(
"3dmath_pyramid: failed to create frame surface: {}", SDL_GetError()));
41 [[nodiscard]] std::filesystem::path
pyramid_path(
const std::string &asset_path) {
return std::filesystem::path(asset_path) /
"data" /
"pyramid.plg"; }
44 std::array<mxvk::vec4D, 3> points{};
57 Math3DPyramidWindow(
const std::string &asset_path,
const std::string &title,
int width,
int height,
bool fullscreen,
bool enable_vsync,
const FramebufferDimensions &framebuffer) :
mxvk::
VK_Window(title, width, height, fullscreen,
MXVK_VALIDATION, enable_vsync), frame_width(framebuffer.width), frame_height(framebuffer.height), fallback_width(width), fallback_height(height) {
61 const std::filesystem::path model_path = pyramid_path(asset_path);
63 throw mxvk::Exception(std::format(
"3dmath_pyramid: failed to load PLG model '{}'", model_path.string()));
67 void event(SDL_Event &e)
override {
68 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
71 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
72 const float delta = e.wheel.y != 0.0f ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
73 camera_distance = std::clamp(camera_distance - delta * CAMERA_ZOOM_STEP, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
82 if (frame_sprite ==
nullptr || frame_surface ==
nullptr || frame_format ==
nullptr) {
87 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
89 const float time =
static_cast<float>(SDL_GetTicks()) * 0.001f;
91 rotation.
BuildXYZ(0.0f, time * 42.0f, 0.0f);
93 std::vector<mxvk::vec4D> camera_vertices(pyramid.local.size());
94 std::vector<mxvk::vec4D> projected(pyramid.local.size());
95 for (std::size_t i = 0; i < pyramid.local.size(); ++i) {
96 camera_vertices[i] = rotation.
MulVec(pyramid.local[i]);
97 camera_vertices[i].z += camera_distance;
98 projected[i] = project_to_screen(camera_vertices[i], frame_width, frame_height);
101 mxvk::vec4D light_direction(-0.35f, -0.55f, -1.0f, 0.0f);
103 std::vector<FaceDraw> faces;
104 faces.reserve(pyramid.vlist.size());
107 const auto first =
static_cast<std::size_t
>(triangle.
vert[0]);
108 const auto second =
static_cast<std::size_t
>(triangle.
vert[1]);
109 const auto third =
static_cast<std::size_t
>(triangle.
vert[2]);
117 const mxvk::vec4D center = (a + b + c) * (1.0f / 3.0f);
118 const mxvk::vec4D view_direction(-center.
x, -center.
y, -center.
z, 0.0f);
119 if (normal.
DotProduct(view_direction) <= 0.0f) {
123 const float diffuse = std::max(0.0f, normal.
DotProduct(light_direction));
124 const float intensity = std::clamp(0.28f + diffuse * 0.72f, 0.0f, 1.0f);
126 {projected[first], projected[second], projected[third]},
128 pyramid.texcoords[first],
129 pyramid.texcoords[second],
130 pyramid.texcoords[third],
136 for (
const FaceDraw &face : faces) {
137 draw_gradient_triangle(face);
140 frame_sprite->updateTexture(frame_surface.get());
141 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
146 SurfacePtr frame_surface;
147 std::vector<float> depth_buffer;
148 const SDL_PixelFormatDetails *frame_format =
nullptr;
150 int frame_width = 1280;
151 int frame_height = 720;
152 int fallback_width = 1280;
153 int fallback_height = 720;
154 float camera_distance = 4.5f;
156 void ensure_framebuffer() {
157 if (frame_surface !=
nullptr) {
161 frame_surface = create_frame_surface(frame_width, frame_height);
162 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
163 if (frame_format ==
nullptr) {
164 throw mxvk::Exception(std::format(
"3dmath_pyramid: failed to query frame format: {}", SDL_GetError()));
167 depth_buffer.resize(
static_cast<std::size_t
>(frame_width) *
static_cast<std::size_t
>(frame_height));
171 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
176 void clear_frame(
mxvk::MXCOLOR color) { SDL_FillSurfaceRect(frame_surface.get(),
nullptr, map_color(color)); }
179 if (x < 0 || y < 0 || x >= frame_width || y >= frame_height) {
183 auto *row =
static_cast<std::uint8_t *
>(frame_surface->pixels) + (
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(frame_surface->pitch));
184 auto *pixel =
reinterpret_cast<std::uint32_t *
>(row) + x;
185 *pixel = map_color(color);
188 void draw_gradient_triangle(
const FaceDraw &face) {
189 const mxvk::vec4D &a = face.points[0];
190 const mxvk::vec4D &b = face.points[1];
191 const mxvk::vec4D &c = face.points[2];
192 const auto edge = [](
const mxvk::vec4D &first,
const mxvk::vec4D &second,
float x,
float y) {
return (x - first.
x) * (second.y - first.
y) - (y - first.
y) * (second.x - first.
x); };
194 const float area = edge(b, c, a.
x, a.
y);
199 const int min_x = std::clamp(
static_cast<int>(std::floor(std::min({a.
x, b.
x, c.
x}))), 0, frame_width - 1);
200 const int max_x = std::clamp(
static_cast<int>(std::ceil(std::max({a.
x, b.
x, c.
x}))), 0, frame_width - 1);
201 const int min_y = std::clamp(
static_cast<int>(std::floor(std::min({a.
y, b.
y, c.
y}))), 0, frame_height - 1);
202 const int max_y = std::clamp(
static_cast<int>(std::ceil(std::max({a.
y, b.
y, c.
y}))), 0, frame_height - 1);
204 for (
int y = min_y; y <= max_y; ++y) {
205 for (
int x = min_x; x <= max_x; ++x) {
206 const float sample_x =
static_cast<float>(x) + 0.5f;
207 const float sample_y =
static_cast<float>(y) + 0.5f;
208 const float weight_a = edge(b, c, sample_x, sample_y) / area;
209 const float weight_b = edge(c, a, sample_x, sample_y) / area;
210 const float weight_c = edge(a, b, sample_x, sample_y) / area;
215 const float reciprocal_depth = weight_a / a.
z + weight_b / b.
z + weight_c / c.
z;
220 const float depth = 1.0f / reciprocal_depth;
221 const std::size_t pixel_index =
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(frame_width) +
static_cast<std::size_t
>(x);
222 if (depth >= depth_buffer[pixel_index]) {
225 depth_buffer[pixel_index] = depth;
227 const float perspective_a = (weight_a / a.
z) * depth;
228 const float perspective_b = (weight_b / b.
z) * depth;
229 const float perspective_c = (weight_c / c.
z) * depth;
230 const float u = face.texcoords[0].x * perspective_a + face.texcoords[1].x * perspective_b + face.texcoords[2].x * perspective_c;
231 const float v = face.texcoords[0].y * perspective_a + face.texcoords[1].y * perspective_b + face.texcoords[2].y * perspective_c;
237 [[nodiscard]]
static mxvk::MXCOLOR gradient_color(
float u,
float v) {
238 u = std::clamp(u, 0.0f, 1.0f);
239 v = std::clamp(v, 0.0f, 1.0f);
245 const auto bilinear_channel = [&](
auto component) {
246 const float bottom =
static_cast<float>(component(BOTTOM_LEFT)) + (
static_cast<float>(component(BOTTOM_RIGHT)) -
static_cast<float>(component(BOTTOM_LEFT))) * u;
247 const float top =
static_cast<float>(component(TOP_LEFT)) + (
static_cast<float>(component(TOP_RIGHT)) -
static_cast<float>(component(TOP_LEFT))) * u;
248 return std::clamp(
static_cast<int>(std::lround(bottom + (top - bottom) * v)), 0, 255);
254 [[nodiscard]]
static mxvk::vec4D project_to_screen(
const mxvk::vec4D &point,
int width,
int height) {
255 const float scale =
static_cast<float>(std::min(width, height)) * 0.52f;
256 const float center_x =
static_cast<float>(width) * 0.5f;
257 const float center_y =
static_cast<float>(height) * 0.5f;
258 const float z = std::max(point.
z, 0.001f);
259 return {center_x + (point.
x / z) * scale, center_y - (point.
y / z) * scale, point.
z, 1.0f};
264int main(
int argc,
char **argv) {
270 std::cerr << std::format(
"mxvk: Exception: {}\n", e.
text());
273 std::cerr << std::format(
"mxvk: Argument Exception: {}\n", e.
text());
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Exception thrown by Argz::proc() on unrecognised or malformed options.
void operator()(SDL_Surface *surface) const
Math3DPyramidWindow(const std::string &asset_path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync, const FramebufferDimensions &framebuffer)
void event(SDL_Event &e) override
Handle one SDL event.
void proc() override
Execute one processing/update step.
Four-by-four homogeneous transform matrix.
void BuildXYZ(float theta_x, float theta_y, float theta_z)
Build an XYZ Euler rotation matrix from angles in degrees.
vec4D MulVec(const vec4D &in) const
Transform a homogeneous 4D vector by this matrix.
Main Vulkan window wrapper for MXVK.
void loop()
Run the main event/render loop.
VK_Sprite * createSprite(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a sprite from a PNG file and register it with this window.
VkExtent2D swapchain_extent
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
void exit()
Request loop termination.
VK_Window()=default
Construct an empty window object.
Simple mesh object loaded from PLG-style indexed triangle data.
Four-dimensional float vector used for homogeneous 3D coordinates.
void Normalize()
Normalize the 3D components in place and reset W to 1.
constexpr float DotProduct(const vec4D &v) const
Compute the 3D dot product, ignoring the W component.
void Build(const vec4D &to)
Replace this vector with the direction from this point to to.
Math, geometry, rasterization, and simple software 3D pipeline helpers for MXVK examples.
constexpr float MIN_CAMERA_DISTANCE
constexpr float MAX_CAMERA_DISTANCE
std::filesystem::path pyramid_path(const std::string &asset_path)
constexpr float CAMERA_ZOOM_STEP
Utilities for loading and saving PNG images.
constexpr std::uint8_t color_r(MXCOLOR color)
Extract the red component from a packed ARGB color.
std::uint32_t MXCOLOR
Packed 32-bit color in ARGB byte order.
void BuildTables()
Rebuild the sine and cosine lookup tables.
MXCOLOR shade_color(MXCOLOR color, float intensity)
Scale the RGB channels of a color while preserving alpha.
constexpr std::uint8_t color_g(MXCOLOR color)
Extract the green component from a packed ARGB color.
constexpr MXCOLOR MXVK_RGB(int r, int g, int b)
Build an opaque ARGB color from red, green, and blue components.
constexpr std::uint8_t color_a(MXCOLOR color)
Extract the alpha component from a packed ARGB color.
constexpr float EPSILON
Default tolerance used for floating-point singularity and zero-length checks.
constexpr std::uint8_t color_b(MXCOLOR color)
Extract the blue component from a packed ARGB color.
Plain data structure returned by proc_args() with all common libmx2 CLI options.
FramebufferDimensions framebuffer
Software framebuffer size requested by --framebuffer.
bool fullscreen
Whether fullscreen mode was requested.
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
int height
Viewport height in pixels (default: 720).
std::string path
Asset root; proc_args() defaults it to the executable directory.
int width
Viewport width in pixels (default: 1280).
Parsed software framebuffer dimensions.
std::array< mxvk::vec2D, 3 > texcoords
Triangle primitive used by the simple software rendering pipeline.
vec4D vlist[3]
Working vertex positions.
int vert[3]
Indices into an object's vertex arrays.