From a1770a269ed409379fef53695726b17ef07a35c3 Mon Sep 17 00:00:00 2001 From: Ryan Bennitt Date: Wed, 16 Apr 2025 15:03:56 +0100 Subject: [PATCH 1/6] Add n-point star to design shapes --- internal/design/shapes.lua | 147 +++++++++++++++++++++++++++++++++++-- 1 file changed, 141 insertions(+), 6 deletions(-) diff --git a/internal/design/shapes.lua b/internal/design/shapes.lua index 5199aa1d10..722cd66aae 100644 --- a/internal/design/shapes.lua +++ b/internal/design/shapes.lua @@ -402,7 +402,9 @@ function Diag:has_point(x, y) end end -Line = defclass(Line, Shape) +LineDrawer = defclass(LineDrawer, Shape) + +Line = defclass(Line, LineDrawer) Line.ATTRS { name = "Line", extra_points = { { label = "Curve Point" }, { label = "Second Curve Point" } }, @@ -431,18 +433,19 @@ function Line:init() } end -function Line:plot_bresenham(x0, y0, x1, y1, thickness) +function LineDrawer:plot_bresenham(x0, y0, x1, y1, thickness) local dx = math.abs(x1 - x0) local dy = math.abs(y1 - y0) local sx = x0 < x1 and 1 or -1 local sy = y0 < y1 and 1 or -1 - local err = dx - dy local e2, x, y for i = 0, thickness - 1 do x = x0 y = y0 + i - while true do + local err = dx - dy + local p = math.max(dx, dy) + while p >= 0 do for j = -math.floor(thickness / 2), math.ceil(thickness / 2) - 1 do if not self.arr[x + j] then self.arr[x + j] = {} end if not self.arr[x + j][y] then @@ -451,7 +454,7 @@ function Line:plot_bresenham(x0, y0, x1, y1, thickness) end end - if x == x1 and y == y1 + i then + if sx * x >= sx * x1 and sy * y >= sy * (y1 + i) then break end @@ -466,6 +469,7 @@ function Line:plot_bresenham(x0, y0, x1, y1, thickness) err = err + dx y = y + sy end + p = p - 1 end end @@ -684,7 +688,138 @@ function FreeForm:point_in_polygon(x, y) return inside end +Star = defclass(Star, LineDrawer) +Star.ATTRS { + name = "Star", + texture_offset = 5, + button_chars = util.make_ascii_button(9, 248), + extra_points = { { label = "Main Axis" } } +} + +function Star:init() + self.options = { + hollow = { + name = "Hollow", + type = "bool", + value = false, + key = "CUSTOM_H", + }, + thickness = { + name = "Line thickness", + type = "plusminus", + value = 1, + enabled = { "hollow", true }, + min = 1, + max = function(shape) if not shape.height or not shape.width then + return nil + else + return math.ceil(math.min(shape.height, shape.width) / 2) + + end + end, + keys = { "CUSTOM_T", "CUSTOM_SHIFT_T" }, + }, + total_points = { + name = "Total points", + type = "plusminus", + value = 5, + min = 3, + max = 100, + keys = { "CUSTOM_B", "CUSTOM_SHIFT_B" }, + }, + next_point_offset = { + name = "Next point offset", + type = "plusminus", + value = 2, + min = 1, + max = 99, + keys = { "CUSTOM_N", "CUSTOM_SHIFT_N" }, + }, + } +end + +function Star:has_point(x, y) + if 1 < ((x-self.center.x) / self.center.x) ^ 2 + ((y-self.center.y) / self.center.y) ^ 2 then return false end + + local inside = 0 + for l = 1, self.options.total_points.value do + if x * self.lines[l].slope.x - self.lines[l].intercept.x < y * self.lines[l].slope.y - self.lines[l].intercept.y then + inside = inside + 1 + else + inside = inside - 1 + end + end + return self.threshold > 0 and inside > self.threshold or inside < self.threshold +end + +function vmagnitude(point) + return math.sqrt(point.x * point.x + point.y * point.y) +end + +function vnormalize(point) + local magnitude = vmagnitude(point) + return { x = point.x / magnitude, y = point.y / magnitude } +end + +function add_offset(coord, offset) + return math.tointeger(coord + (offset > 0 and math.floor(offset+0.5) or math.ceil(offset-0.5))) +end + +function Star:update(points, extra_points) + self.num_tiles = 0 + self.points = copyall(points) + self.arr = {} + if #points < self.min_points then return end + self.threshold = self.options.total_points.value - 2 * self.options.next_point_offset.value + if self.threshold < 0 then return end + local top_left, bot_right = self:get_point_dims() + self.height = bot_right.y - top_left.y + self.width = bot_right.x - top_left.x + if self.height == 1 or self.width == 1 then return end + self.center = { x = self.width * 0.5, y = self.height * 0.5 } + local axes = {} + + axes[1] = (#extra_points > 0) and { x = extra_points[1].x - self.center.x - top_left.x, y = extra_points[1].y - self.center.y - top_left.y } or { x = 0, y = -self.center.y } + if vmagnitude(axes[1]) < 0.5 then axes[1].y = -self.center.y end + axes[1] = vnormalize(axes[1]) + + for a = 2, self.options.total_points.value do + local angle = math.pi * (a - 1.0) * 2.0 / self.options.total_points.value + axes[a] = { x = math.cos(angle) * axes[1].x - math.sin(angle) * axes[1].y, y = math.sin(angle) * axes[1].x + math.cos(angle) * axes[1].y } + end + + local thickness = 1 + if self.options.hollow.value then + thickness = self.options.thickness.value + end + + self.lines = {} + for l = 1, self.options.total_points.value do + local p1 = { x = self.center.x + axes[l].x * self.width * 0.5, y = self.center.y + axes[l].y * self.height * 0.5 } + local next_axis = axes[(l-1+self.options.next_point_offset.value) % self.options.total_points.value + 1] + local p2 = { x = self.center.x + next_axis.x * self.width * 0.5, y = self.center.y + next_axis.y * self.height * 0.5 } + self.lines[l] = { slope = { x = p2.y - p1.y, y = p2.x - p1.x }, intercept = { x = (p2.y - p1.y) * p1.x, y = (p2.x - p1.x) * p1.y } } + self:plot_bresenham(add_offset(top_left.x, p1.x), add_offset(top_left.y, p1.y), add_offset(top_left.x, p2.x), add_offset(top_left.y, p2.y), thickness) + self:plot_bresenham(add_offset(top_left.x, p2.x), add_offset(top_left.y, p2.y), add_offset(top_left.x, p1.x), add_offset(top_left.y, p1.y), thickness) + end + + if not self.options.hollow.value then + for x = top_left.x, bot_right.x do + if not self.arr[x] then self.arr[x] = {} end + for y = top_left.y, bot_right.y do + local value = self:has_point(x - top_left.x, y - top_left.y) + if self.invert then + self.arr[x][y] = not self.arr[x][y] and not value + else + self.arr[x][y] = self.arr[x][y] or value + end + + self.num_tiles = self.num_tiles + (self.arr[x][y] and 1 or 0) + end + end + end +end -- module users can get shapes through this global, shape option values -- persist in these as long as the module is loaded -- idk enough lua to know if this is okay to do or not -all_shapes = { Rectangle {}, Ellipse {}, Rows {}, Diag {}, Line {}, FreeForm {} } +all_shapes = { Rectangle {}, Ellipse {}, Rows {}, Diag {}, Line {}, FreeForm {}, Star {} } From 0719f704aa2810d7f0d0cf99a4712bfdf5209929 Mon Sep 17 00:00:00 2001 From: Ryan Bennitt Date: Wed, 16 Apr 2025 16:53:01 +0100 Subject: [PATCH 2/6] Allow next point offset to increase indefinitely with !!fun!! results --- internal/design/shapes.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/design/shapes.lua b/internal/design/shapes.lua index 722cd66aae..652e94a7a5 100644 --- a/internal/design/shapes.lua +++ b/internal/design/shapes.lua @@ -732,7 +732,7 @@ function Star:init() type = "plusminus", value = 2, min = 1, - max = 99, + max = 100, keys = { "CUSTOM_N", "CUSTOM_SHIFT_N" }, }, } @@ -771,7 +771,6 @@ function Star:update(points, extra_points) self.arr = {} if #points < self.min_points then return end self.threshold = self.options.total_points.value - 2 * self.options.next_point_offset.value - if self.threshold < 0 then return end local top_left, bot_right = self:get_point_dims() self.height = bot_right.y - top_left.y self.width = bot_right.x - top_left.x From 9fd2bc7273cde94fdb4f302ef5964292ed6e90c4 Mon Sep 17 00:00:00 2001 From: 83N170 <83N170@mail.com> Date: Wed, 16 Apr 2025 20:36:17 +0100 Subject: [PATCH 3/6] Fix invert of hollow star --- internal/design/shapes.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/design/shapes.lua b/internal/design/shapes.lua index 652e94a7a5..8d673ee108 100644 --- a/internal/design/shapes.lua +++ b/internal/design/shapes.lua @@ -762,7 +762,7 @@ function vnormalize(point) end function add_offset(coord, offset) - return math.tointeger(coord + (offset > 0 and math.floor(offset+0.5) or math.ceil(offset-0.5))) + return coord + (offset > 0 and math.floor(offset+0.5) or math.ceil(offset-0.5)) end function Star:update(points, extra_points) @@ -802,15 +802,15 @@ function Star:update(points, extra_points) self:plot_bresenham(add_offset(top_left.x, p2.x), add_offset(top_left.y, p2.y), add_offset(top_left.x, p1.x), add_offset(top_left.y, p1.y), thickness) end - if not self.options.hollow.value then + if not self.options.hollow.value or self.invert then for x = top_left.x, bot_right.x do if not self.arr[x] then self.arr[x] = {} end for y = top_left.y, bot_right.y do - local value = self:has_point(x - top_left.x, y - top_left.y) + local value = self.arr[x][y] or (not self.options.hollow.value and self:has_point(x - top_left.x, y - top_left.y)) if self.invert then - self.arr[x][y] = not self.arr[x][y] and not value + self.arr[x][y] = not value else - self.arr[x][y] = self.arr[x][y] or value + self.arr[x][y] = value end self.num_tiles = self.num_tiles + (self.arr[x][y] and 1 or 0) From 771244cdcca7c322d71b4dbe910a01d395cff0df Mon Sep 17 00:00:00 2001 From: 83N170 <83N170@mail.com> Date: Fri, 18 Apr 2025 12:36:07 +0100 Subject: [PATCH 4/6] Use new star icon --- gui/design.lua | 6 +++--- internal/design/shapes.lua | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gui/design.lua b/gui/design.lua index 103ab59c8e..e4573bb5fd 100644 --- a/gui/design.lua +++ b/gui/design.lua @@ -344,14 +344,14 @@ function Design:init() chars=shape.button_chars, tileset=shape_tileset, tileset_offset=shape.texture_offset, - tileset_stride=24, + tileset_stride=28, }) table.insert(shape_button_specs_selected, { chars=shape.button_chars, pens=COLOR_YELLOW, tileset=shape_tileset, - tileset_offset=shape.texture_offset+(24*3), - tileset_stride=24, + tileset_offset=shape.texture_offset+(28*3), + tileset_stride=28, }) end diff --git a/internal/design/shapes.lua b/internal/design/shapes.lua index 8d673ee108..ab5a5142f6 100644 --- a/internal/design/shapes.lua +++ b/internal/design/shapes.lua @@ -691,8 +691,8 @@ end Star = defclass(Star, LineDrawer) Star.ATTRS { name = "Star", - texture_offset = 5, - button_chars = util.make_ascii_button(9, 248), + texture_offset = 25, + button_chars = util.make_ascii_button('*', '*'), extra_points = { { label = "Main Axis" } } } From 1e787155338283036da49fa40c19ac3af5f07e87 Mon Sep 17 00:00:00 2001 From: 83N170 <83N170@mail.com> Date: Fri, 18 Apr 2025 15:56:39 +0100 Subject: [PATCH 5/6] Make icon calculations clearer and tweak ascii icon --- gui/design.lua | 15 +++++++++++---- internal/design/shapes.lua | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/gui/design.lua b/gui/design.lua index e4573bb5fd..5cd3f1d824 100644 --- a/gui/design.lua +++ b/gui/design.lua @@ -336,7 +336,14 @@ function Design:init() table.insert(mode_button_specs_selected, mode_option.value.button_selected_spec) end - local shape_tileset = dfhack.textures.loadTileset('hack/data/art/design.png', 8, 12, true) + local DESIGN_ICONS_WIDTH = 224 -- Must match design.png image width + local DESIGN_ICONS_HEIGHT = 72 -- Must match design.png image height + local DESIGN_ICON_ROW_COUNT = 2 + local DESIGN_CHAR_WIDTH = 8 + local DESIGN_CHAR_HEIGHT = 12 + local shape_tileset = dfhack.textures.loadTileset('hack/data/art/design.png', DESIGN_CHAR_WIDTH, DESIGN_CHAR_HEIGHT, true) + local STRIDE = DESIGN_ICONS_WIDTH / DESIGN_CHAR_WIDTH + local CHARS_PER_ROW = DESIGN_ICONS_HEIGHT / (DESIGN_ICON_ROW_COUNT * DESIGN_CHAR_HEIGHT) local shape_options, shape_button_specs, shape_button_specs_selected = {}, {}, {} for _, shape in ipairs(shapes.all_shapes) do table.insert(shape_options, {label=shape.name, value=shape}) @@ -344,14 +351,14 @@ function Design:init() chars=shape.button_chars, tileset=shape_tileset, tileset_offset=shape.texture_offset, - tileset_stride=28, + tileset_stride=STRIDE, }) table.insert(shape_button_specs_selected, { chars=shape.button_chars, pens=COLOR_YELLOW, tileset=shape_tileset, - tileset_offset=shape.texture_offset+(28*3), - tileset_stride=28, + tileset_offset=shape.texture_offset+(STRIDE*CHARS_PER_ROW), + tileset_stride=STRIDE, }) end diff --git a/internal/design/shapes.lua b/internal/design/shapes.lua index ab5a5142f6..a9eb9897d8 100644 --- a/internal/design/shapes.lua +++ b/internal/design/shapes.lua @@ -692,7 +692,7 @@ Star = defclass(Star, LineDrawer) Star.ATTRS { name = "Star", texture_offset = 25, - button_chars = util.make_ascii_button('*', '*'), + button_chars = util.make_ascii_button('*', 15), extra_points = { { label = "Main Axis" } } } From 6b2e4e4d7423335ac73b6842d5d9db705d250773 Mon Sep 17 00:00:00 2001 From: 83N170 <83N170@mail.com> Date: Fri, 18 Apr 2025 16:03:26 +0100 Subject: [PATCH 6/6] Update changelog --- changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index bc4da721cd..b6a96e85ab 100644 --- a/changelog.txt +++ b/changelog.txt @@ -16,6 +16,8 @@ Template for new versions: ## New Features +- `gui/design`: add option to draw N-point stars, hollow or filled or inverted, and change the main axis to orient in any direction + ## Fixes ## Misc Improvements