sidenote

In the last couple of months I’ve watched some videos of Achievement Hunter’s Let’s Play Minecraft videos where were some encounters with Endermen. I was really fascinated by these creatures since they can teleport, demand respect, mysterious, etc.

I have been “playing” with LaTeX, especially with LuaTeX, recently, and I wanted to make a 3D-like picture. So I have chosen tikz‑3dplot and LuaTeX to draw an Enderman. (I think this is totally drawable with pure TikZ (syntax), therefore compilable with pdflatex, etc., but I found making this using LuaTeX much more simple.)

Enderman Enderman

Image source (left): File:Enderman normal.png – Minecraft Wiki

My drawing (right) is not accurate by many aspects, but I’ve achieved what I wanted.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
% Enderman
% Author: István Szántai (szantaii)
\documentclass{article}

\usepackage{luacode}

\usepackage{xcolor}

\usepackage{tikz}
\usepackage{tikz-3dplot}

\usepackage[active, tightpage]{preview}
\PreviewEnvironment{tikzpicture}
\setlength{\PreviewBorder}{1cm}

\definecolor{endermanblack}{HTML}{000000}
\definecolor{endermangray}{HTML}{161616}
%\definecolor{endermanpurple}{HTML}{CC00FA}
%\definecolor{endermanlightpurple}{HTML}{E079FA}
\definecolor{endermanpurple}{HTML}{FF9EFF}
\definecolor{endermanlightpurple}{HTML}{FFC9FF}
\definecolor{particlecolor}{HTML}{DF4AF8}

\begin{luacode*}
    function draw_coordinate_system()
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(3,0,0) node[text=white!50!gray,anchor=north east]{$x$};")
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(0,3,0) node[text=white!50!gray,anchor=west]{$y$};")
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(0,0,3) node[text=white!50!gray,anchor=south]{$z$};")
    end
   
    function tikzcube(x, y, z, color)
        --[[
        \draw[fill=red] (0.5, 0.5, -0.5) -- (-0.5, 0.5, -0.5) -- (-0.5, -0.5, -0.5) -- (0.5, -0.5, -0.5) -- cycle;
        \draw[fill=red] (-0.5, 0.5, -0.5) -- (-0.5, 0.5, 0.5) -- (-0.5, -0.5, 0.5) -- (-0.5, -0.5, -0.5) -- cycle;
        \draw[fill=red] (-0.5, -0.5, -0.5) -- (0.5, -0.5, -0.5) -- (0.5, -0.5, 0.5) -- (-0.5, -0.5, 0.5) -- cycle;
        \draw[fill=red] (0.5, 0.5, -0.5) -- (-0.5, 0.5, -0.5) -- (-0.5, 0.5, 0.5) -- (0.5, 0.5, 0.5) -- cycle;
        \draw[fill=red] (0.5, -0.5, -0.5) -- (0.5, 0.5, -0.5) -- (0.5, 0.5, 0.5) -- (0.5, -0.5, 0.5) -- cycle;
        \draw[fill=red] (0.5, 0.5, 0.5) -- (-0.5, 0.5, 0.5) -- (-0.5, -0.5, 0.5) -- (0.5, -0.5, 0.5) -- cycle;
        ]]

        local cube = ""
       
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- cycle;"
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- cycle;"
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- cycle;"
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- cycle;"
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- cycle;"
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- cycle;"
       
        tex.sprint(cube)
    end
   
    function draw_head(x_pos, y_pos, z_pos)
        local color
        for x = x_pos, x_pos + 7, 1 do
            for y = y_pos, y_pos + 7, 1 do
                for z = z_pos, z_pos + 6, 1 do
                    if (x == x_pos or x == x_pos + 7 or
                        y == y_pos or y == y_pos + 7 or
                        z == z_pos or z == z_pos + 6) and
                        not (x == x_pos + 7 and y > y_pos and y < y_pos + 7 and z == z_pos) then
                       
                        if x == x_pos + 7 and
                            (y == y_pos or y == y_pos + 2 or
                            y == y_pos + 5 or y == y_pos + 7) and
                            z == z_pos + 2 then
                           
                            tikzcube(x, y, z, "endermanlightpurple")
                        elseif x == x_pos + 7 and
                            (y == y_pos + 1 or y == y_pos + 6) and
                            z == z_pos + 2 then
                           
                            tikzcube(x, y, z, "endermanpurple")
                        else
                            if math.random(0, 8) < 6 then
                                color = "endermangray"
                            else
                                color = "endermanblack"
                            end
                            tikzcube(x, y, z, color)
                        end
                    end
                end
            end
        end
    end
   
    function draw_bodypart(x_pos, y_pos, z_pos, x_length, y_length, z_length)
        local color
        for x = x_pos, x_pos + x_length - 1, 1 do
            for y = y_pos, y_pos + y_length - 1, 1 do
                for z = z_pos, z_pos + z_length - 1, 1 do
                    if x == x_pos or x == x_pos + x_length - 1 or
                        y == y_pos or y == y_pos + y_length - 1 or
                        z == z_pos or z == z_pos + z_length - 1 then
                       
                        if math.random(0, 8) < 6 then
                            color = "endermangray"
                        else
                            color = "endermanblack"
                        end
                        tikzcube(x, y, z, color)
                    end
                end
            end
        end
    end
   
    function draw_particles(x_min, x_max, y_min, y_max, z_min, z_max)
        local x
        local y
        local z
        local black_amount
        local particle_size
        local particle_scale
        local particle_count = math.random(30, 40)
        local particle
       
        for i = 1, particle_count, 1 do
           
            x = math.random(x_min, x_max)
            y = math.random(y_min, y_max)
            z = math.random(z_min, z_max)
           
            particle_size = math.random(1, 8)
            particle_scale = math.random(20, 100) / 100
            black_amount = math.random(0, 25)
           
            tex.sprint("\\tdplottransformmainscreen{" .. x .. "}{" .. y .. "}{" .. z .. "}")
            for i = 0, particle_size - 1, 1 do
                for j = 0, particle_size - 1, 1 do
                    if math.random(0, 1) == 0 and
                        ((i ~= 0 and j ~= 0) and
                        (i ~= particle_size - 1 and j ~= 0) and
                        (j ~= particle_size - 1 and i ~= 0) and
                        (i ~= particle_size - 1 and j ~= particle_size - 1)) then
                       
                        particle = "\\filldraw[black!" .. black_amount ..
                            "!particlecolor, tdplot_screen_coords] (" ..
                            i * particle_scale * 0.25 .. "+\\tdplotresx, " ..
                            j * particle_scale * 0.25 .. "+\\tdplotresy) " ..
                            "rectangle +(" .. particle_scale .. "*0.25, " ..
                            particle_scale .. "*0.25);"
                       
                        tex.sprint(particle)
                    end
                end
            end
        end
    end
   
    function draw_enderman(x_rotation, z_rotation)
        tex.sprint("\\tdplotsetmaincoords{" .. x_rotation .. "}{" .. z_rotation .. "}")
        tex.sprint("\\begin{tikzpicture}[tdplot_main_coords]")
       
        math.randomseed(os.time())
       
        draw_bodypart(3, -2, -30, 2, 2, 30) -- right arm
        draw_bodypart(3, 1, -42, 2, 2, 30) -- right leg
        draw_bodypart(3, 5, -42, 2, 2, 30) -- left leg
        draw_bodypart(2, 0, -12, 4, 8, 12) -- body
        draw_bodypart(3, 8, -30, 2, 2, 30)-- left arm
        draw_head(0, 0, 0) -- head
       
        draw_particles(-10, 10, -10, 10, -44, 10)
        -- draw_coordinate_system()
       
        tex.sprint("\\end{tikzpicture}")
    end
