103 throw mxvk::Exception(std::format(
"3dmath_texture: failed to load PNG '{}'", path));
106 SurfacePtr rgba(SDL_ConvertSurface(loaded.get(), SDL_PIXELFORMAT_RGBA32));
108 throw mxvk::Exception(std::format(
"3dmath_texture: failed to convert PNG '{}': {}", path, SDL_GetError()));
111 const SDL_PixelFormatDetails *format = SDL_GetPixelFormatDetails(rgba->format);
112 if (format ==
nullptr) {
113 throw mxvk::Exception(std::format(
"3dmath_texture: failed to query PNG format '{}': {}", path, SDL_GetError()));
117 texture.width = rgba->w;
118 texture.height = rgba->h;
119 texture.pixels.resize(
static_cast<std::size_t
>(texture.width * texture.height));
121 for (
int y = 0; y < texture.height; ++y) {
122 const auto *row =
static_cast<const std::uint8_t *
>(rgba->pixels) + (
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(rgba->pitch));
123 const auto *src =
reinterpret_cast<const std::uint32_t *
>(row);
124 for (
int x = 0; x < texture.width; ++x) {
129 SDL_GetRGBA(src[x], format,
nullptr, &r, &g, &b, &a);
141 Math3DTextureWindow(
const Arguments &args,
const std::string &title) :
mxvk::
VK_Window(title, args.width, args.height, args.fullscreen,
MXVK_VALIDATION, args.enable_vsync), texture(load_texture(resolve_texture_path(args))), frame_width(args.framebuffer.width), frame_height(args.framebuffer.height), fallback_width(args.width), fallback_height(args.height) {
147 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
150 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
151 const float delta = (e.wheel.y != 0.0f) ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
152 camera_distance = std::clamp(camera_distance - delta * CAMERA_ZOOM_STEP, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
160 ensure_framebuffer();
161 if (frame_sprite ==
nullptr || frame_surface ==
nullptr || frame_format ==
nullptr) {
165 const float time =
static_cast<float>(SDL_GetTicks()) * 0.001f;
168 const std::array<mxvk::vec4D, 8> cube_vertices = {
180 rotation.
BuildXYZ(time * 31.0f, time * 43.0f, time * 17.0f);
182 std::array<mxvk::vec4D, 8> camera_vertices{};
183 std::array<mxvk::vec4D, 8> projected{};
184 for (std::size_t i = 0; i < cube_vertices.size(); ++i) {
186 point.
z += camera_distance;
187 camera_vertices[i] = point;
188 projected[i] = project_to_screen(point, frame_width, frame_height);
191 const std::array<std::array<int, 4>, 6> cube_faces = {{
203 std::vector<FaceDraw> faces;
204 faces.reserve(cube_faces.size());
205 for (
const auto &indices : cube_faces) {
206 const mxvk::vec4D &a = camera_vertices[
static_cast<std::size_t
>(indices[0])];
207 const mxvk::vec4D &b = camera_vertices[
static_cast<std::size_t
>(indices[1])];
208 const mxvk::vec4D &c = camera_vertices[
static_cast<std::size_t
>(indices[2])];
212 const mxvk::vec4D center = (a + b + c + camera_vertices[
static_cast<std::size_t
>(indices[3])]) * 0.25f;
213 const mxvk::vec4D view_vector(-center.
x, -center.
y, -center.
z, 1.0f);
214 if (normal.DotProduct(view_vector) <= 0.0f) {
218 const float diffuse = std::max(0.0f, normal.DotProduct(
mxvk::vec4D(light_dir.
x, light_dir.
y, light_dir.
z, 1.0f)));
219 const float intensity = std::clamp(0.35f + diffuse * 0.65f, 0.0f, 1.0f);
220 faces.push_back({indices, center.
z, intensity});
223 std::ranges::sort(faces, [](
const FaceDraw &left,
const FaceDraw &right) {
return left.depth > right.depth; });
225 for (
const FaceDraw &face : faces) {
226 const auto index0 =
static_cast<std::size_t
>(face.indices[0]);
227 const auto index1 =
static_cast<std::size_t
>(face.indices[1]);
228 const auto index2 =
static_cast<std::size_t
>(face.indices[2]);
229 const auto index3 =
static_cast<std::size_t
>(face.indices[3]);
230 const TexVertex a{projected[index0], {0.0f, 1.0f}, camera_vertices[index0].z};
231 const TexVertex b{projected[index1], {1.0f, 1.0f}, camera_vertices[index1].z};
232 const TexVertex c{projected[index2], {1.0f, 0.0f}, camera_vertices[index2].z};
233 const TexVertex d{projected[index3], {0.0f, 0.0f}, camera_vertices[index3].z};
234 draw_textured_triangle(a, b, c, face.intensity);
235 draw_textured_triangle(a, c, d, face.intensity);
238 frame_sprite->updateTexture(frame_surface->pixels, frame_width, frame_height, frame_surface->pitch);
239 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
244 SurfacePtr frame_surface;
245 const SDL_PixelFormatDetails *frame_format =
nullptr;
247 int frame_width = 1280;
248 int frame_height = 720;
249 int fallback_width = 1280;
250 int fallback_height = 720;
251 float camera_distance = 4.25f;
252 static constexpr float MIN_CAMERA_DISTANCE = 2.2f;
253 static constexpr float MAX_CAMERA_DISTANCE = 10.0f;
254 static constexpr float CAMERA_ZOOM_STEP = 0.45f;
256 void ensure_framebuffer() {
257 if (frame_surface !=
nullptr) {
261 frame_surface = create_frame_surface(frame_width, frame_height);
262 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
263 if (frame_format ==
nullptr) {
264 throw mxvk::Exception(std::format(
"Failed to query 3dmath_texture frame format: {}", SDL_GetError()));
269 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
274 void clear_frame(
mxvk::MXCOLOR color) { SDL_FillSurfaceRect(frame_surface.get(),
nullptr, map_color(color)); }
276 void put_shaded_pixel_unchecked(
int x,
int y,
mxvk::MXCOLOR color, std::uint16_t intensity) {
277 auto *row =
static_cast<std::uint8_t *
>(frame_surface->pixels) + (
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(frame_surface->pitch));
278 auto *pixel = row + (
static_cast<std::size_t
>(x) * 4U);
279 pixel[0] =
static_cast<std::uint8_t
>((
static_cast<std::uint16_t
>(
mxvk::color_r(color)) * intensity) >> 8U);
280 pixel[1] =
static_cast<std::uint8_t
>((
static_cast<std::uint16_t
>(
mxvk::color_g(color)) * intensity) >> 8U);
281 pixel[2] =
static_cast<std::uint8_t
>((
static_cast<std::uint16_t
>(
mxvk::color_b(color)) * intensity) >> 8U);
285 void draw_textured_triangle(
const TexVertex &a,
const TexVertex &b,
const TexVertex &c,
float intensity) {
286 if (texture.width <= 0 || texture.height <= 0 || texture.pixels.empty()) {
290 const mxvk::vec2D p0(a.position.x, a.position.y);
291 const mxvk::vec2D p1(b.position.x, b.position.y);
292 const mxvk::vec2D p2(c.position.x, c.position.y);
297 const bool positive_area = area > 0.0f;
299 const int min_x = std::max(0,
static_cast<int>(std::floor(std::min({p0.x, p1.x, p2.x}))));
300 const int max_x = std::min(frame_width - 1,
static_cast<int>(std::ceil(std::max({p0.x, p1.x, p2.x}))));
301 const int min_y = std::max(0,
static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y}))));
302 const int max_y = std::min(frame_height - 1,
static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y}))));
304 if (min_x > max_x || min_y > max_y) {
308 const float inv_area = 1.0f / area;
309 const float inv_z0 = 1.0f / std::max(a.depth, 0.001f);
310 const float inv_z1 = 1.0f / std::max(b.depth, 0.001f);
311 const float inv_z2 = 1.0f / std::max(c.depth, 0.001f);
312 const float u_over_z0 = a.uv.x * inv_z0;
313 const float u_over_z1 = b.uv.x * inv_z1;
314 const float u_over_z2 = c.uv.x * inv_z2;
315 const float v_over_z0 = a.uv.y * inv_z0;
316 const float v_over_z1 = b.uv.y * inv_z1;
317 const float v_over_z2 = c.uv.y * inv_z2;
318 const std::uint16_t fixed_intensity =
static_cast<std::uint16_t
>(std::clamp(intensity, 0.0f, 1.0f) * 256.0f);
320 const float w0_dx = p2.y - p1.y;
321 const float w0_dy = -(p2.x - p1.x);
322 const float w1_dx = p0.y - p2.y;
323 const float w1_dy = -(p0.x - p2.x);
324 const float w2_dx = p1.y - p0.y;
325 const float w2_dy = -(p1.x - p0.x);
327 const mxvk::vec2D row_start(
static_cast<float>(min_x) + 0.5f,
static_cast<float>(min_y) + 0.5f);
331 float row_inv_z = ((row_w0 * inv_z0) + (row_w1 * inv_z1) + (row_w2 * inv_z2)) * inv_area;
332 float row_u_over_z = ((row_w0 * u_over_z0) + (row_w1 * u_over_z1) + (row_w2 * u_over_z2)) * inv_area;
333 float row_v_over_z = ((row_w0 * v_over_z0) + (row_w1 * v_over_z1) + (row_w2 * v_over_z2)) * inv_area;
335 const float inv_z_dx = ((w0_dx * inv_z0) + (w1_dx * inv_z1) + (w2_dx * inv_z2)) * inv_area;
336 const float inv_z_dy = ((w0_dy * inv_z0) + (w1_dy * inv_z1) + (w2_dy * inv_z2)) * inv_area;
337 const float u_over_z_dx = ((w0_dx * u_over_z0) + (w1_dx * u_over_z1) + (w2_dx * u_over_z2)) * inv_area;
338 const float u_over_z_dy = ((w0_dy * u_over_z0) + (w1_dy * u_over_z1) + (w2_dy * u_over_z2)) * inv_area;
339 const float v_over_z_dx = ((w0_dx * v_over_z0) + (w1_dx * v_over_z1) + (w2_dx * v_over_z2)) * inv_area;
340 const float v_over_z_dy = ((w0_dy * v_over_z0) + (w1_dy * v_over_z1) + (w2_dy * v_over_z2)) * inv_area;
342 for (
int y = min_y; y <= max_y; ++y) {
346 float inv_z = row_inv_z;
347 float u_over_z = row_u_over_z;
348 float v_over_z = row_v_over_z;
350 for (
int x = min_x; x <= max_x; ++x) {
351 if ((positive_area && w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f) || (!positive_area && w0 <= 0.0f && w1 <= 0.0f && w2 <= 0.0f)) {
353 const float reciprocal_z = 1.0f / inv_z;
354 const float u = u_over_z * reciprocal_z;
355 const float v = v_over_z * reciprocal_z;
356 put_shaded_pixel_unchecked(x, y, texture.sample_nearest(u, v), fixed_intensity);
364 u_over_z += u_over_z_dx;
365 v_over_z += v_over_z_dx;
371 row_inv_z += inv_z_dy;
372 row_u_over_z += u_over_z_dy;
373 row_v_over_z += v_over_z_dy;
377 static mxvk::vec4D project_to_screen(
const mxvk::vec4D &point,
int width,
int height) {
378 const float scale =
static_cast<float>(std::min(width, height)) * 0.52f;
379 const float center_x =
static_cast<float>(width) * 0.5f;
380 const float center_y =
static_cast<float>(height) * 0.5f;
381 const float z = std::max(point.
z, 0.001f);
382 return {center_x + (point.
x / z) * scale, center_y - (point.
y / z) * scale, point.
z, 1.0f};
void event(SDL_Event &e) override
Handle one SDL event.
Math3DTextureWindow(const Arguments &args, const std::string &title)
void proc() override
Execute one processing/update step.
std::unique_ptr< SDL_Surface, SurfaceDeleter > SurfacePtr
std::filesystem::path texture_path(const std::string &filename, const std::string &asset_path)
Texture load_texture(const std::string &filename, const std::string &asset_path, bool generate_mipmaps)