From cbb43542246360b14f4cffcbeda2ef557eea2706 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Sun, 1 Dec 2024 00:30:59 +0100 Subject: [PATCH 01/20] feat: wip draw quad for smear --- lua/smear_cursor/animation.lua | 123 +++++++++++++++++++++------------ lua/smear_cursor/config.lua | 8 +-- lua/smear_cursor/draw.lua | 8 +++ lua/smear_cursor/events.lua | 2 +- 4 files changed, 93 insertions(+), 48 deletions(-) diff --git a/lua/smear_cursor/animation.lua b/lua/smear_cursor/animation.lua index a5aef30..48ee8f8 100644 --- a/lua/smear_cursor/animation.lua +++ b/lua/smear_cursor/animation.lua @@ -5,58 +5,96 @@ local round = require("smear_cursor.math").round local screen = require("smear_cursor.screen") local M = {} -local target_position = { 0, 0 } -local current_position = { 0, 0 } -local trailing_position = { 0, 0 } local animating = false +local target_position = { 0, 0 } +local current_corners = {} +local target_corners = {} +local stiffnesses = { 0, 0, 0, 0 } + +-- local loop_metatable = { +-- __index = function(table, key) +-- if key == 5 then +-- return table[1] +-- else +-- return nil +-- end +-- end, +-- } + +-- setmetatable(current_corners, loop_metatable) +-- setmetatable(target_corners, loop_metatable) + +local function set_corners(corners, row, col) + corners[1] = { row, col } + corners[2] = { row, col + 1 } + corners[3] = { row + 1, col + 1 } + corners[4] = { row + 1, col } +end vim.defer_fn(function() local cursor_row, cursor_col = screen.get_screen_cursor_position() target_position = { cursor_row, cursor_col } - current_position = { cursor_row, cursor_col } - trailing_position = { cursor_row, cursor_col } + set_corners(current_corners, cursor_row, cursor_col) + set_corners(target_corners, cursor_row, cursor_col) end, 0) local function update() - current_position[1] = current_position[1] + (target_position[1] - current_position[1]) * config.stiffness - current_position[2] = current_position[2] + (target_position[2] - current_position[2]) * config.stiffness - - local trailing_distance_squared = (current_position[1] - trailing_position[1]) ^ 2 - + (current_position[2] - trailing_position[2]) ^ 2 - local trailing_stiffness = - math.min(1, config.trailing_stiffness * trailing_distance_squared ^ config.trailing_exponent) - trailing_position[1] = trailing_position[1] + (current_position[1] - trailing_position[1]) * trailing_stiffness - trailing_position[2] = trailing_position[2] + (current_position[2] - trailing_position[2]) * trailing_stiffness + for i = 1, 4 do + local distance_squared = (current_corners[i][1] - target_corners[i][1]) ^ 2 + + (current_corners[i][2] - target_corners[i][2]) ^ 2 + local stiffness = math.min(1, stiffnesses[i] * distance_squared ^ config.slowdown_exponent) + for j = 1, 2 do + current_corners[i][j] = current_corners[i][j] + (target_corners[i][j] - current_corners[i][j]) * stiffness + end + end end local function animate() - if not animating then - return - end + animating = true update() + draw.clear() - if not config.dont_erase then - draw.clear() - end - local end_reached = round(current_position[1]) == target_position[1] - and round(current_position[2]) == target_position[2] - draw.draw_line(trailing_position[1], trailing_position[2], current_position[1], current_position[2], end_reached) - if not end_reached and config.hide_target_hack then - draw.draw_character(target_position[1], target_position[2], " ", color.get_hl_group({ inverted = true })) + local max_distance = 0 + for i = 1, 4 do + local distance = math.sqrt( + (current_corners[i][1] - target_corners[i][1]) ^ 2 + (current_corners[i][2] - target_corners[i][2]) ^ 2 + ) + max_distance = math.max(max_distance, distance) end - - local trailing_distance = - math.sqrt((target_position[1] - trailing_position[1]) ^ 2 + (target_position[2] - trailing_position[2]) ^ 2) - if trailing_distance > config.distance_stop_animating then - animating = true + if max_distance > config.distance_stop_animating then + draw.draw_quad(current_corners, target_position) + -- TODO: draw character at cursor if hack is enabled + -- local end_reached = ... + -- if not end_reached and config.hide_target_hack then + -- draw.draw_character(target_position[1], target_position[2], " ", color.get_hl_group({ inverted = true })) + -- end vim.defer_fn(animate, config.time_interval) else animating = false - if not config.dont_erase then - draw.clear() - end - current_position = { target_position[1], target_position[2] } - trailing_position = { target_position[1], target_position[2] } + set_corners(current_corners, target_position[1], target_position[2]) + end +end + +local function set_stiffnesses() + local target_center = { target_position[1] + 0.5, target_position[2] + 0.5 } + local distances = {} + local min_distance = math.huge + local max_distance = 0 + + for i = 1, 4 do + local distance = + math.sqrt((current_corners[i][1] - target_center[1]) ^ 2 + (current_corners[i][2] - target_center[2]) ^ 2) + min_distance = math.min(min_distance, distance) + max_distance = math.max(max_distance, distance) + distances[i] = distance + end + + for i = 1, 4 do + local stiffness = config.stiffness + + (config.trailing_stiffness - config.stiffness) + * (distances[i] - min_distance) + / (max_distance - min_distance) + stiffnesses[i] = math.min(1, stiffness) end end @@ -67,28 +105,27 @@ M.change_target_position = function(row, col, jump) draw.clear() if animating then - draw.draw_line(trailing_position[1], trailing_position[2], target_position[1], target_position[2]) - current_position = { target_position[1], target_position[2] } - trailing_position = { target_position[1], target_position[2] } + draw.draw_quad(current_corners, target_position) + set_corners(current_corners, target_position[1], target_position[2]) end target_position = { row, col } + set_corners(target_corners, row, col) if jump then - current_position = { row, col } - trailing_position = { row, col } + set_corners(current_corners, row, col) return end if not animating then - animating = true + set_stiffnesses() animate() end end setmetatable(M, { __index = function(_, key) - if key == "current_position" then - return current_position + if key == "target_position" then + return target_position else return nil end diff --git a/lua/smear_cursor/config.lua b/lua/smear_cursor/config.lua index b3e7c29..5738621 100644 --- a/lua/smear_cursor/config.lua +++ b/lua/smear_cursor/config.lua @@ -21,15 +21,15 @@ M.time_interval = 17 -- milliseconds -- How fast the smear's head moves towards the target. -- 0: no movement, 1: instantaneous -M.stiffness = 0.6 +M.stiffness = 0.3 -- How fast the smear's tail moves towards the head. -- 0: no movement, 1: instantaneous -M.trailing_stiffness = 0.3 +M.trailing_stiffness = 0.1 -- How much the tail slows down when getting close to the head. -- 0: no slowdown, more: more slowdown -M.trailing_exponent = 0.1 +M.slowdown_exponent = 0.2 -- Stop animating when the smear's tail is within this distance (in characters) from the target. M.distance_stop_animating = 0.1 @@ -48,6 +48,6 @@ M.minimum_thickness = 0.7 -- 0: no limit, 1: no reduction -- For debugging --------------------------------------------------------------- M.logging_level = vim.log.levels.INFO -M.dont_erase = false -- Set to true for debugging, or use trailing_stiffness = 0 +-- Set trailing_stiffness to 0 for debugging return M diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index 3a5a655..155b3c5 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -498,4 +498,12 @@ M.draw_line = function(row_start, col_start, row_end, col_end, end_reached) draw_vertical_ish_line(L, draw_diagonal_vertical_block) end +M.draw_quad = function(corners, target_position) + -- logging.debug("Drawing quad") + M.draw_line(corners[1][1], corners[1][2], corners[2][1], corners[2][2] - 1, false) + M.draw_line(corners[2][1], corners[2][2] - 1, corners[3][1] - 1, corners[3][2] - 1, false) + M.draw_line(corners[3][1] - 1, corners[3][2] - 1, corners[4][1] - 1, corners[4][2], false) + M.draw_line(corners[4][1] - 1, corners[4][2], corners[1][1], corners[1][2], false) +end + return M diff --git a/lua/smear_cursor/events.lua b/lua/smear_cursor/events.lua index b06dde0..b92ef2a 100644 --- a/lua/smear_cursor/events.lua +++ b/lua/smear_cursor/events.lua @@ -11,7 +11,7 @@ local function move_cursor() or ( not config.smear_between_neighbor_lines and not switching_buffer - and math.abs(row - animation.current_position[1]) <= 1 + and math.abs(row - animation.target_position[1]) <= 1 ) animation.change_target_position(row, col, jump) From ed58e4531acaae7e5eca08421acee3ced2209ebd Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Sun, 1 Dec 2024 13:12:10 +0100 Subject: [PATCH 02/20] test: add draw_quad test --- tests/test_draw_quad.lua | 101 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tests/test_draw_quad.lua diff --git a/tests/test_draw_quad.lua b/tests/test_draw_quad.lua new file mode 100644 index 0000000..8abf055 --- /dev/null +++ b/tests/test_draw_quad.lua @@ -0,0 +1,101 @@ +-- Instructions: open this file in Neovim and run `source %` +local draw = require("smear_cursor.draw") + +draw.clear() + +local row = 2 +local col = 2 + +draw.draw_quad({ + { row, col }, + { row, col + 2 }, + { row + 2, col + 2 }, + { row + 2, col }, +}) + +-- Quads slope 1/8 + +col = 6 + +draw.draw_quad({ + { row, col }, + { row + 1, col + 9 }, + { row + 10, col + 8 }, + { row + 9, col - 1 }, +}) + +row = 3 +col = 16 + +draw.draw_quad({ + { row, col }, + { row - 1, col + 9 }, + { row + 8, col + 10 }, + { row + 9, col + 1 }, +}) + +-- Quads slope 1/4 + +row = 2 +col = 29 + +draw.draw_quad({ + { row, col }, + { row + 2, col + 9 }, + { row + 11, col + 7 }, + { row + 9, col - 2 }, +}) + +row = 4 +col = 39 + +draw.draw_quad({ + { row, col }, + { row - 2, col + 9 }, + { row + 7, col + 11 }, + { row + 9, col + 2 }, +}) + +-- Quads slope 1/2 + +row = 2 +col = 55 + +draw.draw_quad({ + { row, col }, + { row + 4, col + 9 }, + { row + 13, col + 5 }, + { row + 9, col - 4 }, +}) + +row = 6 +col = 65 + +draw.draw_quad({ + { row, col }, + { row - 4, col + 9 }, + { row + 5, col + 13 }, + { row + 9, col + 4 }, +}) + +-- Quads slope 1 + +row = 13 +col = 6 + +draw.draw_quad({ + { row, col }, + { row + 4, col + 5 }, + { row + 9, col + 1 }, + { row + 5, col - 4 }, +}) + +row = 17 +col = 12 + +draw.draw_quad({ + { row, col }, + { row - 4, col + 5 }, + { row + 1, col + 9 }, + { row + 5, col + 4 }, +}) From b74c07dd51a0a0a7a051b13829c770bf8c5555d4 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Sun, 1 Dec 2024 15:55:43 +0100 Subject: [PATCH 03/20] feat: wip use shifted blocks in draw_quad --- lua/smear_cursor/draw.lua | 95 ++++++++++++++++++++++++++++++++++++--- tests/test_draw_quad.lua | 39 ++++++++++++++++ 2 files changed, 129 insertions(+), 5 deletions(-) diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index 155b3c5..548a80d 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -499,11 +499,96 @@ M.draw_line = function(row_start, col_start, row_end, col_end, end_reached) end M.draw_quad = function(corners, target_position) - -- logging.debug("Drawing quad") - M.draw_line(corners[1][1], corners[1][2], corners[2][1], corners[2][2] - 1, false) - M.draw_line(corners[2][1], corners[2][2] - 1, corners[3][1] - 1, corners[3][2] - 1, false) - M.draw_line(corners[3][1] - 1, corners[3][2] - 1, corners[4][1] - 1, corners[4][2], false) - M.draw_line(corners[4][1] - 1, corners[4][2], corners[1][1], corners[1][2], false) + local top = math.floor(math.min(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) + local bottom = math.ceil(math.max(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) - 1 + local left = math.floor(math.min(corners[1][2], corners[2][2], corners[3][2], corners[4][2])) + local right = math.ceil(math.max(corners[1][2], corners[2][2], corners[3][2], corners[4][2])) - 1 + local edges = {} + local slopes = {} + + for i = 1, 4 do + local edge = { + corners[i % 4 + 1][1] - corners[i][1], + corners[i % 4 + 1][2] - corners[i][2], + } + edges[i] = edge + slopes[i] = edge[1] / edge[2] + end + + for row = top, bottom do + for col = left, right do + -- Filter cells outside the quad + + -- Intersection of top quad edge with vertical centerline of cell + local top_intersection = corners[1][1] + (col + 0.5 - corners[1][2]) * slopes[1] + -- Intersection of top quad edge with left edge of cell + local top_n_left_intersection = top_intersection - 0.5 * slopes[1] + -- Intersection of top quad edge with right edge of cell + local top_n_right_intersection = top_intersection + 0.5 * slopes[1] + -- Cell is above the quad + if top_n_left_intersection >= row + 1 and top_n_right_intersection >= row + 1 then + goto continue + end + + local right_intersection = corners[2][2] + (row + 0.5 - corners[2][1]) / slopes[2] + local right_n_top_intersection = right_intersection - 0.5 / slopes[2] + local right_n_bottom_intersection = right_intersection + 0.5 / slopes[2] + if right_n_top_intersection <= col and right_n_bottom_intersection <= col then + goto continue + end + + local bottom_intersection = corners[3][1] + (col + 0.5 - corners[3][2]) * slopes[3] + local bottom_n_left_intersection = bottom_intersection - 0.5 * slopes[3] + local bottom_n_right_intersection = bottom_intersection + 0.5 * slopes[3] + if bottom_n_left_intersection <= row and bottom_n_right_intersection <= row then + goto continue + end + + local left_intersection = corners[4][2] + (row + 0.5 - corners[4][1]) / slopes[4] + local left_n_top_intersection = left_intersection - 0.5 / math.abs(slopes[4]) + local left_n_bottom_intersection = left_intersection + 0.5 / math.abs(slopes[4]) + if left_n_top_intersection >= col + 1 and left_n_bottom_intersection >= col + 1 then + goto continue + end + + -- Draw horizontally shifted block + local left_in = left_intersection > col + local left_vertical = math.abs(slopes[4]) >= config.min_slope_vertical + local right_in = right_intersection < col + 1 + local right_vertical = math.abs(slopes[2]) >= config.min_slope_vertical + if + (left_in and left_vertical and (not right_in or right_vertical)) + or (right_in and right_vertical and (not left_in or left_vertical)) + then + draw_horizontally_shifted_sub_block( + row, + math.max(col, left_intersection), + math.min(col + 1, right_intersection) + ) + goto continue + end + + -- Draw vertically shifted block + local top_in = top_intersection > row + local top_horizontal = math.abs(slopes[1]) <= config.max_slope_horizontal + local bottom_in = bottom_intersection < row + 1 + local bottom_horizontal = math.abs(slopes[3]) <= config.max_slope_horizontal + if + (top_in and top_horizontal and (not bottom_in or bottom_horizontal)) + or (bottom_in and bottom_horizontal and (not top_in or top_horizontal)) + then + draw_vertically_shifted_sub_block( + math.max(row, top_intersection), + math.min(row + 1, bottom_intersection), + col + ) + goto continue + end + + M.draw_character(row, col, "█", color.get_hl_group()) + ::continue:: + end + end end return M diff --git a/tests/test_draw_quad.lua b/tests/test_draw_quad.lua index 8abf055..6cd0ed7 100644 --- a/tests/test_draw_quad.lua +++ b/tests/test_draw_quad.lua @@ -1,4 +1,6 @@ -- Instructions: open this file in Neovim and run `source %` +-- Warning: this will open a lot of floating windows + local draw = require("smear_cursor.draw") draw.clear() @@ -99,3 +101,40 @@ draw.draw_quad({ { row + 1, col + 9 }, { row + 5, col + 4 }, }) + +-- Lines + +row = 23 +col = 2 + +for i = 0, 8 do + draw.draw_quad({ + { row, col + 10 * i }, + { row + i, col + 10 * i + 9 }, + { row + i + 1, col + 10 * i + 9 }, + { row + 1, col + 10 * i }, + }) +end + +row = 32 +col = 2 + +for i = 8, 0, -1 do + draw.draw_quad({ + { row, col }, + { row, col + 1 }, + { row + 9, col + i + 1 }, + { row + 9, col + i }, + }) + + col = col + 4 + + draw.draw_quad({ + { row, col }, + { row, col + 1 / 8 }, + { row + 9, col + i + 1 / 8 }, + { row + 9, col + i }, + }) + + col = col + 5 +end From 58da6db8db4ed46e1bfea8e0d23023acf390326a Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Sun, 1 Dec 2024 16:23:19 +0100 Subject: [PATCH 04/20] fix: wip draw end of previous smear --- lua/smear_cursor/animation.lua | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/lua/smear_cursor/animation.lua b/lua/smear_cursor/animation.lua index 48ee8f8..87967f6 100644 --- a/lua/smear_cursor/animation.lua +++ b/lua/smear_cursor/animation.lua @@ -11,19 +11,6 @@ local current_corners = {} local target_corners = {} local stiffnesses = { 0, 0, 0, 0 } --- local loop_metatable = { --- __index = function(table, key) --- if key == 5 then --- return table[1] --- else --- return nil --- end --- end, --- } - --- setmetatable(current_corners, loop_metatable) --- setmetatable(target_corners, loop_metatable) - local function set_corners(corners, row, col) corners[1] = { row, col } corners[2] = { row, col + 1 } @@ -75,7 +62,7 @@ local function animate() end end -local function set_stiffnesses() +local function set_stiffnesses(head_stiffness, trailing_stiffness) local target_center = { target_position[1] + 0.5, target_position[2] + 0.5 } local distances = {} local min_distance = math.huge @@ -90,10 +77,8 @@ local function set_stiffnesses() end for i = 1, 4 do - local stiffness = config.stiffness - + (config.trailing_stiffness - config.stiffness) - * (distances[i] - min_distance) - / (max_distance - min_distance) + local stiffness = head_stiffness + + (trailing_stiffness - head_stiffness) * (distances[i] - min_distance) / (max_distance - min_distance) stiffnesses[i] = math.min(1, stiffness) end end @@ -104,20 +89,24 @@ M.change_target_position = function(row, col, jump) end draw.clear() + -- Draw end of previous smear if animating then + set_stiffnesses(1, 0) + update() draw.draw_quad(current_corners, target_position) set_corners(current_corners, target_position[1], target_position[2]) end target_position = { row, col } set_corners(target_corners, row, col) + set_stiffnesses(config.stiffness, config.trailing_stiffness) + if jump then set_corners(current_corners, row, col) return end if not animating then - set_stiffnesses() animate() end end From cfefaa9da10f3340e6db98f47fbfccd71bd87207 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Sun, 1 Dec 2024 16:35:05 +0100 Subject: [PATCH 05/20] fix: wip hide target --- lua/smear_cursor/animation.lua | 10 ++++------ lua/smear_cursor/draw.lua | 10 ++++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lua/smear_cursor/animation.lua b/lua/smear_cursor/animation.lua index 87967f6..09b608c 100644 --- a/lua/smear_cursor/animation.lua +++ b/lua/smear_cursor/animation.lua @@ -49,12 +49,10 @@ local function animate() max_distance = math.max(max_distance, distance) end if max_distance > config.distance_stop_animating then - draw.draw_quad(current_corners, target_position) - -- TODO: draw character at cursor if hack is enabled - -- local end_reached = ... - -- if not end_reached and config.hide_target_hack then - -- draw.draw_character(target_position[1], target_position[2], " ", color.get_hl_group({ inverted = true })) - -- end + local target_reached = draw.draw_quad(current_corners, target_position) + if not target_reached and config.hide_target_hack then + draw.draw_character(target_position[1], target_position[2], " ", color.get_hl_group({ inverted = true })) + end vim.defer_fn(animate, config.time_interval) else animating = false diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index 548a80d..77892f8 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -515,6 +515,8 @@ M.draw_quad = function(corners, target_position) slopes[i] = edge[1] / edge[2] end + local target_reached = false + for row = top, bottom do for col = left, right do -- Filter cells outside the quad @@ -551,6 +553,12 @@ M.draw_quad = function(corners, target_position) goto continue end + -- Check target + if row == target_position[1] and col == target_position[2] then + target_reached = true + goto continue + end + -- Draw horizontally shifted block local left_in = left_intersection > col local left_vertical = math.abs(slopes[4]) >= config.min_slope_vertical @@ -589,6 +597,8 @@ M.draw_quad = function(corners, target_position) ::continue:: end end + + return target_reached end return M From 98375cf18d2090895586a8bbc28e0268c4d25b1e Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Sun, 1 Dec 2024 17:28:03 +0100 Subject: [PATCH 06/20] feat: wip better select which shifted block to draw --- lua/smear_cursor/draw.lua | 78 +++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index 77892f8..1cdc70a 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -161,7 +161,7 @@ local function draw_matrix_character(row, col, matrix, L) M.draw_character(row, col, character, color.get_hl_group({ level = hl_group_index }), L) end -local function draw_vertically_shifted_sub_block(row_top, row_bottom, col, L) +local function draw_vertically_shifted_sub_block(row_top, row_bottom, col, shade) if row_top >= row_bottom then return end @@ -180,7 +180,7 @@ local function draw_vertically_shifted_sub_block(row_top, row_bottom, col, L) end local character_thickness = character_index / 8 - local shade = thickness / character_thickness + shade = shade * thickness / character_thickness local hl_group_index = round(shade * config.color_levels) if hl_group_index == 0 then return @@ -201,7 +201,7 @@ local function draw_vertically_shifted_sub_block(row_top, row_bottom, col, L) end local character_thickness = 1 - character_index / 8 - local shade = thickness / character_thickness + shade = shade * thickness / character_thickness local hl_group_index = round(shade * config.color_levels) if hl_group_index == 0 then return @@ -223,7 +223,7 @@ local function draw_vertically_shifted_block(row_float, col, L) draw_vertically_shifted_sub_block(row + 1, bottom, col, L) end -local function draw_horizontally_shifted_sub_block(row, col_left, col_right, L) +local function draw_horizontally_shifted_sub_block(row, col_left, col_right, shade) if col_left >= col_right then return end @@ -242,7 +242,7 @@ local function draw_horizontally_shifted_sub_block(row, col_left, col_right, L) end local character_thickness = character_index / 8 - local shade = thickness / character_thickness + shade = shade * thickness / character_thickness local hl_group_index = round(shade * config.color_levels) if hl_group_index == 0 then return @@ -258,7 +258,7 @@ local function draw_horizontally_shifted_sub_block(row, col_left, col_right, L) end local character_thickness = 1 - character_index / 8 - local shade = thickness / character_thickness + shade = shade * thickness / character_thickness local hl_group_index = round(shade * config.color_levels) if hl_group_index == 0 then return @@ -553,13 +553,31 @@ M.draw_quad = function(corners, target_position) goto continue end - -- Check target + -- Check if on target if row == target_position[1] and col == target_position[2] then target_reached = true goto continue end - -- Draw horizontally shifted block + local is_vertically_shifted = false + local vertical_shade = 1 + local is_horizontally_shifted = false + local horizontal_shade = 1 + + -- Check if vertically shifted block + local top_in = top_intersection > row + local top_horizontal = math.abs(slopes[1]) <= config.max_slope_horizontal + local bottom_in = bottom_intersection < row + 1 + local bottom_horizontal = math.abs(slopes[3]) <= config.max_slope_horizontal + if + (top_in and top_horizontal and (not bottom_in or bottom_horizontal)) + or (bottom_in and bottom_horizontal and (not top_in or top_horizontal)) + then + is_vertically_shifted = true + vertical_shade = math.min(row + 1, bottom_intersection) - math.max(row, top_intersection) + end + + -- Check if horizontally shifted block local left_in = left_intersection > col local left_vertical = math.abs(slopes[4]) >= config.min_slope_vertical local right_in = right_intersection < col + 1 @@ -568,32 +586,44 @@ M.draw_quad = function(corners, target_position) (left_in and left_vertical and (not right_in or right_vertical)) or (right_in and right_vertical and (not left_in or left_vertical)) then - draw_horizontally_shifted_sub_block( - row, - math.max(col, left_intersection), - math.min(col + 1, right_intersection) - ) - goto continue + is_horizontally_shifted = true + horizontal_shade = math.min(col + 1, right_intersection) - math.max(col, left_intersection) end - -- Draw vertically shifted block - local top_in = top_intersection > row - local top_horizontal = math.abs(slopes[1]) <= config.max_slope_horizontal - local bottom_in = bottom_intersection < row + 1 - local bottom_horizontal = math.abs(slopes[3]) <= config.max_slope_horizontal - if - (top_in and top_horizontal and (not bottom_in or bottom_horizontal)) - or (bottom_in and bottom_horizontal and (not top_in or top_horizontal)) - then + -- Draw shifted block + if is_vertically_shifted and is_horizontally_shifted then + -- if vertical_shade < 0.5 and horizontal_shade < 0.5 then + -- is_vertically_shifted = false + -- is_horizontally_shifted = false + if vertical_shade < horizontal_shade then + is_horizontally_shifted = false + else + is_vertically_shifted = false + end + end + + if is_vertically_shifted then draw_vertically_shifted_sub_block( math.max(row, top_intersection), math.min(row + 1, bottom_intersection), - col + col, + horizontal_shade + ) + goto continue + end + + if is_horizontally_shifted then + draw_horizontally_shifted_sub_block( + row, + math.max(col, left_intersection), + math.min(col + 1, right_intersection), + vertical_shade ) goto continue end M.draw_character(row, col, "█", color.get_hl_group()) + ::continue:: end end From 0194948e5896bb493ec3febd8e4563dcdaa36cd7 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Sun, 1 Dec 2024 17:36:59 +0100 Subject: [PATCH 07/20] fix: wip blocks sticking out --- lua/smear_cursor/config.lua | 6 +++--- lua/smear_cursor/draw.lua | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lua/smear_cursor/config.lua b/lua/smear_cursor/config.lua index 5738621..103860a 100644 --- a/lua/smear_cursor/config.lua +++ b/lua/smear_cursor/config.lua @@ -21,15 +21,15 @@ M.time_interval = 17 -- milliseconds -- How fast the smear's head moves towards the target. -- 0: no movement, 1: instantaneous -M.stiffness = 0.3 +M.stiffness = 0.6 -- How fast the smear's tail moves towards the head. -- 0: no movement, 1: instantaneous -M.trailing_stiffness = 0.1 +M.trailing_stiffness = 0.3 -- How much the tail slows down when getting close to the head. -- 0: no slowdown, more: more slowdown -M.slowdown_exponent = 0.2 +M.slowdown_exponent = 0.0 -- Stop animating when the smear's tail is within this distance (in characters) from the target. M.distance_stop_animating = 0.1 diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index 1cdc70a..f231a6a 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -592,10 +592,11 @@ M.draw_quad = function(corners, target_position) -- Draw shifted block if is_vertically_shifted and is_horizontally_shifted then - -- if vertical_shade < 0.5 and horizontal_shade < 0.5 then - -- is_vertically_shifted = false - -- is_horizontally_shifted = false - if vertical_shade < horizontal_shade then + if vertical_shade < 0.75 and horizontal_shade < 0.5 then + goto continue + -- is_vertically_shifted = false + -- is_horizontally_shifted = false + elseif vertical_shade < horizontal_shade then is_horizontally_shifted = false else is_vertically_shifted = false From 6534c7e7d974c6642adf47a40cf2c37c7d8d469a Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Sun, 1 Dec 2024 19:45:23 +0100 Subject: [PATCH 08/20] feat: wip add matrix render for quad --- lua/smear_cursor/config.lua | 2 +- lua/smear_cursor/draw.lua | 70 ++++++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/lua/smear_cursor/config.lua b/lua/smear_cursor/config.lua index 103860a..1419ec9 100644 --- a/lua/smear_cursor/config.lua +++ b/lua/smear_cursor/config.lua @@ -40,7 +40,7 @@ M.min_slope_vertical = 2 M.color_levels = 16 -- Minimum 1 M.gamma = 2.2 -- For color blending -M.diagonal_pixel_value_threshold = 0.5 -- 0.1: more pixels, 0.9: less pixels +M.diagonal_pixel_value_threshold = 0.25 -- 0.1: more pixels, 0.9: less pixels M.diagonal_thickness_factor = 0.7 -- Put less than 1 to reduce diagonal smear fatness M.thickness_reduction_exponent = 0.2 -- 0: no reduction, 1: full reduction M.minimum_thickness = 0.7 -- 0: no limit, 1: no reduction diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index f231a6a..47f89be 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -499,6 +499,10 @@ M.draw_line = function(row_start, col_start, row_end, col_end, end_reached) end M.draw_quad = function(corners, target_position) + if target_position == nil then + target_position = { 0, 0 } + end + local top = math.floor(math.min(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) local bottom = math.ceil(math.max(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) - 1 local left = math.floor(math.min(corners[1][2], corners[2][2], corners[3][2], corners[4][2])) @@ -593,9 +597,8 @@ M.draw_quad = function(corners, target_position) -- Draw shifted block if is_vertically_shifted and is_horizontally_shifted then if vertical_shade < 0.75 and horizontal_shade < 0.5 then - goto continue - -- is_vertically_shifted = false - -- is_horizontally_shifted = false + is_vertically_shifted = false + is_horizontally_shifted = false elseif vertical_shade < horizontal_shade then is_horizontally_shifted = false else @@ -623,7 +626,66 @@ M.draw_quad = function(corners, target_position) goto continue end - M.draw_character(row, col, "█", color.get_hl_group()) + -- Draw matrix + local row_float, col_float, matrix_index, shade + local matrix = { + { 1, 1 }, + { 1, 1 }, + } + + for i = 1, 2 do + local shift = (i == 1) and -0.25 or 0.25 + + -- Intersection with top quad edge + row_float = top_intersection + shift * slopes[1] + row_float = 2 * (row_float - row) + matrix_index = math.floor(row_float) + 1 + for index = 1, math.min(2, matrix_index - 1) do + matrix[index][i] = 0 + end + if matrix_index == 1 or matrix_index == 2 then + shade = 1 - (row_float % 1) + matrix[matrix_index][i] = matrix[matrix_index][i] * shade + end + + -- Intersection with right quad edge + col_float = right_intersection + shift / slopes[2] + col_float = 2 * (col_float - col) + matrix_index = math.floor(col_float) + 1 + for index = math.max(1, matrix_index + 1), 2 do + matrix[i][index] = 0 + end + if matrix_index == 1 or matrix_index == 2 then + shade = col_float % 1 + matrix[i][matrix_index] = matrix[i][matrix_index] * shade + end + + -- Intersection with bottom quad edge + row_float = bottom_intersection + shift * slopes[3] + row_float = 2 * (row_float - row) + matrix_index = math.floor(row_float) + 1 + for index = math.max(1, matrix_index + 1), 2 do + matrix[index][i] = 0 + end + if matrix_index == 1 or matrix_index == 2 then + shade = row_float % 1 + matrix[matrix_index][i] = matrix[matrix_index][i] * shade + end + + -- Intersection with left quad edge + col_float = left_intersection + shift / slopes[4] + col_float = 2 * (col_float - col) + matrix_index = math.floor(col_float) + 1 + for index = 1, math.min(2, matrix_index - 1) do + matrix[i][index] = 0 + end + if matrix_index == 1 or matrix_index == 2 then + shade = 1 - (col_float % 1) + matrix[i][matrix_index] = matrix[i][matrix_index] * shade + end + end + + draw_matrix_character(row, col, matrix) ::continue:: end From 1b500b880815f89317289dc89a75db8952d6ae93 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Sun, 1 Dec 2024 23:23:45 +0100 Subject: [PATCH 09/20] fix: wip shifted blocks drawn at wrong spots --- lua/smear_cursor/draw.lua | 125 +++++++++++++++++--------------------- tests/test_draw_quad.lua | 44 ++++++++++++-- 2 files changed, 95 insertions(+), 74 deletions(-) diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index 47f89be..a892084 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -503,11 +503,6 @@ M.draw_quad = function(corners, target_position) target_position = { 0, 0 } end - local top = math.floor(math.min(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) - local bottom = math.ceil(math.max(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) - 1 - local left = math.floor(math.min(corners[1][2], corners[2][2], corners[3][2], corners[4][2])) - local right = math.ceil(math.max(corners[1][2], corners[2][2], corners[3][2], corners[4][2])) - 1 - local edges = {} local slopes = {} for i = 1, 4 do @@ -515,90 +510,80 @@ M.draw_quad = function(corners, target_position) corners[i % 4 + 1][1] - corners[i][1], corners[i % 4 + 1][2] - corners[i][2], } - edges[i] = edge slopes[i] = edge[1] / edge[2] end + local top = math.floor(math.min(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) + local bottom = math.ceil(math.max(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) - 1 local target_reached = false for row = top, bottom do - for col = left, right do - -- Filter cells outside the quad - - -- Intersection of top quad edge with vertical centerline of cell - local top_intersection = corners[1][1] + (col + 0.5 - corners[1][2]) * slopes[1] - -- Intersection of top quad edge with left edge of cell - local top_n_left_intersection = top_intersection - 0.5 * slopes[1] - -- Intersection of top quad edge with right edge of cell - local top_n_right_intersection = top_intersection + 0.5 * slopes[1] - -- Cell is above the quad - if top_n_left_intersection >= row + 1 and top_n_right_intersection >= row + 1 then - goto continue - end - - local right_intersection = corners[2][2] + (row + 0.5 - corners[2][1]) / slopes[2] - local right_n_top_intersection = right_intersection - 0.5 / slopes[2] - local right_n_bottom_intersection = right_intersection + 0.5 / slopes[2] - if right_n_top_intersection <= col and right_n_bottom_intersection <= col then - goto continue - end - - local bottom_intersection = corners[3][1] + (col + 0.5 - corners[3][2]) * slopes[3] - local bottom_n_left_intersection = bottom_intersection - 0.5 * slopes[3] - local bottom_n_right_intersection = bottom_intersection + 0.5 * slopes[3] - if bottom_n_left_intersection <= row and bottom_n_right_intersection <= row then - goto continue - end - - local left_intersection = corners[4][2] + (row + 0.5 - corners[4][1]) / slopes[4] - local left_n_top_intersection = left_intersection - 0.5 / math.abs(slopes[4]) - local left_n_bottom_intersection = left_intersection + 0.5 / math.abs(slopes[4]) - if left_n_top_intersection >= col + 1 and left_n_bottom_intersection >= col + 1 then - goto continue - end + local left = corners[4][2] + (row + 0.5 - corners[4][1]) / slopes[4] + left = left - 0.5 / math.abs(slopes[4]) + local right = corners[2][2] + (row + 0.5 - corners[2][1]) / slopes[2] + right = right + 0.5 / math.abs(slopes[2]) + for col = math.floor(left), math.ceil(right) do -- Check if on target if row == target_position[1] and col == target_position[2] then target_reached = true goto continue end + -- Intersection of quad edge with centerline of cell + local top_centerline = corners[1][1] + (col + 0.5 - corners[1][2]) * slopes[1] + -- Lowest intersection of quad edge with lateral edges of cell + local top_intersection = top_centerline + 0.5 * math.abs(slopes[1]) + local right_centerline = corners[2][2] + (row + 0.5 - corners[2][1]) / slopes[2] + local right_intersection = right_centerline - 0.5 / math.abs(slopes[2]) + local bottom_centerline = corners[3][1] + (col + 0.5 - corners[3][2]) * slopes[3] + local bottom_intersection = bottom_centerline - 0.5 * math.abs(slopes[3]) + local left_centerline = corners[4][2] + (row + 0.5 - corners[4][1]) / slopes[4] + local left_intersection = left_centerline + 0.5 / math.abs(slopes[4]) + local is_vertically_shifted = false local vertical_shade = 1 local is_horizontally_shifted = false local horizontal_shade = 1 -- Check if vertically shifted block - local top_in = top_intersection > row local top_horizontal = math.abs(slopes[1]) <= config.max_slope_horizontal - local bottom_in = bottom_intersection < row + 1 local bottom_horizontal = math.abs(slopes[3]) <= config.max_slope_horizontal - if - (top_in and top_horizontal and (not bottom_in or bottom_horizontal)) - or (bottom_in and bottom_horizontal and (not top_in or top_horizontal)) - then - is_vertically_shifted = true - vertical_shade = math.min(row + 1, bottom_intersection) - math.max(row, top_intersection) - end - - -- Check if horizontally shifted block local left_in = left_intersection > col local left_vertical = math.abs(slopes[4]) >= config.min_slope_vertical local right_in = right_intersection < col + 1 local right_vertical = math.abs(slopes[2]) >= config.min_slope_vertical - if - (left_in and left_vertical and (not right_in or right_vertical)) - or (right_in and right_vertical and (not left_in or left_vertical)) - then - is_horizontally_shifted = true - horizontal_shade = math.min(col + 1, right_intersection) - math.max(col, left_intersection) + if not (left_in and not left_vertical) and not (right_in and not right_vertical) then + local top_near = top_centerline > row + local bottom_near = bottom_centerline < row + 1 + if + (top_near and top_horizontal and (not bottom_near or bottom_horizontal)) + or (bottom_near and bottom_horizontal and (not top_near or top_horizontal)) + then + is_vertically_shifted = true + vertical_shade = math.min(row + 1, bottom_centerline) - math.max(row, top_centerline) + end + end + + -- Check if horizontally shifted block + local top_in = top_intersection > row + local bottom_in = bottom_intersection < row + 1 + if not (top_in and not top_horizontal) and not (bottom_in and not bottom_horizontal) then + local left_near = left_centerline > col + local right_near = right_centerline < col + 1 + if + (left_near and left_vertical and (not right_near or right_vertical)) + or (right_near and right_vertical and (not left_near or left_vertical)) + then + is_horizontally_shifted = true + horizontal_shade = math.min(col + 1, right_centerline) - math.max(col, left_centerline) + end end -- Draw shifted block if is_vertically_shifted and is_horizontally_shifted then - if vertical_shade < 0.75 and horizontal_shade < 0.5 then - is_vertically_shifted = false - is_horizontally_shifted = false + if vertical_shade < 0.75 and horizontal_shade < 0.75 then + goto continue elseif vertical_shade < horizontal_shade then is_horizontally_shifted = false else @@ -606,21 +591,21 @@ M.draw_quad = function(corners, target_position) end end - if is_vertically_shifted then + if is_vertically_shifted and horizontal_shade > 0 then draw_vertically_shifted_sub_block( - math.max(row, top_intersection), - math.min(row + 1, bottom_intersection), + math.max(row, top_centerline), + math.min(row + 1, bottom_centerline), col, horizontal_shade ) goto continue end - if is_horizontally_shifted then + if is_horizontally_shifted and vertical_shade > 0 then draw_horizontally_shifted_sub_block( row, - math.max(col, left_intersection), - math.min(col + 1, right_intersection), + math.max(col, left_centerline), + math.min(col + 1, right_centerline), vertical_shade ) goto continue @@ -637,7 +622,7 @@ M.draw_quad = function(corners, target_position) local shift = (i == 1) and -0.25 or 0.25 -- Intersection with top quad edge - row_float = top_intersection + shift * slopes[1] + row_float = top_centerline + shift * slopes[1] row_float = 2 * (row_float - row) matrix_index = math.floor(row_float) + 1 for index = 1, math.min(2, matrix_index - 1) do @@ -649,7 +634,7 @@ M.draw_quad = function(corners, target_position) end -- Intersection with right quad edge - col_float = right_intersection + shift / slopes[2] + col_float = right_centerline + shift / slopes[2] col_float = 2 * (col_float - col) matrix_index = math.floor(col_float) + 1 for index = math.max(1, matrix_index + 1), 2 do @@ -661,7 +646,7 @@ M.draw_quad = function(corners, target_position) end -- Intersection with bottom quad edge - row_float = bottom_intersection + shift * slopes[3] + row_float = bottom_centerline + shift * slopes[3] row_float = 2 * (row_float - row) matrix_index = math.floor(row_float) + 1 for index = math.max(1, matrix_index + 1), 2 do @@ -673,7 +658,7 @@ M.draw_quad = function(corners, target_position) end -- Intersection with left quad edge - col_float = left_intersection + shift / slopes[4] + col_float = left_centerline + shift / slopes[4] col_float = 2 * (col_float - col) matrix_index = math.floor(col_float) + 1 for index = 1, math.min(2, matrix_index - 1) do diff --git a/tests/test_draw_quad.lua b/tests/test_draw_quad.lua index 6cd0ed7..d57d21f 100644 --- a/tests/test_draw_quad.lua +++ b/tests/test_draw_quad.lua @@ -109,13 +109,49 @@ col = 2 for i = 0, 8 do draw.draw_quad({ - { row, col + 10 * i }, - { row + i, col + 10 * i + 9 }, - { row + i + 1, col + 10 * i + 9 }, - { row + 1, col + 10 * i }, + { row, col }, + { row + i, col + 9 }, + { row + i + 1, col + 9 }, + { row + 1, col }, }) + + col = col + 10 +end + +-- Rhombuses + +row = 26 +col = 2 + +for i = 0, 4 do + draw.draw_quad({ + { row, col }, + { row + i / 2, col + 5 }, + { row + i + 1, col + 9 }, + { row + i / 2 + 1, col + 4 }, + }) + + col = col + 10 end +-- Thin horizontal lines + +row = 29 +col = 2 + +for i = 0, 2 do + draw.draw_quad({ + { row, col }, + { row + i, col + 9 }, + { row + i + 1 / 8, col + 9 }, + { row + 1 / 8, col }, + }) + + col = col + 10 +end + +-- Vertical lines + row = 32 col = 2 From 82635c4e396bd6c49fbbfd19a04257237c6156b3 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Sun, 1 Dec 2024 23:41:29 +0100 Subject: [PATCH 10/20] feat: add quad volume shrinking --- lua/smear_cursor/animation.lua | 37 ++++++++++++++++++++++++++++++++-- lua/smear_cursor/config.lua | 4 ++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lua/smear_cursor/animation.lua b/lua/smear_cursor/animation.lua index 09b608c..4b068a2 100644 --- a/lua/smear_cursor/animation.lua +++ b/lua/smear_cursor/animation.lua @@ -36,6 +36,39 @@ local function update() end end +local function shrink_volume(corners) + local edges = {} + for i = 1, 3 do + edges[i] = { + corners[i + 1][1] - corners[1][1], + corners[i + 1][2] - corners[1][2], + } + end + + local double_volumes = {} + for i = 1, 2 do + double_volumes[i] = edges[1][2] * edges[2][1] - edges[1][1] * edges[2][2] + end + local volume = (double_volumes[1] + double_volumes[2]) / 2 + + local center = { + (corners[1][1] + corners[2][1] + corners[3][1] + corners[4][1]) / 4, + (corners[1][2] + corners[2][2] + corners[3][2] + corners[4][2]) / 4, + } + + local factor = (1 / volume) ^ (config.volume_reduction_exponent / 2) + factor = math.max(config.minimum_volume_factor, factor) + local shrunk_corners = {} + for i = 1, 4 do + shrunk_corners[i] = { + center[1] + (corners[i][1] - center[1]) * factor, + center[2] + (corners[i][2] - center[2]) * factor, + } + end + + return shrunk_corners +end + local function animate() animating = true update() @@ -49,7 +82,7 @@ local function animate() max_distance = math.max(max_distance, distance) end if max_distance > config.distance_stop_animating then - local target_reached = draw.draw_quad(current_corners, target_position) + local target_reached = draw.draw_quad(shrink_volume(current_corners), target_position) if not target_reached and config.hide_target_hack then draw.draw_character(target_position[1], target_position[2], " ", color.get_hl_group({ inverted = true })) end @@ -91,7 +124,7 @@ M.change_target_position = function(row, col, jump) if animating then set_stiffnesses(1, 0) update() - draw.draw_quad(current_corners, target_position) + draw.draw_quad(shrink_volume(current_corners), target_position) set_corners(current_corners, target_position[1], target_position[2]) end diff --git a/lua/smear_cursor/config.lua b/lua/smear_cursor/config.lua index 1419ec9..cf69a4b 100644 --- a/lua/smear_cursor/config.lua +++ b/lua/smear_cursor/config.lua @@ -42,8 +42,8 @@ M.color_levels = 16 -- Minimum 1 M.gamma = 2.2 -- For color blending M.diagonal_pixel_value_threshold = 0.25 -- 0.1: more pixels, 0.9: less pixels M.diagonal_thickness_factor = 0.7 -- Put less than 1 to reduce diagonal smear fatness -M.thickness_reduction_exponent = 0.2 -- 0: no reduction, 1: full reduction -M.minimum_thickness = 0.7 -- 0: no limit, 1: no reduction +M.volume_reduction_exponent = 0.2 -- 0: no reduction, 1: full reduction +M.minimum_volume_factor = 0.7 -- 0: no limit, 1: no reduction -- For debugging --------------------------------------------------------------- From ddd150329e8f80427bc5b3c2a5bd9fe459b1e7a8 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Mon, 2 Dec 2024 00:05:23 +0100 Subject: [PATCH 11/20] chore: remove unused draw functions --- lua/smear_cursor/animation.lua | 13 +- lua/smear_cursor/draw.lua | 252 +-------------------------------- 2 files changed, 19 insertions(+), 246 deletions(-) diff --git a/lua/smear_cursor/animation.lua b/lua/smear_cursor/animation.lua index 4b068a2..d3899ea 100644 --- a/lua/smear_cursor/animation.lua +++ b/lua/smear_cursor/animation.lua @@ -82,7 +82,18 @@ local function animate() max_distance = math.max(max_distance, distance) end if max_distance > config.distance_stop_animating then - local target_reached = draw.draw_quad(shrink_volume(current_corners), target_position) + local shrunk_corners = shrink_volume(current_corners) + draw.draw_quad(shrunk_corners, target_position) + local target_reached = ( + math.floor(current_corners[1][1]) == target_position[1] + and math.floor(current_corners[1][2]) == target_position[2] + ) + or (math.floor(current_corners[2][1]) == target_position[1] and math.ceil(current_corners[2][2]) - 1 == target_position[2]) + or (math.ceil(current_corners[3][1]) - 1 == target_position[1] and math.ceil(current_corners[3][2]) - 1 == target_position[2]) + or ( + math.ceil(current_corners[4][1]) - 1 == target_position[1] + and math.floor(current_corners[4][2]) == target_position[2] + ) if not target_reached and config.hide_target_hack then draw.draw_character(target_position[1], target_position[2], " ", color.get_hl_group({ inverted = true })) end diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index a892084..9732be5 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -97,10 +97,7 @@ local function get_window(tab, row, col) return window_id, buffer_id end -M.draw_character = function(row, col, character, hl_group, L) - if L ~= nil and L.end_reached and row == L.row_end_rounded and col == L.col_end_rounded then - return - end +M.draw_character = function(row, col, character, hl_group) -- logging.debug("Drawing character " .. character .. " at (" .. row .. ", " .. col .. ")") local current_tab = vim.api.nvim_get_current_tabpage() local _, buffer_id = get_window(current_tab, row, col) @@ -132,12 +129,12 @@ M.clear = function() end end -local function draw_partial_block(row, col, character_list, character_index, hl_group, L) +local function draw_partial_block(row, col, character_list, character_index, hl_group) local character = character_list[character_index + 1] - M.draw_character(row, col, character, hl_group, L) + M.draw_character(row, col, character, hl_group) end -local function draw_matrix_character(row, col, matrix, L) +local function draw_matrix_character(row, col, matrix) local threshold = config.diagonal_pixel_value_threshold * math.max(matrix[1][1], matrix[1][2], matrix[2][1], matrix[2][2]) local bit_1 = (matrix[1][1] > threshold) and 1 or 0 @@ -158,7 +155,7 @@ local function draw_matrix_character(row, col, matrix, L) return end - M.draw_character(row, col, character, color.get_hl_group({ level = hl_group_index }), L) + M.draw_character(row, col, character, color.get_hl_group({ level = hl_group_index })) end local function draw_vertically_shifted_sub_block(row_top, row_bottom, col, shade) @@ -211,16 +208,7 @@ local function draw_vertically_shifted_sub_block(row_top, row_bottom, col, shade hl_group = color.get_hl_group({ level = hl_group_index }) end - draw_partial_block(row, col, character_list, character_index, hl_group, L) -end - -local function draw_vertically_shifted_block(row_float, col, L) - local top = row_float + 0.5 - L.thickness / 2 - local bottom = top + L.thickness - local row = math.floor(top) - - draw_vertically_shifted_sub_block(top, math.min(bottom, row + 1), col, L) - draw_vertically_shifted_sub_block(row + 1, bottom, col, L) + draw_partial_block(row, col, character_list, character_index, hl_group) end local function draw_horizontally_shifted_sub_block(row, col_left, col_right, shade) @@ -273,229 +261,7 @@ local function draw_horizontally_shifted_sub_block(row, col_left, col_right, sha end end - draw_partial_block(row, col, character_list, character_index, hl_group, L) -end - -local function draw_horizontally_shifted_block(row, col_float, L) - local left = col_float + 0.5 - L.thickness / 2 - local right = left + L.thickness - local col = math.floor(left) - - draw_horizontally_shifted_sub_block(row, left, math.min(right, col + 1), L) - draw_horizontally_shifted_sub_block(row, col + 1, right, L) -end - -local function fill_matrix_vertical_sub_block(matrix, row_top, row_bottom, col) - if row_top >= row_bottom then - return - end - local row = math.floor(row_top) - if row < 1 or row > #matrix then - return - end - local shade = row_bottom - row_top - matrix[row][col] = math.max(matrix[row][col], shade) -end - -local function fill_matrix_vertically(matrix, row_float, col, thickness) - local top = row_float + 1 - thickness * config.diagonal_thickness_factor - local bottom = top + 2 * thickness * config.diagonal_thickness_factor - local row = math.floor(top) - -- logging.debug("top: " .. top .. ", bottom: " .. bottom) - - fill_matrix_vertical_sub_block(matrix, top, math.min(bottom, row + 1), col) - fill_matrix_vertical_sub_block(matrix, row + 1, math.min(bottom, row + 2), col) - fill_matrix_vertical_sub_block(matrix, row + 2, bottom, col) -end - -local function draw_diagonal_horizontal_block(row_float, col, L) - local row = math.floor(row_float) - local shift = row_float - row - -- Matrix of lit quarters - local m = { - { 0, 0 }, -- Top of row above - { 0, 0 }, -- Bottom of row above - { 0, 0 }, -- Top of current row - { 0, 0 }, -- Bottom of current row - { 0, 0 }, -- Top of row below - { 0, 0 }, -- Bottom of row below - } - - -- Lit from the left - if col > L.left then - local shift_left = shift - 0.5 * L.slope - fill_matrix_vertically(m, 3 + 2 * shift_left, 1, L.thickness) - end - - -- Lit from center - fill_matrix_vertically(m, 3 + 2 * shift, 1, L.thickness) - fill_matrix_vertically(m, 3 + 2 * shift, 2, L.thickness) - - -- Lit from the right - if col < L.right then - local shift_right = shift + 0.5 * L.slope - fill_matrix_vertically(m, 3 + 2 * shift_right, 2, L.thickness) - end - - for i = -1, 1 do - local row_i = row + i - draw_matrix_character(row_i, col, { m[2 * i + 3], m[2 * i + 4] }, L) - end -end - -local function fill_matrix_horizontal_sub_block(matrix, row, col_left, col_right) - if col_left >= col_right then - return - end - local col = math.floor(col_left) - if col < 1 or col > #matrix[1] then - return - end - local shade = col_right - col_left - matrix[row][col] = math.max(matrix[row][col], shade) -end - -local function fill_matrix_horizontally(matrix, row, col_float, thickness) - local left = col_float + 1 - thickness * config.diagonal_thickness_factor - local right = left + 2 * thickness * config.diagonal_thickness_factor - local col = math.floor(left) - -- logging.debug("left: " .. left .. ", right: " .. right) - - fill_matrix_horizontal_sub_block(matrix, row, left, math.min(right, col + 1)) - fill_matrix_horizontal_sub_block(matrix, row, col + 1, math.min(right, col + 2)) - fill_matrix_horizontal_sub_block(matrix, row, col + 2, right) -end - -local function draw_diagonal_vertical_block(row, col_float, L) - local col = math.floor(col_float) - local shift = col_float - col - -- Matrix of lit quarters - local m = { - { 0, 0, 0, 0, 0, 0 }, -- Top - { 0, 0, 0, 0, 0, 0 }, -- Bottom - } -- c-1 c c+1 - - -- Lit from the top - if row > L.top then - local shift_top = shift - 0.5 / L.slope - fill_matrix_horizontally(m, 1, 3 + 2 * shift_top, L.thickness) - end - - -- Lit from center - local half_row = round(shift * 2) - fill_matrix_horizontally(m, 1, 3 + half_row, L.thickness) - fill_matrix_horizontally(m, 2, 3 + half_row, L.thickness) - - -- Lit from the bottom - if row < L.bottom then - local shift_bottom = shift + 0.5 / L.slope - fill_matrix_horizontally(m, 2, 3 + 2 * shift_bottom, L.thickness) - end - - for i = -1, 1 do - local col_i = col + i - draw_matrix_character(row, col_i, { - { m[1][2 * i + 3], m[1][2 * i + 4] }, - { m[2][2 * i + 3], m[2][2 * i + 4] }, - }, L) - end -end - -local function draw_horizontal_ish_line(L, draw_block_function) - for col = L.col_start_rounded, L.col_end_rounded, L.col_direction do - local row_float = L.row_start + L.row_shift * (col - L.col_start) / L.col_shift - draw_block_function(row_float, col, L) - end -end - -local function draw_vertical_ish_line(L, draw_block_function) - for row = L.row_start_rounded, L.row_end_rounded, L.row_direction do - local col_float = L.col_start + L.col_shift * (row - L.row_start) / L.row_shift - draw_block_function(row, col_float, L) - end -end - -local function draw_ending(L) - -- Apply correction to avoid jump before stop animating - local correction = config.distance_stop_animating + (1 - config.distance_stop_animating) * L.shift - local row_shift = L.row_shift * correction - local col_shift = L.col_shift * correction - - -- Apply factors to reduce size of diagonal partial blocks - row_shift = row_shift * (1 - math.abs(col_shift)) - col_shift = col_shift * (1 - math.abs(row_shift)) - - draw_vertically_shifted_block(L.row_end_rounded - row_shift, L.col_end_rounded, L) - draw_horizontally_shifted_block(L.row_end_rounded, L.col_end_rounded - col_shift, L) -end - -M.draw_line = function(row_start, col_start, row_end, col_end, end_reached) - -- logging.debug("Drawing line from (" .. row_start .. ", " .. col_start .. ") to (" .. row_end .. ", " .. col_end .. ")") - - local L = { - row_start = row_start, - col_start = col_start, - row_end = row_end, - col_end = col_end, - row_start_rounded = round(row_start), - col_start_rounded = round(col_start), - row_end_rounded = round(row_end), - col_end_rounded = round(col_end), - row_shift = row_end - row_start, - col_shift = col_end - col_start, - end_reached = end_reached, - } - - L.top = math.min(L.row_start_rounded, L.row_end_rounded) - L.bottom = math.max(L.row_start_rounded, L.row_end_rounded) - L.left = math.min(L.col_start_rounded, L.col_end_rounded) - L.right = math.max(L.col_start_rounded, L.col_end_rounded) - L.row_direction = L.row_shift >= 0 and 1 or -1 - L.col_direction = L.col_shift >= 0 and 1 or -1 - L.slope = L.row_shift / L.col_shift - L.slope_abs = math.abs(L.slope) - L.shift = math.sqrt(L.row_shift ^ 2 + L.col_shift ^ 2) - L.thickness = math.min(1 / L.shift, 1) ^ config.thickness_reduction_exponent - L.thickness = math.max(L.thickness, config.minimum_thickness) - - if L.slope ~= L.slope then - M.draw_character(L.row_end_rounded, L.col_end_rounded, "█", color.get_hl_group(), L) - return - end - - if L.end_reached and L.shift < 1 then - draw_ending(L) - return - end - - if L.slope_abs <= config.max_slope_horizontal then - -- logging.debug("Drawing horizontal-ish line") - -- if math.abs(L.row_shift) > 1 then - -- -- Avoid bulging on thin lines - -- L.thickness = math.max(L.thickness, 1) - -- end - draw_horizontal_ish_line(L, draw_vertically_shifted_block) - return - end - - if L.slope_abs >= config.min_slope_vertical then - -- logging.debug("Drawing vertical-ish line") - -- if math.abs(L.col_shift) > 1 then - -- -- Avoid bulging on thin lines - -- L.thickness = math.max(L.thickness, 1) - -- end - draw_vertical_ish_line(L, draw_horizontally_shifted_block) - return - end - - if L.slope_abs <= 1 then - -- logging.debug("Drawing diagonal-horizontal line") - draw_horizontal_ish_line(L, draw_diagonal_horizontal_block) - return - end - - -- logging.debug("Drawing diagonal-vertical line") - draw_vertical_ish_line(L, draw_diagonal_vertical_block) + draw_partial_block(row, col, character_list, character_index, hl_group) end M.draw_quad = function(corners, target_position) @@ -515,7 +281,6 @@ M.draw_quad = function(corners, target_position) local top = math.floor(math.min(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) local bottom = math.ceil(math.max(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) - 1 - local target_reached = false for row = top, bottom do local left = corners[4][2] + (row + 0.5 - corners[4][1]) / slopes[4] @@ -526,7 +291,6 @@ M.draw_quad = function(corners, target_position) for col = math.floor(left), math.ceil(right) do -- Check if on target if row == target_position[1] and col == target_position[2] then - target_reached = true goto continue end @@ -675,8 +439,6 @@ M.draw_quad = function(corners, target_position) ::continue:: end end - - return target_reached end return M From e65439c8a0ea6cc1385509075faba3f5ef389fd7 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Mon, 2 Dec 2024 00:11:36 +0100 Subject: [PATCH 12/20] docs: update readme with new dynamics options --- README.md | 3 +-- lua/smear_cursor/config.lua | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b7e36f5..08344d0 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,7 @@ As an example of further configuration, you can tune the smear dynamics to be sn ```lua opts = { -- Default Range stiffness = 0.8, -- 0.6 [0, 1] - trailing_stiffness = 0.6, -- 0.3 [0, 1] - trailing_exponent = 0, -- 0.1 >= 0 + trailing_stiffness = 0.5, -- 0.3 [0, 1] distance_stop_animating = 0.5, -- 0.1 > 0 hide_target_hack = false, -- true boolean }, diff --git a/lua/smear_cursor/config.lua b/lua/smear_cursor/config.lua index cf69a4b..b208486 100644 --- a/lua/smear_cursor/config.lua +++ b/lua/smear_cursor/config.lua @@ -23,13 +23,13 @@ M.time_interval = 17 -- milliseconds -- 0: no movement, 1: instantaneous M.stiffness = 0.6 --- How fast the smear's tail moves towards the head. +-- How fast the smear's tail moves towards the target. -- 0: no movement, 1: instantaneous M.trailing_stiffness = 0.3 -- How much the tail slows down when getting close to the head. -- 0: no slowdown, more: more slowdown -M.slowdown_exponent = 0.0 +M.slowdown_exponent = 0 -- Stop animating when the smear's tail is within this distance (in characters) from the target. M.distance_stop_animating = 0.1 @@ -41,7 +41,6 @@ M.min_slope_vertical = 2 M.color_levels = 16 -- Minimum 1 M.gamma = 2.2 -- For color blending M.diagonal_pixel_value_threshold = 0.25 -- 0.1: more pixels, 0.9: less pixels -M.diagonal_thickness_factor = 0.7 -- Put less than 1 to reduce diagonal smear fatness M.volume_reduction_exponent = 0.2 -- 0: no reduction, 1: full reduction M.minimum_volume_factor = 0.7 -- 0: no limit, 1: no reduction From 5a1f6ba6f5af6cdc903a2d597bc95e7f1ee6f626 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Mon, 2 Dec 2024 08:37:43 +0100 Subject: [PATCH 13/20] perf: precompute reused draw_quad quantities --- lua/smear_cursor/draw.lua | 136 +++++++++++++++++++++++--------------- 1 file changed, 83 insertions(+), 53 deletions(-) diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index 9732be5..6ade7ed 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -264,83 +264,113 @@ local function draw_horizontally_shifted_sub_block(row, col_left, col_right, sha draw_partial_block(row, col, character_list, character_index, hl_group) end -M.draw_quad = function(corners, target_position) - if target_position == nil then - target_position = { 0, 0 } - end +local function precompute_quad_geometry(corners) + local G = {} + + -- Bounding box + G.top = math.floor(math.min(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) + G.bottom = math.ceil(math.max(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) - 1 + G.left = math.floor(math.min(corners[1][2], corners[2][2], corners[3][2], corners[4][2])) + G.right = math.ceil(math.max(corners[1][2], corners[2][2], corners[3][2], corners[4][2])) - 1 - local slopes = {} + -- Slopes + G.slopes = {} for i = 1, 4 do local edge = { corners[i % 4 + 1][1] - corners[i][1], corners[i % 4 + 1][2] - corners[i][2], } - slopes[i] = edge[1] / edge[2] + G.slopes[i] = edge[1] / edge[2] end - local top = math.floor(math.min(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) - local bottom = math.ceil(math.max(corners[1][1], corners[2][1], corners[3][1], corners[4][1])) - 1 + G.top_horizontal = math.abs(G.slopes[1]) <= config.max_slope_horizontal + G.bottom_horizontal = math.abs(G.slopes[3]) <= config.max_slope_horizontal + G.left_vertical = math.abs(G.slopes[4]) >= config.min_slope_vertical + G.right_vertical = math.abs(G.slopes[2]) >= config.min_slope_vertical + + -- Intersections + -- Intersection of quad edge with centerline of cells + G.top_centerlines = {} + -- Lowest intersection of quad edge with lateral edges of cells + G.top_intersections = {} + G.bottom_centerlines = {} + G.bottom_intersections = {} + G.left_centerlines = {} + G.left_intersections = {} + G.right_centerlines = {} + G.right_intersections = {} + + for col = G.left, G.right do + G.top_centerlines[col] = corners[1][1] + (col + 0.5 - corners[1][2]) * G.slopes[1] + G.top_intersections[col] = G.top_centerlines[col] + 0.5 * math.abs(G.slopes[1]) + G.bottom_centerlines[col] = corners[3][1] + (col + 0.5 - corners[3][2]) * G.slopes[3] + G.bottom_intersections[col] = G.bottom_centerlines[col] - 0.5 * math.abs(G.slopes[3]) + end + + for row = G.top, G.bottom do + G.right_centerlines[row] = corners[2][2] + (row + 0.5 - corners[2][1]) / G.slopes[2] + G.right_intersections[row] = G.right_centerlines[row] - 0.5 / math.abs(G.slopes[2]) + G.left_centerlines[row] = corners[4][2] + (row + 0.5 - corners[4][1]) / G.slopes[4] + G.left_intersections[row] = G.left_centerlines[row] + 0.5 / math.abs(G.slopes[4]) + end - for row = top, bottom do - local left = corners[4][2] + (row + 0.5 - corners[4][1]) / slopes[4] - left = left - 0.5 / math.abs(slopes[4]) - local right = corners[2][2] + (row + 0.5 - corners[2][1]) / slopes[2] - right = right + 0.5 / math.abs(slopes[2]) + return G +end - for col = math.floor(left), math.ceil(right) do +M.draw_quad = function(corners, target_position) + if target_position == nil then + target_position = { 0, 0 } + end + + local G = precompute_quad_geometry(corners) + + for row = G.top, G.bottom do + local left = corners[4][2] + (row + 0.5 - corners[4][1]) / G.slopes[4] + left = left - 0.5 / math.abs(G.slopes[4]) + local right = corners[2][2] + (row + 0.5 - corners[2][1]) / G.slopes[2] + right = right + 0.5 / math.abs(G.slopes[2]) + + for col = math.max(G.left, math.floor(left)), math.min(G.right, math.ceil(right)) do -- Check if on target if row == target_position[1] and col == target_position[2] then goto continue end - -- Intersection of quad edge with centerline of cell - local top_centerline = corners[1][1] + (col + 0.5 - corners[1][2]) * slopes[1] - -- Lowest intersection of quad edge with lateral edges of cell - local top_intersection = top_centerline + 0.5 * math.abs(slopes[1]) - local right_centerline = corners[2][2] + (row + 0.5 - corners[2][1]) / slopes[2] - local right_intersection = right_centerline - 0.5 / math.abs(slopes[2]) - local bottom_centerline = corners[3][1] + (col + 0.5 - corners[3][2]) * slopes[3] - local bottom_intersection = bottom_centerline - 0.5 * math.abs(slopes[3]) - local left_centerline = corners[4][2] + (row + 0.5 - corners[4][1]) / slopes[4] - local left_intersection = left_centerline + 0.5 / math.abs(slopes[4]) - local is_vertically_shifted = false local vertical_shade = 1 local is_horizontally_shifted = false local horizontal_shade = 1 -- Check if vertically shifted block - local top_horizontal = math.abs(slopes[1]) <= config.max_slope_horizontal - local bottom_horizontal = math.abs(slopes[3]) <= config.max_slope_horizontal - local left_in = left_intersection > col - local left_vertical = math.abs(slopes[4]) >= config.min_slope_vertical - local right_in = right_intersection < col + 1 - local right_vertical = math.abs(slopes[2]) >= config.min_slope_vertical - if not (left_in and not left_vertical) and not (right_in and not right_vertical) then - local top_near = top_centerline > row - local bottom_near = bottom_centerline < row + 1 + local left_in = G.left_intersections[row] > col + local right_in = G.right_intersections[row] < col + 1 + if not (left_in and not G.left_vertical) and not (right_in and not G.right_vertical) then + local top_near = G.top_centerlines[col] > row + local bottom_near = G.bottom_centerlines[col] < row + 1 if - (top_near and top_horizontal and (not bottom_near or bottom_horizontal)) - or (bottom_near and bottom_horizontal and (not top_near or top_horizontal)) + (top_near and G.top_horizontal and (not bottom_near or G.bottom_horizontal)) + or (bottom_near and G.bottom_horizontal and (not top_near or G.top_horizontal)) then is_vertically_shifted = true - vertical_shade = math.min(row + 1, bottom_centerline) - math.max(row, top_centerline) + vertical_shade = math.min(row + 1, G.bottom_centerlines[col]) + - math.max(row, G.top_centerlines[col]) end end -- Check if horizontally shifted block - local top_in = top_intersection > row - local bottom_in = bottom_intersection < row + 1 - if not (top_in and not top_horizontal) and not (bottom_in and not bottom_horizontal) then - local left_near = left_centerline > col - local right_near = right_centerline < col + 1 + local top_in = G.top_intersections[col] > row + local bottom_in = G.bottom_intersections[col] < row + 1 + if not (top_in and not G.top_horizontal) and not (bottom_in and not G.bottom_horizontal) then + local left_near = G.left_centerlines[row] > col + local right_near = G.right_centerlines[row] < col + 1 if - (left_near and left_vertical and (not right_near or right_vertical)) - or (right_near and right_vertical and (not left_near or left_vertical)) + (left_near and G.left_vertical and (not right_near or G.right_vertical)) + or (right_near and G.right_vertical and (not left_near or G.left_vertical)) then is_horizontally_shifted = true - horizontal_shade = math.min(col + 1, right_centerline) - math.max(col, left_centerline) + horizontal_shade = math.min(col + 1, G.right_centerlines[row]) + - math.max(col, G.left_centerlines[row]) end end @@ -357,8 +387,8 @@ M.draw_quad = function(corners, target_position) if is_vertically_shifted and horizontal_shade > 0 then draw_vertically_shifted_sub_block( - math.max(row, top_centerline), - math.min(row + 1, bottom_centerline), + math.max(row, G.top_centerlines[col]), + math.min(row + 1, G.bottom_centerlines[col]), col, horizontal_shade ) @@ -368,8 +398,8 @@ M.draw_quad = function(corners, target_position) if is_horizontally_shifted and vertical_shade > 0 then draw_horizontally_shifted_sub_block( row, - math.max(col, left_centerline), - math.min(col + 1, right_centerline), + math.max(col, G.left_centerlines[row]), + math.min(col + 1, G.right_centerlines[row]), vertical_shade ) goto continue @@ -386,7 +416,7 @@ M.draw_quad = function(corners, target_position) local shift = (i == 1) and -0.25 or 0.25 -- Intersection with top quad edge - row_float = top_centerline + shift * slopes[1] + row_float = G.top_centerlines[col] + shift * G.slopes[1] row_float = 2 * (row_float - row) matrix_index = math.floor(row_float) + 1 for index = 1, math.min(2, matrix_index - 1) do @@ -398,7 +428,7 @@ M.draw_quad = function(corners, target_position) end -- Intersection with right quad edge - col_float = right_centerline + shift / slopes[2] + col_float = G.right_centerlines[row] + shift / G.slopes[2] col_float = 2 * (col_float - col) matrix_index = math.floor(col_float) + 1 for index = math.max(1, matrix_index + 1), 2 do @@ -410,7 +440,7 @@ M.draw_quad = function(corners, target_position) end -- Intersection with bottom quad edge - row_float = bottom_centerline + shift * slopes[3] + row_float = G.bottom_centerlines[col] + shift * G.slopes[3] row_float = 2 * (row_float - row) matrix_index = math.floor(row_float) + 1 for index = math.max(1, matrix_index + 1), 2 do @@ -422,7 +452,7 @@ M.draw_quad = function(corners, target_position) end -- Intersection with left quad edge - col_float = left_centerline + shift / slopes[4] + col_float = G.left_centerlines[row] + shift / G.slopes[4] col_float = 2 * (col_float - col) matrix_index = math.floor(col_float) + 1 for index = 1, math.min(2, matrix_index - 1) do From 0ec397a552ca1d629dcd67e6d73bb56f53b67fe2 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Mon, 2 Dec 2024 14:40:39 +0100 Subject: [PATCH 14/20] perf: more precomputed quantities for draw_quad --- lua/smear_cursor/draw.lua | 59 ++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index 6ade7ed..a20bac4 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -293,26 +293,47 @@ local function precompute_quad_geometry(corners) -- Intersection of quad edge with centerline of cells G.top_centerlines = {} -- Lowest intersection of quad edge with lateral edges of cells - G.top_intersections = {} + G.top_edges = {} + -- Intersection of quad edge with lines at 0.25 and 0.75 + G.top_fractions = {} G.bottom_centerlines = {} - G.bottom_intersections = {} + G.bottom_edges = {} + G.bottom_fractions = {} G.left_centerlines = {} - G.left_intersections = {} + G.left_edges = {} + G.left_fractions = {} G.right_centerlines = {} - G.right_intersections = {} + G.right_edges = {} + G.right_fractions = {} for col = G.left, G.right do G.top_centerlines[col] = corners[1][1] + (col + 0.5 - corners[1][2]) * G.slopes[1] - G.top_intersections[col] = G.top_centerlines[col] + 0.5 * math.abs(G.slopes[1]) + G.top_edges[col] = G.top_centerlines[col] + 0.5 * math.abs(G.slopes[1]) + G.top_fractions[col] = {} G.bottom_centerlines[col] = corners[3][1] + (col + 0.5 - corners[3][2]) * G.slopes[3] - G.bottom_intersections[col] = G.bottom_centerlines[col] - 0.5 * math.abs(G.slopes[3]) + G.bottom_edges[col] = G.bottom_centerlines[col] - 0.5 * math.abs(G.slopes[3]) + G.bottom_fractions[col] = {} + + for i = 1, 2 do + local shift = (i == 1) and -0.25 or 0.25 + G.top_fractions[col][i] = G.top_centerlines[col] + shift * G.slopes[1] + G.bottom_fractions[col][i] = G.bottom_centerlines[col] + shift * G.slopes[3] + end end for row = G.top, G.bottom do G.right_centerlines[row] = corners[2][2] + (row + 0.5 - corners[2][1]) / G.slopes[2] - G.right_intersections[row] = G.right_centerlines[row] - 0.5 / math.abs(G.slopes[2]) + G.right_edges[row] = G.right_centerlines[row] - 0.5 / math.abs(G.slopes[2]) + G.right_fractions[row] = {} G.left_centerlines[row] = corners[4][2] + (row + 0.5 - corners[4][1]) / G.slopes[4] - G.left_intersections[row] = G.left_centerlines[row] + 0.5 / math.abs(G.slopes[4]) + G.left_edges[row] = G.left_centerlines[row] + 0.5 / math.abs(G.slopes[4]) + G.left_fractions[row] = {} + + for i = 1, 2 do + local shift = (i == 1) and -0.25 or 0.25 + G.right_fractions[row][i] = G.right_centerlines[row] + shift / G.slopes[2] + G.left_fractions[row][i] = G.left_centerlines[row] + shift / G.slopes[4] + end end return G @@ -343,8 +364,8 @@ M.draw_quad = function(corners, target_position) local horizontal_shade = 1 -- Check if vertically shifted block - local left_in = G.left_intersections[row] > col - local right_in = G.right_intersections[row] < col + 1 + local left_in = G.left_edges[row] > col + local right_in = G.right_edges[row] < col + 1 if not (left_in and not G.left_vertical) and not (right_in and not G.right_vertical) then local top_near = G.top_centerlines[col] > row local bottom_near = G.bottom_centerlines[col] < row + 1 @@ -359,8 +380,8 @@ M.draw_quad = function(corners, target_position) end -- Check if horizontally shifted block - local top_in = G.top_intersections[col] > row - local bottom_in = G.bottom_intersections[col] < row + 1 + local top_in = G.top_edges[col] > row + local bottom_in = G.bottom_edges[col] < row + 1 if not (top_in and not G.top_horizontal) and not (bottom_in and not G.bottom_horizontal) then local left_near = G.left_centerlines[row] > col local right_near = G.right_centerlines[row] < col + 1 @@ -413,11 +434,8 @@ M.draw_quad = function(corners, target_position) } for i = 1, 2 do - local shift = (i == 1) and -0.25 or 0.25 - -- Intersection with top quad edge - row_float = G.top_centerlines[col] + shift * G.slopes[1] - row_float = 2 * (row_float - row) + row_float = 2 * (G.top_fractions[col][i] - row) matrix_index = math.floor(row_float) + 1 for index = 1, math.min(2, matrix_index - 1) do matrix[index][i] = 0 @@ -428,8 +446,7 @@ M.draw_quad = function(corners, target_position) end -- Intersection with right quad edge - col_float = G.right_centerlines[row] + shift / G.slopes[2] - col_float = 2 * (col_float - col) + col_float = 2 * (G.right_fractions[row][i] - col) matrix_index = math.floor(col_float) + 1 for index = math.max(1, matrix_index + 1), 2 do matrix[i][index] = 0 @@ -440,8 +457,7 @@ M.draw_quad = function(corners, target_position) end -- Intersection with bottom quad edge - row_float = G.bottom_centerlines[col] + shift * G.slopes[3] - row_float = 2 * (row_float - row) + row_float = 2 * (G.bottom_fractions[col][i] - row) matrix_index = math.floor(row_float) + 1 for index = math.max(1, matrix_index + 1), 2 do matrix[index][i] = 0 @@ -452,8 +468,7 @@ M.draw_quad = function(corners, target_position) end -- Intersection with left quad edge - col_float = G.left_centerlines[row] + shift / G.slopes[4] - col_float = 2 * (col_float - col) + col_float = 2 * (G.left_fractions[row][i] - col) matrix_index = math.floor(col_float) + 1 for index = 1, math.min(2, matrix_index - 1) do matrix[i][index] = 0 From 9ee4ab816c606b27f70ede11d1c32fb8ef19d165 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Mon, 2 Dec 2024 20:06:45 +0100 Subject: [PATCH 15/20] fix: too early stopping of drawing hack target --- lua/smear_cursor/animation.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lua/smear_cursor/animation.lua b/lua/smear_cursor/animation.lua index d3899ea..9209e21 100644 --- a/lua/smear_cursor/animation.lua +++ b/lua/smear_cursor/animation.lua @@ -85,14 +85,14 @@ local function animate() local shrunk_corners = shrink_volume(current_corners) draw.draw_quad(shrunk_corners, target_position) local target_reached = ( - math.floor(current_corners[1][1]) == target_position[1] - and math.floor(current_corners[1][2]) == target_position[2] + math.floor(shrunk_corners[1][1]) == target_position[1] + and math.floor(shrunk_corners[1][2]) == target_position[2] ) - or (math.floor(current_corners[2][1]) == target_position[1] and math.ceil(current_corners[2][2]) - 1 == target_position[2]) - or (math.ceil(current_corners[3][1]) - 1 == target_position[1] and math.ceil(current_corners[3][2]) - 1 == target_position[2]) + or (math.floor(shrunk_corners[2][1]) == target_position[1] and math.ceil(shrunk_corners[2][2]) - 1 == target_position[2]) + or (math.ceil(shrunk_corners[3][1]) - 1 == target_position[1] and math.ceil(shrunk_corners[3][2]) - 1 == target_position[2]) or ( - math.ceil(current_corners[4][1]) - 1 == target_position[1] - and math.floor(current_corners[4][2]) == target_position[2] + math.ceil(shrunk_corners[4][1]) - 1 == target_position[1] + and math.floor(shrunk_corners[4][2]) == target_position[2] ) if not target_reached and config.hide_target_hack then draw.draw_character(target_position[1], target_position[2], " ", color.get_hl_group({ inverted = true })) From 0ab8ab8cbd0873ab20553ac8df901d4a0dd4ccfa Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Mon, 2 Dec 2024 23:26:41 +0100 Subject: [PATCH 16/20] feat: are more parameters to quad draw --- lua/smear_cursor/config.lua | 4 +++- lua/smear_cursor/draw.lua | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lua/smear_cursor/config.lua b/lua/smear_cursor/config.lua index b208486..c500e2b 100644 --- a/lua/smear_cursor/config.lua +++ b/lua/smear_cursor/config.lua @@ -40,7 +40,9 @@ M.min_slope_vertical = 2 M.color_levels = 16 -- Minimum 1 M.gamma = 2.2 -- For color blending -M.diagonal_pixel_value_threshold = 0.25 -- 0.1: more pixels, 0.9: less pixels +M.max_shade_no_matrix = 0.9 -- 0: more overhangs, 1: more matrices +M.matrix_pixel_threshold = 0.2 -- 0: all pixels, 1: no pixel +M.matrix_pixel_min_factor = 0.5 -- 0: all pixels, 1: no pixel M.volume_reduction_exponent = 0.2 -- 0: no reduction, 1: full reduction M.minimum_volume_factor = 0.7 -- 0: no limit, 1: no reduction diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index a20bac4..5955886 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -135,8 +135,11 @@ local function draw_partial_block(row, col, character_list, character_index, hl_ end local function draw_matrix_character(row, col, matrix) - local threshold = config.diagonal_pixel_value_threshold - * math.max(matrix[1][1], matrix[1][2], matrix[2][1], matrix[2][2]) + local max = math.max(matrix[1][1], matrix[1][2], matrix[2][1], matrix[2][2]) + if max < config.matrix_pixel_threshold then + return + end + local threshold = max * config.matrix_pixel_min_factor local bit_1 = (matrix[1][1] > threshold) and 1 or 0 local bit_2 = (matrix[1][2] > threshold) and 1 or 0 local bit_3 = (matrix[2][1] > threshold) and 1 or 0 @@ -397,7 +400,7 @@ M.draw_quad = function(corners, target_position) -- Draw shifted block if is_vertically_shifted and is_horizontally_shifted then - if vertical_shade < 0.75 and horizontal_shade < 0.75 then + if vertical_shade < config.max_shade_no_matrix and horizontal_shade < config.max_shade_no_matrix then goto continue elseif vertical_shade < horizontal_shade then is_horizontally_shifted = false From 22ae2089a3db070e557178c7db019127f0485854 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Mon, 2 Dec 2024 23:45:45 +0100 Subject: [PATCH 17/20] feat: tune smear parameters --- README.md | 2 +- lua/smear_cursor/config.lua | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 08344d0..fbe2a65 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ As an example of further configuration, you can tune the smear dynamics to be sn ```lua opts = { -- Default Range stiffness = 0.8, -- 0.6 [0, 1] - trailing_stiffness = 0.5, -- 0.3 [0, 1] + trailing_stiffness = 0.5, -- 0.25 [0, 1] distance_stop_animating = 0.5, -- 0.1 > 0 hide_target_hack = false, -- true boolean }, diff --git a/lua/smear_cursor/config.lua b/lua/smear_cursor/config.lua index c500e2b..a137a6e 100644 --- a/lua/smear_cursor/config.lua +++ b/lua/smear_cursor/config.lua @@ -25,7 +25,7 @@ M.stiffness = 0.6 -- How fast the smear's tail moves towards the target. -- 0: no movement, 1: instantaneous -M.trailing_stiffness = 0.3 +M.trailing_stiffness = 0.25 -- How much the tail slows down when getting close to the head. -- 0: no slowdown, more: more slowdown @@ -41,10 +41,10 @@ M.min_slope_vertical = 2 M.color_levels = 16 -- Minimum 1 M.gamma = 2.2 -- For color blending M.max_shade_no_matrix = 0.9 -- 0: more overhangs, 1: more matrices -M.matrix_pixel_threshold = 0.2 -- 0: all pixels, 1: no pixel +M.matrix_pixel_threshold = 0.5 -- 0: all pixels, 1: no pixel M.matrix_pixel_min_factor = 0.5 -- 0: all pixels, 1: no pixel -M.volume_reduction_exponent = 0.2 -- 0: no reduction, 1: full reduction -M.minimum_volume_factor = 0.7 -- 0: no limit, 1: no reduction +M.volume_reduction_exponent = 0.3 -- 0: no reduction, 1: full reduction +M.minimum_volume_factor = 0.5 -- 0: no limit, 1: no reduction -- For debugging --------------------------------------------------------------- From 22a65c7c1542e7a37594e65e92bc79ea3a57d7f1 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Tue, 3 Dec 2024 09:34:32 +0100 Subject: [PATCH 18/20] fix: reduce hidden target flicker --- lua/smear_cursor/animation.lua | 48 ++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/lua/smear_cursor/animation.lua b/lua/smear_cursor/animation.lua index 9209e21..f91fcd5 100644 --- a/lua/smear_cursor/animation.lua +++ b/lua/smear_cursor/animation.lua @@ -72,7 +72,6 @@ end local function animate() animating = true update() - draw.clear() local max_distance = 0 for i = 1, 4 do @@ -81,27 +80,38 @@ local function animate() ) max_distance = math.max(max_distance, distance) end - if max_distance > config.distance_stop_animating then - local shrunk_corners = shrink_volume(current_corners) - draw.draw_quad(shrunk_corners, target_position) - local target_reached = ( - math.floor(shrunk_corners[1][1]) == target_position[1] - and math.floor(shrunk_corners[1][2]) == target_position[2] - ) - or (math.floor(shrunk_corners[2][1]) == target_position[1] and math.ceil(shrunk_corners[2][2]) - 1 == target_position[2]) - or (math.ceil(shrunk_corners[3][1]) - 1 == target_position[1] and math.ceil(shrunk_corners[3][2]) - 1 == target_position[2]) - or ( - math.ceil(shrunk_corners[4][1]) - 1 == target_position[1] - and math.floor(shrunk_corners[4][2]) == target_position[2] - ) - if not target_reached and config.hide_target_hack then - draw.draw_character(target_position[1], target_position[2], " ", color.get_hl_group({ inverted = true })) - end - vim.defer_fn(animate, config.time_interval) - else + + draw.clear() + + if max_distance <= config.distance_stop_animating then animating = false set_corners(current_corners, target_position[1], target_position[2]) + return end + + local shrunk_corners = shrink_volume(current_corners) + -- stylua: ignore start + local target_reached = ( + math.floor(shrunk_corners[1][1]) == target_position[1] and + math.floor(shrunk_corners[1][2]) == target_position[2] + ) or ( + math.floor(shrunk_corners[2][1]) == target_position[1] and + math.ceil(shrunk_corners[2][2]) - 1 == target_position[2] + ) or ( + math.ceil(shrunk_corners[3][1]) - 1 == target_position[1] and + math.ceil(shrunk_corners[3][2]) - 1 == target_position[2] + ) or ( + math.ceil(shrunk_corners[4][1]) - 1 == target_position[1] and + math.floor(shrunk_corners[4][2]) == target_position[2] + ) + -- stylua: ignore end + + if not target_reached and config.hide_target_hack then + draw.draw_character(target_position[1], target_position[2], " ", color.get_hl_group({ inverted = true })) + end + + draw.draw_quad(shrunk_corners, target_position) + vim.defer_fn(animate, config.time_interval) end local function set_stiffnesses(head_stiffness, trailing_stiffness) From b4e899390a2362033c583c9c58564caa922b3dab Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Tue, 3 Dec 2024 12:10:28 +0100 Subject: [PATCH 19/20] fix: always remove extmarks to prevent flickering --- lua/smear_cursor/config.lua | 2 +- lua/smear_cursor/draw.lua | 5 +++-- tests/test_draw_quad.lua | 12 ++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lua/smear_cursor/config.lua b/lua/smear_cursor/config.lua index a137a6e..24163ea 100644 --- a/lua/smear_cursor/config.lua +++ b/lua/smear_cursor/config.lua @@ -40,7 +40,7 @@ M.min_slope_vertical = 2 M.color_levels = 16 -- Minimum 1 M.gamma = 2.2 -- For color blending -M.max_shade_no_matrix = 0.9 -- 0: more overhangs, 1: more matrices +M.max_shade_no_matrix = 0.75 -- 0: more overhangs, 1: more matrices M.matrix_pixel_threshold = 0.5 -- 0: all pixels, 1: no pixel M.matrix_pixel_min_factor = 0.5 -- 0: all pixels, 1: no pixel M.volume_reduction_exponent = 0.3 -- 0: no reduction, 1: full reduction diff --git a/lua/smear_cursor/draw.lua b/lua/smear_cursor/draw.lua index 5955886..fb887e2 100644 --- a/lua/smear_cursor/draw.lua +++ b/lua/smear_cursor/draw.lua @@ -116,10 +116,10 @@ M.clear = function() local wb = tab_windows.windows[i] if wb and vim.api.nvim_win_is_valid(wb.window_id) then + vim.api.nvim_buf_del_extmark(wb.buffer_id, cursor_namespace, extmark_id) if can_hide then vim.api.nvim_win_set_config(wb.window_id, { hide = true }) else - vim.api.nvim_buf_del_extmark(wb.buffer_id, cursor_namespace, extmark_id) vim.api.nvim_win_set_config(wb.window_id, { relative = "editor", row = 0, col = 0 }) end end @@ -401,7 +401,8 @@ M.draw_quad = function(corners, target_position) -- Draw shifted block if is_vertically_shifted and is_horizontally_shifted then if vertical_shade < config.max_shade_no_matrix and horizontal_shade < config.max_shade_no_matrix then - goto continue + is_horizontally_shifted = false + is_vertically_shifted = false elseif vertical_shade < horizontal_shade then is_horizontally_shifted = false else diff --git a/tests/test_draw_quad.lua b/tests/test_draw_quad.lua index d57d21f..df8edbd 100644 --- a/tests/test_draw_quad.lua +++ b/tests/test_draw_quad.lua @@ -102,6 +102,18 @@ draw.draw_quad({ { row + 5, col + 4 }, }) +-- Degenerate quads (aligned points) + +row = 14 +col = 23 + +draw.draw_quad({ + { row, col }, + { row - 1, col + 2 }, + { row + 3, col + 2 }, + { row + 3, col - 1 }, +}) + -- Lines row = 23 From d7cd64272c9cdd698fc41381f5d11570c2517824 Mon Sep 17 00:00:00 2001 From: Son Pham-Ba Date: Tue, 3 Dec 2024 23:51:16 +0100 Subject: [PATCH 20/20] feat: more or less lagging intermediate points --- README.md | 13 +++++++++++++ lua/smear_cursor/animation.lua | 4 ++-- lua/smear_cursor/config.lua | 6 +++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fbe2a65..2894376 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,19 @@ As an example of further configuration, you can tune the smear dynamics to be sn }, ``` + +> [!WARNING] Fire Hazard +> Feel free to experiment with all the configuration options, but be aware that some combinations may cause your cursor to flicker or even **catch fire**. That can happen with the following settings: +> ```lua +> opts = { +> cursor_color = "#ff8800", +> stiffness = 0.6, +> trailing_stiffness = 0.1, +> trailing_exponent = 5, +> gamma = 1, +> } +> ``` + ### Transparent background Drawing the smear over a transparent background works better when using a font that supports legacy computing symbols, therefore setting the following option: diff --git a/lua/smear_cursor/animation.lua b/lua/smear_cursor/animation.lua index f91fcd5..975b8db 100644 --- a/lua/smear_cursor/animation.lua +++ b/lua/smear_cursor/animation.lua @@ -129,8 +129,8 @@ local function set_stiffnesses(head_stiffness, trailing_stiffness) end for i = 1, 4 do - local stiffness = head_stiffness - + (trailing_stiffness - head_stiffness) * (distances[i] - min_distance) / (max_distance - min_distance) + local x = (distances[i] - min_distance) / (max_distance - min_distance) + local stiffness = head_stiffness + (trailing_stiffness - head_stiffness) * x ^ config.trailing_exponent stiffnesses[i] = math.min(1, stiffness) end end diff --git a/lua/smear_cursor/config.lua b/lua/smear_cursor/config.lua index 24163ea..2ce786a 100644 --- a/lua/smear_cursor/config.lua +++ b/lua/smear_cursor/config.lua @@ -27,7 +27,11 @@ M.stiffness = 0.6 -- 0: no movement, 1: instantaneous M.trailing_stiffness = 0.25 --- How much the tail slows down when getting close to the head. +-- Controls if middle points are closer to the head or the tail. +-- < 1: closer to the tail, > 1: closer to the head +M.trailing_exponent = 2 + +-- How much the smear slows down when getting close to the target. -- 0: no slowdown, more: more slowdown M.slowdown_exponent = 0