\end{luacode*}

\begin{document}
\luadirect{draw_enderman(70, 130)}
\end{document}

Copy the code, save with UTF-8 (without BOM) encoding, and compile using lualatex.

lualatex filename.tex

Update ~16:00 CEST on 21 July 2014:

Yesterday I’ve sent in this code as an example for TeXample.net. I got an answer from the site maintainer including a small improvement suggestion, namely that I should try algorithmize the drawing of a single (TikZ) cube (instead of concatenating a bunch of strings and make the TeX engine do the calculations). So I did, see the code below. (More details below the code.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
% Enderman
% Author: István Szántai (szantaii)
\documentclass{article}

\usepackage{luacode}

\usepackage{xcolor}

\usepackage{tikz}
\usepackage{tikz-3dplot}

\usepackage[active, tightpage]{preview}
\PreviewEnvironment{tikzpicture}
\setlength{\PreviewBorder}{1cm}

\definecolor{endermanblack}{HTML}{000000}
\definecolor{endermangray}{HTML}{161616}
%\definecolor{endermanpurple}{HTML}{CC00FA}
%\definecolor{endermanlightpurple}{HTML}{E079FA}
\definecolor{endermanpurple}{HTML}{FF9EFF}
\definecolor{endermanlightpurple}{HTML}{FFC9FF}
\definecolor{particlecolor}{HTML}{DF4AF8}

\begin{luacode*}
    function draw_coordinate_system()
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(3,0,0) node[text=white!50!gray,anchor=north east]{$x$};")
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(0,3,0) node[text=white!50!gray,anchor=west]{$y$};")
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(0,0,3) node[text=white!50!gray,anchor=south]{$z$};")
    end
   
    function matrix_scalar_multiplication(matrix, scalar)
        local rows = #matrix
        local cols = #matrix[1]
        local tmp_matrix = {}
       
        for i = 1, rows do
            tmp_matrix[i] = {}
            for j = 1, cols do
                tmp_matrix[i][j] = matrix[i][j] * scalar
            end
        end
       
        return tmp_matrix
    end
   
    function shift_coordinates(matrix, array)
        local matrix_rows = #matrix
        local matrix_cols = #matrix[1]
        local array_length = #array
        local tmp_matrix = {}
       
        if matrix_cols == array_length then
            for i = 1, matrix_rows do
                tmp_matrix[i] = {}
                for j = 1, matrix_cols do
                    tmp_matrix[i][j] = matrix[i][j] + array[j]
                end
            end
           
            return tmp_matrix
        else
            return nil
        end
    end
   
    function tikzcube(x, y, z, color)
        local side_1 = {{1, 1, -1},
            {-1, 1, -1},
            {-1, -1, -1},
            {1, -1, -1}}
        local side_2 = {{-1, 1, -1},
            {-1, 1, 1},
            {-1, -1, 1},
            {-1, -1, -1}}
        local side_3 = {{-1, -1, -1},
            {1, -1, -1},
            {1, -1, 1},
            {-1, -1, 1}}
        local side_4 = {{1, 1, -1},
            {-1, 1, -1},
            {-1, 1, 1},
            {1, 1, 1}}
        local side_5 = {{1, -1, -1},
            {1, 1, -1},
            {1, 1, 1},
            {1, -1, 1}}
        local side_6 = {{1, 1, 1},
            {-1, 1, 1},
            {-1, -1, 1},
            {1, -1, 1}}
        local cube_sides = {side_1, side_2, side_3, side_4, side_5, side_6}
        local tex_cube = ""
       
        for i = 1, #cube_sides do
            tex_cube = tex_cube .. "\\draw[ultra thin, fill=" .. color .. "] "
           
            local current_side = matrix_scalar_multiplication(cube_sides[i], 0.5)
            current_side = shift_coordinates(current_side, {x, y, z})
           
            local current_side_rows = #current_side
            local current_side_cols = #current_side[1]
           
            for j = 1, current_side_rows do
                for k = 1, current_side_cols do
                    if k == 1 then
                        tex_cube = tex_cube .. "("
                    end
                   
                    tex_cube = tex_cube .. current_side[j][k]
                   
                    if k ~= current_side_cols then
                        tex_cube = tex_cube .. ", "
                    else
                        tex_cube = tex_cube .. ") -- "
                    end
                end
            end
           
            tex_cube = tex_cube .. "cycle;"
           
        end
       
        tex.sprint(tex_cube)
    end
   
    function draw_head(x_pos, y_pos, z_pos)
        local color
        for x = x_pos, x_pos + 7, 1 do
            for y = y_pos, y_pos + 7, 1 do
                for z = z_pos, z_pos + 6, 1 do
                    if (x == x_pos or x == x_pos + 7 or
                        y == y_pos or y == y_pos + 7 or
                        z == z_pos or z == z_pos + 6) and
                        not (x == x_pos + 7 and y > y_pos and y < y_pos + 7 and z == z_pos) then
                       
                        if x == x_pos + 7 and
                            (y == y_pos or y == y_pos + 2 or
                            y == y_pos + 5 or y == y_pos + 7) and
                            z == z_pos + 2 then
                           
                            tikzcube(x, y, z, "endermanlightpurple")
                        elseif x == x_pos + 7 and
                            (y == y_pos + 1 or y == y_pos + 6) and
                            z == z_pos + 2 then
                           
                            tikzcube(x, y, z, "endermanpurple")
                        else
                            if math.random(0, 8) < 6 then
                                color = "endermangray"
                            else
                                color = "endermanblack"
                            end
                            tikzcube(x, y, z, color)
                        end
                    end
                end
            end
        end
    end
   
    function draw_bodypart(x_pos, y_pos, z_pos, x_length, y_length, z_length)
        local color
        for x = x_pos, x_pos + x_length - 1, 1 do
            for y = y_pos, y_pos + y_length - 1, 1 do
                for z = z_pos, z_pos + z_length - 1, 1 do
                    if x == x_pos or x == x_pos + x_length - 1 or
                        y == y_pos or y == y_pos + y_length - 1 or
                        z == z_pos or z == z_pos + z_length - 1 then
                       
                        if math.random(0, 8) < 6 then
                            color = "endermangray"
                        else
                            color = "endermanblack"
                        end
                        tikzcube(x, y, z, color)
                    end
                end
            end
        end
    end
   
    function draw_particles(x_min, x_max, y_min, y_max, z_min, z_max)
        local x
        local y
        local z
        local black_amount
        local particle_size
        local particle_scale
        local particle_count = math.random(30, 40)
        local particle
       
        for i = 1, particle_count, 1 do
           
            x = math.random(x_min, x_max)
            y = math.random(y_min, y_max)
            z = math.random(z_min, z_max)
           
            particle_size = math.random(1, 8)
            particle_scale = math.random(20, 100) / 100
            black_amount = math.random(0, 25)
           
            tex.sprint("\\tdplottransformmainscreen{" .. x .. "}{" .. y .. "}{" .. z .. "}")
            for i = 0, particle_size - 1, 1 do
                for j = 0, particle_size - 1, 1 do
                    if math.random(0, 1) == 0 and
                        ((i ~= 0 and j ~= 0) and
                        (i ~= particle_size - 1 and j ~= 0) and
                        (j ~= particle_size - 1 and i ~= 0) and
                        (i ~= particle_size - 1 and j ~= particle_size - 1)) then
                       
                        particle = "\\filldraw[black!" .. black_amount ..
                            "!particlecolor, tdplot_screen_coords] (" ..
                            i * particle_scale * 0.25 .. "+\\tdplotresx, " ..
                            j * particle_scale * 0.25 .. "+\\tdplotresy) " ..
                            "rectangle +(" .. particle_scale .. "*0.25, " ..
                            particle_scale .. "*0.25);"
                       
                        tex.sprint(particle)
                    end
                end
            end
        end
    end
   
    function draw_enderman(x_rotation, z_rotation)
        tex.sprint("\\tdplotsetmaincoords{" .. x_rotation .. "}{" .. z_rotation .. "}")
        tex.sprint("\\begin{tikzpicture}[tdplot_main_coords]")
       
        math.randomseed(os.time())
       
        draw_bodypart(3, -2, -30, 2, 2, 30) -- right arm
        draw_bodypart(3, 1, -42, 2, 2, 30) -- right leg
        draw_bodypart(3, 5, -42, 2, 2, 30) -- left leg
        draw_bodypart(2, 0, -12, 4, 8, 12) -- body
        draw_bodypart(3, 8, -30, 2, 2, 30)-- left arm
        draw_head(0, 0, 0) -- head
       
        draw_particles(-10, 10, -10, 10, -44, 10)
        -- draw_coordinate_system()
       
        tex.sprint("\\end{tikzpicture}")
    end
\end{luacode*}

\begin{document}
\luadirect{draw_enderman(70, 130)}
\end{document}

I have added two new functions (matrix_scalar_multiplication, shift_coordinates), and rewrote the tikzcube function, so now the LuaTeX engine makes all necessary calculations instead of the TeX (TikZ?) engine which is considerably slower. Let’s see the numbers.

I’ve measured the compilation time of the original and the improved code. I made 20–20 “dry” compilations (no previous aux, log or pdf files). The average compilation time of the original code was 25.35 seconds while the improved code’s average was only 18.30 seconds. This means that the improved version is 25% faster than the previous one. Stefan, thanks for your suggestion!

0 komment
Megosztás, like stb.

MISS ME?

0 komment
Megosztás, like stb.

The other day I thought about that it would be nice to program Conway’s Game of Life in LaTeX and create an animated PDF output. You could say that implementing the Game of Life is pretty easy. It is. But not in LaTeX, at least not for me. After using LaTeX for years I still find it hard to understand some codes and especially writing programs in “pure LaTeX”.

Since I use PGF/TikZ fairly often I decided to use pgfmath for the implementation. Pretty soon I got stuck, I needed to assign values to array elements, and I didn’t know how to overcome this problem in LaTeX. So I decided to ask a question on TeX.SX. This is the point that things get interesting.

I asked a question with the title “Assign value to array element (PGF/TikZ)”, I also wrote that my aim was to program Conway’s Game of Life in LaTeX, and posted my initial code. Before long two answers came, and the question was renamed to “Programming Conway’s Game of Life in LaTeX”. The first answer implemented the Game of Life in LaTeX, the second implemented it in LuaTeX. I was stunned. Especially from the LaTeX implementation. I didn’t – and still don’t – understand the code. :) At the end I decided I should get acquainted with LuaTeX, and write my own solution in it:

