Skip to content

Instantly share code, notes, and snippets.

@Elv13
Created December 27, 2022 09:23
Show Gist options
  • Save Elv13/78cab8152029f8910c7ff2541805fbb0 to your computer and use it in GitHub Desktop.
Save Elv13/78cab8152029f8910c7ff2541805fbb0 to your computer and use it in GitHub Desktop.
-- Import the necessary modules
local lgi = require 'lgi'
local cairo = lgi.cairo
local Gdk = lgi.Gdk
local Gtk = lgi.Gtk
-- Define the window size
local window_width = 400
local window_height = 300
-- Define the 3D rotation angles and 2D translation
local angle_x = math.rad(45)
local angle_y = math.rad(45)
local angle_z = math.rad(45)
local translation_x = 0
local translation_y = 0
local client = {}
local screen_one = {
geometry = {
x = 0, y = 0, width = 1280, height = 800
}
}
function client.get()
return {
{
geometry = function()
return {x = 100, y = 100, width = 400, height = 200 }
end,
screen = screen_one
},
{
geometry = function()
return {x = 400, y = 400, width = 700, height = 400 }
end,
screen = screen_one
},
}
end
-- Function to multiply two matrices
local function matrix_multiply(a, b)
local c = {}
for i = 1, #a do
c[i] = {}
for j = 1, #b[1] do
c[i][j] = 0
for k = 1, #a[1] do
c[i][j] = c[i][j] + a[i][k] * b[k][j]
end
end
end
return c
end
-- Function to translate a 3D point using rotations around the X, Y, and Z axes and a translation along the X and Y axes
local function translate_3d(point, angle_x, angle_y, angle_z, translation_x, translation_y)
-- Extract the x, y, and z coordinates of the point
local x, y, z = point[1], point[2], point[3]
-- Create the transformation matrices for the rotations and translation
local rot_x = {
{1, 0 , 0 , 0},
{0, math.cos(angle_x), -math.sin(angle_x), 0},
{0, math.sin(angle_x), math.cos(angle_x), 0},
{0, 0 , 0 , 1},
}
local rot_y = {
{math.cos(angle_y) , 0, math.sin(angle_y), 0},
{0 , 1, 0 , 0},
{-math.sin(angle_y), 0, math.cos(angle_y), 0},
{0 , 0, 0 , 1},
}
local rot_z = {
{math.cos(angle_z), -math.sin(angle_z), 0, 0},
{math.sin(angle_z), math.cos(angle_z), 0, 0},
{0 , 0 , 1, 0},
{0 , 0 , 0, 1},
}
local trans = {
{1, 0, 0, translation_x},
{0, 1, 0, translation_y},
{0, 0, 1, 0 },
{0, 0, 0, 1 },
}
-- Multiply the matrices to get the final transformation matrix
local transform = matrix_multiply(rot_x, rot_y)
transform = matrix_multiply(transform, rot_z)
transform = matrix_multiply(transform, trans)
-- Multiply the transformation matrix by the point to get the transformed point
local transformed_point = matrix_multiply({{x, y, z, 1}}, transform)[1]
-- Return the transformed point
return transformed_point
end
-- Function to draw a horizontal line
local function draw_horizontal_line(cr, x1, x2, y, angle_x, translate_x, translate_y)
-- Translate the starting and ending points of the line
local start_point = translate_3d({x1, y, 0}, angle_x, angle_x, angle_x, translate_x, translate_y)
local end_point = translate_3d({x2, y, 0}, angle_x, angle_x, angle_x, translate_x, translate_y)
-- Draw the line
cr:move_to(start_point[1], start_point[2])
cr:line_to(end_point[1], end_point[2])
cr:stroke()
end
-- Function to draw a vertical line
local function draw_vertical_line(cr, x, y1, y2, angle_x, translate_x, translate_y)
-- Translate the starting and ending points of the line
local start_point = translate_3d({x, y1, 0}, angle_x, angle_x, angle_x, translate_x, translate_y)
local end_point = translate_3d({x, y2, 0}, angle_x, angle_x, angle_x, translate_x, translate_y)
-- Draw the line
cr:move_to(start_point[1], start_point[2])
cr:line_to(end_point[1], end_point[2])
cr:stroke()
end
local function draw_rectangle(cr, points, angle_x, angle_y, angle_z, translate_x, translate_y)
for idx, p in ipairs(points) do
local pt = translate_3d(p, angle_x, angle_y, angle_z, translate_x, translate_y)
cr[idx == 1 and "move_to" or "line_to"](cr, pt[1], pt[2])
end
end
-- Function to draw the grid
local function draw_grid(cr, window_width, window_height, angle_x, translate_x, translate_y)
local step_size, step_count = 30, 10
local area = step_size * step_count
-- Draw the solid (ZY) back.
local points = {
{150, -(area/2), -150},
{150, (area/2), -150},
{150, (area/2), 0 },
{150, -(area/2), 0 },
}
draw_rectangle(cr, points, angle_x, angle_x, angle_x, 0, 0)
cr:close_path()
cr:set_source_rgba(0.75, 0.75, 0.75, 0.25)
cr:fill()
-- Draw the solid (XZ) bottom.
points = {
{ 150, (area/2), 0},
{ 150, -(area/2), 0},
{-150, -(area/2), 0},
{-150, (area/2), 0},
}
draw_rectangle(cr, points, angle_x, angle_x, angle_x, 0, 0)
cr:close_path()
--cr:set_source_rgba(0.75, 0.75, 1, 1)
cr:set_source_rgba(0.75, 0.75, 0.75, 0.05)
cr:fill()
-- wallpaper
-- points = {
-- {-(area/2),-150, -150},
-- { (area/2),-150, -150},
-- { (area/2),-150, 0 },
-- {-(area/2),-150, 0 },
-- }
--
-- draw_rectangle(cr, points, angle_x, angle_x, angle_x, 0, 0)
--
-- cr:close_path()
-- cr:set_source_rgba(0.75, 0.75, 1, 0.55)
-- cr:fill()
-- Set the color for the grid lines to blue
cr:set_source_rgb(0.75, 0.75, 0.75)
-- Set the line width for the grid lines
cr:set_line_width(1)
-- Draw the (XZ) horizontal lines
for y = -(area/2), (area/2), step_size do
draw_horizontal_line(cr, -(area/2), (area/2), y, angle_x, translate_x, translate_y)
end
-- Draw the (XZ) vertical lines
for x = -(area/2), (area/2), step_size do
draw_vertical_line(cr, x, -(area/2), (area/2), angle_x, translate_x, translate_y)
end
-- Draw the (ZY) vertical lines
for x = -(area/2), (area/2), step_size do
local start_point = translate_3d({150, x, 0 }, angle_x, angle_x, angle_x, 0, 0)
local end_point = translate_3d({150, x, -150}, angle_x, angle_x, angle_x, 0, 0)
cr:move_to(start_point[1], start_point[2])
cr:line_to(end_point[1], end_point[2])
cr:stroke()
end
-- Close off the (ZY) grid
local start_point = translate_3d({150, -(area/2), -150}, angle_x, angle_x, angle_x, 0, 0)
local end_point = translate_3d({150, (area/2), -150}, angle_x, angle_x, angle_x, 0, 0)
cr:move_to(start_point[1], start_point[2])
cr:line_to(end_point[1], end_point[2])
cr:stroke()
end
-- Function to draw the X axis
local function draw_axis(cr, window_width, angle_x)
-- Set the line width and color
cr:set_line_width(2)
cr:set_font_size(12)
local labels = { "X", "Z", "Y" }
for label_idx, axis in ipairs { {1, 0, 0}, {0, -1, 0}, {0, 0, 1}} do
local p1 = {150 + -300 * axis[1], -150 -300 * axis[2], -150 * axis[3]}
local p2 = {150, -150, 0}
local start_point = translate_3d(p1, angle_x, angle_x, angle_x, 0, 0)
local end_point = translate_3d(p2, angle_x, angle_x, angle_x, 0, 0)
cr:move_to(start_point[1], start_point[2])
cr:line_to(end_point[1], end_point[2])
cr:set_source_rgb(1, 0, 0)
cr:stroke()
cr:move_to(start_point[1], start_point[2])
cr:set_source_rgb(0, 0, 0)
cr:show_text(labels[label_idx])
end
end
local function draw_wallpaper(cr)
local img = cairo.ImageSurface.create_from_png("/home/lepagee/dev/awesome/themes/default/background.png")
local s_geo = client.get()[1].screen.geometry
local projection_width = 300
local projection_height = (s_geo.height * projection_width) / s_geo.width
-- First, create a smaller version with the screen aspect ratio. This could
-- have been done with more matrix multiplications below, but the math is
-- is already heavy enough, so better sacrifice some scaling quality for
-- more readable math.
local target = img:create_similar(cairo.Content.COLOR, projection_width, projection_height)
local cr2 = cairo.Context(target)
cr2:scale(projection_width / img:get_width(), projection_height / img:get_height())
cr2:set_source_surface(img)
cr2:paint()
local wall_pts = {
{-150, -150, -150},
{ 150, -150, -150},
{ 150, -150, 0 },
{-150, -150, 0 },
}
draw_rectangle(cr, wall_pts, angle_x, angle_y, angle_x, 0, 0)
cr:close_path()
cr:set_source_rgba(0.75, 0.75, 1, 0.55)
cr:fill()
-- Create a parallelogram from "wallpaper" area of the box.
local pt = {
translate_3d(wall_pts[1], angle_x, angle_y, angle_z, 0, 0),
translate_3d(wall_pts[2], angle_x, angle_y, angle_z, 0, 0),
translate_3d(wall_pts[3], angle_x, angle_y, angle_z, 0, 0),
translate_3d(wall_pts[4], angle_x, angle_y, angle_z, 0, 0),
}
for _, p in ipairs(pt) do
print("\nPR", p[1], p[2], p[3])
end
local dim = {x =1, y=2}
-- Calculate the center point of the parallelogram
local center_x = (pt[1][dim.x] + pt[2][dim.x] + pt[3][dim.x] + pt[4][dim.x]) / 4
local center_y = (pt[1][dim.y] + pt[2][dim.y] + pt[3][dim.y] + pt[4][dim.y]) / 4
-- Sort the points by their angle relative to the center point
local points = {{p = pt[1], angle = math.atan2(pt[1][dim.y] - center_y, pt[1][dim.x] - center_x)},
{p = pt[2], angle = math.atan2(pt[2][dim.y] - center_y, pt[2][dim.x] - center_x)},
{p = pt[3], angle = math.atan2(pt[3][dim.y] - center_y, pt[3][dim.x] - center_x)},
{p = pt[4], angle = math.atan2(pt[4][dim.y] - center_y, pt[4][dim.x] - center_x)}}
table.sort(points, function(a, b) return a.angle < b.angle end)
-- Unpack the sorted points
local p1x, p1y = points[1].p[1], points[1].p[2]
local p2x, p2y = points[2].p[1], points[2].p[2]
local p3x, p3y = points[3].p[1], points[3].p[2]
local p4x, p4y = points[4].p[1], points[4].p[2]
-- Calculate the scale factor for the x and y dimensions
local sx = (p2x - p1x) / projection_width
local sy = (p3y - p1y) / projection_height
-- Calculate the shear factor for the x and y dimensions
local hx = (p2x - p1x) / (p3y - p1y)
local hy = (p3y - p1y) / (p2x - p1x)
local matrix = cairo.Matrix()
print("\nMOO", sx, hx, hy, sy)
matrix:init(sx, hx, hy, sy, pt[1][dim.x], pt[1][dim.y])
--matrix:init_identity()
-- Use a shear, rotate and scale matrices to project the 2D image into
-- the 3D plan.
-- local matrix = cr:get_matrix()
-- matrix:rotate(math.rad(-45))
-- matrix:scale(1, math.cos(math.rad(-45)))
-- matrix:rotate(math.rad(45))
-- matrix:scale(math.cos(math.rad(60)), 1) --TODO that is wrong
-- matrix:rotate(math.rad(-30))--TODO that is wrong
cr:save()
cr:set_matrix(matrix)
cr:set_source_surface(target)
cr:paint_with_alpha(0.7)
cr:restore()
end
local function draw_cartesian_plan(cr, window_width, window_height, angle_x)
draw_axis(cr, window_width, angle_x)
draw_wallpaper(cr)
end
-- Draw either a wibox or a client.
local function draw_drawable(cr)
for idx, c in ipairs(client.get()) do
local s_geo = c.screen.geometry --TODO replace by root.size()
local c_geo = c:geometry()
local projection_width = 300
local projection_height = (s_geo.height * projection_width) / s_geo.width
-- This only works for screenstarting at 0x0 for now. This template
-- doesn't support multiple screen (until its needed?)
local proj_geo = {
x1 = (c_geo.x * projection_width) / s_geo.width,
x2 = ((c_geo.x+c_geo.width) * projection_width) / s_geo.width,
y1 = (c_geo.y * projection_height) / s_geo.height,
y2 = ((c_geo.x+c_geo.height) * projection_height) / s_geo.height,
}
print(proj_geo.x1, proj_geo.x2, proj_geo.y1, proj_geo.y2)
-- 0x0 is the "top left" while 0x0x0 in 3D is the center of the plan.
-- This map between them.
local vertices = {
{-(projection_width/2) + proj_geo.x1, 0, -(projection_height/2) + proj_geo.y1},
{-(projection_width/2) + proj_geo.x1, 0, -(projection_height/2) + proj_geo.y2},
{-(projection_width/2) + proj_geo.x2, 0, -(projection_height/2) + proj_geo.y2},
{-(projection_width/2) + proj_geo.x2, 0, -(projection_height/2) + proj_geo.y1}
}
-- local vertices = {
-- {-100, 0, -100},
-- {-100, 0, 100},
-- {100 , 0, 100},
-- {100 , 0, -100}
-- }
-- Project the 3D vertices onto the 2D plane
local points = {}
for i, vertex in ipairs(vertices) do
local point = translate_3d(vertex, angle_x, angle_y, angle_z, translation_x, translation_y)
points[i] = {point[1] / point[4], point[2] / point[4]}
end
-- Draw the rectangle
cr:move_to(points[1][1], points[1][2])
for i = 2, #points do
cr:line_to(points[i][1], points[i][2])
end
cr:close_path()
cr:set_source_rgba(0,1,0,0.5)
cr:stroke_preserve()
cr:set_source_rgba(0,1,0,0.1)
cr:fill()
end
end
-- Function to draw the window
local function on_draw(self, cr)
cr:translate(250, 250)
-- Draw the 3D Cartesian plan
draw_grid(cr, window_width, window_height, angle_x, 100, 100)
draw_cartesian_plan(cr, window_width, window_height, angle_x)
draw_drawable(cr)
end
-- Create the GTK window and drawing area
local window = Gtk.Window {
title = '3D Projection Example',
width_request = window_width,
height_request = window_height,
Gtk.DrawingArea {
id = 'canvas',
on_draw = function(self, cr)
print(xpcall(on_draw, debug.traceback, self, cr))
end
}
}
-- Show the window and run the GTK main loop
window:show_all()
Gtk:main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment