Created
December 27, 2022 09:23
-
-
Save Elv13/78cab8152029f8910c7ff2541805fbb0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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