Glider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
\documentclass{article}
\usepackage[a0paper]{geometry}

\usepackage{luacode}
\usepackage{animate}
\usepackage{tikz}
\usepackage{xcolor}
\usepackage[active, tightpage]{preview}
\PreviewEnvironment{animateinline
}
%\PreviewEnvironment{tikzpicture}

\tikzset{%
    cellframe/.style={%
        minimum size=5mm,%
        draw,%
        fill=white,%
        fill opacity=0%
    }%
}

\tikzset{%
    alivecell/.style={%
        circle,%
        inner sep=0pt,%
        minimum size=4mm,%
        fill=black%
    }%
}

\setlength{\PreviewBorder}{5mm}

\begin{document}

\begin{luacode*}
    iterations = 36
   
    grid = {{0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 1, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 1, 0, 0, 0},
        {0, 0, 0, 1, 1, 1, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0}}
\end{luacode*}

\begin{luacode*}
    function evolve(grid)
        local temp = {}
        local gridsize = #grid
       
        for i = 1, gridsize do
            temp[i] = {}
            for j = 1, gridsize do
                temp[i][j] = 0
            end
        end
       
        for i = 1, gridsize do
            for j = 1, gridsize do
               
                iminus = i - 1
                iplus = i + 1
                jminus = j - 1
                jplus = j + 1
               
                if iminus == 0 then
                    iminus = gridsize
                end
               
                if iplus == gridsize + 1 then
                    iplus = 1
                end
               
                if jminus == 0 then
                    jminus = gridsize
                end
               
                if jplus == gridsize + 1 then
                    jplus = 1
                end
               
                neighbourcount = grid[iminus][jminus] +
                    grid[iminus][j] +
                    grid[iminus][jplus] +
                    grid[i][jminus] +
                    grid[i][jplus] +
                    grid[iplus][jminus] +
                    grid[iplus][j] +
                    grid[iplus][jplus]
               
                if (grid[i][j] == 1 and (neighbourcount == 2 or neighbourcount == 3)) or (grid[i][j] == 0 and neighbourcount == 3) then
                    temp[i][j] = 1
                else
                    temp[i][j] = 0
                end
            end
        end
       
        return temp
    end
   
    function display(grid)
        local gridsize = #grid
       
       
        for i = 1, gridsize do
            for j = 1, gridsize do
                tex.sprint([[\node[cellframe] at (]])
                tex.sprint((i - 1) * 5)
                tex.sprint([[mm,]])
                tex.sprint(-((j - 1) * 5))
                tex.sprint([[mm){0};]])
               
                if grid[j][i] == 1 then
                    tex.sprint([[\node[alivecell] at (]])
                    tex.sprint((i - 1) * 5)
                    tex.sprint([[mm,]])
                    tex.sprint(-((j - 1) * 5))
                    tex.sprint([[mm){1};]])
                end
            end
        end
    end
   
    function animate(grid, iterations)
        for i = 1, iterations - 1 do
            display(grid)
            tex.sprint([[\newframe]])
            grid = evolve(grid)
        end
        display(grid)
    end
   
    function frames(grid, iterations)
        for i = 1, iterations - 1 do
            tex.sprint([[\begin{tikzpicture}]])
           
            display(grid)
            grid = evolve(grid)
           
            tex.sprint([[\end{tikzpicture}]])
            tex.sprint([[\clearpage]])
        end
       
        tex.sprint([[\begin{tikzpicture}]])
        display(grid)
        tex.sprint([[\end{tikzpicture}]])
    end
