% Enderman
% Author: István Szántai (szantaii)
\documentclass[tikz]{standalone}
\usepackage{luacode}
\usepackage{tikz-3dplot}

\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}