Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 11 additions & 4 deletions gui/design.lua
Original file line number Diff line number Diff line change
Expand Up @@ -336,22 +336,29 @@ 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})
table.insert(shape_button_specs, {
chars=shape.button_chars,
tileset=shape_tileset,
tileset_offset=shape.texture_offset,
tileset_stride=24,
tileset_stride=STRIDE,
})
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+(STRIDE*CHARS_PER_ROW),
tileset_stride=STRIDE,
})
end

Expand Down
146 changes: 140 additions & 6 deletions internal/design/shapes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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" } },
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -684,7 +688,137 @@ function FreeForm:point_in_polygon(x, y)
return inside
end

Star = defclass(Star, LineDrawer)
Star.ATTRS {
name = "Star",
texture_offset = 25,
button_chars = util.make_ascii_button('*', 15),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice choice

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 = 100,
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 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
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 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.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 value
else
self.arr[x][y] = 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 {} }
Loading