\end{luacode*}

\noindent\begin{animateinline}[autoplay,loop,
begin={\begin{tikzpicture}[scale=1
]},
end={\end{tikzpicture}}]{5}
    \luadirect{animate(grid, iterations)}
\end{animateinline
}
%\noindent\luadirect{frames(grid, iterations)}

\end{document}

Note: More information about my implementation is available in my answer on TeX.SX.

It turned out to be pretty easy to do this in LuaTeX, however I struggled with the modulus operator (%) and printing from Lua to TeX. Still it became a pretty nice solution, I think.

Last but not least I’m very grateful for the answers on my question. Here are some nice outputs of jfbu’s and JLDiaz’s answers (if you have a TeX.SX registration then please post an upvote on them because they are really great):

Gosper glider gun
Glider Glider
Gosper glider gun

0 komment
Megosztás, like stb.

Múlt héten jött a hír, hogy rendelhető a Raspberry Pi-hez készített infravörös filter nélküli kamera a Pi NoIR.

Pi NoIR

Kép: adafruit industries blog

Azonnal rendeltem is egyet, majd írtam egy kommentet a HUP-ra, hogy én mire használom a kamera modult. trey-nek megtetszett, amit írtam, és írt is egy bejegyzést az ételdobozból és Raspberry Pi-ból készített time-lapse kamerámról.

