diff options
Diffstat (limited to 'examples/duck_engine/duck_engine.erl')
-rw-r--r-- | examples/duck_engine/duck_engine.erl | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/examples/duck_engine/duck_engine.erl b/examples/duck_engine/duck_engine.erl new file mode 100644 index 0000000..ab895c0 --- /dev/null +++ b/examples/duck_engine/duck_engine.erl @@ -0,0 +1,251 @@ +%% This is an example. Feel free to copy and reuse as you wish. +%% +%% This example was almost entirely generated by Grok 3 with +%% minor human-driven changes (such as fixing the position of +%% legs). The raster is imperfect, either missing sides or +%% sides being incorrectly defined. +%% +%% The conversation leading to this example can be found at +%% https://x.com/i/grok/share/IN1mWW5a4g1zcqYTInA4X8BXP + +-module(duck_engine). +-export([run/0]). + +run() -> + % Initialize SDL with video subsystem + case sdl:start([video]) of + ok -> + % Ensure SDL stops when this process exits + sdl:stop_on_exit(), + % Create a window (title, x, y, width, height, flags) + {ok, Window} = sdl_window:create(<<"Duck Engine">>, 100, 100, 800, 600, []), + % Create a renderer with acceleration and vsync + {ok, Renderer} = sdl_renderer:create(Window, -1, [accelerated, present_vsync]), + % Define initial duck vertices + Duck = duck_vertices(), + % Start the rendering loop with initial rotation angles + loop(Window, Renderer, Duck, {0.0, 0.0, 0.0}); + {application_start_error, Reason} -> + io:format("Failed to start esdl2 application: ~p~n", [Reason]), + error; + {error, String} -> + io:format("SDL initialization failed: ~s~n", [String]), + error + end. + +% Main rendering loop +loop(Window, Renderer, Duck, {XAngle, YAngle, ZAngle}) -> + % Set clear color to white (R, G, B, A) + sdl_renderer:set_draw_color(Renderer, 255, 255, 255, 255), + % Clear the renderer + sdl_renderer:clear(Renderer), + % Rotate the duck vertices + RotatedDuck = rotate_duck(Duck, XAngle, YAngle, ZAngle), + % Project and draw the rotated duck + project_and_draw(RotatedDuck, Renderer), + % Present the rendered frame + sdl_renderer:present(Renderer), + % Process all events and determine if we should quit + case handle_events() of + quit -> ok; % Exit if quit event is detected + continue -> + % Sleep for ~16ms (approx 60 FPS) and continue with updated angles + timer:sleep(16), + loop(Window, Renderer, Duck, {XAngle + 0.03, YAngle + 0.05, ZAngle + 0.04}) + end. + +% Handle all pending events in the queue +handle_events() -> + case sdl_events:poll() of + #{type := quit} -> quit; % Quit event detected + #{type := _} -> handle_events(); % Other event, keep processing + false -> continue % No more events, proceed with rendering + end. + +% Rotate duck vertices around X, Y, and Z axes +rotate_duck(Vertices, XAngle, YAngle, ZAngle) -> + RotX = matrix3d:rotate_x(XAngle), + RotY = matrix3d:rotate_y(YAngle), + RotZ = matrix3d:rotate_z(ZAngle), + % Combine rotation matrices: X * (Y * Z) + TempMatrix = matrix3d:multiply(RotY, RotZ), + CombinedMatrix = matrix3d:multiply(RotX, TempMatrix), + [matrix3d:apply(CombinedMatrix, Vertex) || Vertex <- Vertices]. + +% Define duck vertices (unchanged from your description) +duck_vertices() -> + [ + % Body: 0-11 + {1.0, 1.4, 0.8}, {1.0, 1.4, -0.8}, {1.4, 0.6, 0.8}, {1.4, 0.6, -0.8}, + {1.0, -1.0, 0.8}, {1.0, -1.0, -0.8}, {-1.0, 1.4, 0.8}, {-1.0, 1.4, -0.8}, + {-1.4, 0.6, 0.8}, {-1.4, 0.6, -0.8}, {-1.0, -1.0, 0.8}, {-1.0, -1.0, -0.8}, + % Head: 12-19 + {-1.4, 2.0, 0.6}, {-1.4, 2.0, -0.6}, {-1.4, 1.2, 0.6}, {-1.4, 1.2, -0.6}, + {-2.0, 2.0, 0.6}, {-2.0, 2.0, -0.6}, {-2.0, 1.2, 0.6}, {-2.0, 1.2, -0.6}, + % Beak: 20-25 + {-2.4, 1.8, 0.2}, {-2.4, 1.8, -0.2}, {-2.4, 1.4, 0.2}, {-2.4, 1.4, -0.2}, + {-2.0, 1.6, 0.2}, {-2.0, 1.6, -0.2}, + % Right Leg: 26-29 + {-1.0, -1.0, -0.3}, {-1.0, -2.0, -0.3}, {-1.2, -2.0, -0.1}, {-1.2, -2.0, -0.5}, + % Left Leg: 30-33 + {-1.0, -1.0, 0.3}, {-1.0, -2.0, 0.3}, {-1.2, -2.0, 0.1}, {-1.2, -2.0, 0.5} + ]. + +% Project 3D vertices to 2D and draw filled triangles +project_and_draw(Vertices, Renderer) -> + Projected = [project_vertex(V) || V <- Vertices], + + % Body - Front (bright yellow) + sdl_renderer:set_draw_color(Renderer, 255, 255, 0, 255), + FrontTriangles = [ + [0, 2, 4], [6, 8, 10], [0, 6, 10], [0, 4, 10] + ], + [fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- FrontTriangles], + + % Body - Back (darker yellow) + sdl_renderer:set_draw_color(Renderer, 220, 220, 0, 255), + BackTriangles = [ + [1, 3, 5], [7, 9, 11], [1, 7, 11], [1, 5, 11] + ], + [fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- BackTriangles], + + % Body - Right (golden yellow) + sdl_renderer:set_draw_color(Renderer, 255, 215, 0, 255), + RightTriangles = [ + [0, 1, 3], [3, 5, 4], [0, 2, 4] + ], + [fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- RightTriangles], + + % Body - Left (pale yellow) + sdl_renderer:set_draw_color(Renderer, 255, 245, 0, 255), + LeftTriangles = [ + [6, 7, 9], [9, 11, 10], [6, 8, 10] + ], + [fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- LeftTriangles], + + % Body - Top (light yellow) + sdl_renderer:set_draw_color(Renderer, 255, 255, 100, 255), + TopTriangles = [ + [0, 1, 6], [1, 7, 6] + ], + [fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- TopTriangles], + + % Body - Bottom (deep yellow) + sdl_renderer:set_draw_color(Renderer, 255, 200, 0, 255), + BottomTriangles = [ + [4, 5, 10], [5, 11, 10] + ], + [fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- BottomTriangles], + + % Neck (match head color: slightly greenish yellow) + sdl_renderer:set_draw_color(Renderer, 245, 255, 0, 255), + NeckTriangles = [ + [6, 7, 18], [7, 19, 18], % Left-back to head bottom + [0, 1, 14], [1, 15, 14] % Right-front to head bottom + ], + [fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- NeckTriangles], + + % Head (slightly greenish yellow) + sdl_renderer:set_draw_color(Renderer, 245, 255, 0, 255), + HeadTriangles = [ + [12, 13, 15], [12, 14, 15], [16, 17, 19], [16, 18, 19], + [12, 16, 18], [12, 14, 18], [13, 17, 19], [13, 15, 19] + ], + [fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- HeadTriangles], + + % Beak (orange-yellow) + sdl_renderer:set_draw_color(Renderer, 255, 235, 0, 255), + BeakTriangles = [ + [20, 21, 23], [20, 22, 23], [20, 24, 25], [20, 21, 25], + [22, 24, 25], [22, 23, 25] + ], + [fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- BeakTriangles], + + % Legs (muted yellow) + sdl_renderer:set_draw_color(Renderer, 235, 235, 0, 255), + LegTriangles = [ + [26, 27, 28], [26, 27, 29], [30, 31, 32], [30, 31, 33] + ], + [fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- LegTriangles], + + % Draw edges in black + sdl_renderer:set_draw_color(Renderer, 0, 0, 0, 255), + Edges = [ + {0, 1}, {1, 3}, {3, 5}, {5, 4}, {4, 2}, {2, 0}, + {6, 7}, {7, 9}, {9, 11}, {11, 10}, {10, 8}, {8, 6}, + {0, 6}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {5, 11}, + {12, 13}, {13, 15}, {15, 14}, {14, 12}, + {16, 17}, {17, 19}, {19, 18}, {18, 16}, + {12, 16}, {13, 17}, {14, 18}, {15, 19}, + {6, 16}, {7, 17}, {8, 18}, {9, 19}, + {20, 21}, {21, 23}, {23, 22}, {22, 20}, + {20, 24}, {21, 25}, {22, 24}, {23, 25}, {24, 25}, + {16, 24}, {17, 25}, + {26, 27}, {27, 28}, {27, 29}, + {30, 31}, {31, 32}, {31, 33} + ], + [draw_line(Renderer, lists:nth(A+1, Projected), lists:nth(B+1, Projected)) || {A, B} <- Edges]. + +% Project a 3D vertex to 2D screen coordinates (unchanged) +project_vertex({X, Y, Z}) -> + ZOffset = Z + 10, + Scale = 800, + ScreenX = 400 + round((X / ZOffset) * Scale), + ScreenY = 300 + round((Y / ZOffset) * Scale), + {ScreenX, ScreenY}. + +% Draw a line (unchanged) +draw_line(Renderer, {X1, Y1}, {X2, Y2}) -> + sdl_renderer:draw_line(Renderer, X1, Y1, X2, Y2). + +% Fill a triangle with scanline approach, handling degenerate cases +fill_triangle(Renderer, Points) -> + [{X1, Y1}, {X2, Y2}, {X3, Y3}] = lists:sort(fun({_, YA}, {_, YB}) -> YA =< YB end, Points), + if + Y1 == Y3 -> % Degenerate case: all Ys equal + if + X1 == X2 andalso X2 == X3 -> ok; % Single point + true -> + XMin = lists:min([X1, X2, X3]), + XMax = lists:max([X1, X2, X3]), + sdl_renderer:draw_line(Renderer, XMin, Y1, XMax, Y1) + end; + true -> + case {Y1 == Y2, Y2 == Y3} of + {true, false} -> % Flat top + fill_flat_top_triangle(Renderer, X1, Y1, X2, Y2, X3, Y3); + {false, true} -> % Flat bottom + fill_flat_bottom_triangle(Renderer, X1, Y1, X2, Y2, X3, Y3); + {false, false} -> % General case + T = (Y2 - Y1) / (Y3 - Y1), + XMid = X1 + round(T * (X3 - X1)), + fill_flat_bottom_triangle(Renderer, X1, Y1, X2, Y2, XMid, Y2), + fill_flat_top_triangle(Renderer, X2, Y2, XMid, Y2, X3, Y3) + end + end. + +% Fill a flat-bottom triangle +fill_flat_bottom_triangle(Renderer, X1, Y1, X2, Y2, X3, _) -> + lists:foreach( + fun(Y) -> + T = (Y - Y1) / (Y2 - Y1), + XA = X1 + round(T * (X2 - X1)), + XB = X1 + round(T * (X3 - X1)), + {XStart, XEnd} = if XA =< XB -> {XA, XB}; true -> {XB, XA} end, + sdl_renderer:draw_line(Renderer, XStart, Y, XEnd, Y) + end, + lists:seq(round(Y1), round(Y2)) + ). + +% Fill a flat-top triangle +fill_flat_top_triangle(Renderer, X1, Y1, X2, _, X3, Y3) -> + lists:foreach( + fun(Y) -> + T = (Y - Y1) / (Y3 - Y1), + XA = X1 + round(T * (X3 - X1)), + XB = X2 + round(T * (X3 - X2)), + {XStart, XEnd} = if XA =< XB -> {XA, XB}; true -> {XB, XA} end, + sdl_renderer:draw_line(Renderer, XStart, Y, XEnd, Y) + end, + lists:seq(round(Y1), round(Y3)) + ). |