aboutsummaryrefslogtreecommitdiffstats
path: root/examples/duck_engine/duck_engine.erl
diff options
context:
space:
mode:
Diffstat (limited to 'examples/duck_engine/duck_engine.erl')
-rw-r--r--examples/duck_engine/duck_engine.erl251
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))
+ ).