Ennek eredményeképpen több megkeresést kaptam, hogy mennyibe kerül egy ilyen hordozható time-lapse kamerát összeállítani, illetve milyen szoftvereket használok, ezt próbálom most összefoglalni.

Szeretném megjegyezni, hogy az eddig elkészült felvételek még nincsenek kész, nincsenek megvágva, nincs rajtuk javítás stb. A cél egy hosszabb videó, vagy klip vagy ilyesmi készítése, és még nagyon az elején járok a dolognak.

Miből áll és mennyibe kerül?

A legegyszerűbb, ha felsorolom az alkatrészeket. (Az árak mellett feltüntettem, hogy milyen pénznemben értendők, mert pár dolgot külföldről rendeltem.)

  • Raspberry Pi Model B (rev2): 35 USD – Napi árfolyam kérdése, hogy Forintban mennyi.
  • Raspberry Pi Camera Board: 16.56 GBP – Napi árfolyam kérdése, hogy Forintban mennyi.
  • Wi-Fi USB adapter: ~2000–4000 HUF – Nagyon fontos, hogy elég legyen neki az az áram, amit az RPi közvetlenül le tud adni, különben powered hubot kellene használni.
  • SD kártya: ~5000+ HUF – Legalább 16 GB-os, class 10-es SD kártyát ajánlok, mert egy-egy fotózás alkalmával simán összejön 4–5 GB fotó.
  • 2 db 4-es AA elemtartó: ~150 HUF – Bármelyik elektronikai üzletben kapható.
  • kapcsoló: ~25 HUF – Nem emlékszem, hogy mennyibe került, 100 HUF alatt van az biztos.
  • 8 db 2500 mAh-ás 1,2 V-os NiMH akkumulátor: ~4000–8000 HUF – Az ár a márkától függ, nekem eléggé noname van. :)
  • ételdoboz: ~1000 HUF – Fontos, hogy légmentesen zárható legyen, így eső esetén nem folyik be a víz, a köd nem okoz gondot stb.
  • matt akrilfesték spray (fekete és fehér): ~2000 HUF – Az ételdoboz belülről feketére, kívülről fehérre van fújva. Belül fekete, hogy a különböző ledek fényét, villogását elnyelje nehogy becsillanjon a kamerába. Kívülről azért fehér, hogy visszaverje a fényt a melegedést elkerülendő.
  • UV szűrő: használtan ~500–1200 HUF
  • csavarok, alátétek: ~100 HUF
  • fényképezőgép állvány: Ehhez inkább nem írok árat (nekem korábbról volt már egy). Viszont nagyon fontos, hogy ha állvánnyal dolgozom, akkor legyen az állványon alul valami kampó, vagy más, hogy súly lehessen akasztani az aljára. Ugyanis az ételdoboz nagyobb, mint egy átlagos fényképezőgép, és a súlya még elemmel együtt is kicsi, ezért könnyen belekap a szél.
  • egyebek: maszkolószalag, szigetelőszalag, drót, forrasztópáka, forrasztóón, fenyőléc, laminált padlódarab. Utóbbi rögzíti a dobozt az állványra; úgy „faragtam”, hogy az állvány fejébe illeszkedjen. Ezek mind voltak itthon, árat nem tudok mondani.

Végösszeg: 28284 HUF (mindenből a legolcsóbbal és a mai USD és GBP árfolyammal számoltam).

Ebben nincs benne a fényképezőgép állvány, az itthon talált felhasznált anyagok, alkatrészek, sem az a készíttetett gyűrű, amelybe az UV szűrő illeszkedik. Nem gondoltam volna, hogy ennyit költöttem rá. Sőt, nekem 32 GB-os SD kártyám van, és nem is mindenből a legolcsóbb cucc, ráadásul több kamera modulom is van (NOiR is)… Szóval én valahol 50 ezer felett járok.

Szoftver

A legutóbb ennyit írtam a szoftverről:

A fényképezés mobiltelefonról vezérelhető:

  • A Pi egy USB-s WiFi modul hostapd és isc-dhcp-server segítségével szolgáltat hálózatot.
  • Egy SSH klienssel be lehet jelentkezni a Pi-re.
  • Egy egyszerű shell script a raspistill alkalmazással preview képet készít, amit netcat-tal browserbe továbbítva lehet megtekinteni.
  • Szintén egy shell scripttel lehet elindítani a fényképezést (5000 ms timeout).

