mirror of
https://github.com/kepler155c/opus
synced 2024-12-29 10:00:26 +00:00
359 lines
12 KiB
Lua
359 lines
12 KiB
Lua
|
-------------------------------------------------------------------------------
|
||
|
--
|
||
|
-- tek.lib.region
|
||
|
-- Written by Timm S. Mueller <tmueller at schulze-mueller.de>
|
||
|
--
|
||
|
-- Copyright 2008 - 2016 by the authors and contributors:
|
||
|
--
|
||
|
-- * Timm S. Muller <tmueller at schulze-mueller.de>
|
||
|
-- * Franciska Schulze <fschulze at schulze-mueller.de>
|
||
|
-- * Tobias Schwinger <tschwinger at isonews2.com>
|
||
|
--
|
||
|
-- Permission is hereby granted, free of charge, to any person obtaining
|
||
|
-- a copy of this software and associated documentation files (the
|
||
|
-- "Software"), to deal in the Software without restriction, including
|
||
|
-- without limitation the rights to use, copy, modify, merge, publish,
|
||
|
-- distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
-- permit persons to whom the Software is furnished to do so, subject to
|
||
|
-- the following conditions:
|
||
|
--
|
||
|
-- The above copyright notice and this permission notice shall be
|
||
|
-- included in all copies or substantial portions of the Software.
|
||
|
--
|
||
|
-- === Disclaimer ===
|
||
|
--
|
||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||
|
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||
|
-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||
|
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||
|
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
--
|
||
|
-- OVERVIEW::
|
||
|
-- This library implements the management of regions, which are
|
||
|
-- collections of non-overlapping rectangles.
|
||
|
--
|
||
|
-- FUNCTIONS::
|
||
|
-- - Region:andRect() - ''And''s a rectangle to a region
|
||
|
-- - Region:andRegion() - ''And''s a region to a region
|
||
|
-- - Region:checkIntersect() - Checks if a rectangle intersects a region
|
||
|
-- - Region:forEach() - Calls a function for each rectangle in a region
|
||
|
-- - Region:get() - Get region's min/max extents
|
||
|
-- - Region.intersect() - Returns the intersection of two rectangles
|
||
|
-- - Region:isEmpty() - Checks if a Region is empty
|
||
|
-- - Region.new() - Creates a new Region
|
||
|
-- - Region:orRect() - ''Or''s a rectangle to a region
|
||
|
-- - Region:orRegion() - ''Or''s a region to a region
|
||
|
-- - Region:setRect() - Resets a region to the given rectangle
|
||
|
-- - Region:shift() - Displaces a region
|
||
|
-- - Region:subRect() - Subtracts a rectangle from a region
|
||
|
-- - Region:subRegion() - Subtracts a region from a region
|
||
|
-- - Region:xorRect() - ''Exclusive Or''s a rectangle to a region
|
||
|
--
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
local insert = table.insert
|
||
|
local ipairs = ipairs
|
||
|
local max = math.max
|
||
|
local min = math.min
|
||
|
local setmetatable = setmetatable
|
||
|
local unpack = unpack or table.unpack
|
||
|
|
||
|
local Region = { }
|
||
|
Region._VERSION = "Region 11.3"
|
||
|
|
||
|
Region.__index = Region
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- x0, y0, x1, y1 = Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4):
|
||
|
-- Returns the coordinates of a rectangle where a rectangle specified by
|
||
|
-- the coordinates s1, s2, s3, s4 overlaps with the rectangle specified
|
||
|
-- by the coordinates d1, d2, d3, d4. The return value is '''nil''' if
|
||
|
-- the rectangles do not overlap.
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4)
|
||
|
if s3 >= d1 and s1 <= d3 and s4 >= d2 and s2 <= d4 then
|
||
|
return max(s1, d1), max(s2, d2), min(s3, d3), min(s4, d4)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- insertrect: insert rect to table, merging with an existing one if possible
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
local function insertrect(d, s1, s2, s3, s4)
|
||
|
for i = 1, min(4, #d) do
|
||
|
local a = d[i]
|
||
|
local a1, a2, a3, a4 = a[1], a[2], a[3], a[4]
|
||
|
if a2 == s2 and a4 == s4 then
|
||
|
if a3 + 1 == s1 then
|
||
|
a[3] = s3
|
||
|
return
|
||
|
elseif a1 == s3 + 1 then
|
||
|
a[1] = s1
|
||
|
return
|
||
|
end
|
||
|
elseif a1 == s1 and a3 == s3 then
|
||
|
if a4 + 1 == s2 then
|
||
|
a[4] = s4
|
||
|
return
|
||
|
elseif a2 == s4 + 1 then
|
||
|
a[2] = s2
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
insert(d, 1, { s1, s2, s3, s4 })
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- cutrect: cut rect d into table of new rects, using rect s as a punch
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
local function cutrect(d1, d2, d3, d4, s1, s2, s3, s4)
|
||
|
if not Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4) then
|
||
|
return { { d1, d2, d3, d4 } }
|
||
|
end
|
||
|
local r = { }
|
||
|
if d1 < s1 then
|
||
|
insertrect(r, d1, d2, s1 - 1, d4)
|
||
|
d1 = s1
|
||
|
end
|
||
|
if d2 < s2 then
|
||
|
insertrect(r, d1, d2, d3, s2 - 1)
|
||
|
d2 = s2
|
||
|
end
|
||
|
if d3 > s3 then
|
||
|
insertrect(r, s3 + 1, d2, d3, d4)
|
||
|
d3 = s3
|
||
|
end
|
||
|
if d4 > s4 then
|
||
|
insertrect(r, d1, s4 + 1, d3, d4)
|
||
|
end
|
||
|
return r
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- cutregion: cut region d, using s as a punch
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
local function cutregion(d, s1, s2, s3, s4)
|
||
|
local r = { }
|
||
|
for _, dr in ipairs(d) do
|
||
|
local d1, d2, d3, d4 = dr[1], dr[2], dr[3], dr[4]
|
||
|
for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do
|
||
|
insertrect(r, t[1], t[2], t[3], t[4])
|
||
|
end
|
||
|
end
|
||
|
return r
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- region = Region.new(r1, r2, r3, r4): Creates a new region from the given
|
||
|
-- coordinates.
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region.new(r1, r2, r3, r4)
|
||
|
if r1 then
|
||
|
return setmetatable({ region = { { r1, r2, r3, r4 } } }, Region)
|
||
|
end
|
||
|
return setmetatable({ region = { } }, Region)
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- self = region:setRect(r1, r2, r3, r4): Resets an existing region
|
||
|
-- to the specified rectangle.
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:setRect(r1, r2, r3, r4)
|
||
|
self.region = { { r1, r2, r3, r4 } }
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- region:orRect(r1, r2, r3, r4): Logical ''or''s a rectangle to a region
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:orRect(s1, s2, s3, s4)
|
||
|
self.region = cutregion(self.region, s1, s2, s3, s4)
|
||
|
insertrect(self.region, s1, s2, s3, s4)
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- region:orRegion(region): Logical ''or''s another region to a region
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:orRegion(s)
|
||
|
for _, r in ipairs(s) do
|
||
|
self:orRect(r[1], r[2], r[3], r[4])
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- region:andRect(r1, r2, r3, r4): Logical ''and''s a rectange to a region
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:andRect(s1, s2, s3, s4)
|
||
|
local r = { }
|
||
|
for _, d in ipairs(self.region) do
|
||
|
local t1, t2, t3, t4 =
|
||
|
Region.intersect(d[1], d[2], d[3], d[4], s1, s2, s3, s4)
|
||
|
if t1 then
|
||
|
insertrect(r, t1, t2, t3, t4)
|
||
|
end
|
||
|
end
|
||
|
self.region = r
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- region:xorRect(r1, r2, r3, r4): Logical ''xor''s a rectange to a region
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:xorRect(s1, s2, s3, s4)
|
||
|
local r1 = { }
|
||
|
local r2 = { { s1, s2, s3, s4 } }
|
||
|
for _, d in ipairs(self.region) do
|
||
|
local d1, d2, d3, d4 = d[1], d[2], d[3], d[4]
|
||
|
for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do
|
||
|
insertrect(r1, t[1], t[2], t[3], t[4])
|
||
|
end
|
||
|
r2 = cutregion(r2, d1, d2, d3, d4)
|
||
|
end
|
||
|
self.region = r1
|
||
|
self:orRegion(r2)
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- self = region:subRect(r1, r2, r3, r4): Subtracts a rectangle from a region
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:subRect(s1, s2, s3, s4)
|
||
|
local r1 = { }
|
||
|
for _, d in ipairs(self.region) do
|
||
|
local d1, d2, d3, d4 = d[1], d[2], d[3], d[4]
|
||
|
for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do
|
||
|
insertrect(r1, t[1], t[2], t[3], t[4])
|
||
|
end
|
||
|
end
|
||
|
self.region = r1
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- region:getRect - gets an iterator on the rectangles in a region [internal]
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:getRects()
|
||
|
local index = 0
|
||
|
return function(object)
|
||
|
index = index + 1
|
||
|
if object[index] then
|
||
|
return unpack(object[index])
|
||
|
end
|
||
|
end, self.region
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- success = region:checkIntersect(x0, y0, x1, y1): Returns a boolean
|
||
|
-- indicating whether a rectangle specified by its coordinates overlaps
|
||
|
-- with a region.
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:checkIntersect(s1, s2, s3, s4)
|
||
|
for _, d in ipairs(self.region) do
|
||
|
if Region.intersect(d[1], d[2], d[3], d[4], s1, s2, s3, s4) then
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- region:subRegion(region2): Subtracts {{region2}} from {{region}}.
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:subRegion(region)
|
||
|
if region then
|
||
|
for r1, r2, r3, r4 in region:getRects() do
|
||
|
self:subRect(r1, r2, r3, r4)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- region:andRegion(r): Logically ''and''s a region to a region
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:andRegion(s)
|
||
|
local r = { }
|
||
|
for _, s in ipairs(s.region) do
|
||
|
for _, d in ipairs(self.region) do
|
||
|
local t1, t2, t3, t4 =
|
||
|
Region.intersect(d[1], d[2], d[3], d[4],
|
||
|
s[1], s[2], s[3], s[4])
|
||
|
if t1 then
|
||
|
insertrect(r, t1, t2, t3, t4)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
self.region = r
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- region:forEach(func, obj, ...): For each rectangle in a region, calls the
|
||
|
-- specified function according the following scheme:
|
||
|
-- func(obj, x0, y0, x1, y1, ...)
|
||
|
-- Extra arguments are passed through to the function.
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:forEach(func, obj, ...)
|
||
|
for x0, y0, x1, y1 in self:getRects() do
|
||
|
func(obj, x0, y0, x1, y1, ...)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- region:shift(dx, dy): Shifts a region by delta x and y.
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:shift(dx, dy)
|
||
|
for _, r in ipairs(self.region) do
|
||
|
r[1] = r[1] + dx
|
||
|
r[2] = r[2] + dy
|
||
|
r[3] = r[3] + dx
|
||
|
r[4] = r[4] + dy
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- region:isEmpty(): Returns '''true''' if a region is empty.
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:isEmpty()
|
||
|
return #self.region == 0
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- minx, miny, maxx, maxy = region:get(): Get region's min/max extents
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
function Region:get()
|
||
|
if #self.region > 0 then
|
||
|
local minx = 1000000 -- ui.HUGE
|
||
|
local miny = 1000000
|
||
|
local maxx = 0
|
||
|
local maxy = 0
|
||
|
for _, r in ipairs(self.region) do
|
||
|
minx = min(minx, r[1])
|
||
|
miny = min(miny, r[2])
|
||
|
maxx = max(maxx, r[3])
|
||
|
maxy = max(maxy, r[4])
|
||
|
end
|
||
|
return minx, miny, maxx, maxy
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return Region
|