Skip to content

Commit c1eeb85

Browse files
committed
Add duck_engine example generated by Grok 3
1 parent dc646d3 commit c1eeb85

File tree

4 files changed

+307
-0
lines changed

4 files changed

+307
-0
lines changed

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,7 @@ hello_sdl:: all
5656
bullet_engine:: all
5757
erlc -o examples/bullet_engine examples/bullet_engine/*.erl
5858
cd examples/bullet_engine && ./start.sh
59+
60+
duck_engine: all
61+
erlc -o examples/duck_engine examples/duck_engine/*.erl
62+
cd examples/duck_engine && ./start.sh

examples/duck_engine/duck_engine.erl

+251
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
%% This is an example. Feel free to copy and reuse as you wish.
2+
%%
3+
%% This example was almost entirely generated by Grok 3 with
4+
%% minor human-driven changes (such as fixing the position of
5+
%% legs). The raster is imperfect, either missing sides or
6+
%% sides being incorrectly defined.
7+
%%
8+
%% The conversation leading to this example can be found at
9+
%% https://x.com/i/grok/share/IN1mWW5a4g1zcqYTInA4X8BXP
10+
11+
-module(duck_engine).
12+
-export([run/0]).
13+
14+
run() ->
15+
% Initialize SDL with video subsystem
16+
case sdl:start([video]) of
17+
ok ->
18+
% Ensure SDL stops when this process exits
19+
sdl:stop_on_exit(),
20+
% Create a window (title, x, y, width, height, flags)
21+
{ok, Window} = sdl_window:create(<<"Duck Engine">>, 100, 100, 800, 600, []),
22+
% Create a renderer with acceleration and vsync
23+
{ok, Renderer} = sdl_renderer:create(Window, -1, [accelerated, present_vsync]),
24+
% Define initial duck vertices
25+
Duck = duck_vertices(),
26+
% Start the rendering loop with initial rotation angles
27+
loop(Window, Renderer, Duck, {0.0, 0.0, 0.0});
28+
{application_start_error, Reason} ->
29+
io:format("Failed to start esdl2 application: ~p~n", [Reason]),
30+
error;
31+
{error, String} ->
32+
io:format("SDL initialization failed: ~s~n", [String]),
33+
error
34+
end.
35+
36+
% Main rendering loop
37+
loop(Window, Renderer, Duck, {XAngle, YAngle, ZAngle}) ->
38+
% Set clear color to white (R, G, B, A)
39+
sdl_renderer:set_draw_color(Renderer, 255, 255, 255, 255),
40+
% Clear the renderer
41+
sdl_renderer:clear(Renderer),
42+
% Rotate the duck vertices
43+
RotatedDuck = rotate_duck(Duck, XAngle, YAngle, ZAngle),
44+
% Project and draw the rotated duck
45+
project_and_draw(RotatedDuck, Renderer),
46+
% Present the rendered frame
47+
sdl_renderer:present(Renderer),
48+
% Process all events and determine if we should quit
49+
case handle_events() of
50+
quit -> ok; % Exit if quit event is detected
51+
continue ->
52+
% Sleep for ~16ms (approx 60 FPS) and continue with updated angles
53+
timer:sleep(16),
54+
loop(Window, Renderer, Duck, {XAngle + 0.03, YAngle + 0.05, ZAngle + 0.04})
55+
end.
56+
57+
% Handle all pending events in the queue
58+
handle_events() ->
59+
case sdl_events:poll() of
60+
#{type := quit} -> quit; % Quit event detected
61+
#{type := _} -> handle_events(); % Other event, keep processing
62+
false -> continue % No more events, proceed with rendering
63+
end.
64+
65+
% Rotate duck vertices around X, Y, and Z axes
66+
rotate_duck(Vertices, XAngle, YAngle, ZAngle) ->
67+
RotX = matrix3d:rotate_x(XAngle),
68+
RotY = matrix3d:rotate_y(YAngle),
69+
RotZ = matrix3d:rotate_z(ZAngle),
70+
% Combine rotation matrices: X * (Y * Z)
71+
TempMatrix = matrix3d:multiply(RotY, RotZ),
72+
CombinedMatrix = matrix3d:multiply(RotX, TempMatrix),
73+
[matrix3d:apply(CombinedMatrix, Vertex) || Vertex <- Vertices].
74+
75+
% Define duck vertices (unchanged from your description)
76+
duck_vertices() ->
77+
[
78+
% Body: 0-11
79+
{1.0, 1.4, 0.8}, {1.0, 1.4, -0.8}, {1.4, 0.6, 0.8}, {1.4, 0.6, -0.8},
80+
{1.0, -1.0, 0.8}, {1.0, -1.0, -0.8}, {-1.0, 1.4, 0.8}, {-1.0, 1.4, -0.8},
81+
{-1.4, 0.6, 0.8}, {-1.4, 0.6, -0.8}, {-1.0, -1.0, 0.8}, {-1.0, -1.0, -0.8},
82+
% Head: 12-19
83+
{-1.4, 2.0, 0.6}, {-1.4, 2.0, -0.6}, {-1.4, 1.2, 0.6}, {-1.4, 1.2, -0.6},
84+
{-2.0, 2.0, 0.6}, {-2.0, 2.0, -0.6}, {-2.0, 1.2, 0.6}, {-2.0, 1.2, -0.6},
85+
% Beak: 20-25
86+
{-2.4, 1.8, 0.2}, {-2.4, 1.8, -0.2}, {-2.4, 1.4, 0.2}, {-2.4, 1.4, -0.2},
87+
{-2.0, 1.6, 0.2}, {-2.0, 1.6, -0.2},
88+
% Right Leg: 26-29
89+
{-1.0, -1.0, -0.3}, {-1.0, -2.0, -0.3}, {-1.2, -2.0, -0.1}, {-1.2, -2.0, -0.5},
90+
% Left Leg: 30-33
91+
{-1.0, -1.0, 0.3}, {-1.0, -2.0, 0.3}, {-1.2, -2.0, 0.1}, {-1.2, -2.0, 0.5}
92+
].
93+
94+
% Project 3D vertices to 2D and draw filled triangles
95+
project_and_draw(Vertices, Renderer) ->
96+
Projected = [project_vertex(V) || V <- Vertices],
97+
98+
% Body - Front (bright yellow)
99+
sdl_renderer:set_draw_color(Renderer, 255, 255, 0, 255),
100+
FrontTriangles = [
101+
[0, 2, 4], [6, 8, 10], [0, 6, 10], [0, 4, 10]
102+
],
103+
[fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- FrontTriangles],
104+
105+
% Body - Back (darker yellow)
106+
sdl_renderer:set_draw_color(Renderer, 220, 220, 0, 255),
107+
BackTriangles = [
108+
[1, 3, 5], [7, 9, 11], [1, 7, 11], [1, 5, 11]
109+
],
110+
[fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- BackTriangles],
111+
112+
% Body - Right (golden yellow)
113+
sdl_renderer:set_draw_color(Renderer, 255, 215, 0, 255),
114+
RightTriangles = [
115+
[0, 1, 3], [3, 5, 4], [0, 2, 4]
116+
],
117+
[fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- RightTriangles],
118+
119+
% Body - Left (pale yellow)
120+
sdl_renderer:set_draw_color(Renderer, 255, 245, 0, 255),
121+
LeftTriangles = [
122+
[6, 7, 9], [9, 11, 10], [6, 8, 10]
123+
],
124+
[fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- LeftTriangles],
125+
126+
% Body - Top (light yellow)
127+
sdl_renderer:set_draw_color(Renderer, 255, 255, 100, 255),
128+
TopTriangles = [
129+
[0, 1, 6], [1, 7, 6]
130+
],
131+
[fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- TopTriangles],
132+
133+
% Body - Bottom (deep yellow)
134+
sdl_renderer:set_draw_color(Renderer, 255, 200, 0, 255),
135+
BottomTriangles = [
136+
[4, 5, 10], [5, 11, 10]
137+
],
138+
[fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- BottomTriangles],
139+
140+
% Neck (match head color: slightly greenish yellow)
141+
sdl_renderer:set_draw_color(Renderer, 245, 255, 0, 255),
142+
NeckTriangles = [
143+
[6, 7, 18], [7, 19, 18], % Left-back to head bottom
144+
[0, 1, 14], [1, 15, 14] % Right-front to head bottom
145+
],
146+
[fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- NeckTriangles],
147+
148+
% Head (slightly greenish yellow)
149+
sdl_renderer:set_draw_color(Renderer, 245, 255, 0, 255),
150+
HeadTriangles = [
151+
[12, 13, 15], [12, 14, 15], [16, 17, 19], [16, 18, 19],
152+
[12, 16, 18], [12, 14, 18], [13, 17, 19], [13, 15, 19]
153+
],
154+
[fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- HeadTriangles],
155+
156+
% Beak (orange-yellow)
157+
sdl_renderer:set_draw_color(Renderer, 255, 235, 0, 255),
158+
BeakTriangles = [
159+
[20, 21, 23], [20, 22, 23], [20, 24, 25], [20, 21, 25],
160+
[22, 24, 25], [22, 23, 25]
161+
],
162+
[fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- BeakTriangles],
163+
164+
% Legs (muted yellow)
165+
sdl_renderer:set_draw_color(Renderer, 235, 235, 0, 255),
166+
LegTriangles = [
167+
[26, 27, 28], [26, 27, 29], [30, 31, 32], [30, 31, 33]
168+
],
169+
[fill_triangle(Renderer, [lists:nth(A+1, Projected) || A <- Tri]) || Tri <- LegTriangles],
170+
171+
% Draw edges in black
172+
sdl_renderer:set_draw_color(Renderer, 0, 0, 0, 255),
173+
Edges = [
174+
{0, 1}, {1, 3}, {3, 5}, {5, 4}, {4, 2}, {2, 0},
175+
{6, 7}, {7, 9}, {9, 11}, {11, 10}, {10, 8}, {8, 6},
176+
{0, 6}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {5, 11},
177+
{12, 13}, {13, 15}, {15, 14}, {14, 12},
178+
{16, 17}, {17, 19}, {19, 18}, {18, 16},
179+
{12, 16}, {13, 17}, {14, 18}, {15, 19},
180+
{6, 16}, {7, 17}, {8, 18}, {9, 19},
181+
{20, 21}, {21, 23}, {23, 22}, {22, 20},
182+
{20, 24}, {21, 25}, {22, 24}, {23, 25}, {24, 25},
183+
{16, 24}, {17, 25},
184+
{26, 27}, {27, 28}, {27, 29},
185+
{30, 31}, {31, 32}, {31, 33}
186+
],
187+
[draw_line(Renderer, lists:nth(A+1, Projected), lists:nth(B+1, Projected)) || {A, B} <- Edges].
188+
189+
% Project a 3D vertex to 2D screen coordinates (unchanged)
190+
project_vertex({X, Y, Z}) ->
191+
ZOffset = Z + 10,
192+
Scale = 800,
193+
ScreenX = 400 + round((X / ZOffset) * Scale),
194+
ScreenY = 300 + round((Y / ZOffset) * Scale),
195+
{ScreenX, ScreenY}.
196+
197+
% Draw a line (unchanged)
198+
draw_line(Renderer, {X1, Y1}, {X2, Y2}) ->
199+
sdl_renderer:draw_line(Renderer, X1, Y1, X2, Y2).
200+
201+
% Fill a triangle with scanline approach, handling degenerate cases
202+
fill_triangle(Renderer, Points) ->
203+
[{X1, Y1}, {X2, Y2}, {X3, Y3}] = lists:sort(fun({_, YA}, {_, YB}) -> YA =< YB end, Points),
204+
if
205+
Y1 == Y3 -> % Degenerate case: all Ys equal
206+
if
207+
X1 == X2 andalso X2 == X3 -> ok; % Single point
208+
true ->
209+
XMin = lists:min([X1, X2, X3]),
210+
XMax = lists:max([X1, X2, X3]),
211+
sdl_renderer:draw_line(Renderer, XMin, Y1, XMax, Y1)
212+
end;
213+
true ->
214+
case {Y1 == Y2, Y2 == Y3} of
215+
{true, false} -> % Flat top
216+
fill_flat_top_triangle(Renderer, X1, Y1, X2, Y2, X3, Y3);
217+
{false, true} -> % Flat bottom
218+
fill_flat_bottom_triangle(Renderer, X1, Y1, X2, Y2, X3, Y3);
219+
{false, false} -> % General case
220+
T = (Y2 - Y1) / (Y3 - Y1),
221+
XMid = X1 + round(T * (X3 - X1)),
222+
fill_flat_bottom_triangle(Renderer, X1, Y1, X2, Y2, XMid, Y2),
223+
fill_flat_top_triangle(Renderer, X2, Y2, XMid, Y2, X3, Y3)
224+
end
225+
end.
226+
227+
% Fill a flat-bottom triangle
228+
fill_flat_bottom_triangle(Renderer, X1, Y1, X2, Y2, X3, _) ->
229+
lists:foreach(
230+
fun(Y) ->
231+
T = (Y - Y1) / (Y2 - Y1),
232+
XA = X1 + round(T * (X2 - X1)),
233+
XB = X1 + round(T * (X3 - X1)),
234+
{XStart, XEnd} = if XA =< XB -> {XA, XB}; true -> {XB, XA} end,
235+
sdl_renderer:draw_line(Renderer, XStart, Y, XEnd, Y)
236+
end,
237+
lists:seq(round(Y1), round(Y2))
238+
).
239+
240+
% Fill a flat-top triangle
241+
fill_flat_top_triangle(Renderer, X1, Y1, X2, _, X3, Y3) ->
242+
lists:foreach(
243+
fun(Y) ->
244+
T = (Y - Y1) / (Y3 - Y1),
245+
XA = X1 + round(T * (X3 - X1)),
246+
XB = X2 + round(T * (X3 - X2)),
247+
{XStart, XEnd} = if XA =< XB -> {XA, XB}; true -> {XB, XA} end,
248+
sdl_renderer:draw_line(Renderer, XStart, Y, XEnd, Y)
249+
end,
250+
lists:seq(round(Y1), round(Y3))
251+
).

examples/duck_engine/matrix3d.erl

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
%% This is an example. Feel free to copy and reuse as you wish.
2+
3+
-module(matrix3d).
4+
-export([rotate_x/1, rotate_y/1, rotate_z/1, multiply/2, apply/2]).
5+
6+
% Rotation matrix around X-axis (angle in radians)
7+
rotate_x(Angle) ->
8+
Cos = math:cos(Angle),
9+
Sin = math:sin(Angle),
10+
[[1, 0, 0 ],
11+
[0, Cos, -Sin ],
12+
[0, Sin, Cos]].
13+
14+
% Rotation matrix around Y-axis
15+
rotate_y(Angle) ->
16+
Cos = math:cos(Angle),
17+
Sin = math:sin(Angle),
18+
[[ Cos, 0, Sin ],
19+
[ 0, 1, 0 ],
20+
[-Sin, 0, Cos]].
21+
22+
% Rotation matrix around Z-axis
23+
rotate_z(Angle) ->
24+
Cos = math:cos(Angle),
25+
Sin = math:sin(Angle),
26+
[[Cos, -Sin, 0 ],
27+
[Sin, Cos, 0 ],
28+
[0, 0, 1]].
29+
30+
% Multiply two 3x3 matrices
31+
multiply(A, B) ->
32+
[[A11, A12, A13],
33+
[A21, A22, A23],
34+
[A31, A32, A33]] = A,
35+
[[B11, B12, B13],
36+
[B21, B22, B23],
37+
[B31, B32, B33]] = B,
38+
[[A11*B11 + A12*B21 + A13*B31, A11*B12 + A12*B22 + A13*B32, A11*B13 + A12*B23 + A13*B33],
39+
[A21*B11 + A22*B21 + A23*B31, A21*B12 + A22*B22 + A23*B32, A21*B13 + A22*B23 + A23*B33],
40+
[A31*B11 + A32*B21 + A33*B31, A31*B12 + A32*B22 + A33*B32, A31*B13 + A32*B23 + A33*B33]].
41+
42+
% Apply a 3x3 matrix to a 3D vertex {X, Y, Z}
43+
apply(Matrix, {X, Y, Z}) ->
44+
[[M11, M12, M13],
45+
[M21, M22, M23],
46+
[M31, M32, M33]] = Matrix,
47+
NewX = M11 * X + M12 * Y + M13 * Z,
48+
NewY = M21 * X + M22 * Y + M23 * Z,
49+
NewZ = M31 * X + M32 * Y + M33 * Z,
50+
{NewX, NewY, NewZ}.

examples/duck_engine/start.sh

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/bin/sh
2+
erl -smp enable +stbt db -pa ../../ebin -eval "duck_engine:run()."

0 commit comments

Comments
 (0)