4#if defined(MXVK_USE_EIGEN_MATH)
26 class SurfaceDeleter {
33 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
36 SurfacePtr
surface(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
38 throw mxvk::Exception(std::format(
"3dmath_pyramid: failed to create frame surface: {}", SDL_GetError()));
43 [[nodiscard]] std::filesystem::path
pyramid_path(
const std::string &asset_path) {
44 return std::filesystem::path(asset_path) /
"data" /
"pyramid.plg";
48 std::array<mxvk::vec4D, 3> points{};
63 frame_width(framebuffer.width),
64 frame_height(framebuffer.height),
65 fallback_width(width),
66 fallback_height(height) {
70 const std::filesystem::path model_path = pyramid_path(asset_path);
72 throw mxvk::Exception(std::format(
"3dmath_pyramid: failed to load PLG model '{}'", model_path.string()));
76 void event(SDL_Event &e)
override {
77 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
80 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
81 const float delta = e.wheel.y != 0.0f ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
82 camera_distance = std::clamp(camera_distance - delta * CAMERA_ZOOM_STEP, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
91 if (frame_sprite ==
nullptr || frame_surface ==
nullptr || frame_format ==
nullptr) {
96 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
98 const float time =
static_cast<float>(SDL_GetTicks()) * 0.001f;
100 rotation.
BuildXYZ(0.0f, time * 42.0f, 0.0f);
102 std::vector<mxvk::vec4D> camera_vertices(pyramid.local.size());
103 std::vector<mxvk::vec4D> projected(pyramid.local.size());
104 for (std::size_t i = 0; i < pyramid.local.size(); ++i) {
105 camera_vertices[i] = rotation.
MulVec(pyramid.local[i]);
106 camera_vertices[i].z += camera_distance;
107 projected[i] = project_to_screen(camera_vertices[i], frame_width, frame_height);
110 mxvk::vec4D light_direction(-0.35f, -0.55f, -1.0f, 0.0f);
112 std::vector<FaceDraw> faces;
113 faces.reserve(pyramid.vlist.size());
116 const auto first =
static_cast<std::size_t
>(triangle.
vert[0]);
117 const auto second =
static_cast<std::size_t
>(triangle.
vert[1]);
118 const auto third =
static_cast<std::size_t
>(triangle.
vert[2]);
126 const mxvk::vec4D center = (a + b + c) * (1.0f / 3.0f);
127 const mxvk::vec4D view_direction(-center.
x, -center.
y, -center.
z, 0.0f);
128 if (normal.
DotProduct(view_direction) <= 0.0f) {
132 const float diffuse = std::max(0.0f, normal.
DotProduct(light_direction));
133 const float intensity = std::clamp(0.28f + diffuse * 0.72f, 0.0f, 1.0f);
135 {projected[first], projected[second], projected[third]},
137 pyramid.texcoords[first],
138 pyramid.texcoords[second],
139 pyramid.texcoords[third],
145 for (
const FaceDraw &face : faces) {
146 draw_gradient_triangle(face);
149 frame_sprite->updateTexture(frame_surface.get());
150 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
155 SurfacePtr frame_surface;
156 std::vector<float> depth_buffer;
157 const SDL_PixelFormatDetails *frame_format =
nullptr;
159 int frame_width = 1280;
160 int frame_height = 720;
161 int fallback_width = 1280;
162 int fallback_height = 720;
163 float camera_distance = 4.5f;
165 void ensure_framebuffer() {
166 if (frame_surface !=
nullptr) {
170 frame_surface = create_frame_surface(frame_width, frame_height);
171 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
172 if (frame_format ==
nullptr) {
173 throw mxvk::Exception(std::format(
"3dmath_pyramid: failed to query frame format: {}", SDL_GetError()));
176 depth_buffer.resize(
static_cast<std::size_t
>(frame_width) *
static_cast<std::size_t
>(frame_height));
180 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
183 [[nodiscard]] std::uint32_t map_color(
mxvk::MXCOLOR color)
const {
188 SDL_FillSurfaceRect(frame_surface.get(),
nullptr, map_color(color));
192 if (x < 0 || y < 0 || x >= frame_width || y >= frame_height) {
196 auto *row =
static_cast<std::uint8_t *
>(frame_surface->pixels) + (
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(frame_surface->pitch));
197 auto *pixel =
reinterpret_cast<std::uint32_t *
>(row) + x;
198 *pixel = map_color(color);
201 void draw_gradient_triangle(
const FaceDraw &face) {
202 const mxvk::vec4D &a = face.points[0];
203 const mxvk::vec4D &b = face.points[1];
204 const mxvk::vec4D &c = face.points[2];
205 const auto edge = [](
const mxvk::vec4D &first,
const mxvk::vec4D &second,
float x,
float y) {
206 return (x - first.
x) * (second.y - first.
y) - (y - first.
y) * (second.x - first.
x);
209 const float area = edge(b, c, a.
x, a.
y);
214 const int min_x = std::clamp(
static_cast<int>(std::floor(std::min({a.
x, b.
x, c.
x}))), 0, frame_width - 1);
215 const int max_x = std::clamp(
static_cast<int>(std::ceil(std::max({a.
x, b.
x, c.
x}))), 0, frame_width - 1);
216 const int min_y = std::clamp(
static_cast<int>(std::floor(std::min({a.
y, b.
y, c.
y}))), 0, frame_height - 1);
217 const int max_y = std::clamp(
static_cast<int>(std::ceil(std::max({a.
y, b.
y, c.
y}))), 0, frame_height - 1);
219 for (
int y = min_y; y <= max_y; ++y) {
220 for (
int x = min_x; x <= max_x; ++x) {
221 const float sample_x =
static_cast<float>(x) + 0.5f;
222 const float sample_y =
static_cast<float>(y) + 0.5f;
223 const float weight_a = edge(b, c, sample_x, sample_y) / area;
224 const float weight_b = edge(c, a, sample_x, sample_y) / area;
225 const float weight_c = edge(a, b, sample_x, sample_y) / area;
230 const float reciprocal_depth =
238 const float depth = 1.0f / reciprocal_depth;
239 const std::size_t pixel_index =
240 static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(frame_width) +
241 static_cast<std::size_t
>(x);
242 if (depth >= depth_buffer[pixel_index]) {
245 depth_buffer[pixel_index] = depth;
247 const float perspective_a = (weight_a / a.
z) * depth;
248 const float perspective_b = (weight_b / b.
z) * depth;
249 const float perspective_c = (weight_c / c.
z) * depth;
251 face.texcoords[0].x * perspective_a +
252 face.texcoords[1].x * perspective_b +
253 face.texcoords[2].x * perspective_c;
255 face.texcoords[0].y * perspective_a +
256 face.texcoords[1].y * perspective_b +
257 face.texcoords[2].y * perspective_c;
263 [[nodiscard]]
static mxvk::MXCOLOR gradient_color(
float u,
float v) {
264 u = std::clamp(u, 0.0f, 1.0f);
265 v = std::clamp(v, 0.0f, 1.0f);
271 const auto bilinear_channel = [&](
auto component) {
273 static_cast<float>(component(BOTTOM_LEFT)) +
274 (
static_cast<float>(component(BOTTOM_RIGHT)) -
static_cast<float>(component(BOTTOM_LEFT))) * u;
276 static_cast<float>(component(TOP_LEFT)) +
277 (
static_cast<float>(component(TOP_RIGHT)) -
static_cast<float>(component(TOP_LEFT))) * u;
278 return std::clamp(
static_cast<int>(std::lround(bottom + (top - bottom) * v)), 0, 255);
287 [[nodiscard]]
static mxvk::vec4D project_to_screen(
const mxvk::vec4D &point,
int width,
int height) {
288 const float scale =
static_cast<float>(std::min(width, height)) * 0.52f;
289 const float center_x =
static_cast<float>(width) * 0.5f;
290 const float center_y =
static_cast<float>(height) * 0.5f;
291 const float z = std::max(point.
z, 0.001f);
292 return {center_x + (point.
x / z) * scale, center_y - (point.
y / z) * scale, point.
z, 1.0f};
297int main(
int argc,
char **argv) {
303 std::cerr << std::format(
"mxvk: Exception: {}\n", e.
text());
306 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)
SurfacePtr create_frame_surface(int width, int height)
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.