Most ezt megpróbálom kicsit jobban kifejteni.

Az RPi-n „gyári” Raspbian fut, a fényképezéshez pedig a raspistill programot használom. Az SSH szerver szintén a „gyári”, a hostapd és az isc-dhcp-server konfigurációját pedig egyszerűen innen puskáztam:

A használt scriptek pedig letölthetők:

Nincs bennük semmi magic. A fényképezésre külön scriptek vannak (capture-*.sh), amelyekben csak a két kép közötti timeout az eltérő. Preview-t pedig a preview.sh scripttel lehet készíteni. Ez utóbbi ahhoz kell, hogy a fényképezés elindítása előtt rendesen be lehessen állítani az állványt és a kamerát.

Képek feldolgozása

A képek bizgetéséhez ImageMagick-et használok. Itt egy szokásos, csak croppoláshoz és átméretezéshez használt parancs, ami végigmegy a megadott könyvtárban található összes jpg fájlon:

mogrify -gravity northwest -crop 2592x1458+0+308 +repage -resize 1920x -format png \
-path /media/szantaii/Tibor/capture/cap1_processed/ /media/szantaii/Tibor/capture/cap1/*.jpg

Egy másik paraméterezéssel már a színeket is állítgattam (ennek az eredménye látható a poszt végén):

mogrify -gravity northwest -contrast -contrast -color-matrix "1.2 -0.1 -0.1 -0.1 1.2 -0.1 -0.1 -0.1 1.2" \
-crop 2592x1458+0+243 +repage -resize 1920x -format png \
-path /media/szantaii/Tibor/capture/cap28_processed_enhanced/ /media/szantaii/Tibor/cap28/*.jpg

Az utolsó példában pedig egy 1,6 fokos forgatás, vágás és átméretezés van:

mogrity -gravity center -background "rgb(0,255,0)" -rotate "1.60" +repage -gravity northwest \
-crop 2446x1376+54+176 +repage -resize 1920x -format png \
-path /media/szantaii/Tibor/capture/cap30_processed/ /media/szantaii/Tibor/capture/cap30/*.jpg

A felsoroltakon kívül még az ImageMagick -sigmoidal-contrast operátorával „játszottam”.

Az eddigi parancsokkal csak a képeket dolgoztam fel. A képekből a videót pedig FFmpeg-gel fűzöm össze. A végeredményt 30 és 60 fps-be is ki szoktam renderelni, hogy lássam melyik néz ki jobban. A 30 és a 60 fps generálása között csak annyi a különbség, hogy az FFmpeg-nek meg kell adni, hogyan értelmezze a bemenetként szolgáló képeket, vagyis az -r kapcsoló után 30-at vagy 60-at kell írni.

ffmpeg -r 30 -i %06d.png -vcodec libx264 -vprofile high444 -vlevel 4.2 -pix_fmt yuv420p ~/Desktop/test_30fps.mp4
ffmpeg -r 60 -i %06d.png -vcodec libx264 -vprofile high444 -vlevel 4.2 -pix_fmt yuv420p ~/Desktop/test_60fps.mp4

Hirtelen (az elmúlt másfél órában) ennyi jutott eszembe.

0 komment
Megosztás, like stb.

A kedvenc Tumblr-em (tumblr.-em?, tumblr-em?, tumblim?) a text-mode:

A collection of text graphics and related works, stretching back thousands of years. Textiles, BBS-graphics, poetry, mosaic, typography, and much more. Collected by Raquel Meyers and Goto80.

Includes formats such as shift-JIS, PETSCII, ASCII, ANSI, RTTY, ATASCII, unicode, braille, xbin

Made for media like videotex, teletext, BBS, buildings, typewriters, clothes, textile, letterpress, toys, telidon, antiope, print, minitel

With styles such as animation, typography, mosaic, poetry, text art, χχχ, text mode, advertising, elite, kufic, sloyd

Putting the emphasis on grids, patterns, emoticons, tiles, tessellations

From ancient times and the 1700s, 1800s, 1900s, 1910s, 1920s, 1930s, 1940s, 1950s, 1960s, 1970s, 1980s, 1990s , 2000s, 2010s

Ezen a héten pedig virus week lesz náluk, ami nem tudom, mi, de val’szeg most jönnek majd a legjobb grafikák ever.

ʌ i ᴚ ∩ ʃ w ǝ Ǝ K ! ! ! ! ! @ text-mode

Forrás: text-mode

0 komment
Megosztás, like stb.

Raspberry Pi stencil

Nem a stencilen. Azon Király András dolgozott: aprólékos munkával kivágta az 5 cm magas mintát, és ráfújta arra a dologra, amin „dolgozom”.

A legutóbbi befuccsolt projektem után, arra gondoltam, hogy valami egyszerűbbel próbálkozom: építek egy hordozható kamerát. Mindehhez egy Raspberry Pi-t, a hozzá tartozó kamera modult, néhány újratölthető elemet és egy hermetikusan zárható ételdobozt használtam fel.

Raspberry Pi camera Raspberry Pi camera

A célom, hogy kihozzam a maximumot a Raspberry Pi kamera moduljából, hogy megmutassam… Valamit, amit még meg kell, hogy fogalmazzak. Igazából ez nekem hobbi és játék. Szeretnék egy érdekes, Budapestet bemutató timelapse videót összerakni.

A működéséről röviden, tömören – és valószínűleg a többség számára érthetetlenül – a képek után.

Raspberry Pi camera Raspberry Pi camera
Raspberry Pi camera Raspberry Pi camera
Feeding Raspberry Pi through GPIO

A táp 2 × 4 db sorban kötött 1.2 V-os 2500 mAh-ás akkumulátor párhuzamosan kötve. Így jön össze a 4.8 V és az 5000 mAh, amivel kb. 4,5–5 órát tényleg ki is bír a Pi.

A fényképezés mobiltelefonról vezérelhető:

  • A Pi egy USB-s WiFi modul hostapd és isc-dhcp-server segítségével szolgáltat hálózatot.
  • Egy SSH klienssel be lehet jelentkezni a Pi-re.
  • Egy egyszerű shell script a raspistill alkalmazással preview képet készít, amit netcat-tal browserbe továbbítva lehet megtekinteni.
  • Szintén egy shell scripttel lehet elindítani a fényképezést (5000 ms timeout).

A következő playlistben láthatók 30 és 60 fps-sel lejátszva az eddig készített felvételek. Tudom, hogy nem jó az optika – hisz ez csak egy kis fix fókuszos „vacak” –, amatőr stb., de mint mondtam, ez nekem hobbi és játék. Remélem, azért másnak is tetszik majd az eredmény.

Végül álljon itt a Raspberry Pi Foundation-t vezető Eben Upton előadása a napokban tartott LinuxCon North America 2013 konferenciáról. (Nagyon jó.)

7 komment
Megosztás, like stb.

A több éves hagyománynak megfelelően minden évben egy konferenciával tervezzük ünnepelni a szabad szoftverek világnapját. Célunk, hogy egyre többen lássák meg a szabad szoftverekben rejlő lehetőségeket új ismereteket és emberi kapcsolatokat szerezve, legyen az akár kezdő otthoni felhasználó, rendszergazda, vállalkozó, kormányzati vagy oktatási felhasználó.

Idén, 2013-ban terveink szerint november 29-én, pénteken tervezzük a konferencia megrendezését.

Tervezett szekcióink:

  • Mobil szekció: szabad szoftveres mobil platformok, pl. Android, Firefox OS, Ubuntu Phone
  • Hardverközeli szekció: szabad szoftverrel programozható, konfigurálható „kütyük”, alap hardveres kapcsolásokkal, pl. Raspberry Pi, Arduino, Udoo, OpenWrt routerken
  • Felhasználói szekció: népszerű és még kevésbé ismert felhasználói programok, pl. LibreOffice, Mozilla, Gimp, …
  • Rendszergazdai szekció: asterisk, biztonsági kérdések
  • Geoinformatikai szekció: a GIS-ben egyre jobban teret nyerő desktop (pl. QGIS, GRASS) és szerver (pl. GeoServer, OpenLayers) megoldások
  • Multimédia, jog: nyílt formátumok, szabad licencek, jogi kérdések

A belépés ingyenes lesz. A megrendezéshez a korábbi évektől eltérően sajnos nem áll rendelkezésünkre pályázati forrás, így azt teljes egészében szponzori felajánlásokból kell összegyűjtenünk. Számításaink szerint 1,4 millió forintra lenne szükségünk, hogy igényesen meg tudjuk rendezni (de minimálisan 1,1 millióra), amelyet – a helyszínnel való megállapodásunk szerint – 2013. szeptember végéig kell összegyűjtenünk. Ha van lehetőséged (neked, cégednek, ismerősődnek), akkor támogasd, hogy megvalósulhasson a konferencia.

Szabad Szoftver Konferencia Szeged 2013

Mindenféle (média)megjelenés, és akármilyen kicsi adomány is sokat számít. Tessék reblogolni, támogatni, mert jó ügyre megy!

0 komment
Megosztás, like stb.

Butterick’s Practical Typography

This book is partly an experiment in taking the web se­ri­ous­ly as a book-pub­lish­ing medi­um. I have a role to play in mak­ing the ex­per­i­ment work. And so do you.

For my part, I want­ed to de­liv­er a lev­el of writ­ing and de­sign qual­i­ty that you’d find in a print­ed book. Not that print will al­ways be the gold stan­dard. But to­day it is. Because so far, web-based books—nice ones, any­how—have been slow to emerge.

[…]

Matthew Butterick

Most kezdtem csak olvasni, de a Practical Typography egy nagyon jó, hiánypótló könyv. Egyszerűen, érthetően, példákkal alátámasztva mesél a tipográfiáról. (Kötelező.)

Ingyenesen elérhető, Butterick csak annyit kér, hogy vegyék meg a fontjait, vagy korábbi könyvét, és akkor így is marad. Szerintem méltányos, de én azt hiszem maradok az 5–10 dolláros PayPal adománynál.

Butterick’s Practical Typography

0 komment
Megosztás, like stb.

About a month and a half ago I planned to build a Raspberry Pi controlled car. The idea was to control it through WiFi with live video feedback. Unfortunately the car won’t be built due to lack of money and time, etc.

However I gathered a little experience during this “project”, so I’m sharing it through a guide on how to put together all the necessary hardware and software parts together. First, though, here are the main weaknesses of my setup:

  • The live video feed takes about 30–50% the Pi’s CPU, depens on motion.
  • High lag (up to 2 seconds).
  • Jittery software PWM.

In sum, this guide shows how to control a DC and a servo motor with live video feedback, but the end result will be slow and imprecise.

Hardware

I planned to use two motors for the car, a DC motor for driving and a servo motor for steering. I followed The Adafruit Learning System’s guides for controlling a DC and a servo motors using the Raspberry Pi, so I won’t go into the details of the hardware setup and PWM signaling.

I combined the circuit diagrams of the two guides into one using Fritzing. You can download the Fritzing format of the diagram, it’s included in the downloadable tarball (doc directory) towards the end of the post.

Circuit diagram

Software

The idea was to control the car through a simple webpage with embedded live video. This had set the course which led me to use the following software setup.

Architectural diagram

Note: As for the OS I stayed with Raspbian, and since I didn’t have a camera board I used a webcam.

Necessary packages

First let’s install the necessary packages.

$ sudo apt-get install subversion libjpeg8-dev imagemagick python-rpi.gpio lighttpd

Subversion will be needed to checkout MJPG-Streamer’s source from SourceForge, while ImageMagick and libjpeg8-dev are needed for building MJPG-Streamer. RPi.GPIO is a Python package which we’ll use to control the GPIO pins, and lighttpd will serve the web page to the client.

MJPG-Streamer

Now we’ll checkout MJPG-Streamer’s source from SourceForge, enter the proper directory, modify the Makefile, and build MJPG-Streamer.

$ svn co https://mjpg-streamer.svn.sourceforge.net/svnroot/mjpg-streamer mjpg-streamer
$ cd mjpg-streamer/mjpg-streamer
$ sed -i 's/$(CC) $(CFLAGS) $(LFLAGS) $(OBJECTS) -o $(APP_BINARY)/$(CC) $(CFLAGS) $(OBJECTS) $(LFLAGS) -o $(APP_BINARY)/g' Makefile
$ make

If MJPG-Streamer was built successfully, edit your /etc/rc.local file.

$ sudo nano /etc/rc.local

Add the following lines right before the exit 0 statement. This command will launch MJPG-Streamer at system startup.

1
2
3
4
5
6
# Start MJPG-Streamer on multiuser runlevels
/home/pi/mjpg-streamer/mjpg-streamer/mjpg_streamer \
-i "/home/pi/mjpg-streamer/mjpg-streamer/input_uvc.so \
-f 30 \
-r 640x480"
\
-o "/home/pi/mjpg-streamer/mjpg-streamer/output_http.so"

Lighttpd

Edit lighttpd’s config file.

$ sudo nano /etc/lighttpd/lighttpd.conf

Make sure that lighttpd’s document root is the /var/www directory.

server.document-root        = "/var/www"

Add the following lines to the end of the file. These will load the CGI module, create an error log at /var/log/lighttpd/breakage.log, run Python scripts with a “custom” binary, and map the /usr/lib/cgi-bin/ directory to the document root as cgi-bin/.

server.modules             += ( "mod_cgi" )
server.breakagelog          = "/var/log/lighttpd/breakage.log"
cgi.assign                  = ( ".py" => "/usr/bin/python-root" )
alias.url                   = ( "/cgi-bin/" => "/usr/lib/cgi-bin/" )

Make lighttpd start on system startup, and restart lighttpd to use the updated configuration.

$ sudo update-rc.d lighttpd defaults
$ sudo service lighttpd restart

Nasty Python hack

I was using RPi.GPIO version 0.5.3a for controlling the GPIO pins and at the moment RPi.GPIO needs root permission to do this. So I needed to run the Python scripts as root, but since lighttpd can not be run as root – which would be a sechole – I needed to hack a little bit.

I found the solution in Dav’s Raspberry Pi – Controlling GPIO from the Web post: I made a copy of Python’s executable and gave it the setuid attribute.

$ sudo cp /usr/bin/python2.7 /usr/bin/python-root
$ sudo chmod u+s /usr/bin/python-root

Note: All Python scripts executed by lighttpd will now run as root. DO NOT USE THIS ON ANY PUBLIC SERVER AS THIS IS “A POTENTIALLY DANGEROUS TECHNIQUE”.

Control scripts

You can download the GPIO control scripts and the client web site by either clicking the link below or typing the following command.

$ wget http://sidenote.hu/wp-content/uploads/2013/07/motorcontrol.tar.gz

Now all we have to do is to extract the archive, copy all the files to the proper places, and add some permissions.

$ tar -xvzf motorcontrol.tar.gz
$ cd motorcontrol
$ sudo cp cgi-bin/* /usr/lib/cgi-bin/
$ sudo chmod +x /usr/lib/cgi-bin/*.py
$ sudo cp -R www/* /var/www/

The code in the archive is mostly uncommented especially the JavaScript part, but it’s not complicated.

The client side uses jQuery, so you’ll have to download jQuery 2.0.2 minified and copy it to /var/www/js/, or use the following command.

$ sudo wget http://code.jquery.com/jquery-2.0.2.min.js -O /var/www/js/jquery-2.0.2.min.js

The final step is to download a HTML5 CSS reset and place it in /var/www with the name reset.css.

$ sudo wget http://reset5.googlecode.com/hg/reset.css -O /var/www/reset.css

Using the software

If everything was setup correctly then all you have to do is to enter your Pi’s IP in your browser’s address bar, and you should see the webpage with the controls and the live video.

Controlling motors with mouse Controlling motors with keyboard

You can either use the mouse or the keyboard to control the motors. If you use the mouse, then you can turn the servo by clicking on the left or right arrows (this will toggle the direction), and drive motor by holding down left click on the forwards or backwards arrow. When you use the keyboard you have to keep the arrow keys pressed to turn or drive the motors.

0 komment
Megosztás, like stb.

Eric S. Raymond: A katedrális és a bazár

Tíz éve el kellett volna olvasnom már ezt a könyvet.

0 komment
Megosztás, like stb.