From 5d8e690586e62c5fe1d855be3c406bbbf58bd5e9 Mon Sep 17 00:00:00 2001 From: OLC Date: Mon, 28 Apr 2025 23:08:08 +0800 Subject: [PATCH 01/71] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20ffi=20=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E7=9A=84=20Vector2/Vector3/Vector4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/math/Vector2.lua | 178 ++++++++++++++++++ .../foundation/math/Vector3.lua | 149 +++++++++++++++ .../foundation/math/Vector4.lua | 153 +++++++++++++++ 3 files changed, 480 insertions(+) create mode 100644 game/packages/thlib-scripts-v2/foundation/math/Vector2.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/math/Vector3.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/math/Vector4.lua diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua new file mode 100644 index 00000000..20a6c2f5 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -0,0 +1,178 @@ +local ffi = require("ffi") + +local type = type +local string = string +local math = math + +ffi.cdef [[ +typedef struct { + double x; + double y; +} Vector2; +]] + +---@class Vector2 +---@field x number X坐标分量 +---@field y number Y坐标分量 +local Vector2 = {} +Vector2.__index = Vector2 + +---创建一个新的二维向量 +---@param x number|nil X坐标分量,默认为0 +---@param y number|nil Y坐标分量,默认为0 +---@return Vector2 新创建的向量 +function Vector2.create(x, y) + ---@diagnostic disable-next-line: return-type-mismatch + return ffi.new("Vector2", x or 0, y or 0) +end + +---向量加法运算符重载 +---@param a Vector2|number 第一个操作数 +---@param b Vector2|number 第二个操作数 +---@return Vector2 相加后的结果 +function Vector2.__add(a, b) + if type(a) == "number" then + return Vector2.create(a + b.x, a + b.y) + elseif type(b) == "number" then + return Vector2.create(a.x + b, a.y + b) + else + return Vector2.create(a.x + b.x, a.y + b.y) + end +end + +---向量减法运算符重载 +---@param a Vector2|number 第一个操作数 +---@param b Vector2|number 第二个操作数 +---@return Vector2 相减后的结果 +function Vector2.__sub(a, b) + if type(a) == "number" then + return Vector2.create(a - b.x, a - b.y) + elseif type(b) == "number" then + return Vector2.create(a.x - b, a.y - b) + else + return Vector2.create(a.x - b.x, a.y - b.y) + end +end + +---向量乘法运算符重载 +---@param a Vector2|number 第一个操作数 +---@param b Vector2|number 第二个操作数 +---@return Vector2 相乘后的结果 +function Vector2.__mul(a, b) + if type(a) == "number" then + return Vector2.create(a * b.x, a * b.y) + elseif type(b) == "number" then + return Vector2.create(a.x * b, a.y * b) + else + return Vector2.create(a.x * b.x, a.y * b.y) + end +end + +---向量除法运算符重载 +---@param a Vector2|number 第一个操作数 +---@param b Vector2|number 第二个操作数 +---@return Vector2 相除后的结果 +function Vector2.__div(a, b) + if type(a) == "number" then + return Vector2.create(a / b.x, a / b.y) + elseif type(b) == "number" then + return Vector2.create(a.x / b, a.y / b) + else + return Vector2.create(a.x / b.x, a.y / b.y) + end +end + +---向量取负运算符重载 +---@param v Vector2 操作数 +---@return Vector2 取反后的向量 +function Vector2.__unm(v) + return Vector2.create(-v.x, -v.y) +end + +---向量相等性比较运算符重载 +---@param a Vector2 第一个操作数 +---@param b Vector2 第二个操作数 +---@return boolean 两个向量是否相等 +function Vector2.__eq(a, b) + return a.x == b.x and a.y == b.y +end + +---向量字符串表示 +---@param v Vector2 操作数 +---@return string 向量的字符串表示 +function Vector2.__tostring(v) + return string.format("Vector2(%f, %f)", v.x, v.y) +end + +---获取向量长度 +---@param v Vector2 操作数 +---@return number 向量的长度 +function Vector2.__len(v) + return math.sqrt(v.x * v.x + v.y * v.y) +end + +Vector2.length = Vector2.__len + +---获取向量的角度(弧度) +---@return number 向量的角度,单位为弧度 +function Vector2:angle() + return math.atan2(self.y, self.x) +end + +---获取向量的角度(度) +---@return number 向量的角度,单位为度 +function Vector2:degreeAngle() + return math.deg(self:angle()) +end + +---计算两个向量的点积 +---@param other Vector2 另一个向量 +---@return number 两个向量的点积 +function Vector2:dot(other) + return self.x * other.x + self.y * other.y +end + +---将当前向量归一化(更改当前向量) +---@return Vector2 归一化后的向量(自身引用) +function Vector2:normalize() + local len = self:length() + if len > 0 then + self.x = self.x / len + self.y = self.y / len + end + return self +end + +---获取向量的归一化副本 +---@return Vector2 归一化后的向量副本 +function Vector2:normalized() + local len = self:length() + if len == 0 then + return Vector2.create(0, 0) + end + return Vector2.create(self.x / len, self.y / len) +end + +--region LuaSTG Evo API +do + Vector2.LuaSTG = Vector2.length + Vector2.Angle = Vector2.degreeAngle + + ---归一化向量(LuaSTG 兼容版) + ---@return Vector2 归一化后的向量副本 + function Vector2:Normalize() + self:normalize() + return Vector2.create(self.x, self.y) + end + + Vector2.Normalized = Vector2.normalized + Vector2.Dot = Vector2.dot + + local lstg = require("lstg") + lstg.Vector2 = Vector2.create +end +--endregion + +ffi.metatype("Vector2", Vector2) + +return Vector2 diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua new file mode 100644 index 00000000..111e0287 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua @@ -0,0 +1,149 @@ +local ffi = require("ffi") + +local type = type +local string = string +local math = math + +ffi.cdef [[ +typedef struct { + double x; + double y; + double z; +} Vector3; +]] + +---@class Vector3 +---@field x number X坐标分量 +---@field y number Y坐标分量 +---@field z number Z坐标分量 +local Vector3 = {} +Vector3.__index = Vector3 + +---创建一个新的三维向量 +---@param x number|nil X坐标分量,默认为0 +---@param y number|nil Y坐标分量,默认为0 +---@param z number|nil Z坐标分量,默认为0 +---@return Vector3 新创建的向量 +function Vector3.create(x, y, z) + ---@diagnostic disable-next-line: return-type-mismatch + return ffi.new("Vector3", x or 0, y or 0, z or 0) +end + +---向量加法运算符重载 +---@param a Vector3|number 第一个操作数 +---@param b Vector3|number 第二个操作数 +---@return Vector3 相加后的结果 +function Vector3.__add(a, b) + if type(a) == "number" then + return Vector3.create(a + b.x, a + b.y, a + b.z) + elseif type(b) == "number" then + return Vector3.create(a.x + b, a.y + b, a.z + b) + else + return Vector3.create(a.x + b.x, a.y + b.y, a.z + b.z) + end +end + +---向量减法运算符重载 +---@param a Vector3|number 第一个操作数 +---@param b Vector3|number 第二个操作数 +---@return Vector3 相减后的结果 +function Vector3.__sub(a, b) + if type(a) == "number" then + return Vector3.create(a - b.x, a - b.y, a - b.z) + elseif type(b) == "number" then + return Vector3.create(a.x - b, a.y - b, a.z - b) + else + return Vector3.create(a.x - b.x, a.y - b.y, a.z - b.z) + end +end + +---向量乘法运算符重载 +---@param a Vector3|number 第一个操作数 +---@param b Vector3|number 第二个操作数 +---@return Vector3 相乘后的结果 +function Vector3.__mul(a, b) + if type(a) == "number" then + return Vector3.create(a * b.x, a * b.y, a * b.z) + elseif type(b) == "number" then + return Vector3.create(a.x * b, a.y * b, a.z * b) + else + return Vector3.create(a.x * b.x, a.y * b.y, a.z * b.z) + end +end + +---向量除法运算符重载 +---@param a Vector3|number 第一个操作数 +---@param b Vector3|number 第二个操作数 +---@return Vector3 相除后的结果 +function Vector3.__div(a, b) + if type(a) == "number" then + return Vector3.create(a / b.x, a / b.y, a / b.z) + elseif type(b) == "number" then + return Vector3.create(a.x / b, a.y / b, a.z / b) + else + return Vector3.create(a.x / b.x, a.y / b.y, a.z / b.z) + end +end + +---向量取负运算符重载 +---@param v Vector3 操作数 +---@return Vector3 取反后的向量 +function Vector3.__unm(v) + return Vector3.create(-v.x, -v.y, -v.z) +end + +---向量相等性比较运算符重载 +---@param a Vector3 第一个操作数 +---@param b Vector3 第二个操作数 +---@return boolean 两个向量是否相等 +function Vector3.__eq(a, b) + return a.x == b.x and a.y == b.y and a.z == b.z +end + +---向量字符串表示 +---@param v Vector3 操作数 +---@return string 向量的字符串表示 +function Vector3.__tostring(v) + return string.format("Vector3(%f, %f, %f)", v.x, v.y, v.z) +end + +---获取向量长度 +---@param v Vector3 操作数 +---@return number 向量的长度 +function Vector3.__len(v) + return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) +end +Vector3.length = Vector3.__len + +---将当前向量归一化(更改当前向量) +---@return Vector3 归一化后的向量(自身引用) +function Vector3:normalize() + local len = self:length() + if len > 0 then + self.x = self.x / len + self.y = self.y / len + self.z = self.z / len + end + return self +end + +---获取向量的归一化副本 +---@return Vector3 归一化后的向量副本 +function Vector3:normalized() + local len = self:length() + if len == 0 then + return Vector3.create(0, 0, 0) + end + return Vector3.create(self.x / len, self.y / len, self.z / len) +end + +---计算两个向量的点积 +---@param other Vector3 另一个向量 +---@return number 两个向量的点积 +function Vector3:dot(other) + return self.x * other.x + self.y * other.y + self.z * other.z +end + +ffi.metatype("Vector3", Vector3) + +return Vector3 \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua new file mode 100644 index 00000000..efbffdaf --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua @@ -0,0 +1,153 @@ +local ffi = require("ffi") + +local type = type +local string = string +local math = math + +ffi.cdef [[ +typedef struct { + double x; + double y; + double z; + double w; +} Vector4; +]] + +---@class Vector4 +---@field x number X坐标分量 +---@field y number Y坐标分量 +---@field z number Z坐标分量 +---@field w number W坐标分量 +local Vector4 = {} +Vector4.__index = Vector4 + +---创建一个新的四维向量 +---@param x number|nil X坐标分量,默认为0 +---@param y number|nil Y坐标分量,默认为0 +---@param z number|nil Z坐标分量,默认为0 +---@param w number|nil W坐标分量,默认为0 +---@return Vector4 新创建的向量 +function Vector4.create(x, y, z, w) + ---@diagnostic disable-next-line: return-type-mismatch + return ffi.new("Vector4", x or 0, y or 0, z or 0, w or 0) +end + +---向量加法运算符重载 +---@param a Vector4|number 第一个操作数 +---@param b Vector4|number 第二个操作数 +---@return Vector4 相加后的结果 +function Vector4.__add(a, b) + if type(a) == "number" then + return Vector4.create(a + b.x, a + b.y, a + b.z, a + b.w) + elseif type(b) == "number" then + return Vector4.create(a.x + b, a.y + b, a.z + b, a.w + b) + else + return Vector4.create(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w) + end +end + +---向量减法运算符重载 +---@param a Vector4|number 第一个操作数 +---@param b Vector4|number 第二个操作数 +---@return Vector4 相减后的结果 +function Vector4.__sub(a, b) + if type(a) == "number" then + return Vector4.create(a - b.x, a - b.y, a - b.z, a - b.w) + elseif type(b) == "number" then + return Vector4.create(a.x - b, a.y - b, a.z - b, a.w - b) + else + return Vector4.create(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w) + end +end + +---向量乘法运算符重载 +---@param a Vector4|number 第一个操作数 +---@param b Vector4|number 第二个操作数 +---@return Vector4 相乘后的结果 +function Vector4.__mul(a, b) + if type(a) == "number" then + return Vector4.create(a * b.x, a * b.y, a * b.z, a * b.w) + elseif type(b) == "number" then + return Vector4.create(a.x * b, a.y * b, a.z * b, a.w * b) + else + return Vector4.create(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w) + end +end + +---向量除法运算符重载 +---@param a Vector4|number 第一个操作数 +---@param b Vector4|number 第二个操作数 +---@return Vector4 相除后的结果 +function Vector4.__div(a, b) + if type(a) == "number" then + return Vector4.create(a / b.x, a / b.y, a / b.z, a / b.w) + elseif type(b) == "number" then + return Vector4.create(a.x / b, a.y / b, a.z / b, a.w / b) + else + return Vector4.create(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w) + end +end + +---向量取负运算符重载 +---@param v Vector4 操作数 +---@return Vector4 取反后的向量 +function Vector4.__unm(v) + return Vector4.create(-v.x, -v.y, -v.z, -v.w) +end + +---向量相等性比较运算符重载 +---@param a Vector4 第一个操作数 +---@param b Vector4 第二个操作数 +---@return boolean 两个向量是否相等 +function Vector4.__eq(a, b) + return a.x == b.x and a.y == b.y and a.z == b.z and a.w == b.w +end + +---向量字符串表示 +---@param v Vector4 操作数 +---@return string 向量的字符串表示 +function Vector4.__tostring(v) + return string.format("Vector4(%f, %f, %f, %f)", v.x, v.y, v.z, v.w) +end + +---获取向量长度 +---@param v Vector4 操作数 +---@return number 向量的长度 +function Vector4.__len(v) + return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w) +end +Vector4.length = Vector4.__len + +---将当前向量归一化(更改当前向量) +---@return Vector4 归一化后的向量(自身引用) +function Vector4:normalize() + local len = self:length() + if len > 0 then + self.x = self.x / len + self.y = self.y / len + self.z = self.z / len + self.w = self.w / len + end + return self +end + +---获取向量的归一化副本 +---@return Vector4 归一化后的向量副本 +function Vector4:normalized() + local len = self:length() + if len == 0 then + return Vector4.create(0, 0, 0, 0) + end + return Vector4.create(self.x / len, self.y / len, self.z / len, self.w / len) +end + +---计算两个向量的点积 +---@param other Vector4 另一个向量 +---@return number 两个向量的点积 +function Vector4:dot(other) + return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w +end + +ffi.metatype("Vector4", Vector4) + +return Vector4 From 5c26343460ace791549e2dd387fb1a3528940a15 Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 01:53:40 +0800 Subject: [PATCH 02/71] =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E6=A0=87=E8=AF=86=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/math/Vector2.lua | 48 +++++++++---------- .../foundation/math/Vector3.lua | 46 +++++++++--------- .../foundation/math/Vector4.lua | 46 +++++++++--------- 3 files changed, 70 insertions(+), 70 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index 20a6c2f5..441eedf7 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -11,7 +11,7 @@ typedef struct { } Vector2; ]] ----@class Vector2 +---@class foundation.math.Vector2 ---@field x number X坐标分量 ---@field y number Y坐标分量 local Vector2 = {} @@ -20,16 +20,16 @@ Vector2.__index = Vector2 ---创建一个新的二维向量 ---@param x number|nil X坐标分量,默认为0 ---@param y number|nil Y坐标分量,默认为0 ----@return Vector2 新创建的向量 +---@return foundation.math.Vector2 新创建的向量 function Vector2.create(x, y) ---@diagnostic disable-next-line: return-type-mismatch return ffi.new("Vector2", x or 0, y or 0) end ---向量加法运算符重载 ----@param a Vector2|number 第一个操作数 ----@param b Vector2|number 第二个操作数 ----@return Vector2 相加后的结果 +---@param a foundation.math.Vector2|number 第一个操作数 +---@param b foundation.math.Vector2|number 第二个操作数 +---@return foundation.math.Vector2 相加后的结果 function Vector2.__add(a, b) if type(a) == "number" then return Vector2.create(a + b.x, a + b.y) @@ -41,9 +41,9 @@ function Vector2.__add(a, b) end ---向量减法运算符重载 ----@param a Vector2|number 第一个操作数 ----@param b Vector2|number 第二个操作数 ----@return Vector2 相减后的结果 +---@param a foundation.math.Vector2|number 第一个操作数 +---@param b foundation.math.Vector2|number 第二个操作数 +---@return foundation.math.Vector2 相减后的结果 function Vector2.__sub(a, b) if type(a) == "number" then return Vector2.create(a - b.x, a - b.y) @@ -55,9 +55,9 @@ function Vector2.__sub(a, b) end ---向量乘法运算符重载 ----@param a Vector2|number 第一个操作数 ----@param b Vector2|number 第二个操作数 ----@return Vector2 相乘后的结果 +---@param a foundation.math.Vector2|number 第一个操作数 +---@param b foundation.math.Vector2|number 第二个操作数 +---@return foundation.math.Vector2 相乘后的结果 function Vector2.__mul(a, b) if type(a) == "number" then return Vector2.create(a * b.x, a * b.y) @@ -69,9 +69,9 @@ function Vector2.__mul(a, b) end ---向量除法运算符重载 ----@param a Vector2|number 第一个操作数 ----@param b Vector2|number 第二个操作数 ----@return Vector2 相除后的结果 +---@param a foundation.math.Vector2|number 第一个操作数 +---@param b foundation.math.Vector2|number 第二个操作数 +---@return foundation.math.Vector2 相除后的结果 function Vector2.__div(a, b) if type(a) == "number" then return Vector2.create(a / b.x, a / b.y) @@ -83,29 +83,29 @@ function Vector2.__div(a, b) end ---向量取负运算符重载 ----@param v Vector2 操作数 ----@return Vector2 取反后的向量 +---@param v foundation.math.Vector2 操作数 +---@return foundation.math.Vector2 取反后的向量 function Vector2.__unm(v) return Vector2.create(-v.x, -v.y) end ---向量相等性比较运算符重载 ----@param a Vector2 第一个操作数 ----@param b Vector2 第二个操作数 +---@param a foundation.math.Vector2 第一个操作数 +---@param b foundation.math.Vector2 第二个操作数 ---@return boolean 两个向量是否相等 function Vector2.__eq(a, b) return a.x == b.x and a.y == b.y end ---向量字符串表示 ----@param v Vector2 操作数 +---@param v foundation.math.Vector2 操作数 ---@return string 向量的字符串表示 function Vector2.__tostring(v) return string.format("Vector2(%f, %f)", v.x, v.y) end ---获取向量长度 ----@param v Vector2 操作数 +---@param v foundation.math.Vector2 操作数 ---@return number 向量的长度 function Vector2.__len(v) return math.sqrt(v.x * v.x + v.y * v.y) @@ -126,14 +126,14 @@ function Vector2:degreeAngle() end ---计算两个向量的点积 ----@param other Vector2 另一个向量 +---@param other foundation.math.Vector2 另一个向量 ---@return number 两个向量的点积 function Vector2:dot(other) return self.x * other.x + self.y * other.y end ---将当前向量归一化(更改当前向量) ----@return Vector2 归一化后的向量(自身引用) +---@return foundation.math.Vector2 归一化后的向量(自身引用) function Vector2:normalize() local len = self:length() if len > 0 then @@ -144,7 +144,7 @@ function Vector2:normalize() end ---获取向量的归一化副本 ----@return Vector2 归一化后的向量副本 +---@return foundation.math.Vector2 归一化后的向量副本 function Vector2:normalized() local len = self:length() if len == 0 then @@ -159,7 +159,7 @@ do Vector2.Angle = Vector2.degreeAngle ---归一化向量(LuaSTG 兼容版) - ---@return Vector2 归一化后的向量副本 + ---@return foundation.math.Vector2 归一化后的向量副本 function Vector2:Normalize() self:normalize() return Vector2.create(self.x, self.y) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua index 111e0287..6ee1d0dd 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua @@ -12,7 +12,7 @@ typedef struct { } Vector3; ]] ----@class Vector3 +---@class foundation.math.Vector3 ---@field x number X坐标分量 ---@field y number Y坐标分量 ---@field z number Z坐标分量 @@ -23,16 +23,16 @@ Vector3.__index = Vector3 ---@param x number|nil X坐标分量,默认为0 ---@param y number|nil Y坐标分量,默认为0 ---@param z number|nil Z坐标分量,默认为0 ----@return Vector3 新创建的向量 +---@return foundation.math.Vector3 新创建的向量 function Vector3.create(x, y, z) ---@diagnostic disable-next-line: return-type-mismatch return ffi.new("Vector3", x or 0, y or 0, z or 0) end ---向量加法运算符重载 ----@param a Vector3|number 第一个操作数 ----@param b Vector3|number 第二个操作数 ----@return Vector3 相加后的结果 +---@param a foundation.math.Vector3|number 第一个操作数 +---@param b foundation.math.Vector3|number 第二个操作数 +---@return foundation.math.Vector3 相加后的结果 function Vector3.__add(a, b) if type(a) == "number" then return Vector3.create(a + b.x, a + b.y, a + b.z) @@ -44,9 +44,9 @@ function Vector3.__add(a, b) end ---向量减法运算符重载 ----@param a Vector3|number 第一个操作数 ----@param b Vector3|number 第二个操作数 ----@return Vector3 相减后的结果 +---@param a foundation.math.Vector3|number 第一个操作数 +---@param b foundation.math.Vector3|number 第二个操作数 +---@return foundation.math.Vector3 相减后的结果 function Vector3.__sub(a, b) if type(a) == "number" then return Vector3.create(a - b.x, a - b.y, a - b.z) @@ -58,9 +58,9 @@ function Vector3.__sub(a, b) end ---向量乘法运算符重载 ----@param a Vector3|number 第一个操作数 ----@param b Vector3|number 第二个操作数 ----@return Vector3 相乘后的结果 +---@param a foundation.math.Vector3|number 第一个操作数 +---@param b foundation.math.Vector3|number 第二个操作数 +---@return foundation.math.Vector3 相乘后的结果 function Vector3.__mul(a, b) if type(a) == "number" then return Vector3.create(a * b.x, a * b.y, a * b.z) @@ -72,9 +72,9 @@ function Vector3.__mul(a, b) end ---向量除法运算符重载 ----@param a Vector3|number 第一个操作数 ----@param b Vector3|number 第二个操作数 ----@return Vector3 相除后的结果 +---@param a foundation.math.Vector3|number 第一个操作数 +---@param b foundation.math.Vector3|number 第二个操作数 +---@return foundation.math.Vector3 相除后的结果 function Vector3.__div(a, b) if type(a) == "number" then return Vector3.create(a / b.x, a / b.y, a / b.z) @@ -86,29 +86,29 @@ function Vector3.__div(a, b) end ---向量取负运算符重载 ----@param v Vector3 操作数 ----@return Vector3 取反后的向量 +---@param v foundation.math.Vector3 操作数 +---@return foundation.math.Vector3 取反后的向量 function Vector3.__unm(v) return Vector3.create(-v.x, -v.y, -v.z) end ---向量相等性比较运算符重载 ----@param a Vector3 第一个操作数 ----@param b Vector3 第二个操作数 +---@param a foundation.math.Vector3 第一个操作数 +---@param b foundation.math.Vector3 第二个操作数 ---@return boolean 两个向量是否相等 function Vector3.__eq(a, b) return a.x == b.x and a.y == b.y and a.z == b.z end ---向量字符串表示 ----@param v Vector3 操作数 +---@param v foundation.math.Vector3 操作数 ---@return string 向量的字符串表示 function Vector3.__tostring(v) return string.format("Vector3(%f, %f, %f)", v.x, v.y, v.z) end ---获取向量长度 ----@param v Vector3 操作数 +---@param v foundation.math.Vector3 操作数 ---@return number 向量的长度 function Vector3.__len(v) return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) @@ -116,7 +116,7 @@ end Vector3.length = Vector3.__len ---将当前向量归一化(更改当前向量) ----@return Vector3 归一化后的向量(自身引用) +---@return foundation.math.Vector3 归一化后的向量(自身引用) function Vector3:normalize() local len = self:length() if len > 0 then @@ -128,7 +128,7 @@ function Vector3:normalize() end ---获取向量的归一化副本 ----@return Vector3 归一化后的向量副本 +---@return foundation.math.Vector3 归一化后的向量副本 function Vector3:normalized() local len = self:length() if len == 0 then @@ -138,7 +138,7 @@ function Vector3:normalized() end ---计算两个向量的点积 ----@param other Vector3 另一个向量 +---@param other foundation.math.Vector3 另一个向量 ---@return number 两个向量的点积 function Vector3:dot(other) return self.x * other.x + self.y * other.y + self.z * other.z diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua index efbffdaf..11cc8d3a 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua @@ -13,7 +13,7 @@ typedef struct { } Vector4; ]] ----@class Vector4 +---@class foundation.math.Vector4 ---@field x number X坐标分量 ---@field y number Y坐标分量 ---@field z number Z坐标分量 @@ -26,16 +26,16 @@ Vector4.__index = Vector4 ---@param y number|nil Y坐标分量,默认为0 ---@param z number|nil Z坐标分量,默认为0 ---@param w number|nil W坐标分量,默认为0 ----@return Vector4 新创建的向量 +---@return foundation.math.Vector4 新创建的向量 function Vector4.create(x, y, z, w) ---@diagnostic disable-next-line: return-type-mismatch return ffi.new("Vector4", x or 0, y or 0, z or 0, w or 0) end ---向量加法运算符重载 ----@param a Vector4|number 第一个操作数 ----@param b Vector4|number 第二个操作数 ----@return Vector4 相加后的结果 +---@param a foundation.math.Vector4|number 第一个操作数 +---@param b foundation.math.Vector4|number 第二个操作数 +---@return foundation.math.Vector4 相加后的结果 function Vector4.__add(a, b) if type(a) == "number" then return Vector4.create(a + b.x, a + b.y, a + b.z, a + b.w) @@ -47,9 +47,9 @@ function Vector4.__add(a, b) end ---向量减法运算符重载 ----@param a Vector4|number 第一个操作数 ----@param b Vector4|number 第二个操作数 ----@return Vector4 相减后的结果 +---@param a foundation.math.Vector4|number 第一个操作数 +---@param b foundation.math.Vector4|number 第二个操作数 +---@return foundation.math.Vector4 相减后的结果 function Vector4.__sub(a, b) if type(a) == "number" then return Vector4.create(a - b.x, a - b.y, a - b.z, a - b.w) @@ -61,9 +61,9 @@ function Vector4.__sub(a, b) end ---向量乘法运算符重载 ----@param a Vector4|number 第一个操作数 ----@param b Vector4|number 第二个操作数 ----@return Vector4 相乘后的结果 +---@param a foundation.math.Vector4|number 第一个操作数 +---@param b foundation.math.Vector4|number 第二个操作数 +---@return foundation.math.Vector4 相乘后的结果 function Vector4.__mul(a, b) if type(a) == "number" then return Vector4.create(a * b.x, a * b.y, a * b.z, a * b.w) @@ -75,9 +75,9 @@ function Vector4.__mul(a, b) end ---向量除法运算符重载 ----@param a Vector4|number 第一个操作数 ----@param b Vector4|number 第二个操作数 ----@return Vector4 相除后的结果 +---@param a foundation.math.Vector4|number 第一个操作数 +---@param b foundation.math.Vector4|number 第二个操作数 +---@return foundation.math.Vector4 相除后的结果 function Vector4.__div(a, b) if type(a) == "number" then return Vector4.create(a / b.x, a / b.y, a / b.z, a / b.w) @@ -89,29 +89,29 @@ function Vector4.__div(a, b) end ---向量取负运算符重载 ----@param v Vector4 操作数 ----@return Vector4 取反后的向量 +---@param v foundation.math.Vector4 操作数 +---@return foundation.math.Vector4 取反后的向量 function Vector4.__unm(v) return Vector4.create(-v.x, -v.y, -v.z, -v.w) end ---向量相等性比较运算符重载 ----@param a Vector4 第一个操作数 ----@param b Vector4 第二个操作数 +---@param a foundation.math.Vector4 第一个操作数 +---@param b foundation.math.Vector4 第二个操作数 ---@return boolean 两个向量是否相等 function Vector4.__eq(a, b) return a.x == b.x and a.y == b.y and a.z == b.z and a.w == b.w end ---向量字符串表示 ----@param v Vector4 操作数 +---@param v foundation.math.Vector4 操作数 ---@return string 向量的字符串表示 function Vector4.__tostring(v) return string.format("Vector4(%f, %f, %f, %f)", v.x, v.y, v.z, v.w) end ---获取向量长度 ----@param v Vector4 操作数 +---@param v foundation.math.Vector4 操作数 ---@return number 向量的长度 function Vector4.__len(v) return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w) @@ -119,7 +119,7 @@ end Vector4.length = Vector4.__len ---将当前向量归一化(更改当前向量) ----@return Vector4 归一化后的向量(自身引用) +---@return foundation.math.Vector4 归一化后的向量(自身引用) function Vector4:normalize() local len = self:length() if len > 0 then @@ -132,7 +132,7 @@ function Vector4:normalize() end ---获取向量的归一化副本 ----@return Vector4 归一化后的向量副本 +---@return foundation.math.Vector4 归一化后的向量副本 function Vector4:normalized() local len = self:length() if len == 0 then @@ -142,7 +142,7 @@ function Vector4:normalized() end ---计算两个向量的点积 ----@param other Vector4 另一个向量 +---@param other foundation.math.Vector4 另一个向量 ---@return number 两个向量的点积 function Vector4:dot(other) return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w From d123dc36da9c5944d943cf4fddc30a73af48a38e Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 01:58:52 +0800 Subject: [PATCH 03/71] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=B8=B8=E7=94=A8=E7=B1=BB=E5=9E=8B=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/struct/array/BooleanArray.lua | 212 ++++++++++++++ .../foundation/struct/array/DoubleArray.lua | 271 ++++++++++++++++++ .../foundation/struct/array/IntArray.lua | 271 ++++++++++++++++++ 3 files changed, 754 insertions(+) create mode 100644 game/packages/thlib-scripts-v2/foundation/struct/array/BooleanArray.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/struct/array/DoubleArray.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/struct/array/IntArray.lua diff --git a/game/packages/thlib-scripts-v2/foundation/struct/array/BooleanArray.lua b/game/packages/thlib-scripts-v2/foundation/struct/array/BooleanArray.lua new file mode 100644 index 00000000..70b1346b --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/struct/array/BooleanArray.lua @@ -0,0 +1,212 @@ +local ffi = require("ffi") + +local type = type +local error = error +local tostring = tostring +local setmetatable = setmetatable +local table = table + +---@class foundation.struct.array.BooleanArray +---@field size number 数组大小 +---@field data userdata FFI布尔数组 +local BooleanArray = {} + +---创建一个新的布尔数组 +---@param size number 数组大小(必须为正整数) +---@return foundation.struct.array.BooleanArray 新创建的布尔数组 +function BooleanArray.create(size) + if type(size) ~= "number" or size <= 0 then + error("Invalid size: " .. tostring(size)) + end + local self = setmetatable({ + size = size, + data = ffi.new("bool[?]", size) + }, BooleanArray) + return self +end + +---索引元方法,获取数组元素 +---@param self foundation.struct.array.BooleanArray +---@param key number|string 索引或方法名 +---@return boolean|function 数组元素值或方法 +function BooleanArray.__index(self, key) + if type(key) == "number" then + if key < 0 or key >= self.size then + error("Index out of bounds: " .. key) + end + return self.data[key] + end + return BooleanArray[key] +end + +---赋值元方法,设置数组元素 +---@param self foundation.struct.array.BooleanArray +---@param key number 数组索引 +---@param value boolean 布尔值 +function BooleanArray.__newindex(self, key, value) + if type(key) ~= "number" then + error("Invalid index type: " .. type(key)) + end + if key < 0 or key >= self.size then + error("Index out of bounds: " .. key) + end + if type(value) ~= "boolean" then + error("Invalid value type: " .. type(value)) + end + self.data[key] = value +end + +---加法元方法 (逻辑OR操作) +---@param a foundation.struct.array.BooleanArray 左操作数 +---@param b foundation.struct.array.BooleanArray 右操作数 +---@return foundation.struct.array.BooleanArray 结果数组 +function BooleanArray.__add(a, b) + if a.size ~= b.size then + error("Cannot add arrays of different sizes") + end + local result = BooleanArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] or b.data[i] + end + return result +end + +---减法元方法 (a且非b) +---@param a foundation.struct.array.BooleanArray 左操作数 +---@param b foundation.struct.array.BooleanArray 右操作数 +---@return foundation.struct.array.BooleanArray 结果数组 +function BooleanArray.__sub(a, b) + if a.size ~= b.size then + error("Cannot subtract arrays of different sizes") + end + local result = BooleanArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] and not b.data[i] + end + return result +end + +---乘法元方法 (逻辑AND操作) +---@param a foundation.struct.array.BooleanArray 左操作数 +---@param b foundation.struct.array.BooleanArray 右操作数 +---@return foundation.struct.array.BooleanArray 结果数组 +function BooleanArray.__mul(a, b) + if a.size ~= b.size then + error("Cannot multiply arrays of different sizes") + end + local result = BooleanArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] and b.data[i] + end + return result +end + +---除法元方法 (逻辑相等操作) +---@param a foundation.struct.array.BooleanArray 左操作数 +---@param b foundation.struct.array.BooleanArray 右操作数 +---@return foundation.struct.array.BooleanArray 结果数组 +function BooleanArray.__div(a, b) + if a.size ~= b.size then + error("Cannot divide arrays of different sizes") + end + local result = BooleanArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] == b.data[i] + end + return result +end + +---一元减法元方法 (逻辑NOT操作) +---@param self foundation.struct.array.BooleanArray +---@return foundation.struct.array.BooleanArray 取反后的数组 +function BooleanArray.__unm(self) + local result = BooleanArray.create(self.size) + for i = 0, self.size - 1 do + result.data[i] = not self.data[i] + end + return result +end + +---连接元方法 +---@param a foundation.struct.array.BooleanArray 左操作数 +---@param b foundation.struct.array.BooleanArray 右操作数 +---@return foundation.struct.array.BooleanArray 连接后的数组 +function BooleanArray.__concat(a, b) + local result = BooleanArray.create(a.size + b.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] + end + for i = 0, b.size - 1 do + result.data[a.size + i] = b.data[i] + end + return result +end + +---相等比较元方法 +---@param a foundation.struct.array.BooleanArray 左操作数 +---@param b foundation.struct.array.BooleanArray 右操作数 +---@return boolean 数组是否相等 +function BooleanArray.__eq(a, b) + if a.size ~= b.size then + return false + end + for i = 0, a.size - 1 do + if a.data[i] ~= b.data[i] then + return false + end + end + return true +end + +---获取数组长度的元方法 +---@param self foundation.struct.array.BooleanArray +---@return number 数组长度 +function BooleanArray.__len(self) + return self.size +end +BooleanArray.length = BooleanArray.__len + +---转为字符串的元方法 +---@param self foundation.struct.array.BooleanArray +---@return string 数组的字符串表示 +function BooleanArray.__tostring(self) + local result = {} + for i = 0, self.size - 1 do + result[i + 1] = tostring(self.data[i]) + end + return "BooleanArray: [" .. table.concat(result, ", ") .. "]" +end + +---用指定的值填充整个数组 +---@param self foundation.struct.array.BooleanArray +---@param value boolean 填充值 +function BooleanArray:fill(value) + for i = 0, self.size - 1 do + self.data[i] = value + end +end + +---获取数组的迭代器 +---@param self foundation.struct.array.BooleanArray +---@return function,foundation.struct.array.BooleanArray,number 迭代器函数、数组对象和初始索引 +function BooleanArray:ipairs() + return function(t, i) + i = i + 1 + if i < t.size then + return i, t.data[i] + end + end, self, -1 +end + +---克隆数组 +---@param self foundation.struct.array.BooleanArray +---@return foundation.struct.array.BooleanArray 数组副本 +function BooleanArray:clone() + local clone = BooleanArray.create(self.size) + for i = 0, self.size - 1 do + clone.data[i] = self.data[i] + end + return clone +end + +return BooleanArray \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/struct/array/DoubleArray.lua b/game/packages/thlib-scripts-v2/foundation/struct/array/DoubleArray.lua new file mode 100644 index 00000000..fe19ebc8 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/struct/array/DoubleArray.lua @@ -0,0 +1,271 @@ +local ffi = require("ffi") + +local type = type +local error = error +local tostring = tostring +local setmetatable = setmetatable +local table = table + +---@class foundation.struct.array.DoubleArray +---@field size number 数组大小 +---@field data userdata FFI双精度浮点数数组 +local DoubleArray = {} + +---创建一个新的双精度浮点数数组 +---@param size number 数组大小(必须为正整数) +---@return foundation.struct.array.DoubleArray 新创建的双精度浮点数数组 +function DoubleArray.create(size) + if type(size) ~= "number" or size <= 0 then + error("Invalid size: " .. tostring(size)) + end + local self = setmetatable({ + size = size, + data = ffi.new("double[?]", size) + }, DoubleArray) + return self +end + +---索引元方法,获取数组元素 +---@param self foundation.struct.array.DoubleArray +---@param key number|string 索引或方法名 +---@return number|function 数组元素值或方法 +function DoubleArray.__index(self, key) + if type(key) == "number" then + if key < 0 or key >= self.size then + error("Index out of bounds: " .. key) + end + return self.data[key] + end + return DoubleArray[key] +end + +---赋值元方法,设置数组元素 +---@param self foundation.struct.array.DoubleArray +---@param key number 数组索引 +---@param value number 浮点数值 +function DoubleArray.__newindex(self, key, value) + if type(key) ~= "number" then + error("Invalid index type: " .. type(key)) + end + if key < 0 or key >= self.size then + error("Index out of bounds: " .. key) + end + if type(value) ~= "number" then + error("Invalid value type: " .. type(value)) + end + self.data[key] = value +end + +---加法元方法 +---@param a foundation.struct.array.DoubleArray|number 左操作数 +---@param b foundation.struct.array.DoubleArray|number 右操作数 +---@return foundation.struct.array.DoubleArray 结果数组 +function DoubleArray.__add(a, b) + if type(a) == "number" then + local result = DoubleArray.create(b.size) + for i = 0, b.size - 1 do + result.data[i] = a + b.data[i] + end + return result + end + if type(b) == "number" then + local result = DoubleArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] + b + end + return result + end + if a.size ~= b.size then + error("Cannot add arrays of different sizes") + end + local result = DoubleArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] + b.data[i] + end + return result +end + +---减法元方法 +---@param a foundation.struct.array.DoubleArray|number 左操作数 +---@param b foundation.struct.array.DoubleArray|number 右操作数 +---@return foundation.struct.array.DoubleArray 结果数组 +function DoubleArray.__sub(a, b) + if type(a) == "number" then + local result = DoubleArray.create(b.size) + for i = 0, b.size - 1 do + result.data[i] = a - b.data[i] + end + return result + end + if type(b) == "number" then + local result = DoubleArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] - b + end + return result + end + if a.size ~= b.size then + error("Cannot subtract arrays of different sizes") + end + local result = DoubleArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] - b.data[i] + end + return result +end + +---乘法元方法 +---@param a foundation.struct.array.DoubleArray|number 左操作数 +---@param b foundation.struct.array.DoubleArray|number 右操作数 +---@return foundation.struct.array.DoubleArray 结果数组 +function DoubleArray.__mul(a, b) + if type(a) == "number" then + local result = DoubleArray.create(b.size) + for i = 0, b.size - 1 do + result.data[i] = a * b.data[i] + end + return result + end + if type(b) == "number" then + local result = DoubleArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] * b + end + return result + end + if a.size ~= b.size then + error("Cannot multiply arrays of different sizes") + end + local result = DoubleArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] * b.data[i] + end + return result +end + +---除法元方法 +---@param a foundation.struct.array.DoubleArray|number 左操作数 +---@param b foundation.struct.array.DoubleArray|number 右操作数 +---@return foundation.struct.array.DoubleArray 结果数组 +function DoubleArray.__div(a, b) + if type(a) == "number" then + local result = DoubleArray.create(b.size) + for i = 0, b.size - 1 do + result.data[i] = a / b.data[i] + end + return result + end + if type(b) == "number" then + local result = DoubleArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] / b + end + return result + end + if a.size ~= b.size then + error("Cannot divide arrays of different sizes") + end + local result = DoubleArray.create(a.size) + for i = 0, a.size - 1 do + if b.data[i] == 0 then + error("Division by zero at index: " .. i) + end + result.data[i] = a.data[i] / b.data[i] + end + return result +end + +---一元减法元方法 +---@param self foundation.struct.array.DoubleArray +---@return foundation.struct.array.DoubleArray 取反后的数组 +function DoubleArray.__unm(self) + local result = DoubleArray.create(self.size) + for i = 0, self.size - 1 do + result.data[i] = -self.data[i] + end + return result +end + +---连接元方法 +---@param a foundation.struct.array.DoubleArray 左操作数 +---@param b foundation.struct.array.DoubleArray 右操作数 +---@return foundation.struct.array.DoubleArray 连接后的数组 +function DoubleArray.__concat(a, b) + local result = DoubleArray.create(a.size + b.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] + end + for i = 0, b.size - 1 do + result.data[a.size + i] = b.data[i] + end + return result +end + +---相等比较元方法 +---@param a foundation.struct.array.DoubleArray 左操作数 +---@param b foundation.struct.array.DoubleArray 右操作数 +---@return boolean 数组是否相等 +function DoubleArray.__eq(a, b) + if a.size ~= b.size then + return false + end + for i = 0, a.size - 1 do + if a.data[i] ~= b.data[i] then + return false + end + end + return true +end + +---获取数组长度的元方法 +---@param self foundation.struct.array.DoubleArray +---@return number 数组长度 +function DoubleArray.__len(self) + return self.size +end +DoubleArray.length = DoubleArray.__len + +---转为字符串的元方法 +---@param self foundation.struct.array.DoubleArray +---@return string 数组的字符串表示 +function DoubleArray.__tostring(self) + local result = {} + for i = 0, self.size - 1 do + result[i + 1] = tostring(self.data[i]) + end + return "DoubleArray: [" .. table.concat(result, ", ") .. "]" +end + +---用指定的值填充整个数组 +---@param self foundation.struct.array.DoubleArray +---@param value number 填充值 +function DoubleArray:fill(value) + for i = 0, self.size - 1 do + self.data[i] = value + end +end + +---获取数组的迭代器 +---@param self foundation.struct.array.DoubleArray +---@return function,foundation.struct.array.DoubleArray,number 迭代器函数、数组对象和初始索引 +function DoubleArray:ipairs() + return function(t, i) + i = i + 1 + if i < t.size then + return i, t.data[i] + end + end, self, -1 +end + +---克隆数组 +---@param self foundation.struct.array.DoubleArray +---@return foundation.struct.array.DoubleArray 数组副本 +function DoubleArray:clone() + local clone = DoubleArray.create(self.size) + for i = 0, self.size - 1 do + clone.data[i] = self.data[i] + end + return clone +end + +return DoubleArray \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/struct/array/IntArray.lua b/game/packages/thlib-scripts-v2/foundation/struct/array/IntArray.lua new file mode 100644 index 00000000..8dfeae63 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/struct/array/IntArray.lua @@ -0,0 +1,271 @@ +local ffi = require("ffi") + +local type = type +local error = error +local tostring = tostring +local setmetatable = setmetatable +local table = table + +---@class foundation.struct.array.IntArray +---@field size number 数组大小 +---@field data userdata FFI整数数组 +local IntArray = {} + +---创建一个新的整型数组 +---@param size number 数组大小(必须为正整数) +---@return foundation.struct.array.IntArray 新创建的整型数组 +function IntArray.create(size) + if type(size) ~= "number" or size <= 0 then + error("Invalid size: " .. tostring(size)) + end + local self = setmetatable({ + size = size, + data = ffi.new("int[?]", size) + }, IntArray) + return self +end + +---索引元方法,获取数组元素 +---@param self foundation.struct.array.IntArray +---@param key number|string 索引或方法名 +---@return number|function 数组元素值或方法 +function IntArray.__index(self, key) + if type(key) == "number" then + if key < 0 or key >= self.size then + error("Index out of bounds: " .. key) + end + return self.data[key] + end + return IntArray[key] +end + +---赋值元方法,设置数组元素 +---@param self foundation.struct.array.IntArray +---@param key number 数组索引 +---@param value number 整数值 +function IntArray.__newindex(self, key, value) + if type(key) ~= "number" then + error("Invalid index type: " .. type(key)) + end + if key < 0 or key >= self.size then + error("Index out of bounds: " .. key) + end + if type(value) ~= "number" then + error("Invalid value type: " .. type(value)) + end + self.data[key] = value +end + +---加法元方法 +---@param a foundation.struct.array.IntArray|number 左操作数 +---@param b foundation.struct.array.IntArray|number 右操作数 +---@return foundation.struct.array.IntArray 结果数组 +function IntArray.__add(a, b) + if type(a) == "number" then + local result = IntArray.create(b.size) + for i = 0, b.size - 1 do + result.data[i] = a + b.data[i] + end + return result + end + if type(b) == "number" then + local result = IntArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] + b + end + return result + end + if a.size ~= b.size then + error("Cannot add arrays of different sizes") + end + local result = IntArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] + b.data[i] + end + return result +end + +---减法元方法 +---@param a foundation.struct.array.IntArray|number 左操作数 +---@param b foundation.struct.array.IntArray|number 右操作数 +---@return foundation.struct.array.IntArray 结果数组 +function IntArray.__sub(a, b) + if type(a) == "number" then + local result = IntArray.create(b.size) + for i = 0, b.size - 1 do + result.data[i] = a - b.data[i] + end + return result + end + if type(b) == "number" then + local result = IntArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] - b + end + return result + end + if a.size ~= b.size then + error("Cannot subtract arrays of different sizes") + end + local result = IntArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] - b.data[i] + end + return result +end + +---乘法元方法 +---@param a foundation.struct.array.IntArray|number 左操作数 +---@param b foundation.struct.array.IntArray|number 右操作数 +---@return foundation.struct.array.IntArray 结果数组 +function IntArray.__mul(a, b) + if type(a) == "number" then + local result = IntArray.create(b.size) + for i = 0, b.size - 1 do + result.data[i] = a * b.data[i] + end + return result + end + if type(b) == "number" then + local result = IntArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] * b + end + return result + end + if a.size ~= b.size then + error("Cannot multiply arrays of different sizes") + end + local result = IntArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] * b.data[i] + end + return result +end + +---除法元方法 +---@param a foundation.struct.array.IntArray|number 左操作数 +---@param b foundation.struct.array.IntArray|number 右操作数 +---@return foundation.struct.array.IntArray 结果数组 +function IntArray.__div(a, b) + if type(a) == "number" then + local result = IntArray.create(b.size) + for i = 0, b.size - 1 do + result.data[i] = a / b.data[i] + end + return result + end + if type(b) == "number" then + local result = IntArray.create(a.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] / b + end + return result + end + if a.size ~= b.size then + error("Cannot divide arrays of different sizes") + end + local result = IntArray.create(a.size) + for i = 0, a.size - 1 do + if b.data[i] == 0 then + error("Division by zero at index: " .. i) + end + result.data[i] = a.data[i] / b.data[i] + end + return result +end + +---一元减法元方法 +---@param self foundation.struct.array.IntArray +---@return foundation.struct.array.IntArray 取反后的数组 +function IntArray.__unm(self) + local result = IntArray.create(self.size) + for i = 0, self.size - 1 do + result.data[i] = -self.data[i] + end + return result +end + +---连接元方法 +---@param a foundation.struct.array.IntArray 左操作数 +---@param b foundation.struct.array.IntArray 右操作数 +---@return foundation.struct.array.IntArray 连接后的数组 +function IntArray.__concat(a, b) + local result = IntArray.create(a.size + b.size) + for i = 0, a.size - 1 do + result.data[i] = a.data[i] + end + for i = 0, b.size - 1 do + result.data[a.size + i] = b.data[i] + end + return result +end + +---相等比较元方法 +---@param a foundation.struct.array.IntArray 左操作数 +---@param b foundation.struct.array.IntArray 右操作数 +---@return boolean 数组是否相等 +function IntArray.__eq(a, b) + if a.size ~= b.size then + return false + end + for i = 0, a.size - 1 do + if a.data[i] ~= b.data[i] then + return false + end + end + return true +end + +---获取数组长度的元方法 +---@param self foundation.struct.array.IntArray +---@return number 数组长度 +function IntArray.__len(self) + return self.size +end +IntArray.length = IntArray.__len + +---转为字符串的元方法 +---@param self foundation.struct.array.IntArray +---@return string 数组的字符串表示 +function IntArray.__tostring(self) + local result = {} + for i = 0, self.size - 1 do + result[i + 1] = tostring(self.data[i]) + end + return "IntArray: [" .. table.concat(result, ", ") .. "]" +end + +---用指定的值填充整个数组 +---@param self foundation.struct.array.IntArray +---@param value number 填充值 +function IntArray:fill(value) + for i = 0, self.size - 1 do + self.data[i] = value + end +end + +---获取数组的迭代器 +---@param self foundation.struct.array.IntArray +---@return function,foundation.struct.array.IntArray,number 迭代器函数、数组对象和初始索引 +function IntArray:ipairs() + return function(t, i) + i = i + 1 + if i < t.size then + return i, t.data[i] + end + end, self, -1 +end + +---克隆数组 +---@param self foundation.struct.array.IntArray +---@return foundation.struct.array.IntArray 数组副本 +function IntArray:clone() + local clone = IntArray.create(self.size) + for i = 0, self.size - 1 do + clone.data[i] = self.data[i] + end + return clone +end + +return IntArray \ No newline at end of file From 9a40e955baa4b0c0808ef1ce8a03c0e00703c024 Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 17:07:47 +0800 Subject: [PATCH 04/71] =?UTF-8?q?=E4=B8=BA=E5=B8=83=E5=B0=94=E6=95=B0?= =?UTF-8?q?=E7=BB=84=E3=80=81=E5=8F=8C=E7=B2=BE=E5=BA=A6=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E5=92=8C=E6=95=B4=E5=9E=8B=E6=95=B0=E7=BB=84=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=81=8F=E7=A7=BB=E9=87=8F=E6=94=AF=E6=8C=81=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=88=9B=E5=BB=BA=E5=87=BD=E6=95=B0=E5=92=8C=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=E6=96=B9=E6=B3=95=E4=BB=A5=E5=A4=84=E7=90=86=E5=81=8F?= =?UTF-8?q?=E7=A7=BB=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/struct/array/BooleanArray.lua | 44 +++++++++++++++---- .../foundation/struct/array/DoubleArray.lua | 44 +++++++++++++++---- .../foundation/struct/array/IntArray.lua | 44 +++++++++++++++---- 3 files changed, 108 insertions(+), 24 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/struct/array/BooleanArray.lua b/game/packages/thlib-scripts-v2/foundation/struct/array/BooleanArray.lua index 70b1346b..ec591b80 100644 --- a/game/packages/thlib-scripts-v2/foundation/struct/array/BooleanArray.lua +++ b/game/packages/thlib-scripts-v2/foundation/struct/array/BooleanArray.lua @@ -5,21 +5,32 @@ local error = error local tostring = tostring local setmetatable = setmetatable local table = table +local math = math ---@class foundation.struct.array.BooleanArray ---@field size number 数组大小 +---@field offset number 偏移量 ---@field data userdata FFI布尔数组 local BooleanArray = {} ---创建一个新的布尔数组 ---@param size number 数组大小(必须为正整数) +---@param offset number 偏移量(默认为1) ---@return foundation.struct.array.BooleanArray 新创建的布尔数组 -function BooleanArray.create(size) +---@overload fun(size:number):foundation.struct.array.BooleanArray +function BooleanArray.create(size, offset) if type(size) ~= "number" or size <= 0 then error("Invalid size: " .. tostring(size)) end + if type(offset) == "nil" then + offset = 1 + end + if type(offset) ~= "number" or offset ~= math.floor(offset) then + error("Invalid offset: " .. tostring(offset)) + end local self = setmetatable({ size = size, + offset = offset, data = ffi.new("bool[?]", size) }, BooleanArray) return self @@ -31,10 +42,11 @@ end ---@return boolean|function 数组元素值或方法 function BooleanArray.__index(self, key) if type(key) == "number" then - if key < 0 or key >= self.size then + local c_key = key - self.offset + if c_key < 0 or c_key >= self.size then error("Index out of bounds: " .. key) end - return self.data[key] + return self.data[c_key] end return BooleanArray[key] end @@ -47,13 +59,14 @@ function BooleanArray.__newindex(self, key, value) if type(key) ~= "number" then error("Invalid index type: " .. type(key)) end - if key < 0 or key >= self.size then + local c_key = key - self.offset + if c_key < 0 or c_key >= self.size then error("Index out of bounds: " .. key) end if type(value) ~= "boolean" then error("Invalid value type: " .. type(value)) end - self.data[key] = value + self.data[c_key] = value end ---加法元方法 (逻辑OR操作) @@ -177,8 +190,25 @@ function BooleanArray.__tostring(self) return "BooleanArray: [" .. table.concat(result, ", ") .. "]" end +---获取数组索引的偏移量 +---@return number 数组偏移量 +function BooleanArray:getOffset() + return self.offset +end + +---设置数组的偏移量 +---@param offset number 新的偏移量 +function BooleanArray:setOffset(offset) + if type(offset) ~= "number" then + error("Invalid offset type: " .. type(offset)) + end + if offset ~= math.floor(offset) then + error("Offset must be an integer") + end + self.offset = offset +end + ---用指定的值填充整个数组 ----@param self foundation.struct.array.BooleanArray ---@param value boolean 填充值 function BooleanArray:fill(value) for i = 0, self.size - 1 do @@ -187,7 +217,6 @@ function BooleanArray:fill(value) end ---获取数组的迭代器 ----@param self foundation.struct.array.BooleanArray ---@return function,foundation.struct.array.BooleanArray,number 迭代器函数、数组对象和初始索引 function BooleanArray:ipairs() return function(t, i) @@ -199,7 +228,6 @@ function BooleanArray:ipairs() end ---克隆数组 ----@param self foundation.struct.array.BooleanArray ---@return foundation.struct.array.BooleanArray 数组副本 function BooleanArray:clone() local clone = BooleanArray.create(self.size) diff --git a/game/packages/thlib-scripts-v2/foundation/struct/array/DoubleArray.lua b/game/packages/thlib-scripts-v2/foundation/struct/array/DoubleArray.lua index fe19ebc8..22e98c5d 100644 --- a/game/packages/thlib-scripts-v2/foundation/struct/array/DoubleArray.lua +++ b/game/packages/thlib-scripts-v2/foundation/struct/array/DoubleArray.lua @@ -5,21 +5,32 @@ local error = error local tostring = tostring local setmetatable = setmetatable local table = table +local math = math ---@class foundation.struct.array.DoubleArray ---@field size number 数组大小 +---@field offset number 偏移量 ---@field data userdata FFI双精度浮点数数组 local DoubleArray = {} ---创建一个新的双精度浮点数数组 ---@param size number 数组大小(必须为正整数) +---@param offset number 偏移量(默认为1) ---@return foundation.struct.array.DoubleArray 新创建的双精度浮点数数组 -function DoubleArray.create(size) +---@overload fun(size:number):foundation.struct.array.DoubleArray +function DoubleArray.create(size, offset) if type(size) ~= "number" or size <= 0 then error("Invalid size: " .. tostring(size)) end + if type(offset) == "nil" then + offset = 1 + end + if type(offset) ~= "number" or offset ~= math.floor(offset) then + error("Invalid offset: " .. tostring(offset)) + end local self = setmetatable({ size = size, + offset = offset, data = ffi.new("double[?]", size) }, DoubleArray) return self @@ -31,10 +42,11 @@ end ---@return number|function 数组元素值或方法 function DoubleArray.__index(self, key) if type(key) == "number" then - if key < 0 or key >= self.size then + local c_key = key - self.offset + if c_key < 0 or c_key >= self.size then error("Index out of bounds: " .. key) end - return self.data[key] + return self.data[c_key] end return DoubleArray[key] end @@ -47,13 +59,14 @@ function DoubleArray.__newindex(self, key, value) if type(key) ~= "number" then error("Invalid index type: " .. type(key)) end - if key < 0 or key >= self.size then + local c_key = key - self.offset + if c_key < 0 or c_key >= self.size then error("Index out of bounds: " .. key) end if type(value) ~= "number" then error("Invalid value type: " .. type(value)) end - self.data[key] = value + self.data[c_key] = value end ---加法元方法 @@ -236,8 +249,25 @@ function DoubleArray.__tostring(self) return "DoubleArray: [" .. table.concat(result, ", ") .. "]" end +---获取数组索引的偏移量 +---@return number 数组偏移量 +function DoubleArray:getOffset() + return self.offset +end + +---设置数组的偏移量 +---@param offset number 新的偏移量 +function DoubleArray:setOffset(offset) + if type(offset) ~= "number" then + error("Invalid offset type: " .. type(offset)) + end + if offset ~= math.floor(offset) then + error("Offset must be an integer") + end + self.offset = offset +end + ---用指定的值填充整个数组 ----@param self foundation.struct.array.DoubleArray ---@param value number 填充值 function DoubleArray:fill(value) for i = 0, self.size - 1 do @@ -246,7 +276,6 @@ function DoubleArray:fill(value) end ---获取数组的迭代器 ----@param self foundation.struct.array.DoubleArray ---@return function,foundation.struct.array.DoubleArray,number 迭代器函数、数组对象和初始索引 function DoubleArray:ipairs() return function(t, i) @@ -258,7 +287,6 @@ function DoubleArray:ipairs() end ---克隆数组 ----@param self foundation.struct.array.DoubleArray ---@return foundation.struct.array.DoubleArray 数组副本 function DoubleArray:clone() local clone = DoubleArray.create(self.size) diff --git a/game/packages/thlib-scripts-v2/foundation/struct/array/IntArray.lua b/game/packages/thlib-scripts-v2/foundation/struct/array/IntArray.lua index 8dfeae63..05dd05b1 100644 --- a/game/packages/thlib-scripts-v2/foundation/struct/array/IntArray.lua +++ b/game/packages/thlib-scripts-v2/foundation/struct/array/IntArray.lua @@ -5,21 +5,32 @@ local error = error local tostring = tostring local setmetatable = setmetatable local table = table +local math = math ---@class foundation.struct.array.IntArray ---@field size number 数组大小 +---@field offset number 偏移量 ---@field data userdata FFI整数数组 local IntArray = {} ---创建一个新的整型数组 ---@param size number 数组大小(必须为正整数) +---@param offset number 偏移量(默认为1) ---@return foundation.struct.array.IntArray 新创建的整型数组 -function IntArray.create(size) +---@overload fun(size:number):foundation.struct.array.IntArray +function IntArray.create(size, offset) if type(size) ~= "number" or size <= 0 then error("Invalid size: " .. tostring(size)) end + if type(offset) == "nil" then + offset = 1 + end + if type(offset) ~= "number" or offset ~= math.floor(offset) then + error("Invalid offset: " .. tostring(offset)) + end local self = setmetatable({ size = size, + offset = offset, data = ffi.new("int[?]", size) }, IntArray) return self @@ -31,10 +42,11 @@ end ---@return number|function 数组元素值或方法 function IntArray.__index(self, key) if type(key) == "number" then - if key < 0 or key >= self.size then + local c_key = key - self.offset + if c_key < 0 or c_key >= self.size then error("Index out of bounds: " .. key) end - return self.data[key] + return self.data[c_key] end return IntArray[key] end @@ -47,13 +59,14 @@ function IntArray.__newindex(self, key, value) if type(key) ~= "number" then error("Invalid index type: " .. type(key)) end - if key < 0 or key >= self.size then + local c_key = key - self.offset + if c_key < 0 or c_key >= self.size then error("Index out of bounds: " .. key) end if type(value) ~= "number" then error("Invalid value type: " .. type(value)) end - self.data[key] = value + self.data[c_key] = value end ---加法元方法 @@ -236,8 +249,25 @@ function IntArray.__tostring(self) return "IntArray: [" .. table.concat(result, ", ") .. "]" end +---获取数组索引的偏移量 +---@return number 数组偏移量 +function IntArray:getOffset() + return self.offset +end + +---设置数组的偏移量 +---@param offset number 新的偏移量 +function IntArray:setOffset(offset) + if type(offset) ~= "number" then + error("Invalid offset type: " .. type(offset)) + end + if offset ~= math.floor(offset) then + error("Offset must be an integer") + end + self.offset = offset +end + ---用指定的值填充整个数组 ----@param self foundation.struct.array.IntArray ---@param value number 填充值 function IntArray:fill(value) for i = 0, self.size - 1 do @@ -246,7 +276,6 @@ function IntArray:fill(value) end ---获取数组的迭代器 ----@param self foundation.struct.array.IntArray ---@return function,foundation.struct.array.IntArray,number 迭代器函数、数组对象和初始索引 function IntArray:ipairs() return function(t, i) @@ -258,7 +287,6 @@ function IntArray:ipairs() end ---克隆数组 ----@param self foundation.struct.array.IntArray ---@return foundation.struct.array.IntArray 数组副本 function IntArray:clone() local clone = IntArray.create(self.size) From 16ae5dfae278b2f9e578b624cf12db6cb39d42f3 Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 17:47:10 +0800 Subject: [PATCH 05/71] add rotation methods to Vector2 --- .../foundation/math/Vector2.lua | 26 +++++++++++++++++++ .../foundation/math/Vector3.lua | 14 +++++----- .../foundation/math/Vector4.lua | 14 +++++----- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index 441eedf7..9c4f3ad7 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -153,6 +153,32 @@ function Vector2:normalized() return Vector2.create(self.x / len, self.y / len) end +---将当前向量旋转指定角度(更改当前向量) +---@param angle number 旋转角度,单位为度 +---@return foundation.math.Vector2 旋转后的向量(自身引用) +function Vector2:rotate(angle) + angle = math.rad(angle) + local cos = math.cos(angle) + local sin = math.sin(angle) + local x = self.x * cos - self.y * sin + local y = self.x * sin + self.y * cos + self.x = x + self.y = y + return self +end + +---获取向量的旋转副本 +---@param angle number 旋转角度,单位为度 +---@return foundation.math.Vector2 旋转后的向量副本 +function Vector2:rotated(angle) + angle = math.rad(angle) + local cos = math.cos(angle) + local sin = math.sin(angle) + local x = self.x * cos - self.y * sin + local y = self.x * sin + self.y * cos + return Vector2.create(x, y) +end + --region LuaSTG Evo API do Vector2.LuaSTG = Vector2.length diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua index 6ee1d0dd..3d89b4f6 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua @@ -115,6 +115,13 @@ function Vector3.__len(v) end Vector3.length = Vector3.__len +---计算两个向量的点积 +---@param other foundation.math.Vector3 另一个向量 +---@return number 两个向量的点积 +function Vector3:dot(other) + return self.x * other.x + self.y * other.y + self.z * other.z +end + ---将当前向量归一化(更改当前向量) ---@return foundation.math.Vector3 归一化后的向量(自身引用) function Vector3:normalize() @@ -137,13 +144,6 @@ function Vector3:normalized() return Vector3.create(self.x / len, self.y / len, self.z / len) end ----计算两个向量的点积 ----@param other foundation.math.Vector3 另一个向量 ----@return number 两个向量的点积 -function Vector3:dot(other) - return self.x * other.x + self.y * other.y + self.z * other.z -end - ffi.metatype("Vector3", Vector3) return Vector3 \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua index 11cc8d3a..2fbaaaf1 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua @@ -118,6 +118,13 @@ function Vector4.__len(v) end Vector4.length = Vector4.__len +---计算两个向量的点积 +---@param other foundation.math.Vector4 另一个向量 +---@return number 两个向量的点积 +function Vector4:dot(other) + return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w +end + ---将当前向量归一化(更改当前向量) ---@return foundation.math.Vector4 归一化后的向量(自身引用) function Vector4:normalize() @@ -141,13 +148,6 @@ function Vector4:normalized() return Vector4.create(self.x / len, self.y / len, self.z / len, self.w / len) end ----计算两个向量的点积 ----@param other foundation.math.Vector4 另一个向量 ----@return number 两个向量的点积 -function Vector4:dot(other) - return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w -end - ffi.metatype("Vector4", Vector4) return Vector4 From 4271d132755266648a5e87c0968dd2857e599d0f Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 18:49:40 +0800 Subject: [PATCH 06/71] refactor Vector2 rotation methods to use radians and improve documentation --- .../foundation/math/Vector2.lua | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index 9c4f3ad7..40148895 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -153,13 +153,12 @@ function Vector2:normalized() return Vector2.create(self.x / len, self.y / len) end ----将当前向量旋转指定角度(更改当前向量) ----@param angle number 旋转角度,单位为度 +---将当前向量旋转指定弧度(更改当前向量) +---@param rad number 旋转弧度 ---@return foundation.math.Vector2 旋转后的向量(自身引用) -function Vector2:rotate(angle) - angle = math.rad(angle) - local cos = math.cos(angle) - local sin = math.sin(angle) +function Vector2:rotate(rad) + local cos = math.cos(rad) + local sin = math.sin(rad) local x = self.x * cos - self.y * sin local y = self.x * sin + self.y * cos self.x = x @@ -167,18 +166,33 @@ function Vector2:rotate(angle) return self end ----获取向量的旋转副本 ----@param angle number 旋转角度,单位为度 ----@return foundation.math.Vector2 旋转后的向量副本 -function Vector2:rotated(angle) +---将向量旋转指定角度(更改当前向量) +---@param angle number 旋转角度 +---@return foundation.math.Vector2 旋转后的向量(自身引用) +function Vector2:degreeRotate(angle) angle = math.rad(angle) - local cos = math.cos(angle) - local sin = math.sin(angle) + return self:rotate(angle) +end + +---获取向量的旋转指定弧度的副本 +---@param rad number 旋转弧度 +---@return foundation.math.Vector2 旋转后的向量副本 +function Vector2:rotated(rad) + local cos = math.cos(rad) + local sin = math.sin(rad) local x = self.x * cos - self.y * sin local y = self.x * sin + self.y * cos return Vector2.create(x, y) end +---获取向量的旋转指定角度的副本 +---@param angle number 旋转角度 +---@return foundation.math.Vector2 旋转后的向量副本 +function Vector2:degreeRotate(angle) + angle = math.rad(angle) + return self:rotated(angle) +end + --region LuaSTG Evo API do Vector2.LuaSTG = Vector2.length From 6ba445870dcf5710aedcd13ec5c3def878e8415b Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 19:52:40 +0800 Subject: [PATCH 07/71] add cross product method to Vector2 --- game/packages/thlib-scripts-v2/foundation/math/Vector2.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index 40148895..ba994543 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -132,6 +132,13 @@ function Vector2:dot(other) return self.x * other.x + self.y * other.y end +---计算两个向量的叉积 +---@param other foundation.math.Vector2 另一个向量 +---@return number 两个向量的叉积 +function Vector2:cross(other) + return self.x * other.y - self.y * other.x +end + ---将当前向量归一化(更改当前向量) ---@return foundation.math.Vector2 归一化后的向量(自身引用) function Vector2:normalize() From 27cbf2bee040200f49817d4d1cedccd4819026a6 Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 19:52:49 +0800 Subject: [PATCH 08/71] add Triangle class with geometric operations and methods --- .../foundation/shape/Triangle.lua | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua new file mode 100644 index 00000000..b2e95de3 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -0,0 +1,300 @@ +local ffi = require("ffi") + +local type = type +local string = string +local math = math + +local Vector2 = require("foundation.math.Vector2") + +ffi.cdef [[ +typedef struct { + Vector2 v1, v2, v3; +} Triangle; +]] + +---@class foundation.shape.Triangle +---@field v1 foundation.math.Vector2 三角形的第一个顶点 +---@field v2 foundation.math.Vector2 三角形的第二个顶点 +---@field v3 foundation.math.Vector2 三角形的第三个顶点 +local Triangle = {} +Triangle.__index = Triangle + +---创建一个新的三角形 +---@param v1 foundation.math.Vector2 三角形的第一个顶点 +---@param v2 foundation.math.Vector2 三角形的第二个顶点 +---@param v3 foundation.math.Vector2 三角形的第三个顶点 +---@return foundation.shape.Triangle 新创建的三角形 +function Triangle.create(v1, v2, v3) + ---@diagnostic disable-next-line: return-type-mismatch + return ffi.new("Triangle", v1, v2, v3) +end + +---三角形加法运算,可以是三角形与三角形相加,或者三角形与标量相加 +---@param a foundation.shape.Triangle|number 第一个操作数 +---@param b foundation.shape.Triangle|number 第二个操作数 +---@return foundation.shape.Triangle 相加后的三角形 +function Triangle.__add(a, b) + if type(a) == "number" then + return Triangle.create(a + b.v1, a + b.v2, a + b.v3) + elseif type(b) == "number" then + return Triangle.create(a.v1 + b, a.v2 + b, a.v3 + b) + else + return Triangle.create(a.v1 + b.v1, a.v2 + b.v2, a.v3 + b.v3) + end +end + +---三角形减法运算,可以是三角形与三角形相减,或者三角形与标量相减 +---@param a foundation.shape.Triangle|number 第一个操作数 +---@param b foundation.shape.Triangle|number 第二个操作数 +---@return foundation.shape.Triangle 相减后的三角形 +function Triangle.__sub(a, b) + if type(a) == "number" then + return Triangle.create(a - b.v1, a - b.v2, a - b.v3) + elseif type(b) == "number" then + return Triangle.create(a.v1 - b, a.v2 - b, a.v3 - b) + else + return Triangle.create(a.v1 - b.v1, a.v2 - b.v2, a.v3 - b.v3) + end +end + +---三角形乘法运算,可以是三角形与三角形相乘,或者三角形与标量相乘 +---@param a foundation.shape.Triangle|number 第一个操作数 +---@param b foundation.shape.Triangle|number 第二个操作数 +---@return foundation.shape.Triangle 相乘后的三角形 +function Triangle.__mul(a, b) + if type(a) == "number" then + return Triangle.create(a * b.v1, a * b.v2, a * b.v3) + elseif type(b) == "number" then + return Triangle.create(a.v1 * b, a.v2 * b, a.v3 * b) + else + return Triangle.create(a.v1 * b.v1, a.v2 * b.v2, a.v3 * b.v3) + end +end + +---三角形除法运算,可以是三角形与三角形相除,或者三角形与标量相除 +---@param a foundation.shape.Triangle|number 第一个操作数 +---@param b foundation.shape.Triangle|number 第二个操作数 +---@return foundation.shape.Triangle 相除后的三角形 +function Triangle.__div(a, b) + if type(a) == "number" then + return Triangle.create(a / b.v1, a / b.v2, a / b.v3) + elseif type(b) == "number" then + return Triangle.create(a.v1 / b, a.v2 / b, a.v3 / b) + else + return Triangle.create(a.v1 / b.v1, a.v2 / b.v2, a.v3 / b.v3) + end +end + +---三角形取反运算 +---@param t foundation.shape.Triangle 要取反的三角形 +---@return foundation.shape.Triangle 取反后的三角形 +function Triangle.__unm(t) + return Triangle.create(-t.v1, -t.v2, -t.v3) +end + +---三角形相等比较 +---@param a foundation.shape.Triangle 第一个三角形 +---@param b foundation.shape.Triangle 第二个三角形 +---@return boolean 如果两个三角形的所有顶点都相等则返回true,否则返回false +function Triangle.__eq(a, b) + return a.v1 == b.v1 and a.v2 == b.v2 and a.v3 == b.v3 +end + +---三角形转字符串表示 +---@param t foundation.shape.Triangle 要转换的三角形 +---@return string 三角形的字符串表示 +function Triangle.__tostring(t) + return string.format("Triangle(%s, %s, %s)", tostring(t.v1), tostring(t.v2), tostring(t.v3)) +end + +---计算三角形的面积 +---@return number 三角形的面积 +function Triangle:area() + local v2v1 = self.v2 - self.v1 + local v3v1 = self.v3 - self.v1 + return 0.5 * math.abs(v2v1:cross(v3v1)) +end + +---计算三角形的重心 +---@return foundation.math.Vector2 三角形的重心 +function Triangle:centroid() + return (self.v1 + self.v2 + self.v3) / 3 +end + +---计算三角形的外接圆半径 +---@return number 三角形的外接圆半径 +function Triangle:circumradius() + local center = self:circumcenter() + if center then + return (center - self.v1):length() + end + return 0 +end + +---计算三角形的内切圆半径 +---@return number 三角形的内切圆半径 +function Triangle:inradius() + local a = (self.v2 - self.v3):length() + local b = (self.v1 - self.v3):length() + local c = (self.v1 - self.v2):length() + return self:area() / ((a + b + c) / 2) +end + +---计算三角形的外心 +---@return foundation.math.Vector2 | nil 三角形的外心 +function Triangle:circumcenter() + local x1, y1 = self.v1.x, self.v1.y + local x2, y2 = self.v2.x, self.v2.y + local x3, y3 = self.v3.x, self.v3.y + + local D = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2) + if math.abs(D) < 1e-10 then + return nil + end + + local x0_num = (x1 * x1 + y1 * y1) * (y2 - y3) + (x2 * x2 + y2 * y2) * (y3 - y1) + (x3 * x3 + y3 * y3) * (y1 - y2) + local x0 = x0_num / (2 * D) + + local y0_num = (x1 * x1 + y1 * y1) * (x3 - x2) + (x2 * x2 + y2 * y2) * (x1 - x3) + (x3 * x3 + y3 * y3) * (x2 - x1) + local y0 = y0_num / (2 * D) + + return Vector2.create(x0, y0) +end + +---计算三角形的内心 +---@return foundation.math.Vector2 三角形的内心 +function Triangle:incenter() + local a = (self.v2 - self.v3):length() + local b = (self.v1 - self.v3):length() + local c = (self.v1 - self.v2):length() + + local p = (a * self.v1 + b * self.v2 + c * self.v3) / (a + b + c) + return p +end + +---将当前三角形旋转指定弧度(更改当前三角形) +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Triangle 旋转后的三角形(自身引用) +---@overload fun(self: foundation.shape.Triangle, rad: number): foundation.shape.Triangle 绕三角形重心旋转指定弧度 +function Triangle:rotate(rad, center) + center = center or self:centroid() + local cosAngle = math.cos(rad) + local sinAngle = math.sin(rad) + local v1 = self.v1 - center + local v2 = self.v2 - center + local v3 = self.v3 - center + self.v1.x = v1.x * cosAngle - v1.y * sinAngle + center.x + self.v1.y = v1.x * sinAngle + v1.y * cosAngle + center.y + self.v2.x = v2.x * cosAngle - v2.y * sinAngle + center.x + self.v2.y = v2.x * sinAngle + v2.y * cosAngle + center.y + self.v3.x = v3.x * cosAngle - v3.y * sinAngle + center.x + self.v3.y = v3.x * sinAngle + v3.y * cosAngle + center.y + return self +end + +---将当前三角形旋转指定角度(更改当前三角形) +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Triangle 旋转后的三角形(自身引用) +---@overload fun(self: foundation.shape.Triangle, angle: number): foundation.shape.Triangle 绕三角形重心旋转指定角度 +function Triangle:degreeRotate(angle, center) + angle = math.rad(angle) + return self:rotate(angle, center) +end + +---获取三角形的旋转指定弧度的副本 +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Triangle 旋转后的三角形副本 +---@overload fun(self: foundation.shape.Triangle, rad: number): foundation.shape.Triangle 绕三角形重心旋转指定弧度 +function Triangle:rotated(rad, center) + center = center or self:centroid() + local cosAngle = math.cos(rad) + local sinAngle = math.sin(rad) + local v1 = self.v1 - center + local v2 = self.v2 - center + local v3 = self.v3 - center + return Triangle.create( + Vector2.create(v1.x * cosAngle - v1.y * sinAngle + center.x, v1.x * sinAngle + v1.y * cosAngle + center.y), + Vector2.create(v2.x * cosAngle - v2.y * sinAngle + center.x, v2.x * sinAngle + v2.y * cosAngle + center.y), + Vector2.create(v3.x * cosAngle - v3.y * sinAngle + center.x, v3.x * sinAngle + v3.y * cosAngle + center.y) + ) +end + +---获取三角形的旋转指定角度的副本 +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Triangle 旋转后的三角形副本 +---@overload fun(self: foundation.shape.Triangle, angle: number): foundation.shape.Triangle 绕三角形重心旋转指定角度 +function Triangle:degreeRotated(angle, center) + angle = math.rad(angle) + return self:rotated(angle, center) +end + +---将当前三角形缩放指定倍数(更改当前三角形) +---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2 缩放中心 +---@return foundation.shape.Triangle 缩放后的三角形(自身引用) +---@overload fun(self: foundation.shape.Triangle, scale: number): foundation.shape.Triangle 相对三角形重心缩放指定倍数 +function Triangle:scale(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + center = center or self:centroid() + self.v1.x = (self.v1.x - center.x) * scaleX + center.x + self.v1.y = (self.v1.y - center.y) * scaleY + center.y + self.v2.x = (self.v2.x - center.x) * scaleX + center.x + self.v2.y = (self.v2.y - center.y) * scaleY + center.y + self.v3.x = (self.v3.x - center.x) * scaleX + center.x + self.v3.y = (self.v3.y - center.y) * scaleY + center.y + return self +end + +---获取三角形的缩放指定倍数的副本 +---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2 缩放中心 +---@return foundation.shape.Triangle 缩放后的三角形副本 +---@overload fun(self: foundation.shape.Triangle, scale: number): foundation.shape.Triangle 相对三角形重心缩放指定倍数 +function Triangle:scaled(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + center = center or self:centroid() + return Triangle.create( + Vector2.create((self.v1.x - center.x) * scaleX + center.x, (self.v1.y - center.y) * scaleY + center.y), + Vector2.create((self.v2.x - center.x) * scaleX + center.x, (self.v2.y - center.y) * scaleY + center.y), + Vector2.create((self.v3.x - center.x) * scaleX + center.x, (self.v3.y - center.y) * scaleY + center.y) + ) +end + +---判断点是否在三角形内(包括边界) +---@param point foundation.math.Vector2 要检查的点 +---@return boolean 如果点在三角形内(包括边界)返回true,否则返回false +function Triangle:contains(point) + local v3v1 = self.v3 - self.v1 + local v2v1 = self.v2 - self.v1 + local pv1 = point - self.v1 + + local dot00 = v3v1:dot(v3v1) + local dot01 = v3v1:dot(v2v1) + local dot02 = v3v1:dot(pv1) + local dot11 = v2v1:dot(v2v1) + local dot12 = v2v1:dot(pv1) + + local invDenom = 1 / (dot00 * dot11 - dot01 * dot01) + local u = (dot11 * dot02 - dot01 * dot12) * invDenom + local v = (dot00 * dot12 - dot01 * dot02) * invDenom + + return (u >= 0) and (v >= 0) and (u + v <= 1) +end + +ffi.metatype("Triangle", Triangle) + +return Triangle \ No newline at end of file From ab034da9bc1fc9d5f52e809e2df38fc43649bb21 Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 20:02:31 +0800 Subject: [PATCH 09/71] add methods to create Vector2 from radians and angles --- .../foundation/math/Vector2.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index ba994543..74ade717 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -26,6 +26,25 @@ function Vector2.create(x, y) return ffi.new("Vector2", x or 0, y or 0) end +---通过给定的弧度和长度创建一个新的二维向量 +---@param rad number 弧度 +---@param length number 向量长度 +---@return foundation.math.Vector2 新创建的向量 +function Vector2.createFromRad(rad, length) + local x = length * math.cos(rad) + local y = length * math.sin(rad) + return Vector2.create(x, y) +end + +---通过给定的角度和长度创建一个新的二维向量 +---@param angle number 角度 +---@param length number 向量长度 +---@return foundation.math.Vector2 新创建的向量 +function Vector2.createFromAngle(angle, length) + local rad = math.rad(angle) + return Vector2.createFromRad(rad, length) +end + ---向量加法运算符重载 ---@param a foundation.math.Vector2|number 第一个操作数 ---@param b foundation.math.Vector2|number 第二个操作数 From f81de14d28567209a4f97f89d8927ceea7a78c31 Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 20:34:46 +0800 Subject: [PATCH 10/71] add move and moved methods to Triangle for translation operations --- .../foundation/shape/Triangle.lua | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index b2e95de3..12582d4f 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -1,6 +1,7 @@ local ffi = require("ffi") local type = type +local tostring = tostring local string = string local math = math @@ -172,7 +173,43 @@ function Triangle:incenter() return p end ----将当前三角形旋转指定弧度(更改当前三角形) +---将当前三角形平移指定距离(更改当前三角形) +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Triangle 移动后的三角形(自身引用) +function Triangle:move(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + self.v1.x = self.v1.x + moveX + self.v1.y = self.v1.y + moveY + self.v2.x = self.v2.x + moveX + self.v2.y = self.v2.y + moveY + self.v3.x = self.v3.x + moveX + self.v3.y = self.v3.y + moveY + return self +end + +---获取三角形平移指定距离的副本 +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Triangle 移动后的三角形副本 +function Triangle:moved(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + return Triangle.create( + Vector2.create(self.v1.x + moveX, self.v1.y + moveY), + Vector2.create(self.v2.x + moveX, self.v2.y + moveY), + Vector2.create(self.v3.x + moveX, self.v3.y + moveY) + ) +end + +---将当前三角形旋转指定弧度(更改当前三角形) ---@param rad number 旋转弧度 ---@param center foundation.math.Vector2 旋转中心 ---@return foundation.shape.Triangle 旋转后的三角形(自身引用) @@ -193,7 +230,7 @@ function Triangle:rotate(rad, center) return self end ----将当前三角形旋转指定角度(更改当前三角形) +---将当前三角形旋转指定角度(更改当前三角形) ---@param angle number 旋转角度 ---@param center foundation.math.Vector2 旋转中心 ---@return foundation.shape.Triangle 旋转后的三角形(自身引用) From f7fd94403ea29088b09411ad486f4912e008f01c Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 21:25:42 +0800 Subject: [PATCH 11/71] add Line class for geometric operations --- .../foundation/shape/Line.lua | 275 ++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Line.lua diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua new file mode 100644 index 00000000..74583626 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -0,0 +1,275 @@ +local ffi = require("ffi") + +local type = type +local tostring = tostring +local string = string +local math = math + +local Vector2 = require("foundation.math.Vector2") + +ffi.cdef [[ +typedef struct { + Vector2 v1, v2; +} Line; +]] + +---@class foundation.shape.Line +---@field v1 foundation.math.Vector2 +---@field v2 foundation.math.Vector2 +local Line = {} +Line.__index = Line + +---创建一个线段 +---@param v1 foundation.math.Vector2 线段的起点 +---@param v2 foundation.math.Vector2 线段的终点 +---@return foundation.shape.Line +function Line.create(v1, v2) + ---@diagnostic disable-next-line: return-type-mismatch + return ffi.new("Line", v1, v2) +end + +---根据给定点和弧度与长度创建线段 +---@param point foundation.math.Vector2 线段的起点 +---@param rad number 线段的弧度 +---@param length number 线段的长度 +---@return foundation.shape.Line +function Line.createFromPointAndRad(point, rad, length) + local v2 = Vector2.create(point.x + math.cos(rad) * length, point.y + math.sin(rad) * length) + return Line.create(point, v2) +end + +---根据给定点和角度与长度创建线段 +---@param point foundation.math.Vector2 线段的起点 +---@param angle number 线段的角度 +---@param length number 线段的长度 +---@return foundation.shape.Line +function Line.createFromPointAndAngle(point, angle, length) + local rad = math.rad(angle) + return Line.createFromPointAndRad(point, rad, length) +end + +---线段转字符串表示 +---@param self foundation.shape.Line +---@return string 线段的字符串表示 +function Line.__tostring(self) + return string.format("Line(%s, %s)", tostring(self.v1), tostring(self.v2)) +end + +---将线段转换为向量 +---@return foundation.math.Vector2 从起点到终点的向量 +function Line:toVector2() + return self.v2 - self.v1 +end + +---获取线段的法向量 +---@return foundation.math.Vector2 线段的单位法向量 +function Line:normal() + local dir = self.v2 - self.v1 + local len = dir:length() + if len == 0 then + return Vector2.create(0, 0) + end + return Vector2.create(-dir.y / len, dir.x / len) +end + +---获取线段的长度 +---@return number 线段的长度 +function Line:length() + return self:toVector2():length() +end + +---获取线段的中点 +---@return foundation.math.Vector2 线段的中点 +function Line:midpoint() + return Vector2.create((self.v1.x + self.v2.x) / 2, (self.v1.y + self.v2.y) / 2) +end + +---获取线段的角度(弧度) +---@return number 线段的角度,单位为弧度 +function Line:angle() + return math.atan2(self.v2.y - self.v1.y, self.v2.x - self.v1.x) +end + +---获取线段的角度(度) +---@return number 线段的角度,单位为度 +function Line:degreeAngle() + return math.deg(self:angle()) +end + +---平移线段(更改当前线段) +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Line 平移后的线段(自身引用) +function Line:move(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + self.v1.x = self.v1.x + moveX + self.v1.y = self.v1.y + moveY + self.v2.x = self.v2.x + moveX + self.v2.y = self.v2.y + moveY + return self +end + +---获取当前线段平移指定距离的副本 +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Line 移动后的线段副本 +function Line:moved(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + return Line.create( + Vector2.create(self.v1.x + moveX, self.v1.y + moveY), + Vector2.create(self.v2.x + moveX, self.v2.y + moveY) + ) +end + +---将当前线段旋转指定弧度(更改当前线段) +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Line 旋转后的线段(自身引用) +---@overload fun(self:foundation.shape.Line, rad:number): foundation.shape.Line 将线段绕中点旋转指定弧度 +function Line:rotate(rad, center) + center = center or self:midpoint() + local cosRad = math.cos(rad) + local sinRad = math.sin(rad) + local v1 = self.v1 - center + local v2 = self.v2 - center + self.v1.x = v1.x * cosRad - v1.y * sinRad + center.x + self.v1.y = v1.x * sinRad + v1.y * cosRad + center.y + self.v2.x = v2.x * cosRad - v2.y * sinRad + center.x + self.v2.y = v2.x * sinRad + v2.y * cosRad + center.y + return self +end + +---将当前线段旋转指定角度(更改当前线段) +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Line 旋转后的线段(自身引用) +---@overload fun(self:foundation.shape.Line, angle:number): foundation.shape.Line 将线段绕中点旋转指定角度 +function Line:degreeRotate(angle, center) + angle = math.rad(angle) + return self:rotate(angle, center) +end + +---获取当前线段旋转指定弧度的副本 +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Line 旋转后的线段副本 +---@overload fun(self:foundation.shape.Line, rad:number): foundation.shape.Line 将线段绕中点旋转指定弧度 +function Line:rotated(rad, center) + center = center or self:midpoint() + local cosRad = math.cos(rad) + local sinRad = math.sin(rad) + local v1 = self.v1 - center + local v2 = self.v2 - center + return Line.create( + Vector2.create(v1.x * cosRad - v1.y * sinRad + center.x, v1.x * sinRad + v1.y * cosRad + center.y), + Vector2.create(v2.x * cosRad - v2.y * sinRad + center.x, v2.x * sinRad + v2.y * cosRad + center.y) + ) +end + +---获取当前线段旋转指定角度的副本 +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Line 旋转后的线段副本 +---@overload fun(self:foundation.shape.Line, angle:number): foundation.shape.Line 将线段绕中点旋转指定角度 +function Line:degreeRotated(angle, center) + angle = math.rad(angle) + return self:rotated(angle, center) +end + +---检查线段是否与另一线段相交 +---@param other foundation.shape.Line 另一线段 +---@return boolean, foundation.math.Vector2|nil +function Line:intersects(other) + local a = self.v1 + local b = self.v2 + local c = other.v1 + local d = other.v2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if t >= 0 and t <= 1 and u >= 0 and u <= 1 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + return true, Vector2.create(x, y) + end + + return false, nil +end + +---计算点到线段的最近点 +---@param point foundation.math.Vector2 要检查的点 +---@return foundation.math.Vector2 线段上最近的点 +function Line:closestPoint(point) + local dir = self.v2 - self.v1 + local len = dir:length() + if len == 0 then + return Vector2.create(self.v1.x, self.v1.y) + end + + local t = ((point.x - self.v1.x) * dir.x + (point.y - self.v1.y) * dir.y) / (len * len) + t = math.max(0, math.min(1, t)) + + return Vector2.create(self.v1.x + t * dir.x, self.v1.y + t * dir.y) +end + +---计算点到线段的距离 +---@param point foundation.math.Vector2 要检查的点 +---@return number 点到线段的距离 +function Line:distanceToPoint(point) + local closest = self:closestPoint(point) + return (point - closest):length() +end + +---将点投影到线段所在的直线上 +---@param point foundation.math.Vector2 要投影的点 +---@return foundation.math.Vector2 投影点 +function Line:projectPoint(point) + local dir = self.v2 - self.v1 + local len = dir:length() + if len == 0 then + return Vector2.create(self.v1.x, self.v1.y) + end + + local t = ((point.x - self.v1.x) * dir.x + (point.y - self.v1.y) * dir.y) / (len * len) + return Vector2.create(self.v1.x + t * dir.x, self.v1.y + t * dir.y) +end + +---检查点是否在线段上 +---@param point foundation.math.Vector2 要检查的点 +---@param tolerance number|nil 容差,默认为1e-10 +---@return boolean 点是否在线段上 +---@overload fun(self:foundation.shape.Line, point:foundation.math.Vector2): boolean +function Line:isPointOnLine(point, tolerance) + tolerance = tolerance or 1e-10 + local dist = self:distanceToPoint(point) + if dist > tolerance then + return false + end + + local dir = self.v2 - self.v1 + local len = dir:length() + if len == 0 then + return point.x == self.v1.x and point.y == self.v1.y + end + + local t = ((point.x - self.v1.x) * dir.x + (point.y - self.v1.y) * dir.y) / (len * len) + return t >= 0 and t <= 1 +end + +ffi.metatype("Line", Line) + +return Line \ No newline at end of file From 868ed00630e3245e9bad71dc8679630cb35228c5 Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 21:31:34 +0800 Subject: [PATCH 12/71] rename Line class to Segment and update methods accordingly --- .../shape/{Line.lua => Segment.lua} | 96 +++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) rename game/packages/thlib-scripts-v2/foundation/shape/{Line.lua => Segment.lua} (75%) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua similarity index 75% rename from game/packages/thlib-scripts-v2/foundation/shape/Line.lua rename to game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index 74583626..172f36ec 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -10,60 +10,60 @@ local Vector2 = require("foundation.math.Vector2") ffi.cdef [[ typedef struct { Vector2 v1, v2; -} Line; +} Segment; ]] ----@class foundation.shape.Line +---@class foundation.shape.Segment ---@field v1 foundation.math.Vector2 ---@field v2 foundation.math.Vector2 -local Line = {} -Line.__index = Line +local Segment = {} +Segment.__index = Segment ---创建一个线段 ---@param v1 foundation.math.Vector2 线段的起点 ---@param v2 foundation.math.Vector2 线段的终点 ----@return foundation.shape.Line -function Line.create(v1, v2) +---@return foundation.shape.Segment +function Segment.create(v1, v2) ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("Line", v1, v2) + return ffi.new("Segment", v1, v2) end ---根据给定点和弧度与长度创建线段 ---@param point foundation.math.Vector2 线段的起点 ---@param rad number 线段的弧度 ---@param length number 线段的长度 ----@return foundation.shape.Line -function Line.createFromPointAndRad(point, rad, length) +---@return foundation.shape.Segment +function Segment.createFromPointAndRad(point, rad, length) local v2 = Vector2.create(point.x + math.cos(rad) * length, point.y + math.sin(rad) * length) - return Line.create(point, v2) + return Segment.create(point, v2) end ---根据给定点和角度与长度创建线段 ---@param point foundation.math.Vector2 线段的起点 ---@param angle number 线段的角度 ---@param length number 线段的长度 ----@return foundation.shape.Line -function Line.createFromPointAndAngle(point, angle, length) +---@return foundation.shape.Segment +function Segment.createFromPointAndAngle(point, angle, length) local rad = math.rad(angle) - return Line.createFromPointAndRad(point, rad, length) + return Segment.createFromPointAndRad(point, rad, length) end ---线段转字符串表示 ----@param self foundation.shape.Line +---@param self foundation.shape.Segment ---@return string 线段的字符串表示 -function Line.__tostring(self) +function Segment.__tostring(self) return string.format("Line(%s, %s)", tostring(self.v1), tostring(self.v2)) end ---将线段转换为向量 ---@return foundation.math.Vector2 从起点到终点的向量 -function Line:toVector2() +function Segment:toVector2() return self.v2 - self.v1 end ---获取线段的法向量 ---@return foundation.math.Vector2 线段的单位法向量 -function Line:normal() +function Segment:normal() local dir = self.v2 - self.v1 local len = dir:length() if len == 0 then @@ -74,32 +74,32 @@ end ---获取线段的长度 ---@return number 线段的长度 -function Line:length() +function Segment:length() return self:toVector2():length() end ---获取线段的中点 ---@return foundation.math.Vector2 线段的中点 -function Line:midpoint() +function Segment:midpoint() return Vector2.create((self.v1.x + self.v2.x) / 2, (self.v1.y + self.v2.y) / 2) end ---获取线段的角度(弧度) ---@return number 线段的角度,单位为弧度 -function Line:angle() +function Segment:angle() return math.atan2(self.v2.y - self.v1.y, self.v2.x - self.v1.x) end ---获取线段的角度(度) ---@return number 线段的角度,单位为度 -function Line:degreeAngle() +function Segment:degreeAngle() return math.deg(self:angle()) end ---平移线段(更改当前线段) ---@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Line 平移后的线段(自身引用) -function Line:move(v) +---@return foundation.shape.Segment 平移后的线段(自身引用) +function Segment:move(v) local moveX, moveY if type(v) == "number" then moveX, moveY = v, v @@ -115,15 +115,15 @@ end ---获取当前线段平移指定距离的副本 ---@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Line 移动后的线段副本 -function Line:moved(v) +---@return foundation.shape.Segment 移动后的线段副本 +function Segment:moved(v) local moveX, moveY if type(v) == "number" then moveX, moveY = v, v else moveX, moveY = v.x, v.y end - return Line.create( + return Segment.create( Vector2.create(self.v1.x + moveX, self.v1.y + moveY), Vector2.create(self.v2.x + moveX, self.v2.y + moveY) ) @@ -132,9 +132,9 @@ end ---将当前线段旋转指定弧度(更改当前线段) ---@param rad number 旋转弧度 ---@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Line 旋转后的线段(自身引用) ----@overload fun(self:foundation.shape.Line, rad:number): foundation.shape.Line 将线段绕中点旋转指定弧度 -function Line:rotate(rad, center) +---@return foundation.shape.Segment 旋转后的线段(自身引用) +---@overload fun(self:foundation.shape.Segment, rad:number): foundation.shape.Segment 将线段绕中点旋转指定弧度 +function Segment:rotate(rad, center) center = center or self:midpoint() local cosRad = math.cos(rad) local sinRad = math.sin(rad) @@ -150,9 +150,9 @@ end ---将当前线段旋转指定角度(更改当前线段) ---@param angle number 旋转角度 ---@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Line 旋转后的线段(自身引用) ----@overload fun(self:foundation.shape.Line, angle:number): foundation.shape.Line 将线段绕中点旋转指定角度 -function Line:degreeRotate(angle, center) +---@return foundation.shape.Segment 旋转后的线段(自身引用) +---@overload fun(self:foundation.shape.Segment, angle:number): foundation.shape.Segment 将线段绕中点旋转指定角度 +function Segment:degreeRotate(angle, center) angle = math.rad(angle) return self:rotate(angle, center) end @@ -160,15 +160,15 @@ end ---获取当前线段旋转指定弧度的副本 ---@param rad number 旋转弧度 ---@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Line 旋转后的线段副本 ----@overload fun(self:foundation.shape.Line, rad:number): foundation.shape.Line 将线段绕中点旋转指定弧度 -function Line:rotated(rad, center) +---@return foundation.shape.Segment 旋转后的线段副本 +---@overload fun(self:foundation.shape.Segment, rad:number): foundation.shape.Segment 将线段绕中点旋转指定弧度 +function Segment:rotated(rad, center) center = center or self:midpoint() local cosRad = math.cos(rad) local sinRad = math.sin(rad) local v1 = self.v1 - center local v2 = self.v2 - center - return Line.create( + return Segment.create( Vector2.create(v1.x * cosRad - v1.y * sinRad + center.x, v1.x * sinRad + v1.y * cosRad + center.y), Vector2.create(v2.x * cosRad - v2.y * sinRad + center.x, v2.x * sinRad + v2.y * cosRad + center.y) ) @@ -177,17 +177,17 @@ end ---获取当前线段旋转指定角度的副本 ---@param angle number 旋转角度 ---@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Line 旋转后的线段副本 ----@overload fun(self:foundation.shape.Line, angle:number): foundation.shape.Line 将线段绕中点旋转指定角度 -function Line:degreeRotated(angle, center) +---@return foundation.shape.Segment 旋转后的线段副本 +---@overload fun(self:foundation.shape.Segment, angle:number): foundation.shape.Segment 将线段绕中点旋转指定角度 +function Segment:degreeRotated(angle, center) angle = math.rad(angle) return self:rotated(angle, center) end ---检查线段是否与另一线段相交 ----@param other foundation.shape.Line 另一线段 +---@param other foundation.shape.Segment 另一线段 ---@return boolean, foundation.math.Vector2|nil -function Line:intersects(other) +function Segment:intersects(other) local a = self.v1 local b = self.v2 local c = other.v1 @@ -213,7 +213,7 @@ end ---计算点到线段的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 线段上最近的点 -function Line:closestPoint(point) +function Segment:closestPoint(point) local dir = self.v2 - self.v1 local len = dir:length() if len == 0 then @@ -229,7 +229,7 @@ end ---计算点到线段的距离 ---@param point foundation.math.Vector2 要检查的点 ---@return number 点到线段的距离 -function Line:distanceToPoint(point) +function Segment:distanceToPoint(point) local closest = self:closestPoint(point) return (point - closest):length() end @@ -237,7 +237,7 @@ end ---将点投影到线段所在的直线上 ---@param point foundation.math.Vector2 要投影的点 ---@return foundation.math.Vector2 投影点 -function Line:projectPoint(point) +function Segment:projectPoint(point) local dir = self.v2 - self.v1 local len = dir:length() if len == 0 then @@ -252,8 +252,8 @@ end ---@param point foundation.math.Vector2 要检查的点 ---@param tolerance number|nil 容差,默认为1e-10 ---@return boolean 点是否在线段上 ----@overload fun(self:foundation.shape.Line, point:foundation.math.Vector2): boolean -function Line:isPointOnLine(point, tolerance) +---@overload fun(self:foundation.shape.Segment, point:foundation.math.Vector2): boolean +function Segment:isPointOnLine(point, tolerance) tolerance = tolerance or 1e-10 local dist = self:distanceToPoint(point) if dist > tolerance then @@ -270,6 +270,6 @@ function Line:isPointOnLine(point, tolerance) return t >= 0 and t <= 1 end -ffi.metatype("Line", Line) +ffi.metatype("Segment", Segment) -return Line \ No newline at end of file +return Segment \ No newline at end of file From a6ec037c51a12ab5a8e14d16680af62d989dd53b Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 21:54:16 +0800 Subject: [PATCH 13/71] refactor: update struct names to include foundation prefix for consistency --- .../packages/thlib-scripts-v2/foundation/math/Vector2.lua | 6 +++--- .../packages/thlib-scripts-v2/foundation/math/Vector3.lua | 6 +++--- .../packages/thlib-scripts-v2/foundation/math/Vector4.lua | 6 +++--- .../thlib-scripts-v2/foundation/shape/Segment.lua | 8 ++++---- .../thlib-scripts-v2/foundation/shape/Triangle.lua | 8 ++++---- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index 74ade717..f7b71830 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -8,7 +8,7 @@ ffi.cdef [[ typedef struct { double x; double y; -} Vector2; +} foundation_math_Vector2; ]] ---@class foundation.math.Vector2 @@ -23,7 +23,7 @@ Vector2.__index = Vector2 ---@return foundation.math.Vector2 新创建的向量 function Vector2.create(x, y) ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("Vector2", x or 0, y or 0) + return ffi.new("foundation_math_Vector2", x or 0, y or 0) end ---通过给定的弧度和长度创建一个新的二维向量 @@ -239,6 +239,6 @@ do end --endregion -ffi.metatype("Vector2", Vector2) +ffi.metatype("foundation_math_Vector2", Vector2) return Vector2 diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua index 3d89b4f6..0c316e3e 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua @@ -9,7 +9,7 @@ typedef struct { double x; double y; double z; -} Vector3; +} foundation_math_Vector3; ]] ---@class foundation.math.Vector3 @@ -26,7 +26,7 @@ Vector3.__index = Vector3 ---@return foundation.math.Vector3 新创建的向量 function Vector3.create(x, y, z) ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("Vector3", x or 0, y or 0, z or 0) + return ffi.new("foundation_math_Vector3", x or 0, y or 0, z or 0) end ---向量加法运算符重载 @@ -144,6 +144,6 @@ function Vector3:normalized() return Vector3.create(self.x / len, self.y / len, self.z / len) end -ffi.metatype("Vector3", Vector3) +ffi.metatype("foundation_math_Vector3", Vector3) return Vector3 \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua index 2fbaaaf1..222370c7 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua @@ -10,7 +10,7 @@ typedef struct { double y; double z; double w; -} Vector4; +} foundation_math_Vector4; ]] ---@class foundation.math.Vector4 @@ -29,7 +29,7 @@ Vector4.__index = Vector4 ---@return foundation.math.Vector4 新创建的向量 function Vector4.create(x, y, z, w) ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("Vector4", x or 0, y or 0, z or 0, w or 0) + return ffi.new("foundation_math_Vector4", x or 0, y or 0, z or 0, w or 0) end ---向量加法运算符重载 @@ -148,6 +148,6 @@ function Vector4:normalized() return Vector4.create(self.x / len, self.y / len, self.z / len, self.w / len) end -ffi.metatype("Vector4", Vector4) +ffi.metatype("foundation_math_Vector4", Vector4) return Vector4 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index 172f36ec..cc8ede5d 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -9,8 +9,8 @@ local Vector2 = require("foundation.math.Vector2") ffi.cdef [[ typedef struct { - Vector2 v1, v2; -} Segment; + foundation_math_Vector2 v1, v2; +} foundation_shape_Segment; ]] ---@class foundation.shape.Segment @@ -25,7 +25,7 @@ Segment.__index = Segment ---@return foundation.shape.Segment function Segment.create(v1, v2) ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("Segment", v1, v2) + return ffi.new("foundation_shape_Segment", v1, v2) end ---根据给定点和弧度与长度创建线段 @@ -270,6 +270,6 @@ function Segment:isPointOnLine(point, tolerance) return t >= 0 and t <= 1 end -ffi.metatype("Segment", Segment) +ffi.metatype("foundation_shape_Segment", Segment) return Segment \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 12582d4f..ccde2c23 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -9,8 +9,8 @@ local Vector2 = require("foundation.math.Vector2") ffi.cdef [[ typedef struct { - Vector2 v1, v2, v3; -} Triangle; + foundation_math_Vector2 v1, v2, v3; +} foundation_shape_Triangle; ]] ---@class foundation.shape.Triangle @@ -27,7 +27,7 @@ Triangle.__index = Triangle ---@return foundation.shape.Triangle 新创建的三角形 function Triangle.create(v1, v2, v3) ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("Triangle", v1, v2, v3) + return ffi.new("foundation_shape_Triangle", v1, v2, v3) end ---三角形加法运算,可以是三角形与三角形相加,或者三角形与标量相加 @@ -332,6 +332,6 @@ function Triangle:contains(point) return (u >= 0) and (v >= 0) and (u + v <= 1) end -ffi.metatype("Triangle", Triangle) +ffi.metatype("foundation_shape_Triangle", Triangle) return Triangle \ No newline at end of file From 4163969fd21256da400d469a6be2687ce702163f Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 22:24:53 +0800 Subject: [PATCH 14/71] feat: add intersection methods for Segment and Triangle classes --- .../foundation/shape/Segment.lua | 90 +++++++++++- .../foundation/shape/Triangle.lua | 133 +++++++++--------- 2 files changed, 158 insertions(+), 65 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index cc8ede5d..c96548dc 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -48,6 +48,14 @@ function Segment.createFromPointAndAngle(point, angle, length) return Segment.createFromPointAndRad(point, rad, length) end +---线段相等比较 +---@param a foundation.shape.Segment 第一个线段 +---@param b foundation.shape.Segment 第二个线段 +---@return boolean 如果两个线段的所有顶点都相等则返回true,否则返回false +function Segment.__eq(a, b) + return a.v1 == b.v1 and a.v2 == b.v2 +end + ---线段转字符串表示 ---@param self foundation.shape.Segment ---@return string 线段的字符串表示 @@ -184,10 +192,61 @@ function Segment:degreeRotated(angle, center) return self:rotated(angle, center) end ----检查线段是否与另一线段相交 ----@param other foundation.shape.Segment 另一线段 +---将当前线段缩放指定倍数(更改当前线段) +---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2 缩放中心 +---@return foundation.shape.Segment 缩放后的线段(自身引用) +---@overload fun(self: foundation.shape.Segment, scale: number): foundation.shape.Segment 相对线段中点缩放指定倍数 +function Segment:scale(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + center = center or self:midpoint() + self.v1.x = (self.v1.x - center.x) * scaleX + center.x + self.v1.y = (self.v1.y - center.y) * scaleY + center.y + self.v2.x = (self.v2.x - center.x) * scaleX + center.x + self.v2.y = (self.v2.y - center.y) * scaleY + center.y + return self +end + +---获取线段的缩放指定倍数的副本 +---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2 缩放中心 +---@return foundation.shape.Segment 缩放后的线段副本 +---@overload fun(self: foundation.shape.Segment, scale: number): foundation.shape.Segment 相对线段中点缩放指定倍数 +function Segment:scaled(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + center = center or self:midpoint() + return Segment.create( + Vector2.create((self.v1.x - center.x) * scaleX + center.x, (self.v1.y - center.y) * scaleY + center.y), + Vector2.create((self.v2.x - center.x) * scaleX + center.x, (self.v2.y - center.y) * scaleY + center.y) + ) +end + +---检查线段是否与其他形状相交 +---@param other any 其他的形状 ---@return boolean, foundation.math.Vector2|nil function Segment:intersects(other) + if ffi.istype("foundation_shape_Segment", other) then + return self:__intersectToSegment(other) + elseif ffi.istype("foundation_shape_Triangle", other) then + return self:__intersectToTriangle(other) + end + return false, nil +end + +---检查线段是否与另一个线段相交 +---@param other foundation.shape.Segment 要检查的线段 +---@return boolean, foundation.math.Vector2|nil +function Segment:__intersectToSegment(other) local a = self.v1 local b = self.v2 local c = other.v1 @@ -210,6 +269,33 @@ function Segment:intersects(other) return false, nil end +---检查线段是否与三角形相交 +---@param other foundation.shape.Triangle 要检查的三角形 +---@return boolean, foundation.math.Vector2|nil +function Segment:__intersectToTriangle(other) + local edges = { + Segment.create(other.v1, other.v2), + Segment.create(other.v2, other.v3), + Segment.create(other.v3, other.v1) + } + for i = 1, #edges do + local edge = edges[i] + local isIntersect, intersectPoint = self:intersects(edge) + if isIntersect then + return true, intersectPoint + end + end + + if other:contains(self.v1) then + return true, Vector2.create(self.v1.x, self.v1.y) + end + if other:contains(self.v2) then + return true, Vector2.create(self.v2.x, self.v2.y) + end + + return false, nil +end + ---计算点到线段的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 线段上最近的点 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index ccde2c23..5569639a 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -6,6 +6,7 @@ local string = string local math = math local Vector2 = require("foundation.math.Vector2") +local Segment = require("foundation.shape.Segment") ffi.cdef [[ typedef struct { @@ -30,69 +31,6 @@ function Triangle.create(v1, v2, v3) return ffi.new("foundation_shape_Triangle", v1, v2, v3) end ----三角形加法运算,可以是三角形与三角形相加,或者三角形与标量相加 ----@param a foundation.shape.Triangle|number 第一个操作数 ----@param b foundation.shape.Triangle|number 第二个操作数 ----@return foundation.shape.Triangle 相加后的三角形 -function Triangle.__add(a, b) - if type(a) == "number" then - return Triangle.create(a + b.v1, a + b.v2, a + b.v3) - elseif type(b) == "number" then - return Triangle.create(a.v1 + b, a.v2 + b, a.v3 + b) - else - return Triangle.create(a.v1 + b.v1, a.v2 + b.v2, a.v3 + b.v3) - end -end - ----三角形减法运算,可以是三角形与三角形相减,或者三角形与标量相减 ----@param a foundation.shape.Triangle|number 第一个操作数 ----@param b foundation.shape.Triangle|number 第二个操作数 ----@return foundation.shape.Triangle 相减后的三角形 -function Triangle.__sub(a, b) - if type(a) == "number" then - return Triangle.create(a - b.v1, a - b.v2, a - b.v3) - elseif type(b) == "number" then - return Triangle.create(a.v1 - b, a.v2 - b, a.v3 - b) - else - return Triangle.create(a.v1 - b.v1, a.v2 - b.v2, a.v3 - b.v3) - end -end - ----三角形乘法运算,可以是三角形与三角形相乘,或者三角形与标量相乘 ----@param a foundation.shape.Triangle|number 第一个操作数 ----@param b foundation.shape.Triangle|number 第二个操作数 ----@return foundation.shape.Triangle 相乘后的三角形 -function Triangle.__mul(a, b) - if type(a) == "number" then - return Triangle.create(a * b.v1, a * b.v2, a * b.v3) - elseif type(b) == "number" then - return Triangle.create(a.v1 * b, a.v2 * b, a.v3 * b) - else - return Triangle.create(a.v1 * b.v1, a.v2 * b.v2, a.v3 * b.v3) - end -end - ----三角形除法运算,可以是三角形与三角形相除,或者三角形与标量相除 ----@param a foundation.shape.Triangle|number 第一个操作数 ----@param b foundation.shape.Triangle|number 第二个操作数 ----@return foundation.shape.Triangle 相除后的三角形 -function Triangle.__div(a, b) - if type(a) == "number" then - return Triangle.create(a / b.v1, a / b.v2, a / b.v3) - elseif type(b) == "number" then - return Triangle.create(a.v1 / b, a.v2 / b, a.v3 / b) - else - return Triangle.create(a.v1 / b.v1, a.v2 / b.v2, a.v3 / b.v3) - end -end - ----三角形取反运算 ----@param t foundation.shape.Triangle 要取反的三角形 ----@return foundation.shape.Triangle 取反后的三角形 -function Triangle.__unm(t) - return Triangle.create(-t.v1, -t.v2, -t.v3) -end - ---三角形相等比较 ---@param a foundation.shape.Triangle 第一个三角形 ---@param b foundation.shape.Triangle 第二个三角形 @@ -332,6 +270,75 @@ function Triangle:contains(point) return (u >= 0) and (v >= 0) and (u + v <= 1) end +---检查三角形是否与其他形状相交 +---@param other any 其他的形状 +---@return boolean, foundation.math.Vector2|nil +function Triangle:intersects(other) + if ffi.istype("foundation_shape_Segment", other) then + return self:__intersectToSegment(other) + elseif ffi.istype("foundation_shape_Triangle", other) then + return self:__intersectToTriangle(other) + end + return false, nil +end + +---检查三角形是否与线段相交 +---@param other foundation.shape.Segment 要检查的线段 +---@return boolean, foundation.math.Vector2|nil +function Triangle:__intersectToSegment(other) + local edges = { + Segment.create(self.v1, self.v2), + Segment.create(self.v2, self.v3), + Segment.create(self.v3, self.v1) + } + for i = 1, #edges do + local edge = edges[i] + local isIntersect, intersectPoint = edge:intersects(other) + if isIntersect then + return true, intersectPoint + end + end + + if self:contains(other.v1) then + return true, Vector2.create(other.v1.x, other.v1.y) + end + if self:contains(other.v2) then + return true, Vector2.create(other.v2.x, other.v2.y) + end + + return false, nil +end + +---检查三角形是否与另一个三角形相交 +---@param other foundation.shape.Triangle 要检查的三角形 +---@return boolean, foundation.math.Vector2|nil +function Segment:__intersectToTriangle(other) + local edges = { + Segment.create(other.v1, other.v2), + Segment.create(other.v2, other.v3), + Segment.create(other.v3, other.v1) + } + for i = 1, #edges do + local edge = edges[i] + local isIntersect, intersectPoint = self:intersects(edge) + if isIntersect then + return true, intersectPoint + end + end + + if other:contains(self.v1) then + return true, Vector2.create(self.v1.x, self.v1.y) + end + if other:contains(self.v2) then + return true, Vector2.create(self.v2.x, self.v2.y) + end + if other:contains(self.v3) then + return true, Vector2.create(self.v3.x, self.v3.y) + end + + return false, nil +end + ffi.metatype("foundation_shape_Triangle", Triangle) return Triangle \ No newline at end of file From 4294d26605e75be21ff87fe2013f17eb2261de31 Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 29 Apr 2025 23:35:12 +0800 Subject: [PATCH 15/71] feat: add Circle, Line, Ray classes and intersection methods for geometric operations --- .../foundation/math/Vector2.lua | 19 +- .../foundation/math/Vector3.lua | 15 +- .../foundation/math/Vector4.lua | 17 +- .../foundation/shape/Circle.lua | 226 +++++++++++++++++ .../foundation/shape/Line.lua | 217 ++++++++++++++++ .../thlib-scripts-v2/foundation/shape/Ray.lua | 216 ++++++++++++++++ .../foundation/shape/Segment.lua | 184 +++++++++----- .../foundation/shape/Triangle.lua | 233 ++++++++++++------ 8 files changed, 987 insertions(+), 140 deletions(-) create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Circle.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Line.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Ray.lua diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index f7b71830..4d0aebf6 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -16,6 +16,13 @@ typedef struct { ---@field y number Y坐标分量 local Vector2 = {} Vector2.__index = Vector2 +Vector2.__type = "foundation.math.Vector2" + +---创建一个零向量 +---@return foundation.math.Vector2 零向量 +function Vector2.zero() + return Vector2.create(0, 0) +end ---创建一个新的二维向量 ---@param x number|nil X坐标分量,默认为0 @@ -132,6 +139,12 @@ end Vector2.length = Vector2.__len +---获取向量的副本 +---@return foundation.math.Vector2 向量的副本 +function Vector2:clone() + return Vector2.create(self.x, self.y) +end + ---获取向量的角度(弧度) ---@return number 向量的角度,单位为弧度 function Vector2:angle() @@ -174,7 +187,7 @@ end function Vector2:normalized() local len = self:length() if len == 0 then - return Vector2.create(0, 0) + return Vector2.zero() end return Vector2.create(self.x / len, self.y / len) end @@ -224,7 +237,7 @@ do Vector2.LuaSTG = Vector2.length Vector2.Angle = Vector2.degreeAngle - ---归一化向量(LuaSTG 兼容版) + ---归一化向量(LuaSTG Evo 兼容) ---@return foundation.math.Vector2 归一化后的向量副本 function Vector2:Normalize() self:normalize() @@ -241,4 +254,4 @@ end ffi.metatype("foundation_math_Vector2", Vector2) -return Vector2 +return Vector2 \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua index 0c316e3e..1ed938d5 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua @@ -18,6 +18,13 @@ typedef struct { ---@field z number Z坐标分量 local Vector3 = {} Vector3.__index = Vector3 +Vector3.__type = "foundation.math.Vector3" + +---创建一个零向量 +---@return foundation.math.Vector3 零向量 +function Vector3.zero() + return Vector3.create(0, 0, 0) +end ---创建一个新的三维向量 ---@param x number|nil X坐标分量,默认为0 @@ -115,6 +122,12 @@ function Vector3.__len(v) end Vector3.length = Vector3.__len +---获取向量的副本 +---@return foundation.math.Vector3 向量的副本 +function Vector3:clone() + return Vector3.create(self.x, self.y, self.z) +end + ---计算两个向量的点积 ---@param other foundation.math.Vector3 另一个向量 ---@return number 两个向量的点积 @@ -139,7 +152,7 @@ end function Vector3:normalized() local len = self:length() if len == 0 then - return Vector3.create(0, 0, 0) + return Vector3.zero() end return Vector3.create(self.x / len, self.y / len, self.z / len) end diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua index 222370c7..52608f80 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua @@ -20,6 +20,13 @@ typedef struct { ---@field w number W坐标分量 local Vector4 = {} Vector4.__index = Vector4 +Vector4.__type = "foundation.math.Vector4" + +---创建一个零向量 +---@return foundation.math.Vector4 零向量 +function Vector4.zero() + return Vector4.create(0, 0, 0, 0) +end ---创建一个新的四维向量 ---@param x number|nil X坐标分量,默认为0 @@ -118,6 +125,12 @@ function Vector4.__len(v) end Vector4.length = Vector4.__len +---获取向量的副本 +---@return foundation.math.Vector4 向量的副本 +function Vector4:clone() + return Vector4.create(self.x, self.y, self.z, self.w) +end + ---计算两个向量的点积 ---@param other foundation.math.Vector4 另一个向量 ---@return number 两个向量的点积 @@ -143,11 +156,11 @@ end function Vector4:normalized() local len = self:length() if len == 0 then - return Vector4.create(0, 0, 0, 0) + return Vector4.zero() end return Vector4.create(self.x / len, self.y / len, self.z / len, self.w / len) end ffi.metatype("foundation_math_Vector4", Vector4) -return Vector4 +return Vector4 \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua new file mode 100644 index 00000000..4d38d8ba --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -0,0 +1,226 @@ +local ffi = require("ffi") +local math = math +local type = type +local tostring = tostring +local string = string + +local Vector2 = require("foundation.math.Vector2") +local Segment = require("foundation.shape.Segment") + +ffi.cdef [[ +typedef struct { + foundation_math_Vector2 center; + double radius; +} foundation_shape_Circle; +]] + +---@class foundation.shape.Circle +---@field center foundation.math.Vector2 圆心位置 +---@field radius number 圆的半径 +local Circle = {} +Circle.__index = Circle +Circle.__type = "foundation.shape.Circle" + +---创建一个新的圆 +---@param center foundation.math.Vector2 圆心位置 +---@param radius number 半径 +---@return foundation.shape.Circle +function Circle.create(center, radius) + ---@diagnostic disable-next-line: return-type-mismatch + return ffi.new("foundation_shape_Circle", center, radius) +end + +---圆相等比较 +---@param a foundation.shape.Circle +---@param b foundation.shape.Circle +---@return boolean +function Circle.__eq(a, b) + return a.center == b.center and math.abs(a.radius - b.radius) < 1e-10 +end + +---圆的字符串表示 +---@param self foundation.shape.Circle +---@return string +function Circle.__tostring(self) + return string.format("Circle(center=%s, radius=%f)", tostring(self.center), self.radius) +end + +---检查点是否在圆内或圆上 +---@param point foundation.math.Vector2 +---@return boolean +function Circle:contains(point) + return (point - self.center):length() <= self.radius +end + +---移动圆(修改当前圆) +---@param v foundation.math.Vector2 | number +---@return foundation.shape.Circle 自身引用 +function Circle:move(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + self.center.x = self.center.x + moveX + self.center.y = self.center.y + moveY + return self +end + +---获取移动后的圆副本 +---@param v foundation.math.Vector2 | number +---@return foundation.shape.Circle +function Circle:moved(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + return Circle.create(Vector2.create(self.center.x + moveX, self.center.y + moveY), self.radius) +end + +---缩放圆(修改当前圆) +---@param scale number 缩放比例 +---@return foundation.shape.Circle 自身引用 +function Circle:scale(scale) + self.radius = self.radius * scale + return self +end + +---获取缩放后的圆副本 +---@param scale number 缩放比例 +---@return foundation.shape.Circle +function Circle:scaled(scale) + return Circle.create(self.center, self.radius * scale) +end + +---检查与其他形状的相交 +---@param other any +---@return boolean, foundation.math.Vector2|nil +function Circle:intersects(other) + if other.__type == "foundation.shape.Segment" then + return self:__intersectToSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__intersectToTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__intersectToLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__intersectToRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__intersectToCircle(other) + end + return false, nil +end + +---检查与线段的相交 +---@param other foundation.shape.Segment +---@return boolean, foundation.math.Vector2|nil +function Circle:__intersectToSegment(other) + local closest = other:closestPoint(self.center) + if (closest - self.center):length() <= self.radius then + return true, closest + end + return false, nil +end + +---检查与三角形的相交 +---@param other foundation.shape.Triangle +---@return boolean, foundation.math.Vector2|nil +function Circle:__intersectToTriangle(other) + local edges = { + Segment.create(other.point1, other.point2), + Segment.create(other.point2, other.point3), + Segment.create(other.point3, other.point1) + } + for i = 1, #edges do + local isIntersect, intersectPoint = self:__intersectToSegment(edges[i]) + if isIntersect then + return true, intersectPoint + end + end + if other:contains(self.center) then + return true, self.center:clone() + end + if self:contains(other.point1) then + return true, other.point1:clone() + end + if self:contains(other.point2) then + return true, other.point2:clone() + end + if self:contains(other.point3) then + return true, other.point3:clone() + end + return false, nil +end + +---检查与直线的相交 +---@param other foundation.shape.Line +---@return boolean, foundation.math.Vector2|nil +function Circle:__intersectToLine(other) + local dir = other.direction + local len = dir:length() + if len == 0 then + return false, nil + end + dir = dir / len + local L = other.point - self.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - self.radius * self.radius + local discriminant = b * b - 4 * a * c + if discriminant < 0 then + return false, nil + end + local t = (-b - math.sqrt(discriminant)) / (2 * a) + local p1 = other.point + dir * t + return true, p1 +end + +---检查与射线的相交 +---@param other foundation.shape.Ray +---@return boolean, foundation.math.Vector2|nil +function Circle:__intersectToRay(other) + local dir = other.direction + local len = dir:length() + if len == 0 then + return false, nil + end + dir = dir / len + local L = other.point - self.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - self.radius * self.radius + local discriminant = b * b - 4 * a * c + if discriminant < 0 then + return false, nil + end + local t = (-b - math.sqrt(discriminant)) / (2 * a) + if t >= 0 then + local p1 = other.point + dir * t + return true, p1 + end + local t2 = (-b + math.sqrt(discriminant)) / (2 * a) + if t2 >= 0 then + local p2 = other.point + dir * t2 + return true, p2 + end + return false, nil +end + +---检查与另一个圆的相交 +---@param other foundation.shape.Circle +---@return boolean, foundation.math.Vector2|nil +function Circle:__intersectToCircle(other) + local d = (self.center - other.center):length() + if d <= self.radius + other.radius and d >= math.abs(self.radius - other.radius) then + local dir = (other.center - self.center):normalized() + local point = self.center + dir * self.radius + return true, point + end + return false, nil +end + +ffi.metatype("foundation_shape_Circle", Circle) + +return Circle \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua new file mode 100644 index 00000000..13101c6a --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -0,0 +1,217 @@ +local ffi = require("ffi") +local math = math +local tostring = tostring +local string = string + +local Vector2 = require("foundation.math.Vector2") +local Segment = require("foundation.shape.Segment") + +ffi.cdef [[ +typedef struct { + foundation_math_Vector2 point; + foundation_math_Vector2 direction; +} foundation_shape_Line; +]] + +---@class foundation.shape.Line +---@field point foundation.math.Vector2 直线上的一点 +---@field direction foundation.math.Vector2 直线的方向向量 +local Line = {} +Line.__index = Line +Line.__type = "foundation.shape.Line" + +---创建一条新的直线,由一个点和方向向量确定 +---@param point foundation.math.Vector2 直线上的点 +---@param direction foundation.math.Vector2 方向向量 +---@return foundation.shape.Line +function Line.create(point, direction) + ---@diagnostic disable-next-line: return-type-mismatch + return ffi.new("foundation_shape_Line", point, direction:normalized()) +end + +---根据两个点创建一条直线 +---@param p1 foundation.math.Vector2 第一个点 +---@param p2 foundation.math.Vector2 第二个点 +---@return foundation.shape.Line +function Line.createFromPoints(p1, p2) + local direction = p2 - p1 + return Line.create(p1, direction) +end + +---根据一个点、角度创建一条直线 +---@param point foundation.math.Vector2 起始点 +---@param angle number 角度(度) +---@return foundation.shape.Line +function Line.createFromPointAndAngle(point, angle) + local direction = Vector2.createFromAngle(angle, 1) + return Line.create(point, direction) +end + +---直线相等比较 +---@param a foundation.shape.Line +---@param b foundation.shape.Line +---@return boolean +function Line.__eq(a, b) + local dir_cross = a.direction:cross(b.direction) + if math.abs(dir_cross) > 1e-10 then + return false + end + local point_diff = b.point - a.point + return math.abs(point_diff:cross(a.direction)) < 1e-10 +end + +---直线的字符串表示 +---@param self foundation.shape.Line +---@return string +function Line.__tostring(self) + return string.format("Line(%s, dir=%s)", tostring(self.point), tostring(self.direction)) +end + +---获取直线上相对 point 指定距离的点 +---@param length number 距离 +---@return foundation.math.Vector2 +function Line:getPoint(length) + return self.point + self.direction * length +end + +---检查与其他形状的相交 +---@param other any +---@return boolean, foundation.math.Vector2|nil +function Line:intersects(other) + if other.__type == "foundation.shape.Segment" then + return self:__intersectToSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__intersectToTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__intersectToLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__intersectToRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__intersectToCircle(other) + end + return false, nil +end + +---检查与线段的相交 +---@param other foundation.shape.Segment +---@return boolean, foundation.math.Vector2|nil +function Line:__intersectToSegment(other) + local a = self.point + local b = self.point + self.direction + local c = other.point1 + local d = other.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if u >= 0 and u <= 1 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + return true, Vector2.create(x, y) + end + + return false, nil +end + +---检查与三角形的相交 +---@param other foundation.shape.Triangle +---@return boolean, foundation.math.Vector2|nil +function Line:__intersectToTriangle(other) + local edges = { + Segment.create(other.point1, other.point2), + Segment.create(other.point2, other.point3), + Segment.create(other.point3, other.point1) + } + for i = 1, #edges do + local isIntersect, intersectPoint = self:__intersectToSegment(edges[i]) + if isIntersect then + return true, intersectPoint + end + end + return false, nil +end + +---检查与另一条直线的相交 +---@param other foundation.shape.Line +---@return boolean, foundation.math.Vector2|nil +function Line:__intersectToLine(other) + local a = self.point + local b = self.point + self.direction + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + return true, Vector2.create(x, y) +end + +---检查与射线的相交 +---@param other foundation.shape.Ray +---@return boolean, foundation.math.Vector2|nil +function Line:__intersectToRay(other) + local a = self.point + local b = self.point + self.direction + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if u >= 0 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + return true, Vector2.create(x, y) + end + + return false, nil +end + +---检查与圆的相交 +---@param other foundation.shape.Circle +---@return boolean, foundation.math.Vector2|nil +function Line:__intersectToCircle(other) + local dir = self.direction + local len = dir:length() + if len == 0 then + return false, nil + end + dir = dir / len + local L = self.point - other.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - other.radius * other.radius + local discriminant = b * b - 4 * a * c + if discriminant < 0 then + return false, nil + end + local t = (-b - math.sqrt(discriminant)) / (2 * a) + local p1 = self.point + dir * t + --[[ -- 因为只求一个交点,所以不用判断其他解 + if discriminant == 0 then + return true, p1 + end + local t2 = (-b + math.sqrt(discriminant)) / (2 * a) + local p2 = self.point + dir * t2 + --]] + return true, p1 +end + +ffi.metatype("foundation_shape_Line", Line) + +return Line \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua new file mode 100644 index 00000000..cb26deaf --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -0,0 +1,216 @@ +local ffi = require("ffi") +local math = math +local tostring = tostring +local string = string + +local Vector2 = require("foundation.math.Vector2") +local Segment = require("foundation.shape.Segment") + +ffi.cdef [[ +typedef struct { + foundation_math_Vector2 point; + foundation_math_Vector2 direction; +} foundation_shape_Ray; +]] + +---@class foundation.shape.Ray +---@field point foundation.math.Vector2 射线的起始点 +---@field direction foundation.math.Vector2 射线的方向向量 +local Ray = {} +Ray.__index = Ray +Ray.__type = "foundation.shape.Ray" + +---创建一条新的射线,由起始点和方向向量确定 +---@param point foundation.math.Vector2 起始点 +---@param direction foundation.math.Vector2 方向向量 +---@return foundation.shape.Ray +function Ray.create(point, direction) + ---@diagnostic disable-next-line: return-type-mismatch + return ffi.new("foundation_shape_Ray", point, direction:normalized()) +end + +---根据起始点、角度创建射线 +---@param point foundation.math.Vector2 起始点 +---@param angle number 角度(度) +---@return foundation.shape.Ray +function Ray.createFromAngle(point, angle) + local direction = Vector2.createFromAngle(angle, 1) + return Ray.create(point, direction) +end + +---射线相等比较 +---@param a foundation.shape.Ray +---@param b foundation.shape.Ray +---@return boolean +function Ray.__eq(a, b) + return a.point == b.point and a.direction == b.direction +end + +---射线的字符串表示 +---@param self foundation.shape.Ray +---@return string +function Ray.__tostring(self) + return string.format("Ray(%s, dir=%s)", tostring(self.point), tostring(self.direction)) +end + +---获取射线上相对 point 指定距离的点 +---@param length number 距离 +---@return foundation.math.Vector2 +function Ray:getPoint(length) + return self.point + self.direction * length +end + +---检查与其他形状的相交 +---@param other any +---@return boolean, foundation.math.Vector2|nil +function Ray:intersects(other) + if other.__type == "foundation.shape.Segment" then + return self:__intersectToSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__intersectToTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__intersectToLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__intersectToRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__intersectToCircle(other) + end + return false, nil +end + +---检查与线段的相交 +---@param other foundation.shape.Segment +---@return boolean, foundation.math.Vector2|nil +function Ray:__intersectToSegment(other) + local a = self.point + local b = self.point + self.direction + local c = other.point1 + local d = other.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if t >= 0 and u >= 0 and u <= 1 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + return true, Vector2.create(x, y) + end + + return false, nil +end + +---检查与三角形的相交 +---@param other foundation.shape.Triangle +---@return boolean, foundation.math.Vector2|nil +function Ray:__intersectToTriangle(other) + local edges = { + Segment.create(other.point1, other.point2), + Segment.create(other.point2, other.point3), + Segment.create(other.point3, other.point1) + } + local closestPoint, minT = nil, math.huge + for i = 1, #edges do + local isIntersect, intersectPoint = self:__intersectToSegment(edges[i]) + if isIntersect then + local t = ((intersectPoint.x - self.point.x) * self.direction.x + (intersectPoint.y - self.point.y) * self.direction.y) / (self.direction:length() ^ 2) + if t < minT then + minT = t + closestPoint = intersectPoint + end + end + end + if closestPoint then + return true, closestPoint + end + return false, nil +end + +---检查与直线的相交 +---@param other foundation.shape.Line +---@return boolean, foundation.math.Vector2|nil +function Ray:__intersectToLine(other) + local a = self.point + local b = self.point + self.direction + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + if t >= 0 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + return true, Vector2.create(x, y) + end + + return false, nil +end + +---检查与另一条射线的相交 +---@param other foundation.shape.Ray +---@return boolean, foundation.math.Vector2|nil +function Ray:__intersectToRay(other) + local a = self.point + local b = self.point + self.direction + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if t >= 0 and u >= 0 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + return true, Vector2.create(x, y) + end + + return false, nil +end + +---检查与圆的相交 +---@param other foundation.shape.Circle +---@return boolean, foundation.math.Vector2|nil +function Ray:__intersectToCircle(other) + local dir = self.direction + local len = dir:length() + if len == 0 then + return false, nil + end + dir = dir / len + local L = self.point - other.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - other.radius * other.radius + local discriminant = b * b - 4 * a * c + if discriminant < 0 then + return false, nil + end + local t = (-b - math.sqrt(discriminant)) / (2 * a) + if t >= 0 then + local p1 = self.point + dir * t + return true, p1 + end + local t2 = (-b + math.sqrt(discriminant)) / (2 * a) + if t2 >= 0 then + local p2 = self.point + dir * t2 + return true, p2 + end + return false, nil +end + +ffi.metatype("foundation_shape_Ray", Ray) + +return Ray \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index c96548dc..83239e59 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -9,23 +9,24 @@ local Vector2 = require("foundation.math.Vector2") ffi.cdef [[ typedef struct { - foundation_math_Vector2 v1, v2; + foundation_math_Vector2 point1, point2; } foundation_shape_Segment; ]] ---@class foundation.shape.Segment ----@field v1 foundation.math.Vector2 ----@field v2 foundation.math.Vector2 +---@field point1 foundation.math.Vector2 +---@field point2 foundation.math.Vector2 local Segment = {} Segment.__index = Segment +Segment.__type = "foundation.shape.Segment" ---创建一个线段 ----@param v1 foundation.math.Vector2 线段的起点 ----@param v2 foundation.math.Vector2 线段的终点 +---@param point1 foundation.math.Vector2 线段的起点 +---@param point2 foundation.math.Vector2 线段的终点 ---@return foundation.shape.Segment -function Segment.create(v1, v2) +function Segment.create(point1, point2) ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("foundation_shape_Segment", v1, v2) + return ffi.new("foundation_shape_Segment", point1, point2) end ---根据给定点和弧度与长度创建线段 @@ -53,29 +54,29 @@ end ---@param b foundation.shape.Segment 第二个线段 ---@return boolean 如果两个线段的所有顶点都相等则返回true,否则返回false function Segment.__eq(a, b) - return a.v1 == b.v1 and a.v2 == b.v2 + return a.point1 == b.point1 and a.point2 == b.point2 end ---线段转字符串表示 ---@param self foundation.shape.Segment ---@return string 线段的字符串表示 function Segment.__tostring(self) - return string.format("Line(%s, %s)", tostring(self.v1), tostring(self.v2)) + return string.format("Line(%s, %s)", tostring(self.point1), tostring(self.point2)) end ---将线段转换为向量 ---@return foundation.math.Vector2 从起点到终点的向量 function Segment:toVector2() - return self.v2 - self.v1 + return self.point2 - self.point1 end ---获取线段的法向量 ---@return foundation.math.Vector2 线段的单位法向量 function Segment:normal() - local dir = self.v2 - self.v1 + local dir = self.point2 - self.point1 local len = dir:length() if len == 0 then - return Vector2.create(0, 0) + return Vector2.zero() end return Vector2.create(-dir.y / len, dir.x / len) end @@ -89,13 +90,13 @@ end ---获取线段的中点 ---@return foundation.math.Vector2 线段的中点 function Segment:midpoint() - return Vector2.create((self.v1.x + self.v2.x) / 2, (self.v1.y + self.v2.y) / 2) + return Vector2.create((self.point1.x + self.point2.x) / 2, (self.point1.y + self.point2.y) / 2) end ---获取线段的角度(弧度) ---@return number 线段的角度,单位为弧度 function Segment:angle() - return math.atan2(self.v2.y - self.v1.y, self.v2.x - self.v1.x) + return math.atan2(self.point2.y - self.point1.y, self.point2.x - self.point1.x) end ---获取线段的角度(度) @@ -114,10 +115,10 @@ function Segment:move(v) else moveX, moveY = v.x, v.y end - self.v1.x = self.v1.x + moveX - self.v1.y = self.v1.y + moveY - self.v2.x = self.v2.x + moveX - self.v2.y = self.v2.y + moveY + self.point1.x = self.point1.x + moveX + self.point1.y = self.point1.y + moveY + self.point2.x = self.point2.x + moveX + self.point2.y = self.point2.y + moveY return self end @@ -132,8 +133,8 @@ function Segment:moved(v) moveX, moveY = v.x, v.y end return Segment.create( - Vector2.create(self.v1.x + moveX, self.v1.y + moveY), - Vector2.create(self.v2.x + moveX, self.v2.y + moveY) + Vector2.create(self.point1.x + moveX, self.point1.y + moveY), + Vector2.create(self.point2.x + moveX, self.point2.y + moveY) ) end @@ -146,12 +147,12 @@ function Segment:rotate(rad, center) center = center or self:midpoint() local cosRad = math.cos(rad) local sinRad = math.sin(rad) - local v1 = self.v1 - center - local v2 = self.v2 - center - self.v1.x = v1.x * cosRad - v1.y * sinRad + center.x - self.v1.y = v1.x * sinRad + v1.y * cosRad + center.y - self.v2.x = v2.x * cosRad - v2.y * sinRad + center.x - self.v2.y = v2.x * sinRad + v2.y * cosRad + center.y + local v1 = self.point1 - center + local v2 = self.point2 - center + self.point1.x = v1.x * cosRad - v1.y * sinRad + center.x + self.point1.y = v1.x * sinRad + v1.y * cosRad + center.y + self.point2.x = v2.x * cosRad - v2.y * sinRad + center.x + self.point2.y = v2.x * sinRad + v2.y * cosRad + center.y return self end @@ -174,8 +175,8 @@ function Segment:rotated(rad, center) center = center or self:midpoint() local cosRad = math.cos(rad) local sinRad = math.sin(rad) - local v1 = self.v1 - center - local v2 = self.v2 - center + local v1 = self.point1 - center + local v2 = self.point2 - center return Segment.create( Vector2.create(v1.x * cosRad - v1.y * sinRad + center.x, v1.x * sinRad + v1.y * cosRad + center.y), Vector2.create(v2.x * cosRad - v2.y * sinRad + center.x, v2.x * sinRad + v2.y * cosRad + center.y) @@ -205,10 +206,10 @@ function Segment:scale(scale, center) scaleX, scaleY = scale.x, scale.y end center = center or self:midpoint() - self.v1.x = (self.v1.x - center.x) * scaleX + center.x - self.v1.y = (self.v1.y - center.y) * scaleY + center.y - self.v2.x = (self.v2.x - center.x) * scaleX + center.x - self.v2.y = (self.v2.y - center.y) * scaleY + center.y + self.point1.x = (self.point1.x - center.x) * scaleX + center.x + self.point1.y = (self.point1.y - center.y) * scaleY + center.y + self.point2.x = (self.point2.x - center.x) * scaleX + center.x + self.point2.y = (self.point2.y - center.y) * scaleY + center.y return self end @@ -226,8 +227,8 @@ function Segment:scaled(scale, center) end center = center or self:midpoint() return Segment.create( - Vector2.create((self.v1.x - center.x) * scaleX + center.x, (self.v1.y - center.y) * scaleY + center.y), - Vector2.create((self.v2.x - center.x) * scaleX + center.x, (self.v2.y - center.y) * scaleY + center.y) + Vector2.create((self.point1.x - center.x) * scaleX + center.x, (self.point1.y - center.y) * scaleY + center.y), + Vector2.create((self.point2.x - center.x) * scaleX + center.x, (self.point2.y - center.y) * scaleY + center.y) ) end @@ -235,10 +236,16 @@ end ---@param other any 其他的形状 ---@return boolean, foundation.math.Vector2|nil function Segment:intersects(other) - if ffi.istype("foundation_shape_Segment", other) then + if other.__type == "foundation.shape.Segment" then return self:__intersectToSegment(other) - elseif ffi.istype("foundation_shape_Triangle", other) then + elseif other.__type == "foundation.shape.Triangle" then return self:__intersectToTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__intersectToLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__intersectToRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__intersectToCircle(other) end return false, nil end @@ -247,10 +254,10 @@ end ---@param other foundation.shape.Segment 要检查的线段 ---@return boolean, foundation.math.Vector2|nil function Segment:__intersectToSegment(other) - local a = self.v1 - local b = self.v2 - local c = other.v1 - local d = other.v2 + local a = self.point1 + local b = self.point2 + local c = other.point1 + local d = other.point2 local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) if math.abs(denom) < 1e-10 then @@ -274,42 +281,101 @@ end ---@return boolean, foundation.math.Vector2|nil function Segment:__intersectToTriangle(other) local edges = { - Segment.create(other.v1, other.v2), - Segment.create(other.v2, other.v3), - Segment.create(other.v3, other.v1) + Segment.create(other.point1, other.point2), + Segment.create(other.point2, other.point3), + Segment.create(other.point3, other.point1) } for i = 1, #edges do local edge = edges[i] - local isIntersect, intersectPoint = self:intersects(edge) + local isIntersect, intersectPoint = self:__intersectToSegment(edge) if isIntersect then return true, intersectPoint end end + if other:contains(self.point1) then + return true, self.point1:clone() + end + if other:contains(self.point2) then + return true, self.point2:clone() + end + return false, nil +end + +---检查线段是否与直线相交 +---@param other foundation.shape.Line 要检查的直线 +---@return boolean, foundation.math.Vector2|nil +function Segment:__intersectToLine(other) + local a = self.point1 + local b = self.point2 + local c = other.point + local d = other.point + other.direction - if other:contains(self.v1) then - return true, Vector2.create(self.v1.x, self.v1.y) + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, nil end - if other:contains(self.v2) then - return true, Vector2.create(self.v2.x, self.v2.y) + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + if t >= 0 and t <= 1 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + return true, Vector2.create(x, y) end return false, nil end +---检查线段是否与射线相交 +---@param other foundation.shape.Ray 要检查的射线 +---@return boolean, foundation.math.Vector2|nil +function Segment:__intersectToRay(other) + local a = self.point1 + local b = self.point2 + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if t >= 0 and t <= 1 and u >= 0 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + return true, Vector2.create(x, y) + end + + return false, nil +end + +---检查线段是否与圆相交 +---@param other foundation.shape.Circle 要检查的圆 +---@return boolean, foundation.math.Vector2|nil +function Segment:__intersectToCircle(other) + local closest = self:closestPoint(other.center) + if (closest - other.center):length() <= other.radius then + return true, closest + end + return false, nil +end + ---计算点到线段的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 线段上最近的点 function Segment:closestPoint(point) - local dir = self.v2 - self.v1 + local dir = self.point2 - self.point1 local len = dir:length() if len == 0 then - return Vector2.create(self.v1.x, self.v1.y) + return self.point1:clone() end - local t = ((point.x - self.v1.x) * dir.x + (point.y - self.v1.y) * dir.y) / (len * len) + local t = ((point.x - self.point1.x) * dir.x + (point.y - self.point1.y) * dir.y) / (len * len) t = math.max(0, math.min(1, t)) - return Vector2.create(self.v1.x + t * dir.x, self.v1.y + t * dir.y) + return Vector2.create(self.point1.x + t * dir.x, self.point1.y + t * dir.y) end ---计算点到线段的距离 @@ -324,14 +390,14 @@ end ---@param point foundation.math.Vector2 要投影的点 ---@return foundation.math.Vector2 投影点 function Segment:projectPoint(point) - local dir = self.v2 - self.v1 + local dir = self.point2 - self.point1 local len = dir:length() if len == 0 then - return Vector2.create(self.v1.x, self.v1.y) + return self.point1:clone() end - local t = ((point.x - self.v1.x) * dir.x + (point.y - self.v1.y) * dir.y) / (len * len) - return Vector2.create(self.v1.x + t * dir.x, self.v1.y + t * dir.y) + local t = ((point.x - self.point1.x) * dir.x + (point.y - self.point1.y) * dir.y) / (len * len) + return Vector2.create(self.point1.x + t * dir.x, self.point1.y + t * dir.y) end ---检查点是否在线段上 @@ -346,13 +412,13 @@ function Segment:isPointOnLine(point, tolerance) return false end - local dir = self.v2 - self.v1 + local dir = self.point2 - self.point1 local len = dir:length() if len == 0 then - return point.x == self.v1.x and point.y == self.v1.y + return point.x == self.point1.x and point.y == self.point1.y end - local t = ((point.x - self.v1.x) * dir.x + (point.y - self.v1.y) * dir.y) / (len * len) + local t = ((point.x - self.point1.x) * dir.x + (point.y - self.point1.y) * dir.y) / (len * len) return t >= 0 and t <= 1 end diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 5569639a..7f33653e 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -10,16 +10,17 @@ local Segment = require("foundation.shape.Segment") ffi.cdef [[ typedef struct { - foundation_math_Vector2 v1, v2, v3; + foundation_math_Vector2 point1, point2, point3; } foundation_shape_Triangle; ]] ---@class foundation.shape.Triangle ----@field v1 foundation.math.Vector2 三角形的第一个顶点 ----@field v2 foundation.math.Vector2 三角形的第二个顶点 ----@field v3 foundation.math.Vector2 三角形的第三个顶点 +---@field point1 foundation.math.Vector2 三角形的第一个顶点 +---@field point2 foundation.math.Vector2 三角形的第二个顶点 +---@field point3 foundation.math.Vector2 三角形的第三个顶点 local Triangle = {} Triangle.__index = Triangle +Triangle.__type = "foundation.shape.Triangle" ---创建一个新的三角形 ---@param v1 foundation.math.Vector2 三角形的第一个顶点 @@ -36,28 +37,28 @@ end ---@param b foundation.shape.Triangle 第二个三角形 ---@return boolean 如果两个三角形的所有顶点都相等则返回true,否则返回false function Triangle.__eq(a, b) - return a.v1 == b.v1 and a.v2 == b.v2 and a.v3 == b.v3 + return a.point1 == b.point1 and a.point2 == b.point2 and a.point3 == b.point3 end ---三角形转字符串表示 ---@param t foundation.shape.Triangle 要转换的三角形 ---@return string 三角形的字符串表示 function Triangle.__tostring(t) - return string.format("Triangle(%s, %s, %s)", tostring(t.v1), tostring(t.v2), tostring(t.v3)) + return string.format("Triangle(%s, %s, %s)", tostring(t.point1), tostring(t.point2), tostring(t.point3)) end ---计算三角形的面积 ---@return number 三角形的面积 function Triangle:area() - local v2v1 = self.v2 - self.v1 - local v3v1 = self.v3 - self.v1 + local v2v1 = self.point2 - self.point1 + local v3v1 = self.point3 - self.point1 return 0.5 * math.abs(v2v1:cross(v3v1)) end ---计算三角形的重心 ---@return foundation.math.Vector2 三角形的重心 function Triangle:centroid() - return (self.v1 + self.v2 + self.v3) / 3 + return (self.point1 + self.point2 + self.point3) / 3 end ---计算三角形的外接圆半径 @@ -65,7 +66,7 @@ end function Triangle:circumradius() local center = self:circumcenter() if center then - return (center - self.v1):length() + return (center - self.point1):length() end return 0 end @@ -73,18 +74,18 @@ end ---计算三角形的内切圆半径 ---@return number 三角形的内切圆半径 function Triangle:inradius() - local a = (self.v2 - self.v3):length() - local b = (self.v1 - self.v3):length() - local c = (self.v1 - self.v2):length() + local a = (self.point2 - self.point3):length() + local b = (self.point1 - self.point3):length() + local c = (self.point1 - self.point2):length() return self:area() / ((a + b + c) / 2) end ---计算三角形的外心 ---@return foundation.math.Vector2 | nil 三角形的外心 function Triangle:circumcenter() - local x1, y1 = self.v1.x, self.v1.y - local x2, y2 = self.v2.x, self.v2.y - local x3, y3 = self.v3.x, self.v3.y + local x1, y1 = self.point1.x, self.point1.y + local x2, y2 = self.point2.x, self.point2.y + local x3, y3 = self.point3.x, self.point3.y local D = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2) if math.abs(D) < 1e-10 then @@ -103,11 +104,11 @@ end ---计算三角形的内心 ---@return foundation.math.Vector2 三角形的内心 function Triangle:incenter() - local a = (self.v2 - self.v3):length() - local b = (self.v1 - self.v3):length() - local c = (self.v1 - self.v2):length() + local a = (self.point2 - self.point3):length() + local b = (self.point1 - self.point3):length() + local c = (self.point1 - self.point2):length() - local p = (a * self.v1 + b * self.v2 + c * self.v3) / (a + b + c) + local p = (a * self.point1 + b * self.point2 + c * self.point3) / (a + b + c) return p end @@ -121,12 +122,12 @@ function Triangle:move(v) else moveX, moveY = v.x, v.y end - self.v1.x = self.v1.x + moveX - self.v1.y = self.v1.y + moveY - self.v2.x = self.v2.x + moveX - self.v2.y = self.v2.y + moveY - self.v3.x = self.v3.x + moveX - self.v3.y = self.v3.y + moveY + self.point1.x = self.point1.x + moveX + self.point1.y = self.point1.y + moveY + self.point2.x = self.point2.x + moveX + self.point2.y = self.point2.y + moveY + self.point3.x = self.point3.x + moveX + self.point3.y = self.point3.y + moveY return self end @@ -141,9 +142,9 @@ function Triangle:moved(v) moveX, moveY = v.x, v.y end return Triangle.create( - Vector2.create(self.v1.x + moveX, self.v1.y + moveY), - Vector2.create(self.v2.x + moveX, self.v2.y + moveY), - Vector2.create(self.v3.x + moveX, self.v3.y + moveY) + Vector2.create(self.point1.x + moveX, self.point1.y + moveY), + Vector2.create(self.point2.x + moveX, self.point2.y + moveY), + Vector2.create(self.point3.x + moveX, self.point3.y + moveY) ) end @@ -156,15 +157,15 @@ function Triangle:rotate(rad, center) center = center or self:centroid() local cosAngle = math.cos(rad) local sinAngle = math.sin(rad) - local v1 = self.v1 - center - local v2 = self.v2 - center - local v3 = self.v3 - center - self.v1.x = v1.x * cosAngle - v1.y * sinAngle + center.x - self.v1.y = v1.x * sinAngle + v1.y * cosAngle + center.y - self.v2.x = v2.x * cosAngle - v2.y * sinAngle + center.x - self.v2.y = v2.x * sinAngle + v2.y * cosAngle + center.y - self.v3.x = v3.x * cosAngle - v3.y * sinAngle + center.x - self.v3.y = v3.x * sinAngle + v3.y * cosAngle + center.y + local v1 = self.point1 - center + local v2 = self.point2 - center + local v3 = self.point3 - center + self.point1.x = v1.x * cosAngle - v1.y * sinAngle + center.x + self.point1.y = v1.x * sinAngle + v1.y * cosAngle + center.y + self.point2.x = v2.x * cosAngle - v2.y * sinAngle + center.x + self.point2.y = v2.x * sinAngle + v2.y * cosAngle + center.y + self.point3.x = v3.x * cosAngle - v3.y * sinAngle + center.x + self.point3.y = v3.x * sinAngle + v3.y * cosAngle + center.y return self end @@ -187,9 +188,9 @@ function Triangle:rotated(rad, center) center = center or self:centroid() local cosAngle = math.cos(rad) local sinAngle = math.sin(rad) - local v1 = self.v1 - center - local v2 = self.v2 - center - local v3 = self.v3 - center + local v1 = self.point1 - center + local v2 = self.point2 - center + local v3 = self.point3 - center return Triangle.create( Vector2.create(v1.x * cosAngle - v1.y * sinAngle + center.x, v1.x * sinAngle + v1.y * cosAngle + center.y), Vector2.create(v2.x * cosAngle - v2.y * sinAngle + center.x, v2.x * sinAngle + v2.y * cosAngle + center.y), @@ -220,12 +221,12 @@ function Triangle:scale(scale, center) scaleX, scaleY = scale.x, scale.y end center = center or self:centroid() - self.v1.x = (self.v1.x - center.x) * scaleX + center.x - self.v1.y = (self.v1.y - center.y) * scaleY + center.y - self.v2.x = (self.v2.x - center.x) * scaleX + center.x - self.v2.y = (self.v2.y - center.y) * scaleY + center.y - self.v3.x = (self.v3.x - center.x) * scaleX + center.x - self.v3.y = (self.v3.y - center.y) * scaleY + center.y + self.point1.x = (self.point1.x - center.x) * scaleX + center.x + self.point1.y = (self.point1.y - center.y) * scaleY + center.y + self.point2.x = (self.point2.x - center.x) * scaleX + center.x + self.point2.y = (self.point2.y - center.y) * scaleY + center.y + self.point3.x = (self.point3.x - center.x) * scaleX + center.x + self.point3.y = (self.point3.y - center.y) * scaleY + center.y return self end @@ -243,9 +244,9 @@ function Triangle:scaled(scale, center) end center = center or self:centroid() return Triangle.create( - Vector2.create((self.v1.x - center.x) * scaleX + center.x, (self.v1.y - center.y) * scaleY + center.y), - Vector2.create((self.v2.x - center.x) * scaleX + center.x, (self.v2.y - center.y) * scaleY + center.y), - Vector2.create((self.v3.x - center.x) * scaleX + center.x, (self.v3.y - center.y) * scaleY + center.y) + Vector2.create((self.point1.x - center.x) * scaleX + center.x, (self.point1.y - center.y) * scaleY + center.y), + Vector2.create((self.point2.x - center.x) * scaleX + center.x, (self.point2.y - center.y) * scaleY + center.y), + Vector2.create((self.point3.x - center.x) * scaleX + center.x, (self.point3.y - center.y) * scaleY + center.y) ) end @@ -253,9 +254,9 @@ end ---@param point foundation.math.Vector2 要检查的点 ---@return boolean 如果点在三角形内(包括边界)返回true,否则返回false function Triangle:contains(point) - local v3v1 = self.v3 - self.v1 - local v2v1 = self.v2 - self.v1 - local pv1 = point - self.v1 + local v3v1 = self.point3 - self.point1 + local v2v1 = self.point2 - self.point1 + local pv1 = point - self.point1 local dot00 = v3v1:dot(v3v1) local dot01 = v3v1:dot(v2v1) @@ -274,10 +275,16 @@ end ---@param other any 其他的形状 ---@return boolean, foundation.math.Vector2|nil function Triangle:intersects(other) - if ffi.istype("foundation_shape_Segment", other) then + if other.__type == "foundation.shape.Segment" then return self:__intersectToSegment(other) - elseif ffi.istype("foundation_shape_Triangle", other) then + elseif other.__type == "foundation.shape.Triangle" then return self:__intersectToTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__intersectToLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__intersectToRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__intersectToCircle(other) end return false, nil end @@ -287,9 +294,9 @@ end ---@return boolean, foundation.math.Vector2|nil function Triangle:__intersectToSegment(other) local edges = { - Segment.create(self.v1, self.v2), - Segment.create(self.v2, self.v3), - Segment.create(self.v3, self.v1) + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) } for i = 1, #edges do local edge = edges[i] @@ -298,44 +305,120 @@ function Triangle:__intersectToSegment(other) return true, intersectPoint end end - - if self:contains(other.v1) then - return true, Vector2.create(other.v1.x, other.v1.y) + if self:contains(other.point1) then + return true, other.point1:clone() end - if self:contains(other.v2) then - return true, Vector2.create(other.v2.x, other.v2.y) + if self:contains(other.point2) then + return true, other.point2:clone() end - return false, nil end ---检查三角形是否与另一个三角形相交 ---@param other foundation.shape.Triangle 要检查的三角形 ---@return boolean, foundation.math.Vector2|nil -function Segment:__intersectToTriangle(other) +function Triangle:__intersectToTriangle(other) local edges = { - Segment.create(other.v1, other.v2), - Segment.create(other.v2, other.v3), - Segment.create(other.v3, other.v1) + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) } for i = 1, #edges do local edge = edges[i] - local isIntersect, intersectPoint = self:intersects(edge) + local isIntersect, intersectPoint = edge:intersects(other) if isIntersect then return true, intersectPoint end end + if self:contains(other.point1) then + return true, other.point1:clone() + end + if self:contains(other.point2) then + return true, other.point2:clone() + end + if self:contains(other.point3) then + return true, other.point3:clone() + end + return false, nil +end - if other:contains(self.v1) then - return true, Vector2.create(self.v1.x, self.v1.y) +---检查三角形是否与直线相交 +---@param other foundation.shape.Line 要检查的直线 +---@return boolean, foundation.math.Vector2|nil +function Triangle:__intersectToLine(other) + local edges = { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } + for i = 1, #edges do + local edge = edges[i] + local isIntersect, intersectPoint = edge:intersects(other) + if isIntersect then + return true, intersectPoint + end end - if other:contains(self.v2) then - return true, Vector2.create(self.v2.x, self.v2.y) + return false, nil +end + +---检查三角形是否与射线相交 +---@param other foundation.shape.Ray 要检查的射线 +---@return boolean, foundation.math.Vector2|nil +function Triangle:__intersectToRay(other) + local edges = { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } + local closestPoint, minT = nil, math.huge + for i = 1, #edges do + local edge = edges[i] + local isIntersect, intersectPoint = edge:intersects(other) + if isIntersect then + local t = ((intersectPoint.x - other.point.x) * other.direction.x + (intersectPoint.y - other.point.y) * other.direction.y) / (other.direction:length() ^ 2) + if t < minT then + minT = t + closestPoint = intersectPoint + end + end + end + if closestPoint then + return true, closestPoint end - if other:contains(self.v3) then - return true, Vector2.create(self.v3.x, self.v3.y) + if self:contains(other.point) then + return true, other.point:clone() end + return false, nil +end +---检查三角形是否与圆相交 +---@param other foundation.shape.Circle 要检查的圆 +---@return boolean, foundation.math.Vector2|nil +function Triangle:__intersectToCircle(other) + local edges = { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } + for i = 1, #edges do + local edge = edges[i] + local isIntersect, intersectPoint = edge:intersects(other) + if isIntersect then + return true, intersectPoint + end + end + if other:contains(self.point1) then + return true, self.point1:clone() + end + if other:contains(self.point2) then + return true, self.point2:clone() + end + if other:contains(self.point3) then + return true, self.point3:clone() + end + if self:contains(other.center) then + return true, other.center:clone() + end return false, nil end From a60abcf906ef773539bbaefc304652b78fb7ab42 Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 00:09:29 +0800 Subject: [PATCH 16/71] feat: update intersection methods to return multiple points for geometric shapes --- .../foundation/shape/Circle.lua | 145 +++++++++++---- .../foundation/shape/Line.lua | 99 ++++++++--- .../thlib-scripts-v2/foundation/shape/Ray.lua | 113 ++++++++---- .../foundation/shape/Segment.lua | 138 ++++++++++++--- .../foundation/shape/Triangle.lua | 166 ++++++++++++------ 5 files changed, 486 insertions(+), 175 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index 4d38d8ba..65d1a1f6 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -1,6 +1,8 @@ local ffi = require("ffi") + local math = math local type = type +local ipairs = ipairs local tostring = tostring local string = string @@ -97,7 +99,7 @@ end ---检查与其他形状的相交 ---@param other any ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Circle:intersects(other) if other.__type == "foundation.shape.Segment" then return self:__intersectToSegment(other) @@ -115,49 +117,99 @@ end ---检查与线段的相交 ---@param other foundation.shape.Segment ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Circle:__intersectToSegment(other) local closest = other:closestPoint(self.center) + local points = {} if (closest - self.center):length() <= self.radius then - return true, closest + local dir = other.point2 - other.point1 + local len = dir:length() + if len == 0 then + if (other.point1 - self.center):length() <= self.radius then + points[#points + 1] = other.point1:clone() + end + + if #points == 0 then + return false, nil + end + return true, points + end + dir = dir / len + local L = other.point1 - self.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - self.radius * self.radius + local discriminant = b * b - 4 * a * c + if discriminant >= 0 then + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + if t1 >= 0 and t1 <= len then + points[#points + 1] = other.point1 + dir * t1 + end + if t2 >= 0 and t2 <= len and math.abs(t2 - t1) > 1e-10 then + points[#points + 1] = other.point1 + dir * t2 + end + end end - return false, nil + + if #points == 0 then + return false, nil + end + return true, points end ---检查与三角形的相交 ---@param other foundation.shape.Triangle ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Circle:__intersectToTriangle(other) + local points = {} local edges = { Segment.create(other.point1, other.point2), Segment.create(other.point2, other.point3), Segment.create(other.point3, other.point1) } for i = 1, #edges do - local isIntersect, intersectPoint = self:__intersectToSegment(edges[i]) - if isIntersect then - return true, intersectPoint + local success, edge_points = self:__intersectToSegment(edges[i]) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end end end if other:contains(self.center) then - return true, self.center:clone() + points[#points + 1] = self.center:clone() end if self:contains(other.point1) then - return true, other.point1:clone() + points[#points + 1] = other.point1:clone() end if self:contains(other.point2) then - return true, other.point2:clone() + points[#points + 1] = other.point2:clone() end if self:contains(other.point3) then - return true, other.point3:clone() + points[#points + 1] = other.point3:clone() end - return false, nil + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + + if #unique_points == 0 then + return false, nil + end + return true, unique_points end ---检查与直线的相交 ---@param other foundation.shape.Line ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Circle:__intersectToLine(other) + local points = {} local dir = other.direction local len = dir:length() if len == 0 then @@ -169,18 +221,27 @@ function Circle:__intersectToLine(other) local b = 2 * L:dot(dir) local c = L:dot(L) - self.radius * self.radius local discriminant = b * b - 4 * a * c - if discriminant < 0 then + if discriminant >= 0 then + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + points[#points + 1] = other.point + dir * t1 + if math.abs(t2 - t1) > 1e-10 then + points[#points + 1] = other.point + dir * t2 + end + end + + if #points == 0 then return false, nil end - local t = (-b - math.sqrt(discriminant)) / (2 * a) - local p1 = other.point + dir * t - return true, p1 + return true, points end ---检查与射线的相交 ---@param other foundation.shape.Ray ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Circle:__intersectToRay(other) + local points = {} local dir = other.direction local len = dir:length() if len == 0 then @@ -192,33 +253,45 @@ function Circle:__intersectToRay(other) local b = 2 * L:dot(dir) local c = L:dot(L) - self.radius * self.radius local discriminant = b * b - 4 * a * c - if discriminant < 0 then - return false, nil - end - local t = (-b - math.sqrt(discriminant)) / (2 * a) - if t >= 0 then - local p1 = other.point + dir * t - return true, p1 + if discriminant >= 0 then + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + if t1 >= 0 then + points[#points + 1] = other.point + dir * t1 + end + if t2 >= 0 and math.abs(t2 - t1) > 1e-10 then + points[#points + 1] = other.point + dir * t2 + end end - local t2 = (-b + math.sqrt(discriminant)) / (2 * a) - if t2 >= 0 then - local p2 = other.point + dir * t2 - return true, p2 + + if #points == 0 then + return false, nil end - return false, nil + return true, points end ---检查与另一个圆的相交 ---@param other foundation.shape.Circle ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Circle:__intersectToCircle(other) + local points = {} local d = (self.center - other.center):length() if d <= self.radius + other.radius and d >= math.abs(self.radius - other.radius) then - local dir = (other.center - self.center):normalized() - local point = self.center + dir * self.radius - return true, point + local a = (self.radius * self.radius - other.radius * other.radius + d * d) / (2 * d) + local h = math.sqrt(self.radius * self.radius - a * a) + local p2 = self.center + (other.center - self.center) * (a / d) + local perp = Vector2.create(-(other.center.y - self.center.y), other.center.x - self.center.x):normalized() * h + points[#points + 1] = p2 + perp + if math.abs(h) > 1e-10 then + points[#points + 1] = p2 - perp + end end - return false, nil + + if #points == 0 then + return false, nil + end + return true, points end ffi.metatype("foundation_shape_Circle", Circle) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index 13101c6a..94eae931 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -1,5 +1,7 @@ local ffi = require("ffi") + local math = math +local ipairs = ipairs local tostring = tostring local string = string @@ -76,7 +78,7 @@ end ---检查与其他形状的相交 ---@param other any ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Line:intersects(other) if other.__type == "foundation.shape.Segment" then return self:__intersectToSegment(other) @@ -94,8 +96,9 @@ end ---检查与线段的相交 ---@param other foundation.shape.Segment ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Line:__intersectToSegment(other) + local points = {} local a = self.point local b = self.point + self.direction local c = other.point1 @@ -103,7 +106,7 @@ function Line:__intersectToSegment(other) local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) if math.abs(denom) < 1e-10 then - return false, nil + return false, points end local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom @@ -112,34 +115,54 @@ function Line:__intersectToSegment(other) if u >= 0 and u <= 1 then local x = a.x + t * (b.x - a.x) local y = a.y + t * (b.y - a.y) - return true, Vector2.create(x, y) + points[#points + 1] = Vector2.create(x, y) end - return false, nil + if #points == 0 then + return false, nil + end + return true, points end ---检查与三角形的相交 ---@param other foundation.shape.Triangle ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Line:__intersectToTriangle(other) + local points = {} local edges = { Segment.create(other.point1, other.point2), Segment.create(other.point2, other.point3), Segment.create(other.point3, other.point1) } for i = 1, #edges do - local isIntersect, intersectPoint = self:__intersectToSegment(edges[i]) - if isIntersect then - return true, intersectPoint + local success, edge_points = self:__intersectToSegment(edges[i]) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end end end - return false, nil + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + + if #unique_points == 0 then + return false, nil + end + return true, unique_points end ---检查与另一条直线的相交 ---@param other foundation.shape.Line ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Line:__intersectToLine(other) + local points = {} local a = self.point local b = self.point + self.direction local c = other.point @@ -147,19 +170,34 @@ function Line:__intersectToLine(other) local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) if math.abs(denom) < 1e-10 then - return false, nil + local dir_cross = self.direction:cross(other.direction) + if math.abs(dir_cross) < 1e-10 then + local point_diff = other.point - self.point + if math.abs(point_diff:cross(self.direction)) < 1e-10 then + points[#points + 1] = self.point:clone() + points[#points + 1] = self:getPoint(1) + end + end + + if #points == 0 then + return false, nil + end + return true, points end local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom local x = a.x + t * (b.x - a.x) local y = a.y + t * (b.y - a.y) - return true, Vector2.create(x, y) + points[#points + 1] = Vector2.create(x, y) + + return true, points end ---检查与射线的相交 ---@param other foundation.shape.Ray ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Line:__intersectToRay(other) + local points = {} local a = self.point local b = self.point + self.direction local c = other.point @@ -176,16 +214,20 @@ function Line:__intersectToRay(other) if u >= 0 then local x = a.x + t * (b.x - a.x) local y = a.y + t * (b.y - a.y) - return true, Vector2.create(x, y) + points[#points + 1] = Vector2.create(x, y) end - return false, nil + if #points == 0 then + return false, nil + end + return true, points end ---检查与圆的相交 ---@param other foundation.shape.Circle ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Line:__intersectToCircle(other) + local points = {} local dir = self.direction local len = dir:length() if len == 0 then @@ -197,19 +239,20 @@ function Line:__intersectToCircle(other) local b = 2 * L:dot(dir) local c = L:dot(L) - other.radius * other.radius local discriminant = b * b - 4 * a * c - if discriminant < 0 then - return false, nil + if discriminant >= 0 then + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + points[#points + 1] = self.point + dir * t1 + if math.abs(t2 - t1) > 1e-10 then + points[#points + 1] = self.point + dir * t2 + end end - local t = (-b - math.sqrt(discriminant)) / (2 * a) - local p1 = self.point + dir * t - --[[ -- 因为只求一个交点,所以不用判断其他解 - if discriminant == 0 then - return true, p1 + + if #points == 0 then + return false, nil end - local t2 = (-b + math.sqrt(discriminant)) / (2 * a) - local p2 = self.point + dir * t2 - --]] - return true, p1 + return true, points end ffi.metatype("foundation_shape_Line", Line) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index cb26deaf..fe620b4a 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -1,5 +1,7 @@ local ffi = require("ffi") + local math = math +local ipairs = ipairs local tostring = tostring local string = string @@ -50,10 +52,10 @@ end ---@param self foundation.shape.Ray ---@return string function Ray.__tostring(self) - return string.format("Ray(%s, dir=%s)", tostring(self.point), tostring(self.direction)) + return string.format("Ray(point=%s, direction=%s)", tostring(self.point), tostring(self.direction)) end ----获取射线上相对 point 指定距离的点 +---获取射线上相对起始点指定距离的点 ---@param length number 距离 ---@return foundation.math.Vector2 function Ray:getPoint(length) @@ -62,7 +64,7 @@ end ---检查与其他形状的相交 ---@param other any ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Ray:intersects(other) if other.__type == "foundation.shape.Segment" then return self:__intersectToSegment(other) @@ -80,8 +82,9 @@ end ---检查与线段的相交 ---@param other foundation.shape.Segment ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Ray:__intersectToSegment(other) + local points = {} local a = self.point local b = self.point + self.direction local c = other.point1 @@ -98,42 +101,54 @@ function Ray:__intersectToSegment(other) if t >= 0 and u >= 0 and u <= 1 then local x = a.x + t * (b.x - a.x) local y = a.y + t * (b.y - a.y) - return true, Vector2.create(x, y) + points[#points + 1] = Vector2.create(x, y) end - return false, nil + if #points == 0 then + return false, nil + end + return true, points end ---检查与三角形的相交 ---@param other foundation.shape.Triangle ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Ray:__intersectToTriangle(other) + local points = {} local edges = { Segment.create(other.point1, other.point2), Segment.create(other.point2, other.point3), Segment.create(other.point3, other.point1) } - local closestPoint, minT = nil, math.huge for i = 1, #edges do - local isIntersect, intersectPoint = self:__intersectToSegment(edges[i]) - if isIntersect then - local t = ((intersectPoint.x - self.point.x) * self.direction.x + (intersectPoint.y - self.point.y) * self.direction.y) / (self.direction:length() ^ 2) - if t < minT then - minT = t - closestPoint = intersectPoint + local success, edge_points = self:__intersectToSegment(edges[i]) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p end end end - if closestPoint then - return true, closestPoint + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end end - return false, nil + + if #unique_points == 0 then + return false, nil + end + return true, unique_points end ---检查与直线的相交 ---@param other foundation.shape.Line ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Ray:__intersectToLine(other) + local points = {} local a = self.point local b = self.point + self.direction local c = other.point @@ -145,19 +160,25 @@ function Ray:__intersectToLine(other) end local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + --local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + if t >= 0 then local x = a.x + t * (b.x - a.x) local y = a.y + t * (b.y - a.y) - return true, Vector2.create(x, y) + points[#points + 1] = Vector2.create(x, y) end - return false, nil + if #points == 0 then + return false, nil + end + return true, points end ---检查与另一条射线的相交 ---@param other foundation.shape.Ray ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Ray:__intersectToRay(other) + local points = {} local a = self.point local b = self.point + self.direction local c = other.point @@ -165,7 +186,19 @@ function Ray:__intersectToRay(other) local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) if math.abs(denom) < 1e-10 then - return false, nil + local dir_cross = self.direction:cross(other.direction) + if math.abs(dir_cross) < 1e-10 then + local point_diff = other.point - self.point + local t = point_diff:dot(self.direction) + if t >= 0 then + points[#points + 1] = self.point + self.direction * t + end + end + + if #points == 0 then + return false, nil + end + return true, points end local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom @@ -174,16 +207,20 @@ function Ray:__intersectToRay(other) if t >= 0 and u >= 0 then local x = a.x + t * (b.x - a.x) local y = a.y + t * (b.y - a.y) - return true, Vector2.create(x, y) + points[#points + 1] = Vector2.create(x, y) end - return false, nil + if #points == 0 then + return false, nil + end + return true, points end ---检查与圆的相交 ---@param other foundation.shape.Circle ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Ray:__intersectToCircle(other) + local points = {} local dir = self.direction local len = dir:length() if len == 0 then @@ -195,20 +232,22 @@ function Ray:__intersectToCircle(other) local b = 2 * L:dot(dir) local c = L:dot(L) - other.radius * other.radius local discriminant = b * b - 4 * a * c - if discriminant < 0 then - return false, nil - end - local t = (-b - math.sqrt(discriminant)) / (2 * a) - if t >= 0 then - local p1 = self.point + dir * t - return true, p1 + if discriminant >= 0 then + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + if t1 >= 0 then + points[#points + 1] = self.point + dir * t1 + end + if t2 >= 0 and math.abs(t2 - t1) > 1e-10 then + points[#points + 1] = self.point + dir * t2 + end end - local t2 = (-b + math.sqrt(discriminant)) / (2 * a) - if t2 >= 0 then - local p2 = self.point + dir * t2 - return true, p2 + + if #points == 0 then + return false, nil end - return false, nil + return true, points end ffi.metatype("foundation_shape_Ray", Ray) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index 83239e59..d0e074af 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -1,6 +1,7 @@ local ffi = require("ffi") local type = type +local ipairs = ipairs local tostring = tostring local string = string local math = math @@ -234,7 +235,7 @@ end ---检查线段是否与其他形状相交 ---@param other any 其他的形状 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Segment:intersects(other) if other.__type == "foundation.shape.Segment" then return self:__intersectToSegment(other) @@ -252,8 +253,9 @@ end ---检查线段是否与另一个线段相交 ---@param other foundation.shape.Segment 要检查的线段 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Segment:__intersectToSegment(other) + local points = {} local a = self.point1 local b = self.point2 local c = other.point1 @@ -261,7 +263,37 @@ function Segment:__intersectToSegment(other) local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) if math.abs(denom) < 1e-10 then - return false, nil + local dir = self.point2 - self.point1 + local len = dir:length() + if len == 0 then + if other:isPointOnLine(self.point1) then + points[#points + 1] = self.point1:clone() + end + + if #points == 0 then + return false, nil + end + return true, points + end + dir = dir / len + local t1 = ((c.x - a.x) * dir.x + (c.y - a.y) * dir.y) / len + local t2 = ((d.x - a.x) * dir.x + (d.y - a.y) * dir.y) / len + if t1 > 1 or t2 < 0 or (t1 < 0 and t2 < 0) or (t1 > 1 and t2 > 1) then + return false, nil + end + local start_t = math.max(0, math.min(t1, t2)) + local end_t = math.min(1, math.max(t1, t2)) + if start_t <= end_t then + points[#points + 1] = self.point1 + dir * start_t * len + if math.abs(end_t - start_t) > 1e-10 then + points[#points + 1] = self.point1 + dir * end_t * len + end + end + + if #points == 0 then + return false, nil + end + return true, points end local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom @@ -270,41 +302,60 @@ function Segment:__intersectToSegment(other) if t >= 0 and t <= 1 and u >= 0 and u <= 1 then local x = a.x + t * (b.x - a.x) local y = a.y + t * (b.y - a.y) - return true, Vector2.create(x, y) + points[#points + 1] = Vector2.create(x, y) end - return false, nil + if #points == 0 then + return false, nil + end + return true, points end ---检查线段是否与三角形相交 ---@param other foundation.shape.Triangle 要检查的三角形 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Segment:__intersectToTriangle(other) + local points = {} local edges = { Segment.create(other.point1, other.point2), Segment.create(other.point2, other.point3), Segment.create(other.point3, other.point1) } for i = 1, #edges do - local edge = edges[i] - local isIntersect, intersectPoint = self:__intersectToSegment(edge) - if isIntersect then - return true, intersectPoint + local success, edge_points = self:__intersectToSegment(edges[i]) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end end end if other:contains(self.point1) then - return true, self.point1:clone() + points[#points + 1] = self.point1:clone() end - if other:contains(self.point2) then - return true, self.point2:clone() + if self.point1 ~= self.point2 and other:contains(self.point2) then + points[#points + 1] = self.point2:clone() end - return false, nil + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + + if #unique_points == 0 then + return false, nil + end + return true, unique_points end ---检查线段是否与直线相交 ---@param other foundation.shape.Line 要检查的直线 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Segment:__intersectToLine(other) + local points = {} local a = self.point1 local b = self.point2 local c = other.point @@ -319,16 +370,20 @@ function Segment:__intersectToLine(other) if t >= 0 and t <= 1 then local x = a.x + t * (b.x - a.x) local y = a.y + t * (b.y - a.y) - return true, Vector2.create(x, y) + points[#points + 1] = Vector2.create(x, y) end - return false, nil + if #points == 0 then + return false, nil + end + return true, points end ---检查线段是否与射线相交 ---@param other foundation.shape.Ray 要检查的射线 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Segment:__intersectToRay(other) + local points = {} local a = self.point1 local b = self.point2 local c = other.point @@ -345,21 +400,54 @@ function Segment:__intersectToRay(other) if t >= 0 and t <= 1 and u >= 0 then local x = a.x + t * (b.x - a.x) local y = a.y + t * (b.y - a.y) - return true, Vector2.create(x, y) + points[#points + 1] = Vector2.create(x, y) end - return false, nil + if #points == 0 then + return false, nil + end + return true, points end ---检查线段是否与圆相交 ---@param other foundation.shape.Circle 要检查的圆 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Segment:__intersectToCircle(other) - local closest = self:closestPoint(other.center) - if (closest - other.center):length() <= other.radius then - return true, closest + local points = {} + local dir = self.point2 - self.point1 + local len = dir:length() + if len == 0 then + if (self.point1 - other.center):length() <= other.radius then + points[#points + 1] = self.point1:clone() + end + + if #points == 0 then + return false, nil + end + return true, points end - return false, nil + dir = dir / len + local L = self.point1 - other.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - other.radius * other.radius + local discriminant = b * b - 4 * a * c + if discriminant >= 0 then + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + if t1 >= 0 and t1 <= len then + points[#points + 1] = self.point1 + dir * t1 + end + if t2 >= 0 and t2 <= len and math.abs(t2 - t1) > 1e-10 then + points[#points + 1] = self.point1 + dir * t2 + end + end + + if #points == 0 then + return false, nil + end + return true, points end ---计算点到线段的最近点 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 7f33653e..d5552ba9 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -1,6 +1,7 @@ local ffi = require("ffi") local type = type +local ipairs = ipairs local tostring = tostring local string = string local math = math @@ -273,7 +274,7 @@ end ---检查三角形是否与其他形状相交 ---@param other any 其他的形状 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Triangle:intersects(other) if other.__type == "foundation.shape.Segment" then return self:__intersectToSegment(other) @@ -291,135 +292,202 @@ end ---检查三角形是否与线段相交 ---@param other foundation.shape.Segment 要检查的线段 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Triangle:__intersectToSegment(other) + local points = {} local edges = { Segment.create(self.point1, self.point2), Segment.create(self.point2, self.point3), Segment.create(self.point3, self.point1) } for i = 1, #edges do - local edge = edges[i] - local isIntersect, intersectPoint = edge:intersects(other) - if isIntersect then - return true, intersectPoint + local success, edge_points = edges[i]:intersects(other) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end end end if self:contains(other.point1) then - return true, other.point1:clone() + points[#points + 1] = other.point1:clone() end - if self:contains(other.point2) then - return true, other.point2:clone() + if other.point1 ~= other.point2 and self:contains(other.point2) then + points[#points + 1] = other.point2:clone() end - return false, nil + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + + if #unique_points == 0 then + return false, nil + end + return true, unique_points end ---检查三角形是否与另一个三角形相交 ---@param other foundation.shape.Triangle 要检查的三角形 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Triangle:__intersectToTriangle(other) + local points = {} local edges = { Segment.create(self.point1, self.point2), Segment.create(self.point2, self.point3), Segment.create(self.point3, self.point1) } for i = 1, #edges do - local edge = edges[i] - local isIntersect, intersectPoint = edge:intersects(other) - if isIntersect then - return true, intersectPoint + local success, edge_points = edges[i]:intersects(other) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end end end if self:contains(other.point1) then - return true, other.point1:clone() + points[#points + 1] = other.point1:clone() end if self:contains(other.point2) then - return true, other.point2:clone() + points[#points + 1] = other.point2:clone() end if self:contains(other.point3) then - return true, other.point3:clone() + points[#points + 1] = other.point3:clone() end - return false, nil + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + + if #unique_points == 0 then + return false, nil + end + return true, unique_points end ---检查三角形是否与直线相交 ---@param other foundation.shape.Line 要检查的直线 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Triangle:__intersectToLine(other) + local points = {} local edges = { Segment.create(self.point1, self.point2), Segment.create(self.point2, self.point3), Segment.create(self.point3, self.point1) } for i = 1, #edges do - local edge = edges[i] - local isIntersect, intersectPoint = edge:intersects(other) - if isIntersect then - return true, intersectPoint + local success, edge_points = edges[i]:intersects(other) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end end end - return false, nil + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + + if #unique_points == 0 then + return false, nil + end + return true, unique_points end ---检查三角形是否与射线相交 ---@param other foundation.shape.Ray 要检查的射线 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Triangle:__intersectToRay(other) + local points = {} local edges = { Segment.create(self.point1, self.point2), Segment.create(self.point2, self.point3), Segment.create(self.point3, self.point1) } - local closestPoint, minT = nil, math.huge for i = 1, #edges do - local edge = edges[i] - local isIntersect, intersectPoint = edge:intersects(other) - if isIntersect then - local t = ((intersectPoint.x - other.point.x) * other.direction.x + (intersectPoint.y - other.point.y) * other.direction.y) / (other.direction:length() ^ 2) - if t < minT then - minT = t - closestPoint = intersectPoint + local success, edge_points = edges[i]:intersects(other) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p end end end - if closestPoint then - return true, closestPoint - end if self:contains(other.point) then - return true, other.point:clone() + points[#points + 1] = other.point:clone() end - return false, nil + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + + if #unique_points == 0 then + return false, nil + end + return true, unique_points end ---检查三角形是否与圆相交 ---@param other foundation.shape.Circle 要检查的圆 ----@return boolean, foundation.math.Vector2|nil +---@return boolean, foundation.math.Vector2[] | nil function Triangle:__intersectToCircle(other) + local points = {} local edges = { Segment.create(self.point1, self.point2), Segment.create(self.point2, self.point3), Segment.create(self.point3, self.point1) } for i = 1, #edges do - local edge = edges[i] - local isIntersect, intersectPoint = edge:intersects(other) - if isIntersect then - return true, intersectPoint + local success, edge_points = edges[i]:intersects(other) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end end end if other:contains(self.point1) then - return true, self.point1:clone() + points[#points + 1] = self.point1:clone() end if other:contains(self.point2) then - return true, self.point2:clone() + points[#points + 1] = self.point2:clone() end if other:contains(self.point3) then - return true, self.point3:clone() + points[#points + 1] = self.point3:clone() end if self:contains(other.center) then - return true, other.center:clone() + points[#points + 1] = other.center:clone() end - return false, nil + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + + if #unique_points == 0 then + return false, nil + end + return true, unique_points end ffi.metatype("foundation_shape_Triangle", Triangle) From 08c097db4aefa8fc88cc56a1b08f8066e7ab7690 Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 01:08:47 +0800 Subject: [PATCH 17/71] feat: add intersection methods for Circle, Line, Ray, Segment, and Triangle classes --- .../foundation/shape/Circle.lua | 142 +++++++++++ .../foundation/shape/Line.lua | 158 ++++++++++-- .../thlib-scripts-v2/foundation/shape/Ray.lua | 172 +++++++++++++- .../foundation/shape/Segment.lua | 146 +++++++++++- .../foundation/shape/Triangle.lua | 224 +++++++++++++++++- 5 files changed, 823 insertions(+), 19 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index 65d1a1f6..d1da1570 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -115,6 +115,24 @@ function Circle:intersects(other) return false, nil end +---只检查是否与其他形状相交,不计算交点 +---@param other any +---@return boolean +function Circle:hasIntersection(other) + if other.__type == "foundation.shape.Segment" then + return self:__hasIntersectionWithSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__hasIntersectionWithTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__hasIntersectionWithLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__hasIntersectionWithRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__hasIntersectionWithCircle(other) + end + return false +end + ---检查与线段的相交 ---@param other foundation.shape.Segment ---@return boolean, foundation.math.Vector2[] | nil @@ -159,6 +177,14 @@ function Circle:__intersectToSegment(other) return true, points end +---检查是否与线段相交 +---@param other foundation.shape.Segment +---@return boolean +function Circle:__hasIntersectionWithSegment(other) + local closest = other:closestPoint(self.center) + return (closest - self.center):length() <= self.radius +end + ---检查与三角形的相交 ---@param other foundation.shape.Triangle ---@return boolean, foundation.math.Vector2[] | nil @@ -205,6 +231,30 @@ function Circle:__intersectToTriangle(other) return true, unique_points end +---检查是否与三角形相交 +---@param other foundation.shape.Triangle +---@return boolean +function Circle:__hasIntersectionWithTriangle(other) + local edges = { + Segment.create(other.point1, other.point2), + Segment.create(other.point2, other.point3), + Segment.create(other.point3, other.point1) + } + for i = 1, #edges do + if self:__hasIntersectionWithSegment(edges[i]) then + return true + end + end + + if other:contains(self.center) then + return true + end + + return self:contains(other.point1) or + self:contains(other.point2) or + self:contains(other.point3) +end + ---检查与直线的相交 ---@param other foundation.shape.Line ---@return boolean, foundation.math.Vector2[] | nil @@ -237,6 +287,24 @@ function Circle:__intersectToLine(other) return true, points end +---检查是否与直线相交 +---@param other foundation.shape.Line +---@return boolean +function Circle:__hasIntersectionWithLine(other) + local dir = other.direction + local len = dir:length() + if len == 0 then + return false + end + dir = dir / len + local L = other.point - self.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - self.radius * self.radius + local discriminant = b * b - 4 * a * c + return discriminant >= 0 +end + ---检查与射线的相交 ---@param other foundation.shape.Ray ---@return boolean, foundation.math.Vector2[] | nil @@ -271,6 +339,33 @@ function Circle:__intersectToRay(other) return true, points end +---检查是否与射线相交 +---@param other foundation.shape.Ray +---@return boolean +function Circle:__hasIntersectionWithRay(other) + local dir = other.direction + local len = dir:length() + if len == 0 then + return false + end + dir = dir / len + local L = other.point - self.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - self.radius * self.radius + local discriminant = b * b - 4 * a * c + + if discriminant < 0 then + return false + end + + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + + return t1 >= 0 or t2 >= 0 +end + ---检查与另一个圆的相交 ---@param other foundation.shape.Circle ---@return boolean, foundation.math.Vector2[] | nil @@ -294,6 +389,53 @@ function Circle:__intersectToCircle(other) return true, points end +---检查是否与另一个圆相交 +---@param other foundation.shape.Circle +---@return boolean +function Circle:__hasIntersectionWithCircle(other) + local d = (self.center - other.center):length() + return d <= self.radius + other.radius and d >= math.abs(self.radius - other.radius) +end + +---计算点到圆的最近点 +---@param point foundation.math.Vector2 要检查的点 +---@return foundation.math.Vector2 圆上最近的点 +function Circle:closestPoint(point) + local dir = point - self.center + local dist = dir:length() + if dist == 0 then + return Vector2.create(self.center.x + self.radius, self.center.y) + end + local normalized_dir = dir / dist + return self.center + normalized_dir * self.radius +end + +---计算点到圆的距离 +---@param point foundation.math.Vector2 要检查的点 +---@return number 点到圆的距离 +function Circle:distanceToPoint(point) + local dist = (point - self.center):length() + return math.abs(dist - self.radius) +end + +---将点投影到圆上 +---@param point foundation.math.Vector2 要投影的点 +---@return foundation.math.Vector2 投影点 +function Circle:projectPoint(point) + return self:closestPoint(point) +end + +---检查点是否在圆上 +---@param point foundation.math.Vector2 要检查的点 +---@param tolerance number|nil 容差,默认为1e-10 +---@return boolean 点是否在圆上 +---@overload fun(self:foundation.shape.Circle, point:foundation.math.Vector2): boolean +function Circle:containsPoint(point, tolerance) + tolerance = tolerance or 1e-10 + local dist = (point - self.center):length() + return math.abs(dist - self.radius) <= tolerance +end + ffi.metatype("foundation_shape_Circle", Circle) return Circle \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index 94eae931..bf5e77c9 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -94,6 +94,24 @@ function Line:intersects(other) return false, nil end +---检查是否与其他形状相交,只返回是否相交的布尔值 +---@param other any +---@return boolean +function Line:hasIntersection(other) + if other.__type == "foundation.shape.Segment" then + return self:__hasIntersectionWithSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__hasIntersectionWithTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__hasIntersectionWithLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__hasIntersectionWithRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__hasIntersectionWithCircle(other) + end + return false +end + ---检查与线段的相交 ---@param other foundation.shape.Segment ---@return boolean, foundation.math.Vector2[] | nil @@ -124,6 +142,26 @@ function Line:__intersectToSegment(other) return true, points end +---检查是否与线段相交 +---@param other foundation.shape.Segment +---@return boolean +function Line:__hasIntersectionWithSegment(other) + local a = self.point + local b = self.point + self.direction + local c = other.point1 + local d = other.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + --local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return u >= 0 and u <= 1 +end + ---检查与三角形的相交 ---@param other foundation.shape.Triangle ---@return boolean, foundation.math.Vector2[] | nil @@ -158,6 +196,23 @@ function Line:__intersectToTriangle(other) return true, unique_points end +---检查是否与三角形相交 +---@param other foundation.shape.Triangle +---@return boolean +function Line:__hasIntersectionWithTriangle(other) + local edges = { + Segment.create(other.point1, other.point2), + Segment.create(other.point2, other.point3), + Segment.create(other.point3, other.point1) + } + for i = 1, #edges do + if self:__hasIntersectionWithSegment(edges[i]) then + return true + end + end + return false +end + ---检查与另一条直线的相交 ---@param other foundation.shape.Line ---@return boolean, foundation.math.Vector2[] | nil @@ -168,23 +223,19 @@ function Line:__intersectToLine(other) local c = other.point local d = other.point + other.direction - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - local dir_cross = self.direction:cross(other.direction) - if math.abs(dir_cross) < 1e-10 then - local point_diff = other.point - self.point - if math.abs(point_diff:cross(self.direction)) < 1e-10 then - points[#points + 1] = self.point:clone() - points[#points + 1] = self:getPoint(1) - end - end - - if #points == 0 then + local dir_cross = self.direction:cross(other.direction) + if math.abs(dir_cross) < 1e-10 then + local point_diff = other.point - self.point + if math.abs(point_diff:cross(self.direction)) < 1e-10 then + points[#points + 1] = self.point:clone() + points[#points + 1] = self:getPoint(1) + return true, points + else return false, nil end - return true, points end + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom local x = a.x + t * (b.x - a.x) local y = a.y + t * (b.y - a.y) @@ -193,6 +244,18 @@ function Line:__intersectToLine(other) return true, points end +---检查是否与另一条直线相交 +---@param other foundation.shape.Line +---@return boolean +function Line:__hasIntersectionWithLine(other) + local dir_cross = self.direction:cross(other.direction) + if math.abs(dir_cross) < 1e-10 then + local point_diff = other.point - self.point + return math.abs(point_diff:cross(self.direction)) < 1e-10 + end + return true +end + ---检查与射线的相交 ---@param other foundation.shape.Ray ---@return boolean, foundation.math.Vector2[] | nil @@ -223,6 +286,25 @@ function Line:__intersectToRay(other) return true, points end +---检查是否与射线相交 +---@param other foundation.shape.Ray +---@return boolean +function Line:__hasIntersectionWithRay(other) + local a = self.point + local b = self.point + self.direction + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return u >= 0 +end + ---检查与圆的相交 ---@param other foundation.shape.Circle ---@return boolean, foundation.math.Vector2[] | nil @@ -255,6 +337,54 @@ function Line:__intersectToCircle(other) return true, points end +---检查是否与圆相交 +---@param other foundation.shape.Circle +---@return boolean +function Line:__hasIntersectionWithCircle(other) + local dir = self.direction + local len = dir:length() + if len == 0 then + return false + end + dir = dir / len + local L = self.point - other.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - other.radius * other.radius + local discriminant = b * b - 4 * a * c + return discriminant >= 0 +end + +---计算点到直线的距离 +---@param point foundation.math.Vector2 点 +---@return number 距离 +function Line:distanceToPoint(point) + local point_vec = point - self.point + local proj_length = point_vec:dot(self.direction) + local proj_point = self.point + self.direction * proj_length + return (point - proj_point):length() +end + +---检查点是否在直线上 +---@param point foundation.math.Vector2 点 +---@param tolerance number 误差容忍度,默认为1e-10 +---@return boolean +function Line:containsPoint(point, tolerance) + tolerance = tolerance or 1e-10 + local point_vec = point - self.point + local cross = point_vec:cross(self.direction) + return math.abs(cross) < tolerance +end + +---获取点在直线上的投影 +---@param point foundation.math.Vector2 点 +---@return foundation.math.Vector2 投影点 +function Line:projectPoint(point) + local point_vec = point - self.point + local proj_length = point_vec:dot(self.direction) + return self.point + self.direction * proj_length +end + ffi.metatype("foundation_shape_Line", Line) -return Line \ No newline at end of file +return Line diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index fe620b4a..accfa402 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -250,6 +250,176 @@ function Ray:__intersectToCircle(other) return true, points end +---检查是否与其他形状相交,只返回是否相交的布尔值 +---@param other any +---@return boolean +function Ray:hasIntersection(other) + if other.__type == "foundation.shape.Segment" then + return self:__hasIntersectionWithSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__hasIntersectionWithTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__hasIntersectionWithLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__hasIntersectionWithRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__hasIntersectionWithCircle(other) + end + return false +end + +---检查是否与线段相交 +---@param other foundation.shape.Segment +---@return boolean +function Ray:__hasIntersectionWithSegment(other) + local a = self.point + local b = self.point + self.direction + local c = other.point1 + local d = other.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and u >= 0 and u <= 1 +end + +---检查是否与三角形相交 +---@param other foundation.shape.Triangle +---@return boolean +function Ray:__hasIntersectionWithTriangle(other) + local edges = { + Segment.create(other.point1, other.point2), + Segment.create(other.point2, other.point3), + Segment.create(other.point3, other.point1) + } + for i = 1, #edges do + if self:__hasIntersectionWithSegment(edges[i]) then + return true + end + end + return false +end + +---检查是否与直线相交 +---@param other foundation.shape.Line +---@return boolean +function Ray:__hasIntersectionWithLine(other) + local a = self.point + local b = self.point + self.direction + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + return t >= 0 +end + +---检查是否与另一条射线相交 +---@param other foundation.shape.Ray +---@return boolean +function Ray:__hasIntersectionWithRay(other) + local a = self.point + local b = self.point + self.direction + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + local dir_cross = self.direction:cross(other.direction) + if math.abs(dir_cross) < 1e-10 then + local point_diff = other.point - self.point + local t = point_diff:dot(self.direction) + return t >= 0 + end + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and u >= 0 +end + +---检查是否与圆相交 +---@param other foundation.shape.Circle +---@return boolean +function Ray:__hasIntersectionWithCircle(other) + local dir = self.direction + local len = dir:length() + if len == 0 then + return false + end + dir = dir / len + local L = self.point - other.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - other.radius * other.radius + local discriminant = b * b - 4 * a * c + + if discriminant < 0 then + return false + end + + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + return t1 >= 0 +end + +---计算点到射线的距离 +---@param point foundation.math.Vector2 点 +---@return number 距离 +function Ray:distanceToPoint(point) + local point_vec = point - self.point + local proj_length = point_vec:dot(self.direction) + + if proj_length < 0 then + return (point - self.point):length() + else + local proj_point = self.point + self.direction * proj_length + return (point - proj_point):length() + end +end + +---检查点是否在射线上 +---@param point foundation.math.Vector2 点 +---@param tolerance number 误差容忍度,默认为1e-10 +---@return boolean +function Ray:containsPoint(point, tolerance) + tolerance = tolerance or 1e-10 + local point_vec = point - self.point + + local proj_length = point_vec:dot(self.direction) + if proj_length < 0 then + return false + end + + local cross = point_vec:cross(self.direction) + return math.abs(cross) < tolerance +end + +---获取点在射线上的投影 +---@param point foundation.math.Vector2 点 +---@return foundation.math.Vector2 投影点 +function Ray:projectPoint(point) + local point_vec = point - self.point + local proj_length = point_vec:dot(self.direction) + + if proj_length < 0 then + return self.point:clone() + else + return self.point + self.direction * proj_length + end +end + ffi.metatype("foundation_shape_Ray", Ray) -return Ray \ No newline at end of file +return Ray diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index d0e074af..3344d8c1 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -251,6 +251,146 @@ function Segment:intersects(other) return false, nil end +---仅检查线段是否与其他形状相交,不返回相交点 +---@param other any 其他的形状 +---@return boolean +function Segment:hasIntersection(other) + if other.__type == "foundation.shape.Segment" then + return self:__hasIntersectionWithSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__hasIntersectionWithTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__hasIntersectionWithLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__hasIntersectionWithRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__hasIntersectionWithCircle(other) + end + return false +end + +---仅检查线段是否与另一个线段相交 +---@param other foundation.shape.Segment 要检查的线段 +---@return boolean +function Segment:__hasIntersectionWithSegment(other) + local a = self.point1 + local b = self.point2 + local c = other.point1 + local d = other.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + local dir = self.point2 - self.point1 + local len = dir:length() + if len == 0 then + return other:containsPoint(self.point1) + end + dir = dir / len + local t1 = ((c.x - a.x) * dir.x + (c.y - a.y) * dir.y) / len + local t2 = ((d.x - a.x) * dir.x + (d.y - a.y) * dir.y) / len + if t1 > 1 or t2 < 0 or (t1 < 0 and t2 < 0) or (t1 > 1 and t2 > 1) then + return false + end + local start_t = math.max(0, math.min(t1, t2)) + local end_t = math.min(1, math.max(t1, t2)) + return start_t <= end_t + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and t <= 1 and u >= 0 and u <= 1 +end + +---仅检查线段是否与三角形相交 +---@param other foundation.shape.Triangle 要检查的三角形 +---@return boolean +function Segment:__hasIntersectionWithTriangle(other) + local edges = { + Segment.create(other.point1, other.point2), + Segment.create(other.point2, other.point3), + Segment.create(other.point3, other.point1) + } + + for i = 1, #edges do + if self:__hasIntersectionWithSegment(edges[i]) then + return true + end + end + + if other:contains(self.point1) or (self.point1 ~= self.point2 and other:contains(self.point2)) then + return true + end + + return false +end + +---仅检查线段是否与直线相交 +---@param other foundation.shape.Line 要检查的直线 +---@return boolean +function Segment:__hasIntersectionWithLine(other) + local a = self.point1 + local b = self.point2 + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + return t >= 0 and t <= 1 +end + +---仅检查线段是否与射线相交 +---@param other foundation.shape.Ray 要检查的射线 +---@return boolean +function Segment:__hasIntersectionWithRay(other) + local a = self.point1 + local b = self.point2 + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and t <= 1 and u >= 0 +end + +---仅检查线段是否与圆相交 +---@param other foundation.shape.Circle 要检查的圆 +---@return boolean +function Segment:__hasIntersectionWithCircle(other) + local dir = self.point2 - self.point1 + local len = dir:length() + if len == 0 then + return (self.point1 - other.center):length() <= other.radius + end + + dir = dir / len + local L = self.point1 - other.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - other.radius * other.radius + local discriminant = b * b - 4 * a * c + + if discriminant < 0 then + return false + end + + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + + return (t1 >= 0 and t1 <= len) or (t2 >= 0 and t2 <= len) +end + ---检查线段是否与另一个线段相交 ---@param other foundation.shape.Segment 要检查的线段 ---@return boolean, foundation.math.Vector2[] | nil @@ -266,7 +406,7 @@ function Segment:__intersectToSegment(other) local dir = self.point2 - self.point1 local len = dir:length() if len == 0 then - if other:isPointOnLine(self.point1) then + if other:containsPoint(self.point1) then points[#points + 1] = self.point1:clone() end @@ -493,7 +633,7 @@ end ---@param tolerance number|nil 容差,默认为1e-10 ---@return boolean 点是否在线段上 ---@overload fun(self:foundation.shape.Segment, point:foundation.math.Vector2): boolean -function Segment:isPointOnLine(point, tolerance) +function Segment:containsPoint(point, tolerance) tolerance = tolerance or 1e-10 local dist = self:distanceToPoint(point) if dist > tolerance then @@ -512,4 +652,4 @@ end ffi.metatype("foundation_shape_Segment", Segment) -return Segment \ No newline at end of file +return Segment diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index d5552ba9..502a8252 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -490,6 +490,228 @@ function Triangle:__intersectToCircle(other) return true, unique_points end +---仅检查三角形是否与其他形状相交,不返回相交点 +---@param other any 其他的形状 +---@return boolean +function Triangle:hasIntersection(other) + if other.__type == "foundation.shape.Segment" then + return self:__hasIntersectionWithSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__hasIntersectionWithTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__hasIntersectionWithLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__hasIntersectionWithRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__hasIntersectionWithCircle(other) + end + return false +end + +---仅检查三角形是否与线段相交 +---@param other foundation.shape.Segment 要检查的线段 +---@return boolean +function Triangle:__hasIntersectionWithSegment(other) + local edges = { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } + + for i = 1, #edges do + if edges[i]:hasIntersection(other) then + return true + end + end + + if self:contains(other.point1) or self:contains(other.point2) then + return true + end + + return false +end + +---仅检查三角形是否与另一个三角形相交 +---@param other foundation.shape.Triangle 要检查的三角形 +---@return boolean +function Triangle:__hasIntersectionWithTriangle(other) + local edges1 = { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } + + local edges2 = { + Segment.create(other.point1, other.point2), + Segment.create(other.point2, other.point3), + Segment.create(other.point3, other.point1) + } + + for i = 1, #edges1 do + for j = 1, #edges2 do + if edges1[i]:hasIntersection(edges2[j]) then + return true + end + end + end + + if self:contains(other.point1) or self:contains(other.point2) or self:contains(other.point3) or + other:contains(self.point1) or other:contains(self.point2) or other:contains(self.point3) then + return true + end + + return false +end + +---仅检查三角形是否与直线相交 +---@param other foundation.shape.Line 要检查的直线 +---@return boolean +function Triangle:__hasIntersectionWithLine(other) + local edges = { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } + + for i = 1, #edges do + if edges[i]:hasIntersection(other) then + return true + end + end + + return false +end + +---仅检查三角形是否与射线相交 +---@param other foundation.shape.Ray 要检查的射线 +---@return boolean +function Triangle:__hasIntersectionWithRay(other) + local edges = { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } + + for i = 1, #edges do + if edges[i]:hasIntersection(other) then + return true + end + end + + if self:contains(other.point) then + return true + end + + return false +end + +---仅检查三角形是否与圆相交 +---@param other foundation.shape.Circle 要检查的圆 +---@return boolean +function Triangle:__hasIntersectionWithCircle(other) + if self:contains(other.center) then + return true + end + + local edges = { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } + + for i = 1, #edges do + local distance = edges[i]:distanceToPoint(other.center) + if distance <= other.radius then + return true + end + end + + return false +end + +---计算点到三角形的最近点 +---@param point foundation.math.Vector2 要检查的点 +---@return foundation.math.Vector2 三角形上最近的点 +function Triangle:closestPoint(point) + if self:contains(point) then + return point:clone() + end + + local edges = { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } + + local minDistance = math.huge + local closestPoint + + for i = 1, #edges do + local edgeClosest = edges[i]:closestPoint(point) + local distance = (point - edgeClosest):length() + + if distance < minDistance then + minDistance = distance + closestPoint = edgeClosest + end + end + + return closestPoint +end + +---计算点到三角形的距离 +---@param point foundation.math.Vector2 要检查的点 +---@return number 点到三角形的距离 +function Triangle:distanceToPoint(point) + if self:contains(point) then + return 0 + end + + local edges = { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } + + local minDistance = math.huge + + for i = 1, #edges do + local distance = edges[i]:distanceToPoint(point) + if distance < minDistance then + minDistance = distance + end + end + + return minDistance +end + +---将点投影到三角形平面上(2D中与closest相同) +---@param point foundation.math.Vector2 要投影的点 +---@return foundation.math.Vector2 投影点 +function Triangle:projectPoint(point) + return self:closestPoint(point) +end + +---检查点是否在三角形上 +---@param point foundation.math.Vector2 要检查的点 +---@param tolerance number|nil 容差,默认为1e-10 +---@return boolean 点是否在三角形上 +---@overload fun(self:foundation.shape.Triangle, point:foundation.math.Vector2): boolean +function Triangle:containsPoint(point, tolerance) + tolerance = tolerance or 1e-10 + local edges = { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } + for i = 1, #edges do + if edges[i]:containsPoint(point, tolerance) then + return true + end + end + return false +end + ffi.metatype("foundation_shape_Triangle", Triangle) -return Triangle \ No newline at end of file +return Triangle From 74276829098797ae66aebcde6f989d09d95441be Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 01:44:24 +0800 Subject: [PATCH 18/71] feat: add intersection methods for Rectangle in Circle, Line, Ray, Segment, and Triangle classes --- .../foundation/math/Vector2.lua | 2 +- .../foundation/math/Vector3.lua | 4 +- .../foundation/math/Vector4.lua | 5 +- .../foundation/shape/Circle.lua | 18 +++++ .../foundation/shape/Line.lua | 39 +++++++++- .../thlib-scripts-v2/foundation/shape/Ray.lua | 75 ++++++++++++++----- .../foundation/shape/Segment.lua | 24 +++++- .../foundation/shape/Triangle.lua | 54 ++++++++----- 8 files changed, 175 insertions(+), 46 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index 4d0aebf6..05773b0f 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -120,7 +120,7 @@ end ---@param b foundation.math.Vector2 第二个操作数 ---@return boolean 两个向量是否相等 function Vector2.__eq(a, b) - return a.x == b.x and a.y == b.y + return math.abs(a.x - b.x) < 1e-10 and math.abs(a.y - b.y) < 1e-10 end ---向量字符串表示 diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua index 1ed938d5..6f9df478 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua @@ -104,7 +104,9 @@ end ---@param b foundation.math.Vector3 第二个操作数 ---@return boolean 两个向量是否相等 function Vector3.__eq(a, b) - return a.x == b.x and a.y == b.y and a.z == b.z + return math.abs(a.x - b.x) < 1e-10 and + math.abs(a.y - b.y) < 1e-10 and + math.abs(a.z - b.z) < 1e-10 end ---向量字符串表示 diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua index 52608f80..45cde2d5 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua @@ -107,7 +107,10 @@ end ---@param b foundation.math.Vector4 第二个操作数 ---@return boolean 两个向量是否相等 function Vector4.__eq(a, b) - return a.x == b.x and a.y == b.y and a.z == b.z and a.w == b.w + return math.abs(a.x - b.x) < 1e-10 and + math.abs(a.y - b.y) < 1e-10 and + math.abs(a.z - b.z) < 1e-10 and + math.abs(a.w - b.w) < 1e-10 end ---向量字符串表示 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index d1da1570..f2251667 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -111,6 +111,8 @@ function Circle:intersects(other) return self:__intersectToRay(other) elseif other.__type == "foundation.shape.Circle" then return self:__intersectToCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__intersectToRectangle(other) end return false, nil end @@ -129,6 +131,8 @@ function Circle:hasIntersection(other) return self:__hasIntersectionWithRay(other) elseif other.__type == "foundation.shape.Circle" then return self:__hasIntersectionWithCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__hasIntersectionWithRectangle(other) end return false end @@ -397,6 +401,20 @@ function Circle:__hasIntersectionWithCircle(other) return d <= self.radius + other.radius and d >= math.abs(self.radius - other.radius) end +---检查与矩形的相交 +---@param other foundation.shape.Rectangle +---@return boolean, foundation.math.Vector2[] | nil +function Circle:__intersectToRectangle(other) + return other:__intersectToCircle(self) +end + +---仅检查是否与矩形相交 +---@param other foundation.shape.Rectangle +---@return boolean +function Circle:__hasIntersectionWithRectangle(other) + return other:__hasIntersectionWithCircle(self) +end + ---计算点到圆的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 圆上最近的点 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index bf5e77c9..50e6b198 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -27,8 +27,16 @@ Line.__type = "foundation.shape.Line" ---@param direction foundation.math.Vector2 方向向量 ---@return foundation.shape.Line function Line.create(point, direction) + local dist = direction and direction:length() or 0 + if dist == 0 then + direction = Vector2.create(1, 0) + elseif dist ~= 1 then + direction = direction:normalized() + else + direction = direction:clone() + end ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("foundation_shape_Line", point, direction:normalized()) + return ffi.new("foundation_shape_Line", point, direction) end ---根据两个点创建一条直线 @@ -40,9 +48,18 @@ function Line.createFromPoints(p1, p2) return Line.create(p1, direction) end +---根据一个点、弧度创建一条直线 +---@param point foundation.math.Vector2 起始点 +---@param rad number 弧度 +---@return foundation.shape.Line +function Line.createFromPointAndRad(point, rad) + local direction = Vector2.createFromRad(rad, 1) + return Line.create(point, direction) +end + ---根据一个点、角度创建一条直线 ---@param point foundation.math.Vector2 起始点 ----@param angle number 角度(度) +---@param angle number 角度 ---@return foundation.shape.Line function Line.createFromPointAndAngle(point, angle) local direction = Vector2.createFromAngle(angle, 1) @@ -90,6 +107,8 @@ function Line:intersects(other) return self:__intersectToRay(other) elseif other.__type == "foundation.shape.Circle" then return self:__intersectToCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__intersectToRectangle(other) end return false, nil end @@ -108,6 +127,8 @@ function Line:hasIntersection(other) return self:__hasIntersectionWithRay(other) elseif other.__type == "foundation.shape.Circle" then return self:__hasIntersectionWithCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__hasIntersectionWithRectangle(other) end return false end @@ -355,6 +376,20 @@ function Line:__hasIntersectionWithCircle(other) return discriminant >= 0 end +---检查与矩形的相交 +---@param other foundation.shape.Rectangle +---@return boolean, foundation.math.Vector2[] | nil +function Line:__intersectToRectangle(other) + return other:__intersectToLine(self) +end + +---仅检查是否与矩形相交 +---@param other foundation.shape.Rectangle +---@return boolean +function Line:__hasIntersectionWithRectangle(other) + return other:__hasIntersectionWithLine(self) +end + ---计算点到直线的距离 ---@param point foundation.math.Vector2 点 ---@return number 距离 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index accfa402..a581337b 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -27,13 +27,30 @@ Ray.__type = "foundation.shape.Ray" ---@param direction foundation.math.Vector2 方向向量 ---@return foundation.shape.Ray function Ray.create(point, direction) + local dist = direction and direction:length() or 0 + if dist == 0 then + direction = Vector2.create(1, 0) + elseif dist ~= 1 then + direction = direction:normalized() + else + direction = direction:clone() + end ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("foundation_shape_Ray", point, direction:normalized()) + return ffi.new("foundation_shape_Ray", point, direction) +end + +---根据起始点、弧度创建射线 +---@param point foundation.math.Vector2 起始点 +---@param radian number 弧度 +---@return foundation.shape.Ray +function Ray.createFromRad(point, radian) + local direction = Vector2.createFromRad(radian, 1) + return Ray.create(point, direction) end ---根据起始点、角度创建射线 ---@param point foundation.math.Vector2 起始点 ----@param angle number 角度(度) +---@param angle number 角度 ---@return foundation.shape.Ray function Ray.createFromAngle(point, angle) local direction = Vector2.createFromAngle(angle, 1) @@ -76,10 +93,32 @@ function Ray:intersects(other) return self:__intersectToRay(other) elseif other.__type == "foundation.shape.Circle" then return self:__intersectToCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__intersectToRectangle(other) end return false, nil end +---检查是否与其他形状相交,只返回是否相交的布尔值 +---@param other any +---@return boolean +function Ray:hasIntersection(other) + if other.__type == "foundation.shape.Segment" then + return self:__hasIntersectionWithSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__hasIntersectionWithTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__hasIntersectionWithLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__hasIntersectionWithRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__hasIntersectionWithCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__hasIntersectionWithRectangle(other) + end + return false +end + ---检查与线段的相交 ---@param other foundation.shape.Segment ---@return boolean, foundation.math.Vector2[] | nil @@ -250,24 +289,6 @@ function Ray:__intersectToCircle(other) return true, points end ----检查是否与其他形状相交,只返回是否相交的布尔值 ----@param other any ----@return boolean -function Ray:hasIntersection(other) - if other.__type == "foundation.shape.Segment" then - return self:__hasIntersectionWithSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__hasIntersectionWithTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__hasIntersectionWithLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__hasIntersectionWithRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__hasIntersectionWithCircle(other) - end - return false -end - ---检查是否与线段相交 ---@param other foundation.shape.Segment ---@return boolean @@ -374,6 +395,20 @@ function Ray:__hasIntersectionWithCircle(other) return t1 >= 0 end +---检查与矩形的相交 +---@param other foundation.shape.Rectangle +---@return boolean, foundation.math.Vector2[] | nil +function Ray:__intersectToRectangle(other) + return other:__intersectToRay(self) +end + +---仅检查是否与矩形相交 +---@param other foundation.shape.Rectangle +---@return boolean +function Ray:__hasIntersectionWithRectangle(other) + return other:__hasIntersectionWithRay(self) +end + ---计算点到射线的距离 ---@param point foundation.math.Vector2 点 ---@return number 距离 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index 3344d8c1..90fc2c80 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -35,7 +35,7 @@ end ---@param rad number 线段的弧度 ---@param length number 线段的长度 ---@return foundation.shape.Segment -function Segment.createFromPointAndRad(point, rad, length) +function Segment.createFromRad(point, rad, length) local v2 = Vector2.create(point.x + math.cos(rad) * length, point.y + math.sin(rad) * length) return Segment.create(point, v2) end @@ -45,9 +45,9 @@ end ---@param angle number 线段的角度 ---@param length number 线段的长度 ---@return foundation.shape.Segment -function Segment.createFromPointAndAngle(point, angle, length) +function Segment.createFromAngle(point, angle, length) local rad = math.rad(angle) - return Segment.createFromPointAndRad(point, rad, length) + return Segment.createFromRad(point, rad, length) end ---线段相等比较 @@ -247,6 +247,8 @@ function Segment:intersects(other) return self:__intersectToRay(other) elseif other.__type == "foundation.shape.Circle" then return self:__intersectToCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__intersectToRectangle(other) end return false, nil end @@ -265,6 +267,8 @@ function Segment:hasIntersection(other) return self:__hasIntersectionWithRay(other) elseif other.__type == "foundation.shape.Circle" then return self:__hasIntersectionWithCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__hasIntersectionWithRectangle(other) end return false end @@ -590,6 +594,20 @@ function Segment:__intersectToCircle(other) return true, points end +---检查与矩形的相交 +---@param other foundation.shape.Rectangle +---@return boolean, foundation.math.Vector2[] | nil +function Segment:__intersectToRectangle(other) + return other:__intersectToSegment(self) +end + +---仅检查是否与矩形相交 +---@param other foundation.shape.Rectangle +---@return boolean +function Segment:__hasIntersectionWithRectangle(other) + return other:__hasIntersectionWithSegment(self) +end + ---计算点到线段的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 线段上最近的点 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 502a8252..3129fd7f 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -286,10 +286,32 @@ function Triangle:intersects(other) return self:__intersectToRay(other) elseif other.__type == "foundation.shape.Circle" then return self:__intersectToCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__intersectToRectangle(other) end return false, nil end +---仅检查三角形是否与其他形状相交,不返回相交点 +---@param other any 其他的形状 +---@return boolean +function Triangle:hasIntersection(other) + if other.__type == "foundation.shape.Segment" then + return self:__hasIntersectionWithSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__hasIntersectionWithTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__hasIntersectionWithLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__hasIntersectionWithRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__hasIntersectionWithCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__hasIntersectionWithRectangle(other) + end + return false +end + ---检查三角形是否与线段相交 ---@param other foundation.shape.Segment 要检查的线段 ---@return boolean, foundation.math.Vector2[] | nil @@ -490,24 +512,6 @@ function Triangle:__intersectToCircle(other) return true, unique_points end ----仅检查三角形是否与其他形状相交,不返回相交点 ----@param other any 其他的形状 ----@return boolean -function Triangle:hasIntersection(other) - if other.__type == "foundation.shape.Segment" then - return self:__hasIntersectionWithSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__hasIntersectionWithTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__hasIntersectionWithLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__hasIntersectionWithRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__hasIntersectionWithCircle(other) - end - return false -end - ---仅检查三角形是否与线段相交 ---@param other foundation.shape.Segment 要检查的线段 ---@return boolean @@ -629,6 +633,20 @@ function Triangle:__hasIntersectionWithCircle(other) return false end +---检查与矩形的相交 +---@param other foundation.shape.Rectangle +---@return boolean, foundation.math.Vector2[] | nil +function Triangle:__intersectToRectangle(other) + return other:__intersectToTriangle(self) +end + +---仅检查是否与矩形相交 +---@param other foundation.shape.Rectangle +---@return boolean +function Triangle:__hasIntersectionWithRectangle(other) + return other:__hasIntersectionWithTriangle(self) +end + ---计算点到三角形的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 三角形上最近的点 From 4e026cc9159900e3555774dcd712429dd5db5523 Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 01:46:08 +0800 Subject: [PATCH 19/71] feat: add Rectangle class with geometric operations and intersection methods --- .../foundation/shape/Rectangle.lua | 637 ++++++++++++++++++ 1 file changed, 637 insertions(+) create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua new file mode 100644 index 00000000..8e880c2e --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -0,0 +1,637 @@ +local ffi = require("ffi") + +local type = type +local ipairs = ipairs +local tostring = tostring +local string = string +local math = math + +local Vector2 = require("foundation.math.Vector2") +local Segment = require("foundation.shape.Segment") + +ffi.cdef [[ +typedef struct { + foundation_math_Vector2 center; + double width; + double height; + foundation_math_Vector2 direction; +} foundation_shape_Rectangle; +]] + +---@class foundation.shape.Rectangle +---@field center foundation.math.Vector2 矩形的中心点 +---@field width number 矩形的宽度 +---@field height number 矩形的高度 +---@field direction foundation.math.Vector2 矩形的宽度轴方向(归一化向量) +local Rectangle = {} +Rectangle.__index = Rectangle +Rectangle.__type = "foundation.shape.Rectangle" + +---创建一个新的矩形 +---@param center foundation.math.Vector2 中心点 +---@param width number 宽度 +---@param height number 高度 +---@param direction foundation.math.Vector2|nil 宽度轴方向(归一化向量),默认为(1,0) +---@return foundation.shape.Rectangle +function Rectangle.create(center, width, height, direction) + local dist = direction and direction:length() or 0 + if dist == 0 then + direction = Vector2.create(1, 0) + elseif dist ~= 1 then + direction = direction:normalized() + else + direction = direction:clone() + end + ---@diagnostic disable-next-line: return-type-mismatch + return ffi.new("foundation_shape_Rectangle", center, width, height, direction) +end + +---使用给定的弧度创建矩形 +---@param center foundation.math.Vector2 中心点 +---@param width number 宽度 +---@param height number 高度 +---@param rad number 旋转弧度 +---@return foundation.shape.Rectangle +function Rectangle.createFromRad(center, width, height, rad) + local direction = Vector2.createFromRad(rad, 1) + return Rectangle.create(center, width, height, direction) +end + +---使用给定的角度创建矩形 +---@param center foundation.math.Vector2 中心点 +---@param width number 宽度 +---@param height number 高度 +---@param angle number 旋转角度 +---@return foundation.shape.Rectangle +function Rectangle.createFromAngle(center, width, height, angle) + local direction = Vector2.createFromAngle(angle, 1) + return Rectangle.create(center, width, height, direction) +end + +---矩形相等比较 +---@param a foundation.shape.Rectangle +---@param b foundation.shape.Rectangle +---@return boolean +function Rectangle.__eq(a, b) + return a.center == b.center and + math.abs(a.width - b.width) < 1e-10 and + math.abs(a.height - b.height) < 1e-10 and + a.direction == b.direction +end + +---矩形的字符串表示 +---@param self foundation.shape.Rectangle +---@return string +function Rectangle.__tostring(self) + return string.format("Rectangle(center=%s, width=%f, height=%f, direction=%s)", + tostring(self.center), self.width, self.height, tostring(self.direction)) +end + +---获取矩形的四个顶点 +---@return foundation.math.Vector2[] +function Rectangle:getVertices() + local hw, hh = self.width / 2, self.height / 2 + local dir = self.direction + local perp = Vector2.create(-dir.y, dir.x) -- 垂直向量(高度方向) + local vertices = { + Vector2.create(-hw, -hh), + Vector2.create(hw, -hh), + Vector2.create(hw, hh), + Vector2.create(-hw, hh) + } + for i, v in ipairs(vertices) do + local x = v.x * dir.x + v.y * perp.x + local y = v.x * dir.y + v.y * perp.y + vertices[i] = self.center + Vector2.create(x, y) + end + return vertices +end + +---获取矩形的四条边(线段) +---@return foundation.shape.Segment[] +function Rectangle:getEdges() + local vertices = self:getVertices() + return { + Segment.create(vertices[1], vertices[2]), + Segment.create(vertices[2], vertices[3]), + Segment.create(vertices[3], vertices[4]), + Segment.create(vertices[4], vertices[1]) + } +end + +---平移矩形(更改当前矩形) +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Rectangle 自身引用 +function Rectangle:move(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + self.center.x = self.center.x + moveX + self.center.y = self.center.y + moveY + return self +end + +---获取平移后的矩形副本 +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Rectangle +function Rectangle:moved(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + return Rectangle.create( + Vector2.create(self.center.x + moveX, self.center.y + moveY), + self.width, self.height, self.direction + ) +end + +---旋转矩形(更改当前矩形) +---@param rad number 旋转弧度 +---@return foundation.shape.Rectangle 自身引用 +function Rectangle:rotate(rad) + local cosA, sinA = math.cos(rad), math.sin(rad) + local x = self.direction.x * cosA - self.direction.y * sinA + local y = self.direction.x * sinA + self.direction.y * cosA + self.direction = Vector2.create(x, y):normalized() + return self +end + +---获取旋转后的矩形副本 +---@param rad number 旋转弧度 +---@return foundation.shape.Rectangle +function Rectangle:rotated(rad) + local cosA, sinA = math.cos(rad), math.sin(rad) + local x = self.direction.x * cosA - self.direction.y * sinA + local y = self.direction.x * sinA + self.direction.y * cosA + return Rectangle.create(self.center, self.width, self.height, Vector2.create(x, y):normalized()) +end + +---缩放矩形(更改当前矩形) +---@param scale number|foundation.math.Vector2 缩放倍数 +---@return foundation.shape.Rectangle 自身引用 +function Rectangle:scale(scale) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + self.width = self.width * scaleX + self.height = self.height * scaleY + return self +end + +---获取缩放后的矩形副本 +---@param scale number|foundation.math.Vector2 缩放倍数 +---@return foundation.shape.Rectangle +function Rectangle:scaled(scale) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + return Rectangle.create(self.center, self.width * scaleX, self.height * scaleY, self.direction) +end + +---检查点是否在矩形内(包括边界) +---@param point foundation.math.Vector2 +---@return boolean +function Rectangle:contains(point) + local p = point - self.center + local dir = self.direction + local perp = Vector2.create(-dir.y, dir.x) + local x = p.x * dir.x + p.y * dir.y + local y = p.x * perp.x + p.y * perp.y + local hw, hh = self.width / 2, self.height / 2 + return math.abs(x) <= hw and math.abs(y) <= hh +end + +---检查与其他形状的相交 +---@param other any +---@return boolean, foundation.math.Vector2[] | nil +function Rectangle:intersects(other) + if other.__type == "foundation.shape.Segment" then + return self:__intersectToSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__intersectToTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__intersectToLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__intersectToRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__intersectToCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__intersectToRectangle(other) + end + return false, nil +end + +---仅检查是否与其他形状相交 +---@param other any +---@return boolean +function Rectangle:hasIntersection(other) + if other.__type == "foundation.shape.Segment" then + return self:__hasIntersectionWithSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__hasIntersectionWithTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__hasIntersectionWithLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__hasIntersectionWithRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__hasIntersectionWithCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__hasIntersectionWithRectangle(other) + end + return false +end + +---检查与线段的相交 +---@param other foundation.shape.Segment +---@return boolean, foundation.math.Vector2[] | nil +function Rectangle:__intersectToSegment(other) + local points = {} + local edges = self:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = edge:intersects(other) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + if self:contains(other.point1) then + points[#points + 1] = other.point1:clone() + end + if other.point1 ~= other.point2 and self:contains(other.point2) then + points[#points + 1] = other.point2:clone() + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与线段相交 +---@param other foundation.shape.Segment +---@return boolean +function Rectangle:__hasIntersectionWithSegment(other) + local edges = self:getEdges() + for _, edge in ipairs(edges) do + if edge:hasIntersection(other) then + return true + end + end + return self:contains(other.point1) or self:contains(other.point2) +end + +---检查与三角形的相交 +---@param other foundation.shape.Triangle +---@return boolean, foundation.math.Vector2[] | nil +function Rectangle:__intersectToTriangle(other) + local points = {} + local edges = self:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = edge:intersects(other) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + if self:contains(other.point1) then + points[#points + 1] = other.point1:clone() + end + if self:contains(other.point2) then + points[#points + 1] = other.point2:clone() + end + if self:contains(other.point3) then + points[#points + 1] = other.point3:clone() + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与三角形相交 +---@param other foundation.shape.Triangle +---@return boolean +function Rectangle:__hasIntersectionWithTriangle(other) + local edges = self:getEdges() + for _, edge in ipairs(edges) do + if edge:hasIntersection(other) then + return true + end + end + return self:contains(other.point1) or self:contains(other.point2) or self:contains(other.point3) +end + +---检查与直线的相交 +---@param other foundation.shape.Line +---@return boolean, foundation.math.Vector2[] | nil +function Rectangle:__intersectToLine(other) + local points = {} + local edges = self:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = edge:intersects(other) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与直线相交 +---@param other foundation.shape.Line +---@return boolean +function Rectangle:__hasIntersectionWithLine(other) + local edges = self:getEdges() + for _, edge in ipairs(edges) do + if edge:hasIntersection(other) then + return true + end + end + return false +end + +---检查与射线的相交 +---@param other foundation.shape.Ray +---@return boolean, foundation.math.Vector2[] | nil +function Rectangle:__intersectToRay(other) + local points = {} + local edges = self:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = edge:intersects(other) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + if self:contains(other.point) then + points[#points + 1] = other.point + points[#points + 1] = other.point:clone() + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与射线相交 +---@param other foundation.shape.Ray +---@return boolean +function Rectangle:__hasIntersectionWithRay(other) + local edges = self:getEdges() + for _, edge in ipairs(edges) do + if edge:hasIntersection(other) then + return true + end + end + return self:contains(other.point) +end + +---检查与圆的相交 +---@param other foundation.shape.Circle +---@return boolean, foundation.math.Vector2[] | nil +function Rectangle:__intersectToCircle(other) + local points = {} + local edges = self:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = edge:intersects(other) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + local vertices = self:getVertices() + for _, vertex in ipairs(vertices) do + if other:contains(vertex) then + points[#points + 1] = vertex:clone() + end + end + if self:contains(other.center) then + points[#points + 1] = other.center:clone() + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与圆相交 +---@param other foundation.shape.Circle +---@return boolean +function Rectangle:__hasIntersectionWithCircle(other) + if self:contains(other.center) then + return true + end + local edges = self:getEdges() + for _, edge in ipairs(edges) do + if edge:hasIntersection(other) then + return true + end + end + local vertices = self:getVertices() + for _, vertex in ipairs(vertices) do + if other:contains(vertex) then + return true + end + end + return false +end + +---检查与另一个矩形的相交 +---@param other foundation.shape.Rectangle +---@return boolean, foundation.math.Vector2[] | nil +function Rectangle:__intersectToRectangle(other) + local points = {} + local edges1 = self:getEdges() + local edges2 = other:getEdges() + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = edge1:intersects(edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + end + local vertices1 = self:getVertices() + for _, vertex in ipairs(vertices1) do + if other:contains(vertex) then + points[#points + 1] = vertex:clone() + end + end + local vertices2 = other:getVertices() + for _, vertex in ipairs(vertices2) do + if self:contains(vertex) then + points[#points + 1] = vertex:clone() + end + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与另一个矩形相交 +---@param other foundation.shape.Rectangle +---@return boolean +function Rectangle:__hasIntersectionWithRectangle(other) + local edges1 = self:getEdges() + local edges2 = other:getEdges() + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if edge1:hasIntersection(edge2) then + return true + end + end + end + local vertices1 = self:getVertices() + for _, vertex in ipairs(vertices1) do + if other:contains(vertex) then + return true + end + end + local vertices2 = other:getVertices() + for _, vertex in ipairs(vertices2) do + if self:contains(vertex) then + return true + end + end + return false +end + +---计算点到矩形的最近点 +---@param point foundation.math.Vector2 +---@return foundation.math.Vector2 +function Rectangle:closestPoint(point) + if self:contains(point) then + return point:clone() + end + local edges = self:getEdges() + local minDistance = math.huge + local closestPoint + for _, edge in ipairs(edges) do + local edgeClosest = edge:closestPoint(point) + local distance = (point - edgeClosest):length() + if distance < minDistance then + minDistance = distance + closestPoint = edgeClosest + end + end + return closestPoint +end + +---计算点到矩形的距离 +---@param point foundation.math.Vector2 +---@return number +function Rectangle:distanceToPoint(point) + if self:contains(point) then + return 0 + end + local edges = self:getEdges() + local minDistance = math.huge + for _, edge in ipairs(edges) do + local distance = edge:distanceToPoint(point) + if distance < minDistance then + minDistance = distance + end + end + return minDistance +end + +---将点投影到矩形上 +---@param point foundation.math.Vector2 +---@return foundation.math.Vector2 +function Rectangle:projectPoint(point) + return self:closestPoint(point) +end + +---检查点是否在矩形边界上 +---@param point foundation.math.Vector2 +---@param tolerance number|nil 默认为1e-10 +---@return boolean +function Rectangle:containsPoint(point, tolerance) + tolerance = tolerance or 1e-10 + local edges = self:getEdges() + for _, edge in ipairs(edges) do + if edge:containsPoint(point, tolerance) then + return true + end + end + return false +end + +ffi.metatype("foundation_shape_Rectangle", Rectangle) + +return Rectangle \ No newline at end of file From 885444bd56bc010c2eacd87055a876c7fbfa475f Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 02:19:24 +0800 Subject: [PATCH 20/71] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E4=B8=89=E8=A7=92=E5=BD=A2=E9=A1=B6=E7=82=B9=E5=92=8C?= =?UTF-8?q?=E8=BE=B9=E7=9A=84=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=9B=B8=E4=BA=A4=E6=A3=80=E6=B5=8B=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/shape/Triangle.lua | 218 +++++++++--------- 1 file changed, 104 insertions(+), 114 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 3129fd7f..6de5d36f 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -312,18 +312,34 @@ function Triangle:hasIntersection(other) return false end +---获取三角形的顶点 +---@return foundation.math.Vector2[] +function Triangle:getVertices() + return { + self.point1:clone(), + self.point2:clone(), + self.point3:clone() + } +end + +---获取三角形的边(线段) +---@return foundation.shape.Segment[] +function Triangle:getEdges() + return { + Segment.create(self.point1, self.point2), + Segment.create(self.point2, self.point3), + Segment.create(self.point3, self.point1) + } +end + ---检查三角形是否与线段相交 ---@param other foundation.shape.Segment 要检查的线段 ---@return boolean, foundation.math.Vector2[] | nil function Triangle:__intersectToSegment(other) local points = {} - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } - for i = 1, #edges do - local success, edge_points = edges[i]:intersects(other) + local edges = self:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = edge:intersects(other) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p @@ -357,28 +373,34 @@ end ---@return boolean, foundation.math.Vector2[] | nil function Triangle:__intersectToTriangle(other) local points = {} - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } - for i = 1, #edges do - local success, edge_points = edges[i]:intersects(other) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p + local edges1 = self:getEdges() + local edges2 = other:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = edge1:intersects(edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end end end end - if self:contains(other.point1) then - points[#points + 1] = other.point1:clone() - end - if self:contains(other.point2) then - points[#points + 1] = other.point2:clone() + + local vertices = other:getVertices() + for _, vertex in ipairs(vertices) do + if self:contains(vertex) then + points[#points + 1] = vertex:clone() + end end - if self:contains(other.point3) then - points[#points + 1] = other.point3:clone() + + vertices = self:getVertices() + for _, vertex in ipairs(vertices) do + if other:contains(vertex) then + points[#points + 1] = vertex:clone() + end end + local unique_points = {} local seen = {} for _, p in ipairs(points) do @@ -400,13 +422,9 @@ end ---@return boolean, foundation.math.Vector2[] | nil function Triangle:__intersectToLine(other) local points = {} - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } - for i = 1, #edges do - local success, edge_points = edges[i]:intersects(other) + local edges = self:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = edge:intersects(other) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p @@ -434,13 +452,9 @@ end ---@return boolean, foundation.math.Vector2[] | nil function Triangle:__intersectToRay(other) local points = {} - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } - for i = 1, #edges do - local success, edge_points = edges[i]:intersects(other) + local edges = self:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = edge:intersects(other) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p @@ -471,31 +485,27 @@ end ---@return boolean, foundation.math.Vector2[] | nil function Triangle:__intersectToCircle(other) local points = {} - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } - for i = 1, #edges do - local success, edge_points = edges[i]:intersects(other) + local edges = self:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = edge:intersects(other) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p end end end - if other:contains(self.point1) then - points[#points + 1] = self.point1:clone() - end - if other:contains(self.point2) then - points[#points + 1] = self.point2:clone() - end - if other:contains(self.point3) then - points[#points + 1] = self.point3:clone() + + local vertices = self:getVertices() + for _, vertex in ipairs(vertices) do + if other:contains(vertex) then + points[#points + 1] = vertex:clone() + end end + if self:contains(other.center) then points[#points + 1] = other.center:clone() end + local unique_points = {} local seen = {} for _, p in ipairs(points) do @@ -516,14 +526,10 @@ end ---@param other foundation.shape.Segment 要检查的线段 ---@return boolean function Triangle:__hasIntersectionWithSegment(other) - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } + local edges = self:getEdges() - for i = 1, #edges do - if edges[i]:hasIntersection(other) then + for _, edge in ipairs(edges) do + if edge:hasIntersection(other) then return true end end @@ -539,29 +545,29 @@ end ---@param other foundation.shape.Triangle 要检查的三角形 ---@return boolean function Triangle:__hasIntersectionWithTriangle(other) - local edges1 = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } - - local edges2 = { - Segment.create(other.point1, other.point2), - Segment.create(other.point2, other.point3), - Segment.create(other.point3, other.point1) - } + local edges1 = self:getEdges() + local edges2 = other:getEdges() - for i = 1, #edges1 do - for j = 1, #edges2 do - if edges1[i]:hasIntersection(edges2[j]) then + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if edge1:hasIntersection(edge2) then return true end end end - if self:contains(other.point1) or self:contains(other.point2) or self:contains(other.point3) or - other:contains(self.point1) or other:contains(self.point2) or other:contains(self.point3) then - return true + local vertices = other:getVertices() + for _, vertex in ipairs(vertices) do + if self:contains(vertex) then + return true + end + end + + vertices = self:getVertices() + for _, vertex in ipairs(vertices) do + if other:contains(vertex) then + return true + end end return false @@ -571,14 +577,10 @@ end ---@param other foundation.shape.Line 要检查的直线 ---@return boolean function Triangle:__hasIntersectionWithLine(other) - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } + local edges = self:getEdges() - for i = 1, #edges do - if edges[i]:hasIntersection(other) then + for _, edge in ipairs(edges) do + if edge:hasIntersection(other) then return true end end @@ -590,14 +592,10 @@ end ---@param other foundation.shape.Ray 要检查的射线 ---@return boolean function Triangle:__hasIntersectionWithRay(other) - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } + local edges = self:getEdges() - for i = 1, #edges do - if edges[i]:hasIntersection(other) then + for _, edge in ipairs(edges) do + if edge:hasIntersection(other) then return true end end @@ -617,15 +615,16 @@ function Triangle:__hasIntersectionWithCircle(other) return true end - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } + local edges = self:getEdges() + for _, edge in ipairs(edges) do + if edge:hasIntersection(other) then + return true + end + end - for i = 1, #edges do - local distance = edges[i]:distanceToPoint(other.center) - if distance <= other.radius then + local vertices = self:getVertices() + for _, vertex in ipairs(vertices) do + if other:contains(vertex) then return true end end @@ -655,17 +654,12 @@ function Triangle:closestPoint(point) return point:clone() end - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } - + local edges = self:getEdges() local minDistance = math.huge local closestPoint - for i = 1, #edges do - local edgeClosest = edges[i]:closestPoint(point) + for _, edge in ipairs(edges) do + local edgeClosest = edge:closestPoint(point) local distance = (point - edgeClosest):length() if distance < minDistance then @@ -717,13 +711,9 @@ end ---@overload fun(self:foundation.shape.Triangle, point:foundation.math.Vector2): boolean function Triangle:containsPoint(point, tolerance) tolerance = tolerance or 1e-10 - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } - for i = 1, #edges do - if edges[i]:containsPoint(point, tolerance) then + local edges = self:getEdges() + for _, edge in ipairs(edges) do + if edge:containsPoint(point, tolerance) then return true end end From 7ccbc4aca5bc56469e237b3d8c9a0254799eff4d Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 03:34:40 +0800 Subject: [PATCH 21/71] feat: add conversion methods for Vector2, Vector3, and Vector4, and enhance rotation functionalities --- .../foundation/math/Vector2.lua | 20 +- .../foundation/math/Vector3.lua | 199 ++++++++++++++++++ .../foundation/math/Vector4.lua | 171 +++++++++++++++ 3 files changed, 389 insertions(+), 1 deletion(-) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index 05773b0f..c303dd51 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -4,6 +4,9 @@ local type = type local string = string local math = math +local Vector3 = require("foundation.math.Vector3") +local Vector4 = require("foundation.math.Vector4") + ffi.cdef [[ typedef struct { double x; @@ -227,11 +230,26 @@ end ---获取向量的旋转指定角度的副本 ---@param angle number 旋转角度 ---@return foundation.math.Vector2 旋转后的向量副本 -function Vector2:degreeRotate(angle) +function Vector2:degreeRotated(angle) angle = math.rad(angle) return self:rotated(angle) end +---将Vector2转换为Vector3 +---@param z number|nil Z坐标分量,默认为0 +---@return foundation.math.Vector3 转换后的Vector3 +function Vector2:toVector3(z) + return Vector3.create(self.x, self.y, z or 0) +end + +---将Vector2转换为Vector4 +---@param z number|nil Z坐标分量,默认为0 +---@param w number|nil W坐标分量,默认为0 +---@return foundation.math.Vector4 转换后的Vector4 +function Vector2:toVector4(z, w) + return Vector4.create(self.x, self.y, z or 0, w or 0) +end + --region LuaSTG Evo API do Vector2.LuaSTG = Vector2.length diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua index 6f9df478..20485dc6 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua @@ -4,6 +4,9 @@ local type = type local string = string local math = math +local Vector2 = require("foundation.math.Vector2") +local Vector4 = require("foundation.math.Vector4") + ffi.cdef [[ typedef struct { double x; @@ -130,6 +133,19 @@ function Vector3:clone() return Vector3.create(self.x, self.y, self.z) end +---将Vector3转换为Vector2 +---@return foundation.math.Vector2 转换后的Vector2 +function Vector3:toVector2() + return Vector2.create(self.x, self.y) +end + +---将Vector3转换为Vector4 +---@param w number|nil W坐标分量,默认为0 +---@return foundation.math.Vector4 转换后的Vector4 +function Vector3:toVector4(w) + return Vector4.create(self.x, self.y, self.z, w or 0) +end + ---计算两个向量的点积 ---@param other foundation.math.Vector3 另一个向量 ---@return number 两个向量的点积 @@ -137,6 +153,36 @@ function Vector3:dot(other) return self.x * other.x + self.y * other.y + self.z * other.z end +---计算两个向量的叉积 +---@param other foundation.math.Vector3 另一个向量 +---@return foundation.math.Vector3 两个向量的叉积 +function Vector3:cross(other) + return Vector3.create( + self.y * other.z - self.z * other.y, + self.z * other.x - self.x * other.z, + self.x * other.y - self.y * other.x + ) +end + +---获取向量的球坐标角度(弧度) +---@return number,number 向量的极角和方位角,单位为弧度 +function Vector3:sphericalAngle() + local len = self:length() + if len < 1e-10 then + return 0, 0 + end + local theta = math.acos(self.z / len) + local phi = math.atan2(self.y, self.x) + return theta, phi +end + +---获取向量的球坐标角度(度) +---@return number,number 向量的极角和方位角,单位为度 +function Vector3:sphericalDegreeAngle() + local theta, phi = self:sphericalAngle() + return math.deg(theta), math.deg(phi) +end + ---将当前向量归一化(更改当前向量) ---@return foundation.math.Vector3 归一化后的向量(自身引用) function Vector3:normalize() @@ -159,6 +205,159 @@ function Vector3:normalized() return Vector3.create(self.x / len, self.y / len, self.z / len) end +---将当前向量围绕任意轴旋转指定弧度(更改当前向量) +---@param axis foundation.math.Vector3 旋转轴(应为单位向量) +---@param rad number 旋转弧度 +---@return foundation.math.Vector3 旋转后的向量(自身引用) +function Vector3:rotate(axis, rad) + local axis = axis:normalized() + local c = math.cos(rad) + local s = math.sin(rad) + local k = 1 - c + + local nx = self.x * (c + axis.x * axis.x * k) + + self.y * (axis.x * axis.y * k - axis.z * s) + + self.z * (axis.x * axis.z * k + axis.y * s) + + local ny = self.x * (axis.y * axis.x * k + axis.z * s) + + self.y * (c + axis.y * axis.y * k) + + self.z * (axis.y * axis.z * k - axis.x * s) + + local nz = self.x * (axis.z * axis.x * k - axis.y * s) + + self.y * (axis.z * axis.y * k + axis.x * s) + + self.z * (c + axis.z * axis.z * k) + + self.x, self.y, self.z = nx, ny, nz + return self +end + +---获取向量围绕任意轴旋转指定弧度后的副本 +---@param axis foundation.math.Vector3 旋转轴(应为单位向量) +---@param rad number 旋转弧度 +---@return foundation.math.Vector3 旋转后的向量副本 +function Vector3:rotated(axis, rad) + local result = self:clone() + return result:rotate(axis, rad) +end + +---将向量围绕任意轴旋转指定角度(更改当前向量) +---@param axis foundation.math.Vector3 旋转轴(应为单位向量) +---@param angle number 旋转角度(度) +---@return foundation.math.Vector3 旋转后的向量(自身引用) +function Vector3:degreeRotate(axis, angle) + return self:rotate(axis, math.rad(angle)) +end + +---获取向量围绕任意轴旋转指定角度后的副本 +---@param axis foundation.math.Vector3 旋转轴(应为单位向量) +---@param angle number 旋转角度(度) +---@return foundation.math.Vector3 旋转后的向量副本 +function Vector3:degreeRotated(axis, angle) + return self:rotated(axis, math.rad(angle)) +end + +---将向量围绕X轴旋转指定弧度(更改当前向量) +---@param rad number 旋转弧度 +---@return foundation.math.Vector3 旋转后的向量(自身引用) +function Vector3:rotateX(rad) + local c = math.cos(rad) + local s = math.sin(rad) + local y = self.y * c - self.z * s + local z = self.y * s + self.z * c + self.y, self.z = y, z + return self +end + +---获取向量围绕X轴旋转指定弧度后的副本 +---@param rad number 旋转弧度 +---@return foundation.math.Vector3 旋转后的向量副本 +function Vector3:rotatedX(rad) + local result = self:clone() + return result:rotateX(rad) +end + +---将向量围绕Y轴旋转指定弧度(更改当前向量) +---@param rad number 旋转弧度 +---@return foundation.math.Vector3 旋转后的向量(自身引用) +function Vector3:rotateY(rad) + local c = math.cos(rad) + local s = math.sin(rad) + local x = self.x * c + self.z * s + local z = -self.x * s + self.z * c + self.x, self.z = x, z + return self +end + +---获取向量围绕Y轴旋转指定弧度后的副本 +---@param rad number 旋转弧度 +---@return foundation.math.Vector3 旋转后的向量副本 +function Vector3:rotatedY(rad) + local result = self:clone() + return result:rotateY(rad) +end + +---将向量围绕Z轴旋转指定弧度(更改当前向量) +---@param rad number 旋转弧度 +---@return foundation.math.Vector3 旋转后的向量(自身引用) +function Vector3:rotateZ(rad) + local c = math.cos(rad) + local s = math.sin(rad) + local x = self.x * c - self.y * s + local y = self.x * s + self.y * c + self.x, self.y = x, y + return self +end + +---获取向量围绕Z轴旋转指定弧度后的副本 +---@param rad number 旋转弧度 +---@return foundation.math.Vector3 旋转后的向量副本 +function Vector3:rotatedZ(rad) + local result = self:clone() + return result:rotateZ(rad) +end + +---将向量围绕X轴旋转指定角度(更改当前向量) +---@param angle number 旋转角度(度) +---@return foundation.math.Vector3 旋转后的向量(自身引用) +function Vector3:degreeRotateX(angle) + return self:rotateX(math.rad(angle)) +end + +---获取向量围绕X轴旋转指定角度后的副本 +---@param angle number 旋转角度(度) +---@return foundation.math.Vector3 旋转后的向量副本 +function Vector3:degreeRotatedX(angle) + return self:rotatedX(math.rad(angle)) +end + +---将向量围绕Y轴旋转指定角度(更改当前向量) +---@param angle number 旋转角度(度) +---@return foundation.math.Vector3 旋转后的向量(自身引用) +function Vector3:degreeRotateY(angle) + return self:rotateY(math.rad(angle)) +end + +---获取向量围绕Y轴旋转指定角度后的副本 +---@param angle number 旋转角度(度) +---@return foundation.math.Vector3 旋转后的向量副本 +function Vector3:degreeRotatedY(angle) + return self:rotatedY(math.rad(angle)) +end + +---将向量围绕Z轴旋转指定角度(更改当前向量) +---@param angle number 旋转角度(度) +---@return foundation.math.Vector3 旋转后的向量(自身引用) +function Vector3:degreeRotateZ(angle) + return self:rotateZ(math.rad(angle)) +end + +---获取向量围绕Z轴旋转指定角度后的副本 +---@param angle number 旋转角度(度) +---@return foundation.math.Vector3 旋转后的向量副本 +function Vector3:degreeRotatedZ(angle) + return self:rotatedZ(math.rad(angle)) +end + ffi.metatype("foundation_math_Vector3", Vector3) return Vector3 \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua index 45cde2d5..f71aabfd 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua @@ -4,6 +4,9 @@ local type = type local string = string local math = math +local Vector2 = require("foundation.math.Vector2") +local Vector3 = require("foundation.math.Vector3") + ffi.cdef [[ typedef struct { double x; @@ -134,6 +137,18 @@ function Vector4:clone() return Vector4.create(self.x, self.y, self.z, self.w) end +---将Vector4转换为Vector2 +---@return foundation.math.Vector2 转换后的Vector2 +function Vector4:toVector2() + return Vector2.create(self.x, self.y) +end + +---将Vector4转换为Vector3 +---@return foundation.math.Vector3 转换后的Vector3 +function Vector4:toVector3() + return Vector3.create(self.x, self.y, self.z) +end + ---计算两个向量的点积 ---@param other foundation.math.Vector4 另一个向量 ---@return number 两个向量的点积 @@ -164,6 +179,162 @@ function Vector4:normalized() return Vector4.create(self.x / len, self.y / len, self.z / len, self.w / len) end +---获取向量的齐次坐标(将w分量归一化为1) +---@return foundation.math.Vector4 归一化后的齐次坐标向量 +function Vector4:homogeneous() + if math.abs(self.w) < 1e-10 then + return self:clone() + end + return Vector4.create(self.x / self.w, self.y / self.w, self.z / self.w, 1) +end + +---获取向量的投影坐标(将w分量归一化为1后返回一个Vector3) +---@return foundation.math.Vector3 投影后的三维向量 +function Vector4:projectTo3D() + if math.abs(self.w) < 1e-10 then + return Vector3.create(self.x, self.y, self.z) + end + return Vector3.create(self.x / self.w, self.y / self.w, self.z / self.w) +end + +---将当前向量的三维部分围绕任意轴旋转指定弧度(更改当前向量) +---@param axis foundation.math.Vector3 旋转轴(应为单位向量) +---@param rad number 旋转弧度 +---@return foundation.math.Vector4 旋转后的向量(自身引用) +function Vector4:rotate(axis, rad) + local vec3 = self:toVector3() + vec3:rotate(axis, rad) + self.x, self.y, self.z = vec3.x, vec3.y, vec3.z + return self +end + +---获取向量的三维部分围绕任意轴旋转指定弧度后的副本 +---@param axis foundation.math.Vector3 旋转轴(应为单位向量) +---@param rad number 旋转弧度 +---@return foundation.math.Vector4 旋转后的向量副本 +function Vector4:rotated(axis, rad) + local result = self:clone() + return result:rotate(axis, rad) +end + +---将向量的三维部分围绕任意轴旋转指定角度(更改当前向量) +---@param axis foundation.math.Vector3 旋转轴(应为单位向量) +---@param angle number 旋转角度(度) +---@return foundation.math.Vector4 旋转后的向量(自身引用) +function Vector4:degreeRotate(axis, angle) + return self:rotate(axis, math.rad(angle)) +end + +---获取向量的三维部分围绕任意轴旋转指定角度后的副本 +---@param axis foundation.math.Vector3 旋转轴(应为单位向量) +---@param angle number 旋转角度(度) +---@return foundation.math.Vector4 旋转后的向量副本 +function Vector4:degreeRotated(axis, angle) + return self:rotated(axis, math.rad(angle)) +end + +---将向量的三维部分围绕X轴旋转指定弧度(更改当前向量) +---@param rad number 旋转弧度 +---@return foundation.math.Vector4 旋转后的向量(自身引用) +function Vector4:rotateX(rad) + local c = math.cos(rad) + local s = math.sin(rad) + local y = self.y * c - self.z * s + local z = self.y * s + self.z * c + self.y, self.z = y, z + return self +end + +---获取向量的三维部分围绕X轴旋转指定弧度后的副本 +---@param rad number 旋转弧度 +---@return foundation.math.Vector4 旋转后的向量副本 +function Vector4:rotatedX(rad) + local result = self:clone() + return result:rotateX(rad) +end + +---将向量的三维部分围绕Y轴旋转指定弧度(更改当前向量) +---@param rad number 旋转弧度 +---@return foundation.math.Vector4 旋转后的向量(自身引用) +function Vector4:rotateY(rad) + local c = math.cos(rad) + local s = math.sin(rad) + local x = self.x * c + self.z * s + local z = -self.x * s + self.z * c + self.x, self.z = x, z + return self +end + +---获取向量的三维部分围绕Y轴旋转指定弧度后的副本 +---@param rad number 旋转弧度 +---@return foundation.math.Vector4 旋转后的向量副本 +function Vector4:rotatedY(rad) + local result = self:clone() + return result:rotateY(rad) +end + +---将向量的三维部分围绕Z轴旋转指定弧度(更改当前向量) +---@param rad number 旋转弧度 +---@return foundation.math.Vector4 旋转后的向量(自身引用) +function Vector4:rotateZ(rad) + local c = math.cos(rad) + local s = math.sin(rad) + local x = self.x * c - self.y * s + local y = self.x * s + self.y * c + self.x, self.y = x, y + return self +end + +---获取向量的三维部分围绕Z轴旋转指定弧度后的副本 +---@param rad number 旋转弧度 +---@return foundation.math.Vector4 旋转后的向量副本 +function Vector4:rotatedZ(rad) + local result = self:clone() + return result:rotateZ(rad) +end + +---将向量的三维部分围绕X轴旋转指定角度(更改当前向量) +---@param angle number 旋转角度(度) +---@return foundation.math.Vector4 旋转后的向量(自身引用) +function Vector4:degreeRotateX(angle) + return self:rotateX(math.rad(angle)) +end + +---获取向量的三维部分围绕X轴旋转指定角度后的副本 +---@param angle number 旋转角度(度) +---@return foundation.math.Vector4 旋转后的向量副本 +function Vector4:degreeRotatedX(angle) + return self:rotatedX(math.rad(angle)) +end + +---将向量的三维部分围绕Y轴旋转指定角度(更改当前向量) +---@param angle number 旋转角度(度) +---@return foundation.math.Vector4 旋转后的向量(自身引用) +function Vector4:degreeRotateY(angle) + return self:rotateY(math.rad(angle)) +end + +---获取向量的三维部分围绕Y轴旋转指定角度后的副本 +---@param angle number 旋转角度(度) +---@return foundation.math.Vector4 旋转后的向量副本 +function Vector4:degreeRotatedY(angle) + return self:rotatedY(math.rad(angle)) +end + +---将向量的三维部分围绕Z轴旋转指定角度(更改当前向量) +---@param angle number 旋转角度(度) +---@return foundation.math.Vector4 旋转后的向量(自身引用) +function Vector4:degreeRotateZ(angle) + return self:rotateZ(math.rad(angle)) +end + +---获取向量的三维部分围绕Z轴旋转指定角度后的副本 +---@param angle number 旋转角度(度) +---@return foundation.math.Vector4 旋转后的向量副本 +function Vector4:degreeRotatedZ(angle) + return self:rotatedZ(math.rad(angle)) +end + ffi.metatype("foundation_math_Vector4", Vector4) return Vector4 \ No newline at end of file From 83295d5c8df9ecfe34e4fdfbf34bbb589ef9bf1c Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 04:41:36 +0800 Subject: [PATCH 22/71] feat: add Sector class with intersection methods for Circle, Line, Ray, Segment, Rectangle, and Triangle --- .../foundation/math/Vector2.lua | 1 + .../foundation/shape/Circle.lua | 32 +- .../foundation/shape/Line.lua | 131 +--- .../thlib-scripts-v2/foundation/shape/Ray.lua | 220 ++---- .../foundation/shape/Rectangle.lua | 109 ++- .../foundation/shape/Sector.lua | 723 ++++++++++++++++++ .../foundation/shape/Segment.lua | 296 +++---- .../foundation/shape/Triangle.lua | 44 +- 8 files changed, 1071 insertions(+), 485 deletions(-) create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Sector.lua diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index c303dd51..ad67e527 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -41,6 +41,7 @@ end ---@param length number 向量长度 ---@return foundation.math.Vector2 新创建的向量 function Vector2.createFromRad(rad, length) + length = length or 1 local x = length * math.cos(rad) local y = length * math.sin(rad) return Vector2.create(x, y) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index f2251667..094be72a 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -28,7 +28,7 @@ Circle.__type = "foundation.shape.Circle" ---@param radius number 半径 ---@return foundation.shape.Circle function Circle.create(center, radius) - ---@diagnostic disable-next-line: return-type-mismatch + ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value return ffi.new("foundation_shape_Circle", center, radius) end @@ -113,6 +113,8 @@ function Circle:intersects(other) return self:__intersectToCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__intersectToRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__intersectToSector(other) end return false, nil end @@ -133,6 +135,8 @@ function Circle:hasIntersection(other) return self:__hasIntersectionWithCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__hasIntersectionWithRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__hasIntersectionWithSector(other) end return false end @@ -194,11 +198,7 @@ end ---@return boolean, foundation.math.Vector2[] | nil function Circle:__intersectToTriangle(other) local points = {} - local edges = { - Segment.create(other.point1, other.point2), - Segment.create(other.point2, other.point3), - Segment.create(other.point3, other.point1) - } + local edges = other:getEdges() for i = 1, #edges do local success, edge_points = self:__intersectToSegment(edges[i]) if success then @@ -415,6 +415,26 @@ function Circle:__hasIntersectionWithRectangle(other) return other:__hasIntersectionWithCircle(self) end +---检查与扇形的相交 +---@param other foundation.shape.Sector +---@return boolean, foundation.math.Vector2[] | nil +function Circle:__intersectToSector(other) + return other:__intersectToCircle(self) +end + +---仅检查是否与扇形相交 +---@param other foundation.shape.Sector +---@return boolean +function Circle:__hasIntersectionWithSector(other) + return other:__hasIntersectionWithCircle(self) +end + +---计算圆的面积 +---@return number 圆的面积 +function Circle:area() + return math.pi * self.radius * self.radius +end + ---计算点到圆的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 圆上最近的点 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index 50e6b198..bdde514c 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -31,11 +31,13 @@ function Line.create(point, direction) if dist == 0 then direction = Vector2.create(1, 0) elseif dist ~= 1 then + ---@diagnostic disable-next-line: need-check-nil direction = direction:normalized() else + ---@diagnostic disable-next-line: need-check-nil direction = direction:clone() end - ---@diagnostic disable-next-line: return-type-mismatch + ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value return ffi.new("foundation_shape_Line", point, direction) end @@ -53,7 +55,7 @@ end ---@param rad number 弧度 ---@return foundation.shape.Line function Line.createFromPointAndRad(point, rad) - local direction = Vector2.createFromRad(rad, 1) + local direction = Vector2.createFromRad(rad) return Line.create(point, direction) end @@ -62,7 +64,7 @@ end ---@param angle number 角度 ---@return foundation.shape.Line function Line.createFromPointAndAngle(point, angle) - local direction = Vector2.createFromAngle(angle, 1) + local direction = Vector2.createFromAngle(angle) return Line.create(point, direction) end @@ -109,6 +111,8 @@ function Line:intersects(other) return self:__intersectToCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__intersectToRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__intersectToSector(other) end return false, nil end @@ -129,6 +133,8 @@ function Line:hasIntersection(other) return self:__hasIntersectionWithCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__hasIntersectionWithRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__hasIntersectionWithSector(other) end return false end @@ -183,57 +189,6 @@ function Line:__hasIntersectionWithSegment(other) return u >= 0 and u <= 1 end ----检查与三角形的相交 ----@param other foundation.shape.Triangle ----@return boolean, foundation.math.Vector2[] | nil -function Line:__intersectToTriangle(other) - local points = {} - local edges = { - Segment.create(other.point1, other.point2), - Segment.create(other.point2, other.point3), - Segment.create(other.point3, other.point1) - } - for i = 1, #edges do - local success, edge_points = self:__intersectToSegment(edges[i]) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----检查是否与三角形相交 ----@param other foundation.shape.Triangle ----@return boolean -function Line:__hasIntersectionWithTriangle(other) - local edges = { - Segment.create(other.point1, other.point2), - Segment.create(other.point2, other.point3), - Segment.create(other.point3, other.point1) - } - for i = 1, #edges do - if self:__hasIntersectionWithSegment(edges[i]) then - return true - end - end - return false -end - ---检查与另一条直线的相交 ---@param other foundation.shape.Line ---@return boolean, foundation.math.Vector2[] | nil @@ -326,54 +281,32 @@ function Line:__hasIntersectionWithRay(other) return u >= 0 end +---检查与三角形的相交 +---@param other foundation.shape.Triangle +---@return boolean, foundation.math.Vector2[] | nil +function Line:__intersectToTriangle(other) + return other:__intersectToLine(self) +end + +---检查是否与三角形相交 +---@param other foundation.shape.Triangle +---@return boolean +function Line:__hasIntersectionWithTriangle(other) + return other:__hasIntersectionWithLine(self) +end + ---检查与圆的相交 ---@param other foundation.shape.Circle ---@return boolean, foundation.math.Vector2[] | nil function Line:__intersectToCircle(other) - local points = {} - local dir = self.direction - local len = dir:length() - if len == 0 then - return false, nil - end - dir = dir / len - local L = self.point - other.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - other.radius * other.radius - local discriminant = b * b - 4 * a * c - if discriminant >= 0 then - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - points[#points + 1] = self.point + dir * t1 - if math.abs(t2 - t1) > 1e-10 then - points[#points + 1] = self.point + dir * t2 - end - end - - if #points == 0 then - return false, nil - end - return true, points + return other:__intersectToLine(self) end ---检查是否与圆相交 ---@param other foundation.shape.Circle ---@return boolean function Line:__hasIntersectionWithCircle(other) - local dir = self.direction - local len = dir:length() - if len == 0 then - return false - end - dir = dir / len - local L = self.point - other.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - other.radius * other.radius - local discriminant = b * b - 4 * a * c - return discriminant >= 0 + return other:__hasIntersectionWithLine(self) end ---检查与矩形的相交 @@ -390,6 +323,20 @@ function Line:__hasIntersectionWithRectangle(other) return other:__hasIntersectionWithLine(self) end +---检查与扇形的相交 +---@param other foundation.shape.Sector +---@return boolean, foundation.math.Vector2[] | nil +function Line:__intersectToSector(other) + return other:__intersectToLine(self) +end + +---仅检查是否与扇形相交 +---@param other foundation.shape.Sector +---@return boolean +function Line:__hasIntersectionWithSector(other) + return other:__hasIntersectionWithLine(self) +end + ---计算点到直线的距离 ---@param point foundation.math.Vector2 点 ---@return number 距离 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index a581337b..4ff32f5d 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -31,11 +31,13 @@ function Ray.create(point, direction) if dist == 0 then direction = Vector2.create(1, 0) elseif dist ~= 1 then + ---@diagnostic disable-next-line: need-check-nil direction = direction:normalized() else + ---@diagnostic disable-next-line: need-check-nil direction = direction:clone() end - ---@diagnostic disable-next-line: return-type-mismatch + ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value return ffi.new("foundation_shape_Ray", point, direction) end @@ -44,7 +46,7 @@ end ---@param radian number 弧度 ---@return foundation.shape.Ray function Ray.createFromRad(point, radian) - local direction = Vector2.createFromRad(radian, 1) + local direction = Vector2.createFromRad(radian) return Ray.create(point, direction) end @@ -53,7 +55,7 @@ end ---@param angle number 角度 ---@return foundation.shape.Ray function Ray.createFromAngle(point, angle) - local direction = Vector2.createFromAngle(angle, 1) + local direction = Vector2.createFromAngle(angle) return Ray.create(point, direction) end @@ -95,6 +97,8 @@ function Ray:intersects(other) return self:__intersectToCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__intersectToRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__intersectToSector(other) end return false, nil end @@ -115,6 +119,8 @@ function Ray:hasIntersection(other) return self:__hasIntersectionWithCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__hasIntersectionWithRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__hasIntersectionWithSector(other) end return false end @@ -149,38 +155,24 @@ function Ray:__intersectToSegment(other) return true, points end ----检查与三角形的相交 ----@param other foundation.shape.Triangle ----@return boolean, foundation.math.Vector2[] | nil -function Ray:__intersectToTriangle(other) - local points = {} - local edges = { - Segment.create(other.point1, other.point2), - Segment.create(other.point2, other.point3), - Segment.create(other.point3, other.point1) - } - for i = 1, #edges do - local success, edge_points = self:__intersectToSegment(edges[i]) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end +---检查是否与线段相交 +---@param other foundation.shape.Segment +---@return boolean +function Ray:__hasIntersectionWithSegment(other) + local a = self.point + local b = self.point + self.direction + local c = other.point1 + local d = other.point2 - if #unique_points == 0 then - return false, nil + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false end - return true, unique_points + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and u >= 0 and u <= 1 end ---检查与直线的相交 @@ -213,6 +205,24 @@ function Ray:__intersectToLine(other) return true, points end +---检查是否与直线相交 +---@param other foundation.shape.Line +---@return boolean +function Ray:__hasIntersectionWithLine(other) + local a = self.point + local b = self.point + self.direction + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + return t >= 0 +end + ---检查与另一条射线的相交 ---@param other foundation.shape.Ray ---@return boolean, foundation.math.Vector2[] | nil @@ -255,95 +265,6 @@ function Ray:__intersectToRay(other) return true, points end ----检查与圆的相交 ----@param other foundation.shape.Circle ----@return boolean, foundation.math.Vector2[] | nil -function Ray:__intersectToCircle(other) - local points = {} - local dir = self.direction - local len = dir:length() - if len == 0 then - return false, nil - end - dir = dir / len - local L = self.point - other.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - other.radius * other.radius - local discriminant = b * b - 4 * a * c - if discriminant >= 0 then - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - if t1 >= 0 then - points[#points + 1] = self.point + dir * t1 - end - if t2 >= 0 and math.abs(t2 - t1) > 1e-10 then - points[#points + 1] = self.point + dir * t2 - end - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查是否与线段相交 ----@param other foundation.shape.Segment ----@return boolean -function Ray:__hasIntersectionWithSegment(other) - local a = self.point - local b = self.point + self.direction - local c = other.point1 - local d = other.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and u >= 0 and u <= 1 -end - ----检查是否与三角形相交 ----@param other foundation.shape.Triangle ----@return boolean -function Ray:__hasIntersectionWithTriangle(other) - local edges = { - Segment.create(other.point1, other.point2), - Segment.create(other.point2, other.point3), - Segment.create(other.point3, other.point1) - } - for i = 1, #edges do - if self:__hasIntersectionWithSegment(edges[i]) then - return true - end - end - return false -end - ----检查是否与直线相交 ----@param other foundation.shape.Line ----@return boolean -function Ray:__hasIntersectionWithLine(other) - local a = self.point - local b = self.point + self.direction - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - return t >= 0 -end - ---检查是否与另一条射线相交 ---@param other foundation.shape.Ray ---@return boolean @@ -370,29 +291,32 @@ function Ray:__hasIntersectionWithRay(other) return t >= 0 and u >= 0 end +---检查与三角形的相交 +---@param other foundation.shape.Triangle +---@return boolean, foundation.math.Vector2[] | nil +function Ray:__intersectToTriangle(other) + return other:__intersectToRay(self) +end + +---检查是否与三角形相交 +---@param other foundation.shape.Triangle +---@return boolean +function Ray:__hasIntersectionWithTriangle(other) + return other:__hasIntersectionWithRay(self) +end + +---检查与圆的相交 +---@param other foundation.shape.Circle +---@return boolean, foundation.math.Vector2[] | nil +function Ray:__intersectToCircle(other) + return other:__intersectToRay(self) +end + ---检查是否与圆相交 ---@param other foundation.shape.Circle ---@return boolean function Ray:__hasIntersectionWithCircle(other) - local dir = self.direction - local len = dir:length() - if len == 0 then - return false - end - dir = dir / len - local L = self.point - other.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - other.radius * other.radius - local discriminant = b * b - 4 * a * c - - if discriminant < 0 then - return false - end - - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - return t1 >= 0 + return other:__hasIntersectionWithRay(self) end ---检查与矩形的相交 @@ -409,6 +333,20 @@ function Ray:__hasIntersectionWithRectangle(other) return other:__hasIntersectionWithRay(self) end +---检查与扇形的相交 +---@param other foundation.shape.Sector +---@return boolean, foundation.math.Vector2[] | nil +function Ray:__intersectToSector(other) + return other:__intersectToRay(self) +end + +---仅检查是否与扇形相交 +---@param other foundation.shape.Sector +---@return boolean +function Ray:__hasIntersectionWithSector(other) + return other:__hasIntersectionWithRay(self) +end + ---计算点到射线的距离 ---@param point foundation.math.Vector2 点 ---@return number 距离 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index 8e880c2e..146421c0 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -38,11 +38,13 @@ function Rectangle.create(center, width, height, direction) if dist == 0 then direction = Vector2.create(1, 0) elseif dist ~= 1 then + ---@diagnostic disable-next-line: need-check-nil direction = direction:normalized() else + ---@diagnostic disable-next-line: need-check-nil direction = direction:clone() end - ---@diagnostic disable-next-line: return-type-mismatch + ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value return ffi.new("foundation_shape_Rectangle", center, width, height, direction) end @@ -53,7 +55,7 @@ end ---@param rad number 旋转弧度 ---@return foundation.shape.Rectangle function Rectangle.createFromRad(center, width, height, rad) - local direction = Vector2.createFromRad(rad, 1) + local direction = Vector2.createFromRad(rad) return Rectangle.create(center, width, height, direction) end @@ -64,7 +66,7 @@ end ---@param angle number 旋转角度 ---@return foundation.shape.Rectangle function Rectangle.createFromAngle(center, width, height, angle) - local direction = Vector2.createFromAngle(angle, 1) + local direction = Vector2.createFromAngle(angle) return Rectangle.create(center, width, height, direction) end @@ -228,6 +230,8 @@ function Rectangle:intersects(other) return self:__intersectToCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__intersectToRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__intersectToSector(other) end return false, nil end @@ -248,6 +252,8 @@ function Rectangle:hasIntersection(other) return self:__hasIntersectionWithCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__hasIntersectionWithRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__hasIntersectionWithSector(other) end return false end @@ -259,7 +265,7 @@ function Rectangle:__intersectToSegment(other) local points = {} local edges = self:getEdges() for _, edge in ipairs(edges) do - local success, edge_points = edge:intersects(other) + local success, edge_points = other:__intersectToSegment(edge) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p @@ -300,29 +306,20 @@ function Rectangle:__hasIntersectionWithSegment(other) return self:contains(other.point1) or self:contains(other.point2) end ----检查与三角形的相交 ----@param other foundation.shape.Triangle +---检查与直线的相交 +---@param other foundation.shape.Line ---@return boolean, foundation.math.Vector2[] | nil -function Rectangle:__intersectToTriangle(other) +function Rectangle:__intersectToLine(other) local points = {} local edges = self:getEdges() for _, edge in ipairs(edges) do - local success, edge_points = edge:intersects(other) + local success, edge_points = other:__intersectToSegment(edge) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p end end end - if self:contains(other.point1) then - points[#points + 1] = other.point1:clone() - end - if self:contains(other.point2) then - points[#points + 1] = other.point2:clone() - end - if self:contains(other.point3) then - points[#points + 1] = other.point3:clone() - end local unique_points = {} local seen = {} for _, p in ipairs(points) do @@ -338,33 +335,36 @@ function Rectangle:__intersectToTriangle(other) return true, unique_points end ----仅检查是否与三角形相交 ----@param other foundation.shape.Triangle +---仅检查是否与直线相交 +---@param other foundation.shape.Line ---@return boolean -function Rectangle:__hasIntersectionWithTriangle(other) +function Rectangle:__hasIntersectionWithLine(other) local edges = self:getEdges() for _, edge in ipairs(edges) do if edge:hasIntersection(other) then return true end end - return self:contains(other.point1) or self:contains(other.point2) or self:contains(other.point3) + return false end ----检查与直线的相交 ----@param other foundation.shape.Line +---检查与射线的相交 +---@param other foundation.shape.Ray ---@return boolean, foundation.math.Vector2[] | nil -function Rectangle:__intersectToLine(other) +function Rectangle:__intersectToRay(other) local points = {} local edges = self:getEdges() for _, edge in ipairs(edges) do - local success, edge_points = edge:intersects(other) + local success, edge_points = other:__intersectToSegment(edge) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p end end end + if self:contains(other.point) then + points[#points + 1] = other.point:clone() + end local unique_points = {} local seen = {} for _, p in ipairs(points) do @@ -380,36 +380,41 @@ function Rectangle:__intersectToLine(other) return true, unique_points end ----仅检查是否与直线相交 ----@param other foundation.shape.Line +---仅检查是否与射线相交 +---@param other foundation.shape.Ray ---@return boolean -function Rectangle:__hasIntersectionWithLine(other) +function Rectangle:__hasIntersectionWithRay(other) local edges = self:getEdges() for _, edge in ipairs(edges) do if edge:hasIntersection(other) then return true end end - return false + return self:contains(other.point) end ----检查与射线的相交 ----@param other foundation.shape.Ray +---检查与三角形的相交 +---@param other foundation.shape.Triangle ---@return boolean, foundation.math.Vector2[] | nil -function Rectangle:__intersectToRay(other) +function Rectangle:__intersectToTriangle(other) local points = {} local edges = self:getEdges() for _, edge in ipairs(edges) do - local success, edge_points = edge:intersects(other) + local success, edge_points = other:__intersectToSegment(edge) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p end end end - if self:contains(other.point) then - points[#points + 1] = other.point - points[#points + 1] = other.point:clone() + if self:contains(other.point1) then + points[#points + 1] = other.point1:clone() + end + if self:contains(other.point2) then + points[#points + 1] = other.point2:clone() + end + if self:contains(other.point3) then + points[#points + 1] = other.point3:clone() end local unique_points = {} local seen = {} @@ -426,17 +431,17 @@ function Rectangle:__intersectToRay(other) return true, unique_points end ----仅检查是否与射线相交 ----@param other foundation.shape.Ray +---仅检查是否与三角形相交 +---@param other foundation.shape.Triangle ---@return boolean -function Rectangle:__hasIntersectionWithRay(other) +function Rectangle:__hasIntersectionWithTriangle(other) local edges = self:getEdges() for _, edge in ipairs(edges) do if edge:hasIntersection(other) then return true end end - return self:contains(other.point) + return self:contains(other.point1) or self:contains(other.point2) or self:contains(other.point3) end ---检查与圆的相交 @@ -446,7 +451,7 @@ function Rectangle:__intersectToCircle(other) local points = {} local edges = self:getEdges() for _, edge in ipairs(edges) do - local success, edge_points = edge:intersects(other) + local success, edge_points = other:__intersectToSegment(edge) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p @@ -508,7 +513,7 @@ function Rectangle:__intersectToRectangle(other) local edges2 = other:getEdges() for _, edge1 in ipairs(edges1) do for _, edge2 in ipairs(edges2) do - local success, edge_points = edge1:intersects(edge2) + local success, edge_points = edge1:__intersectToSegment(edge2) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p @@ -571,6 +576,26 @@ function Rectangle:__hasIntersectionWithRectangle(other) return false end +---检查与扇形的相交 +---@param other foundation.shape.Sector +---@return boolean, foundation.math.Vector2[] | nil +function Rectangle:__intersectToSector(other) + return other:__intersectToRay(self) +end + +---仅检查是否与扇形相交 +---@param other foundation.shape.Sector +---@return boolean +function Rectangle:__hasIntersectionWithSector(other) + return other:__hasIntersectionWithRay(self) +end + +---计算矩形的面积 +---@return number 矩形的面积 +function Rectangle:area() + return self.width * self.height +end + ---计算点到矩形的最近点 ---@param point foundation.math.Vector2 ---@return foundation.math.Vector2 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua new file mode 100644 index 00000000..5c9c150f --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -0,0 +1,723 @@ +local ffi = require("ffi") + +local math = math +local type = type +local ipairs = ipairs +local tostring = tostring +local string = string + +local Vector2 = require("foundation.math.Vector2") +local Segment = require("foundation.shape.Segment") +local Circle = require("foundation.shape.Circle") + +ffi.cdef [[ +typedef struct { + foundation_math_Vector2 center; + double radius; + foundation_math_Vector2 startDirection; + foundation_math_Vector2 endDirection; +} foundation_shape_Sector; +]] + +---@class foundation.shape.Sector +---@field center foundation.math.Vector2 扇形的中心点 +---@field radius number 扇形的半径 +---@field startDirection foundation.math.Vector2 起始方向(归一化向量) +---@field endDirection foundation.math.Vector2 终止方向(归一化向量) +local Sector = {} +Sector.__index = Sector +Sector.__type = "foundation.shape.Sector" + +---创建一个新的扇形 +---@param center foundation.math.Vector2 中心点 +---@param radius number 半径 +---@param startDirection foundation.math.Vector2 起始方向(将归一化) +---@param endDirection foundation.math.Vector2 终止方向(将归一化) +---@return foundation.shape.Sector +function Sector.create(center, radius, startDirection, endDirection) + local startDir = startDirection:normalized() + local endDir = endDirection:normalized() + ---@diagnostic disable-next-line: return-type-mismatch + return ffi.new("foundation_shape_Sector", center, radius, startDir, endDir) +end + +---使用弧度创建扇形 +---@param center foundation.math.Vector2 中心点 +---@param radius number 半径 +---@param startRad number 起始弧度 +---@param endRad number 终止弧度 +---@return foundation.shape.Sector +function Sector.createFromRad(center, radius, startRad, endRad) + local startDir = Vector2.createFromRad(startRad) + local endDir = Vector2.createFromRad(endRad) + return Sector.create(center, radius, startDir, endDir) +end + +---使用角度创建扇形 +---@param center foundation.math.Vector2 中心点 +---@param radius number 半径 +---@param startAngle number 起始角度 +---@param endAngle number 终止角度 +---@return foundation.shape.Sector +function Sector.createFromAngle(center, radius, startAngle, endAngle) + local startRad = math.rad(startAngle) + local endRad = math.rad(endAngle) + return Sector.createFromRad(center, radius, startRad, endRad) +end + +---扇形相等比较 +---@param a foundation.shape.Sector +---@param b foundation.shape.Sector +---@return boolean +function Sector.__eq(a, b) + return a.center == b.center and + math.abs(a.radius - b.radius) < 1e-10 and + a.startDirection == b.startDirection and + a.endDirection == b.endDirection +end + +---扇形的字符串表示 +---@param self foundation.shape.Sector +---@return string +function Sector.__tostring(self) + return string.format("Sector(center=%s, radius=%f, startDirection=%s, endDirection=%s)", + tostring(self.center), self.radius, tostring(self.startDirection), tostring(self.endDirection)) +end + +---计算扇形角度(弧度) +---@return number +function Sector:getAngle() + local dot = self.startDirection:dot(self.endDirection) + dot = math.max(-1, math.min(1, dot)) -- 防止浮点误差 + local angle = math.acos(dot) + local cross = self.startDirection.x * self.endDirection.y - self.startDirection.y * self.endDirection.x + if cross < 0 then + angle = 2 * math.pi - angle + end + return angle +end + +---计算扇形的面积 +---@return number +function Sector:area() + return 0.5 * self.radius * self.radius * self:getAngle() +end + +---检查点是否在扇形内(包括边界) +---@param point foundation.math.Vector2 +---@return boolean +function Sector:contains(point) + local vec = point - self.center + local dist = vec:length() + if dist > self.radius then + return false + end + local dir = vec:normalized() + local startAngle = math.atan2(self.startDirection.y, self.startDirection.x) % (2 * math.pi) + local endAngle = math.atan2(self.endDirection.y, self.endDirection.x) % (2 * math.pi) + local pointAngle = math.atan2(dir.y, dir.x) % (2 * math.pi) + if startAngle < 0 then + startAngle = startAngle + 2 * math.pi + end + if endAngle < 0 then + endAngle = endAngle + 2 * math.pi + end + if pointAngle < 0 then + pointAngle = pointAngle + 2 * math.pi + end + if startAngle <= endAngle then + return pointAngle >= startAngle and pointAngle <= endAngle + else + return pointAngle >= startAngle or pointAngle <= endAngle + end +end + +---移动扇形(修改当前扇形) +---@param v foundation.math.Vector2 | number +---@return foundation.shape.Sector +function Sector:move(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + self.center.x = self.center.x + moveX + self.center.y = self.center.y + moveY + return self +end + +---获取移动后的扇形副本 +---@param v foundation.math.Vector2 | number +---@return foundation.shape.Sector +function Sector:moved(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + return Sector.create( + Vector2.create(self.center.x + moveX, self.center.y + moveY), + self.radius, self.startDirection, self.endDirection + ) +end + +---旋转扇形(修改当前扇形) +---@param rad number 旋转弧度 +---@return foundation.shape.Sector +function Sector:rotate(rad) + self.startDirection = self.startDirection:rotated(rad) + self.endDirection = self.endDirection:rotated(rad) + return self +end + +---获取旋转后的扇形副本 +---@param rad number 旋转弧度 +---@return foundation.shape.Sector +function Sector:rotated(rad) + return Sector.create( + self.center, self.radius, + self.startDirection:rotated(rad), + self.endDirection:rotated(rad) + ) +end + +---缩放扇形(修改当前扇形) +---@param scale number +---@return foundation.shape.Sector +function Sector:scale(scale) + self.radius = self.radius * scale + return self +end + +---获取缩放后的扇形副本 +---@param scale number +---@return foundation.shape.Sector +function Sector:scaled(scale) + return Sector.create(self.center, self.radius * scale, self.startDirection, self.endDirection) +end + +---检查与其他形状的相交 +---@param other any +---@return boolean, foundation.math.Vector2[] | nil +function Sector:intersects(other) + if other.__type == "foundation.shape.Segment" then + return self:__intersectToSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__intersectToTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__intersectToLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__intersectToRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__intersectToCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__intersectToRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__intersectToSector(other) + end + return false, nil +end + +---仅检查是否与其他形状相交 +---@param other any +---@return boolean +function Sector:hasIntersection(other) + if other.__type == "foundation.shape.Segment" then + return self:__hasIntersectionWithSegment(other) + elseif other.__type == "foundation.shape.Triangle" then + return self:__hasIntersectionWithTriangle(other) + elseif other.__type == "foundation.shape.Line" then + return self:__hasIntersectionWithLine(other) + elseif other.__type == "foundation.shape.Ray" then + return self:__hasIntersectionWithRay(other) + elseif other.__type == "foundation.shape.Circle" then + return self:__hasIntersectionWithCircle(other) + elseif other.__type == "foundation.shape.Rectangle" then + return self:__hasIntersectionWithRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__hasIntersectionWithSector(other) + end + return false +end + +---检查与线段的相交 +---@param other foundation.shape.Segment +---@return boolean, foundation.math.Vector2[] | nil +function Sector:__intersectToSegment(other) + local points = {} + local circle = Circle.create(self.center, self.radius) + local success, circle_points = circle:__intersectToSegment(other) + if success then + for _, p in ipairs(circle_points) do + if self:contains(p) then + points[#points + 1] = p + end + end + end + if self:contains(other.point1) then + points[#points + 1] = other.point1:clone() + end + if other.point1 ~= other.point2 and self:contains(other.point2) then + points[#points + 1] = other.point2:clone() + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与线段相交 +---@param other foundation.shape.Segment +---@return boolean +function Sector:__hasIntersectionWithSegment(other) + local circle = Circle.create(self.center, self.radius) + if not circle:hasIntersection(other) then + return false + end + if self:contains(other.point1) or self:contains(other.point2) then + return true + end + local success, points = circle:__intersectToSegment(other) + if success then + for _, p in ipairs(points) do + if self:contains(p) then + return true + end + end + end + return false +end + +---检查与三角形的相交 +---@param other foundation.shape.Triangle +---@return boolean, foundation.math.Vector2[] | nil +function Sector:__intersectToTriangle(other) + local points = {} + local edges = other:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = self:__intersectToSegment(edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + local vertices = other:getVertices() + for _, vertex in ipairs(vertices) do + if self:contains(vertex) then + points[#points + 1] = vertex:clone() + end + end + if other:contains(self.center) then + points[#points + 1] = self.center:clone() + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与三角形相交 +---@param other foundation.shape.Triangle +---@return boolean +function Sector:__hasIntersectionWithTriangle(other) + if other:contains(self.center) then + return true + end + local edges = other:getEdges() + for _, edge in ipairs(edges) do + if self:__hasIntersectionWithSegment(edge) then + return true + end + end + local vertices = other:getVertices() + for _, vertex in ipairs(vertices) do + if self:contains(vertex) then + return true + end + end + return false +end + +---检查与直线的相交 +---@param other foundation.shape.Line +---@return boolean, foundation.math.Vector2[] | nil +function Sector:__intersectToLine(other) + local points = {} + local circle = Circle.create(self.center, self.radius) + local success, circle_points = circle:__intersectToLine(other) + if success then + for _, p in ipairs(circle_points) do + if self:contains(p) then + points[#points + 1] = p + end + end + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与直线相交 +---@param other foundation.shape.Line +---@return boolean +function Sector:__hasIntersectionWithLine(other) + local circle = Circle.create(self.center, self.radius) + if not circle:hasIntersection(other) then + return false + end + local success, points = circle:__intersectToLine(other) + if success then + for _, p in ipairs(points) do + if self:contains(p) then + return true + end + end + end + return false +end + +---检查与射线的相交 +---@param other foundation.shape.Ray +---@return boolean, foundation.math.Vector2[] | nil +function Sector:__intersectToRay(other) + local points = {} + local circle = Circle.create(self.center, self.radius) + local success, circle_points = circle:__intersectToRay(other) + if success then + for _, p in ipairs(circle_points) do + if self:contains(p) then + points[#points + 1] = p + end + end + end + if self:contains(other.point) then + points[#points + 1] = other.point:clone() + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与射线相交 +---@param other foundation.shape.Ray +---@return boolean +function Sector:__hasIntersectionWithRay(other) + if self:contains(other.point) then + return true + end + local circle = Circle.create(self.center, self.radius) + if not circle:hasIntersection(other) then + return false + end + local success, points = circle:__intersectToRay(other) + if success then + for _, p in ipairs(points) do + if self:contains(p) then + return true + end + end + end + return false +end + +---检查与圆的相交 +---@param other foundation.shape.Circle +---@return boolean, foundation.math.Vector2[] | nil +function Sector:__intersectToCircle(other) + local points = {} + local circle = Circle.create(self.center, self.radius) + local success, circle_points = circle:__intersectToCircle(other) + if success then + for _, p in ipairs(circle_points) do + if self:contains(p) then + points[#points + 1] = p + end + end + end + if self:contains(other.center) then + points[#points + 1] = other.center:clone() + end + if other:contains(self.center) then + points[#points + 1] = self.center:clone() + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与圆相交 +---@param other foundation.shape.Circle +---@return boolean +function Sector:__hasIntersectionWithCircle(other) + if self:contains(other.center) or other:contains(self.center) then + return true + end + local circle = Circle.create(self.center, self.radius) + if not circle:hasIntersection(other) then + return false + end + local success, points = circle:__intersectToCircle(other) + if success then + for _, p in ipairs(points) do + if self:contains(p) then + return true + end + end + end + return false +end + +---检查与矩形的相交 +---@param other foundation.shape.Rectangle +---@return boolean, foundation.math.Vector2[] | nil +function Sector:__intersectToRectangle(other) + local points = {} + local edges = other:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = self:__intersectToSegment(edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + local vertices = other:getVertices() + for _, vertex in ipairs(vertices) do + if self:contains(vertex) then + points[#points + 1] = vertex:clone() + end + end + if other:contains(self.center) then + points[#points + 1] = self.center:clone() + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与矩形相交 +---@param other foundation.shape.Rectangle +---@return boolean +function Sector:__hasIntersectionWithRectangle(other) + if other:contains(self.center) then + return true + end + local edges = other:getEdges() + for _, edge in ipairs(edges) do + if self:__hasIntersectionWithSegment(edge) then + return true + end + end + local vertices = other:getVertices() + for _, vertex in ipairs(vertices) do + if self:contains(vertex) then + return true + end + end + return false +end + +---检查与另一个扇形的相交 +---@param other foundation.shape.Sector +---@return boolean, foundation.math.Vector2[] | nil +function Sector:__intersectToSector(other) + local points = {} + local circle1 = Circle.create(self.center, self.radius) + local circle2 = Circle.create(other.center, other.radius) + local success, circle_points = circle1:__intersectToCircle(circle2) + if success then + for _, p in ipairs(circle_points) do + if self:contains(p) and other:contains(p) then + points[#points + 1] = p + end + end + end + if self:contains(other.center) then + points[#points + 1] = other.center:clone() + end + if other:contains(self.center) then + points[#points + 1] = self.center:clone() + end + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查是否与另一个扇形相交 +---@param other foundation.shape.Sector +---@return boolean +function Sector:__hasIntersectionWithSector(other) + if self:contains(other.center) or other:contains(self.center) then + return true + end + local circle1 = Circle.create(self.center, self.radius) + local circle2 = Circle.create(other.center, other.radius) + if not circle1:hasIntersection(circle2) then + return false + end + local success, points = circle1:__intersectToCircle(circle2) + if success then + for _, p in ipairs(points) do + if self:contains(p) and other:contains(p) then + return true + end + end + end + return false +end + +---计算点到扇形的最近点 +---@param point foundation.math.Vector2 +---@return foundation.math.Vector2 +function Sector:closestPoint(point) + if self:contains(point) then + return point:clone() + end + local circle = Circle.create(self.center, self.radius) + local circle_closest = circle:closestPoint(point) + if self:contains(circle_closest) then + return circle_closest + end + local start_point = self.center + self.startDirection * self.radius + local end_point = self.center + self.endDirection * self.radius + local start_segment = Segment.create(self.center, start_point) + local end_segment = Segment.create(self.center, end_point) + local candidates = { + start_segment:closestPoint(point), + end_segment:closestPoint(point) + } + local min_distance = math.huge + local closest_point = candidates[1] + for _, candidate in ipairs(candidates) do + local distance = (point - candidate):length() + if distance < min_distance then + min_distance = distance + closest_point = candidate + end + end + return closest_point +end + +---计算点到扇形的距离 +---@param point foundation.math.Vector2 +---@return number +function Sector:distanceToPoint(point) + if self:contains(point) then + return 0 + end + return (point - self:closestPoint(point)):length() +end + +---将点投影到扇形上 +---@param point foundation.math.Vector2 +---@return foundation.math.Vector2 +function Sector:projectPoint(point) + return self:closestPoint(point) +end + +---检查点是否在扇形边界上 +---@param point foundation.math.Vector2 +---@param tolerance number|nil +---@return boolean +function Sector:containsPoint(point, tolerance) + tolerance = tolerance or 1e-10 + local circle = Circle.create(self.center, self.radius) + if not circle:containsPoint(point, tolerance) then + return false + end + local vec = point - self.center + local dir = vec:normalized() + local startAngle = math.atan2(self.startDirection.y, self.startDirection.x) % (2 * math.pi) + local endAngle = math.atan2(self.endDirection.y, self.endDirection.x) % (2 * math.pi) + local pointAngle = math.atan2(dir.y, dir.x) % (2 * math.pi) + if startAngle < 0 then + startAngle = startAngle + 2 * math.pi + end + if endAngle < 0 then + endAngle = endAngle + 2 * math.pi + end + if pointAngle < 0 then + pointAngle = pointAngle + 2 * math.pi + end + if startAngle <= endAngle then + return math.abs(pointAngle - startAngle) < tolerance or math.abs(pointAngle - endAngle) < tolerance + else + return math.abs(pointAngle - startAngle) < tolerance or math.abs(pointAngle - endAngle) < tolerance or + (pointAngle >= startAngle or pointAngle <= endAngle) + end +end + +ffi.metatype("foundation_shape_Sector", Sector) + +return Sector \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index 90fc2c80..9e0c6b07 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -26,7 +26,7 @@ Segment.__type = "foundation.shape.Segment" ---@param point2 foundation.math.Vector2 线段的终点 ---@return foundation.shape.Segment function Segment.create(point1, point2) - ---@diagnostic disable-next-line: return-type-mismatch + ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value return ffi.new("foundation_shape_Segment", point1, point2) end @@ -249,6 +249,8 @@ function Segment:intersects(other) return self:__intersectToCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__intersectToRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__intersectToSector(other) end return false, nil end @@ -269,132 +271,12 @@ function Segment:hasIntersection(other) return self:__hasIntersectionWithCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__hasIntersectionWithRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__hasIntersectionWithSector(other) end return false end ----仅检查线段是否与另一个线段相交 ----@param other foundation.shape.Segment 要检查的线段 ----@return boolean -function Segment:__hasIntersectionWithSegment(other) - local a = self.point1 - local b = self.point2 - local c = other.point1 - local d = other.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - local dir = self.point2 - self.point1 - local len = dir:length() - if len == 0 then - return other:containsPoint(self.point1) - end - dir = dir / len - local t1 = ((c.x - a.x) * dir.x + (c.y - a.y) * dir.y) / len - local t2 = ((d.x - a.x) * dir.x + (d.y - a.y) * dir.y) / len - if t1 > 1 or t2 < 0 or (t1 < 0 and t2 < 0) or (t1 > 1 and t2 > 1) then - return false - end - local start_t = math.max(0, math.min(t1, t2)) - local end_t = math.min(1, math.max(t1, t2)) - return start_t <= end_t - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and t <= 1 and u >= 0 and u <= 1 -end - ----仅检查线段是否与三角形相交 ----@param other foundation.shape.Triangle 要检查的三角形 ----@return boolean -function Segment:__hasIntersectionWithTriangle(other) - local edges = { - Segment.create(other.point1, other.point2), - Segment.create(other.point2, other.point3), - Segment.create(other.point3, other.point1) - } - - for i = 1, #edges do - if self:__hasIntersectionWithSegment(edges[i]) then - return true - end - end - - if other:contains(self.point1) or (self.point1 ~= self.point2 and other:contains(self.point2)) then - return true - end - - return false -end - ----仅检查线段是否与直线相交 ----@param other foundation.shape.Line 要检查的直线 ----@return boolean -function Segment:__hasIntersectionWithLine(other) - local a = self.point1 - local b = self.point2 - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - return t >= 0 and t <= 1 -end - ----仅检查线段是否与射线相交 ----@param other foundation.shape.Ray 要检查的射线 ----@return boolean -function Segment:__hasIntersectionWithRay(other) - local a = self.point1 - local b = self.point2 - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and t <= 1 and u >= 0 -end - ----仅检查线段是否与圆相交 ----@param other foundation.shape.Circle 要检查的圆 ----@return boolean -function Segment:__hasIntersectionWithCircle(other) - local dir = self.point2 - self.point1 - local len = dir:length() - if len == 0 then - return (self.point1 - other.center):length() <= other.radius - end - - dir = dir / len - local L = self.point1 - other.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - other.radius * other.radius - local discriminant = b * b - 4 * a * c - - if discriminant < 0 then - return false - end - - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - - return (t1 >= 0 and t1 <= len) or (t2 >= 0 and t2 <= len) -end - ---检查线段是否与另一个线段相交 ---@param other foundation.shape.Segment 要检查的线段 ---@return boolean, foundation.math.Vector2[] | nil @@ -455,44 +337,37 @@ function Segment:__intersectToSegment(other) return true, points end ----检查线段是否与三角形相交 ----@param other foundation.shape.Triangle 要检查的三角形 ----@return boolean, foundation.math.Vector2[] | nil -function Segment:__intersectToTriangle(other) - local points = {} - local edges = { - Segment.create(other.point1, other.point2), - Segment.create(other.point2, other.point3), - Segment.create(other.point3, other.point1) - } - for i = 1, #edges do - local success, edge_points = self:__intersectToSegment(edges[i]) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end +---仅检查线段是否与另一个线段相交 +---@param other foundation.shape.Segment 要检查的线段 +---@return boolean +function Segment:__hasIntersectionWithSegment(other) + local a = self.point1 + local b = self.point2 + local c = other.point1 + local d = other.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + local dir = self.point2 - self.point1 + local len = dir:length() + if len == 0 then + return other:containsPoint(self.point1) end - end - if other:contains(self.point1) then - points[#points + 1] = self.point1:clone() - end - if self.point1 ~= self.point2 and other:contains(self.point2) then - points[#points + 1] = self.point2:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p + dir = dir / len + local t1 = ((c.x - a.x) * dir.x + (c.y - a.y) * dir.y) / len + local t2 = ((d.x - a.x) * dir.x + (d.y - a.y) * dir.y) / len + if t1 > 1 or t2 < 0 or (t1 < 0 and t2 < 0) or (t1 > 1 and t2 > 1) then + return false end + local start_t = math.max(0, math.min(t1, t2)) + local end_t = math.min(1, math.max(t1, t2)) + return start_t <= end_t end - if #unique_points == 0 then - return false, nil - end - return true, unique_points + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and t <= 1 and u >= 0 and u <= 1 end ---检查线段是否与直线相交 @@ -523,6 +398,24 @@ function Segment:__intersectToLine(other) return true, points end +---仅检查线段是否与直线相交 +---@param other foundation.shape.Line 要检查的直线 +---@return boolean +function Segment:__hasIntersectionWithLine(other) + local a = self.point1 + local b = self.point2 + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + return t >= 0 and t <= 1 +end + ---检查线段是否与射线相交 ---@param other foundation.shape.Ray 要检查的射线 ---@return boolean, foundation.math.Vector2[] | nil @@ -553,45 +446,52 @@ function Segment:__intersectToRay(other) return true, points end +---仅检查线段是否与射线相交 +---@param other foundation.shape.Ray 要检查的射线 +---@return boolean +function Segment:__hasIntersectionWithRay(other) + local a = self.point1 + local b = self.point2 + local c = other.point + local d = other.point + other.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and t <= 1 and u >= 0 +end + +---检查线段是否与三角形相交 +---@param other foundation.shape.Triangle 要检查的三角形 +---@return boolean, foundation.math.Vector2[] | nil +function Segment:__intersectToTriangle(other) + return other:__intersectToSegment(self) +end + +---仅检查线段是否与三角形相交 +---@param other foundation.shape.Triangle 要检查的三角形 +---@return boolean +function Segment:__hasIntersectionWithTriangle(other) + return other:__hasIntersectionWithSegment(self) +end + ---检查线段是否与圆相交 ---@param other foundation.shape.Circle 要检查的圆 ---@return boolean, foundation.math.Vector2[] | nil function Segment:__intersectToCircle(other) - local points = {} - local dir = self.point2 - self.point1 - local len = dir:length() - if len == 0 then - if (self.point1 - other.center):length() <= other.radius then - points[#points + 1] = self.point1:clone() - end - - if #points == 0 then - return false, nil - end - return true, points - end - dir = dir / len - local L = self.point1 - other.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - other.radius * other.radius - local discriminant = b * b - 4 * a * c - if discriminant >= 0 then - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - if t1 >= 0 and t1 <= len then - points[#points + 1] = self.point1 + dir * t1 - end - if t2 >= 0 and t2 <= len and math.abs(t2 - t1) > 1e-10 then - points[#points + 1] = self.point1 + dir * t2 - end - end + return other:__intersectToSegment(self) +end - if #points == 0 then - return false, nil - end - return true, points +---仅检查线段是否与圆相交 +---@param other foundation.shape.Circle 要检查的圆 +---@return boolean +function Segment:__hasIntersectionWithCircle(other) + return other:__hasIntersectionWithSegment(self) end ---检查与矩形的相交 @@ -608,6 +508,20 @@ function Segment:__hasIntersectionWithRectangle(other) return other:__hasIntersectionWithSegment(self) end +---检查与扇形的相交 +---@param other foundation.shape.Sector +---@return boolean, foundation.math.Vector2[] | nil +function Segment:__intersectToSector(other) + return other:__intersectToSegment(self) +end + +---仅检查是否与扇形相交 +---@param other foundation.shape.Sector +---@return boolean +function Segment:__hasIntersectionWithSector(other) + return other:__hasIntersectionWithSegment(self) +end + ---计算点到线段的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 线段上最近的点 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 6de5d36f..4bee08c3 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -29,7 +29,7 @@ Triangle.__type = "foundation.shape.Triangle" ---@param v3 foundation.math.Vector2 三角形的第三个顶点 ---@return foundation.shape.Triangle 新创建的三角形 function Triangle.create(v1, v2, v3) - ---@diagnostic disable-next-line: return-type-mismatch + ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value return ffi.new("foundation_shape_Triangle", v1, v2, v3) end @@ -288,6 +288,8 @@ function Triangle:intersects(other) return self:__intersectToCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__intersectToRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__intersectToSector(other) end return false, nil end @@ -308,6 +310,8 @@ function Triangle:hasIntersection(other) return self:__hasIntersectionWithCircle(other) elseif other.__type == "foundation.shape.Rectangle" then return self:__hasIntersectionWithRectangle(other) + elseif other.__type == "foundation.shape.Sector" then + return self:__hasIntersectionWithSector(other) end return false end @@ -339,7 +343,7 @@ function Triangle:__intersectToSegment(other) local points = {} local edges = self:getEdges() for _, edge in ipairs(edges) do - local success, edge_points = edge:intersects(other) + local success, edge_points = edge:__intersectToSegment(other) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p @@ -375,10 +379,10 @@ function Triangle:__intersectToTriangle(other) local points = {} local edges1 = self:getEdges() local edges2 = other:getEdges() - + for _, edge1 in ipairs(edges1) do for _, edge2 in ipairs(edges2) do - local success, edge_points = edge1:intersects(edge2) + local success, edge_points = edge1:__intersectToSegment(edge2) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p @@ -386,21 +390,21 @@ function Triangle:__intersectToTriangle(other) end end end - + local vertices = other:getVertices() for _, vertex in ipairs(vertices) do if self:contains(vertex) then points[#points + 1] = vertex:clone() end end - + vertices = self:getVertices() for _, vertex in ipairs(vertices) do if other:contains(vertex) then points[#points + 1] = vertex:clone() end end - + local unique_points = {} local seen = {} for _, p in ipairs(points) do @@ -424,7 +428,7 @@ function Triangle:__intersectToLine(other) local points = {} local edges = self:getEdges() for _, edge in ipairs(edges) do - local success, edge_points = edge:intersects(other) + local success, edge_points = edge:__intersectToLine(other) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p @@ -454,7 +458,7 @@ function Triangle:__intersectToRay(other) local points = {} local edges = self:getEdges() for _, edge in ipairs(edges) do - local success, edge_points = edge:intersects(other) + local success, edge_points = edge:__intersectToRay(other) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p @@ -487,25 +491,25 @@ function Triangle:__intersectToCircle(other) local points = {} local edges = self:getEdges() for _, edge in ipairs(edges) do - local success, edge_points = edge:intersects(other) + local success, edge_points = edge:__intersectToCircle(other) if success then for _, p in ipairs(edge_points) do points[#points + 1] = p end end end - + local vertices = self:getVertices() for _, vertex in ipairs(vertices) do if other:contains(vertex) then points[#points + 1] = vertex:clone() end end - + if self:contains(other.center) then points[#points + 1] = other.center:clone() end - + local unique_points = {} local seen = {} for _, p in ipairs(points) do @@ -646,6 +650,20 @@ function Triangle:__hasIntersectionWithRectangle(other) return other:__hasIntersectionWithTriangle(self) end +---检查与扇形的相交 +---@param other foundation.shape.Sector +---@return boolean, foundation.math.Vector2[] | nil +function Triangle:__intersectToSector(other) + return other:__intersectToTriangle(self) +end + +---仅检查是否与扇形相交 +---@param other foundation.shape.Sector +---@return boolean +function Triangle:__hasIntersectionWithSector(other) + return other:__hasIntersectionWithTriangle(self) +end + ---计算点到三角形的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 三角形上最近的点 From 5e44fdefa4b53542399a5b4d6b3fb70675f3d181 Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 04:42:18 +0800 Subject: [PATCH 23/71] refactor: remove unused local variables from Line, Ray, and Segment modules --- game/packages/thlib-scripts-v2/foundation/shape/Line.lua | 2 -- game/packages/thlib-scripts-v2/foundation/shape/Ray.lua | 2 -- game/packages/thlib-scripts-v2/foundation/shape/Segment.lua | 1 - 3 files changed, 5 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index bdde514c..ebec5f65 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -1,12 +1,10 @@ local ffi = require("ffi") local math = math -local ipairs = ipairs local tostring = tostring local string = string local Vector2 = require("foundation.math.Vector2") -local Segment = require("foundation.shape.Segment") ffi.cdef [[ typedef struct { diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index 4ff32f5d..b0558d79 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -1,12 +1,10 @@ local ffi = require("ffi") local math = math -local ipairs = ipairs local tostring = tostring local string = string local Vector2 = require("foundation.math.Vector2") -local Segment = require("foundation.shape.Segment") ffi.cdef [[ typedef struct { diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index 9e0c6b07..0080034d 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -1,7 +1,6 @@ local ffi = require("ffi") local type = type -local ipairs = ipairs local tostring = tostring local string = string local math = math From 7f8fed5c27852edeb5149fc8f7972040ecc6d73d Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 05:24:07 +0800 Subject: [PATCH 24/71] fix comment --- game/packages/thlib-scripts-v2/foundation/math/Vector2.lua | 2 ++ game/packages/thlib-scripts-v2/foundation/shape/Sector.lua | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index ad67e527..7bd28cdd 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -40,6 +40,7 @@ end ---@param rad number 弧度 ---@param length number 向量长度 ---@return foundation.math.Vector2 新创建的向量 +---@overload fun(rad: number): foundation.math.Vector2 function Vector2.createFromRad(rad, length) length = length or 1 local x = length * math.cos(rad) @@ -51,6 +52,7 @@ end ---@param angle number 角度 ---@param length number 向量长度 ---@return foundation.math.Vector2 新创建的向量 +---@overload fun(angle: number): foundation.math.Vector2 function Vector2.createFromAngle(angle, length) local rad = math.rad(angle) return Vector2.createFromRad(rad, length) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index 5c9c150f..90a95c6b 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -37,7 +37,7 @@ Sector.__type = "foundation.shape.Sector" function Sector.create(center, radius, startDirection, endDirection) local startDir = startDirection:normalized() local endDir = endDirection:normalized() - ---@diagnostic disable-next-line: return-type-mismatch + ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value return ffi.new("foundation_shape_Sector", center, radius, startDir, endDir) end From 7003c4a3d2682e854d9aebb2a8e130d9449808fe Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 07:06:15 +0800 Subject: [PATCH 25/71] refactor: simplify intersection checks by utilizing ShapeIntersector methods --- .../foundation/math/Vector2.lua | 5 +- .../foundation/math/Vector3.lua | 7 +- .../foundation/math/Vector4.lua | 6 +- .../foundation/shape/Circle.lua | 327 +-- .../foundation/shape/Line.lua | 233 +-- .../thlib-scripts-v2/foundation/shape/Ray.lua | 257 +-- .../foundation/shape/Rectangle.lua | 375 +--- .../foundation/shape/Sector.lua | 458 +---- .../foundation/shape/Segment.lua | 284 +-- .../foundation/shape/ShapeIntersector.lua | 1769 +++++++++++++++++ .../foundation/shape/Triangle.lua | 382 +--- 11 files changed, 1806 insertions(+), 2297 deletions(-) create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index 7bd28cdd..e76884b0 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -4,9 +4,6 @@ local type = type local string = string local math = math -local Vector3 = require("foundation.math.Vector3") -local Vector4 = require("foundation.math.Vector4") - ffi.cdef [[ typedef struct { double x; @@ -242,6 +239,7 @@ end ---@param z number|nil Z坐标分量,默认为0 ---@return foundation.math.Vector3 转换后的Vector3 function Vector2:toVector3(z) + local Vector3 = require("foundation.math.Vector3") return Vector3.create(self.x, self.y, z or 0) end @@ -250,6 +248,7 @@ end ---@param w number|nil W坐标分量,默认为0 ---@return foundation.math.Vector4 转换后的Vector4 function Vector2:toVector4(z, w) + local Vector4 = require("foundation.math.Vector4") return Vector4.create(self.x, self.y, z or 0, w or 0) end diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua index 20485dc6..c6ec9c11 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua @@ -4,9 +4,6 @@ local type = type local string = string local math = math -local Vector2 = require("foundation.math.Vector2") -local Vector4 = require("foundation.math.Vector4") - ffi.cdef [[ typedef struct { double x; @@ -136,6 +133,7 @@ end ---将Vector3转换为Vector2 ---@return foundation.math.Vector2 转换后的Vector2 function Vector3:toVector2() + local Vector2 = require("foundation.math.Vector2") return Vector2.create(self.x, self.y) end @@ -143,6 +141,7 @@ end ---@param w number|nil W坐标分量,默认为0 ---@return foundation.math.Vector4 转换后的Vector4 function Vector3:toVector4(w) + local Vector4 = require("foundation.math.Vector4") return Vector4.create(self.x, self.y, self.z, w or 0) end @@ -210,7 +209,7 @@ end ---@param rad number 旋转弧度 ---@return foundation.math.Vector3 旋转后的向量(自身引用) function Vector3:rotate(axis, rad) - local axis = axis:normalized() + axis = axis:normalized() local c = math.cos(rad) local s = math.sin(rad) local k = 1 - c diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua index f71aabfd..2e5b4898 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua @@ -4,9 +4,6 @@ local type = type local string = string local math = math -local Vector2 = require("foundation.math.Vector2") -local Vector3 = require("foundation.math.Vector3") - ffi.cdef [[ typedef struct { double x; @@ -140,12 +137,14 @@ end ---将Vector4转换为Vector2 ---@return foundation.math.Vector2 转换后的Vector2 function Vector4:toVector2() + local Vector2 = require("foundation.math.Vector2") return Vector2.create(self.x, self.y) end ---将Vector4转换为Vector3 ---@return foundation.math.Vector3 转换后的Vector3 function Vector4:toVector3() + local Vector3 = require("foundation.math.Vector3") return Vector3.create(self.x, self.y, self.z) end @@ -191,6 +190,7 @@ end ---获取向量的投影坐标(将w分量归一化为1后返回一个Vector3) ---@return foundation.math.Vector3 投影后的三维向量 function Vector4:projectTo3D() + local Vector3 = require("foundation.math.Vector3") if math.abs(self.w) < 1e-10 then return Vector3.create(self.x, self.y, self.z) end diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index 094be72a..a6d29c1f 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -2,12 +2,11 @@ local ffi = require("ffi") local math = math local type = type -local ipairs = ipairs local tostring = tostring local string = string local Vector2 = require("foundation.math.Vector2") -local Segment = require("foundation.shape.Segment") +local ShapeIntersector = require("foundation.shape.ShapeIntersector") ffi.cdef [[ typedef struct { @@ -51,7 +50,7 @@ end ---@param point foundation.math.Vector2 ---@return boolean function Circle:contains(point) - return (point - self.center):length() <= self.radius + return ShapeIntersector.circleContainsPoint(self, point) end ---移动圆(修改当前圆) @@ -101,332 +100,14 @@ end ---@param other any ---@return boolean, foundation.math.Vector2[] | nil function Circle:intersects(other) - if other.__type == "foundation.shape.Segment" then - return self:__intersectToSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__intersectToTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__intersectToLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__intersectToRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__intersectToCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__intersectToRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__intersectToSector(other) - end - return false, nil + return ShapeIntersector.intersect(self, other) end ---只检查是否与其他形状相交,不计算交点 ---@param other any ---@return boolean function Circle:hasIntersection(other) - if other.__type == "foundation.shape.Segment" then - return self:__hasIntersectionWithSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__hasIntersectionWithTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__hasIntersectionWithLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__hasIntersectionWithRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__hasIntersectionWithCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__hasIntersectionWithRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__hasIntersectionWithSector(other) - end - return false -end - ----检查与线段的相交 ----@param other foundation.shape.Segment ----@return boolean, foundation.math.Vector2[] | nil -function Circle:__intersectToSegment(other) - local closest = other:closestPoint(self.center) - local points = {} - if (closest - self.center):length() <= self.radius then - local dir = other.point2 - other.point1 - local len = dir:length() - if len == 0 then - if (other.point1 - self.center):length() <= self.radius then - points[#points + 1] = other.point1:clone() - end - - if #points == 0 then - return false, nil - end - return true, points - end - dir = dir / len - local L = other.point1 - self.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - self.radius * self.radius - local discriminant = b * b - 4 * a * c - if discriminant >= 0 then - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - if t1 >= 0 and t1 <= len then - points[#points + 1] = other.point1 + dir * t1 - end - if t2 >= 0 and t2 <= len and math.abs(t2 - t1) > 1e-10 then - points[#points + 1] = other.point1 + dir * t2 - end - end - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查是否与线段相交 ----@param other foundation.shape.Segment ----@return boolean -function Circle:__hasIntersectionWithSegment(other) - local closest = other:closestPoint(self.center) - return (closest - self.center):length() <= self.radius -end - ----检查与三角形的相交 ----@param other foundation.shape.Triangle ----@return boolean, foundation.math.Vector2[] | nil -function Circle:__intersectToTriangle(other) - local points = {} - local edges = other:getEdges() - for i = 1, #edges do - local success, edge_points = self:__intersectToSegment(edges[i]) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if other:contains(self.center) then - points[#points + 1] = self.center:clone() - end - if self:contains(other.point1) then - points[#points + 1] = other.point1:clone() - end - if self:contains(other.point2) then - points[#points + 1] = other.point2:clone() - end - if self:contains(other.point3) then - points[#points + 1] = other.point3:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----检查是否与三角形相交 ----@param other foundation.shape.Triangle ----@return boolean -function Circle:__hasIntersectionWithTriangle(other) - local edges = { - Segment.create(other.point1, other.point2), - Segment.create(other.point2, other.point3), - Segment.create(other.point3, other.point1) - } - for i = 1, #edges do - if self:__hasIntersectionWithSegment(edges[i]) then - return true - end - end - - if other:contains(self.center) then - return true - end - - return self:contains(other.point1) or - self:contains(other.point2) or - self:contains(other.point3) -end - ----检查与直线的相交 ----@param other foundation.shape.Line ----@return boolean, foundation.math.Vector2[] | nil -function Circle:__intersectToLine(other) - local points = {} - local dir = other.direction - local len = dir:length() - if len == 0 then - return false, nil - end - dir = dir / len - local L = other.point - self.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - self.radius * self.radius - local discriminant = b * b - 4 * a * c - if discriminant >= 0 then - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - points[#points + 1] = other.point + dir * t1 - if math.abs(t2 - t1) > 1e-10 then - points[#points + 1] = other.point + dir * t2 - end - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查是否与直线相交 ----@param other foundation.shape.Line ----@return boolean -function Circle:__hasIntersectionWithLine(other) - local dir = other.direction - local len = dir:length() - if len == 0 then - return false - end - dir = dir / len - local L = other.point - self.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - self.radius * self.radius - local discriminant = b * b - 4 * a * c - return discriminant >= 0 -end - ----检查与射线的相交 ----@param other foundation.shape.Ray ----@return boolean, foundation.math.Vector2[] | nil -function Circle:__intersectToRay(other) - local points = {} - local dir = other.direction - local len = dir:length() - if len == 0 then - return false, nil - end - dir = dir / len - local L = other.point - self.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - self.radius * self.radius - local discriminant = b * b - 4 * a * c - if discriminant >= 0 then - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - if t1 >= 0 then - points[#points + 1] = other.point + dir * t1 - end - if t2 >= 0 and math.abs(t2 - t1) > 1e-10 then - points[#points + 1] = other.point + dir * t2 - end - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查是否与射线相交 ----@param other foundation.shape.Ray ----@return boolean -function Circle:__hasIntersectionWithRay(other) - local dir = other.direction - local len = dir:length() - if len == 0 then - return false - end - dir = dir / len - local L = other.point - self.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - self.radius * self.radius - local discriminant = b * b - 4 * a * c - - if discriminant < 0 then - return false - end - - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - - return t1 >= 0 or t2 >= 0 -end - ----检查与另一个圆的相交 ----@param other foundation.shape.Circle ----@return boolean, foundation.math.Vector2[] | nil -function Circle:__intersectToCircle(other) - local points = {} - local d = (self.center - other.center):length() - if d <= self.radius + other.radius and d >= math.abs(self.radius - other.radius) then - local a = (self.radius * self.radius - other.radius * other.radius + d * d) / (2 * d) - local h = math.sqrt(self.radius * self.radius - a * a) - local p2 = self.center + (other.center - self.center) * (a / d) - local perp = Vector2.create(-(other.center.y - self.center.y), other.center.x - self.center.x):normalized() * h - points[#points + 1] = p2 + perp - if math.abs(h) > 1e-10 then - points[#points + 1] = p2 - perp - end - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查是否与另一个圆相交 ----@param other foundation.shape.Circle ----@return boolean -function Circle:__hasIntersectionWithCircle(other) - local d = (self.center - other.center):length() - return d <= self.radius + other.radius and d >= math.abs(self.radius - other.radius) -end - ----检查与矩形的相交 ----@param other foundation.shape.Rectangle ----@return boolean, foundation.math.Vector2[] | nil -function Circle:__intersectToRectangle(other) - return other:__intersectToCircle(self) -end - ----仅检查是否与矩形相交 ----@param other foundation.shape.Rectangle ----@return boolean -function Circle:__hasIntersectionWithRectangle(other) - return other:__hasIntersectionWithCircle(self) -end - ----检查与扇形的相交 ----@param other foundation.shape.Sector ----@return boolean, foundation.math.Vector2[] | nil -function Circle:__intersectToSector(other) - return other:__intersectToCircle(self) -end - ----仅检查是否与扇形相交 ----@param other foundation.shape.Sector ----@return boolean -function Circle:__hasIntersectionWithSector(other) - return other:__hasIntersectionWithCircle(self) + return ShapeIntersector.hasIntersection(self, other) end ---计算圆的面积 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index ebec5f65..644ff298 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -5,6 +5,7 @@ local tostring = tostring local string = string local Vector2 = require("foundation.math.Vector2") +local ShapeIntersector = require("foundation.shape.ShapeIntersector") ffi.cdef [[ typedef struct { @@ -97,242 +98,14 @@ end ---@param other any ---@return boolean, foundation.math.Vector2[] | nil function Line:intersects(other) - if other.__type == "foundation.shape.Segment" then - return self:__intersectToSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__intersectToTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__intersectToLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__intersectToRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__intersectToCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__intersectToRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__intersectToSector(other) - end - return false, nil + return ShapeIntersector.intersect(self, other) end ---检查是否与其他形状相交,只返回是否相交的布尔值 ---@param other any ---@return boolean function Line:hasIntersection(other) - if other.__type == "foundation.shape.Segment" then - return self:__hasIntersectionWithSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__hasIntersectionWithTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__hasIntersectionWithLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__hasIntersectionWithRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__hasIntersectionWithCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__hasIntersectionWithRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__hasIntersectionWithSector(other) - end - return false -end - ----检查与线段的相交 ----@param other foundation.shape.Segment ----@return boolean, foundation.math.Vector2[] | nil -function Line:__intersectToSegment(other) - local points = {} - local a = self.point - local b = self.point + self.direction - local c = other.point1 - local d = other.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false, points - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if u >= 0 and u <= 1 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查是否与线段相交 ----@param other foundation.shape.Segment ----@return boolean -function Line:__hasIntersectionWithSegment(other) - local a = self.point - local b = self.point + self.direction - local c = other.point1 - local d = other.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false - end - - --local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return u >= 0 and u <= 1 -end - ----检查与另一条直线的相交 ----@param other foundation.shape.Line ----@return boolean, foundation.math.Vector2[] | nil -function Line:__intersectToLine(other) - local points = {} - local a = self.point - local b = self.point + self.direction - local c = other.point - local d = other.point + other.direction - - local dir_cross = self.direction:cross(other.direction) - if math.abs(dir_cross) < 1e-10 then - local point_diff = other.point - self.point - if math.abs(point_diff:cross(self.direction)) < 1e-10 then - points[#points + 1] = self.point:clone() - points[#points + 1] = self:getPoint(1) - return true, points - else - return false, nil - end - end - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - - return true, points -end - ----检查是否与另一条直线相交 ----@param other foundation.shape.Line ----@return boolean -function Line:__hasIntersectionWithLine(other) - local dir_cross = self.direction:cross(other.direction) - if math.abs(dir_cross) < 1e-10 then - local point_diff = other.point - self.point - return math.abs(point_diff:cross(self.direction)) < 1e-10 - end - return true -end - ----检查与射线的相交 ----@param other foundation.shape.Ray ----@return boolean, foundation.math.Vector2[] | nil -function Line:__intersectToRay(other) - local points = {} - local a = self.point - local b = self.point + self.direction - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if u >= 0 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查是否与射线相交 ----@param other foundation.shape.Ray ----@return boolean -function Line:__hasIntersectionWithRay(other) - local a = self.point - local b = self.point + self.direction - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false - end - - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return u >= 0 -end - ----检查与三角形的相交 ----@param other foundation.shape.Triangle ----@return boolean, foundation.math.Vector2[] | nil -function Line:__intersectToTriangle(other) - return other:__intersectToLine(self) -end - ----检查是否与三角形相交 ----@param other foundation.shape.Triangle ----@return boolean -function Line:__hasIntersectionWithTriangle(other) - return other:__hasIntersectionWithLine(self) -end - ----检查与圆的相交 ----@param other foundation.shape.Circle ----@return boolean, foundation.math.Vector2[] | nil -function Line:__intersectToCircle(other) - return other:__intersectToLine(self) -end - ----检查是否与圆相交 ----@param other foundation.shape.Circle ----@return boolean -function Line:__hasIntersectionWithCircle(other) - return other:__hasIntersectionWithLine(self) -end - ----检查与矩形的相交 ----@param other foundation.shape.Rectangle ----@return boolean, foundation.math.Vector2[] | nil -function Line:__intersectToRectangle(other) - return other:__intersectToLine(self) -end - ----仅检查是否与矩形相交 ----@param other foundation.shape.Rectangle ----@return boolean -function Line:__hasIntersectionWithRectangle(other) - return other:__hasIntersectionWithLine(self) -end - ----检查与扇形的相交 ----@param other foundation.shape.Sector ----@return boolean, foundation.math.Vector2[] | nil -function Line:__intersectToSector(other) - return other:__intersectToLine(self) -end - ----仅检查是否与扇形相交 ----@param other foundation.shape.Sector ----@return boolean -function Line:__hasIntersectionWithSector(other) - return other:__hasIntersectionWithLine(self) + return ShapeIntersector.hasIntersection(self, other) end ---计算点到直线的距离 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index b0558d79..c100cdb8 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -5,6 +5,7 @@ local tostring = tostring local string = string local Vector2 = require("foundation.math.Vector2") +local ShapeIntersector = require("foundation.shape.ShapeIntersector") ffi.cdef [[ typedef struct { @@ -83,266 +84,14 @@ end ---@param other any ---@return boolean, foundation.math.Vector2[] | nil function Ray:intersects(other) - if other.__type == "foundation.shape.Segment" then - return self:__intersectToSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__intersectToTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__intersectToLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__intersectToRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__intersectToCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__intersectToRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__intersectToSector(other) - end - return false, nil + return ShapeIntersector.intersect(self, other) end ---检查是否与其他形状相交,只返回是否相交的布尔值 ---@param other any ---@return boolean function Ray:hasIntersection(other) - if other.__type == "foundation.shape.Segment" then - return self:__hasIntersectionWithSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__hasIntersectionWithTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__hasIntersectionWithLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__hasIntersectionWithRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__hasIntersectionWithCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__hasIntersectionWithRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__hasIntersectionWithSector(other) - end - return false -end - ----检查与线段的相交 ----@param other foundation.shape.Segment ----@return boolean, foundation.math.Vector2[] | nil -function Ray:__intersectToSegment(other) - local points = {} - local a = self.point - local b = self.point + self.direction - local c = other.point1 - local d = other.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if t >= 0 and u >= 0 and u <= 1 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查是否与线段相交 ----@param other foundation.shape.Segment ----@return boolean -function Ray:__hasIntersectionWithSegment(other) - local a = self.point - local b = self.point + self.direction - local c = other.point1 - local d = other.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and u >= 0 and u <= 1 -end - ----检查与直线的相交 ----@param other foundation.shape.Line ----@return boolean, foundation.math.Vector2[] | nil -function Ray:__intersectToLine(other) - local points = {} - local a = self.point - local b = self.point + self.direction - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - --local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if t >= 0 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查是否与直线相交 ----@param other foundation.shape.Line ----@return boolean -function Ray:__hasIntersectionWithLine(other) - local a = self.point - local b = self.point + self.direction - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - return t >= 0 -end - ----检查与另一条射线的相交 ----@param other foundation.shape.Ray ----@return boolean, foundation.math.Vector2[] | nil -function Ray:__intersectToRay(other) - local points = {} - local a = self.point - local b = self.point + self.direction - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - local dir_cross = self.direction:cross(other.direction) - if math.abs(dir_cross) < 1e-10 then - local point_diff = other.point - self.point - local t = point_diff:dot(self.direction) - if t >= 0 then - points[#points + 1] = self.point + self.direction * t - end - end - - if #points == 0 then - return false, nil - end - return true, points - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if t >= 0 and u >= 0 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查是否与另一条射线相交 ----@param other foundation.shape.Ray ----@return boolean -function Ray:__hasIntersectionWithRay(other) - local a = self.point - local b = self.point + self.direction - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - local dir_cross = self.direction:cross(other.direction) - if math.abs(dir_cross) < 1e-10 then - local point_diff = other.point - self.point - local t = point_diff:dot(self.direction) - return t >= 0 - end - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and u >= 0 -end - ----检查与三角形的相交 ----@param other foundation.shape.Triangle ----@return boolean, foundation.math.Vector2[] | nil -function Ray:__intersectToTriangle(other) - return other:__intersectToRay(self) -end - ----检查是否与三角形相交 ----@param other foundation.shape.Triangle ----@return boolean -function Ray:__hasIntersectionWithTriangle(other) - return other:__hasIntersectionWithRay(self) -end - ----检查与圆的相交 ----@param other foundation.shape.Circle ----@return boolean, foundation.math.Vector2[] | nil -function Ray:__intersectToCircle(other) - return other:__intersectToRay(self) -end - ----检查是否与圆相交 ----@param other foundation.shape.Circle ----@return boolean -function Ray:__hasIntersectionWithCircle(other) - return other:__hasIntersectionWithRay(self) -end - ----检查与矩形的相交 ----@param other foundation.shape.Rectangle ----@return boolean, foundation.math.Vector2[] | nil -function Ray:__intersectToRectangle(other) - return other:__intersectToRay(self) -end - ----仅检查是否与矩形相交 ----@param other foundation.shape.Rectangle ----@return boolean -function Ray:__hasIntersectionWithRectangle(other) - return other:__hasIntersectionWithRay(self) -end - ----检查与扇形的相交 ----@param other foundation.shape.Sector ----@return boolean, foundation.math.Vector2[] | nil -function Ray:__intersectToSector(other) - return other:__intersectToRay(self) -end - ----仅检查是否与扇形相交 ----@param other foundation.shape.Sector ----@return boolean -function Ray:__hasIntersectionWithSector(other) - return other:__hasIntersectionWithRay(self) + return ShapeIntersector.hasIntersection(self, other) end ---计算点到射线的距离 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index 146421c0..ad395142 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -8,6 +8,7 @@ local math = math local Vector2 = require("foundation.math.Vector2") local Segment = require("foundation.shape.Segment") +local ShapeIntersector = require("foundation.shape.ShapeIntersector") ffi.cdef [[ typedef struct { @@ -205,389 +206,21 @@ end ---@param point foundation.math.Vector2 ---@return boolean function Rectangle:contains(point) - local p = point - self.center - local dir = self.direction - local perp = Vector2.create(-dir.y, dir.x) - local x = p.x * dir.x + p.y * dir.y - local y = p.x * perp.x + p.y * perp.y - local hw, hh = self.width / 2, self.height / 2 - return math.abs(x) <= hw and math.abs(y) <= hh + return ShapeIntersector.rectangleContainsPoint(self, point) end ---检查与其他形状的相交 ---@param other any ---@return boolean, foundation.math.Vector2[] | nil function Rectangle:intersects(other) - if other.__type == "foundation.shape.Segment" then - return self:__intersectToSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__intersectToTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__intersectToLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__intersectToRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__intersectToCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__intersectToRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__intersectToSector(other) - end - return false, nil + return ShapeIntersector.intersect(self, other) end ---仅检查是否与其他形状相交 ---@param other any ---@return boolean function Rectangle:hasIntersection(other) - if other.__type == "foundation.shape.Segment" then - return self:__hasIntersectionWithSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__hasIntersectionWithTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__hasIntersectionWithLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__hasIntersectionWithRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__hasIntersectionWithCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__hasIntersectionWithRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__hasIntersectionWithSector(other) - end - return false -end - ----检查与线段的相交 ----@param other foundation.shape.Segment ----@return boolean, foundation.math.Vector2[] | nil -function Rectangle:__intersectToSegment(other) - local points = {} - local edges = self:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = other:__intersectToSegment(edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if self:contains(other.point1) then - points[#points + 1] = other.point1:clone() - end - if other.point1 ~= other.point2 and self:contains(other.point2) then - points[#points + 1] = other.point2:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与线段相交 ----@param other foundation.shape.Segment ----@return boolean -function Rectangle:__hasIntersectionWithSegment(other) - local edges = self:getEdges() - for _, edge in ipairs(edges) do - if edge:hasIntersection(other) then - return true - end - end - return self:contains(other.point1) or self:contains(other.point2) -end - ----检查与直线的相交 ----@param other foundation.shape.Line ----@return boolean, foundation.math.Vector2[] | nil -function Rectangle:__intersectToLine(other) - local points = {} - local edges = self:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = other:__intersectToSegment(edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与直线相交 ----@param other foundation.shape.Line ----@return boolean -function Rectangle:__hasIntersectionWithLine(other) - local edges = self:getEdges() - for _, edge in ipairs(edges) do - if edge:hasIntersection(other) then - return true - end - end - return false -end - ----检查与射线的相交 ----@param other foundation.shape.Ray ----@return boolean, foundation.math.Vector2[] | nil -function Rectangle:__intersectToRay(other) - local points = {} - local edges = self:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = other:__intersectToSegment(edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if self:contains(other.point) then - points[#points + 1] = other.point:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与射线相交 ----@param other foundation.shape.Ray ----@return boolean -function Rectangle:__hasIntersectionWithRay(other) - local edges = self:getEdges() - for _, edge in ipairs(edges) do - if edge:hasIntersection(other) then - return true - end - end - return self:contains(other.point) -end - ----检查与三角形的相交 ----@param other foundation.shape.Triangle ----@return boolean, foundation.math.Vector2[] | nil -function Rectangle:__intersectToTriangle(other) - local points = {} - local edges = self:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = other:__intersectToSegment(edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if self:contains(other.point1) then - points[#points + 1] = other.point1:clone() - end - if self:contains(other.point2) then - points[#points + 1] = other.point2:clone() - end - if self:contains(other.point3) then - points[#points + 1] = other.point3:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与三角形相交 ----@param other foundation.shape.Triangle ----@return boolean -function Rectangle:__hasIntersectionWithTriangle(other) - local edges = self:getEdges() - for _, edge in ipairs(edges) do - if edge:hasIntersection(other) then - return true - end - end - return self:contains(other.point1) or self:contains(other.point2) or self:contains(other.point3) -end - ----检查与圆的相交 ----@param other foundation.shape.Circle ----@return boolean, foundation.math.Vector2[] | nil -function Rectangle:__intersectToCircle(other) - local points = {} - local edges = self:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = other:__intersectToSegment(edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - local vertices = self:getVertices() - for _, vertex in ipairs(vertices) do - if other:contains(vertex) then - points[#points + 1] = vertex:clone() - end - end - if self:contains(other.center) then - points[#points + 1] = other.center:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与圆相交 ----@param other foundation.shape.Circle ----@return boolean -function Rectangle:__hasIntersectionWithCircle(other) - if self:contains(other.center) then - return true - end - local edges = self:getEdges() - for _, edge in ipairs(edges) do - if edge:hasIntersection(other) then - return true - end - end - local vertices = self:getVertices() - for _, vertex in ipairs(vertices) do - if other:contains(vertex) then - return true - end - end - return false -end - ----检查与另一个矩形的相交 ----@param other foundation.shape.Rectangle ----@return boolean, foundation.math.Vector2[] | nil -function Rectangle:__intersectToRectangle(other) - local points = {} - local edges1 = self:getEdges() - local edges2 = other:getEdges() - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = edge1:__intersectToSegment(edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - local vertices1 = self:getVertices() - for _, vertex in ipairs(vertices1) do - if other:contains(vertex) then - points[#points + 1] = vertex:clone() - end - end - local vertices2 = other:getVertices() - for _, vertex in ipairs(vertices2) do - if self:contains(vertex) then - points[#points + 1] = vertex:clone() - end - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与另一个矩形相交 ----@param other foundation.shape.Rectangle ----@return boolean -function Rectangle:__hasIntersectionWithRectangle(other) - local edges1 = self:getEdges() - local edges2 = other:getEdges() - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if edge1:hasIntersection(edge2) then - return true - end - end - end - local vertices1 = self:getVertices() - for _, vertex in ipairs(vertices1) do - if other:contains(vertex) then - return true - end - end - local vertices2 = other:getVertices() - for _, vertex in ipairs(vertices2) do - if self:contains(vertex) then - return true - end - end - return false -end - ----检查与扇形的相交 ----@param other foundation.shape.Sector ----@return boolean, foundation.math.Vector2[] | nil -function Rectangle:__intersectToSector(other) - return other:__intersectToRay(self) -end - ----仅检查是否与扇形相交 ----@param other foundation.shape.Sector ----@return boolean -function Rectangle:__hasIntersectionWithSector(other) - return other:__hasIntersectionWithRay(self) + return ShapeIntersector.hasIntersection(self, other) end ---计算矩形的面积 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index 90a95c6b..a51290d0 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -9,6 +9,7 @@ local string = string local Vector2 = require("foundation.math.Vector2") local Segment = require("foundation.shape.Segment") local Circle = require("foundation.shape.Circle") +local ShapeIntersector = require("foundation.shape.ShapeIntersector") ffi.cdef [[ typedef struct { @@ -107,29 +108,7 @@ end ---@param point foundation.math.Vector2 ---@return boolean function Sector:contains(point) - local vec = point - self.center - local dist = vec:length() - if dist > self.radius then - return false - end - local dir = vec:normalized() - local startAngle = math.atan2(self.startDirection.y, self.startDirection.x) % (2 * math.pi) - local endAngle = math.atan2(self.endDirection.y, self.endDirection.x) % (2 * math.pi) - local pointAngle = math.atan2(dir.y, dir.x) % (2 * math.pi) - if startAngle < 0 then - startAngle = startAngle + 2 * math.pi - end - if endAngle < 0 then - endAngle = endAngle + 2 * math.pi - end - if pointAngle < 0 then - pointAngle = pointAngle + 2 * math.pi - end - if startAngle <= endAngle then - return pointAngle >= startAngle and pointAngle <= endAngle - else - return pointAngle >= startAngle or pointAngle <= endAngle - end + return ShapeIntersector.sectorContainsPoint(self, point) end ---移动扇形(修改当前扇形) @@ -167,8 +146,8 @@ end ---@param rad number 旋转弧度 ---@return foundation.shape.Sector function Sector:rotate(rad) - self.startDirection = self.startDirection:rotated(rad) - self.endDirection = self.endDirection:rotated(rad) + self.startDirection:rotate(rad) + self.endDirection:rotate(rad) return self end @@ -202,439 +181,14 @@ end ---@param other any ---@return boolean, foundation.math.Vector2[] | nil function Sector:intersects(other) - if other.__type == "foundation.shape.Segment" then - return self:__intersectToSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__intersectToTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__intersectToLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__intersectToRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__intersectToCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__intersectToRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__intersectToSector(other) - end - return false, nil + return ShapeIntersector.intersect(self, other) end ---仅检查是否与其他形状相交 ---@param other any ---@return boolean function Sector:hasIntersection(other) - if other.__type == "foundation.shape.Segment" then - return self:__hasIntersectionWithSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__hasIntersectionWithTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__hasIntersectionWithLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__hasIntersectionWithRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__hasIntersectionWithCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__hasIntersectionWithRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__hasIntersectionWithSector(other) - end - return false -end - ----检查与线段的相交 ----@param other foundation.shape.Segment ----@return boolean, foundation.math.Vector2[] | nil -function Sector:__intersectToSegment(other) - local points = {} - local circle = Circle.create(self.center, self.radius) - local success, circle_points = circle:__intersectToSegment(other) - if success then - for _, p in ipairs(circle_points) do - if self:contains(p) then - points[#points + 1] = p - end - end - end - if self:contains(other.point1) then - points[#points + 1] = other.point1:clone() - end - if other.point1 ~= other.point2 and self:contains(other.point2) then - points[#points + 1] = other.point2:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与线段相交 ----@param other foundation.shape.Segment ----@return boolean -function Sector:__hasIntersectionWithSegment(other) - local circle = Circle.create(self.center, self.radius) - if not circle:hasIntersection(other) then - return false - end - if self:contains(other.point1) or self:contains(other.point2) then - return true - end - local success, points = circle:__intersectToSegment(other) - if success then - for _, p in ipairs(points) do - if self:contains(p) then - return true - end - end - end - return false -end - ----检查与三角形的相交 ----@param other foundation.shape.Triangle ----@return boolean, foundation.math.Vector2[] | nil -function Sector:__intersectToTriangle(other) - local points = {} - local edges = other:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = self:__intersectToSegment(edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - local vertices = other:getVertices() - for _, vertex in ipairs(vertices) do - if self:contains(vertex) then - points[#points + 1] = vertex:clone() - end - end - if other:contains(self.center) then - points[#points + 1] = self.center:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与三角形相交 ----@param other foundation.shape.Triangle ----@return boolean -function Sector:__hasIntersectionWithTriangle(other) - if other:contains(self.center) then - return true - end - local edges = other:getEdges() - for _, edge in ipairs(edges) do - if self:__hasIntersectionWithSegment(edge) then - return true - end - end - local vertices = other:getVertices() - for _, vertex in ipairs(vertices) do - if self:contains(vertex) then - return true - end - end - return false -end - ----检查与直线的相交 ----@param other foundation.shape.Line ----@return boolean, foundation.math.Vector2[] | nil -function Sector:__intersectToLine(other) - local points = {} - local circle = Circle.create(self.center, self.radius) - local success, circle_points = circle:__intersectToLine(other) - if success then - for _, p in ipairs(circle_points) do - if self:contains(p) then - points[#points + 1] = p - end - end - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与直线相交 ----@param other foundation.shape.Line ----@return boolean -function Sector:__hasIntersectionWithLine(other) - local circle = Circle.create(self.center, self.radius) - if not circle:hasIntersection(other) then - return false - end - local success, points = circle:__intersectToLine(other) - if success then - for _, p in ipairs(points) do - if self:contains(p) then - return true - end - end - end - return false -end - ----检查与射线的相交 ----@param other foundation.shape.Ray ----@return boolean, foundation.math.Vector2[] | nil -function Sector:__intersectToRay(other) - local points = {} - local circle = Circle.create(self.center, self.radius) - local success, circle_points = circle:__intersectToRay(other) - if success then - for _, p in ipairs(circle_points) do - if self:contains(p) then - points[#points + 1] = p - end - end - end - if self:contains(other.point) then - points[#points + 1] = other.point:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与射线相交 ----@param other foundation.shape.Ray ----@return boolean -function Sector:__hasIntersectionWithRay(other) - if self:contains(other.point) then - return true - end - local circle = Circle.create(self.center, self.radius) - if not circle:hasIntersection(other) then - return false - end - local success, points = circle:__intersectToRay(other) - if success then - for _, p in ipairs(points) do - if self:contains(p) then - return true - end - end - end - return false -end - ----检查与圆的相交 ----@param other foundation.shape.Circle ----@return boolean, foundation.math.Vector2[] | nil -function Sector:__intersectToCircle(other) - local points = {} - local circle = Circle.create(self.center, self.radius) - local success, circle_points = circle:__intersectToCircle(other) - if success then - for _, p in ipairs(circle_points) do - if self:contains(p) then - points[#points + 1] = p - end - end - end - if self:contains(other.center) then - points[#points + 1] = other.center:clone() - end - if other:contains(self.center) then - points[#points + 1] = self.center:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与圆相交 ----@param other foundation.shape.Circle ----@return boolean -function Sector:__hasIntersectionWithCircle(other) - if self:contains(other.center) or other:contains(self.center) then - return true - end - local circle = Circle.create(self.center, self.radius) - if not circle:hasIntersection(other) then - return false - end - local success, points = circle:__intersectToCircle(other) - if success then - for _, p in ipairs(points) do - if self:contains(p) then - return true - end - end - end - return false -end - ----检查与矩形的相交 ----@param other foundation.shape.Rectangle ----@return boolean, foundation.math.Vector2[] | nil -function Sector:__intersectToRectangle(other) - local points = {} - local edges = other:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = self:__intersectToSegment(edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - local vertices = other:getVertices() - for _, vertex in ipairs(vertices) do - if self:contains(vertex) then - points[#points + 1] = vertex:clone() - end - end - if other:contains(self.center) then - points[#points + 1] = self.center:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与矩形相交 ----@param other foundation.shape.Rectangle ----@return boolean -function Sector:__hasIntersectionWithRectangle(other) - if other:contains(self.center) then - return true - end - local edges = other:getEdges() - for _, edge in ipairs(edges) do - if self:__hasIntersectionWithSegment(edge) then - return true - end - end - local vertices = other:getVertices() - for _, vertex in ipairs(vertices) do - if self:contains(vertex) then - return true - end - end - return false -end - ----检查与另一个扇形的相交 ----@param other foundation.shape.Sector ----@return boolean, foundation.math.Vector2[] | nil -function Sector:__intersectToSector(other) - local points = {} - local circle1 = Circle.create(self.center, self.radius) - local circle2 = Circle.create(other.center, other.radius) - local success, circle_points = circle1:__intersectToCircle(circle2) - if success then - for _, p in ipairs(circle_points) do - if self:contains(p) and other:contains(p) then - points[#points + 1] = p - end - end - end - if self:contains(other.center) then - points[#points + 1] = other.center:clone() - end - if other:contains(self.center) then - points[#points + 1] = self.center:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查是否与另一个扇形相交 ----@param other foundation.shape.Sector ----@return boolean -function Sector:__hasIntersectionWithSector(other) - if self:contains(other.center) or other:contains(self.center) then - return true - end - local circle1 = Circle.create(self.center, self.radius) - local circle2 = Circle.create(other.center, other.radius) - if not circle1:hasIntersection(circle2) then - return false - end - local success, points = circle1:__intersectToCircle(circle2) - if success then - for _, p in ipairs(points) do - if self:contains(p) and other:contains(p) then - return true - end - end - end - return false + return ShapeIntersector.hasIntersection(self, other) end ---计算点到扇形的最近点 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index 0080034d..dcc0501f 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -6,6 +6,7 @@ local string = string local math = math local Vector2 = require("foundation.math.Vector2") +local ShapeIntersector = require("foundation.shape.ShapeIntersector") ffi.cdef [[ typedef struct { @@ -232,293 +233,18 @@ function Segment:scaled(scale, center) ) end ----检查线段是否与其他形状相交 ----@param other any 其他的形状 +---检查与其他形状的相交 +---@param other any ---@return boolean, foundation.math.Vector2[] | nil function Segment:intersects(other) - if other.__type == "foundation.shape.Segment" then - return self:__intersectToSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__intersectToTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__intersectToLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__intersectToRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__intersectToCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__intersectToRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__intersectToSector(other) - end - return false, nil + return ShapeIntersector.intersect(self, other) end ---仅检查线段是否与其他形状相交,不返回相交点 ---@param other any 其他的形状 ---@return boolean function Segment:hasIntersection(other) - if other.__type == "foundation.shape.Segment" then - return self:__hasIntersectionWithSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__hasIntersectionWithTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__hasIntersectionWithLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__hasIntersectionWithRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__hasIntersectionWithCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__hasIntersectionWithRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__hasIntersectionWithSector(other) - end - return false -end - ----检查线段是否与另一个线段相交 ----@param other foundation.shape.Segment 要检查的线段 ----@return boolean, foundation.math.Vector2[] | nil -function Segment:__intersectToSegment(other) - local points = {} - local a = self.point1 - local b = self.point2 - local c = other.point1 - local d = other.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - local dir = self.point2 - self.point1 - local len = dir:length() - if len == 0 then - if other:containsPoint(self.point1) then - points[#points + 1] = self.point1:clone() - end - - if #points == 0 then - return false, nil - end - return true, points - end - dir = dir / len - local t1 = ((c.x - a.x) * dir.x + (c.y - a.y) * dir.y) / len - local t2 = ((d.x - a.x) * dir.x + (d.y - a.y) * dir.y) / len - if t1 > 1 or t2 < 0 or (t1 < 0 and t2 < 0) or (t1 > 1 and t2 > 1) then - return false, nil - end - local start_t = math.max(0, math.min(t1, t2)) - local end_t = math.min(1, math.max(t1, t2)) - if start_t <= end_t then - points[#points + 1] = self.point1 + dir * start_t * len - if math.abs(end_t - start_t) > 1e-10 then - points[#points + 1] = self.point1 + dir * end_t * len - end - end - - if #points == 0 then - return false, nil - end - return true, points - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if t >= 0 and t <= 1 and u >= 0 and u <= 1 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----仅检查线段是否与另一个线段相交 ----@param other foundation.shape.Segment 要检查的线段 ----@return boolean -function Segment:__hasIntersectionWithSegment(other) - local a = self.point1 - local b = self.point2 - local c = other.point1 - local d = other.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - local dir = self.point2 - self.point1 - local len = dir:length() - if len == 0 then - return other:containsPoint(self.point1) - end - dir = dir / len - local t1 = ((c.x - a.x) * dir.x + (c.y - a.y) * dir.y) / len - local t2 = ((d.x - a.x) * dir.x + (d.y - a.y) * dir.y) / len - if t1 > 1 or t2 < 0 or (t1 < 0 and t2 < 0) or (t1 > 1 and t2 > 1) then - return false - end - local start_t = math.max(0, math.min(t1, t2)) - local end_t = math.min(1, math.max(t1, t2)) - return start_t <= end_t - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and t <= 1 and u >= 0 and u <= 1 -end - ----检查线段是否与直线相交 ----@param other foundation.shape.Line 要检查的直线 ----@return boolean, foundation.math.Vector2[] | nil -function Segment:__intersectToLine(other) - local points = {} - local a = self.point1 - local b = self.point2 - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - if t >= 0 and t <= 1 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----仅检查线段是否与直线相交 ----@param other foundation.shape.Line 要检查的直线 ----@return boolean -function Segment:__hasIntersectionWithLine(other) - local a = self.point1 - local b = self.point2 - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - return t >= 0 and t <= 1 -end - ----检查线段是否与射线相交 ----@param other foundation.shape.Ray 要检查的射线 ----@return boolean, foundation.math.Vector2[] | nil -function Segment:__intersectToRay(other) - local points = {} - local a = self.point1 - local b = self.point2 - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if t >= 0 and t <= 1 and u >= 0 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----仅检查线段是否与射线相交 ----@param other foundation.shape.Ray 要检查的射线 ----@return boolean -function Segment:__hasIntersectionWithRay(other) - local a = self.point1 - local b = self.point2 - local c = other.point - local d = other.point + other.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and t <= 1 and u >= 0 -end - ----检查线段是否与三角形相交 ----@param other foundation.shape.Triangle 要检查的三角形 ----@return boolean, foundation.math.Vector2[] | nil -function Segment:__intersectToTriangle(other) - return other:__intersectToSegment(self) -end - ----仅检查线段是否与三角形相交 ----@param other foundation.shape.Triangle 要检查的三角形 ----@return boolean -function Segment:__hasIntersectionWithTriangle(other) - return other:__hasIntersectionWithSegment(self) -end - ----检查线段是否与圆相交 ----@param other foundation.shape.Circle 要检查的圆 ----@return boolean, foundation.math.Vector2[] | nil -function Segment:__intersectToCircle(other) - return other:__intersectToSegment(self) -end - ----仅检查线段是否与圆相交 ----@param other foundation.shape.Circle 要检查的圆 ----@return boolean -function Segment:__hasIntersectionWithCircle(other) - return other:__hasIntersectionWithSegment(self) -end - ----检查与矩形的相交 ----@param other foundation.shape.Rectangle ----@return boolean, foundation.math.Vector2[] | nil -function Segment:__intersectToRectangle(other) - return other:__intersectToSegment(self) -end - ----仅检查是否与矩形相交 ----@param other foundation.shape.Rectangle ----@return boolean -function Segment:__hasIntersectionWithRectangle(other) - return other:__hasIntersectionWithSegment(self) -end - ----检查与扇形的相交 ----@param other foundation.shape.Sector ----@return boolean, foundation.math.Vector2[] | nil -function Segment:__intersectToSector(other) - return other:__intersectToSegment(self) -end - ----仅检查是否与扇形相交 ----@param other foundation.shape.Sector ----@return boolean -function Segment:__hasIntersectionWithSector(other) - return other:__hasIntersectionWithSegment(self) + return ShapeIntersector.hasIntersection(self, other) end ---计算点到线段的最近点 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua new file mode 100644 index 00000000..a1306343 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua @@ -0,0 +1,1769 @@ +local Vector2 = require("foundation.math.Vector2") +local math = math +local tostring = tostring +local ipairs = ipairs + +---@class foundation.shape.ShapeIntersector +local ShapeIntersector = {} + +---检查点是否在圆内或圆上 +---@param circle foundation.shape.Circle 圆形 +---@param point foundation.math.Vector2 点 +---@return boolean +function ShapeIntersector.circleContainsPoint(circle, point) + return (point - circle.center):length() <= circle.radius +end + +---检查点是否在矩形内(包括边界) +---@param rectangle foundation.shape.Rectangle 矩形 +---@param point foundation.math.Vector2 点 +---@return boolean +function ShapeIntersector.rectangleContainsPoint(rectangle, point) + local p = point - rectangle.center + local dir = rectangle.direction + local perp = Vector2.create(-dir.y, dir.x) + local x = p.x * dir.x + p.y * dir.y + local y = p.x * perp.x + p.y * perp.y + local hw, hh = rectangle.width / 2, rectangle.height / 2 + return math.abs(x) <= hw and math.abs(y) <= hh +end + +---检查点是否在扇形内(包括边界) +---@param sector foundation.shape.Sector 扇形 +---@param point foundation.math.Vector2 点 +---@return boolean +function ShapeIntersector.sectorContainsPoint(sector, point) + local v = point - sector.center + local dist = v:length() + if dist > sector.radius then + return false + end + + if dist < 1e-10 then + return true + end + + local angle = math.atan2(v.y, v.x) + if angle < 0 then + angle = angle + 2 * math.pi + end + + local start = sector.startDirection + local end_dir = sector.endDirection + + if start > end_dir then + end_dir = end_dir + 2 * math.pi + end + + if angle < start then + angle = angle + 2 * math.pi + end + + return angle <= end_dir +end + +---检查点是否在三角形内 +---@param triangle foundation.shape.Triangle 三角形 +---@param point foundation.math.Vector2 点 +---@return boolean +function ShapeIntersector.triangleContainsPoint(triangle, point) + local v1 = triangle.point1 + local v2 = triangle.point2 + local v3 = triangle.point3 + local p = point + + local v3v1 = v3 - v1 + local v2v1 = v2 - v1 + local pv1 = p - v1 + + local dot00 = v3v1:dot(v3v1) + local dot01 = v3v1:dot(v2v1) + local dot02 = v3v1:dot(pv1) + local dot11 = v2v1:dot(v2v1) + local dot12 = v2v1:dot(pv1) + + local invDenom = 1 / (dot00 * dot11 - dot01 * dot01) + local u = (dot11 * dot02 - dot01 * dot12) * invDenom + local v = (dot00 * dot12 - dot01 * dot02) * invDenom + + return (u >= 0) and (v >= 0) and (u + v <= 1) +end + +---整理相交点,去除重复点 +---@param points foundation.math.Vector2[] 原始点列表 +---@return foundation.math.Vector2[] 去重后的点列表 +function ShapeIntersector.getUniquePoints(points) + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p + end + end + return unique_points +end + +---检查三角形与线段的相交 +---@param triangle foundation.shape.Triangle 三角形 +---@param segment foundation.shape.Segment 线段 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.triangleToSegment(triangle, segment) + local points = {} + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge, segment) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + if ShapeIntersector.triangleContainsPoint(triangle, segment.point1) then + points[#points + 1] = segment.point1:clone() + end + if segment.point1 ~= segment.point2 and ShapeIntersector.triangleContainsPoint(triangle, segment.point2) then + points[#points + 1] = segment.point2:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---只检查三角形与线段是否相交 +---@param triangle foundation.shape.Triangle 三角形 +---@param segment foundation.shape.Segment 线段 +---@return boolean +function ShapeIntersector.triangleHasIntersectionWithSegment(triangle, segment) + local edges = triangle:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then + return true + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, segment.point1) or + ShapeIntersector.triangleContainsPoint(triangle, segment.point2) then + return true + end + + return false +end + +---检查三角形与另一个三角形的相交 +---@param triangle1 foundation.shape.Triangle 第一个三角形 +---@param triangle2 foundation.shape.Triangle 第二个三角形 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.triangleToTriangle(triangle1, triangle2) + local points = {} + local edges1 = triangle1:getEdges() + local edges2 = triangle2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + end + + local vertices = triangle2:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle1, vertex) then + points[#points + 1] = vertex + end + end + + vertices = triangle1:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle2, vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---只检查三角形与另一个三角形是否相交 +---@param triangle1 foundation.shape.Triangle 第一个三角形 +---@param triangle2 foundation.shape.Triangle 第二个三角形 +---@return boolean +function ShapeIntersector.triangleHasIntersectionWithTriangle(triangle1, triangle2) + local edges1 = triangle1:getEdges() + local edges2 = triangle2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then + return true + end + end + end + + local vertices = triangle2:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle1, vertex) then + return true + end + end + + vertices = triangle1:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle2, vertex) then + return true + end + end + + return false +end + +---检查三角形与直线的相交 +---@param triangle foundation.shape.Triangle 三角形 +---@param line foundation.shape.Line 直线 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.triangleToLine(triangle, line) + local points = {} + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.lineToSegment(line, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---只检查三角形与直线是否相交 +---@param triangle foundation.shape.Triangle 三角形 +---@param line foundation.shape.Line 直线 +---@return boolean +function ShapeIntersector.triangleHasIntersectionWithLine(triangle, line) + local edges = triangle:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then + return true + end + end + + return false +end + +---检查三角形与射线的相交 +---@param triangle foundation.shape.Triangle 三角形 +---@param ray foundation.shape.Ray 射线 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.triangleToRay(triangle, ray) + local points = {} + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + if ShapeIntersector.triangleContainsPoint(triangle, ray.point) then + points[#points + 1] = ray.point:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---只检查三角形与射线是否相交 +---@param triangle foundation.shape.Triangle 三角形 +---@param ray foundation.shape.Ray 射线 +---@return boolean +function ShapeIntersector.triangleHasIntersectionWithRay(triangle, ray) + local edges = triangle:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then + return true + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, ray.point) then + return true + end + + return false +end + +---检查三角形与圆的相交 +---@param triangle foundation.shape.Triangle 三角形 +---@param circle foundation.shape.Circle 圆 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.triangleToCircle(triangle, circle) + local points = {} + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + points[#points + 1] = vertex + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, circle.center) then + points[#points + 1] = circle.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---只检查三角形与圆是否相交 +---@param triangle foundation.shape.Triangle 三角形 +---@param circle foundation.shape.Circle 圆 +---@return boolean +function ShapeIntersector.triangleHasIntersectionWithCircle(triangle, circle) + if ShapeIntersector.triangleContainsPoint(triangle, circle.center) then + return true + end + + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then + return true + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + return true + end + end + + return false +end + +---检查两条线段的相交 +---@param segment1 foundation.shape.Segment 第一条线段 +---@param segment2 foundation.shape.Segment 第二条线段 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.segmentToSegment(segment1, segment2) + local points = {} + local a = segment1.point1 + local b = segment1.point2 + local c = segment2.point1 + local d = segment2.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + -- 平行或共线情况 + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if t >= 0 and t <= 1 and u >= 0 and u <= 1 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + end + + if #points == 0 then + return false, nil + end + return true, points +end + +---检查两条线段是否相交 +---@param segment1 foundation.shape.Segment 第一条线段 +---@param segment2 foundation.shape.Segment 第二条线段 +---@return boolean +function ShapeIntersector.segmentHasIntersectionWithSegment(segment1, segment2) + local a = segment1.point1 + local b = segment1.point2 + local c = segment2.point1 + local d = segment2.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + -- 平行或共线情况 + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and t <= 1 and u >= 0 and u <= 1 +end + +---检查直线与线段的相交 +---@param line foundation.shape.Line 直线 +---@param segment foundation.shape.Segment 线段 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.lineToSegment(line, segment) + local points = {} + local a = line.point + local b = line.point + line.direction + local c = segment.point1 + local d = segment.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, points + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if u >= 0 and u <= 1 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + end + + if #points == 0 then + return false, nil + end + return true, points +end + +---检查直线是否与线段相交 +---@param line foundation.shape.Line 直线 +---@param segment foundation.shape.Segment 线段 +---@return boolean +function ShapeIntersector.lineHasIntersectionWithSegment(line, segment) + local a = line.point + local b = line.point + line.direction + local c = segment.point1 + local d = segment.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return u >= 0 and u <= 1 +end + +---检查射线与线段的相交 +---@param ray foundation.shape.Ray 射线 +---@param segment foundation.shape.Segment 线段 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.rayToSegment(ray, segment) + local points = {} + local a = ray.point + local b = ray.point + ray.direction + local c = segment.point1 + local d = segment.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if t >= 0 and u >= 0 and u <= 1 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + end + + if #points == 0 then + return false, nil + end + return true, points +end + +---检查射线是否与线段相交 +---@param ray foundation.shape.Ray 射线 +---@param segment foundation.shape.Segment 线段 +---@return boolean +function ShapeIntersector.rayHasIntersectionWithSegment(ray, segment) + local a = ray.point + local b = ray.point + ray.direction + local c = segment.point1 + local d = segment.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and u >= 0 and u <= 1 +end + +---检查圆与线段的相交 +---@param circle foundation.shape.Circle 圆 +---@param segment foundation.shape.Segment 线段 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.circleToSegment(circle, segment) + local points = {} + local closest = segment:closestPoint(circle.center) + + if (closest - circle.center):length() <= circle.radius then + local dir = segment.point2 - segment.point1 + local len = dir:length() + if len == 0 then + if (segment.point1 - circle.center):length() <= circle.radius then + points[#points + 1] = segment.point1:clone() + end + + if #points == 0 then + return false, nil + end + return true, points + end + dir = dir / len + local L = segment.point1 - circle.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - circle.radius * circle.radius + local discriminant = b * b - 4 * a * c + if discriminant >= 0 then + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + if t1 >= 0 and t1 <= len then + points[#points + 1] = segment.point1 + dir * t1 + end + if t2 >= 0 and t2 <= len and math.abs(t2 - t1) > 1e-10 then + points[#points + 1] = segment.point1 + dir * t2 + end + end + end + + if #points == 0 then + return false, nil + end + return true, points +end + +---检查圆是否与线段相交 +---@param circle foundation.shape.Circle 圆 +---@param segment foundation.shape.Segment 线段 +---@return boolean +function ShapeIntersector.circleHasIntersectionWithSegment(circle, segment) + local closest = segment:closestPoint(circle.center) + return (closest - circle.center):length() <= circle.radius +end + +---检查直线与直线的相交 +---@param line1 foundation.shape.Line 第一条直线 +---@param line2 foundation.shape.Line 第二条直线 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.lineToLine(line1, line2) + local points = {} + local a = line1.point + local b = line1.point + line1.direction + local c = line2.point + local d = line2.point + line2.direction + + local dir_cross = line1.direction:cross(line2.direction) + if math.abs(dir_cross) < 1e-10 then + local point_diff = line2.point - line1.point + if math.abs(point_diff:cross(line1.direction)) < 1e-10 then + points[#points + 1] = line1.point:clone() + points[#points + 1] = line1:getPoint(1) + return true, points + else + return false, nil + end + end + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + + return true, points +end + +---检查直线是否与直线相交 +---@param line1 foundation.shape.Line 第一条直线 +---@param line2 foundation.shape.Line 第二条直线 +---@return boolean +function ShapeIntersector.lineHasIntersectionWithLine(line1, line2) + local dir_cross = line1.direction:cross(line2.direction) + if math.abs(dir_cross) < 1e-10 then + local point_diff = line2.point - line1.point + return math.abs(point_diff:cross(line1.direction)) < 1e-10 + end + return true +end + +---检查直线与射线的相交 +---@param line foundation.shape.Line 直线 +---@param ray foundation.shape.Ray 射线 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.lineToRay(line, ray) + local points = {} + local a = line.point + local b = line.point + line.direction + local c = ray.point + local d = ray.point + ray.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if u >= 0 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + end + + if #points == 0 then + return false, nil + end + return true, points +end + +---检查直线是否与射线相交 +---@param line foundation.shape.Line 直线 +---@param ray foundation.shape.Ray 射线 +---@return boolean +function ShapeIntersector.lineHasIntersectionWithRay(line, ray) + local a = line.point + local b = line.point + line.direction + local c = ray.point + local d = ray.point + ray.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + return false + end + + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return u >= 0 +end + +---检查直线与圆的相交 +---@param line foundation.shape.Line 直线 +---@param circle foundation.shape.Circle 圆 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.lineToCircle(line, circle) + local points = {} + local dir = line.direction + local len = dir:length() + if len == 0 then + return false, nil + end + dir = dir / len + local L = line.point - circle.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - circle.radius * circle.radius + local discriminant = b * b - 4 * a * c + if discriminant >= 0 then + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + points[#points + 1] = line.point + dir * t1 + if math.abs(t2 - t1) > 1e-10 then + points[#points + 1] = line.point + dir * t2 + end + end + + if #points == 0 then + return false, nil + end + return true, points +end + +---检查直线是否与圆相交 +---@param line foundation.shape.Line 直线 +---@param circle foundation.shape.Circle 圆 +---@return boolean +function ShapeIntersector.lineHasIntersectionWithCircle(line, circle) + local dir = line.direction + local len = dir:length() + if len == 0 then + return false + end + dir = dir / len + local L = line.point - circle.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - circle.radius * circle.radius + local discriminant = b * b - 4 * a * c + return discriminant >= 0 +end + +---检查射线与射线的相交 +---@param ray1 foundation.shape.Ray 第一条射线 +---@param ray2 foundation.shape.Ray 第二条射线 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.rayToRay(ray1, ray2) + local points = {} + local a = ray1.point + local b = ray1.point + ray1.direction + local c = ray2.point + local d = ray2.point + ray2.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + local dir_cross = ray1.direction:cross(ray2.direction) + if math.abs(dir_cross) < 1e-10 then + local point_diff = ray2.point - ray1.point + local t = point_diff:dot(ray1.direction) + if t >= 0 then + points[#points + 1] = ray1.point + ray1.direction * t + end + end + + if #points == 0 then + return false, nil + end + return true, points + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if t >= 0 and u >= 0 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + end + + if #points == 0 then + return false, nil + end + return true, points +end + +---检查射线是否与射线相交 +---@param ray1 foundation.shape.Ray 第一条射线 +---@param ray2 foundation.shape.Ray 第二条射线 +---@return boolean +function ShapeIntersector.rayHasIntersectionWithRay(ray1, ray2) + local a = ray1.point + local b = ray1.point + ray1.direction + local c = ray2.point + local d = ray2.point + ray2.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + local dir_cross = ray1.direction:cross(ray2.direction) + if math.abs(dir_cross) < 1e-10 then + local point_diff = ray2.point - ray1.point + local t = point_diff:dot(ray1.direction) + return t >= 0 + end + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and u >= 0 +end + +---检查射线与圆的相交 +---@param ray foundation.shape.Ray 射线 +---@param circle foundation.shape.Circle 圆 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.rayToCircle(ray, circle) + local points = {} + local dir = ray.direction + local len = dir:length() + if len == 0 then + return false, nil + end + dir = dir / len + local L = ray.point - circle.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - circle.radius * circle.radius + local discriminant = b * b - 4 * a * c + if discriminant >= 0 then + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + if t1 >= 0 then + points[#points + 1] = ray.point + dir * t1 + end + if t2 >= 0 and math.abs(t2 - t1) > 1e-10 then + points[#points + 1] = ray.point + dir * t2 + end + end + + if #points == 0 then + return false, nil + end + return true, points +end + +---检查射线是否与圆相交 +---@param ray foundation.shape.Ray 射线 +---@param circle foundation.shape.Circle 圆 +---@return boolean +function ShapeIntersector.rayHasIntersectionWithCircle(ray, circle) + local dir = ray.direction + local len = dir:length() + if len == 0 then + return false + end + dir = dir / len + local L = ray.point - circle.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - circle.radius * circle.radius + local discriminant = b * b - 4 * a * c + + if discriminant < 0 then + return false + end + + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + + return t1 >= 0 or t2 >= 0 +end + +---检查圆与圆的相交 +---@param circle1 foundation.shape.Circle 第一个圆 +---@param circle2 foundation.shape.Circle 第二个圆 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.circleToCircle(circle1, circle2) + local points = {} + local d = (circle1.center - circle2.center):length() + if d <= circle1.radius + circle2.radius and d >= math.abs(circle1.radius - circle2.radius) then + local a = (circle1.radius * circle1.radius - circle2.radius * circle2.radius + d * d) / (2 * d) + local h = math.sqrt(circle1.radius * circle1.radius - a * a) + local p2 = circle1.center + (circle2.center - circle1.center) * (a / d) + local perp = Vector2.create(-(circle2.center.y - circle1.center.y), circle2.center.x - circle1.center.x):normalized() * h + points[#points + 1] = p2 + perp + if math.abs(h) > 1e-10 then + points[#points + 1] = p2 - perp + end + end + + if #points == 0 then + return false, nil + end + return true, points +end + +---检查圆是否与圆相交 +---@param circle1 foundation.shape.Circle 第一个圆 +---@param circle2 foundation.shape.Circle 第二个圆 +---@return boolean +function ShapeIntersector.circleHasIntersectionWithCircle(circle1, circle2) + local d = (circle1.center - circle2.center):length() + return d <= circle1.radius + circle2.radius and d >= math.abs(circle1.radius - circle2.radius) +end + +---检查矩形与线段的相交 +---@param rectangle foundation.shape.Rectangle 矩形 +---@param segment foundation.shape.Segment 线段 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.rectangleToSegment(rectangle, segment) + local points = {} + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.segmentToSegment(segment, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + if rectangle:contains(segment.point1) then + points[#points + 1] = segment.point1:clone() + end + if segment.point1 ~= segment.point2 and rectangle:contains(segment.point2) then + points[#points + 1] = segment.point2:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查矩形是否与线段相交 +---@param rectangle foundation.shape.Rectangle 矩形 +---@param segment foundation.shape.Segment 线段 +---@return boolean +function ShapeIntersector.rectangleHasIntersectionWithSegment(rectangle, segment) + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then + return true + end + end + return rectangle:contains(segment.point1) or rectangle:contains(segment.point2) +end + +---检查矩形与三角形的相交 +---@param rectangle foundation.shape.Rectangle 矩形 +---@param triangle foundation.shape.Triangle 三角形 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.rectangleToTriangle(rectangle, triangle) + local points = {} + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.triangleToSegment(triangle, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if rectangle:contains(vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查矩形是否与三角形相交 +---@param rectangle foundation.shape.Rectangle 矩形 +---@param triangle foundation.shape.Triangle 三角形 +---@return boolean +function ShapeIntersector.rectangleHasIntersectionWithTriangle(rectangle, triangle) + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.triangleHasIntersectionWithSegment(triangle, edge) then + return true + end + end + return rectangle:contains(triangle.point1) or rectangle:contains(triangle.point2) or rectangle:contains(triangle.point3) +end + +---检查矩形与直线的相交 +---@param rectangle foundation.shape.Rectangle 矩形 +---@param line foundation.shape.Line 直线 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.rectangleToLine(rectangle, line) + local points = {} + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.lineToSegment(line, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查矩形是否与直线相交 +---@param rectangle foundation.shape.Rectangle 矩形 +---@param line foundation.shape.Line 直线 +---@return boolean +function ShapeIntersector.rectangleHasIntersectionWithLine(rectangle, line) + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then + return true + end + end + return rectangle:contains(line.point) +end + +---检查矩形与射线的相交 +---@param rectangle foundation.shape.Rectangle 矩形 +---@param ray foundation.shape.Ray 射线 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.rectangleToRay(rectangle, ray) + local points = {} + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + if rectangle:contains(ray.point) then + points[#points + 1] = ray.point:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查矩形是否与射线相交 +---@param rectangle foundation.shape.Rectangle 矩形 +---@param ray foundation.shape.Ray 射线 +---@return boolean +function ShapeIntersector.rectangleHasIntersectionWithRay(rectangle, ray) + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then + return true + end + end + return rectangle:contains(ray.point) +end + +---检查矩形与圆的相交 +---@param rectangle foundation.shape.Rectangle 矩形 +---@param circle foundation.shape.Circle 圆 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.rectangleToCircle(rectangle, circle) + local points = {} + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + points[#points + 1] = vertex + end + end + + if rectangle:contains(circle.center) then + points[#points + 1] = circle.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查矩形是否与圆相交 +---@param rectangle foundation.shape.Rectangle 矩形 +---@param circle foundation.shape.Circle 圆 +---@return boolean +function ShapeIntersector.rectangleHasIntersectionWithCircle(rectangle, circle) + if rectangle:contains(circle.center) then + return true + end + + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then + return true + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + return true + end + end + + return false +end + +---检查矩形与矩形的相交 +---@param rectangle1 foundation.shape.Rectangle 第一个矩形 +---@param rectangle2 foundation.shape.Rectangle 第二个矩形 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.rectangleToRectangle(rectangle1, rectangle2) + local points = {} + local edges1 = rectangle1:getEdges() + local edges2 = rectangle2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + end + + local vertices1 = rectangle1:getVertices() + for _, vertex in ipairs(vertices1) do + if rectangle2:contains(vertex) then + points[#points + 1] = vertex + end + end + + local vertices2 = rectangle2:getVertices() + for _, vertex in ipairs(vertices2) do + if rectangle1:contains(vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查矩形是否与矩形相交 +---@param rectangle1 foundation.shape.Rectangle 第一个矩形 +---@param rectangle2 foundation.shape.Rectangle 第二个矩形 +---@return boolean +function ShapeIntersector.rectangleHasIntersectionWithRectangle(rectangle1, rectangle2) + local edges1 = rectangle1:getEdges() + local edges2 = rectangle2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then + return true + end + end + end + + local vertices1 = rectangle1:getVertices() + for _, vertex in ipairs(vertices1) do + if rectangle2:contains(vertex) then + return true + end + end + + local vertices2 = rectangle2:getVertices() + for _, vertex in ipairs(vertices2) do + if rectangle1:contains(vertex) then + return true + end + end + + return false +end + +---检查扇形与线段的相交 +---@param sector foundation.shape.Sector 扇形 +---@param segment foundation.shape.Segment 线段 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.sectorToSegment(sector, segment) + local points = {} + local success, circle_points = ShapeIntersector.circleToSegment({ center = sector.center, radius = sector.radius }, segment) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + points[#points + 1] = p + end + end + end + + if ShapeIntersector.sectorContainsPoint(sector, segment.point1) then + points[#points + 1] = segment.point1:clone() + end + if segment.point1 ~= segment.point2 and ShapeIntersector.sectorContainsPoint(sector, segment.point2) then + points[#points + 1] = segment.point2:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查扇形是否与线段相交 +---@param sector foundation.shape.Sector 扇形 +---@param segment foundation.shape.Segment 线段 +---@return boolean +function ShapeIntersector.sectorHasIntersectionWithSegment(sector, segment) + if ShapeIntersector.sectorContainsPoint(sector, segment.point1) or + ShapeIntersector.sectorContainsPoint(sector, segment.point2) then + return true + end + + local success, circle_points = ShapeIntersector.circleToSegment({ center = sector.center, radius = sector.radius }, segment) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + return true + end + end + end + + return false +end + +---检查扇形与直线的相交 +---@param sector foundation.shape.Sector 扇形 +---@param line foundation.shape.Line 直线 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.sectorToLine(sector, line) + local points = {} + local success, circle_points = ShapeIntersector.lineToCircle(line, { center = sector.center, radius = sector.radius }) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + points[#points + 1] = p + end + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查扇形是否与直线相交 +---@param sector foundation.shape.Sector 扇形 +---@param line foundation.shape.Line 直线 +---@return boolean +function ShapeIntersector.sectorHasIntersectionWithLine(sector, line) + local success, circle_points = ShapeIntersector.lineToCircle(line, { center = sector.center, radius = sector.radius }) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + return true + end + end + end + return false +end + +---检查扇形与射线的相交 +---@param sector foundation.shape.Sector 扇形 +---@param ray foundation.shape.Ray 射线 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.sectorToRay(sector, ray) + local points = {} + local success, circle_points = ShapeIntersector.rayToCircle(ray, { center = sector.center, radius = sector.radius }) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + points[#points + 1] = p + end + end + end + + if ShapeIntersector.sectorContainsPoint(sector, ray.point) then + points[#points + 1] = ray.point:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查扇形是否与射线相交 +---@param sector foundation.shape.Sector 扇形 +---@param ray foundation.shape.Ray 射线 +---@return boolean +function ShapeIntersector.sectorHasIntersectionWithRay(sector, ray) + if ShapeIntersector.sectorContainsPoint(sector, ray.point) then + return true + end + + local success, circle_points = ShapeIntersector.rayToCircle(ray, { center = sector.center, radius = sector.radius }) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + return true + end + end + end + return false +end + +---检查扇形与三角形的相交 +---@param sector foundation.shape.Sector 扇形 +---@param triangle foundation.shape.Triangle 三角形 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.sectorToTriangle(sector, triangle) + local points = {} + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + points[#points + 1] = vertex + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, sector.center) then + points[#points + 1] = sector.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查扇形是否与三角形相交 +---@param sector foundation.shape.Sector 扇形 +---@param triangle foundation.shape.Triangle 三角形 +---@return boolean +function ShapeIntersector.sectorHasIntersectionWithTriangle(sector, triangle) + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then + return true + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + return true + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, sector.center) then + return true + end + + return false +end + +---检查扇形与圆的相交 +---@param sector foundation.shape.Sector 扇形 +---@param circle foundation.shape.Circle 圆 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.sectorToCircle(sector, circle) + local points = {} + local success, circle_points = ShapeIntersector.circleToCircle({ center = sector.center, radius = sector.radius }, circle) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + points[#points + 1] = p + end + end + end + + if ShapeIntersector.sectorContainsPoint(sector, circle.center) then + points[#points + 1] = circle.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查扇形是否与圆相交 +---@param sector foundation.shape.Sector 扇形 +---@param circle foundation.shape.Circle 圆 +---@return boolean +function ShapeIntersector.sectorHasIntersectionWithCircle(sector, circle) + if ShapeIntersector.sectorContainsPoint(sector, circle.center) then + return true + end + + local success, circle_points = ShapeIntersector.circleToCircle({ center = sector.center, radius = sector.radius }, circle) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + return true + end + end + end + + return false +end + +---检查扇形与矩形的相交 +---@param sector foundation.shape.Sector 扇形 +---@param rectangle foundation.shape.Rectangle 矩形 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.sectorToRectangle(sector, rectangle) + local points = {} + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + points[#points + 1] = vertex + end + end + + if ShapeIntersector.rectangleContainsPoint(rectangle, sector.center) then + points[#points + 1] = sector.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查扇形是否与矩形相交 +---@param sector foundation.shape.Sector 扇形 +---@param rectangle foundation.shape.Rectangle 矩形 +---@return boolean +function ShapeIntersector.sectorHasIntersectionWithRectangle(sector, rectangle) + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then + return true + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + return true + end + end + + if ShapeIntersector.rectangleContainsPoint(rectangle, sector.center) then + return true + end + + return false +end + +---检查扇形与扇形的相交 +---@param sector1 foundation.shape.Sector 第一个扇形 +---@param sector2 foundation.shape.Sector 第二个扇形 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.sectorToSector(sector1, sector2) + local points = {} + local success, circle_points = ShapeIntersector.circleToCircle( + { center = sector1.center, radius = sector1.radius }, + { center = sector2.center, radius = sector2.radius } + ) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then + points[#points + 1] = p + end + end + end + + if ShapeIntersector.sectorContainsPoint(sector1, sector2.center) then + points[#points + 1] = sector2.center:clone() + end + if ShapeIntersector.sectorContainsPoint(sector2, sector1.center) then + points[#points + 1] = sector1.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查扇形是否与扇形相交 +---@param sector1 foundation.shape.Sector 第一个扇形 +---@param sector2 foundation.shape.Sector 第二个扇形 +---@return boolean +function ShapeIntersector.sectorHasIntersectionWithSector(sector1, sector2) + if ShapeIntersector.sectorContainsPoint(sector1, sector2.center) or + ShapeIntersector.sectorContainsPoint(sector2, sector1.center) then + return true + end + + local success, circle_points = ShapeIntersector.circleToCircle( + { center = sector1.center, radius = sector1.radius }, + { center = sector2.center, radius = sector2.radius } + ) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then + return true + end + end + end + + return false +end + +---@type table> +local intersectionMap = { + ["foundation.shape.Sector"] = { + ["foundation.shape.Segment"] = ShapeIntersector.sectorToSegment, + ["foundation.shape.Line"] = ShapeIntersector.sectorToLine, + ["foundation.shape.Ray"] = ShapeIntersector.sectorToRay, + ["foundation.shape.Triangle"] = ShapeIntersector.sectorToTriangle, + ["foundation.shape.Circle"] = ShapeIntersector.sectorToCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.sectorToRectangle, + ["foundation.shape.Sector"] = ShapeIntersector.sectorToSector, + }, + ["foundation.shape.Triangle"] = { + ["foundation.shape.Segment"] = ShapeIntersector.triangleToSegment, + ["foundation.shape.Triangle"] = ShapeIntersector.triangleToTriangle, + ["foundation.shape.Line"] = ShapeIntersector.triangleToLine, + ["foundation.shape.Ray"] = ShapeIntersector.triangleToRay, + ["foundation.shape.Circle"] = ShapeIntersector.triangleToCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleToTriangle, + }, + ["foundation.shape.Rectangle"] = { + ["foundation.shape.Triangle"] = ShapeIntersector.rectangleToTriangle, + ["foundation.shape.Segment"] = ShapeIntersector.rectangleToSegment, + ["foundation.shape.Line"] = ShapeIntersector.rectangleToLine, + ["foundation.shape.Ray"] = ShapeIntersector.rectangleToRay, + ["foundation.shape.Circle"] = ShapeIntersector.rectangleToCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleToRectangle, + }, + ["foundation.shape.Line"] = { + ["foundation.shape.Segment"] = ShapeIntersector.lineToSegment, + ["foundation.shape.Line"] = ShapeIntersector.lineToLine, + ["foundation.shape.Ray"] = ShapeIntersector.lineToRay, + ["foundation.shape.Circle"] = ShapeIntersector.lineToCircle, + }, + ["foundation.shape.Ray"] = { + ["foundation.shape.Segment"] = ShapeIntersector.rayToSegment, + ["foundation.shape.Ray"] = ShapeIntersector.rayToRay, + ["foundation.shape.Circle"] = ShapeIntersector.rayToCircle, + }, + ["foundation.shape.Circle"] = { + ["foundation.shape.Segment"] = ShapeIntersector.circleToSegment, + ["foundation.shape.Circle"] = ShapeIntersector.circleToCircle, + }, + ["foundation.shape.Segment"] = { + ["foundation.shape.Segment"] = ShapeIntersector.segmentToSegment, + }, +} + +---检查与其他形状的相交 +---@param shape1 any 第一个形状 +---@param shape2 any 第二个形状 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.intersect(shape1, shape2) + local type1 = shape1.__type + local type2 = shape2.__type + + local intersectionFunc = intersectionMap[type1] and intersectionMap[type1][type2] + if intersectionFunc then + return intersectionFunc(shape1, shape2) + end + + intersectionFunc = intersectionMap[type2] and intersectionMap[type2][type1] + if intersectionFunc then + return intersectionFunc(shape2, shape1) + end + + return false, nil +end + +---@type table> +local hasIntersectionMap = { + ["foundation.shape.Sector"] = { + ["foundation.shape.Segment"] = ShapeIntersector.sectorHasIntersectionWithSegment, + ["foundation.shape.Line"] = ShapeIntersector.sectorHasIntersectionWithLine, + ["foundation.shape.Ray"] = ShapeIntersector.sectorHasIntersectionWithRay, + ["foundation.shape.Triangle"] = ShapeIntersector.sectorHasIntersectionWithTriangle, + ["foundation.shape.Circle"] = ShapeIntersector.sectorHasIntersectionWithCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.sectorHasIntersectionWithRectangle, + ["foundation.shape.Sector"] = ShapeIntersector.sectorHasIntersectionWithSector, + }, + ["foundation.shape.Triangle"] = { + ["foundation.shape.Segment"] = ShapeIntersector.triangleHasIntersectionWithSegment, + ["foundation.shape.Triangle"] = ShapeIntersector.triangleHasIntersectionWithTriangle, + ["foundation.shape.Line"] = ShapeIntersector.triangleHasIntersectionWithLine, + ["foundation.shape.Ray"] = ShapeIntersector.triangleHasIntersectionWithRay, + ["foundation.shape.Circle"] = ShapeIntersector.triangleHasIntersectionWithCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleHasIntersectionWithTriangle, + }, + ["foundation.shape.Rectangle"] = { + ["foundation.shape.Triangle"] = ShapeIntersector.rectangleHasIntersectionWithTriangle, + ["foundation.shape.Segment"] = ShapeIntersector.rectangleHasIntersectionWithSegment, + ["foundation.shape.Line"] = ShapeIntersector.rectangleHasIntersectionWithLine, + ["foundation.shape.Ray"] = ShapeIntersector.rectangleHasIntersectionWithRay, + ["foundation.shape.Circle"] = ShapeIntersector.rectangleHasIntersectionWithCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleHasIntersectionWithRectangle, + }, + ["foundation.shape.Line"] = { + ["foundation.shape.Segment"] = ShapeIntersector.lineHasIntersectionWithSegment, + ["foundation.shape.Line"] = ShapeIntersector.lineHasIntersectionWithLine, + ["foundation.shape.Ray"] = ShapeIntersector.lineHasIntersectionWithRay, + ["foundation.shape.Circle"] = ShapeIntersector.lineHasIntersectionWithCircle, + }, + ["foundation.shape.Ray"] = { + ["foundation.shape.Segment"] = ShapeIntersector.rayHasIntersectionWithSegment, + ["foundation.shape.Ray"] = ShapeIntersector.rayHasIntersectionWithRay, + ["foundation.shape.Circle"] = ShapeIntersector.rayHasIntersectionWithCircle, + }, + ["foundation.shape.Circle"] = { + ["foundation.shape.Segment"] = ShapeIntersector.circleHasIntersectionWithSegment, + ["foundation.shape.Circle"] = ShapeIntersector.circleHasIntersectionWithCircle, + }, + ["foundation.shape.Segment"] = { + ["foundation.shape.Segment"] = ShapeIntersector.segmentHasIntersectionWithSegment, + }, +} + +---只检查是否与其他形状相交 +---@param shape1 any 第一个形状 +---@param shape2 any 第二个形状 +---@return boolean +function ShapeIntersector.hasIntersection(shape1, shape2) + local type1 = shape1.__type + local type2 = shape2.__type + + local intersectionFunc = hasIntersectionMap[type1] and hasIntersectionMap[type1][type2] + if intersectionFunc then + return intersectionFunc(shape1, shape2) + end + + intersectionFunc = hasIntersectionMap[type2] and hasIntersectionMap[type2][type1] + if intersectionFunc then + return intersectionFunc(shape2, shape1) + end + + return false +end + +function ShapeIntersector.checkMissingIntersection() + local keys = {} + for k, _ in pairs(intersectionMap) do + keys[#keys + 1] = k + end + + local missing = {} + for i = 1, #keys do + local key1 = keys[i] + for j = i, #keys do + local key2 = keys[j] + if not intersectionMap[key1][key2] and not intersectionMap[key2][key1] then + missing[#missing + 1] = { key1, key2 } + end + end + end + + if #missing > 0 then + print("Missing intersections:") + for _, pair in ipairs(missing) do + print(pair[1], pair[2]) + end + else + print("No missing intersections found.") + end + + local hasIntersectionKeys = {} + for k, _ in pairs(hasIntersectionMap) do + hasIntersectionKeys[#hasIntersectionKeys + 1] = k + end + + local missingHasIntersection = {} + for i = 1, #hasIntersectionKeys do + local key1 = hasIntersectionKeys[i] + for j = i, #hasIntersectionKeys do + local key2 = hasIntersectionKeys[j] + if not hasIntersectionMap[key1][key2] and not hasIntersectionMap[key2][key1] then + missingHasIntersection[#missingHasIntersection + 1] = { key1, key2 } + end + end + end + + if #missingHasIntersection > 0 then + print("Missing hasIntersection:") + for _, pair in ipairs(missingHasIntersection) do + print(pair[1], pair[2]) + end + else + print("No missing hasIntersection found.") + end +end + +ShapeIntersector.checkMissingIntersection() + +return ShapeIntersector \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 4bee08c3..61f9dfac 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -8,6 +8,7 @@ local math = math local Vector2 = require("foundation.math.Vector2") local Segment = require("foundation.shape.Segment") +local ShapeIntersector = require("foundation.shape.ShapeIntersector") ffi.cdef [[ typedef struct { @@ -251,69 +252,22 @@ function Triangle:scaled(scale, center) ) end ----判断点是否在三角形内(包括边界) ----@param point foundation.math.Vector2 要检查的点 ----@return boolean 如果点在三角形内(包括边界)返回true,否则返回false function Triangle:contains(point) - local v3v1 = self.point3 - self.point1 - local v2v1 = self.point2 - self.point1 - local pv1 = point - self.point1 - - local dot00 = v3v1:dot(v3v1) - local dot01 = v3v1:dot(v2v1) - local dot02 = v3v1:dot(pv1) - local dot11 = v2v1:dot(v2v1) - local dot12 = v2v1:dot(pv1) - - local invDenom = 1 / (dot00 * dot11 - dot01 * dot01) - local u = (dot11 * dot02 - dot01 * dot12) * invDenom - local v = (dot00 * dot12 - dot01 * dot02) * invDenom - - return (u >= 0) and (v >= 0) and (u + v <= 1) + return ShapeIntersector.triangleContainsPoint(self, point) end ---检查三角形是否与其他形状相交 ---@param other any 其他的形状 ---@return boolean, foundation.math.Vector2[] | nil function Triangle:intersects(other) - if other.__type == "foundation.shape.Segment" then - return self:__intersectToSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__intersectToTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__intersectToLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__intersectToRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__intersectToCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__intersectToRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__intersectToSector(other) - end - return false, nil + return ShapeIntersector.intersect(self, other) end ---仅检查三角形是否与其他形状相交,不返回相交点 ---@param other any 其他的形状 ---@return boolean function Triangle:hasIntersection(other) - if other.__type == "foundation.shape.Segment" then - return self:__hasIntersectionWithSegment(other) - elseif other.__type == "foundation.shape.Triangle" then - return self:__hasIntersectionWithTriangle(other) - elseif other.__type == "foundation.shape.Line" then - return self:__hasIntersectionWithLine(other) - elseif other.__type == "foundation.shape.Ray" then - return self:__hasIntersectionWithRay(other) - elseif other.__type == "foundation.shape.Circle" then - return self:__hasIntersectionWithCircle(other) - elseif other.__type == "foundation.shape.Rectangle" then - return self:__hasIntersectionWithRectangle(other) - elseif other.__type == "foundation.shape.Sector" then - return self:__hasIntersectionWithSector(other) - end - return false + return ShapeIntersector.hasIntersection(self, other) end ---获取三角形的顶点 @@ -336,334 +290,6 @@ function Triangle:getEdges() } end ----检查三角形是否与线段相交 ----@param other foundation.shape.Segment 要检查的线段 ----@return boolean, foundation.math.Vector2[] | nil -function Triangle:__intersectToSegment(other) - local points = {} - local edges = self:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = edge:__intersectToSegment(other) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if self:contains(other.point1) then - points[#points + 1] = other.point1:clone() - end - if other.point1 ~= other.point2 and self:contains(other.point2) then - points[#points + 1] = other.point2:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----检查三角形是否与另一个三角形相交 ----@param other foundation.shape.Triangle 要检查的三角形 ----@return boolean, foundation.math.Vector2[] | nil -function Triangle:__intersectToTriangle(other) - local points = {} - local edges1 = self:getEdges() - local edges2 = other:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = edge1:__intersectToSegment(edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - - local vertices = other:getVertices() - for _, vertex in ipairs(vertices) do - if self:contains(vertex) then - points[#points + 1] = vertex:clone() - end - end - - vertices = self:getVertices() - for _, vertex in ipairs(vertices) do - if other:contains(vertex) then - points[#points + 1] = vertex:clone() - end - end - - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----检查三角形是否与直线相交 ----@param other foundation.shape.Line 要检查的直线 ----@return boolean, foundation.math.Vector2[] | nil -function Triangle:__intersectToLine(other) - local points = {} - local edges = self:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = edge:__intersectToLine(other) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----检查三角形是否与射线相交 ----@param other foundation.shape.Ray 要检查的射线 ----@return boolean, foundation.math.Vector2[] | nil -function Triangle:__intersectToRay(other) - local points = {} - local edges = self:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = edge:__intersectToRay(other) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if self:contains(other.point) then - points[#points + 1] = other.point:clone() - end - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----检查三角形是否与圆相交 ----@param other foundation.shape.Circle 要检查的圆 ----@return boolean, foundation.math.Vector2[] | nil -function Triangle:__intersectToCircle(other) - local points = {} - local edges = self:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = edge:__intersectToCircle(other) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = self:getVertices() - for _, vertex in ipairs(vertices) do - if other:contains(vertex) then - points[#points + 1] = vertex:clone() - end - end - - if self:contains(other.center) then - points[#points + 1] = other.center:clone() - end - - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查三角形是否与线段相交 ----@param other foundation.shape.Segment 要检查的线段 ----@return boolean -function Triangle:__hasIntersectionWithSegment(other) - local edges = self:getEdges() - - for _, edge in ipairs(edges) do - if edge:hasIntersection(other) then - return true - end - end - - if self:contains(other.point1) or self:contains(other.point2) then - return true - end - - return false -end - ----仅检查三角形是否与另一个三角形相交 ----@param other foundation.shape.Triangle 要检查的三角形 ----@return boolean -function Triangle:__hasIntersectionWithTriangle(other) - local edges1 = self:getEdges() - local edges2 = other:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if edge1:hasIntersection(edge2) then - return true - end - end - end - - local vertices = other:getVertices() - for _, vertex in ipairs(vertices) do - if self:contains(vertex) then - return true - end - end - - vertices = self:getVertices() - for _, vertex in ipairs(vertices) do - if other:contains(vertex) then - return true - end - end - - return false -end - ----仅检查三角形是否与直线相交 ----@param other foundation.shape.Line 要检查的直线 ----@return boolean -function Triangle:__hasIntersectionWithLine(other) - local edges = self:getEdges() - - for _, edge in ipairs(edges) do - if edge:hasIntersection(other) then - return true - end - end - - return false -end - ----仅检查三角形是否与射线相交 ----@param other foundation.shape.Ray 要检查的射线 ----@return boolean -function Triangle:__hasIntersectionWithRay(other) - local edges = self:getEdges() - - for _, edge in ipairs(edges) do - if edge:hasIntersection(other) then - return true - end - end - - if self:contains(other.point) then - return true - end - - return false -end - ----仅检查三角形是否与圆相交 ----@param other foundation.shape.Circle 要检查的圆 ----@return boolean -function Triangle:__hasIntersectionWithCircle(other) - if self:contains(other.center) then - return true - end - - local edges = self:getEdges() - for _, edge in ipairs(edges) do - if edge:hasIntersection(other) then - return true - end - end - - local vertices = self:getVertices() - for _, vertex in ipairs(vertices) do - if other:contains(vertex) then - return true - end - end - - return false -end - ----检查与矩形的相交 ----@param other foundation.shape.Rectangle ----@return boolean, foundation.math.Vector2[] | nil -function Triangle:__intersectToRectangle(other) - return other:__intersectToTriangle(self) -end - ----仅检查是否与矩形相交 ----@param other foundation.shape.Rectangle ----@return boolean -function Triangle:__hasIntersectionWithRectangle(other) - return other:__hasIntersectionWithTriangle(self) -end - ----检查与扇形的相交 ----@param other foundation.shape.Sector ----@return boolean, foundation.math.Vector2[] | nil -function Triangle:__intersectToSector(other) - return other:__intersectToTriangle(self) -end - ----仅检查是否与扇形相交 ----@param other foundation.shape.Sector ----@return boolean -function Triangle:__hasIntersectionWithSector(other) - return other:__hasIntersectionWithTriangle(self) -end - ---计算点到三角形的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 三角形上最近的点 From b31c6354997090c5cd9f6ddefc0596708b0e01ea Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 07:35:18 +0800 Subject: [PATCH 26/71] feat: add angle and rotation methods for Line, Ray, Rectangle, and Sector classes --- .../foundation/shape/Line.lua | 99 +++++++++++++++++++ .../thlib-scripts-v2/foundation/shape/Ray.lua | 99 +++++++++++++++++++ .../foundation/shape/Rectangle.lua | 14 +++ .../foundation/shape/Sector.lua | 14 +++ 4 files changed, 226 insertions(+) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index 644ff298..7ef61382 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -1,5 +1,6 @@ local ffi = require("ffi") +local type = type local math = math local tostring = tostring local string = string @@ -94,6 +95,104 @@ function Line:getPoint(length) return self.point + self.direction * length end +---获取直线的角度(弧度) +---@return number 直线的角度,单位为弧度 +function Line:angle() + return math.atan2(self.direction.y, self.direction.x) +end + +---获取直线的角度(度) +---@return number 直线的角度,单位为度 +function Line:degreeAngle() + return math.deg(self:angle()) +end + +---平移直线(更改当前直线) +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Line 平移后的直线(自身引用) +function Line:move(v) + local moveX, moveY + if type(v) == "number" then + moveX = v + moveY = v + else + moveX = v.x + moveY = v.y + end + self.point.x = self.point.x + moveX + self.point.y = self.point.y + moveY + return self +end + +---获取当前直线平移指定距离的副本 +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Line 移动后的直线副本 +function Line:moved(v) + local moveX, moveY + if type(v) == "number" then + moveX = v + moveY = v + else + moveX = v.x + moveY = v.y + end + return Line.create( + Vector2.create(self.point.x + moveX, self.point.y + moveY), + self.direction:clone() + ) +end + +---将当前直线旋转指定弧度(更改当前直线) +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Line 旋转后的直线(自身引用) +---@overload fun(self: foundation.shape.Line, rad: number): foundation.shape.Line 将当前直线绕定义的点旋转指定弧度(更改当前直线) +function Line:rotate(rad, center) + center = center or self.point + local cosRad = math.cos(rad) + local sinRad = math.sin(rad) + local v = self.direction + self.direction.x = v.x * cosRad - v.y * sinRad + self.direction.y = v.x * sinRad + v.y * cosRad + return self +end + +---将当前直线旋转指定角度(更改当前直线) +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Line 旋转后的直线(自身引用) +---@overload fun(self: foundation.shape.Line, angle: number): foundation.shape.Line 将当前直线绕定义的点旋转指定角度(更改当前直线) +function Line:degreeRotate(angle, center) + angle = math.rad(angle) + return self:rotate(angle, center) +end + +---获取当前直线旋转指定弧度的副本 +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Line 旋转后的直线副本 +---@overload fun(self: foundation.shape.Line, rad: number): foundation.shape.Line 获取当前直线绕定义的点旋转指定弧度的副本 +function Line:rotated(rad, center) + center = center or self.point + local cosRad = math.cos(rad) + local sinRad = math.sin(rad) + local v = self.direction + return Line.create( + Vector2.create(v.x * cosRad - v.y * sinRad + center.x, v.x * sinRad + v.y * cosRad + center.y), + Vector2.create(v.x * cosRad - v.y * sinRad, v.x * sinRad + v.y * cosRad) + ) +end + +---获取当前直线旋转指定角度的副本 +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Line 旋转后的直线副本 +------@overload fun(self: foundation.shape.Line, angle: number): foundation.shape.Line 获取当前直线绕定义的点旋转指定角度的副本 +function Line:degreeRotated(angle, center) + angle = math.rad(angle) + return self:rotated(angle, center) +end + ---检查与其他形状的相交 ---@param other any ---@return boolean, foundation.math.Vector2[] | nil diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index c100cdb8..a87ea5a3 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -1,5 +1,6 @@ local ffi = require("ffi") +local type = type local math = math local tostring = tostring local string = string @@ -80,6 +81,104 @@ function Ray:getPoint(length) return self.point + self.direction * length end +---获取射线的角度(弧度) +---@return number 射线的角度,单位为弧度 +function Ray:angle() + return math.atan2(self.direction.y, self.direction.x) +end + +---获取射线的角度(度) +---@return number 射线的角度,单位为度 +function Ray:degreeAngle() + return math.deg(self:angle()) +end + +---平移射线(更改当前射线) +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Ray 平移后的射线(自身引用) +function Ray:move(v) + local moveX, moveY + if type(v) == "number" then + moveX = v + moveY = v + else + moveX = v.x + moveY = v.y + end + self.point.x = self.point.x + moveX + self.point.y = self.point.y + moveY + return self +end + +---获取当前射线平移指定距离的副本 +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Ray 移动后的射线副本 +function Ray:moved(v) + local moveX, moveY + if type(v) == "number" then + moveX = v + moveY = v + else + moveX = v.x + moveY = v.y + end + return Ray.create( + Vector2.create(self.point.x + moveX, self.point.y + moveY), + self.direction:clone() + ) +end + +---将当前射线旋转指定弧度(更改当前射线) +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Ray 旋转后的射线(自身引用) +---@overload fun(self:foundation.shape.Ray, rad:number): foundation.shape.Ray 将射线绕起始点旋转指定弧度 +function Ray:rotate(rad, center) + center = center or self.point + local cosRad = math.cos(rad) + local sinRad = math.sin(rad) + local v = self.direction + self.direction.x = v.x * cosRad - v.y * sinRad + self.direction.y = v.x * sinRad + v.y * cosRad + return self +end + +---将当前射线旋转指定角度(更改当前射线) +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Ray 旋转后的射线(自身引用) +---@overload fun(self:foundation.shape.Ray, angle:number): foundation.shape.Ray 将射线绕起始点旋转指定角度 +function Ray:degreeRotate(angle, center) + angle = math.rad(angle) + return self:rotate(angle, center) +end + +---获取当前射线旋转指定弧度的副本 +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Ray 旋转后的射线副本 +---@overload fun(self:foundation.shape.Ray, rad:number): foundation.shape.Ray 将射线绕起始点旋转指定弧度 +function Ray:rotated(rad, center) + center = center or self.point + local cosRad = math.cos(rad) + local sinRad = math.sin(rad) + local v = self.direction + return Ray.create( + Vector2.create(v.x * cosRad - v.y * sinRad + center.x, v.x * sinRad + v.y * cosRad + center.y), + Vector2.create(v.x * cosRad - v.y * sinRad, v.x * sinRad + v.y * cosRad) + ) +end + +---获取当前射线旋转指定角度的副本 +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Ray 旋转后的射线副本 +---@overload fun(self:foundation.shape.Ray, angle:number): foundation.shape.Ray 将射线绕起始点旋转指定角度 +function Ray:degreeRotated(angle, center) + angle = math.rad(angle) + return self:rotated(angle, center) +end + ---检查与其他形状的相交 ---@param other any ---@return boolean, foundation.math.Vector2[] | nil diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index ad395142..822efc3b 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -164,6 +164,13 @@ function Rectangle:rotate(rad) return self end +---旋转矩形(更改当前矩形) +---@param angle number 旋转角度 +---@return foundation.shape.Rectangle 自身引用 +function Rectangle:degreeRotate(angle) + return self:rotate(math.rad(angle)) +end + ---获取旋转后的矩形副本 ---@param rad number 旋转弧度 ---@return foundation.shape.Rectangle @@ -174,6 +181,13 @@ function Rectangle:rotated(rad) return Rectangle.create(self.center, self.width, self.height, Vector2.create(x, y):normalized()) end +---获取旋转后的矩形副本 +---@param angle number 旋转角度 +---@return foundation.shape.Rectangle +function Rectangle:degreeRotated(angle) + return self:rotated(math.rad(angle)) +end + ---缩放矩形(更改当前矩形) ---@param scale number|foundation.math.Vector2 缩放倍数 ---@return foundation.shape.Rectangle 自身引用 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index a51290d0..2075f76f 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -151,6 +151,13 @@ function Sector:rotate(rad) return self end +---旋转扇形(修改当前扇形) +---@param angle number 旋转角度 +---@return foundation.shape.Sector +function Sector:degreeRotate(angle) + return self:rotate(math.rad(angle)) +end + ---获取旋转后的扇形副本 ---@param rad number 旋转弧度 ---@return foundation.shape.Sector @@ -162,6 +169,13 @@ function Sector:rotated(rad) ) end +---获取旋转后的扇形副本 +---@param angle number 旋转角度 +---@return foundation.shape.Sector +function Sector:degreeRotated(angle) + return self:rotated(math.rad(angle)) +end + ---缩放扇形(修改当前扇形) ---@param scale number ---@return foundation.shape.Sector From 731b0eed1253ed644251b18ac91a8c1410bda9af Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 07:39:02 +0800 Subject: [PATCH 27/71] refactor: reorganize Triangle intersection methods in ShapeIntersector --- .../foundation/shape/ShapeIntersector.lua | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua index a1306343..9b1f0952 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua @@ -1587,14 +1587,6 @@ local intersectionMap = { ["foundation.shape.Rectangle"] = ShapeIntersector.sectorToRectangle, ["foundation.shape.Sector"] = ShapeIntersector.sectorToSector, }, - ["foundation.shape.Triangle"] = { - ["foundation.shape.Segment"] = ShapeIntersector.triangleToSegment, - ["foundation.shape.Triangle"] = ShapeIntersector.triangleToTriangle, - ["foundation.shape.Line"] = ShapeIntersector.triangleToLine, - ["foundation.shape.Ray"] = ShapeIntersector.triangleToRay, - ["foundation.shape.Circle"] = ShapeIntersector.triangleToCircle, - ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleToTriangle, - }, ["foundation.shape.Rectangle"] = { ["foundation.shape.Triangle"] = ShapeIntersector.rectangleToTriangle, ["foundation.shape.Segment"] = ShapeIntersector.rectangleToSegment, @@ -1603,6 +1595,13 @@ local intersectionMap = { ["foundation.shape.Circle"] = ShapeIntersector.rectangleToCircle, ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleToRectangle, }, + ["foundation.shape.Triangle"] = { + ["foundation.shape.Segment"] = ShapeIntersector.triangleToSegment, + ["foundation.shape.Triangle"] = ShapeIntersector.triangleToTriangle, + ["foundation.shape.Line"] = ShapeIntersector.triangleToLine, + ["foundation.shape.Ray"] = ShapeIntersector.triangleToRay, + ["foundation.shape.Circle"] = ShapeIntersector.triangleToCircle, + }, ["foundation.shape.Line"] = { ["foundation.shape.Segment"] = ShapeIntersector.lineToSegment, ["foundation.shape.Line"] = ShapeIntersector.lineToLine, @@ -1655,14 +1654,6 @@ local hasIntersectionMap = { ["foundation.shape.Rectangle"] = ShapeIntersector.sectorHasIntersectionWithRectangle, ["foundation.shape.Sector"] = ShapeIntersector.sectorHasIntersectionWithSector, }, - ["foundation.shape.Triangle"] = { - ["foundation.shape.Segment"] = ShapeIntersector.triangleHasIntersectionWithSegment, - ["foundation.shape.Triangle"] = ShapeIntersector.triangleHasIntersectionWithTriangle, - ["foundation.shape.Line"] = ShapeIntersector.triangleHasIntersectionWithLine, - ["foundation.shape.Ray"] = ShapeIntersector.triangleHasIntersectionWithRay, - ["foundation.shape.Circle"] = ShapeIntersector.triangleHasIntersectionWithCircle, - ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleHasIntersectionWithTriangle, - }, ["foundation.shape.Rectangle"] = { ["foundation.shape.Triangle"] = ShapeIntersector.rectangleHasIntersectionWithTriangle, ["foundation.shape.Segment"] = ShapeIntersector.rectangleHasIntersectionWithSegment, @@ -1671,6 +1662,13 @@ local hasIntersectionMap = { ["foundation.shape.Circle"] = ShapeIntersector.rectangleHasIntersectionWithCircle, ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleHasIntersectionWithRectangle, }, + ["foundation.shape.Triangle"] = { + ["foundation.shape.Segment"] = ShapeIntersector.triangleHasIntersectionWithSegment, + ["foundation.shape.Triangle"] = ShapeIntersector.triangleHasIntersectionWithTriangle, + ["foundation.shape.Line"] = ShapeIntersector.triangleHasIntersectionWithLine, + ["foundation.shape.Ray"] = ShapeIntersector.triangleHasIntersectionWithRay, + ["foundation.shape.Circle"] = ShapeIntersector.triangleHasIntersectionWithCircle, + }, ["foundation.shape.Line"] = { ["foundation.shape.Segment"] = ShapeIntersector.lineHasIntersectionWithSegment, ["foundation.shape.Line"] = ShapeIntersector.lineHasIntersectionWithLine, From 13f0071ca7d29fa39b42c118556be113325a6fd0 Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 07:42:47 +0800 Subject: [PATCH 28/71] refactor: update sector direction calculations to use angle method --- .../thlib-scripts-v2/foundation/shape/ShapeIntersector.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua index 9b1f0952..4fe755fb 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua @@ -48,8 +48,8 @@ function ShapeIntersector.sectorContainsPoint(sector, point) angle = angle + 2 * math.pi end - local start = sector.startDirection - local end_dir = sector.endDirection + local start = sector.startDirection:angle() + local end_dir = sector.endDirection:angle() if start > end_dir then end_dir = end_dir + 2 * math.pi From d2a031c871265af1f10b48ee2c3a60f49a5c443d Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 08:09:55 +0800 Subject: [PATCH 29/71] refactor: consolidate sector direction and range properties in Sector class --- .../foundation/shape/Sector.lua | 125 +++++--- .../foundation/shape/ShapeIntersector.lua | 298 +++++++++++++++--- 2 files changed, 324 insertions(+), 99 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index 2075f76f..bba1af65 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -15,16 +15,16 @@ ffi.cdef [[ typedef struct { foundation_math_Vector2 center; double radius; - foundation_math_Vector2 startDirection; - foundation_math_Vector2 endDirection; + foundation_math_Vector2 direction; + double range; } foundation_shape_Sector; ]] ---@class foundation.shape.Sector ---@field center foundation.math.Vector2 扇形的中心点 ---@field radius number 扇形的半径 ----@field startDirection foundation.math.Vector2 起始方向(归一化向量) ----@field endDirection foundation.math.Vector2 终止方向(归一化向量) +---@field direction foundation.math.Vector2 方向(归一化向量) +---@field range number 扇形范围(-1到1,1或-1为整圆,0.5或-0.5为半圆) local Sector = {} Sector.__index = Sector Sector.__type = "foundation.shape.Sector" @@ -32,38 +32,36 @@ Sector.__type = "foundation.shape.Sector" ---创建一个新的扇形 ---@param center foundation.math.Vector2 中心点 ---@param radius number 半径 ----@param startDirection foundation.math.Vector2 起始方向(将归一化) ----@param endDirection foundation.math.Vector2 终止方向(将归一化) +---@param direction foundation.math.Vector2 方向(将归一化) +---@param range number 范围(-1到1) ---@return foundation.shape.Sector -function Sector.create(center, radius, startDirection, endDirection) - local startDir = startDirection:normalized() - local endDir = endDirection:normalized() +function Sector.create(center, radius, direction, range) + local dir = direction:normalized() + range = math.max(-1, math.min(1, range)) -- 限制范围在-1到1 ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return ffi.new("foundation_shape_Sector", center, radius, startDir, endDir) + return ffi.new("foundation_shape_Sector", center, radius, dir, range) end ---使用弧度创建扇形 ---@param center foundation.math.Vector2 中心点 ---@param radius number 半径 ----@param startRad number 起始弧度 ----@param endRad number 终止弧度 +---@param rad number 方向弧度 +---@param range number 范围(-1到1) ---@return foundation.shape.Sector -function Sector.createFromRad(center, radius, startRad, endRad) - local startDir = Vector2.createFromRad(startRad) - local endDir = Vector2.createFromRad(endRad) - return Sector.create(center, radius, startDir, endDir) +function Sector.createFromRad(center, radius, rad, range) + local dir = Vector2.createFromRad(rad) + return Sector.create(center, radius, dir, range) end ---使用角度创建扇形 ---@param center foundation.math.Vector2 中心点 ---@param radius number 半径 ----@param startAngle number 起始角度 ----@param endAngle number 终止角度 +---@param angle number 方向角度 +---@param range number 范围(-1到1) ---@return foundation.shape.Sector -function Sector.createFromAngle(center, radius, startAngle, endAngle) - local startRad = math.rad(startAngle) - local endRad = math.rad(endAngle) - return Sector.createFromRad(center, radius, startRad, endRad) +function Sector.createFromAngle(center, radius, angle, range) + local rad = math.rad(angle) + return Sector.createFromRad(center, radius, rad, range) end ---扇形相等比较 @@ -73,29 +71,34 @@ end function Sector.__eq(a, b) return a.center == b.center and math.abs(a.radius - b.radius) < 1e-10 and - a.startDirection == b.startDirection and - a.endDirection == b.endDirection + a.direction == b.direction and + math.abs(a.range - b.range) < 1e-10 end ---扇形的字符串表示 ---@param self foundation.shape.Sector ---@return string function Sector.__tostring(self) - return string.format("Sector(center=%s, radius=%f, startDirection=%s, endDirection=%s)", - tostring(self.center), self.radius, tostring(self.startDirection), tostring(self.endDirection)) + return string.format("Sector(center=%s, radius=%f, direction=%s, range=%f)", + tostring(self.center), self.radius, tostring(self.direction), self.range) +end + +---转为圆形 +---@return foundation.shape.Circle +function Sector:toCircle() + return Circle.create(self.center, self.radius) end ---计算扇形角度(弧度) ---@return number function Sector:getAngle() - local dot = self.startDirection:dot(self.endDirection) - dot = math.max(-1, math.min(1, dot)) -- 防止浮点误差 - local angle = math.acos(dot) - local cross = self.startDirection.x * self.endDirection.y - self.startDirection.y * self.endDirection.x - if cross < 0 then - angle = 2 * math.pi - angle - end - return angle + return math.abs(self.range) * 2 * math.pi +end + +---计算扇形角度(角度) +---@return number +function Sector:getDegreeAngle() + return math.deg(self:getAngle()) end ---计算扇形的面积 @@ -108,7 +111,21 @@ end ---@param point foundation.math.Vector2 ---@return boolean function Sector:contains(point) - return ShapeIntersector.sectorContainsPoint(self, point) + if math.abs(self.range) >= 1 then + return Circle.create(self.center, self.radius):contains(point) + end + local vec = point - self.center + if vec:length() > self.radius then + return false + end + local dir = vec:normalized() + local angle = math.acos(self.direction:dot(dir)) + local maxAngle = math.abs(self.range) * math.pi + local cross = self.direction.x * dir.y - self.direction.y * dir.x + if self.range < 0 then + cross = -cross + end + return angle <= maxAngle and cross >= 0 end ---移动扇形(修改当前扇形) @@ -138,7 +155,7 @@ function Sector:moved(v) end return Sector.create( Vector2.create(self.center.x + moveX, self.center.y + moveY), - self.radius, self.startDirection, self.endDirection + self.radius, self.direction, self.range ) end @@ -146,8 +163,7 @@ end ---@param rad number 旋转弧度 ---@return foundation.shape.Sector function Sector:rotate(rad) - self.startDirection:rotate(rad) - self.endDirection:rotate(rad) + self.direction:rotate(rad) return self end @@ -164,8 +180,8 @@ end function Sector:rotated(rad) return Sector.create( self.center, self.radius, - self.startDirection:rotated(rad), - self.endDirection:rotated(rad) + self.direction:rotated(rad), + self.range ) end @@ -188,7 +204,7 @@ end ---@param scale number ---@return foundation.shape.Sector function Sector:scaled(scale) - return Sector.create(self.center, self.radius * scale, self.startDirection, self.endDirection) + return Sector.create(self.center, self.radius * scale, self.direction, self.range) end ---检查与其他形状的相交 @@ -209,6 +225,9 @@ end ---@param point foundation.math.Vector2 ---@return foundation.math.Vector2 function Sector:closestPoint(point) + if math.abs(self.range) >= 1 then + return Circle.create(self.center, self.radius):closestPoint(point) + end if self:contains(point) then return point:clone() end @@ -217,8 +236,10 @@ function Sector:closestPoint(point) if self:contains(circle_closest) then return circle_closest end - local start_point = self.center + self.startDirection * self.radius - local end_point = self.center + self.endDirection * self.radius + local startDir = self.direction + local endDir = self.direction:rotated(self.range * 2 * math.pi) + local start_point = self.center + startDir * self.radius + local end_point = self.center + endDir * self.radius local start_segment = Segment.create(self.center, start_point) local end_segment = Segment.create(self.center, end_point) local candidates = { @@ -260,15 +281,20 @@ end ---@return boolean function Sector:containsPoint(point, tolerance) tolerance = tolerance or 1e-10 + if math.abs(self.range) >= 1 then + return Circle.create(self.center, self.radius):containsPoint(point, tolerance) + end local circle = Circle.create(self.center, self.radius) if not circle:containsPoint(point, tolerance) then return false end local vec = point - self.center local dir = vec:normalized() - local startAngle = math.atan2(self.startDirection.y, self.startDirection.x) % (2 * math.pi) - local endAngle = math.atan2(self.endDirection.y, self.endDirection.x) % (2 * math.pi) + local centerAngle = math.atan2(self.direction.y, self.direction.x) % (2 * math.pi) local pointAngle = math.atan2(dir.y, dir.x) % (2 * math.pi) + local maxAngle = math.abs(self.range) * math.pi + local startAngle = centerAngle + local endAngle = (centerAngle + self.range * 2 * math.pi) % (2 * math.pi) if startAngle < 0 then startAngle = startAngle + 2 * math.pi end @@ -278,12 +304,11 @@ function Sector:containsPoint(point, tolerance) if pointAngle < 0 then pointAngle = pointAngle + 2 * math.pi end - if startAngle <= endAngle then - return math.abs(pointAngle - startAngle) < tolerance or math.abs(pointAngle - endAngle) < tolerance - else - return math.abs(pointAngle - startAngle) < tolerance or math.abs(pointAngle - endAngle) < tolerance or - (pointAngle >= startAngle or pointAngle <= endAngle) + local angleDiff = math.abs(pointAngle - centerAngle) + if angleDiff > math.pi then + angleDiff = 2 * math.pi - angleDiff end + return angleDiff <= maxAngle + tolerance end ffi.metatype("foundation_shape_Sector", Sector) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua index 4fe755fb..2332b78f 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua @@ -33,33 +33,22 @@ end ---@param point foundation.math.Vector2 点 ---@return boolean function ShapeIntersector.sectorContainsPoint(sector, point) + if math.abs(sector.range) >= 1 then + return (point - sector.center):length() <= sector.radius + end local v = point - sector.center local dist = v:length() - if dist > sector.radius then + if dist > sector.radius or dist < 1e-10 then return false end - - if dist < 1e-10 then - return true - end - - local angle = math.atan2(v.y, v.x) - if angle < 0 then - angle = angle + 2 * math.pi - end - - local start = sector.startDirection:angle() - local end_dir = sector.endDirection:angle() - - if start > end_dir then - end_dir = end_dir + 2 * math.pi - end - - if angle < start then - angle = angle + 2 * math.pi + local dir = v / dist + local angle = math.acos(sector.direction:dot(dir)) + local maxAngle = math.abs(sector.range) * math.pi + local cross = sector.direction.x * dir.y - sector.direction.y * dir.x + if sector.range < 0 then + cross = -cross end - - return angle <= end_dir + return angle <= maxAngle and cross >= 0 end ---检查点是否在三角形内 @@ -1215,8 +1204,12 @@ end ---@param segment foundation.shape.Segment 线段 ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToSegment(sector, segment) + local Segment = require("foundation.shape.Segment") local points = {} - local success, circle_points = ShapeIntersector.circleToSegment({ center = sector.center, radius = sector.radius }, segment) + if math.abs(sector.range) >= 1 then + return ShapeIntersector.circleToSegment(sector:toCircle(), segment) + end + local success, circle_points = ShapeIntersector.circleToSegment(sector:toCircle(), segment) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1224,7 +1217,24 @@ function ShapeIntersector.sectorToSegment(sector, segment) end end end - + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + local success1, edge_points1 = ShapeIntersector.segmentToSegment(startSegment, segment) + if success1 then + for _, p in ipairs(edge_points1) do + points[#points + 1] = p + end + end + local success2, edge_points2 = ShapeIntersector.segmentToSegment(endSegment, segment) + if success2 then + for _, p in ipairs(edge_points2) do + points[#points + 1] = p + end + end if ShapeIntersector.sectorContainsPoint(sector, segment.point1) then points[#points + 1] = segment.point1:clone() end @@ -1245,12 +1255,15 @@ end ---@param segment foundation.shape.Segment 线段 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithSegment(sector, segment) + local Segment = require("foundation.shape.Segment") + if math.abs(sector.range) >= 1 then + return ShapeIntersector.circleHasIntersectionWithSegment(sector:toCircle(), segment) + end if ShapeIntersector.sectorContainsPoint(sector, segment.point1) or ShapeIntersector.sectorContainsPoint(sector, segment.point2) then return true end - - local success, circle_points = ShapeIntersector.circleToSegment({ center = sector.center, radius = sector.radius }, segment) + local success, circle_points = ShapeIntersector.circleToSegment(sector:toCircle(), segment) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1258,7 +1271,16 @@ function ShapeIntersector.sectorHasIntersectionWithSegment(sector, segment) end end end - + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + if ShapeIntersector.segmentHasIntersectionWithSegment(startSegment, segment) or + ShapeIntersector.segmentHasIntersectionWithSegment(endSegment, segment) then + return true + end return false end @@ -1267,8 +1289,12 @@ end ---@param line foundation.shape.Line 直线 ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToLine(sector, line) + local Segment = require("foundation.shape.Segment") local points = {} - local success, circle_points = ShapeIntersector.lineToCircle(line, { center = sector.center, radius = sector.radius }) + if math.abs(sector.range) >= 1 then + return ShapeIntersector.lineToCircle(line, sector:toCircle()) + end + local success, circle_points = ShapeIntersector.lineToCircle(line, sector:toCircle()) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1276,6 +1302,24 @@ function ShapeIntersector.sectorToLine(sector, line) end end end + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + local success1, edge_points1 = ShapeIntersector.lineToSegment(line, startSegment) + if success1 then + for _, p in ipairs(edge_points1) do + points[#points + 1] = p + end + end + local success2, edge_points2 = ShapeIntersector.lineToSegment(line, endSegment) + if success2 then + for _, p in ipairs(edge_points2) do + points[#points + 1] = p + end + end local unique_points = ShapeIntersector.getUniquePoints(points) @@ -1290,7 +1334,11 @@ end ---@param line foundation.shape.Line 直线 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithLine(sector, line) - local success, circle_points = ShapeIntersector.lineToCircle(line, { center = sector.center, radius = sector.radius }) + local Segment = require("foundation.shape.Segment") + if math.abs(sector.range) >= 1 then + return ShapeIntersector.lineHasIntersectionWithCircle(line, sector:toCircle()) + end + local success, circle_points = ShapeIntersector.lineToCircle(line, sector:toCircle()) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1298,6 +1346,16 @@ function ShapeIntersector.sectorHasIntersectionWithLine(sector, line) end end end + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + if ShapeIntersector.lineHasIntersectionWithSegment(line, startSegment) or + ShapeIntersector.lineHasIntersectionWithSegment(line, endSegment) then + return true + end return false end @@ -1306,8 +1364,12 @@ end ---@param ray foundation.shape.Ray 射线 ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToRay(sector, ray) + local Segment = require("foundation.shape.Segment") local points = {} - local success, circle_points = ShapeIntersector.rayToCircle(ray, { center = sector.center, radius = sector.radius }) + if math.abs(sector.range) >= 1 then + return ShapeIntersector.rayToCircle(ray, sector:toCircle()) + end + local success, circle_points = ShapeIntersector.rayToCircle(ray, sector:toCircle()) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1315,7 +1377,24 @@ function ShapeIntersector.sectorToRay(sector, ray) end end end - + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + local success1, edge_points1 = ShapeIntersector.rayToSegment(ray, startSegment) + if success1 then + for _, p in ipairs(edge_points1) do + points[#points + 1] = p + end + end + local success2, edge_points2 = ShapeIntersector.rayToSegment(ray, endSegment) + if success2 then + for _, p in ipairs(edge_points2) do + points[#points + 1] = p + end + end if ShapeIntersector.sectorContainsPoint(sector, ray.point) then points[#points + 1] = ray.point:clone() end @@ -1333,11 +1412,14 @@ end ---@param ray foundation.shape.Ray 射线 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithRay(sector, ray) + local Segment = require("foundation.shape.Segment") + if math.abs(sector.range) >= 1 then + return ShapeIntersector.rayHasIntersectionWithCircle(ray, sector:toCircle()) + end if ShapeIntersector.sectorContainsPoint(sector, ray.point) then return true end - - local success, circle_points = ShapeIntersector.rayToCircle(ray, { center = sector.center, radius = sector.radius }) + local success, circle_points = ShapeIntersector.rayToCircle(ray, sector:toCircle()) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1345,6 +1427,16 @@ function ShapeIntersector.sectorHasIntersectionWithRay(sector, ray) end end end + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + if ShapeIntersector.rayHasIntersectionWithSegment(ray, startSegment) or + ShapeIntersector.rayHasIntersectionWithSegment(ray, endSegment) then + return true + end return false end @@ -1354,6 +1446,9 @@ end ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToTriangle(sector, triangle) local points = {} + if math.abs(sector.range) >= 1 then + return ShapeIntersector.triangleToCircle(triangle, sector:toCircle()) + end local edges = triangle:getEdges() for _, edge in ipairs(edges) do local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) @@ -1388,6 +1483,9 @@ end ---@param triangle foundation.shape.Triangle 三角形 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithTriangle(sector, triangle) + if math.abs(sector.range) >= 1 then + return ShapeIntersector.triangleHasIntersectionWithCircle(triangle, sector:toCircle()) + end local edges = triangle:getEdges() for _, edge in ipairs(edges) do if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then @@ -1414,8 +1512,12 @@ end ---@param circle foundation.shape.Circle 圆 ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToCircle(sector, circle) + local Segment = require("foundation.shape.Segment") local points = {} - local success, circle_points = ShapeIntersector.circleToCircle({ center = sector.center, radius = sector.radius }, circle) + if math.abs(sector.range) >= 1 then + return ShapeIntersector.circleToCircle(sector:toCircle(), circle) + end + local success, circle_points = ShapeIntersector.circleToCircle(sector:toCircle(), circle) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1423,7 +1525,24 @@ function ShapeIntersector.sectorToCircle(sector, circle) end end end - + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + local success1, edge_points1 = ShapeIntersector.circleToSegment(circle, startSegment) + if success1 then + for _, p in ipairs(edge_points1) do + points[#points + 1] = p + end + end + local success2, edge_points2 = ShapeIntersector.circleToSegment(circle, endSegment) + if success2 then + for _, p in ipairs(edge_points2) do + points[#points + 1] = p + end + end if ShapeIntersector.sectorContainsPoint(sector, circle.center) then points[#points + 1] = circle.center:clone() end @@ -1441,11 +1560,14 @@ end ---@param circle foundation.shape.Circle 圆 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithCircle(sector, circle) + local Segment = require("foundation.shape.Segment") + if math.abs(sector.range) >= 1 then + return ShapeIntersector.circleHasIntersectionWithCircle(sector:toCircle(), circle) + end if ShapeIntersector.sectorContainsPoint(sector, circle.center) then return true end - - local success, circle_points = ShapeIntersector.circleToCircle({ center = sector.center, radius = sector.radius }, circle) + local success, circle_points = ShapeIntersector.circleToCircle(sector:toCircle(), circle) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1453,7 +1575,16 @@ function ShapeIntersector.sectorHasIntersectionWithCircle(sector, circle) end end end - + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + if ShapeIntersector.circleHasIntersectionWithSegment(circle, startSegment) or + ShapeIntersector.circleHasIntersectionWithSegment(circle, endSegment) then + return true + end return false end @@ -1463,6 +1594,9 @@ end ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToRectangle(sector, rectangle) local points = {} + if math.abs(sector.range) >= 1 then + return ShapeIntersector.rectangleToCircle(rectangle, sector:toCircle()) + end local edges = rectangle:getEdges() for _, edge in ipairs(edges) do local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) @@ -1497,6 +1631,9 @@ end ---@param rectangle foundation.shape.Rectangle 矩形 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithRectangle(sector, rectangle) + if math.abs(sector.range) >= 1 then + return ShapeIntersector.rectangleHasIntersectionWithCircle(rectangle, sector:toCircle()) + end local edges = rectangle:getEdges() for _, edge in ipairs(edges) do if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then @@ -1523,11 +1660,12 @@ end ---@param sector2 foundation.shape.Sector 第二个扇形 ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToSector(sector1, sector2) + local Segment = require("foundation.shape.Segment") local points = {} - local success, circle_points = ShapeIntersector.circleToCircle( - { center = sector1.center, radius = sector1.radius }, - { center = sector2.center, radius = sector2.radius } - ) + if math.abs(sector1.range) >= 1 and math.abs(sector2.range) >= 1 then + return ShapeIntersector.circleToCircle(sector1:toCircle(), sector2:toCircle()) + end + local success, circle_points = ShapeIntersector.circleToCircle(sector1:toCircle(), sector2:toCircle()) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then @@ -1535,7 +1673,46 @@ function ShapeIntersector.sectorToSector(sector1, sector2) end end end - + if math.abs(sector1.range) < 1 then + local startDir1 = sector1.direction + local endDir1 = sector1.direction:rotated(sector1.range * 2 * math.pi) + local startPoint1 = sector1.center + startDir1 * sector1.radius + local endPoint1 = sector1.center + endDir1 * sector1.radius + local startSegment1 = Segment.create(sector1.center, startPoint1) + local endSegment1 = Segment.create(sector1.center, endPoint1) + local success1, edge_points1 = ShapeIntersector.sectorToSegment(sector2, startSegment1) + if success1 then + for _, p in ipairs(edge_points1) do + points[#points + 1] = p + end + end + local success2, edge_points2 = ShapeIntersector.sectorToSegment(sector2, endSegment1) + if success2 then + for _, p in ipairs(edge_points2) do + points[#points + 1] = p + end + end + end + if math.abs(sector2.range) < 1 then + local startDir2 = sector2.direction + local endDir2 = sector2.direction:rotated(sector2.range * 2 * math.pi) + local startPoint2 = sector2.center + startDir2 * sector2.radius + local endPoint2 = sector2.center + endDir2 * sector2.radius + local startSegment2 = Segment.create(sector2.center, startPoint2) + local endSegment2 = Segment.create(sector2.center, endPoint2) + local success3, edge_points3 = ShapeIntersector.sectorToSegment(sector1, startSegment2) + if success3 then + for _, p in ipairs(edge_points3) do + points[#points + 1] = p + end + end + local success4, edge_points4 = ShapeIntersector.sectorToSegment(sector1, endSegment2) + if success4 then + for _, p in ipairs(edge_points4) do + points[#points + 1] = p + end + end + end if ShapeIntersector.sectorContainsPoint(sector1, sector2.center) then points[#points + 1] = sector2.center:clone() end @@ -1556,15 +1733,15 @@ end ---@param sector2 foundation.shape.Sector 第二个扇形 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithSector(sector1, sector2) + local Segment = require("foundation.shape.Segment") + if math.abs(sector1.range) >= 1 and math.abs(sector2.range) >= 1 then + return ShapeIntersector.circleHasIntersectionWithCircle(sector1:toCircle(), sector2:toCircle()) + end if ShapeIntersector.sectorContainsPoint(sector1, sector2.center) or ShapeIntersector.sectorContainsPoint(sector2, sector1.center) then return true end - - local success, circle_points = ShapeIntersector.circleToCircle( - { center = sector1.center, radius = sector1.radius }, - { center = sector2.center, radius = sector2.radius } - ) + local success, circle_points = ShapeIntersector.circleToCircle(sector1:toCircle(), sector2:toCircle()) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then @@ -1572,7 +1749,30 @@ function ShapeIntersector.sectorHasIntersectionWithSector(sector1, sector2) end end end - + if math.abs(sector1.range) < 1 then + local startDir1 = sector1.direction + local endDir1 = sector1.direction:rotated(sector1.range * 2 * math.pi) + local startPoint1 = sector1.center + startDir1 * sector1.radius + local endPoint1 = sector1.center + endDir1 * sector1.radius + local startSegment1 = Segment.create(sector1.center, startPoint1) + local endSegment1 = Segment.create(sector1.center, endPoint1) + if ShapeIntersector.sectorHasIntersectionWithSegment(sector2, startSegment1) or + ShapeIntersector.sectorHasIntersectionWithSegment(sector2, endSegment1) then + return true + end + end + if math.abs(sector2.range) < 1 then + local startDir2 = sector2.direction + local endDir2 = sector2.direction:rotated(sector2.range * 2 * math.pi) + local startPoint2 = sector2.center + startDir2 * sector2.radius + local endPoint2 = sector2.center + endDir2 * sector2.radius + local startSegment2 = Segment.create(sector2.center, startPoint2) + local endSegment2 = Segment.create(sector2.center, endPoint2) + if ShapeIntersector.sectorHasIntersectionWithSegment(sector1, startSegment2) or + ShapeIntersector.sectorHasIntersectionWithSegment(sector1, endSegment2) then + return true + end + end return false end From dae0a1cbd63da0a47b0f4a1aa5627a04a3a5ec1c Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 09:32:28 +0800 Subject: [PATCH 30/71] refactor: optimize point containment checks in Sector and ShapeIntersector --- .../foundation/math/Vector2.lua | 7 +- .../foundation/math/Vector3.lua | 7 +- .../foundation/math/Vector4.lua | 9 +- .../foundation/shape/Sector.lua | 51 +++--- .../foundation/shape/ShapeIntersector.lua | 160 ++++++++++-------- 5 files changed, 132 insertions(+), 102 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index e76884b0..f0ca1e02 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -3,6 +3,9 @@ local ffi = require("ffi") local type = type local string = string local math = math +local require = require + +local Vector3, Vector4 ffi.cdef [[ typedef struct { @@ -239,7 +242,7 @@ end ---@param z number|nil Z坐标分量,默认为0 ---@return foundation.math.Vector3 转换后的Vector3 function Vector2:toVector3(z) - local Vector3 = require("foundation.math.Vector3") + Vector3 = Vector3 or require("foundation.math.Vector3") return Vector3.create(self.x, self.y, z or 0) end @@ -248,7 +251,7 @@ end ---@param w number|nil W坐标分量,默认为0 ---@return foundation.math.Vector4 转换后的Vector4 function Vector2:toVector4(z, w) - local Vector4 = require("foundation.math.Vector4") + Vector4 = Vector4 or require("foundation.math.Vector4") return Vector4.create(self.x, self.y, z or 0, w or 0) end diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua index c6ec9c11..b1ae658d 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua @@ -3,6 +3,9 @@ local ffi = require("ffi") local type = type local string = string local math = math +local require = require + +local Vector2, Vector4 ffi.cdef [[ typedef struct { @@ -133,7 +136,7 @@ end ---将Vector3转换为Vector2 ---@return foundation.math.Vector2 转换后的Vector2 function Vector3:toVector2() - local Vector2 = require("foundation.math.Vector2") + Vector2 = Vector2 or require("foundation.math.Vector2") return Vector2.create(self.x, self.y) end @@ -141,7 +144,7 @@ end ---@param w number|nil W坐标分量,默认为0 ---@return foundation.math.Vector4 转换后的Vector4 function Vector3:toVector4(w) - local Vector4 = require("foundation.math.Vector4") + Vector4 = Vector4 or require("foundation.math.Vector4") return Vector4.create(self.x, self.y, self.z, w or 0) end diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua index 2e5b4898..1136f031 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua @@ -3,6 +3,9 @@ local ffi = require("ffi") local type = type local string = string local math = math +local require = require + +local Vector2, Vector3 ffi.cdef [[ typedef struct { @@ -137,14 +140,14 @@ end ---将Vector4转换为Vector2 ---@return foundation.math.Vector2 转换后的Vector2 function Vector4:toVector2() - local Vector2 = require("foundation.math.Vector2") + Vector2 = Vector2 or require("foundation.math.Vector2") return Vector2.create(self.x, self.y) end ---将Vector4转换为Vector3 ---@return foundation.math.Vector3 转换后的Vector3 function Vector4:toVector3() - local Vector3 = require("foundation.math.Vector3") + Vector3 = Vector3 or require("foundation.math.Vector3") return Vector3.create(self.x, self.y, self.z) end @@ -190,7 +193,7 @@ end ---获取向量的投影坐标(将w分量归一化为1后返回一个Vector3) ---@return foundation.math.Vector3 投影后的三维向量 function Vector4:projectTo3D() - local Vector3 = require("foundation.math.Vector3") + Vector3 = Vector3 or require("foundation.math.Vector3") if math.abs(self.w) < 1e-10 then return Vector3.create(self.x, self.y, self.z) end diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index bba1af65..cccd0594 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -281,34 +281,43 @@ end ---@return boolean function Sector:containsPoint(point, tolerance) tolerance = tolerance or 1e-10 + + local vec = point - self.center + local range = self.range * 2 * math.pi if math.abs(self.range) >= 1 then - return Circle.create(self.center, self.radius):containsPoint(point, tolerance) + local dist = (point - self.center):length() + return math.abs(dist - self.radius) <= tolerance end - local circle = Circle.create(self.center, self.radius) - if not circle:containsPoint(point, tolerance) then - return false + + local segment1 = Segment.create(self.center, self.center + self.direction * self.radius) + if segment1:containsPoint(point, tolerance) then + return true end - local vec = point - self.center - local dir = vec:normalized() - local centerAngle = math.atan2(self.direction.y, self.direction.x) % (2 * math.pi) - local pointAngle = math.atan2(dir.y, dir.x) % (2 * math.pi) - local maxAngle = math.abs(self.range) * math.pi - local startAngle = centerAngle - local endAngle = (centerAngle + self.range * 2 * math.pi) % (2 * math.pi) - if startAngle < 0 then - startAngle = startAngle + 2 * math.pi + + local segment2 = Segment.create(self.center, self.center + self.direction:rotated(range) * self.radius) + if segment2:containsPoint(point, tolerance) then + return true end - if endAngle < 0 then - endAngle = endAngle + 2 * math.pi + + local distance = vec:length() + if math.abs(distance - self.radius) > tolerance then + return false end - if pointAngle < 0 then - pointAngle = pointAngle + 2 * math.pi + if distance < tolerance then + return true end - local angleDiff = math.abs(pointAngle - centerAngle) - if angleDiff > math.pi then - angleDiff = 2 * math.pi - angleDiff + + local angle_begin + if range > 0 then + angle_begin = self.direction:angle() + else + range = -range + angle_begin = self.direction:angle() - range end - return angleDiff <= maxAngle + tolerance + + local vec_angle = vec:angle() + vec_angle = vec_angle - 2 * math.pi * math.floor((vec_angle - angle_begin) / (2 * math.pi)) + return angle_begin <= vec_angle and vec_angle <= angle_begin + range end ffi.metatype("foundation_shape_Sector", Sector) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua index 2332b78f..9092dc5b 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua @@ -2,6 +2,9 @@ local Vector2 = require("foundation.math.Vector2") local math = math local tostring = tostring local ipairs = ipairs +local require = require + +local Segment ---@class foundation.shape.ShapeIntersector local ShapeIntersector = {} @@ -33,22 +36,27 @@ end ---@param point foundation.math.Vector2 点 ---@return boolean function ShapeIntersector.sectorContainsPoint(sector, point) - if math.abs(sector.range) >= 1 then - return (point - sector.center):length() <= sector.radius - end - local v = point - sector.center - local dist = v:length() - if dist > sector.radius or dist < 1e-10 then + local inCircle = ShapeIntersector.circleContainsPoint(sector, point) + if not inCircle then return false end - local dir = v / dist - local angle = math.acos(sector.direction:dot(dir)) - local maxAngle = math.abs(sector.range) * math.pi - local cross = sector.direction.x * dir.y - sector.direction.y * dir.x - if sector.range < 0 then - cross = -cross + if math.abs(sector.range) >= 1 then + return true end - return angle <= maxAngle and cross >= 0 + + local range = sector.range * math.pi * 2 + local angle_begin + if range > 0 then + angle_begin = sector.direction:angle() + else + range = -range + angle_begin = sector.direction:angle() - range + end + + local vec = point - sector.center + local vec_angle = vec:angle() + vec_angle = vec_angle - 2 * math.pi * math.floor((vec_angle - angle_begin) / (2 * math.pi)) + return angle_begin <= vec_angle and vec_angle <= angle_begin + range end ---检查点是否在三角形内 @@ -531,37 +539,39 @@ end ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.circleToSegment(circle, segment) local points = {} + local dir = segment.point2 - segment.point1 + local len = dir:length() + + if len < 1e-10 then + if ShapeIntersector.circleContainsPoint(circle, segment.point1) then + return true, { segment.point1:clone() } + end + return false, nil + end + + dir = dir / len + local closest = segment:closestPoint(circle.center) + local dist = (closest - circle.center):length() + if dist > circle.radius then + return false, nil + end - if (closest - circle.center):length() <= circle.radius then - local dir = segment.point2 - segment.point1 - local len = dir:length() - if len == 0 then - if (segment.point1 - circle.center):length() <= circle.radius then - points[#points + 1] = segment.point1:clone() - end + local vector_to_start = segment.point1 - circle.center + local b = 2 * vector_to_start:dot(dir) + local c = vector_to_start:dot(vector_to_start) - circle.radius * circle.radius + local discriminant = b * b - 4 * c - if #points == 0 then - return false, nil - end - return true, points + if discriminant >= -1e-10 then + local sqrt_d = math.sqrt(math.max(discriminant, 0)) + local t1 = (-b - sqrt_d) / 2 + local t2 = (-b + sqrt_d) / 2 + + if t1 >= 0 and t1 <= len then + points[#points + 1] = segment.point1 + dir * t1 end - dir = dir / len - local L = segment.point1 - circle.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - circle.radius * circle.radius - local discriminant = b * b - 4 * a * c - if discriminant >= 0 then - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - if t1 >= 0 and t1 <= len then - points[#points + 1] = segment.point1 + dir * t1 - end - if t2 >= 0 and t2 <= len and math.abs(t2 - t1) > 1e-10 then - points[#points + 1] = segment.point1 + dir * t2 - end + if t2 >= 0 and t2 <= len and discriminant > 1e-10 then + points[#points + 1] = segment.point1 + dir * t2 end end @@ -1204,15 +1214,17 @@ end ---@param segment foundation.shape.Segment 线段 ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToSegment(sector, segment) - local Segment = require("foundation.shape.Segment") + Segment = Segment or require("foundation.shape.Segment") local points = {} if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleToSegment(sector:toCircle(), segment) + return ShapeIntersector.circleToSegment(sector, segment) end - local success, circle_points = ShapeIntersector.circleToSegment(sector:toCircle(), segment) + local success, circle_points = ShapeIntersector.circleToSegment(sector, segment) if success then + local n = 0 for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then + n = n + 1 points[#points + 1] = p end end @@ -1255,15 +1267,15 @@ end ---@param segment foundation.shape.Segment 线段 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithSegment(sector, segment) - local Segment = require("foundation.shape.Segment") + Segment = Segment or require("foundation.shape.Segment") if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleHasIntersectionWithSegment(sector:toCircle(), segment) + return ShapeIntersector.circleHasIntersectionWithSegment(sector, segment) end if ShapeIntersector.sectorContainsPoint(sector, segment.point1) or ShapeIntersector.sectorContainsPoint(sector, segment.point2) then return true end - local success, circle_points = ShapeIntersector.circleToSegment(sector:toCircle(), segment) + local success, circle_points = ShapeIntersector.circleToSegment(sector, segment) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1289,12 +1301,12 @@ end ---@param line foundation.shape.Line 直线 ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToLine(sector, line) - local Segment = require("foundation.shape.Segment") + Segment = Segment or require("foundation.shape.Segment") local points = {} if math.abs(sector.range) >= 1 then - return ShapeIntersector.lineToCircle(line, sector:toCircle()) + return ShapeIntersector.lineToCircle(line, sector) end - local success, circle_points = ShapeIntersector.lineToCircle(line, sector:toCircle()) + local success, circle_points = ShapeIntersector.lineToCircle(line, sector) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1334,11 +1346,11 @@ end ---@param line foundation.shape.Line 直线 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithLine(sector, line) - local Segment = require("foundation.shape.Segment") + Segment = Segment or require("foundation.shape.Segment") if math.abs(sector.range) >= 1 then - return ShapeIntersector.lineHasIntersectionWithCircle(line, sector:toCircle()) + return ShapeIntersector.lineHasIntersectionWithCircle(line, sector) end - local success, circle_points = ShapeIntersector.lineToCircle(line, sector:toCircle()) + local success, circle_points = ShapeIntersector.lineToCircle(line, sector) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1364,12 +1376,12 @@ end ---@param ray foundation.shape.Ray 射线 ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToRay(sector, ray) - local Segment = require("foundation.shape.Segment") + Segment = Segment or require("foundation.shape.Segment") local points = {} if math.abs(sector.range) >= 1 then - return ShapeIntersector.rayToCircle(ray, sector:toCircle()) + return ShapeIntersector.rayToCircle(ray, sector) end - local success, circle_points = ShapeIntersector.rayToCircle(ray, sector:toCircle()) + local success, circle_points = ShapeIntersector.rayToCircle(ray, sector) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1412,14 +1424,14 @@ end ---@param ray foundation.shape.Ray 射线 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithRay(sector, ray) - local Segment = require("foundation.shape.Segment") + Segment = Segment or require("foundation.shape.Segment") if math.abs(sector.range) >= 1 then - return ShapeIntersector.rayHasIntersectionWithCircle(ray, sector:toCircle()) + return ShapeIntersector.rayHasIntersectionWithCircle(ray, sector) end if ShapeIntersector.sectorContainsPoint(sector, ray.point) then return true end - local success, circle_points = ShapeIntersector.rayToCircle(ray, sector:toCircle()) + local success, circle_points = ShapeIntersector.rayToCircle(ray, sector) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1447,7 +1459,7 @@ end function ShapeIntersector.sectorToTriangle(sector, triangle) local points = {} if math.abs(sector.range) >= 1 then - return ShapeIntersector.triangleToCircle(triangle, sector:toCircle()) + return ShapeIntersector.triangleToCircle(triangle, sector) end local edges = triangle:getEdges() for _, edge in ipairs(edges) do @@ -1484,7 +1496,7 @@ end ---@return boolean function ShapeIntersector.sectorHasIntersectionWithTriangle(sector, triangle) if math.abs(sector.range) >= 1 then - return ShapeIntersector.triangleHasIntersectionWithCircle(triangle, sector:toCircle()) + return ShapeIntersector.triangleHasIntersectionWithCircle(triangle, sector) end local edges = triangle:getEdges() for _, edge in ipairs(edges) do @@ -1512,12 +1524,12 @@ end ---@param circle foundation.shape.Circle 圆 ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToCircle(sector, circle) - local Segment = require("foundation.shape.Segment") + Segment = Segment or require("foundation.shape.Segment") local points = {} if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleToCircle(sector:toCircle(), circle) + return ShapeIntersector.circleToCircle(sector, circle) end - local success, circle_points = ShapeIntersector.circleToCircle(sector:toCircle(), circle) + local success, circle_points = ShapeIntersector.circleToCircle(sector, circle) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1560,14 +1572,14 @@ end ---@param circle foundation.shape.Circle 圆 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithCircle(sector, circle) - local Segment = require("foundation.shape.Segment") + Segment = Segment or require("foundation.shape.Segment") if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleHasIntersectionWithCircle(sector:toCircle(), circle) + return ShapeIntersector.circleHasIntersectionWithCircle(sector, circle) end if ShapeIntersector.sectorContainsPoint(sector, circle.center) then return true end - local success, circle_points = ShapeIntersector.circleToCircle(sector:toCircle(), circle) + local success, circle_points = ShapeIntersector.circleToCircle(sector, circle) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector, p) then @@ -1595,7 +1607,7 @@ end function ShapeIntersector.sectorToRectangle(sector, rectangle) local points = {} if math.abs(sector.range) >= 1 then - return ShapeIntersector.rectangleToCircle(rectangle, sector:toCircle()) + return ShapeIntersector.rectangleToCircle(rectangle, sector) end local edges = rectangle:getEdges() for _, edge in ipairs(edges) do @@ -1632,7 +1644,7 @@ end ---@return boolean function ShapeIntersector.sectorHasIntersectionWithRectangle(sector, rectangle) if math.abs(sector.range) >= 1 then - return ShapeIntersector.rectangleHasIntersectionWithCircle(rectangle, sector:toCircle()) + return ShapeIntersector.rectangleHasIntersectionWithCircle(rectangle, sector) end local edges = rectangle:getEdges() for _, edge in ipairs(edges) do @@ -1660,12 +1672,12 @@ end ---@param sector2 foundation.shape.Sector 第二个扇形 ---@return boolean, foundation.math.Vector2[] | nil function ShapeIntersector.sectorToSector(sector1, sector2) - local Segment = require("foundation.shape.Segment") + Segment = Segment or require("foundation.shape.Segment") local points = {} if math.abs(sector1.range) >= 1 and math.abs(sector2.range) >= 1 then - return ShapeIntersector.circleToCircle(sector1:toCircle(), sector2:toCircle()) + return ShapeIntersector.circleToCircle(sector1, sector2) end - local success, circle_points = ShapeIntersector.circleToCircle(sector1:toCircle(), sector2:toCircle()) + local success, circle_points = ShapeIntersector.circleToCircle(sector1, sector2) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then @@ -1733,15 +1745,15 @@ end ---@param sector2 foundation.shape.Sector 第二个扇形 ---@return boolean function ShapeIntersector.sectorHasIntersectionWithSector(sector1, sector2) - local Segment = require("foundation.shape.Segment") + Segment = Segment or require("foundation.shape.Segment") if math.abs(sector1.range) >= 1 and math.abs(sector2.range) >= 1 then - return ShapeIntersector.circleHasIntersectionWithCircle(sector1:toCircle(), sector2:toCircle()) + return ShapeIntersector.circleHasIntersectionWithCircle(sector1, sector2) end if ShapeIntersector.sectorContainsPoint(sector1, sector2.center) or ShapeIntersector.sectorContainsPoint(sector2, sector1.center) then return true end - local success, circle_points = ShapeIntersector.circleToCircle(sector1:toCircle(), sector2:toCircle()) + local success, circle_points = ShapeIntersector.circleToCircle(sector1, sector2) if success then for _, p in ipairs(circle_points) do if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then From 892523d3264b8cd763f6dd609ab357e68b46ff2e Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 09:51:47 +0800 Subject: [PATCH 31/71] refactor: update equality checks and distance comparisons to use inclusive tolerance --- .../foundation/math/Vector2.lua | 9 ++- .../foundation/math/Vector3.lua | 6 +- .../foundation/math/Vector4.lua | 18 +++--- .../foundation/shape/Circle.lua | 4 +- .../foundation/shape/Line.lua | 6 +- .../thlib-scripts-v2/foundation/shape/Ray.lua | 10 ++-- .../foundation/shape/Rectangle.lua | 6 +- .../foundation/shape/Sector.lua | 8 +-- .../foundation/shape/Segment.lua | 10 ++-- .../foundation/shape/ShapeIntersector.lua | 58 +++++++++---------- .../foundation/shape/Triangle.lua | 2 +- 11 files changed, 72 insertions(+), 65 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index f0ca1e02..c8a09900 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -126,7 +126,7 @@ end ---@param b foundation.math.Vector2 第二个操作数 ---@return boolean 两个向量是否相等 function Vector2.__eq(a, b) - return math.abs(a.x - b.x) < 1e-10 and math.abs(a.y - b.y) < 1e-10 + return math.abs(a.x - b.x) <= 1e-10 and math.abs(a.y - b.y) <= 1e-10 end ---向量字符串表示 @@ -181,9 +181,12 @@ end ---@return foundation.math.Vector2 归一化后的向量(自身引用) function Vector2:normalize() local len = self:length() - if len > 0 then + if len > 1e-10 then self.x = self.x / len self.y = self.y / len + else + self.x = 0 + self.y = 0 end return self end @@ -192,7 +195,7 @@ end ---@return foundation.math.Vector2 归一化后的向量副本 function Vector2:normalized() local len = self:length() - if len == 0 then + if len <= 1e-10 then return Vector2.zero() end return Vector2.create(self.x / len, self.y / len) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua index b1ae658d..92a0eb11 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua @@ -189,10 +189,12 @@ end ---@return foundation.math.Vector3 归一化后的向量(自身引用) function Vector3:normalize() local len = self:length() - if len > 0 then + if len > 1e-10 then self.x = self.x / len self.y = self.y / len self.z = self.z / len + else + self.x, self.y, self.z = 0, 0, 0 end return self end @@ -201,7 +203,7 @@ end ---@return foundation.math.Vector3 归一化后的向量副本 function Vector3:normalized() local len = self:length() - if len == 0 then + if len <= 1e-10 then return Vector3.zero() end return Vector3.create(self.x / len, self.y / len, self.z / len) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua index 1136f031..f009e6f7 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua @@ -110,10 +110,10 @@ end ---@param b foundation.math.Vector4 第二个操作数 ---@return boolean 两个向量是否相等 function Vector4.__eq(a, b) - return math.abs(a.x - b.x) < 1e-10 and - math.abs(a.y - b.y) < 1e-10 and - math.abs(a.z - b.z) < 1e-10 and - math.abs(a.w - b.w) < 1e-10 + return math.abs(a.x - b.x) <= 1e-10 and + math.abs(a.y - b.y) <= 1e-10 and + math.abs(a.z - b.z) <= 1e-10 and + math.abs(a.w - b.w) <= 1e-10 end ---向量字符串表示 @@ -162,11 +162,13 @@ end ---@return foundation.math.Vector4 归一化后的向量(自身引用) function Vector4:normalize() local len = self:length() - if len > 0 then + if len > 1e-10 then self.x = self.x / len self.y = self.y / len self.z = self.z / len self.w = self.w / len + else + self.x, self.y, self.z, self.w = 0, 0, 0, 0 end return self end @@ -175,7 +177,7 @@ end ---@return foundation.math.Vector4 归一化后的向量副本 function Vector4:normalized() local len = self:length() - if len == 0 then + if len <= 1e-10 then return Vector4.zero() end return Vector4.create(self.x / len, self.y / len, self.z / len, self.w / len) @@ -184,7 +186,7 @@ end ---获取向量的齐次坐标(将w分量归一化为1) ---@return foundation.math.Vector4 归一化后的齐次坐标向量 function Vector4:homogeneous() - if math.abs(self.w) < 1e-10 then + if math.abs(self.w) <= 1e-10 then return self:clone() end return Vector4.create(self.x / self.w, self.y / self.w, self.z / self.w, 1) @@ -194,7 +196,7 @@ end ---@return foundation.math.Vector3 投影后的三维向量 function Vector4:projectTo3D() Vector3 = Vector3 or require("foundation.math.Vector3") - if math.abs(self.w) < 1e-10 then + if math.abs(self.w) <= 1e-10 then return Vector3.create(self.x, self.y, self.z) end return Vector3.create(self.x / self.w, self.y / self.w, self.z / self.w) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index a6d29c1f..67f74bdf 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -36,7 +36,7 @@ end ---@param b foundation.shape.Circle ---@return boolean function Circle.__eq(a, b) - return a.center == b.center and math.abs(a.radius - b.radius) < 1e-10 + return a.center == b.center and math.abs(a.radius - b.radius) <= 1e-10 end ---圆的字符串表示 @@ -122,7 +122,7 @@ end function Circle:closestPoint(point) local dir = point - self.center local dist = dir:length() - if dist == 0 then + if dist <= 1e-10 then return Vector2.create(self.center.x + self.radius, self.center.y) end local normalized_dir = dir / dist diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index 7ef61382..efaf3a46 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -28,7 +28,7 @@ Line.__type = "foundation.shape.Line" ---@return foundation.shape.Line function Line.create(point, direction) local dist = direction and direction:length() or 0 - if dist == 0 then + if dist <= 1e-10 then direction = Vector2.create(1, 0) elseif dist ~= 1 then ---@diagnostic disable-next-line: need-check-nil @@ -78,7 +78,7 @@ function Line.__eq(a, b) return false end local point_diff = b.point - a.point - return math.abs(point_diff:cross(a.direction)) < 1e-10 + return math.abs(point_diff:cross(a.direction)) <= 1e-10 end ---直线的字符串表示 @@ -225,7 +225,7 @@ function Line:containsPoint(point, tolerance) tolerance = tolerance or 1e-10 local point_vec = point - self.point local cross = point_vec:cross(self.direction) - return math.abs(cross) < tolerance + return math.abs(cross) <= tolerance end ---获取点在直线上的投影 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index a87ea5a3..180c719a 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -28,7 +28,7 @@ Ray.__type = "foundation.shape.Ray" ---@return foundation.shape.Ray function Ray.create(point, direction) local dist = direction and direction:length() or 0 - if dist == 0 then + if dist <= 1e-10 then direction = Vector2.create(1, 0) elseif dist ~= 1 then ---@diagnostic disable-next-line: need-check-nil @@ -200,7 +200,7 @@ function Ray:distanceToPoint(point) local point_vec = point - self.point local proj_length = point_vec:dot(self.direction) - if proj_length < 0 then + if proj_length <= 1e-10 then return (point - self.point):length() else local proj_point = self.point + self.direction * proj_length @@ -217,12 +217,12 @@ function Ray:containsPoint(point, tolerance) local point_vec = point - self.point local proj_length = point_vec:dot(self.direction) - if proj_length < 0 then + if proj_length <= 1e-10 then return false end local cross = point_vec:cross(self.direction) - return math.abs(cross) < tolerance + return math.abs(cross) <= tolerance end ---获取点在射线上的投影 @@ -232,7 +232,7 @@ function Ray:projectPoint(point) local point_vec = point - self.point local proj_length = point_vec:dot(self.direction) - if proj_length < 0 then + if proj_length <= 1e-10 then return self.point:clone() else return self.point + self.direction * proj_length diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index 822efc3b..28bdaf89 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -36,7 +36,7 @@ Rectangle.__type = "foundation.shape.Rectangle" ---@return foundation.shape.Rectangle function Rectangle.create(center, width, height, direction) local dist = direction and direction:length() or 0 - if dist == 0 then + if dist <= 1e-10 then direction = Vector2.create(1, 0) elseif dist ~= 1 then ---@diagnostic disable-next-line: need-check-nil @@ -77,8 +77,8 @@ end ---@return boolean function Rectangle.__eq(a, b) return a.center == b.center and - math.abs(a.width - b.width) < 1e-10 and - math.abs(a.height - b.height) < 1e-10 and + math.abs(a.width - b.width) <= 1e-10 and + math.abs(a.height - b.height) <= 1e-10 and a.direction == b.direction end diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index cccd0594..93a00235 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -70,9 +70,9 @@ end ---@return boolean function Sector.__eq(a, b) return a.center == b.center and - math.abs(a.radius - b.radius) < 1e-10 and + math.abs(a.radius - b.radius) <= 1e-10 and a.direction == b.direction and - math.abs(a.range - b.range) < 1e-10 + math.abs(a.range - b.range) <= 1e-10 end ---扇形的字符串表示 @@ -115,7 +115,7 @@ function Sector:contains(point) return Circle.create(self.center, self.radius):contains(point) end local vec = point - self.center - if vec:length() > self.radius then + if vec:length() > self.radius + 1e-10 then return false end local dir = vec:normalized() @@ -303,7 +303,7 @@ function Sector:containsPoint(point, tolerance) if math.abs(distance - self.radius) > tolerance then return false end - if distance < tolerance then + if distance <= tolerance then return true end diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index dcc0501f..86384e07 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -76,7 +76,7 @@ end function Segment:normal() local dir = self.point2 - self.point1 local len = dir:length() - if len == 0 then + if len <= 1e-10 then return Vector2.zero() end return Vector2.create(-dir.y / len, dir.x / len) @@ -253,7 +253,7 @@ end function Segment:closestPoint(point) local dir = self.point2 - self.point1 local len = dir:length() - if len == 0 then + if len <= 1e-10 then return self.point1:clone() end @@ -277,7 +277,7 @@ end function Segment:projectPoint(point) local dir = self.point2 - self.point1 local len = dir:length() - if len == 0 then + if len <= 1e-10 then return self.point1:clone() end @@ -299,8 +299,8 @@ function Segment:containsPoint(point, tolerance) local dir = self.point2 - self.point1 local len = dir:length() - if len == 0 then - return point.x == self.point1.x and point.y == self.point1.y + if len <= 1e-10 then + return point == self.point1 end local t = ((point.x - self.point1.x) * dir.x + (point.y - self.point1.y) * dir.y) / (len * len) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua index 9092dc5b..626a6157 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua @@ -14,7 +14,7 @@ local ShapeIntersector = {} ---@param point foundation.math.Vector2 点 ---@return boolean function ShapeIntersector.circleContainsPoint(circle, point) - return (point - circle.center):length() <= circle.radius + return (point - circle.center):length() <= circle.radius + 1e-10 end ---检查点是否在矩形内(包括边界) @@ -28,7 +28,7 @@ function ShapeIntersector.rectangleContainsPoint(rectangle, point) local x = p.x * dir.x + p.y * dir.y local y = p.x * perp.x + p.y * perp.y local hw, hh = rectangle.width / 2, rectangle.height / 2 - return math.abs(x) <= hw and math.abs(y) <= hh + return math.abs(x) <= hw + 1e-10 and math.abs(y) <= hh + 1e-10 end ---检查点是否在扇形内(包括边界) @@ -388,7 +388,7 @@ function ShapeIntersector.segmentToSegment(segment1, segment2) local d = segment2.point2 local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then + if math.abs(denom) <= 1e-10 then -- 平行或共线情况 return false, nil end @@ -402,7 +402,7 @@ function ShapeIntersector.segmentToSegment(segment1, segment2) points[#points + 1] = Vector2.create(x, y) end - if #points == 0 then + if #points <= 1e-10 then return false, nil end return true, points @@ -419,7 +419,7 @@ function ShapeIntersector.segmentHasIntersectionWithSegment(segment1, segment2) local d = segment2.point2 local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then + if math.abs(denom) <= 1e-10 then -- 平行或共线情况 return false end @@ -442,8 +442,8 @@ function ShapeIntersector.lineToSegment(line, segment) local d = segment.point2 local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - return false, points + if math.abs(denom) <= 1e-10 then + return false, nil end local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom @@ -472,7 +472,7 @@ function ShapeIntersector.lineHasIntersectionWithSegment(line, segment) local d = segment.point2 local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then + if math.abs(denom) <= 1e-10 then return false end @@ -493,7 +493,7 @@ function ShapeIntersector.rayToSegment(ray, segment) local d = segment.point2 local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then + if math.abs(denom) <= 1e-10 then return false, nil end @@ -506,7 +506,7 @@ function ShapeIntersector.rayToSegment(ray, segment) points[#points + 1] = Vector2.create(x, y) end - if #points == 0 then + if #points <= 1e-10 then return false, nil end return true, points @@ -523,7 +523,7 @@ function ShapeIntersector.rayHasIntersectionWithSegment(ray, segment) local d = segment.point2 local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then + if math.abs(denom) <= 1e-10 then return false end @@ -542,7 +542,7 @@ function ShapeIntersector.circleToSegment(circle, segment) local dir = segment.point2 - segment.point1 local len = dir:length() - if len < 1e-10 then + if len <= 1e-10 then if ShapeIntersector.circleContainsPoint(circle, segment.point1) then return true, { segment.point1:clone() } end @@ -553,7 +553,7 @@ function ShapeIntersector.circleToSegment(circle, segment) local closest = segment:closestPoint(circle.center) local dist = (closest - circle.center):length() - if dist > circle.radius then + if dist > circle.radius + 1e-10 then return false, nil end @@ -575,7 +575,7 @@ function ShapeIntersector.circleToSegment(circle, segment) end end - if #points == 0 then + if #points <= 1e-10 then return false, nil end return true, points @@ -587,7 +587,7 @@ end ---@return boolean function ShapeIntersector.circleHasIntersectionWithSegment(circle, segment) local closest = segment:closestPoint(circle.center) - return (closest - circle.center):length() <= circle.radius + return (closest - circle.center):length() <= circle.radius + 1e-10 end ---检查直线与直线的相交 @@ -602,9 +602,9 @@ function ShapeIntersector.lineToLine(line1, line2) local d = line2.point + line2.direction local dir_cross = line1.direction:cross(line2.direction) - if math.abs(dir_cross) < 1e-10 then + if math.abs(dir_cross) <= 1e-10 then local point_diff = line2.point - line1.point - if math.abs(point_diff:cross(line1.direction)) < 1e-10 then + if math.abs(point_diff:cross(line1.direction)) <= 1e-10 then points[#points + 1] = line1.point:clone() points[#points + 1] = line1:getPoint(1) return true, points @@ -628,9 +628,9 @@ end ---@return boolean function ShapeIntersector.lineHasIntersectionWithLine(line1, line2) local dir_cross = line1.direction:cross(line2.direction) - if math.abs(dir_cross) < 1e-10 then + if math.abs(dir_cross) <= 1e-10 then local point_diff = line2.point - line1.point - return math.abs(point_diff:cross(line1.direction)) < 1e-10 + return math.abs(point_diff:cross(line1.direction)) <= 1e-10 end return true end @@ -647,7 +647,7 @@ function ShapeIntersector.lineToRay(line, ray) local d = ray.point + ray.direction local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then + if math.abs(denom) <= 1e-10 then return false, nil end @@ -677,7 +677,7 @@ function ShapeIntersector.lineHasIntersectionWithRay(line, ray) local d = ray.point + ray.direction local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then + if math.abs(denom) <= 1e-10 then return false end @@ -694,7 +694,7 @@ function ShapeIntersector.lineToCircle(line, circle) local points = {} local dir = line.direction local len = dir:length() - if len == 0 then + if len <= 1e-10 then return false, nil end dir = dir / len @@ -726,7 +726,7 @@ end function ShapeIntersector.lineHasIntersectionWithCircle(line, circle) local dir = line.direction local len = dir:length() - if len == 0 then + if len <= 1e-10 then return false end dir = dir / len @@ -792,9 +792,9 @@ function ShapeIntersector.rayHasIntersectionWithRay(ray1, ray2) local d = ray2.point + ray2.direction local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then + if math.abs(denom) <= 1e-10 then local dir_cross = ray1.direction:cross(ray2.direction) - if math.abs(dir_cross) < 1e-10 then + if math.abs(dir_cross) <= 1e-10 then local point_diff = ray2.point - ray1.point local t = point_diff:dot(ray1.direction) return t >= 0 @@ -816,7 +816,7 @@ function ShapeIntersector.rayToCircle(ray, circle) local points = {} local dir = ray.direction local len = dir:length() - if len == 0 then + if len <= 1e-10 then return false, nil end dir = dir / len @@ -850,7 +850,7 @@ end function ShapeIntersector.rayHasIntersectionWithCircle(ray, circle) local dir = ray.direction local len = dir:length() - if len == 0 then + if len <= 1e-10 then return false end dir = dir / len @@ -860,7 +860,7 @@ function ShapeIntersector.rayHasIntersectionWithCircle(ray, circle) local c = L:dot(L) - circle.radius * circle.radius local discriminant = b * b - 4 * a * c - if discriminant < 0 then + if discriminant <= 1e-10 then return false end @@ -889,7 +889,7 @@ function ShapeIntersector.circleToCircle(circle1, circle2) end end - if #points == 0 then + if #points <= 1e-10 then return false, nil end return true, points diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 61f9dfac..8c636324 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -90,7 +90,7 @@ function Triangle:circumcenter() local x3, y3 = self.point3.x, self.point3.y local D = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2) - if math.abs(D) < 1e-10 then + if math.abs(D) <= 1e-10 then return nil end From a12e824c287c72c5fa5bf13bc3e86fe26e510d46 Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 10:01:41 +0800 Subject: [PATCH 32/71] refactor: add methods to calculate closest points on Line and Ray --- .../thlib-scripts-v2/foundation/shape/Line.lua | 14 ++++++++++++++ .../thlib-scripts-v2/foundation/shape/Ray.lua | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index efaf3a46..3df01889 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -207,6 +207,20 @@ function Line:hasIntersection(other) return ShapeIntersector.hasIntersection(self, other) end +---计算点到直线的最近点 +---@param point foundation.math.Vector2 点 +---@return foundation.math.Vector2 最近点 +function Line:closestPoint(point) + local point_vec = point - self.point + local proj_length = point_vec:dot(self.direction) + + if proj_length <= 1e-10 then + return self.point:clone() + else + return self.point + self.direction * proj_length + end +end + ---计算点到直线的距离 ---@param point foundation.math.Vector2 点 ---@return number 距离 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index 180c719a..1e110bd7 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -193,6 +193,20 @@ function Ray:hasIntersection(other) return ShapeIntersector.hasIntersection(self, other) end +---计算点到射线的最近点 +---@param point foundation.math.Vector2 点 +---@return foundation.math.Vector2 最近点 +function Ray:nearestPoint(point) + local point_vec = point - self.point + local proj_length = point_vec:dot(self.direction) + + if proj_length <= 1e-10 then + return self.point:clone() + else + return self.point + self.direction * proj_length + end +end + ---计算点到射线的距离 ---@param point foundation.math.Vector2 点 ---@return number 距离 From 2c53ce4b36ae5b56e94ac2689db7d20fc8121c81 Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 10:03:23 +0800 Subject: [PATCH 33/71] refactor: rename nearestPoint method to closestPoint in Ray class --- game/packages/thlib-scripts-v2/foundation/shape/Ray.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index 1e110bd7..4d628076 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -196,7 +196,7 @@ end ---计算点到射线的最近点 ---@param point foundation.math.Vector2 点 ---@return foundation.math.Vector2 最近点 -function Ray:nearestPoint(point) +function Ray:closestPoint(point) local point_vec = point - self.point local proj_length = point_vec:dot(self.direction) From 18ce64ecc6e19612a24001f337b67e67d5cec9d5 Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 10:27:22 +0800 Subject: [PATCH 34/71] refactor: simplify direction calculations and streamline closestPoint methods in Line and Ray classes --- .../foundation/shape/Line.lua | 20 ++++++-------- .../thlib-scripts-v2/foundation/shape/Ray.lua | 26 +++++++++---------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index 3df01889..72e57168 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -152,8 +152,10 @@ function Line:rotate(rad, center) local cosRad = math.cos(rad) local sinRad = math.sin(rad) local v = self.direction - self.direction.x = v.x * cosRad - v.y * sinRad - self.direction.y = v.x * sinRad + v.y * cosRad + local x = v.x * cosRad - v.y * sinRad + local y = v.x * sinRad + v.y * cosRad + self.direction.x = x + self.direction.y = y return self end @@ -211,14 +213,7 @@ end ---@param point foundation.math.Vector2 点 ---@return foundation.math.Vector2 最近点 function Line:closestPoint(point) - local point_vec = point - self.point - local proj_length = point_vec:dot(self.direction) - - if proj_length <= 1e-10 then - return self.point:clone() - else - return self.point + self.direction * proj_length - end + return self:projectPoint(point) end ---计算点到直线的距离 @@ -246,9 +241,10 @@ end ---@param point foundation.math.Vector2 点 ---@return foundation.math.Vector2 投影点 function Line:projectPoint(point) + local dir = self.direction local point_vec = point - self.point - local proj_length = point_vec:dot(self.direction) - return self.point + self.direction * proj_length + local proj_length = point_vec:dot(dir) + return self.point + dir * proj_length end ffi.metatype("foundation_shape_Line", Line) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index 4d628076..52fa5cb7 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -138,8 +138,10 @@ function Ray:rotate(rad, center) local cosRad = math.cos(rad) local sinRad = math.sin(rad) local v = self.direction - self.direction.x = v.x * cosRad - v.y * sinRad - self.direction.y = v.x * sinRad + v.y * cosRad + local x = v.x * cosRad - v.y * sinRad + local y = v.x * sinRad + v.y * cosRad + self.direction.x = x + self.direction.y = y return self end @@ -197,13 +199,14 @@ end ---@param point foundation.math.Vector2 点 ---@return foundation.math.Vector2 最近点 function Ray:closestPoint(point) + local dir = self.direction:normalized() local point_vec = point - self.point - local proj_length = point_vec:dot(self.direction) + local proj_length = point_vec:dot(dir) - if proj_length <= 1e-10 then + if proj_length <= 0 then return self.point:clone() else - return self.point + self.direction * proj_length + return self.point + dir * proj_length end end @@ -239,18 +242,13 @@ function Ray:containsPoint(point, tolerance) return math.abs(cross) <= tolerance end ----获取点在射线上的投影 +---获取点在射线所在直线上的投影 ---@param point foundation.math.Vector2 点 ---@return foundation.math.Vector2 投影点 function Ray:projectPoint(point) - local point_vec = point - self.point - local proj_length = point_vec:dot(self.direction) - - if proj_length <= 1e-10 then - return self.point:clone() - else - return self.point + self.direction * proj_length - end + local dir = self.direction + local t = ((point.x - self.point.x) * dir.x + (point.y - self.point.y) * dir.y) + return Vector2.create(self.point.x + t * dir.x, self.point.y + t * dir.y) end ffi.metatype("foundation_shape_Ray", Ray) From d8882e82df4dc6c886c472e46bb4b47eeaff4b25 Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 10:35:30 +0800 Subject: [PATCH 35/71] refactor: simplify point containment and closest point calculations in Sector class --- .../foundation/shape/Sector.lua | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index 93a00235..891a9946 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -111,21 +111,7 @@ end ---@param point foundation.math.Vector2 ---@return boolean function Sector:contains(point) - if math.abs(self.range) >= 1 then - return Circle.create(self.center, self.radius):contains(point) - end - local vec = point - self.center - if vec:length() > self.radius + 1e-10 then - return false - end - local dir = vec:normalized() - local angle = math.acos(self.direction:dot(dir)) - local maxAngle = math.abs(self.range) * math.pi - local cross = self.direction.x * dir.y - self.direction.y * dir.x - if self.range < 0 then - cross = -cross - end - return angle <= maxAngle and cross >= 0 + return ShapeIntersector.sectorContainsPoint(self, point) end ---移动扇形(修改当前扇形) @@ -226,13 +212,12 @@ end ---@return foundation.math.Vector2 function Sector:closestPoint(point) if math.abs(self.range) >= 1 then - return Circle.create(self.center, self.radius):closestPoint(point) + return Circle.closestPoint(self, point) end if self:contains(point) then return point:clone() end - local circle = Circle.create(self.center, self.radius) - local circle_closest = circle:closestPoint(point) + local circle_closest = Circle.closestPoint(self, point) if self:contains(circle_closest) then return circle_closest end From a2791e2baa1c266a8477dc1c05553c0e1eb23f31 Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 20:43:26 +0800 Subject: [PATCH 36/71] refactor: add perimeter calculation methods for Circle, Rectangle, Sector, and Triangle classes --- .../thlib-scripts-v2/foundation/shape/Circle.lua | 6 ++++++ .../thlib-scripts-v2/foundation/shape/Rectangle.lua | 6 ++++++ .../thlib-scripts-v2/foundation/shape/Sector.lua | 11 +++++++++++ .../thlib-scripts-v2/foundation/shape/Triangle.lua | 9 +++++++++ 4 files changed, 32 insertions(+) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index 67f74bdf..1bfa769b 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -116,6 +116,12 @@ function Circle:area() return math.pi * self.radius * self.radius end +---计算圆的周长 +---@return number 圆的周长 +function Circle:getPerimeter() + return 2 * math.pi * self.radius +end + ---计算点到圆的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 圆上最近的点 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index 28bdaf89..1e6c9fc9 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -243,6 +243,12 @@ function Rectangle:area() return self.width * self.height end +---计算矩形的周长 +---@return number 矩形的周长 +function Rectangle:getPerimeter() + return 2 * (self.width + self.height) +end + ---计算点到矩形的最近点 ---@param point foundation.math.Vector2 ---@return foundation.math.Vector2 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index 891a9946..85c98254 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -107,6 +107,17 @@ function Sector:area() return 0.5 * self.radius * self.radius * self:getAngle() end +---计算扇形的周长 +---@return number 扇形的周长(弧长+两条半径) +function Sector:getPerimeter() + if math.abs(self.range) >= 1 then + return 2 * math.pi * self.radius + end + + local arcLength = self.radius * self:getAngle() + return arcLength + 2 * self.radius +end + ---检查点是否在扇形内(包括边界) ---@param point foundation.math.Vector2 ---@return boolean diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 8c636324..2a8713ca 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -290,6 +290,15 @@ function Triangle:getEdges() } end +---计算三角形的周长 +---@return number 三角形的周长 +function Triangle:getPerimeter() + local a = (self.point2 - self.point3):length() + local b = (self.point1 - self.point3):length() + local c = (self.point1 - self.point2):length() + return a + b + c +end + ---计算点到三角形的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@return foundation.math.Vector2 三角形上最近的点 From c5ffcecbc081247af304bb6d9ae6d266c85b2a0c Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 30 Apr 2025 21:48:30 +0800 Subject: [PATCH 37/71] feat: implement Polygon class with geometric operations and intersection checks --- .../foundation/shape/Polygon.lua | 568 ++++++++++++++++++ .../foundation/shape/ShapeIntersector.lua | 555 +++++++++++++++++ 2 files changed, 1123 insertions(+) create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua new file mode 100644 index 00000000..72e836de --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -0,0 +1,568 @@ +local ffi = require("ffi") + +local type = type +local ipairs = ipairs +local table = table +local math = math +local tostring = tostring +local string = string + +local Vector2 = require("foundation.math.Vector2") +local Segment = require("foundation.shape.Segment") +local Triangle = require("foundation.shape.Triangle") +local ShapeIntersector = require("foundation.shape.ShapeIntersector") + +ffi.cdef [[ +typedef struct { + size_t size; + foundation_math_Vector2* points; +} foundation_shape_Polygon; +]] + +---@class foundation.shape.Polygon +---@field size number 多边形顶点数量 +---@field points foundation.math.Vector2[] 多边形的顶点数组 +local Polygon = {} +Polygon.__index = Polygon +Polygon.__type = "foundation.shape.Polygon" + +---创建一个多边形 +---@param points foundation.math.Vector2[] 多边形的顶点数组,按顺序连线并首尾相接 +---@return foundation.shape.Polygon +function Polygon.create(points) + if not points or #points < 3 then + error("多边形至少需要3个点") + end + + local size = #points + local points_array = ffi.new("foundation_math_Vector2[?]", size) + + for i = 1, size do + points_array[i - 1] = Vector2.create(points[i].x, points[i].y) + end + + ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value + return ffi.new("foundation_shape_Polygon", size, points_array) +end + +---创建一个正多边形 +---@param center foundation.math.Vector2 多边形的中心 +---@param radius number 外接圆半径 +---@param numSides number 边数 +---@param startRad number 起始角度(弧度) +---@return foundation.shape.Polygon +function Polygon.createRegularRad(center, radius, numSides, startRad) + startRad = startRad or 0 + local points = {} + local angleStep = 2 * math.pi / numSides + + for i = 1, numSides do + local angle = startRad + (i - 1) * angleStep + local x = center.x + radius * math.cos(angle) + local y = center.y + radius * math.sin(angle) + points[i] = Vector2.create(x, y) + end + + return Polygon.create(points) +end + +---创建一个正多边形 +---@param center foundation.math.Vector2 多边形的中心 +---@param radius number 外接圆半径 +---@param numSides number 边数 +---@param startAngle number 起始角度(角度) +---@return foundation.shape.Polygon +function Polygon.createRegularDegree(center, radius, numSides, startAngle) + return Polygon.createRegularRad(center, radius, numSides, math.rad(startAngle)) +end + +---多边形相等比较 +---@param a foundation.shape.Polygon 第一个多边形 +---@param b foundation.shape.Polygon 第二个多边形 +---@return boolean +function Polygon.__eq(a, b) + if a.size ~= b.size then + return false + end + + for i = 0, a.size - 1 do + if a.points[i] ~= b.points[i] then + return false + end + end + + return true +end + +---多边形转字符串表示 +---@param self foundation.shape.Polygon +---@return string +function Polygon.__tostring(self) + local pointsStr = {} + for i = 0, self.size - 1 do + pointsStr[i + 1] = tostring(self.points[i]) + end + return string.format("Polygon(%s)", table.concat(pointsStr, ", ")) +end + +---获取多边形的边数 +---@return number +function Polygon:getEdgeCount() + return self.size +end + +---获取多边形的所有边(线段表示) +---@return foundation.shape.Segment[] +function Polygon:getEdges() + local edges = {} + + for i = 0, self.size - 1 do + local nextIdx = (i + 1) % self.size + edges[i + 1] = Segment.create(self.points[i], self.points[nextIdx]) + end + + return edges +end + +---获取多边形的顶点数组 +---@return foundation.math.Vector2[] +function Polygon:getVertices() + local vertices = {} + for i = 0, self.size - 1 do + vertices[i + 1] = self.points[i]:clone() + end + return vertices +end + +---获取多边形的中心点 +---@return foundation.math.Vector2 +function Polygon:getCenter() + local sumX, sumY = 0, 0 + + for i = 0, self.size - 1 do + sumX = sumX + self.points[i].x + sumY = sumY + self.points[i].y + end + + return Vector2.create(sumX / self.size, sumY / self.size) +end + +---计算多边形的面积 +---@return number +function Polygon:getArea() + local area = 0 + + for i = 0, self.size - 1 do + local j = (i + 1) % self.size + area = area + (self.points[i].x * self.points[j].y) - (self.points[j].x * self.points[i].y) + end + + return math.abs(area) / 2 +end + +---计算多边形的周长 +---@return number +function Polygon:getPerimeter() + local perimeter = 0 + + for i = 0, self.size - 1 do + local nextIdx = (i + 1) % self.size + perimeter = perimeter + (self.points[i] - self.points[nextIdx]):length() + end + + return perimeter +end + +---判断多边形是否为凸多边形 +---@return boolean +function Polygon:isConvex() + if self.size < 3 then + return false + end + + local sign = 0 + + for i = 0, self.size - 1 do + local j = (i + 1) % self.size + local k = (j + 1) % self.size + + local dx1 = self.points[j].x - self.points[i].x + local dy1 = self.points[j].y - self.points[i].y + local dx2 = self.points[k].x - self.points[j].x + local dy2 = self.points[k].y - self.points[j].y + + local cross = dx1 * dy2 - dy1 * dx2 + + if i == 0 then + sign = cross > 0 and 1 or (cross < 0 and -1 or 0) + elseif (cross > 0 and sign < 0) or (cross < 0 and sign > 0) then + return false + end + end + + return true +end + +---平移多边形(更改当前多边形) +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Polygon 平移后的多边形(自身引用) +function Polygon:move(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + + for i = 0, self.size - 1 do + self.points[i].x = self.points[i].x + moveX + self.points[i].y = self.points[i].y + moveY + end + + return self +end + +---获取当前多边形平移指定距离的副本 +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.Polygon 移动后的多边形副本 +function Polygon:moved(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + + local newPoints = {} + for i = 0, self.size - 1 do + newPoints[i + 1] = Vector2.create(self.points[i].x + moveX, self.points[i].y + moveY) + end + + return Polygon.create(newPoints) +end + +---将当前多边形旋转指定弧度(更改当前多边形) +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Polygon 旋转后的多边形(自身引用) +---@overload fun(self:foundation.shape.Polygon, rad:number): foundation.shape.Polygon 将多边形绕中心点旋转指定弧度 +function Polygon:rotate(rad, center) + center = center or self:getCenter() + local cosRad = math.cos(rad) + local sinRad = math.sin(rad) + + for i = 0, self.size - 1 do + local dx = self.points[i].x - center.x + local dy = self.points[i].y - center.y + self.points[i].x = dx * cosRad - dy * sinRad + center.x + self.points[i].y = dx * sinRad + dy * cosRad + center.y + end + + return self +end + +---将当前多边形旋转指定角度(更改当前多边形) +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Polygon 旋转后的多边形(自身引用) +---@overload fun(self:foundation.shape.Polygon, angle:number): foundation.shape.Polygon 将多边形绕中心点旋转指定角度 +function Polygon:degreeRotate(angle, center) + angle = math.rad(angle) + return self:rotate(angle, center) +end + +---获取当前多边形旋转指定弧度的副本 +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Polygon 旋转后的多边形副本 +---@overload fun(self:foundation.shape.Polygon, rad:number): foundation.shape.Polygon 将多边形绕中心点旋转指定弧度 +function Polygon:rotated(rad, center) + center = center or self:getCenter() + local cosRad = math.cos(rad) + local sinRad = math.sin(rad) + + local newPoints = {} + for i = 0, self.size - 1 do + local dx = self.points[i].x - center.x + local dy = self.points[i].y - center.y + newPoints[i + 1] = Vector2.create( + dx * cosRad - dy * sinRad + center.x, + dx * sinRad + dy * cosRad + center.y + ) + end + + return Polygon.create(newPoints) +end + +---获取当前多边形旋转指定角度的副本 +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心 +---@return foundation.shape.Polygon 旋转后的多边形副本 +---@overload fun(self:foundation.shape.Polygon, angle:number): foundation.shape.Polygon 将多边形绕中心点旋转指定角度 +function Polygon:degreeRotated(angle, center) + angle = math.rad(angle) + return self:rotated(angle, center) +end + +---将当前多边形缩放指定倍数(更改当前多边形) +---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2 缩放中心 +---@return foundation.shape.Polygon 缩放后的多边形(自身引用) +---@overload fun(self: foundation.shape.Polygon, scale: number): foundation.shape.Polygon 相对多边形中心点缩放指定倍数 +function Polygon:scale(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + center = center or self:getCenter() + + for i = 0, self.size - 1 do + self.points[i].x = (self.points[i].x - center.x) * scaleX + center.x + self.points[i].y = (self.points[i].y - center.y) * scaleY + center.y + end + + return self +end + +---获取当前多边形缩放指定倍数的副本 +---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2 缩放中心 +---@return foundation.shape.Polygon 缩放后的多边形副本 +---@overload fun(self: foundation.shape.Polygon, scale: number): foundation.shape.Polygon 相对多边形中心点缩放指定倍数 +function Polygon:scaled(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + center = center or self:getCenter() + + local newPoints = {} + for i = 0, self.size - 1 do + newPoints[i + 1] = Vector2.create( + (self.points[i].x - center.x) * scaleX + center.x, + (self.points[i].y - center.y) * scaleY + center.y + ) + end + + return Polygon.create(newPoints) +end + +---判断点是否在多边形内(射线法) +---@param point foundation.math.Vector2 要判断的点 +---@return boolean 如果点在多边形内或边上则返回true,否则返回false +function Polygon:contains(point) + return ShapeIntersector.polygonContainsPoint(self, point) +end + +---检查与其他形状的相交 +---@param other any +---@return boolean, foundation.math.Vector2[] | nil +function Polygon:intersects(other) + return ShapeIntersector.intersect(self, other) +end + +---仅检查是否与其他形状相交 +---@param other any +---@return boolean +function Polygon:hasIntersection(other) + return ShapeIntersector.hasIntersection(self, other) +end + +---判断多边形是否包含另一个多边形 +---@param other foundation.shape.Polygon 另一个多边形 +---@return boolean 如果当前多边形完全包含另一个多边形则返回true,否则返回false +function Polygon:containsPolygon(other) + for i = 0, other.size - 1 do + if not self:contains(other.points[i]) then + return false + end + end + + return true +end + +---计算点到多边形的最近点 +---@param point foundation.math.Vector2 要检查的点 +---@return foundation.math.Vector2 多边形上最近的点 +function Polygon:closestPoint(point) + if self:contains(point) then + return point:clone() + end + + local edges = self:getEdges() + local minDistance = math.huge + local closestPoint + + for _, edge in ipairs(edges) do + local edgeClosest = edge:closestPoint(point) + local distance = (point - edgeClosest):length() + + if distance < minDistance then + minDistance = distance + closestPoint = edgeClosest + end + end + + return closestPoint +end + +---计算点到多边形的距离 +---@param point foundation.math.Vector2 要检查的点 +---@return number 点到多边形的距离 +function Polygon:distanceToPoint(point) + if self:contains(point) then + return 0 + end + + local closestPoint = self:closestPoint(point) + return (point - closestPoint):length() +end + +---将点投影到多边形上(2D中与closest相同) +---@param point foundation.math.Vector2 要投影的点 +---@return foundation.math.Vector2 投影点 +function Polygon:projectPoint(point) + return self:closestPoint(point) +end + +--region 三角剖分的辅助函数 +--- 获取前一个点索引 +---@param points table 点数组 +---@param i number 当前索引 +---@return number 前一个点的索引 +local function getPrev(points, i) + return i == 1 and #points or i - 1 +end + +---获取后一个点索引 +---@param points table 点数组 +---@param i number 当前索引 +---@return number 后一个点的索引 +local function getNext(points, i) + return i == #points and 1 or i + 1 +end + +---检查点是否为凸点 +---@param points table 点数组 +---@param i number 当前索引 +---@param isClockwise boolean 是否顺时针 +---@return boolean 是否为凸点 +local function isConvex(points, i, isClockwise) + local prev = getPrev(points, i) + local next = getNext(points, i) + + local v1 = points[prev].point - points[i].point + local v2 = points[next].point - points[i].point + + local cross = v1:cross(v2) + return (isClockwise and cross < 0) or (not isClockwise and cross > 0) +end + +---检查点是否在三角形内部 +---@param p foundation.math.Vector2 要检查的点 +---@param a foundation.math.Vector2 三角形顶点1 +---@param b foundation.math.Vector2 三角形顶点2 +---@param c foundation.math.Vector2 三角形顶点3 +---@param isClockwise boolean 是否顺时针 +---@return boolean 点是否在三角形内部 +local function isPointInTriangle(p, a, b, c, isClockwise) + local ab = b - a + local bc = c - b + local ca = a - c + + local ap = p - a + local bp = p - b + local cp = p - c + + local cross1 = ab:cross(ap) + local cross2 = bc:cross(bp) + local cross3 = ca:cross(cp) + + if isClockwise then + return cross1 >= 0 and cross2 >= 0 and cross3 >= 0 + else + return cross1 <= 0 and cross2 <= 0 and cross3 <= 0 + end +end + +---检查耳朵是否有效 +---@param points table 点数组 +---@param i number 当前索引 +---@param isClockwise boolean 是否顺时针 +---@return boolean 是否为有效的耳朵 +local function isEar(points, i, isClockwise) + if not isConvex(points, i, isClockwise) then + return false + end + + local prev = getPrev(points, i) + local next = getNext(points, i) + + local a = points[prev].point + local b = points[i].point + local c = points[next].point + + for j = 1, #points do + if j ~= prev and j ~= i and j ~= next then + if isPointInTriangle(points[j].point, a, b, c, isClockwise) then + return false + end + end + end + + return true +end +--endregion + +---将多边形三角剖分(简单多边形的三角剖分,基于耳切法) +---@return foundation.shape.Triangle[] 三角形数组 +function Polygon:triangulate() + local points = {} + for i = 0, self.size - 1 do + points[i + 1] = { index = i, point = self.points[i]:clone() } + end + local area = 0 + for i = 0, self.size - 1 do + local j = (i + 1) % self.size + area = area + (points[i + 1].point.x * points[j + 1].point.y) - (points[j + 1].point.x * points[i + 1].point.y) + end + local isClockwise = area < 0 + local triangles = {} + local remainingPoints = self.size + while remainingPoints > 3 do + local foundEar = false + for i = 1, #points do + if points[i] and isEar(points, i, isClockwise) then + local prev, next = getPrev(points, i), getNext(points, i) + local a, b, c = points[prev].point, points[i].point, points[next].point + triangles[#triangles + 1] = Triangle.create(a, b, c) + points[i] = nil + local newPoints = {} + for j = 1, #points do + if points[j] then + newPoints[#newPoints + 1] = points[j] + end + end + points = newPoints + remainingPoints = remainingPoints - 1 + foundEar = true + break + end + end + if not foundEar then + break + end + end + if #points == 3 then + triangles[#triangles + 1] = Triangle.create(points[1].point, points[2].point, points[3].point) + end + return triangles +end + +ffi.metatype("foundation_shape_Polygon", Polygon) + +return Polygon \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua index 626a6157..bc12be0a 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua @@ -86,6 +86,55 @@ function ShapeIntersector.triangleContainsPoint(triangle, point) return (u >= 0) and (v >= 0) and (u + v <= 1) end +---检查点是否在多边形内 +---@param polygon foundation.shape.Polygon 多边形 +---@param point foundation.math.Vector2 点 +---@return boolean +function ShapeIntersector.polygonContainsPoint(polygon, point) + if polygon.size < 3 then + return false + end + + for i = 0, polygon.size - 1 do + local j = (i + 1) % polygon.size + local segment = Segment.create(polygon.points[i], polygon.points[j]) + if ShapeIntersector.segmentContainsPoint(segment, point) then + return true + end + end + + local inside = false + for i = 0, polygon.size - 1 do + local j = (i + 1) % polygon.size + + local pi = polygon.points[i] + local pj = polygon.points[j] + + if (pi.x - point.x) * (pi.x - point.x) + (pi.y - point.y) * (pi.y - point.y) < 1e-10 then + return true + end + + if ((pi.y > point.y) ~= (pj.y > point.y)) and + (point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x) then + inside = not inside + end + end + + return inside +end + +---判断点是否在线段上 +---@param segment foundation.shape.Segment 线段 +---@param point foundation.math.Vector2 点 +---@return boolean +function ShapeIntersector.segmentContainsPoint(segment, point) + local d1 = (point - segment.point1):length() + local d2 = (point - segment.point2):length() + local lineLen = (segment.point2 - segment.point1):length() + + return math.abs(d1 + d2 - lineLen) <= 1e-10 +end + ---整理相交点,去除重复点 ---@param points foundation.math.Vector2[] 原始点列表 ---@return foundation.math.Vector2[] 去重后的点列表 @@ -376,6 +425,433 @@ function ShapeIntersector.triangleHasIntersectionWithCircle(triangle, circle) return false end +---检查多边形与线段的相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param segment foundation.shape.Segment 线段 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.polygonToSegment(polygon, segment) + local points = {} + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge, segment) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, segment.point1) then + points[#points + 1] = segment.point1:clone() + end + + if segment.point1 ~= segment.point2 and ShapeIntersector.polygonContainsPoint(polygon, segment.point2) then + points[#points + 1] = segment.point2:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查多边形是否与线段相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param segment foundation.shape.Segment 线段 +---@return boolean +function ShapeIntersector.polygonHasIntersectionWithSegment(polygon, segment) + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then + return true + end + end + + return ShapeIntersector.polygonContainsPoint(polygon, segment.point1) or + ShapeIntersector.polygonContainsPoint(polygon, segment.point2) +end + +---检查多边形与线的相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param line foundation.shape.Line 线 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.polygonToLine(polygon, line) + local points = {} + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.lineToSegment(line, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, line.point) then + points[#points + 1] = line.point:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查多边形是否与线相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param line foundation.shape.Line 线 +---@return boolean +function ShapeIntersector.polygonHasIntersectionWithLine(polygon, line) + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then + return true + end + end + + return ShapeIntersector.polygonContainsPoint(polygon, line.point) +end + +---检查多边形与射线的相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param ray foundation.shape.Ray 射线 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.polygonToRay(polygon, ray) + local points = {} + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, ray.point) then + points[#points + 1] = ray.point:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查多边形是否与射线相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param ray foundation.shape.Ray 射线 +---@return boolean +function ShapeIntersector.polygonHasIntersectionWithRay(polygon, ray) + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then + return true + end + end + + return ShapeIntersector.polygonContainsPoint(polygon, ray.point) +end + +---检查多边形与圆的相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param circle foundation.shape.Circle 圆 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.polygonToCircle(polygon, circle) + local points = {} + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + points[#points + 1] = vertex + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, circle.center) then + points[#points + 1] = circle.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查多边形是否与圆相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param circle foundation.shape.Circle 圆 +---@return boolean +function ShapeIntersector.polygonHasIntersectionWithCircle(polygon, circle) + if ShapeIntersector.polygonContainsPoint(polygon, circle.center) then + return true + end + + local edges = polygon:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then + return true + end + end + + local vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + return true + end + end + + return false +end + +---检查多边形与三角形的相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param triangle foundation.shape.Triangle 三角形 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.polygonToTriangle(polygon, triangle) + local points = {} + local edges1 = polygon:getEdges() + local edges2 = triangle:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon, vertex) then + points[#points + 1] = vertex + end + end + + vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle, vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查多边形是否与三角形相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param triangle foundation.shape.Triangle 三角形 +---@return boolean +function ShapeIntersector.polygonHasIntersectionWithTriangle(polygon, triangle) + local edges1 = polygon:getEdges() + local edges2 = triangle:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then + return true + end + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon, vertex) then + return true + end + end + + vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle, vertex) then + return true + end + end + + return false +end + +---检查多边形与矩形的相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param rectangle foundation.shape.Rectangle 矩形 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.polygonToRectangle(polygon, rectangle) + local points = {} + local edges1 = polygon:getEdges() + local edges2 = rectangle:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon, vertex) then + points[#points + 1] = vertex + end + end + + vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.rectangleContainsPoint(rectangle, vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查多边形是否与矩形相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param rectangle foundation.shape.Rectangle 矩形 +---@return boolean +function ShapeIntersector.polygonHasIntersectionWithRectangle(polygon, rectangle) + local edges1 = polygon:getEdges() + local edges2 = rectangle:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then + return true + end + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon, vertex) then + return true + end + end + + vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.rectangleContainsPoint(rectangle, vertex) then + return true + end + end + + return false +end + +---检查多边形与多边形的相交 +---@param polygon1 foundation.shape.Polygon 第一个多边形 +---@param polygon2 foundation.shape.Polygon 第二个多边形 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.polygonToPolygon(polygon1, polygon2) + local points = {} + local edges1 = polygon1:getEdges() + local edges2 = polygon2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + end + + local vertices = polygon2:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon1, vertex) then + points[#points + 1] = vertex + end + end + + vertices = polygon1:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon2, vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查多边形是否与多边形相交 +---@param polygon1 foundation.shape.Polygon 第一个多边形 +---@param polygon2 foundation.shape.Polygon 第二个多边形 +---@return boolean +function ShapeIntersector.polygonHasIntersectionWithPolygon(polygon1, polygon2) + local edges1 = polygon1:getEdges() + local edges2 = polygon2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then + return true + end + end + end + + local vertices = polygon2:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon1, vertex) then + return true + end + end + + vertices = polygon1:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon2, vertex) then + return true + end + end + + return false +end + ---检查两条线段的相交 ---@param segment1 foundation.shape.Segment 第一条线段 ---@param segment2 foundation.shape.Segment 第二条线段 @@ -1788,8 +2264,77 @@ function ShapeIntersector.sectorHasIntersectionWithSector(sector1, sector2) return false end +---检查多边形与扇形的相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param sector foundation.shape.Sector 扇形 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.polygonToSector(polygon, sector) + local points = {} + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + points[#points + 1] = vertex + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, sector.center) then + points[#points + 1] = sector.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points +end + +---仅检查多边形是否与扇形相交 +---@param polygon foundation.shape.Polygon 多边形 +---@param sector foundation.shape.Sector 扇形 +---@return boolean +function ShapeIntersector.polygonHasIntersectionWithSector(polygon, sector) + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then + return true + end + end + + local vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + return true + end + end + + return ShapeIntersector.polygonContainsPoint(polygon, sector.center) +end + ---@type table> local intersectionMap = { + ["foundation.shape.Polygon"] = { + ["foundation.shape.Segment"] = ShapeIntersector.polygonToSegment, + ["foundation.shape.Line"] = ShapeIntersector.polygonToLine, + ["foundation.shape.Ray"] = ShapeIntersector.polygonToRay, + ["foundation.shape.Triangle"] = ShapeIntersector.polygonToTriangle, + ["foundation.shape.Circle"] = ShapeIntersector.polygonToCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.polygonToRectangle, + ["foundation.shape.Sector"] = ShapeIntersector.polygonToSector, + ["foundation.shape.Polygon"] = ShapeIntersector.polygonToPolygon, + }, ["foundation.shape.Sector"] = { ["foundation.shape.Segment"] = ShapeIntersector.sectorToSegment, ["foundation.shape.Line"] = ShapeIntersector.sectorToLine, @@ -1857,6 +2402,16 @@ end ---@type table> local hasIntersectionMap = { + ["foundation.shape.Polygon"] = { + ["foundation.shape.Segment"] = ShapeIntersector.polygonHasIntersectionWithSegment, + ["foundation.shape.Line"] = ShapeIntersector.polygonHasIntersectionWithLine, + ["foundation.shape.Ray"] = ShapeIntersector.polygonHasIntersectionWithRay, + ["foundation.shape.Triangle"] = ShapeIntersector.polygonHasIntersectionWithTriangle, + ["foundation.shape.Circle"] = ShapeIntersector.polygonHasIntersectionWithCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.polygonHasIntersectionWithRectangle, + ["foundation.shape.Sector"] = ShapeIntersector.polygonHasIntersectionWithSector, + ["foundation.shape.Polygon"] = ShapeIntersector.polygonHasIntersectionWithPolygon, + }, ["foundation.shape.Sector"] = { ["foundation.shape.Segment"] = ShapeIntersector.sectorHasIntersectionWithSegment, ["foundation.shape.Line"] = ShapeIntersector.sectorHasIntersectionWithLine, From 42d68a1542e1d62ab620cd20c62045cb41307f22 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 01:03:31 +0800 Subject: [PATCH 38/71] refactor: change size type from size_t to int in Polygon struct --- game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index 72e836de..9e58a1fd 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -14,7 +14,7 @@ local ShapeIntersector = require("foundation.shape.ShapeIntersector") ffi.cdef [[ typedef struct { - size_t size; + int size; foundation_math_Vector2* points; } foundation_shape_Polygon; ]] @@ -139,6 +139,7 @@ end function Polygon:getCenter() local sumX, sumY = 0, 0 + print("size", self.size) for i = 0, self.size - 1 do sumX = sumX + self.points[i].x sumY = sumY + self.points[i].y From 4265b4aeb380886cb624bcb66706f910bedfdca3 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 01:07:23 +0800 Subject: [PATCH 39/71] feat: add containsPoint method to Polygon class for point containment checks --- .../foundation/shape/Polygon.lua | 23 ++++++++++++++++++- .../foundation/shape/ShapeIntersector.lua | 1 + 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index 9e58a1fd..63619b8d 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -139,7 +139,6 @@ end function Polygon:getCenter() local sumX, sumY = 0, 0 - print("size", self.size) for i = 0, self.size - 1 do sumX = sumX + self.points[i].x sumY = sumY + self.points[i].y @@ -430,6 +429,28 @@ function Polygon:projectPoint(point) return self:closestPoint(point) end +---检查点是否在多边形上 +---@param point foundation.math.Vector2 要检查的点 +---@param tolerance number|nil 容差,默认为1e-10 +---@return boolean 点是否在多边形上 +---@overload fun(self: foundation.shape.Polygon, point: foundation.math.Vector2): boolean +function Polygon:containsPoint(point, tolerance) + tolerance = tolerance or 1e-10 + local dist = self:distanceToPoint(point) + if dist > tolerance then + return false + end + + for i = 0, self.size - 1 do + local edge = Segment.create(self.points[i], self.points[(i + 1) % self.size]) + if edge:containsPoint(point, tolerance) then + return true + end + end + + return false +end + --region 三角剖分的辅助函数 --- 获取前一个点索引 ---@param points table 点数组 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua index bc12be0a..d2718b19 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua @@ -95,6 +95,7 @@ function ShapeIntersector.polygonContainsPoint(polygon, point) return false end + Segment = Segment or require("foundation.shape.Segment") for i = 0, polygon.size - 1 do local j = (i + 1) % polygon.size local segment = Segment.create(polygon.points[i], polygon.points[j]) From f8af2e49e153a9177f37462d6548d8c08f08ec9c Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 01:59:05 +0800 Subject: [PATCH 40/71] feat: add createFromTable methods for Vector2, Vector3, and Vector4 classes --- .../thlib-scripts-v2/foundation/math/Vector2.lua | 13 +++++++++++++ .../thlib-scripts-v2/foundation/math/Vector3.lua | 13 +++++++++++++ .../thlib-scripts-v2/foundation/math/Vector4.lua | 13 +++++++++++++ 3 files changed, 39 insertions(+) diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua index c8a09900..64d3046b 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua @@ -36,6 +36,19 @@ function Vector2.create(x, y) return ffi.new("foundation_math_Vector2", x or 0, y or 0) end +---通过特定结构的对象创建一个新的二维向量 +---@param tbl table|foundation.math.Vector2 表或向量 +---@return foundation.math.Vector2 新创建的向量 +function Vector2.createFromTable(tbl) + if tbl.x and tbl.y then + return Vector2.create(tbl.x, tbl.y) + end + if tbl[1] and tbl[2] then + return Vector2.create(tbl[1], tbl[2]) + end + error("Invalid table format for Vector2 creation") +end + ---通过给定的弧度和长度创建一个新的二维向量 ---@param rad number 弧度 ---@param length number 向量长度 diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua index 92a0eb11..d2d619dd 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua @@ -39,6 +39,19 @@ function Vector3.create(x, y, z) return ffi.new("foundation_math_Vector3", x or 0, y or 0, z or 0) end +---通过特定结构的对象创建一个新的三维向量 +---@param tbl table|foundation.math.Vector3 表或向量 +---@return foundation.math.Vector3 新创建的向量 +function Vector3.createFromTable(tbl) + if tbl.x and tbl.y and tbl.z then + return Vector3.create(tbl.x, tbl.y, tbl.z) + end + if tbl[1] and tbl[2] and tbl[3] then + return Vector3.create(tbl[1], tbl[2], tbl[3]) + end + error("Invalid table format for Vector2 creation") +end + ---向量加法运算符重载 ---@param a foundation.math.Vector3|number 第一个操作数 ---@param b foundation.math.Vector3|number 第二个操作数 diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua index f009e6f7..06a441bb 100644 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua +++ b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua @@ -42,6 +42,19 @@ function Vector4.create(x, y, z, w) return ffi.new("foundation_math_Vector4", x or 0, y or 0, z or 0, w or 0) end +---通过特定结构的对象创建一个新的四维向量 +---@param tbl table|foundation.math.Vector4 表或向量 +---@return foundation.math.Vector4 新创建的向量 +function Vector4.createFromTable(tbl) + if tbl.x and tbl.y and tbl.z and tbl.w then + return Vector4.create(tbl.x, tbl.y, tbl.z, tbl.w) + end + if tbl[1] and tbl[2] and tbl[3] and tbl[4] then + return Vector4.create(tbl[1], tbl[2], tbl[3], tbl[4]) + end + error("Invalid table format for Vector4 creation") +end + ---向量加法运算符重载 ---@param a foundation.math.Vector4|number 第一个操作数 ---@param b foundation.math.Vector4|number 第二个操作数 From 0d508c9dda4ef4d810d43cdccbbff28345c144ad Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 01:59:14 +0800 Subject: [PATCH 41/71] feat: add garbage collection for Polygon creation to manage memory --- game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index 63619b8d..2e06e8a5 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -42,7 +42,10 @@ function Polygon.create(points) end ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return ffi.new("foundation_shape_Polygon", size, points_array) + return ffi.gc(ffi.new("foundation_shape_Polygon", size, points_array), function() + points_array = nil + print("released polygon") + end) end ---创建一个正多边形 From 656ca90d2018a6058be70ed6acc401175c64a956 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 02:00:00 +0800 Subject: [PATCH 42/71] refactor: remove debug print statement from garbage collection in Polygon creation --- game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index 2e06e8a5..ec6a59a1 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -44,7 +44,6 @@ function Polygon.create(points) ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value return ffi.gc(ffi.new("foundation_shape_Polygon", size, points_array), function() points_array = nil - print("released polygon") end) end From 416802903f870d51acf861418adbcc886e71e202 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 13:11:32 +0800 Subject: [PATCH 43/71] feat: implement custom __index and __newindex methods for Polygon class --- .../foundation/shape/Polygon.lua | 61 +++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index ec6a59a1..67bdd005 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -6,6 +6,8 @@ local table = table local math = math local tostring = tostring local string = string +local error = error +local setmetatable = setmetatable local Vector2 = require("foundation.math.Vector2") local Segment = require("foundation.shape.Segment") @@ -23,28 +25,65 @@ typedef struct { ---@field size number 多边形顶点数量 ---@field points foundation.math.Vector2[] 多边形的顶点数组 local Polygon = {} -Polygon.__index = Polygon Polygon.__type = "foundation.shape.Polygon" +---@param self foundation.shape.Polygon +---@param key string +---@return any +function Polygon.__index(self, key) + if key == "size" then + return self.__data.size + elseif key == "points" then + return self.__data.points + end + return Polygon[key] +end + +---@param t foundation.math.Vector2[] +---@return number, foundation.math.Vector2[] +local function buildNewVector2Array(t) + local size = #t + local points_array = ffi.new("foundation_math_Vector2[?]", size) + + for i = 1, size do + points_array[i - 1] = Vector2.create(t[i].x, t[i].y) + end + + return size, points_array +end + +---@param self foundation.shape.Polygon +---@param key any +---@param value any +function Polygon.__newindex(self, key, value) + if key == "size" then + error("cannot modify size directly") + elseif key == "points" then + local size, points_array = buildNewVector2Array(value) + self.__data.size = size + self.__data.points = points_array + elseif key == "__data" then + error("cannot modify __data directly") + end +end + ---创建一个多边形 ---@param points foundation.math.Vector2[] 多边形的顶点数组,按顺序连线并首尾相接 ---@return foundation.shape.Polygon function Polygon.create(points) if not points or #points < 3 then - error("多边形至少需要3个点") + error("Polygon must have at least 3 points") end - local size = #points - local points_array = ffi.new("foundation_math_Vector2[?]", size) - - for i = 1, size do - points_array[i - 1] = Vector2.create(points[i].x, points[i].y) - end + local size, points_array = buildNewVector2Array(points) + local polygon = ffi.new("foundation_shape_Polygon", size, points_array) + local result = { + __data = polygon, + __point_ref = points_array, + } ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return ffi.gc(ffi.new("foundation_shape_Polygon", size, points_array), function() - points_array = nil - end) + return setmetatable(result, Polygon) end ---创建一个正多边形 From 05434b049ef2819c892a98ce6335eb64103bcf6d Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 13:22:48 +0800 Subject: [PATCH 44/71] feat: enhance convexity check in Polygon class for improved validity verification --- .../foundation/shape/Polygon.lua | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index 67bdd005..b4aed218 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -222,6 +222,7 @@ function Polygon:isConvex() end local sign = 0 + local allCollinear = true for i = 0, self.size - 1 do local j = (i + 1) % self.size @@ -233,14 +234,26 @@ function Polygon:isConvex() local dy2 = self.points[k].y - self.points[j].y local cross = dx1 * dy2 - dy1 * dx2 + local absCross = math.abs(cross) + + if absCross > 1e-10 then + allCollinear = false + end if i == 0 then - sign = cross > 0 and 1 or (cross < 0 and -1 or 0) - elseif (cross > 0 and sign < 0) or (cross < 0 and sign > 0) then + if absCross > 1e-10 then + sign = cross > 0 and 1 or -1 + end + elseif (cross > 1e-10 and sign < 0) or (cross < -1e-10 and sign > 0) or (sign == 0 and absCross > 1e-10) then return false end end + -- 如果所有点共线,不是合法的凸多边形 + if allCollinear then + return false + end + return true end @@ -596,14 +609,23 @@ function Polygon:triangulate() local isClockwise = area < 0 local triangles = {} local remainingPoints = self.size + + local isConvexCache = {} + local isEarCache = {} + for i = 1, #points do + isConvexCache[i] = isConvex(points, i, isClockwise) + isEarCache[i] = isConvexCache[i] and isEar(points, i, isClockwise) + end + while remainingPoints > 3 do local foundEar = false for i = 1, #points do - if points[i] and isEar(points, i, isClockwise) then + if points[i] and isEarCache[i] then local prev, next = getPrev(points, i), getNext(points, i) local a, b, c = points[prev].point, points[i].point, points[next].point triangles[#triangles + 1] = Triangle.create(a, b, c) points[i] = nil + local newPoints = {} for j = 1, #points do if points[j] then @@ -612,6 +634,17 @@ function Polygon:triangulate() end points = newPoints remainingPoints = remainingPoints - 1 + + if points[prev] then + isConvexCache[prev] = isConvex(points, prev, isClockwise) + isEarCache[prev] = isConvexCache[prev] and isEar(points, prev, isClockwise) + end + + if points[next] then + isConvexCache[next] = isConvex(points, next, isClockwise) + isEarCache[next] = isConvexCache[next] and isEar(points, next, isClockwise) + end + foundEar = true break end From 71187be9da86f59fa85345a668639cecd0419fc6 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 15:57:36 +0800 Subject: [PATCH 45/71] feat: add rotation and incenter calculations for Circle and Rectangle classes --- .../foundation/shape/Circle.lua | 28 +++++++++++++++++++ .../foundation/shape/Rectangle.lua | 25 +++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index 1bfa769b..8b1a26bf 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -81,6 +81,34 @@ function Circle:moved(v) return Circle.create(Vector2.create(self.center.x + moveX, self.center.y + moveY), self.radius) end +---旋转圆(没有实际效果) +---@param _ number 旋转弧度 +---@return foundation.shape.Circle 自身引用 +function Circle:rotate(_) + return self +end + +---旋转圆(没有实际效果) +---@param _ number 旋转角度 +---@return foundation.shape.Circle 自身引用 +function Circle:degreeRotate(_) + return self +end + +---获取旋转后的圆副本(没有实际效果) +---@param _ number 旋转弧度 +---@return foundation.shape.Circle +function Circle:rotated(_) + return Circle.create(self.center:clone(), self.radius) +end + +---获取旋转后的圆副本(没有实际效果) +---@param _ number 旋转角度 +---@return foundation.shape.Circle +function Circle:degreeRotated(_) + return Circle.create(self.center:clone(), self.radius) +end + ---缩放圆(修改当前圆) ---@param scale number 缩放比例 ---@return foundation.shape.Circle 自身引用 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index 1e6c9fc9..74fdc9b1 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -249,6 +249,31 @@ function Rectangle:getPerimeter() return 2 * (self.width + self.height) end +---计算矩形的内心 +---@return foundation.math.Vector2 矩形的内心 +function Rectangle:incenter() + return self.center +end + +---计算矩形的内切圆半径 +---@return number 矩形的内切圆半径 +function Rectangle:inradius() + local min = math.min(self.width, self.height) / 2 + return min +end + +---计算矩形的外心 +---@return foundation.math.Vector2 矩形的外心 +function Rectangle:circumcenter() + return self.center +end + +---计算矩形的外接圆半径 +---@return number 矩形的外接圆半径 +function Rectangle:circumradius() + return math.sqrt((self.width / 2) ^ 2 + (self.height / 2) ^ 2) +end + ---计算点到矩形的最近点 ---@param point foundation.math.Vector2 ---@return foundation.math.Vector2 From 20a50449a9b4118bb605d13c97ae773c8cd3ba2b Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 15:59:15 +0800 Subject: [PATCH 46/71] feat: add laboratory for geometry --- laboratory/geometry/.gitignore | 2 + laboratory/geometry/config.json | 11 + laboratory/geometry/main.lua | 771 ++++++++++++++++++++++++++++++++ laboratory/geometry/start.bat | 4 + laboratory/geometry/white.png | Bin 0 -> 4316 bytes 5 files changed, 788 insertions(+) create mode 100644 laboratory/geometry/.gitignore create mode 100644 laboratory/geometry/config.json create mode 100644 laboratory/geometry/main.lua create mode 100644 laboratory/geometry/start.bat create mode 100644 laboratory/geometry/white.png diff --git a/laboratory/geometry/.gitignore b/laboratory/geometry/.gitignore new file mode 100644 index 00000000..084f6144 --- /dev/null +++ b/laboratory/geometry/.gitignore @@ -0,0 +1,2 @@ +engine.log +userdata \ No newline at end of file diff --git a/laboratory/geometry/config.json b/laboratory/geometry/config.json new file mode 100644 index 00000000..ea00826e --- /dev/null +++ b/laboratory/geometry/config.json @@ -0,0 +1,11 @@ +{ + "log_file_enable": true, + "log_file_path": "engine.log", + "persistent_log_file_enable": false, + "persistent_log_file_directory": "userdata/logs/", + "persistent_log_file_max_count": 10, + "engine_cache_directory": "", + "single_application_instance": true, + "application_instance_id": "0689df8f-7886-4d89-a335-5fba6e855d05", + "debug_track_window_focus": false +} \ No newline at end of file diff --git a/laboratory/geometry/main.lua b/laboratory/geometry/main.lua new file mode 100644 index 00000000..9d38c98f --- /dev/null +++ b/laboratory/geometry/main.lua @@ -0,0 +1,771 @@ +local lstg = require("lstg") +lstg.FileManager.AddSearchPath("../../game/packages/thlib-scripts/") +lstg.FileManager.AddSearchPath("../../game/packages/thlib-scripts-v2/") +lstg.FileManager.AddSearchPath("../../game/packages/thlib-resources/") + +local Keyboard = lstg.Input.Keyboard +local Mouse = lstg.Input.Mouse + +local window = { + width = 640, + height = 480, + view_width = 1280, + view_height = 960, +} +function window:applyWindowSetting() + lstg.ChangeVideoMode(self.view_width, self.view_height, true, false) +end +function window:applyCameraSetting() + lstg.SetViewport(0, self.view_width, 0, self.view_height) + lstg.SetScissorRect(0, self.view_width, 0, self.view_height) + lstg.SetOrtho(0, self.width, 0, self.height) + lstg.SetFog() + lstg.SetImageScale(1.0) + lstg.SetZBufferEnable(0) +end + +local function loadSprite(name, path, mipmap) + lstg.LoadTexture(name, path, mipmap) + local width, height = lstg.GetTextureSize(name) + lstg.LoadImage(name, name, 0, 0, width, height) +end +loadSprite("white", "white.png", false) + +--region Geometry +local Vector2 = require("foundation.math.Vector2") + +local Line = require("foundation.shape.Line") +local Ray = require("foundation.shape.Ray") +local Segment = require("foundation.shape.Segment") + +local Triangle = require("foundation.shape.Triangle") +local Rectangle = require("foundation.shape.Rectangle") +local Polygon = require("foundation.shape.Polygon") +local Circle = require("foundation.shape.Circle") +local Sector = require("foundation.shape.Sector") +--endregion + +--region Math Method +local function _A(x1, y1, x2, y2) + return math.deg(math.atan2(y2 - y1, x2 - x1)) +end +local function Angle(a, b, c, d) + if a and b and c and d then + return _A(a, b, c, d) + elseif a and b and c then + if type(a) == "table" then + return _A(a.x, a.y, b, c) + elseif type(c) == "table" then + return _A(a, b, c.x, c.y) + else + error("Error parameters") + end + elseif a and b then + return _A(a.x, a.y, b.x, b.y) + else + error("Error parameters") + end +end + +local function _D(x1, y1, x2, y2) + return math.sqrt((x2 - x1) ^ 2 + (y2 - y1) ^ 2) +end +local function Dist(a, b, c, d) + if a and b and c and d then + return _D(a, b, c, d) + elseif a and b and c then + if type(a) == "table" then + return _D(a.x, a.y, b, c) + elseif type(c) == "table" then + return _D(a, b, c.x, c.y) + else + error("Error parameters") + end + elseif a and b then + return _D(a.x, a.y, b.x, b.y) + else + error("Error parameters") + end +end +--endregion + +--region Render Method +local function setColor(a, r, g, b) + lstg.SetImageState("white", "", lstg.Color(a, r, g, b)) +end + +---@param p {x:number, y:number} +---@param size number +local function renderPoint(p, size) + lstg.Render("white", p.x, p.y, 0, size / 8) +end + +---@param p1 {x:number, y:number} +---@param p2 {x:number, y:number} +---@param width number +local function renderLine(p1, p2, width) + local cx = (p1.x + p2.x) / 2 + local cy = (p1.y + p2.y) / 2 + local length = Dist(p1, p2) + local rot = Angle(p1, p2) + local hscale = length / 16 + local vscale = width / 16 + lstg.Render("white", cx, cy, rot, hscale, vscale) +end + +---@param p {x:number, y:number} +---@param radius1 number +---@param radius2 number +---@param n number +local function renderCircle(p, radius1, radius2, n) + local angle = 360 / n + for i = 1, n do + local angle1 = (i - 1) * angle + local angle2 = i * angle + local x1 = p.x + radius1 * lstg.cos(angle1) + local y1 = p.y + radius1 * lstg.sin(angle1) + local x2 = p.x + radius1 * lstg.cos(angle2) + local y2 = p.y + radius1 * lstg.sin(angle2) + local x3 = p.x + radius2 * lstg.cos(angle2) + local y3 = p.y + radius2 * lstg.sin(angle2) + local x4 = p.x + radius2 * lstg.cos(angle1) + local y4 = p.y + radius2 * lstg.sin(angle1) + lstg.Render4V("white", x1, y1, 0.5, + x2, y2, 0.5, + x3, y3, 0.5, + x4, y4, 0.5) + end +end + +---@param p {x:number, y:number} +---@param radius1 number +---@param radius2 number +---@param angleFrom number +---@param angleTo number +---@param n number +local function renderSector(p, radius1, radius2, angleFrom, angleTo, n) + local angle = (angleTo - angleFrom) / n + for i = 1, n do + local angle1 = angleFrom + (i - 1) * angle + local angle2 = angleFrom + i * angle + local x1 = p.x + radius1 * lstg.cos(angle1) + local y1 = p.y + radius1 * lstg.sin(angle1) + local x2 = p.x + radius1 * lstg.cos(angle2) + local y2 = p.y + radius1 * lstg.sin(angle2) + local x3 = p.x + radius2 * lstg.cos(angle2) + local y3 = p.y + radius2 * lstg.sin(angle2) + local x4 = p.x + radius2 * lstg.cos(angle1) + local y4 = p.y + radius2 * lstg.sin(angle1) + lstg.Render4V("white", x1, y1, 0.5, + x2, y2, 0.5, + x3, y3, 0.5, + x4, y4, 0.5) + end +end +--endregion + +--region Player Method +local player = {} +function player:pos() + local mouseX, mouseY = Mouse.GetPosition() + mouseX = mouseX / window.view_width * window.width + mouseY = mouseY / window.view_height * window.height + return Vector2.create(mouseX, mouseY) +end +--endregion + +--region Object Method +local object = { + pool = {}, + collision_result = {}, +} +function object:insert(obj) + table.insert(self.pool, obj) +end +function object:clear() + self.pool = {} +end +function object:enum() + return ipairs(self.pool) +end +function object:updateCollisionCheck() + local result = {} + for i = 1, #self.pool do + local obj1 = self.pool[i] + for j = i + 1, #self.pool do + local obj2 = self.pool[j] + local intersection, points = obj1:intersects(obj2) + if intersection then + for _, point in ipairs(points) do + table.insert(result, point) + end + end + end + end + self.collision_result = result +end +function object:update() + self:updateCollisionCheck() +end +function object:draw() + local player_pos = player:pos() + self:renderPlayerPoint(player_pos) + for _, obj in self:enum() do + if obj.__type == "foundation.shape.Line" then + self:renderLine(obj, player_pos) + elseif obj.__type == "foundation.shape.Ray" then + self:renderRay(obj, player_pos) + elseif obj.__type == "foundation.shape.Segment" then + self:renderSegment(obj, player_pos) + elseif obj.__type == "foundation.shape.Triangle" then + self:renderTriangle(obj, player_pos) + elseif obj.__type == "foundation.shape.Circle" then + self:renderCircle(obj, player_pos) + elseif obj.__type == "foundation.shape.Rectangle" then + self:renderRectangle(obj, player_pos) + elseif obj.__type == "foundation.shape.Sector" then + self:renderSector(obj, player_pos) + elseif obj.__type == "foundation.shape.Polygon" then + self:renderPolygon(obj, player_pos) + end + self:renderClosestPoint(obj, player_pos) + end + setColor(192, 0, 255, 255) + for _, point in ipairs(self.collision_result) do + renderPoint(point, 4) + end +end + +---@param player_pos {x:number, y:number} +function object:renderPlayerPoint(player_pos) + setColor(255, 255, 255, 255) + renderPoint(player_pos, 4) +end + +---@param obj {__type:string, closestPoint:function} +---@param player_pos {x:number, y:number} +function object:renderClosestPoint(obj, player_pos) + local nearestPoint = obj:closestPoint(player_pos) + setColor(127, 127, 127, 127) + renderLine(player_pos, nearestPoint, 2) + setColor(192, 255, 200, 127) + renderPoint(nearestPoint, 4) +end + +---@param line foundation.shape.Line +---@param player_pos {x:number, y:number} +function object:renderLine(line, player_pos) + if line:containsPoint(player_pos, 1) then + setColor(192, 0, 0, 255) + else + setColor(192, 255, 255, 255) + end + renderLine(line:getPoint(-1000), line:getPoint(1000), 2) + setColor(127, 255, 0, 0) + renderPoint(line.point, 4) + setColor(127, 255, 63, 63) + renderLine(line.point, line:getPoint(50), 2) +end + +---@param ray foundation.shape.Ray +---@param player_pos {x:number, y:number} +function object:renderRay(ray, player_pos) + if ray:containsPoint(player_pos, 1) then + setColor(192, 0, 0, 255) + else + setColor(192, 255, 255, 255) + end + renderLine(ray.point, ray:getPoint(1000), 2) + setColor(127, 255, 0, 0) + renderPoint(ray.point, 4) + setColor(127, 255, 63, 63) + renderLine(ray.point, ray:getPoint(50), 2) +end + +---@param segment foundation.shape.Segment +---@param player_pos {x:number, y:number} +function object:renderSegment(segment, player_pos) + if segment:containsPoint(player_pos, 1) then + setColor(192, 0, 0, 255) + else + setColor(192, 255, 255, 255) + end + renderLine(segment.point1, segment.point2, 2) + setColor(127, 127, 0, 0) + renderPoint(segment.point1, 4) + renderPoint(segment.point2, 4) + setColor(127, 255, 0, 0) + renderPoint(segment:midpoint(), 4) + setColor(127, 255, 63, 63) + renderLine(segment:midpoint(), segment.point1, 2) +end + +---@param triangle foundation.shape.Triangle +---@param player_pos {x:number, y:number} +function object:renderTriangle(triangle, player_pos) + setColor(63, 127, 255, 192) + local incenter = triangle:incenter() + local inradius = triangle:inradius() + local circumcenter = triangle:circumcenter() + local circumradius = triangle:circumradius() + setColor(63, 127, 255, 192) + renderCircle(incenter, inradius - 1, inradius + 1, 64) + setColor(63, 127, 192, 255) + renderCircle(circumcenter, circumradius - 1, circumradius + 1, 64) + if triangle:containsPoint(player_pos, 1) then + setColor(192, 0, 0, 255) + elseif triangle:contains(player_pos) then + setColor(192, 127, 255, 0) + else + setColor(192, 255, 255, 255) + end + renderLine(triangle.point1, triangle.point2, 2) + renderLine(triangle.point2, triangle.point3, 2) + renderLine(triangle.point3, triangle.point1, 2) + setColor(127, 127, 0, 0) + renderPoint(triangle.point1, 4) + renderPoint(triangle.point2, 4) + renderPoint(triangle.point3, 4) + setColor(127, 255, 0, 0) + renderPoint(triangle:centroid(), 4) + setColor(127, 255, 63, 63) + renderLine(triangle:centroid(), triangle.point1, 2) +end + +---@param rectangle foundation.shape.Rectangle +---@param player_pos {x:number, y:number} +function object:renderRectangle(rectangle, player_pos) + local p = rectangle.center + local w = rectangle.width / 2 + local h = rectangle.height / 2 + local a = rectangle.direction:degreeAngle() + local inradius = rectangle:inradius() + local circumradius = rectangle:circumradius() + setColor(63, 127, 255, 192) + renderCircle(p, inradius - 1, inradius + 1, 64) + setColor(63, 127, 192, 255) + renderCircle(p, circumradius - 1, circumradius + 1, 64) + if rectangle:containsPoint(player_pos, 1) then + setColor(192, 0, 0, 255) + elseif rectangle:contains(player_pos) then + setColor(192, 127, 255, 0) + else + setColor(192, 255, 255, 255) + end + renderLine(p + Vector2.create(-w, -h):degreeRotated(a), p + Vector2.create(w, -h):degreeRotated(a), 2) + renderLine(p + Vector2.create(w, -h):degreeRotated(a), p + Vector2.create(w, h):degreeRotated(a), 2) + renderLine(p + Vector2.create(w, h):degreeRotated(a), p + Vector2.create(-w, h):degreeRotated(a), 2) + renderLine(p + Vector2.create(-w, h):degreeRotated(a), p + Vector2.create(-w, -h):degreeRotated(a), 2) + setColor(127, 127, 0, 0) + renderPoint(p + Vector2.create(-w, -h):degreeRotated(a), 4) + renderPoint(p + Vector2.create(w, -h):degreeRotated(a), 4) + renderPoint(p + Vector2.create(w, h):degreeRotated(a), 4) + renderPoint(p + Vector2.create(-w, h):degreeRotated(a), 4) + setColor(127, 255, 0, 0) + renderPoint(p, 4) + setColor(127, 255, 63, 63) + renderLine(p, p + Vector2.create(w, 0):degreeRotated(a), 2) +end + +---@param polygon foundation.shape.Polygon +---@param player_pos {x:number, y:number} +function object:renderPolygon(polygon, player_pos) + local vertices = polygon:getVertices() + if polygon:containsPoint(player_pos, 1) then + setColor(192, 0, 0, 255) + elseif polygon:contains(player_pos) then + setColor(192, 127, 255, 0) + else + setColor(192, 255, 255, 255) + end + for i = 1, #vertices do + local p1 = vertices[i] + local p2 = vertices[i % #vertices + 1] + renderLine(p1, p2, 2) + end + setColor(127, 127, 0, 0) + for _, vertex in ipairs(vertices) do + renderPoint(vertex, 4) + end + setColor(127, 255, 0, 0) + renderPoint(polygon:getCenter(), 4) + setColor(127, 255, 63, 63) + renderLine(polygon:getCenter(), vertices[1], 2) +end + +---@param circle foundation.shape.Circle +---@param player_pos {x:number, y:number} +function object:renderCircle(circle, player_pos) + local p = circle.center + local r1 = circle.radius + 1 + local r2 = circle.radius - 1 + if circle:containsPoint(player_pos, 1) then + setColor(192, 0, 0, 255) + elseif circle:contains(player_pos) then + setColor(192, 127, 255, 0) + else + setColor(192, 255, 255, 255) + end + renderCircle(p, r1, r2, 64) + setColor(127, 255, 0, 0) + renderPoint(p, 4) + setColor(127, 255, 63, 63) + renderLine(p, p + Vector2.create(0, r1), 2) +end + +---@param sector foundation.shape.Sector +---@param player_pos {x:number, y:number} +function object:renderSector(sector, player_pos) + local p = sector.center + local r1 = sector.radius - 1 + local r2 = sector.radius + 1 + if sector:containsPoint(player_pos, 1) then + setColor(192, 0, 0, 255) + elseif sector:contains(player_pos) then + setColor(192, 127, 255, 0) + else + setColor(192, 255, 255, 255) + end + local a1 = sector.direction:degreeAngle() + local a2 = a1 + sector.range * 360 + renderSector(p, r1, r2, a1, a2, 64) + renderLine(p, p + sector.direction * r1, 2) + renderLine(p, p + sector.direction:degreeRotated(sector.range * 360) * r1, 2) + setColor(127, 255, 0, 0) + renderPoint(p, 4) + setColor(127, 255, 63, 63) + renderLine(p, p + sector.direction * r1, 2) +end +--endregion + +--region Scene +local Scene1 = {} +Scene1.name = "Line" +function Scene1:create() + self.timer = -1 + object:insert( + Line.create(Vector2.create(0, 0), Vector2.createFromAngle(45)) + :move(Vector2.create(window.width / 4 * 3, window.height / 2)) + ) + object:insert( + Line.create(Vector2.create(0, 0), Vector2.createFromAngle(45)) + :move(Vector2.create(window.width / 4, window.height / 2)) + ) +end +function Scene1:destroy() + object:clear() +end +function Scene1:update() + self.timer = self.timer + 1 + for i, obj in object:enum() do + obj:degreeRotate(-1 + 2 * (i % 2)) + end + object:updateCollisionCheck() +end +function Scene1:draw() + object:draw() +end + +local Scene2 = {} +Scene2.name = "Ray" +function Scene2:create() + self.timer = -1 + object:insert( + Ray.create(Vector2.create(0, 0), Vector2.createFromAngle(135)) + :move(Vector2.create(window.width / 4 * 3, window.height / 2)) + ) + object:insert( + Ray.create(Vector2.create(0, 0), Vector2.createFromAngle(45)) + :move(Vector2.create(window.width / 4, window.height / 2)) + ) +end +function Scene2:destroy() + object:clear() +end +function Scene2:update() + self.timer = self.timer + 1 + for i, obj in object:enum() do + obj:degreeRotate(-1 + 2 * (i % 2)) + end + object:updateCollisionCheck() +end +function Scene2:draw() + object:draw() +end + +local Scene3 = {} +Scene3.name = "Segment" +function Scene3:create() + self.timer = -1 + object:insert( + Segment.create(Vector2.create(0, 0), Vector2.create(400, 0)) + :move(Vector2.create(0, window.height / 2)) + ) + object:insert( + Segment.create(Vector2.create(0, 0), Vector2.create(0, 400)) + :move(Vector2.create(window.width / 2, window.height / 4)) + ) + object:insert( + Segment.create(Vector2.create(0, 0), Vector2.create(200, 200)) + :move(Vector2.create(window.width / 3, window.height / 2)) + ) +end +function Scene3:destroy() + object:clear() +end +function Scene3:update() + self.timer = self.timer + 1 + for i, obj in object:enum() do + local rotate = (-1 + 2 * (i % 2)) * math.floor((i + 1) / 2) + local center + if i % 3 == 0 then + center = obj.point1 + elseif i % 3 == 1 then + center = obj.point2 + else + center = obj:midpoint() + end + obj:degreeRotate(rotate, center) + end + object:updateCollisionCheck() +end +function Scene3:draw() + object:draw() +end + +local Scene4 = {} +Scene4.name = "Triangle" +function Scene4:create() + self.timer = -1 + object:insert( + Triangle.create(Vector2.create(0, 0), Vector2.create(200, 0), Vector2.create(100, 300)) + :move(Vector2.create(window.width / 4, window.height / 2)) + ) + object:insert( + Triangle.create(Vector2.create(0, 0), Vector2.create(200, 0), Vector2.create(400, -300)) + :move(Vector2.create(0, window.height / 2)) + ) + object:insert( + Triangle.create(Vector2.create(0, 0), Vector2.create(0, 400), Vector2.create(300, 0)) + :move(Vector2.create(window.width / 2, 0)) + ) +end +function Scene4:destroy() + object:clear() +end +function Scene4:update() + self.timer = self.timer + 1 + for i, obj in object:enum() do + obj:degreeRotate(-1 + 2 * (i % 2)) + end + object:updateCollisionCheck() +end +function Scene4:draw() + object:draw() +end + +local Scene5 = {} +Scene5.name = "Rectangle" +function Scene5:create() + self.timer = -1 + object:insert( + Rectangle.create(Vector2.create(0, 0), 200, 200) + :move(Vector2.create(window.width / 5 * 2, window.height / 2)) + ) + object:insert( + Rectangle.create(Vector2.create(0, 0), 400, 100) + :move(Vector2.create(window.width / 5 * 3, window.height / 2)) + ) +end +function Scene5:destroy() + object:clear() +end +function Scene5:update() + self.timer = self.timer + 1 + for i, obj in object:enum() do + obj:degreeRotate(-1 + 2 * (i % 2)) + end + object:updateCollisionCheck() +end +function Scene5:draw() + object:draw() +end + +local Scene6 = {} +Scene6.name = "Polygon and Sector and Circle" +function Scene6:create() + self.timer = -1 + object:insert( + Polygon.create({ + Vector2.create(0, -100), + Vector2.create(-100, 0), + Vector2.create(0, 100), + Vector2.create(100, 0), + Vector2.create(0, -100), + Vector2.create(-100, -100), + Vector2.create(-100, 100), + Vector2.create(100, 100), + Vector2.create(100, -100), + }) :move(Vector2.create(window.width / 3, window.height / 7 * 5)) + ) + object:insert( + Sector.create(Vector2.create(0, 0), 100, Vector2.createFromAngle(45), 0.75) + :move(Vector2.create(window.width / 3, window.height / 7 * 3)) + ) + object:insert( + Circle.create(Vector2.create(0, 0), 150) + :move(Vector2.create(window.width / 3 * 2, window.height / 2)) + ) +end +function Scene6:destroy() + object:clear() +end +function Scene6:update() + self.timer = self.timer + 1 + for i, obj in object:enum() do + obj:degreeRotate(-1 + 2 * (i % 2)) + end + object:updateCollisionCheck() +end +function Scene6:draw() + object:draw() +end + +local Scene7 = {} +Scene7.name = "Crazy" +function Scene7:create() + self.timer = -1 + local rand = lstg.Rand() + rand:Seed(os.time()) + --每种形状都随机创建一个 + object:insert( + Line.create(Vector2.create(0, 0), Vector2.createFromAngle(rand:Float(0, 360))) + :move(Vector2.create(window.width * rand:Float(0.25, 0.75), window.height * rand:Float(0.25, 0.75))) + ) + object:insert( + Ray.create(Vector2.create(0, 0), Vector2.createFromAngle(rand:Float(0, 360))) + :move(Vector2.create(window.width * rand:Float(0.25, 0.75), window.height * rand:Float(0.25, 0.75))) + ) + object:insert( + Segment.create(Vector2.create(0, 0), Vector2.createFromAngle(rand:Float(0, 360)) * rand:Float(100, 300)) + :move(Vector2.create(window.width * rand:Float(0.25, 0.75), window.height * rand:Float(0.25, 0.75))) + ) + object:insert( + Triangle.create(Vector2.create(0, 0), + Vector2.createFromAngle(rand:Float(0, 360)) * rand:Float(100, 300), + Vector2.createFromAngle(rand:Float(0, 360)) * rand:Float(100, 300)) + :move(Vector2.create(window.width * rand:Float(0.25, 0.75), window.height * rand:Float(0.25, 0.75))) + ) + object:insert( + Rectangle.create(Vector2.create(0, 0), rand:Float(100, 300), rand:Float(100, 300)) + :move(Vector2.create(window.width * rand:Float(0.25, 0.75), window.height * rand:Float(0.25, 0.75))) + ) + object:insert( + object:insert( + Polygon.create({ + Vector2.create(0, -100), + Vector2.create(-100, 0), + Vector2.create(0, 100), + Vector2.create(100, 0), + Vector2.create(0, -100), + Vector2.create(-100, -100), + Vector2.create(-100, 100), + Vector2.create(100, 100), + Vector2.create(100, -100), + }) :move(Vector2.create(window.width * rand:Float(0.25, 0.75), window.height * rand:Float(0.25, 0.75))) + ) + ) + object:insert( + Circle.create(Vector2.create(0, 0), rand:Float(100, 300)) + :move(Vector2.create(window.width * rand:Float(0.25, 0.75), window.height * rand:Float(0.25, 0.75))) + ) + object:insert( + Sector.create(Vector2.create(0, 0), rand:Float(100, 300), Vector2.createFromAngle(rand:Float(0, 360)), rand:Float(0.1, 0.9)) + :move(Vector2.create(window.width * rand:Float(0.25, 0.75), window.height * rand:Float(0.25, 0.75))) + ) +end +function Scene7:destroy() + object:clear() +end +function Scene7:update() + self.timer = self.timer + 1 + for i, obj in object:enum() do + obj:degreeRotate(-1 + 2 * (i % 2)) + end + object:updateCollisionCheck() +end +function Scene7:draw() + object:draw() +end +--endregion + +---@generic T +---@param class T +---@return T +local function makeInstance(class) + local instance = {} + setmetatable(instance, { __index = class }) + return instance +end + +local scenes = { + Scene1, + Scene2, + Scene3, + Scene4, + Scene5, + Scene6, + Scene7, +} +local current_scene_index = 1 +local current_scene = makeInstance(scenes[current_scene_index]) +local any_key_down = false + +function GameInit() + window:applyWindowSetting() + lstg.LoadTTF("Sans", "assets/font/SourceHanSansCN-Bold.otf", 0, 24) + current_scene:create() +end + +function GameExit() + current_scene:destroy() +end + +function FrameFunc() + local change = 0 + if Keyboard.GetKeyState(Keyboard.Left) then + if not any_key_down then + any_key_down = true + if current_scene_index > 1 then + change = -1 + end + end + elseif Keyboard.GetKeyState(Keyboard.Right) then + if not any_key_down then + any_key_down = true + if current_scene_index < #scenes then + change = 1 + end + end + elseif any_key_down then + any_key_down = false + end + if change ~= 0 then + current_scene:destroy() + current_scene_index = current_scene_index + change + current_scene = makeInstance(scenes[current_scene_index]) + current_scene:create() + end + current_scene:update() + return false +end + +function RenderFunc() + lstg.BeginScene() + window:applyCameraSetting() + lstg.RenderClear(lstg.Color(255, 0, 0, 0)) + current_scene:draw() + local edge = 4 + lstg.RenderTTF("Sans", string.format("%s\n< %d/%d >", current_scene.name, current_scene_index, #scenes), edge, window.width - edge, edge, window.height - edge, 1 + 8, lstg.Color(255, 255, 255, 64), 2) + lstg.EndScene() +end \ No newline at end of file diff --git a/laboratory/geometry/start.bat b/laboratory/geometry/start.bat new file mode 100644 index 00000000..74d655e6 --- /dev/null +++ b/laboratory/geometry/start.bat @@ -0,0 +1,4 @@ +@setlocal + @cd %~dp0 + @start ..\..\game\LuaSTGSub.exe +@endlocal diff --git a/laboratory/geometry/white.png b/laboratory/geometry/white.png new file mode 100644 index 0000000000000000000000000000000000000000..4ae1d8e570e7bd4ddf1a7203ecbfe7799abd1072 GIT binary patch literal 4316 zcmcInc|4SB`+taJNs<#PnZ}Z6c4n9iWsJ2cSyCZpd1ee|ANxsZLs`O6N}-Ta5{fK! zY$-%2QIsVuLPDZaevh`_`TTzGd*1WUJD+F0@9+J+zSnhM_chNQx5tuP`v4$6F`6huks?&fQT%eD{o z%_SQf9Y8_S)y7rvYme#;m@mzJxd>bLVdqkp=Pq?hS#|f+^$h5tn6=2FHg*OjNENps z?#}W;(^pDCD?ILz;Lj2fgV7q}%Y+g56UjOQ-S9^aznvFSX1A|OtEaJ~_Q7LDrIn5B z>sWC8aq%o(;d>|Ii3%2kKUEV*h`nMcFf@M{F^evg(7D&=|~|g zIi+T>c<9OyM|1PD6~OYb*`3&dC+!qU3BL4t3BCl4nLFn+7dXjx=;@|}X}&LGtK^R@ z>GuAZ(#=u-ChYtIZ=7>`%#%FAd5oj-48W~T3{ z++^+9+B;>b_4V+rkc^m_8@)~B;ODyNLl>pXZ}2X^e<)d#k)Be`6uz$CyLw>ns+rDE z4Oy|O>F>&0)s`YpVUeh94|%K8jQY+`zV>{!*Kfd5>2vb(SG#l_zO3CCXCmdiz;)kj zkDQYld?Q`?NKj_)=a*4S4{vj2sjHh|_Xo`EtbPiyz;;f^E)giQV=!Vq5 zRQNrq%|e6&mL&OUQNo9Sw# zy8B`Onlrbb)=>vujys2NE43_u`}GCY>d%MAy=J6lWg^mH)K)!H6)X9MgNTD7E5&}* zeyM)FM7xKuC#nacJqK8HvvJdL1!3$=ye=6FD@&r=S+yhEm$rwtd$l`aPhKVm!EKXe z2fYSuCMXj+6CUzM4#pfTmGPh_Tc=o8)KOzzC56~??b!`(N&b3Gx?3HT?ON>mb0pcG zIjPPobMmsc*vHv5+q+~p=Pby6kbN{eExW*BYl9CcVfKJs*idW~h-{2?Q8}W2JEi7a zK+Z!KH(fcNpG4Bx;6U~z8uND<=BEbj$8Qct z<-%XBzmwR#t`i>Y@;0tAsWREz_SVK*ZV`50Z%TVrd#$0+60D+X$do_MMbeMa;|Dg~ zc07V}aMcUfJMJ9WE}s+Lc(o`}bajIGqPB5Qqu4OdFx^no@HazsHenI{%eiu0({qsBh9+l_Db+UZda>hE}c0^=lU0Qaw*ejRJc5vU_=@FdID}K9x zRFs~z`~2=R&$xSHEy$LXxqV+3FX*ajgf(O~`c$dlui*z*tVXXgwp#gk#dz$SSesb; z`j+~~_09DRedA@8$)3r|$*<1uI^T9ad~-O}^5o8wIj1_(o)S)ArZM+T#;|i8HC6k; zZJ;n*J~ZY|64jA-qVlTowZR>$uT?TAg%hPucpcoSwkP$c_)qC1ZWg$V?ZU;?WMZW? zDX~T+z8>n`)RJ6o@8g}_@*`WizR>vr8T4MJ<#pHKb!^U;cA?y2x%1@w+Sk}mMK{2x z<8j2i4Op$=RoWXk#KJQxPV1kUI+J`R!OSukBkr%bR#qvzE`Hh<_H40mQeq?+4n84V zA})~Z9X#8g__}QbGt7BW)-qYC{-mkXKcO?a^a!D;qAmQ$W-K-aCh+T54hj<`m~d8A^P7tcibjf6;bUpu&Z785b?irVG*p z>%(^jxqaw9{qA(~r5yLk?bG>~9mV42!CeX0y$i_sjQru<1-BxaJ{2x*S?NcxTKSw1 z#BcBUqm)B0e)TfTW08k*$?P%z;~q(${ru}5T`^CoYz}?iZC+~f%5iXV(zc|VU+_5c zN%hmVKgA6#N6us@bjfxqG%GYISgI8sxv~fR>h{rHu|hRyO5HQK^kz31Uyrb|w=J1RPOj~;y*eEnY0WtJuFPr;mTnRh7r(DvJJk1R>CPa(S@-SRsc zJ2qVEUFD;FBeZqM$1%=AvVE6-zfj;AGCNc%>{ZTq zM{3)XBrJNdVRS67+kh)O>h$^4hsW{R@og;^TB=-si_YZmu{74Uykq{E`7__zN-1Uk`|ujFBs~3oEA?&^@u70KbB074wIXr z7Dm+`9*eDujeSPzJz1U>Hhuf%NciM5!E&ReL5RWB)se)Bta9zCy#ZR+SCeor@sFlW z2gkn%AI%hJ6h(YOCr%%n%?@zp76}{aZ{A3gS- zK0xg$PAp!Ann5j(K!jai4P$ zgSiWHg~Q2XxpImeMa4g*rVmbjUYeP45&E8lG3=bF01&1R0Q(LCz$d6_-)jH}L<7JZ z9{?bx0)V>Ubon-Os6$?0w^IxNip%CtDd5y;4FHgx;JEIP?4WKYGWc91jmf8j$Plgo zLIZ%gWr%>r@CPL@I>_emNbs?WKjAPAlLU7+p`xh*GU&^(3lo7ZVOv}oVg3v=Cfw2j zW*$O>2)Lkx1`FW^@WjLr68sx45qh3)M!{jTV4%@x z7{LrlGbP|KbhGs^EEDGG*eDG2*@i-6QCKVjLLkJUJP9oX!4vEJWUvOs3=u~l;qZB|c}5zYA1EQg zA*tVe;0k`x^29&l1Vs!LLKC1cNc6l*0-$S|FUudy}5(HWKq4!@Bi(NwnAj%mO z^8-Z;kP-y)BsxEhF&V$c3Ias|-#le9P+$Pag Date: Thu, 1 May 2025 16:05:47 +0800 Subject: [PATCH 47/71] feat: add rendering for projected points in object visualization --- laboratory/geometry/main.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/laboratory/geometry/main.lua b/laboratory/geometry/main.lua index 9d38c98f..8b98e09f 100644 --- a/laboratory/geometry/main.lua +++ b/laboratory/geometry/main.lua @@ -228,6 +228,7 @@ function object:draw() elseif obj.__type == "foundation.shape.Polygon" then self:renderPolygon(obj, player_pos) end + self:renderProjectPoint(obj, player_pos) self:renderClosestPoint(obj, player_pos) end setColor(192, 0, 255, 255) @@ -252,6 +253,16 @@ function object:renderClosestPoint(obj, player_pos) renderPoint(nearestPoint, 4) end +---@param obj {__type:string, projectPoint:function} +---@param player_pos {x:number, y:number} +function object:renderProjectPoint(obj, player_pos) + local projectPoint = obj:projectPoint(player_pos) + setColor(127, 127, 127, 127) + renderLine(player_pos, projectPoint, 2) + setColor(192, 200, 127, 255) + renderPoint(projectPoint, 4) +end + ---@param line foundation.shape.Line ---@param player_pos {x:number, y:number} function object:renderLine(line, player_pos) From b9e14e86ed34c844914fbbe249fe826527794cd8 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 16:29:06 +0800 Subject: [PATCH 48/71] feat: update closestPoint method in Circle class to return input point if within radius --- game/packages/thlib-scripts-v2/foundation/shape/Circle.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index 8b1a26bf..1e16812a 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -156,8 +156,8 @@ end function Circle:closestPoint(point) local dir = point - self.center local dist = dir:length() - if dist <= 1e-10 then - return Vector2.create(self.center.x + self.radius, self.center.y) + if dist <= self.radius then + return point:clone() end local normalized_dir = dir / dist return self.center + normalized_dir * self.radius From 815decf203f6eaeca2b311b540ba07bb6d944df7 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 16:40:58 +0800 Subject: [PATCH 49/71] feat: add boundary parameter to closestPoint methods in geometric shapes --- .../foundation/shape/Circle.lua | 12 ++++++++--- .../foundation/shape/Polygon.lua | 8 +++++--- .../foundation/shape/Rectangle.lua | 8 +++++--- .../foundation/shape/Sector.lua | 20 +++++++++++-------- .../foundation/shape/Triangle.lua | 8 +++++--- 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index 1e16812a..0fb7d74a 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -152,11 +152,17 @@ end ---计算点到圆的最近点 ---@param point foundation.math.Vector2 要检查的点 +---@param boundary boolean 是否限制在边界内,默认为false ---@return foundation.math.Vector2 圆上最近的点 -function Circle:closestPoint(point) +---@overload fun(self:foundation.shape.Circle, point:foundation.math.Vector2): foundation.math.Vector2 +function Circle:closestPoint(point, boundary) local dir = point - self.center local dist = dir:length() - if dist <= self.radius then + if boundary then + if dist <= 1e-10 then + return Vector2.create(self.center.x + self.radius, self.center.y) + end + elseif dist <= self.radius then return point:clone() end local normalized_dir = dir / dist @@ -175,7 +181,7 @@ end ---@param point foundation.math.Vector2 要投影的点 ---@return foundation.math.Vector2 投影点 function Circle:projectPoint(point) - return self:closestPoint(point) + return self:closestPoint(point, true) end ---检查点是否在圆上 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index b4aed218..9b390cb3 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -441,9 +441,11 @@ end ---计算点到多边形的最近点 ---@param point foundation.math.Vector2 要检查的点 +---@param boundary boolean 是否限制在边界内,默认为false ---@return foundation.math.Vector2 多边形上最近的点 -function Polygon:closestPoint(point) - if self:contains(point) then +---@overload fun(self: foundation.shape.Polygon, point: foundation.math.Vector2): foundation.math.Vector2 +function Polygon:closestPoint(point, boundary) + if not boundary and self:contains(point) then return point:clone() end @@ -480,7 +482,7 @@ end ---@param point foundation.math.Vector2 要投影的点 ---@return foundation.math.Vector2 投影点 function Polygon:projectPoint(point) - return self:closestPoint(point) + return self:closestPoint(point, true) end ---检查点是否在多边形上 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index 74fdc9b1..099b530f 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -276,9 +276,11 @@ end ---计算点到矩形的最近点 ---@param point foundation.math.Vector2 +---@param boundary boolean 是否限制在边界内,默认为false ---@return foundation.math.Vector2 -function Rectangle:closestPoint(point) - if self:contains(point) then +---@overload fun(self: foundation.shape.Rectangle, point: foundation.math.Vector2): foundation.math.Vector2 +function Rectangle:closestPoint(point, boundary) + if not boundary and self:contains(point) then return point:clone() end local edges = self:getEdges() @@ -317,7 +319,7 @@ end ---@param point foundation.math.Vector2 ---@return foundation.math.Vector2 function Rectangle:projectPoint(point) - return self:closestPoint(point) + return self:closestPoint(point, true) end ---检查点是否在矩形边界上 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index 85c98254..1d68f579 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -220,16 +220,19 @@ end ---计算点到扇形的最近点 ---@param point foundation.math.Vector2 +---@param boundary boolean 是否限制在边界内,默认为false ---@return foundation.math.Vector2 -function Sector:closestPoint(point) +---@overload fun(self: foundation.shape.Sector, point: foundation.math.Vector2): foundation.math.Vector2 +function Sector:closestPoint(point, boundary) if math.abs(self.range) >= 1 then - return Circle.closestPoint(self, point) + return Circle.closestPoint(self, point, boundary) end - if self:contains(point) then + if not boundary and self:contains(point) then return point:clone() end - local circle_closest = Circle.closestPoint(self, point) - if self:contains(circle_closest) then + local circle_closest = Circle.closestPoint(self, point, boundary) + local contains = self:contains(circle_closest) + if not boundary and contains then return circle_closest end local startDir = self.direction @@ -239,8 +242,9 @@ function Sector:closestPoint(point) local start_segment = Segment.create(self.center, start_point) local end_segment = Segment.create(self.center, end_point) local candidates = { - start_segment:closestPoint(point), - end_segment:closestPoint(point) + start_segment:closestPoint(point, boundary), + end_segment:closestPoint(point, boundary), + boundary and contains and circle_closest or nil, } local min_distance = math.huge local closest_point = candidates[1] @@ -268,7 +272,7 @@ end ---@param point foundation.math.Vector2 ---@return foundation.math.Vector2 function Sector:projectPoint(point) - return self:closestPoint(point) + return self:closestPoint(point, true) end ---检查点是否在扇形边界上 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 2a8713ca..c819e0cd 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -301,9 +301,11 @@ end ---计算点到三角形的最近点 ---@param point foundation.math.Vector2 要检查的点 +---@param boundary boolean 是否限制在边界内,默认为false ---@return foundation.math.Vector2 三角形上最近的点 -function Triangle:closestPoint(point) - if self:contains(point) then +---@overload fun(self: foundation.shape.Triangle, point: foundation.math.Vector2): foundation.math.Vector2 +function Triangle:closestPoint(point, boundary) + if not boundary and self:contains(point) then return point:clone() end @@ -354,7 +356,7 @@ end ---@param point foundation.math.Vector2 要投影的点 ---@return foundation.math.Vector2 投影点 function Triangle:projectPoint(point) - return self:closestPoint(point) + return self:closestPoint(point, true) end ---检查点是否在三角形上 From a2be53f7769b77e3ab04c60b80702d526578f946 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 17:10:03 +0800 Subject: [PATCH 50/71] feat: add centroid and getCenter methods for Circle, Polygon, Rectangle, Sector, and Triangle classes --- .../foundation/shape/Circle.lua | 12 ++++++ .../foundation/shape/Polygon.lua | 37 ++++++++++++++++--- .../foundation/shape/Rectangle.lua | 10 ++++- .../foundation/shape/Sector.lua | 19 ++++++++++ .../foundation/shape/Triangle.lua | 10 +++++ laboratory/geometry/main.lua | 4 +- 6 files changed, 82 insertions(+), 10 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index 0fb7d74a..d80314b1 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -150,6 +150,18 @@ function Circle:getPerimeter() return 2 * math.pi * self.radius end +---计算圆的中心 +---@return foundation.math.Vector2 圆心位置 +function Circle:getCenter() + return self.center:clone() +end + +---计算圆的重心 +---@return foundation.math.Vector2 圆心位置 +function Circle:centroid() + return self.center:clone() +end + ---计算点到圆的最近点 ---@param point foundation.math.Vector2 要检查的点 ---@param boundary boolean 是否限制在边界内,默认为false diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index 9b390cb3..2cbeeeab 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -175,9 +175,9 @@ function Polygon:getVertices() return vertices end ----获取多边形的中心点 +---获取多边形的重心 ---@return foundation.math.Vector2 -function Polygon:getCenter() +function Polygon:centroid() local sumX, sumY = 0, 0 for i = 0, self.size - 1 do @@ -188,6 +188,31 @@ function Polygon:getCenter() return Vector2.create(sumX / self.size, sumY / self.size) end +---计算多边形的中心 +---@return foundation.math.Vector2 +function Polygon:getCenter() + local minX, minY = math.huge, math.huge + local maxX, maxY = -math.huge, -math.huge + + for i = 0, self.size - 1 do + local point = self.points[i] + if point.x < minX then + minX = point.x + end + if point.y < minY then + minY = point.y + end + if point.x > maxX then + maxX = point.x + end + if point.y > maxY then + maxY = point.y + end + end + + return Vector2.create((minX + maxX) / 2, (minY + maxY) / 2) +end + ---计算多边形的面积 ---@return number function Polygon:getArea() @@ -301,7 +326,7 @@ end ---@return foundation.shape.Polygon 旋转后的多边形(自身引用) ---@overload fun(self:foundation.shape.Polygon, rad:number): foundation.shape.Polygon 将多边形绕中心点旋转指定弧度 function Polygon:rotate(rad, center) - center = center or self:getCenter() + center = center or self:centroid() local cosRad = math.cos(rad) local sinRad = math.sin(rad) @@ -331,7 +356,7 @@ end ---@return foundation.shape.Polygon 旋转后的多边形副本 ---@overload fun(self:foundation.shape.Polygon, rad:number): foundation.shape.Polygon 将多边形绕中心点旋转指定弧度 function Polygon:rotated(rad, center) - center = center or self:getCenter() + center = center or self:centroid() local cosRad = math.cos(rad) local sinRad = math.sin(rad) @@ -370,7 +395,7 @@ function Polygon:scale(scale, center) else scaleX, scaleY = scale.x, scale.y end - center = center or self:getCenter() + center = center or self:centroid() for i = 0, self.size - 1 do self.points[i].x = (self.points[i].x - center.x) * scaleX + center.x @@ -392,7 +417,7 @@ function Polygon:scaled(scale, center) else scaleX, scaleY = scale.x, scale.y end - center = center or self:getCenter() + center = center or self:centroid() local newPoints = {} for i = 0, self.size - 1 do diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index 099b530f..acff6464 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -249,10 +249,16 @@ function Rectangle:getPerimeter() return 2 * (self.width + self.height) end +---计算矩形的中心 +---@return foundation.math.Vector2 矩形的中心 +function Rectangle:getCenter() + return self.center:clone() +end + ---计算矩形的内心 ---@return foundation.math.Vector2 矩形的内心 function Rectangle:incenter() - return self.center + return self.center:clone() end ---计算矩形的内切圆半径 @@ -265,7 +271,7 @@ end ---计算矩形的外心 ---@return foundation.math.Vector2 矩形的外心 function Rectangle:circumcenter() - return self.center + return self.center:clone() end ---计算矩形的外接圆半径 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index 1d68f579..9912d615 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -118,6 +118,25 @@ function Sector:getPerimeter() return arcLength + 2 * self.radius end +---计算扇形的中心点 +---@return foundation.math.Vector2 +function Sector:getCenter() + return self.center:clone() +end + +---获取扇形的重心 +---@return foundation.math.Vector2 +function Sector:centroid() + local angle = self:getAngle() + if math.abs(angle) >= 1 then + return self.center:clone() + end + + local x = self.center.x + (self.radius / 3) * math.cos(self.direction:angle() + angle / 2) + local y = self.center.y + (self.radius / 3) * math.sin(self.direction:angle() + angle / 2) + return Vector2.create(x, y) +end + ---检查点是否在扇形内(包括边界) ---@param point foundation.math.Vector2 ---@return boolean diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index c819e0cd..9fd70fd4 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -63,6 +63,16 @@ function Triangle:centroid() return (self.point1 + self.point2 + self.point3) / 3 end +---计算三角形的中心 +---@return foundation.math.Vector2 三角形的中心 +function Triangle:getCenter() + local minX = math.min(self.point1.x, self.point2.x, self.point3.x) + local maxX = math.max(self.point1.x, self.point2.x, self.point3.x) + local minY = math.min(self.point1.y, self.point2.y, self.point3.y) + local maxY = math.max(self.point1.y, self.point2.y, self.point3.y) + return Vector2.create((minX + maxX) / 2, (minY + maxY) / 2) +end + ---计算三角形的外接圆半径 ---@return number 三角形的外接圆半径 function Triangle:circumradius() diff --git a/laboratory/geometry/main.lua b/laboratory/geometry/main.lua index 8b98e09f..1e482839 100644 --- a/laboratory/geometry/main.lua +++ b/laboratory/geometry/main.lua @@ -399,9 +399,9 @@ function object:renderPolygon(polygon, player_pos) renderPoint(vertex, 4) end setColor(127, 255, 0, 0) - renderPoint(polygon:getCenter(), 4) + renderPoint(polygon:centroid(), 4) setColor(127, 255, 63, 63) - renderLine(polygon:getCenter(), vertices[1], 2) + renderLine(polygon:centroid(), vertices[1], 2) end ---@param circle foundation.shape.Circle From 4d2d4d9792a4e27b6c93f0edc2e9acb9d3049375 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 18:05:56 +0800 Subject: [PATCH 51/71] feat: add getCenter and getBoundingBoxSize methods for Circle, Line, Polygon, Ray, Rectangle, Sector, Segment, and Triangle classes --- .../foundation/shape/Circle.lua | 6 + .../foundation/shape/Line.lua | 17 +++ .../foundation/shape/Polygon.lua | 25 ++++ .../thlib-scripts-v2/foundation/shape/Ray.lua | 30 +++++ .../foundation/shape/Rectangle.lua | 15 +++ .../foundation/shape/Sector.lua | 107 ++++++++++++++++-- .../foundation/shape/Segment.lua | 16 +++ .../foundation/shape/Triangle.lua | 10 ++ laboratory/geometry/main.lua | 63 +++++++++-- 9 files changed, 272 insertions(+), 17 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index d80314b1..c7a45eb3 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -156,6 +156,12 @@ function Circle:getCenter() return self.center:clone() end +---计算圆的包围盒宽高 +---@return number, number +function Circle:getBoundingBoxSize() + return self.radius * 2, self.radius * 2 +end + ---计算圆的重心 ---@return foundation.math.Vector2 圆心位置 function Circle:centroid() diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index 72e57168..107fc15d 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -95,6 +95,23 @@ function Line:getPoint(length) return self.point + self.direction * length end +---计算直线的中心 +---@return foundation.math.Vector2 +function Line:getCenter() + return self.point +end + +---计算直线的包围盒宽高 +---@return number, number +function Line:getBoundingBoxSize() + if math.abs(self.direction.x) < 1e-10 then + return 0, math.huge + elseif math.abs(self.direction.y) < 1e-10 then + return math.huge, 0 + end + return math.huge, math.huge +end + ---获取直线的角度(弧度) ---@return number 直线的角度,单位为弧度 function Line:angle() diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index 2cbeeeab..4750e3c8 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -213,6 +213,31 @@ function Polygon:getCenter() return Vector2.create((minX + maxX) / 2, (minY + maxY) / 2) end +---计算多边形的包围盒宽高 +---@return number, number +function Polygon:getBoundingBoxSize() + local minX, minY = math.huge, math.huge + local maxX, maxY = -math.huge, -math.huge + + for i = 0, self.size - 1 do + local point = self.points[i] + if point.x < minX then + minX = point.x + end + if point.y < minY then + minY = point.y + end + if point.x > maxX then + maxX = point.x + end + if point.y > maxY then + maxY = point.y + end + end + + return maxX - minX, maxY - minY +end + ---计算多边形的面积 ---@return number function Polygon:getArea() diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index 52fa5cb7..f9865fb5 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -81,6 +81,36 @@ function Ray:getPoint(length) return self.point + self.direction * length end +---计算射线的中心 +---@return foundation.math.Vector2 +function Ray:getCenter() + if math.abs(self.direction.x) < 1e-10 then + return Vector2.create(0, math.huge) + elseif math.abs(self.direction.y) < 1e-10 then + return Vector2.create(math.huge, 0) + end + if self.direction.x > 0 and self.direction.y > 0 then + return Vector2.create(math.huge, math.huge) + elseif self.direction.x > 0 and self.direction.y < 0 then + return Vector2.create(math.huge, -math.huge) + elseif self.direction.x < 0 and self.direction.y > 0 then + return Vector2.create(-math.huge, math.huge) + else + return Vector2.create(-math.huge, -math.huge) + end +end + +---计算射线的包围盒宽高 +---@return number, number +function Ray:getBoundingBoxSize() + if math.abs(self.direction.x) < 1e-10 then + return 0, math.huge + elseif math.abs(self.direction.y) < 1e-10 then + return math.huge, 0 + end + return math.huge, math.huge +end + ---获取射线的角度(弧度) ---@return number 射线的角度,单位为弧度 function Ray:angle() diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index acff6464..14135d8c 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -255,6 +255,21 @@ function Rectangle:getCenter() return self.center:clone() end +---计算矩形的包围盒宽高 +---@return number, number +function Rectangle:getBoundingBoxSize() + local vertices = self:getVertices() + local minX, minY = math.huge, math.huge + local maxX, maxY = -math.huge, -math.huge + for _, vertex in ipairs(vertices) do + minX = math.min(minX, vertex.x) + minY = math.min(minY, vertex.y) + maxX = math.max(maxX, vertex.x) + maxY = math.max(maxY, vertex.y) + end + return maxX - minX, maxY - minY +end + ---计算矩形的内心 ---@return foundation.math.Vector2 矩形的内心 function Rectangle:incenter() diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index 9912d615..d13baa23 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -121,20 +121,113 @@ end ---计算扇形的中心点 ---@return foundation.math.Vector2 function Sector:getCenter() - return self.center:clone() + if math.abs(self.range) >= 1 then + return self.center:clone() + end + + local points = { self.center:clone() } + local start_dir = self.direction + local end_dir = self.direction:rotated(self.range * 2 * math.pi) + local start_point = self.center + start_dir * self.radius + local end_point = self.center + end_dir * self.radius + points[#points + 1] = start_point + points[#points + 1] = end_point + + local start_angle = self.direction:angle() + local end_angle = start_angle + self.range * 2 * math.pi + local min_angle = math.min(start_angle, end_angle) + local max_angle = math.max(start_angle, end_angle) + + local critical_points = { + { angle = 0, point = Vector2.create(self.center.x + self.radius, self.center.y) }, -- x_max + { angle = math.pi, point = Vector2.create(self.center.x - self.radius, self.center.y) }, -- x_min + { angle = math.pi / 2, point = Vector2.create(self.center.x, self.center.y + self.radius) }, -- y_max + { angle = 3 * math.pi / 2, point = Vector2.create(self.center.x, self.center.y - self.radius) } -- y_min + } + + for _, cp in ipairs(critical_points) do + local angle = cp.angle + angle = angle - 2 * math.pi * math.floor((angle - min_angle) / (2 * math.pi)) + if min_angle <= angle and angle <= max_angle then + points[#points + 1] = cp.point + end + end + + local x_min, x_max = points[1].x, points[1].x + local y_min, y_max = points[1].y, points[1].y + for _, p in ipairs(points) do + x_min = math.min(x_min, p.x) + x_max = math.max(x_max, p.x) + y_min = math.min(y_min, p.y) + y_max = math.max(y_max, p.y) + end + + return Vector2.create((x_min + x_max) / 2, (y_min + y_max) / 2) +end + +---计算扇形的包围盒宽高 +---@return number, number +function Sector:getBoundingBoxSize() + if math.abs(self.range) >= 1 then + return 2 * self.radius, 2 * self.radius + end + + local points = { self.center:clone() } + local start_dir = self.direction + local end_dir = self.direction:rotated(self.range * 2 * math.pi) + local start_point = self.center + start_dir * self.radius + local end_point = self.center + end_dir * self.radius + points[#points + 1] = start_point + points[#points + 1] = end_point + + local start_angle = self.direction:angle() + local end_angle = start_angle + self.range * 2 * math.pi + local min_angle = math.min(start_angle, end_angle) + local max_angle = math.max(start_angle, end_angle) + + local critical_points = { + { angle = 0, point = Vector2.create(self.center.x + self.radius, self.center.y) }, -- x_max + { angle = math.pi, point = Vector2.create(self.center.x - self.radius, self.center.y) }, -- x_min + { angle = math.pi / 2, point = Vector2.create(self.center.x, self.center.y + self.radius) }, -- y_max + { angle = 3 * math.pi / 2, point = Vector2.create(self.center.x, self.center.y - self.radius) } -- y_min + } + + for _, cp in ipairs(critical_points) do + local angle = cp.angle + angle = angle - 2 * math.pi * math.floor((angle - min_angle) / (2 * math.pi)) + if min_angle <= angle and angle <= max_angle then + points[#points + 1] = cp.point + end + end + + local x_min, x_max = points[1].x, points[1].x + local y_min, y_max = points[1].y, points[1].y + for _, p in ipairs(points) do + x_min = math.min(x_min, p.x) + x_max = math.max(x_max, p.x) + y_min = math.min(y_min, p.y) + y_max = math.max(y_max, p.y) + end + + return x_max - x_min, y_max - y_min end ---获取扇形的重心 ---@return foundation.math.Vector2 function Sector:centroid() - local angle = self:getAngle() - if math.abs(angle) >= 1 then + if math.abs(self.range) >= 1 then return self.center:clone() end - - local x = self.center.x + (self.radius / 3) * math.cos(self.direction:angle() + angle / 2) - local y = self.center.y + (self.radius / 3) * math.sin(self.direction:angle() + angle / 2) - return Vector2.create(x, y) + if self.range <= 1e-10 then + return self.center + self.direction * (self.radius / 2) + end + local theta = self:getAngle() + local half_theta = theta / 2 + local mid_angle = self.direction:angle() + self.range * math.pi + local factor = (2 * self.radius / 3) * (math.sin(half_theta) / half_theta) + local x_c = self.center.x + factor * math.cos(mid_angle) + local y_c = self.center.y + factor * math.sin(mid_angle) + return Vector2.create(x_c, y_c) end ---检查点是否在扇形内(包括边界) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index 86384e07..9a6232a5 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -100,6 +100,22 @@ function Segment:angle() return math.atan2(self.point2.y - self.point1.y, self.point2.x - self.point1.x) end +---计算线段的中心 +---@return foundation.math.Vector2 线段的中心 +function Segment:getCenter() + return self:midpoint() +end + +---计算线段的包围盒宽高 +---@return number, number 线段的宽度和高度 +function Segment:getBoundingBoxSize() + local minX = math.min(self.point1.x, self.point2.x) + local maxX = math.max(self.point1.x, self.point2.x) + local minY = math.min(self.point1.y, self.point2.y) + local maxY = math.max(self.point1.y, self.point2.y) + return maxX - minX, maxY - minY +end + ---获取线段的角度(度) ---@return number 线段的角度,单位为度 function Segment:degreeAngle() diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 9fd70fd4..7d4b9727 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -73,6 +73,16 @@ function Triangle:getCenter() return Vector2.create((minX + maxX) / 2, (minY + maxY) / 2) end +---计算三角形的包围盒宽高 +---@return number, number +function Triangle:getBoundingBoxSize() + local minX = math.min(self.point1.x, self.point2.x, self.point3.x) + local maxX = math.max(self.point1.x, self.point2.x, self.point3.x) + local minY = math.min(self.point1.y, self.point2.y, self.point3.y) + local maxY = math.max(self.point1.y, self.point2.y, self.point3.y) + return maxX - minX, maxY - minY +end + ---计算三角形的外接圆半径 ---@return number 三角形的外接圆半径 function Triangle:circumradius() diff --git a/laboratory/geometry/main.lua b/laboratory/geometry/main.lua index 1e482839..cf9536b0 100644 --- a/laboratory/geometry/main.lua +++ b/laboratory/geometry/main.lua @@ -210,6 +210,9 @@ end function object:draw() local player_pos = player:pos() self:renderPlayerPoint(player_pos) + for _, obj in self:enum() do + self:renderBoundingBox(obj) + end for _, obj in self:enum() do if obj.__type == "foundation.shape.Line" then self:renderLine(obj, player_pos) @@ -263,6 +266,19 @@ function object:renderProjectPoint(obj, player_pos) renderPoint(projectPoint, 4) end +---@param obj {__type:string, getBoundingBoxSize:function, getCenter:function} +function object:renderBoundingBox(obj) + local center = obj:getCenter() + local w, h = obj:getBoundingBoxSize() + local v1 = Vector2.create(center.x - w / 2, center.y - h / 2) + local v2 = Vector2.create(center.x + w / 2, center.y + h / 2) + setColor(192, 127, 0, 127) + renderLine(v1, Vector2.create(v1.x, v2.y), 2) + renderLine(v1, Vector2.create(v2.x, v1.y), 2) + renderLine(v2, Vector2.create(v1.x, v2.y), 2) + renderLine(v2, Vector2.create(v2.x, v1.y), 2) +end + ---@param line foundation.shape.Line ---@param player_pos {x:number, y:number} function object:renderLine(line, player_pos) @@ -603,8 +619,34 @@ function Scene5:draw() end local Scene6 = {} -Scene6.name = "Polygon and Sector and Circle" +Scene6.name = "Sector" function Scene6:create() + self.timer = -1 + object:insert( + Sector.create(Vector2.create(0, 0), 100, Vector2.createFromAngle(45), 0.25) + :move(Vector2.create(window.width / 2, window.height / 2)) + ) +end +function Scene6:destroy() + object:clear() +end +function Scene6:update() + self.timer = self.timer + 1 + for i, obj in object:enum() do + obj:degreeRotate(-1 + 2 * (i % 2)) + if obj.__type == "foundation.shape.Sector" then + obj.range = 0.75 * math.sin(self.timer / 100) + end + end + object:updateCollisionCheck() +end +function Scene6:draw() + object:draw() +end + +local Scene7 = {} +Scene7.name = "Polygon and Sector and Circle" +function Scene7:create() self.timer = -1 object:insert( Polygon.create({ @@ -628,23 +670,23 @@ function Scene6:create() :move(Vector2.create(window.width / 3 * 2, window.height / 2)) ) end -function Scene6:destroy() +function Scene7:destroy() object:clear() end -function Scene6:update() +function Scene7:update() self.timer = self.timer + 1 for i, obj in object:enum() do obj:degreeRotate(-1 + 2 * (i % 2)) end object:updateCollisionCheck() end -function Scene6:draw() +function Scene7:draw() object:draw() end -local Scene7 = {} -Scene7.name = "Crazy" -function Scene7:create() +local Scene8 = {} +Scene8.name = "Crazy" +function Scene8:create() self.timer = -1 local rand = lstg.Rand() rand:Seed(os.time()) @@ -695,17 +737,17 @@ function Scene7:create() :move(Vector2.create(window.width * rand:Float(0.25, 0.75), window.height * rand:Float(0.25, 0.75))) ) end -function Scene7:destroy() +function Scene8:destroy() object:clear() end -function Scene7:update() +function Scene8:update() self.timer = self.timer + 1 for i, obj in object:enum() do obj:degreeRotate(-1 + 2 * (i % 2)) end object:updateCollisionCheck() end -function Scene7:draw() +function Scene8:draw() object:draw() end --endregion @@ -727,6 +769,7 @@ local scenes = { Scene5, Scene6, Scene7, + Scene8, } local current_scene_index = 1 local current_scene = makeInstance(scenes[current_scene_index]) From e52bd60d4957030ea84b64fa3f6b775916db9900 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 19:27:06 +0800 Subject: [PATCH 52/71] add MIT LICENSE about this --- .../thlib-scripts-v2/foundation/math/LICENSE | 21 +++++++++++++++++++ .../thlib-scripts-v2/foundation/shape/LICENSE | 21 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 game/packages/thlib-scripts-v2/foundation/math/LICENSE create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/LICENSE diff --git a/game/packages/thlib-scripts-v2/foundation/math/LICENSE b/game/packages/thlib-scripts-v2/foundation/math/LICENSE new file mode 100644 index 00000000..6e56e2e4 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/math/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 OLC + +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. + +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. \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/LICENSE b/game/packages/thlib-scripts-v2/foundation/shape/LICENSE new file mode 100644 index 00000000..6e56e2e4 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 OLC + +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. + +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. \ No newline at end of file From ad0a104e1448cffe08f23e414d1a367e0a4ebf33 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 20:42:45 +0800 Subject: [PATCH 53/71] feat: implement __index and __newindex metamethods for Circle, Line, Polygon, Ray, Rectangle, Sector, Segment, and Triangle classes --- .../foundation/shape/Circle.lua | 34 ++++++++++++++- .../foundation/shape/Line.lua | 35 +++++++++++++++- .../foundation/shape/Polygon.lua | 8 ++-- .../thlib-scripts-v2/foundation/shape/Ray.lua | 35 +++++++++++++++- .../foundation/shape/Rectangle.lua | 42 ++++++++++++++++++- .../foundation/shape/Sector.lua | 42 ++++++++++++++++++- .../foundation/shape/Segment.lua | 34 ++++++++++++++- .../foundation/shape/Triangle.lua | 38 ++++++++++++++++- 8 files changed, 251 insertions(+), 17 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index c7a45eb3..e967dcad 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -4,6 +4,8 @@ local math = math local type = type local tostring = tostring local string = string +local rawset = rawset +local setmetatable = setmetatable local Vector2 = require("foundation.math.Vector2") local ShapeIntersector = require("foundation.shape.ShapeIntersector") @@ -19,16 +21,44 @@ typedef struct { ---@field center foundation.math.Vector2 圆心位置 ---@field radius number 圆的半径 local Circle = {} -Circle.__index = Circle Circle.__type = "foundation.shape.Circle" +---@param self foundation.shape.Circle +---@param key any +---@return any +function Circle.__index(self, key) + if key == "center" then + return self.__data.center + elseif key == "radius" then + return self.__data.radius + end + return Circle[key] +end + +---@param self foundation.shape.Circle +---@param key any +---@param value any +function Circle.__newindex(self, key, value) + if key == "center" then + self.__data.center = value + elseif key == "radius" then + self.__data.radius = value + else + rawset(self, key, value) + end +end + ---创建一个新的圆 ---@param center foundation.math.Vector2 圆心位置 ---@param radius number 半径 ---@return foundation.shape.Circle function Circle.create(center, radius) + local circle = ffi.new("foundation_shape_Circle", center, radius) + local result = { + __data = circle, + } ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return ffi.new("foundation_shape_Circle", center, radius) + return setmetatable(result, Circle) end ---圆相等比较 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index 107fc15d..1693c8ff 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -4,6 +4,8 @@ local type = type local math = math local tostring = tostring local string = string +local rawset = rawset +local setmetatable = setmetatable local Vector2 = require("foundation.math.Vector2") local ShapeIntersector = require("foundation.shape.ShapeIntersector") @@ -19,9 +21,33 @@ typedef struct { ---@field point foundation.math.Vector2 直线上的一点 ---@field direction foundation.math.Vector2 直线的方向向量 local Line = {} -Line.__index = Line Line.__type = "foundation.shape.Line" +---@param self foundation.shape.Line +---@param key any +---@return any +function Line.__index(self, key) + if key == "point" then + return self.__data.point + elseif key == "direction" then + return self.__data.direction + end + return Line[key] +end + +---@param self foundation.shape.Line +---@param key any +---@param value any +function Line.__newindex(self, key, value) + if key == "point" then + self.__data.point = value + elseif key == "direction" then + self.__data.direction = value + else + rawset(self, key, value) + end +end + ---创建一条新的直线,由一个点和方向向量确定 ---@param point foundation.math.Vector2 直线上的点 ---@param direction foundation.math.Vector2 方向向量 @@ -37,8 +63,13 @@ function Line.create(point, direction) ---@diagnostic disable-next-line: need-check-nil direction = direction:clone() end + + local line = ffi.new("foundation_shape_Line", point, direction) + local result = { + __data = line, + } ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return ffi.new("foundation_shape_Line", point, direction) + return setmetatable(result, Line) end ---根据两个点创建一条直线 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index 4750e3c8..830554f0 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -7,6 +7,7 @@ local math = math local tostring = tostring local string = string local error = error +local rawset = rawset local setmetatable = setmetatable local Vector2 = require("foundation.math.Vector2") @@ -62,8 +63,9 @@ function Polygon.__newindex(self, key, value) local size, points_array = buildNewVector2Array(value) self.__data.size = size self.__data.points = points_array - elseif key == "__data" then - error("cannot modify __data directly") + self.__data_point_ref = points_array + else + rawset(self, key, value) end end @@ -80,7 +82,7 @@ function Polygon.create(points) local polygon = ffi.new("foundation_shape_Polygon", size, points_array) local result = { __data = polygon, - __point_ref = points_array, + __data_point_ref = points_array, } ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value return setmetatable(result, Polygon) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index f9865fb5..db8eb160 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -4,6 +4,8 @@ local type = type local math = math local tostring = tostring local string = string +local rawset = rawset +local setmetatable = setmetatable local Vector2 = require("foundation.math.Vector2") local ShapeIntersector = require("foundation.shape.ShapeIntersector") @@ -19,9 +21,33 @@ typedef struct { ---@field point foundation.math.Vector2 射线的起始点 ---@field direction foundation.math.Vector2 射线的方向向量 local Ray = {} -Ray.__index = Ray Ray.__type = "foundation.shape.Ray" +---@param self foundation.shape.Ray +---@param key any +---@return any +function Ray.__index(self, key) + if key == "point" then + return self.__data.point + elseif key == "direction" then + return self.__data.direction + end + return Ray[key] +end + +---@param self foundation.shape.Ray +---@param key any +---@param value any +function Ray.__newindex(self, key, value) + if key == "point" then + self.__data.point = value + elseif key == "direction" then + self.__data.direction = value + else + rawset(self, key, value) + end +end + ---创建一条新的射线,由起始点和方向向量确定 ---@param point foundation.math.Vector2 起始点 ---@param direction foundation.math.Vector2 方向向量 @@ -37,8 +63,13 @@ function Ray.create(point, direction) ---@diagnostic disable-next-line: need-check-nil direction = direction:clone() end + + local ray = ffi.new("foundation_shape_Ray", point, direction) + local result = { + __data = ray, + } ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return ffi.new("foundation_shape_Ray", point, direction) + return setmetatable(result, Ray) end ---根据起始点、弧度创建射线 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index 14135d8c..0b693fbd 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -5,6 +5,8 @@ local ipairs = ipairs local tostring = tostring local string = string local math = math +local rawset = rawset +local setmetatable = setmetatable local Vector2 = require("foundation.math.Vector2") local Segment = require("foundation.shape.Segment") @@ -25,9 +27,41 @@ typedef struct { ---@field height number 矩形的高度 ---@field direction foundation.math.Vector2 矩形的宽度轴方向(归一化向量) local Rectangle = {} -Rectangle.__index = Rectangle Rectangle.__type = "foundation.shape.Rectangle" +---@param self foundation.shape.Rectangle +---@param key any +---@return any +function Rectangle.__index(self, key) + if key == "center" then + return self.__data.center + elseif key == "width" then + return self.__data.width + elseif key == "height" then + return self.__data.height + elseif key == "direction" then + return self.__data.direction + end + return Rectangle[key] +end + +---@param self foundation.shape.Rectangle +---@param key string +---@param value any +function Rectangle.__newindex(self, key, value) + if key == "center" then + self.__data.center = value + elseif key == "width" then + self.__data.width = value + elseif key == "height" then + self.__data.height = value + elseif key == "direction" then + self.__data.direction = value + else + rawset(self, key, value) + end +end + ---创建一个新的矩形 ---@param center foundation.math.Vector2 中心点 ---@param width number 宽度 @@ -45,8 +79,12 @@ function Rectangle.create(center, width, height, direction) ---@diagnostic disable-next-line: need-check-nil direction = direction:clone() end + local rectangle = ffi.new("foundation_shape_Rectangle", center, width, height, direction) + local result = { + __data = rectangle + } ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return ffi.new("foundation_shape_Rectangle", center, width, height, direction) + return setmetatable(result, Rectangle) end ---使用给定的弧度创建矩形 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index d13baa23..bd22e5cf 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -5,6 +5,8 @@ local type = type local ipairs = ipairs local tostring = tostring local string = string +local rawset = rawset +local setmetatable = setmetatable local Vector2 = require("foundation.math.Vector2") local Segment = require("foundation.shape.Segment") @@ -26,9 +28,41 @@ typedef struct { ---@field direction foundation.math.Vector2 方向(归一化向量) ---@field range number 扇形范围(-1到1,1或-1为整圆,0.5或-0.5为半圆) local Sector = {} -Sector.__index = Sector Sector.__type = "foundation.shape.Sector" +---@param self foundation.shape.Sector +---@param key any +---@return any +function Sector.__index(self, key) + if key == "center" then + return self.__data.center + elseif key == "radius" then + return self.__data.radius + elseif key == "direction" then + return self.__data.direction + elseif key == "range" then + return self.__data.range + end + return Sector[key] +end + +---@param self foundation.shape.Sector +---@param key any +---@param value any +function Sector.__newindex(self, key, value) + if key == "center" then + self.__data.center = value + elseif key == "radius" then + self.__data.radius = value + elseif key == "direction" then + self.__data.direction = value + elseif key == "range" then + self.__data.range = value + else + rawset(self, key, value) + end +end + ---创建一个新的扇形 ---@param center foundation.math.Vector2 中心点 ---@param radius number 半径 @@ -38,8 +72,12 @@ Sector.__type = "foundation.shape.Sector" function Sector.create(center, radius, direction, range) local dir = direction:normalized() range = math.max(-1, math.min(1, range)) -- 限制范围在-1到1 + local sector = ffi.new("foundation_shape_Sector", center, radius, dir, range) + local result = { + __data = sector, + } ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return ffi.new("foundation_shape_Sector", center, radius, dir, range) + return setmetatable(result, Sector) end ---使用弧度创建扇形 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index 9a6232a5..fa065982 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -4,6 +4,8 @@ local type = type local tostring = tostring local string = string local math = math +local rawset = rawset +local setmetatable = setmetatable local Vector2 = require("foundation.math.Vector2") local ShapeIntersector = require("foundation.shape.ShapeIntersector") @@ -18,16 +20,44 @@ typedef struct { ---@field point1 foundation.math.Vector2 ---@field point2 foundation.math.Vector2 local Segment = {} -Segment.__index = Segment Segment.__type = "foundation.shape.Segment" +---@param self foundation.shape.Segment +---@param key any +---@return any +function Segment.__index(self, key) + if key == "point1" then + return self.__data.point1 + elseif key == "point2" then + return self.__data.point2 + end + return Segment[key] +end + +---@param self foundation.shape.Segment +---@param key any +---@param value any +function Segment.__newindex(self, key, value) + if key == "point1" then + self.__data.point1 = value + elseif key == "point2" then + self.__data.point2 = value + else + rawset(self, key, value) + end +end + ---创建一个线段 ---@param point1 foundation.math.Vector2 线段的起点 ---@param point2 foundation.math.Vector2 线段的终点 ---@return foundation.shape.Segment function Segment.create(point1, point2) + local segment = ffi.new("foundation_shape_Segment", point1, point2) + local result = { + __data = segment, + } ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return ffi.new("foundation_shape_Segment", point1, point2) + return setmetatable(result, Segment) end ---根据给定点和弧度与长度创建线段 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 7d4b9727..773ab45a 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -5,6 +5,8 @@ local ipairs = ipairs local tostring = tostring local string = string local math = math +local rawset = rawset +local setmetatable = setmetatable local Vector2 = require("foundation.math.Vector2") local Segment = require("foundation.shape.Segment") @@ -21,17 +23,49 @@ typedef struct { ---@field point2 foundation.math.Vector2 三角形的第二个顶点 ---@field point3 foundation.math.Vector2 三角形的第三个顶点 local Triangle = {} -Triangle.__index = Triangle Triangle.__type = "foundation.shape.Triangle" +---@param self foundation.shape.Triangle +---@param key string +---@return any +function Triangle.__index(self, key) + if key == "point1" then + return self.__data.point1 + elseif key == "point2" then + return self.__data.point2 + elseif key == "point3" then + return self.__data.point3 + end + return Triangle[key] +end + +---@param self foundation.shape.Triangle +---@param key string +---@param value any +function Triangle.__newindex(self, key, value) + if key == "point1" then + self.__data.point1 = value + elseif key == "point2" then + self.__data.point2 = value + elseif key == "point3" then + self.__data.point3 = value + else + rawset(self, key, value) + end +end + ---创建一个新的三角形 ---@param v1 foundation.math.Vector2 三角形的第一个顶点 ---@param v2 foundation.math.Vector2 三角形的第二个顶点 ---@param v3 foundation.math.Vector2 三角形的第三个顶点 ---@return foundation.shape.Triangle 新创建的三角形 function Triangle.create(v1, v2, v3) + local triangle = ffi.new("foundation_shape_Triangle", v1, v2, v3) + local result = { + __data = triangle, + } ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return ffi.new("foundation_shape_Triangle", v1, v2, v3) + return setmetatable(result, Triangle) end ---三角形相等比较 From a0e9cb1bcaf70314acdd7ac712c870236e939d6e Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 21:30:11 +0800 Subject: [PATCH 54/71] feat: add rendering toggle options for various geometric features in the object class --- laboratory/geometry/main.lua | 276 ++++++++++++++++++++++++----------- 1 file changed, 192 insertions(+), 84 deletions(-) diff --git a/laboratory/geometry/main.lua b/laboratory/geometry/main.lua index cf9536b0..1126683f 100644 --- a/laboratory/geometry/main.lua +++ b/laboratory/geometry/main.lua @@ -178,12 +178,22 @@ end local object = { pool = {}, collision_result = {}, + render_player_point = true, + render_closest_point = true, + render_project_point = true, + render_bounding_box = true, + render_collision_result = true, + render_incircle = true, + render_circumcircle = true, + render_vertex = true, + render_direction = true, } function object:insert(obj) table.insert(self.pool, obj) end function object:clear() self.pool = {} + self.collision_result = {} end function object:enum() return ipairs(self.pool) @@ -209,9 +219,13 @@ function object:update() end function object:draw() local player_pos = player:pos() - self:renderPlayerPoint(player_pos) - for _, obj in self:enum() do - self:renderBoundingBox(obj) + if self.render_player_point then + self:renderPlayerPoint(player_pos) + end + if self.render_bounding_box then + for _, obj in self:enum() do + self:renderBoundingBox(obj) + end end for _, obj in self:enum() do if obj.__type == "foundation.shape.Line" then @@ -231,12 +245,15 @@ function object:draw() elseif obj.__type == "foundation.shape.Polygon" then self:renderPolygon(obj, player_pos) end - self:renderProjectPoint(obj, player_pos) - self:renderClosestPoint(obj, player_pos) + if self.render_project_point then + self:renderProjectPoint(obj, player_pos) + end + if self.render_closest_point then + self:renderClosestPoint(obj, player_pos) + end end - setColor(192, 0, 255, 255) - for _, point in ipairs(self.collision_result) do - renderPoint(point, 4) + if self.render_collision_result then + self:renderCollisionResult() end end @@ -246,6 +263,13 @@ function object:renderPlayerPoint(player_pos) renderPoint(player_pos, 4) end +function object:renderCollisionResult() + setColor(192, 0, 255, 255) + for _, point in ipairs(self.collision_result) do + renderPoint(point, 4) + end +end + ---@param obj {__type:string, closestPoint:function} ---@param player_pos {x:number, y:number} function object:renderClosestPoint(obj, player_pos) @@ -288,10 +312,14 @@ function object:renderLine(line, player_pos) setColor(192, 255, 255, 255) end renderLine(line:getPoint(-1000), line:getPoint(1000), 2) - setColor(127, 255, 0, 0) - renderPoint(line.point, 4) - setColor(127, 255, 63, 63) - renderLine(line.point, line:getPoint(50), 2) + if self.render_vertex then + setColor(127, 255, 0, 0) + renderPoint(line.point, 4) + end + if self.render_direction then + setColor(127, 255, 63, 63) + renderLine(line.point, line:getPoint(50), 2) + end end ---@param ray foundation.shape.Ray @@ -303,10 +331,14 @@ function object:renderRay(ray, player_pos) setColor(192, 255, 255, 255) end renderLine(ray.point, ray:getPoint(1000), 2) - setColor(127, 255, 0, 0) - renderPoint(ray.point, 4) - setColor(127, 255, 63, 63) - renderLine(ray.point, ray:getPoint(50), 2) + if self.render_vertex then + setColor(127, 255, 0, 0) + renderPoint(ray.point, 4) + end + if self.render_direction then + setColor(127, 255, 63, 63) + renderLine(ray.point, ray:getPoint(50), 2) + end end ---@param segment foundation.shape.Segment @@ -318,27 +350,35 @@ function object:renderSegment(segment, player_pos) setColor(192, 255, 255, 255) end renderLine(segment.point1, segment.point2, 2) - setColor(127, 127, 0, 0) - renderPoint(segment.point1, 4) - renderPoint(segment.point2, 4) - setColor(127, 255, 0, 0) - renderPoint(segment:midpoint(), 4) - setColor(127, 255, 63, 63) - renderLine(segment:midpoint(), segment.point1, 2) + if self.render_vertex then + setColor(127, 127, 0, 0) + renderPoint(segment.point1, 4) + renderPoint(segment.point2, 4) + setColor(127, 255, 0, 0) + renderPoint(segment:midpoint(), 4) + end + if self.render_direction then + setColor(127, 255, 63, 63) + renderLine(segment:midpoint(), segment.point1, 2) + end end ---@param triangle foundation.shape.Triangle ---@param player_pos {x:number, y:number} function object:renderTriangle(triangle, player_pos) setColor(63, 127, 255, 192) - local incenter = triangle:incenter() - local inradius = triangle:inradius() - local circumcenter = triangle:circumcenter() - local circumradius = triangle:circumradius() - setColor(63, 127, 255, 192) - renderCircle(incenter, inradius - 1, inradius + 1, 64) - setColor(63, 127, 192, 255) - renderCircle(circumcenter, circumradius - 1, circumradius + 1, 64) + if self.render_incircle then + local incenter = triangle:incenter() + local inradius = triangle:inradius() + setColor(63, 127, 255, 192) + renderCircle(incenter, inradius - 1, inradius + 1, 64) + end + if self.render_circumcircle then + local circumcenter = triangle:circumcenter() + local circumradius = triangle:circumradius() + setColor(63, 127, 192, 255) + renderCircle(circumcenter, circumradius - 1, circumradius + 1, 64) + end if triangle:containsPoint(player_pos, 1) then setColor(192, 0, 0, 255) elseif triangle:contains(player_pos) then @@ -349,14 +389,18 @@ function object:renderTriangle(triangle, player_pos) renderLine(triangle.point1, triangle.point2, 2) renderLine(triangle.point2, triangle.point3, 2) renderLine(triangle.point3, triangle.point1, 2) - setColor(127, 127, 0, 0) - renderPoint(triangle.point1, 4) - renderPoint(triangle.point2, 4) - renderPoint(triangle.point3, 4) - setColor(127, 255, 0, 0) - renderPoint(triangle:centroid(), 4) - setColor(127, 255, 63, 63) - renderLine(triangle:centroid(), triangle.point1, 2) + if self.render_vertex then + setColor(127, 127, 0, 0) + renderPoint(triangle.point1, 4) + renderPoint(triangle.point2, 4) + renderPoint(triangle.point3, 4) + setColor(127, 255, 0, 0) + renderPoint(triangle:centroid(), 4) + end + if self.render_direction then + setColor(127, 255, 63, 63) + renderLine(triangle:centroid(), triangle.point1, 2) + end end ---@param rectangle foundation.shape.Rectangle @@ -366,12 +410,16 @@ function object:renderRectangle(rectangle, player_pos) local w = rectangle.width / 2 local h = rectangle.height / 2 local a = rectangle.direction:degreeAngle() - local inradius = rectangle:inradius() - local circumradius = rectangle:circumradius() - setColor(63, 127, 255, 192) - renderCircle(p, inradius - 1, inradius + 1, 64) - setColor(63, 127, 192, 255) - renderCircle(p, circumradius - 1, circumradius + 1, 64) + if self.render_incircle then + local inradius = rectangle:inradius() + setColor(63, 127, 255, 192) + renderCircle(p, inradius - 1, inradius + 1, 64) + end + if self.render_circumcircle then + local circumradius = rectangle:circumradius() + setColor(63, 127, 192, 255) + renderCircle(p, circumradius - 1, circumradius + 1, 64) + end if rectangle:containsPoint(player_pos, 1) then setColor(192, 0, 0, 255) elseif rectangle:contains(player_pos) then @@ -383,15 +431,19 @@ function object:renderRectangle(rectangle, player_pos) renderLine(p + Vector2.create(w, -h):degreeRotated(a), p + Vector2.create(w, h):degreeRotated(a), 2) renderLine(p + Vector2.create(w, h):degreeRotated(a), p + Vector2.create(-w, h):degreeRotated(a), 2) renderLine(p + Vector2.create(-w, h):degreeRotated(a), p + Vector2.create(-w, -h):degreeRotated(a), 2) - setColor(127, 127, 0, 0) - renderPoint(p + Vector2.create(-w, -h):degreeRotated(a), 4) - renderPoint(p + Vector2.create(w, -h):degreeRotated(a), 4) - renderPoint(p + Vector2.create(w, h):degreeRotated(a), 4) - renderPoint(p + Vector2.create(-w, h):degreeRotated(a), 4) - setColor(127, 255, 0, 0) - renderPoint(p, 4) - setColor(127, 255, 63, 63) - renderLine(p, p + Vector2.create(w, 0):degreeRotated(a), 2) + if self.render_vertex then + setColor(127, 127, 0, 0) + renderPoint(p + Vector2.create(-w, -h):degreeRotated(a), 4) + renderPoint(p + Vector2.create(w, -h):degreeRotated(a), 4) + renderPoint(p + Vector2.create(w, h):degreeRotated(a), 4) + renderPoint(p + Vector2.create(-w, h):degreeRotated(a), 4) + setColor(127, 255, 0, 0) + renderPoint(p, 4) + end + if self.render_direction then + setColor(127, 255, 63, 63) + renderLine(p, p + Vector2.create(w, 0):degreeRotated(a), 2) + end end ---@param polygon foundation.shape.Polygon @@ -410,14 +462,18 @@ function object:renderPolygon(polygon, player_pos) local p2 = vertices[i % #vertices + 1] renderLine(p1, p2, 2) end - setColor(127, 127, 0, 0) - for _, vertex in ipairs(vertices) do - renderPoint(vertex, 4) + if self.render_vertex then + setColor(127, 127, 0, 0) + for _, vertex in ipairs(vertices) do + renderPoint(vertex, 4) + end + setColor(127, 255, 0, 0) + renderPoint(polygon:centroid(), 4) + end + if self.render_direction then + setColor(127, 255, 63, 63) + renderLine(polygon:centroid(), vertices[1], 2) end - setColor(127, 255, 0, 0) - renderPoint(polygon:centroid(), 4) - setColor(127, 255, 63, 63) - renderLine(polygon:centroid(), vertices[1], 2) end ---@param circle foundation.shape.Circle @@ -434,10 +490,14 @@ function object:renderCircle(circle, player_pos) setColor(192, 255, 255, 255) end renderCircle(p, r1, r2, 64) - setColor(127, 255, 0, 0) - renderPoint(p, 4) - setColor(127, 255, 63, 63) - renderLine(p, p + Vector2.create(0, r1), 2) + if self.render_vertex then + setColor(127, 255, 0, 0) + renderPoint(p, 4) + end + if self.render_direction then + setColor(127, 255, 63, 63) + renderLine(p, p + Vector2.create(0, r1), 2) + end end ---@param sector foundation.shape.Sector @@ -458,10 +518,14 @@ function object:renderSector(sector, player_pos) renderSector(p, r1, r2, a1, a2, 64) renderLine(p, p + sector.direction * r1, 2) renderLine(p, p + sector.direction:degreeRotated(sector.range * 360) * r1, 2) - setColor(127, 255, 0, 0) - renderPoint(p, 4) - setColor(127, 255, 63, 63) - renderLine(p, p + sector.direction * r1, 2) + if self.render_vertex then + setColor(127, 255, 0, 0) + renderPoint(p, 4) + end + if self.render_direction then + setColor(127, 255, 63, 63) + renderLine(p, p + sector.direction * r1, 2) + end end --endregion @@ -773,7 +837,30 @@ local scenes = { } local current_scene_index = 1 local current_scene = makeInstance(scenes[current_scene_index]) -local any_key_down = false +local keyState = {} +local keyStatePre = {} +local registeredKey = { + Keyboard.Left, + Keyboard.Right, + Keyboard.D1, + Keyboard.D2, + Keyboard.D3, + Keyboard.D4, + Keyboard.D5, + Keyboard.D6, + Keyboard.D7, + Keyboard.D8, + Keyboard.D9, +} +local function UpdateKeyState() + for _, key in ipairs(registeredKey) do + keyStatePre[key] = keyState[key] + keyState[key] = Keyboard.GetKeyState(key) + end +end +local function KeyIsPressed(key) + return keyState[key] and not keyStatePre[key] +end function GameInit() window:applyWindowSetting() @@ -786,23 +873,44 @@ function GameExit() end function FrameFunc() + UpdateKeyState() local change = 0 - if Keyboard.GetKeyState(Keyboard.Left) then - if not any_key_down then - any_key_down = true - if current_scene_index > 1 then - change = -1 - end + if KeyIsPressed(Keyboard.Left) then + if current_scene_index > 1 then + change = -1 end - elseif Keyboard.GetKeyState(Keyboard.Right) then - if not any_key_down then - any_key_down = true - if current_scene_index < #scenes then - change = 1 - end + end + if KeyIsPressed(Keyboard.Right) then + if current_scene_index < #scenes then + change = 1 end - elseif any_key_down then - any_key_down = false + end + if KeyIsPressed(Keyboard.D1) then + object.render_player_point = not object.render_player_point + end + if KeyIsPressed(Keyboard.D2) then + object.render_closest_point = not object.render_closest_point + end + if KeyIsPressed(Keyboard.D3) then + object.render_project_point = not object.render_project_point + end + if KeyIsPressed(Keyboard.D4) then + object.render_bounding_box = not object.render_bounding_box + end + if KeyIsPressed(Keyboard.D5) then + object.render_collision_result = not object.render_collision_result + end + if KeyIsPressed(Keyboard.D6) then + object.render_incircle = not object.render_incircle + end + if KeyIsPressed(Keyboard.D7) then + object.render_circumcircle = not object.render_circumcircle + end + if KeyIsPressed(Keyboard.D8) then + object.render_vertex = not object.render_vertex + end + if KeyIsPressed(Keyboard.D9) then + object.render_direction = not object.render_direction end if change ~= 0 then current_scene:destroy() From 76b65d447c4582448bb616f3e7935796e84b38b4 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 21:57:14 +0800 Subject: [PATCH 55/71] fix: handle NaN values in player position calculation --- laboratory/geometry/main.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/laboratory/geometry/main.lua b/laboratory/geometry/main.lua index 1126683f..fa7b5eb2 100644 --- a/laboratory/geometry/main.lua +++ b/laboratory/geometry/main.lua @@ -168,6 +168,10 @@ end local player = {} function player:pos() local mouseX, mouseY = Mouse.GetPosition() + if mouseX ~= mouseX or mouseY ~= mouseY then + -- why LuaSTG got NaN? + return Vector2.create(0, 0) + end mouseX = mouseX / window.view_width * window.width mouseY = mouseY / window.view_height * window.height return Vector2.create(mouseX, mouseY) From a2d7d083347931eb951dfa7c07aca1c2e65717d4 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 22:52:35 +0800 Subject: [PATCH 56/71] fix: rename data reference for points in Polygon class --- game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index 830554f0..d87aee04 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -63,7 +63,7 @@ function Polygon.__newindex(self, key, value) local size, points_array = buildNewVector2Array(value) self.__data.size = size self.__data.points = points_array - self.__data_point_ref = points_array + self.__data_points_ref = points_array else rawset(self, key, value) end @@ -82,7 +82,7 @@ function Polygon.create(points) local polygon = ffi.new("foundation_shape_Polygon", size, points_array) local result = { __data = polygon, - __data_point_ref = points_array, + __data_points_ref = points_array, } ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value return setmetatable(result, Polygon) From 55fdcba3b9fbb9dd1e002f8c7f5a8f6d4fab5ba3 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 1 May 2025 23:42:05 +0800 Subject: [PATCH 57/71] feat: add clone method for geometric shape classes --- .../thlib-scripts-v2/foundation/shape/Circle.lua | 6 ++++++ .../thlib-scripts-v2/foundation/shape/Line.lua | 6 ++++++ .../thlib-scripts-v2/foundation/shape/Polygon.lua | 10 ++++++++++ .../packages/thlib-scripts-v2/foundation/shape/Ray.lua | 6 ++++++ .../thlib-scripts-v2/foundation/shape/Rectangle.lua | 6 ++++++ .../thlib-scripts-v2/foundation/shape/Sector.lua | 6 ++++++ .../thlib-scripts-v2/foundation/shape/Segment.lua | 6 ++++++ .../thlib-scripts-v2/foundation/shape/Triangle.lua | 6 ++++++ 8 files changed, 52 insertions(+) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index e967dcad..37939fb2 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -243,6 +243,12 @@ function Circle:containsPoint(point, tolerance) return math.abs(dist - self.radius) <= tolerance end +---复制圆 +---@return foundation.shape.Circle 圆的副本 +function Circle:clone() + return Circle.create(self.center:clone(), self.radius) +end + ffi.metatype("foundation_shape_Circle", Circle) return Circle \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index 1693c8ff..648153a6 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -295,6 +295,12 @@ function Line:projectPoint(point) return self.point + dir * proj_length end +---复制当前直线 +---@return foundation.shape.Line 复制的直线 +function Line:clone() + return Line.create(self.point:clone(), self.direction:clone()) +end + ffi.metatype("foundation_shape_Line", Line) return Line diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index d87aee04..79be5629 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -713,6 +713,16 @@ function Polygon:triangulate() return triangles end +---复制当前多边形 +---@return foundation.shape.Polygon 复制后的多边形 +function Polygon:clone() + local newPoints = {} + for i = 0, self.size - 1 do + newPoints[i + 1] = self.points[i]:clone() + end + return Polygon.create(newPoints) +end + ffi.metatype("foundation_shape_Polygon", Polygon) return Polygon \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index db8eb160..c280ede0 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -312,6 +312,12 @@ function Ray:projectPoint(point) return Vector2.create(self.point.x + t * dir.x, self.point.y + t * dir.y) end +---复制射线 +---@return foundation.shape.Ray 射线副本 +function Ray:clone() + return Ray.create(self.point:clone(), self.direction:clone()) +end + ffi.metatype("foundation_shape_Ray", Ray) return Ray diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index 0b693fbd..d53f99c5 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -396,6 +396,12 @@ function Rectangle:containsPoint(point, tolerance) return false end +---复制矩形 +---@return foundation.shape.Rectangle +function Rectangle:clone() + return Rectangle.create(self.center:clone(), self.width, self.height, self.direction:clone()) +end + ffi.metatype("foundation_shape_Rectangle", Rectangle) return Rectangle \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index bd22e5cf..24c008fb 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -470,6 +470,12 @@ function Sector:containsPoint(point, tolerance) return angle_begin <= vec_angle and vec_angle <= angle_begin + range end +---复制扇形 +---@return foundation.shape.Sector +function Sector:clone() + return Sector.create(self.center:clone(), self.radius, self.direction:clone(), self.range) +end + ffi.metatype("foundation_shape_Sector", Sector) return Sector \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index fa065982..52406ad3 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -353,6 +353,12 @@ function Segment:containsPoint(point, tolerance) return t >= 0 and t <= 1 end +---复制线段 +---@return foundation.shape.Segment 线段的副本 +function Segment:clone() + return Segment.create(self.point1:clone(), self.point2:clone()) +end + ffi.metatype("foundation_shape_Segment", Segment) return Segment diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 773ab45a..0f6042da 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -429,6 +429,12 @@ function Triangle:containsPoint(point, tolerance) return false end +---复制三角形 +---@return foundation.shape.Triangle 三角形的副本 +function Triangle:clone() + return Triangle.create(self.point1:clone(), self.point2:clone(), self.point3:clone()) +end + ffi.metatype("foundation_shape_Triangle", Triangle) return Triangle From 03ac1c43f67e1888af8c4e19c88e6db9d3cb592d Mon Sep 17 00:00:00 2001 From: OLC Date: Fri, 2 May 2025 00:17:06 +0800 Subject: [PATCH 58/71] =?UTF-8?q?=E9=87=8D=E6=96=B0=E6=95=B4=E7=90=86?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/shape/ShapeIntersector.lua | 2457 +---------------- .../ShapeIntersector/CircleIntersector.lua | 98 + .../shape/ShapeIntersector/ContainPoint.lua | 138 + .../ShapeIntersector/LineIntersector.lua | 206 ++ .../ShapeIntersector/PolygonIntersector.lua | 492 ++++ .../shape/ShapeIntersector/RayIntersector.lua | 193 ++ .../ShapeIntersector/RectangleIntersector.lua | 311 +++ .../ShapeIntersector/SectorIntersector.lua | 590 ++++ .../ShapeIntersector/SegmentIntersector.lua | 60 + .../ShapeIntersector/TriangleIntersector.lua | 280 ++ 10 files changed, 2453 insertions(+), 2372 deletions(-) create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/CircleIntersector.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/LineIntersector.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/PolygonIntersector.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RayIntersector.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RectangleIntersector.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SectorIntersector.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SegmentIntersector.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/TriangleIntersector.lua diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua index d2718b19..30d6e68a 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua @@ -1,2462 +1,175 @@ -local Vector2 = require("foundation.math.Vector2") -local math = math local tostring = tostring local ipairs = ipairs local require = require -local Segment - ----@class foundation.shape.ShapeIntersector -local ShapeIntersector = {} - ----检查点是否在圆内或圆上 ----@param circle foundation.shape.Circle 圆形 ----@param point foundation.math.Vector2 点 ----@return boolean -function ShapeIntersector.circleContainsPoint(circle, point) - return (point - circle.center):length() <= circle.radius + 1e-10 -end - ----检查点是否在矩形内(包括边界) ----@param rectangle foundation.shape.Rectangle 矩形 ----@param point foundation.math.Vector2 点 ----@return boolean -function ShapeIntersector.rectangleContainsPoint(rectangle, point) - local p = point - rectangle.center - local dir = rectangle.direction - local perp = Vector2.create(-dir.y, dir.x) - local x = p.x * dir.x + p.y * dir.y - local y = p.x * perp.x + p.y * perp.y - local hw, hh = rectangle.width / 2, rectangle.height / 2 - return math.abs(x) <= hw + 1e-10 and math.abs(y) <= hh + 1e-10 -end - ----检查点是否在扇形内(包括边界) ----@param sector foundation.shape.Sector 扇形 ----@param point foundation.math.Vector2 点 ----@return boolean -function ShapeIntersector.sectorContainsPoint(sector, point) - local inCircle = ShapeIntersector.circleContainsPoint(sector, point) - if not inCircle then - return false - end - if math.abs(sector.range) >= 1 then - return true - end - - local range = sector.range * math.pi * 2 - local angle_begin - if range > 0 then - angle_begin = sector.direction:angle() - else - range = -range - angle_begin = sector.direction:angle() - range - end - - local vec = point - sector.center - local vec_angle = vec:angle() - vec_angle = vec_angle - 2 * math.pi * math.floor((vec_angle - angle_begin) / (2 * math.pi)) - return angle_begin <= vec_angle and vec_angle <= angle_begin + range -end - ----检查点是否在三角形内 ----@param triangle foundation.shape.Triangle 三角形 ----@param point foundation.math.Vector2 点 ----@return boolean -function ShapeIntersector.triangleContainsPoint(triangle, point) - local v1 = triangle.point1 - local v2 = triangle.point2 - local v3 = triangle.point3 - local p = point - - local v3v1 = v3 - v1 - local v2v1 = v2 - v1 - local pv1 = p - v1 - - local dot00 = v3v1:dot(v3v1) - local dot01 = v3v1:dot(v2v1) - local dot02 = v3v1:dot(pv1) - local dot11 = v2v1:dot(v2v1) - local dot12 = v2v1:dot(pv1) - - local invDenom = 1 / (dot00 * dot11 - dot01 * dot01) - local u = (dot11 * dot02 - dot01 * dot12) * invDenom - local v = (dot00 * dot12 - dot01 * dot02) * invDenom - - return (u >= 0) and (v >= 0) and (u + v <= 1) -end - ----检查点是否在多边形内 ----@param polygon foundation.shape.Polygon 多边形 ----@param point foundation.math.Vector2 点 ----@return boolean -function ShapeIntersector.polygonContainsPoint(polygon, point) - if polygon.size < 3 then - return false - end - - Segment = Segment or require("foundation.shape.Segment") - for i = 0, polygon.size - 1 do - local j = (i + 1) % polygon.size - local segment = Segment.create(polygon.points[i], polygon.points[j]) - if ShapeIntersector.segmentContainsPoint(segment, point) then - return true - end - end - - local inside = false - for i = 0, polygon.size - 1 do - local j = (i + 1) % polygon.size - - local pi = polygon.points[i] - local pj = polygon.points[j] - - if (pi.x - point.x) * (pi.x - point.x) + (pi.y - point.y) * (pi.y - point.y) < 1e-10 then - return true - end - - if ((pi.y > point.y) ~= (pj.y > point.y)) and - (point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x) then - inside = not inside - end - end - - return inside -end - ----判断点是否在线段上 ----@param segment foundation.shape.Segment 线段 ----@param point foundation.math.Vector2 点 ----@return boolean -function ShapeIntersector.segmentContainsPoint(segment, point) - local d1 = (point - segment.point1):length() - local d2 = (point - segment.point2):length() - local lineLen = (segment.point2 - segment.point1):length() - - return math.abs(d1 + d2 - lineLen) <= 1e-10 -end - ----整理相交点,去除重复点 ----@param points foundation.math.Vector2[] 原始点列表 ----@return foundation.math.Vector2[] 去重后的点列表 -function ShapeIntersector.getUniquePoints(points) - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - return unique_points -end - ----检查三角形与线段的相交 ----@param triangle foundation.shape.Triangle 三角形 ----@param segment foundation.shape.Segment 线段 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.triangleToSegment(triangle, segment) - local points = {} - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge, segment) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if ShapeIntersector.triangleContainsPoint(triangle, segment.point1) then - points[#points + 1] = segment.point1:clone() - end - if segment.point1 ~= segment.point2 and ShapeIntersector.triangleContainsPoint(triangle, segment.point2) then - points[#points + 1] = segment.point2:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----只检查三角形与线段是否相交 ----@param triangle foundation.shape.Triangle 三角形 ----@param segment foundation.shape.Segment 线段 ----@return boolean -function ShapeIntersector.triangleHasIntersectionWithSegment(triangle, segment) - local edges = triangle:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then - return true - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, segment.point1) or - ShapeIntersector.triangleContainsPoint(triangle, segment.point2) then - return true - end - - return false -end - ----检查三角形与另一个三角形的相交 ----@param triangle1 foundation.shape.Triangle 第一个三角形 ----@param triangle2 foundation.shape.Triangle 第二个三角形 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.triangleToTriangle(triangle1, triangle2) - local points = {} - local edges1 = triangle1:getEdges() - local edges2 = triangle2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - - local vertices = triangle2:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle1, vertex) then - points[#points + 1] = vertex - end - end - - vertices = triangle1:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle2, vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----只检查三角形与另一个三角形是否相交 ----@param triangle1 foundation.shape.Triangle 第一个三角形 ----@param triangle2 foundation.shape.Triangle 第二个三角形 ----@return boolean -function ShapeIntersector.triangleHasIntersectionWithTriangle(triangle1, triangle2) - local edges1 = triangle1:getEdges() - local edges2 = triangle2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then - return true - end - end - end - - local vertices = triangle2:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle1, vertex) then - return true - end - end - - vertices = triangle1:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle2, vertex) then - return true - end - end - - return false -end - ----检查三角形与直线的相交 ----@param triangle foundation.shape.Triangle 三角形 ----@param line foundation.shape.Line 直线 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.triangleToLine(triangle, line) - local points = {} - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.lineToSegment(line, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----只检查三角形与直线是否相交 ----@param triangle foundation.shape.Triangle 三角形 ----@param line foundation.shape.Line 直线 ----@return boolean -function ShapeIntersector.triangleHasIntersectionWithLine(triangle, line) - local edges = triangle:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then - return true - end - end - - return false -end - ----检查三角形与射线的相交 ----@param triangle foundation.shape.Triangle 三角形 ----@param ray foundation.shape.Ray 射线 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.triangleToRay(triangle, ray) - local points = {} - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if ShapeIntersector.triangleContainsPoint(triangle, ray.point) then - points[#points + 1] = ray.point:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----只检查三角形与射线是否相交 ----@param triangle foundation.shape.Triangle 三角形 ----@param ray foundation.shape.Ray 射线 ----@return boolean -function ShapeIntersector.triangleHasIntersectionWithRay(triangle, ray) - local edges = triangle:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then - return true - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, ray.point) then - return true - end - - return false -end - ----检查三角形与圆的相交 ----@param triangle foundation.shape.Triangle 三角形 ----@param circle foundation.shape.Circle 圆 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.triangleToCircle(triangle, circle) - local points = {} - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - points[#points + 1] = vertex - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, circle.center) then - points[#points + 1] = circle.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----只检查三角形与圆是否相交 ----@param triangle foundation.shape.Triangle 三角形 ----@param circle foundation.shape.Circle 圆 ----@return boolean -function ShapeIntersector.triangleHasIntersectionWithCircle(triangle, circle) - if ShapeIntersector.triangleContainsPoint(triangle, circle.center) then - return true - end - - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then - return true - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - return true - end - end - - return false -end - ----检查多边形与线段的相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param segment foundation.shape.Segment 线段 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.polygonToSegment(polygon, segment) - local points = {} - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge, segment) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, segment.point1) then - points[#points + 1] = segment.point1:clone() - end - - if segment.point1 ~= segment.point2 and ShapeIntersector.polygonContainsPoint(polygon, segment.point2) then - points[#points + 1] = segment.point2:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查多边形是否与线段相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param segment foundation.shape.Segment 线段 ----@return boolean -function ShapeIntersector.polygonHasIntersectionWithSegment(polygon, segment) - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then - return true - end - end - - return ShapeIntersector.polygonContainsPoint(polygon, segment.point1) or - ShapeIntersector.polygonContainsPoint(polygon, segment.point2) -end - ----检查多边形与线的相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param line foundation.shape.Line 线 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.polygonToLine(polygon, line) - local points = {} - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.lineToSegment(line, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, line.point) then - points[#points + 1] = line.point:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查多边形是否与线相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param line foundation.shape.Line 线 ----@return boolean -function ShapeIntersector.polygonHasIntersectionWithLine(polygon, line) - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then - return true - end - end - - return ShapeIntersector.polygonContainsPoint(polygon, line.point) -end - ----检查多边形与射线的相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param ray foundation.shape.Ray 射线 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.polygonToRay(polygon, ray) - local points = {} - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, ray.point) then - points[#points + 1] = ray.point:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查多边形是否与射线相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param ray foundation.shape.Ray 射线 ----@return boolean -function ShapeIntersector.polygonHasIntersectionWithRay(polygon, ray) - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then - return true - end - end - - return ShapeIntersector.polygonContainsPoint(polygon, ray.point) -end - ----检查多边形与圆的相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param circle foundation.shape.Circle 圆 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.polygonToCircle(polygon, circle) - local points = {} - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - points[#points + 1] = vertex - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, circle.center) then - points[#points + 1] = circle.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查多边形是否与圆相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param circle foundation.shape.Circle 圆 ----@return boolean -function ShapeIntersector.polygonHasIntersectionWithCircle(polygon, circle) - if ShapeIntersector.polygonContainsPoint(polygon, circle.center) then - return true - end - - local edges = polygon:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then - return true - end - end - - local vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - return true - end - end - - return false -end - ----检查多边形与三角形的相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param triangle foundation.shape.Triangle 三角形 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.polygonToTriangle(polygon, triangle) - local points = {} - local edges1 = polygon:getEdges() - local edges2 = triangle:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon, vertex) then - points[#points + 1] = vertex - end - end - - vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle, vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查多边形是否与三角形相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param triangle foundation.shape.Triangle 三角形 ----@return boolean -function ShapeIntersector.polygonHasIntersectionWithTriangle(polygon, triangle) - local edges1 = polygon:getEdges() - local edges2 = triangle:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then - return true - end - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon, vertex) then - return true - end - end - - vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle, vertex) then - return true - end - end - - return false -end - ----检查多边形与矩形的相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param rectangle foundation.shape.Rectangle 矩形 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.polygonToRectangle(polygon, rectangle) - local points = {} - local edges1 = polygon:getEdges() - local edges2 = rectangle:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon, vertex) then - points[#points + 1] = vertex - end - end - - vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.rectangleContainsPoint(rectangle, vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查多边形是否与矩形相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param rectangle foundation.shape.Rectangle 矩形 ----@return boolean -function ShapeIntersector.polygonHasIntersectionWithRectangle(polygon, rectangle) - local edges1 = polygon:getEdges() - local edges2 = rectangle:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then - return true - end - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon, vertex) then - return true - end - end - - vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.rectangleContainsPoint(rectangle, vertex) then - return true - end - end - - return false -end - ----检查多边形与多边形的相交 ----@param polygon1 foundation.shape.Polygon 第一个多边形 ----@param polygon2 foundation.shape.Polygon 第二个多边形 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.polygonToPolygon(polygon1, polygon2) - local points = {} - local edges1 = polygon1:getEdges() - local edges2 = polygon2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - - local vertices = polygon2:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon1, vertex) then - points[#points + 1] = vertex - end - end - - vertices = polygon1:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon2, vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查多边形是否与多边形相交 ----@param polygon1 foundation.shape.Polygon 第一个多边形 ----@param polygon2 foundation.shape.Polygon 第二个多边形 ----@return boolean -function ShapeIntersector.polygonHasIntersectionWithPolygon(polygon1, polygon2) - local edges1 = polygon1:getEdges() - local edges2 = polygon2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then - return true - end - end - end - - local vertices = polygon2:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon1, vertex) then - return true - end - end - - vertices = polygon1:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon2, vertex) then - return true - end - end - - return false -end - ----检查两条线段的相交 ----@param segment1 foundation.shape.Segment 第一条线段 ----@param segment2 foundation.shape.Segment 第二条线段 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.segmentToSegment(segment1, segment2) - local points = {} - local a = segment1.point1 - local b = segment1.point2 - local c = segment2.point1 - local d = segment2.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - -- 平行或共线情况 - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if t >= 0 and t <= 1 and u >= 0 and u <= 1 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points <= 1e-10 then - return false, nil - end - return true, points -end - ----检查两条线段是否相交 ----@param segment1 foundation.shape.Segment 第一条线段 ----@param segment2 foundation.shape.Segment 第二条线段 ----@return boolean -function ShapeIntersector.segmentHasIntersectionWithSegment(segment1, segment2) - local a = segment1.point1 - local b = segment1.point2 - local c = segment2.point1 - local d = segment2.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - -- 平行或共线情况 - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and t <= 1 and u >= 0 and u <= 1 -end - ----检查直线与线段的相交 ----@param line foundation.shape.Line 直线 ----@param segment foundation.shape.Segment 线段 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.lineToSegment(line, segment) - local points = {} - local a = line.point - local b = line.point + line.direction - local c = segment.point1 - local d = segment.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if u >= 0 and u <= 1 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查直线是否与线段相交 ----@param line foundation.shape.Line 直线 ----@param segment foundation.shape.Segment 线段 ----@return boolean -function ShapeIntersector.lineHasIntersectionWithSegment(line, segment) - local a = line.point - local b = line.point + line.direction - local c = segment.point1 - local d = segment.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false - end - - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return u >= 0 and u <= 1 -end - ----检查射线与线段的相交 ----@param ray foundation.shape.Ray 射线 ----@param segment foundation.shape.Segment 线段 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.rayToSegment(ray, segment) - local points = {} - local a = ray.point - local b = ray.point + ray.direction - local c = segment.point1 - local d = segment.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if t >= 0 and u >= 0 and u <= 1 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points <= 1e-10 then - return false, nil - end - return true, points -end - ----检查射线是否与线段相交 ----@param ray foundation.shape.Ray 射线 ----@param segment foundation.shape.Segment 线段 ----@return boolean -function ShapeIntersector.rayHasIntersectionWithSegment(ray, segment) - local a = ray.point - local b = ray.point + ray.direction - local c = segment.point1 - local d = segment.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and u >= 0 and u <= 1 -end - ----检查圆与线段的相交 ----@param circle foundation.shape.Circle 圆 ----@param segment foundation.shape.Segment 线段 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.circleToSegment(circle, segment) - local points = {} - local dir = segment.point2 - segment.point1 - local len = dir:length() - - if len <= 1e-10 then - if ShapeIntersector.circleContainsPoint(circle, segment.point1) then - return true, { segment.point1:clone() } - end - return false, nil - end - - dir = dir / len - - local closest = segment:closestPoint(circle.center) - local dist = (closest - circle.center):length() - if dist > circle.radius + 1e-10 then - return false, nil - end - - local vector_to_start = segment.point1 - circle.center - local b = 2 * vector_to_start:dot(dir) - local c = vector_to_start:dot(vector_to_start) - circle.radius * circle.radius - local discriminant = b * b - 4 * c - - if discriminant >= -1e-10 then - local sqrt_d = math.sqrt(math.max(discriminant, 0)) - local t1 = (-b - sqrt_d) / 2 - local t2 = (-b + sqrt_d) / 2 - - if t1 >= 0 and t1 <= len then - points[#points + 1] = segment.point1 + dir * t1 - end - if t2 >= 0 and t2 <= len and discriminant > 1e-10 then - points[#points + 1] = segment.point1 + dir * t2 - end - end - - if #points <= 1e-10 then - return false, nil - end - return true, points -end - ----检查圆是否与线段相交 ----@param circle foundation.shape.Circle 圆 ----@param segment foundation.shape.Segment 线段 ----@return boolean -function ShapeIntersector.circleHasIntersectionWithSegment(circle, segment) - local closest = segment:closestPoint(circle.center) - return (closest - circle.center):length() <= circle.radius + 1e-10 -end - ----检查直线与直线的相交 ----@param line1 foundation.shape.Line 第一条直线 ----@param line2 foundation.shape.Line 第二条直线 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.lineToLine(line1, line2) - local points = {} - local a = line1.point - local b = line1.point + line1.direction - local c = line2.point - local d = line2.point + line2.direction - - local dir_cross = line1.direction:cross(line2.direction) - if math.abs(dir_cross) <= 1e-10 then - local point_diff = line2.point - line1.point - if math.abs(point_diff:cross(line1.direction)) <= 1e-10 then - points[#points + 1] = line1.point:clone() - points[#points + 1] = line1:getPoint(1) - return true, points - else - return false, nil - end - end - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - - return true, points -end - ----检查直线是否与直线相交 ----@param line1 foundation.shape.Line 第一条直线 ----@param line2 foundation.shape.Line 第二条直线 ----@return boolean -function ShapeIntersector.lineHasIntersectionWithLine(line1, line2) - local dir_cross = line1.direction:cross(line2.direction) - if math.abs(dir_cross) <= 1e-10 then - local point_diff = line2.point - line1.point - return math.abs(point_diff:cross(line1.direction)) <= 1e-10 - end - return true -end - ----检查直线与射线的相交 ----@param line foundation.shape.Line 直线 ----@param ray foundation.shape.Ray 射线 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.lineToRay(line, ray) - local points = {} - local a = line.point - local b = line.point + line.direction - local c = ray.point - local d = ray.point + ray.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if u >= 0 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查直线是否与射线相交 ----@param line foundation.shape.Line 直线 ----@param ray foundation.shape.Ray 射线 ----@return boolean -function ShapeIntersector.lineHasIntersectionWithRay(line, ray) - local a = line.point - local b = line.point + line.direction - local c = ray.point - local d = ray.point + ray.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false - end - - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return u >= 0 -end - ----检查直线与圆的相交 ----@param line foundation.shape.Line 直线 ----@param circle foundation.shape.Circle 圆 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.lineToCircle(line, circle) - local points = {} - local dir = line.direction - local len = dir:length() - if len <= 1e-10 then - return false, nil - end - dir = dir / len - local L = line.point - circle.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - circle.radius * circle.radius - local discriminant = b * b - 4 * a * c - if discriminant >= 0 then - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - points[#points + 1] = line.point + dir * t1 - if math.abs(t2 - t1) > 1e-10 then - points[#points + 1] = line.point + dir * t2 - end - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查直线是否与圆相交 ----@param line foundation.shape.Line 直线 ----@param circle foundation.shape.Circle 圆 ----@return boolean -function ShapeIntersector.lineHasIntersectionWithCircle(line, circle) - local dir = line.direction - local len = dir:length() - if len <= 1e-10 then - return false - end - dir = dir / len - local L = line.point - circle.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - circle.radius * circle.radius - local discriminant = b * b - 4 * a * c - return discriminant >= 0 -end - ----检查射线与射线的相交 ----@param ray1 foundation.shape.Ray 第一条射线 ----@param ray2 foundation.shape.Ray 第二条射线 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.rayToRay(ray1, ray2) - local points = {} - local a = ray1.point - local b = ray1.point + ray1.direction - local c = ray2.point - local d = ray2.point + ray2.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - local dir_cross = ray1.direction:cross(ray2.direction) - if math.abs(dir_cross) < 1e-10 then - local point_diff = ray2.point - ray1.point - local t = point_diff:dot(ray1.direction) - if t >= 0 then - points[#points + 1] = ray1.point + ray1.direction * t - end - end - - if #points == 0 then - return false, nil - end - return true, points - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if t >= 0 and u >= 0 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查射线是否与射线相交 ----@param ray1 foundation.shape.Ray 第一条射线 ----@param ray2 foundation.shape.Ray 第二条射线 ----@return boolean -function ShapeIntersector.rayHasIntersectionWithRay(ray1, ray2) - local a = ray1.point - local b = ray1.point + ray1.direction - local c = ray2.point - local d = ray2.point + ray2.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - local dir_cross = ray1.direction:cross(ray2.direction) - if math.abs(dir_cross) <= 1e-10 then - local point_diff = ray2.point - ray1.point - local t = point_diff:dot(ray1.direction) - return t >= 0 - end - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and u >= 0 -end - ----检查射线与圆的相交 ----@param ray foundation.shape.Ray 射线 ----@param circle foundation.shape.Circle 圆 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.rayToCircle(ray, circle) - local points = {} - local dir = ray.direction - local len = dir:length() - if len <= 1e-10 then - return false, nil - end - dir = dir / len - local L = ray.point - circle.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - circle.radius * circle.radius - local discriminant = b * b - 4 * a * c - if discriminant >= 0 then - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - if t1 >= 0 then - points[#points + 1] = ray.point + dir * t1 - end - if t2 >= 0 and math.abs(t2 - t1) > 1e-10 then - points[#points + 1] = ray.point + dir * t2 - end - end - - if #points == 0 then - return false, nil - end - return true, points -end - ----检查射线是否与圆相交 ----@param ray foundation.shape.Ray 射线 ----@param circle foundation.shape.Circle 圆 ----@return boolean -function ShapeIntersector.rayHasIntersectionWithCircle(ray, circle) - local dir = ray.direction - local len = dir:length() - if len <= 1e-10 then - return false - end - dir = dir / len - local L = ray.point - circle.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - circle.radius * circle.radius - local discriminant = b * b - 4 * a * c - - if discriminant <= 1e-10 then - return false - end - - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - - return t1 >= 0 or t2 >= 0 -end - ----检查圆与圆的相交 ----@param circle1 foundation.shape.Circle 第一个圆 ----@param circle2 foundation.shape.Circle 第二个圆 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.circleToCircle(circle1, circle2) - local points = {} - local d = (circle1.center - circle2.center):length() - if d <= circle1.radius + circle2.radius and d >= math.abs(circle1.radius - circle2.radius) then - local a = (circle1.radius * circle1.radius - circle2.radius * circle2.radius + d * d) / (2 * d) - local h = math.sqrt(circle1.radius * circle1.radius - a * a) - local p2 = circle1.center + (circle2.center - circle1.center) * (a / d) - local perp = Vector2.create(-(circle2.center.y - circle1.center.y), circle2.center.x - circle1.center.x):normalized() * h - points[#points + 1] = p2 + perp - if math.abs(h) > 1e-10 then - points[#points + 1] = p2 - perp - end - end - - if #points <= 1e-10 then - return false, nil - end - return true, points -end - ----检查圆是否与圆相交 ----@param circle1 foundation.shape.Circle 第一个圆 ----@param circle2 foundation.shape.Circle 第二个圆 ----@return boolean -function ShapeIntersector.circleHasIntersectionWithCircle(circle1, circle2) - local d = (circle1.center - circle2.center):length() - return d <= circle1.radius + circle2.radius and d >= math.abs(circle1.radius - circle2.radius) -end - ----检查矩形与线段的相交 ----@param rectangle foundation.shape.Rectangle 矩形 ----@param segment foundation.shape.Segment 线段 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.rectangleToSegment(rectangle, segment) - local points = {} - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.segmentToSegment(segment, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if rectangle:contains(segment.point1) then - points[#points + 1] = segment.point1:clone() - end - if segment.point1 ~= segment.point2 and rectangle:contains(segment.point2) then - points[#points + 1] = segment.point2:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查矩形是否与线段相交 ----@param rectangle foundation.shape.Rectangle 矩形 ----@param segment foundation.shape.Segment 线段 ----@return boolean -function ShapeIntersector.rectangleHasIntersectionWithSegment(rectangle, segment) - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then - return true - end - end - return rectangle:contains(segment.point1) or rectangle:contains(segment.point2) -end - ----检查矩形与三角形的相交 ----@param rectangle foundation.shape.Rectangle 矩形 ----@param triangle foundation.shape.Triangle 三角形 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.rectangleToTriangle(rectangle, triangle) - local points = {} - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.triangleToSegment(triangle, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if rectangle:contains(vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查矩形是否与三角形相交 ----@param rectangle foundation.shape.Rectangle 矩形 ----@param triangle foundation.shape.Triangle 三角形 ----@return boolean -function ShapeIntersector.rectangleHasIntersectionWithTriangle(rectangle, triangle) - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.triangleHasIntersectionWithSegment(triangle, edge) then - return true - end - end - return rectangle:contains(triangle.point1) or rectangle:contains(triangle.point2) or rectangle:contains(triangle.point3) -end - ----检查矩形与直线的相交 ----@param rectangle foundation.shape.Rectangle 矩形 ----@param line foundation.shape.Line 直线 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.rectangleToLine(rectangle, line) - local points = {} - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.lineToSegment(line, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查矩形是否与直线相交 ----@param rectangle foundation.shape.Rectangle 矩形 ----@param line foundation.shape.Line 直线 ----@return boolean -function ShapeIntersector.rectangleHasIntersectionWithLine(rectangle, line) - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then - return true - end - end - return rectangle:contains(line.point) -end - ----检查矩形与射线的相交 ----@param rectangle foundation.shape.Rectangle 矩形 ----@param ray foundation.shape.Ray 射线 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.rectangleToRay(rectangle, ray) - local points = {} - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - if rectangle:contains(ray.point) then - points[#points + 1] = ray.point:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查矩形是否与射线相交 ----@param rectangle foundation.shape.Rectangle 矩形 ----@param ray foundation.shape.Ray 射线 ----@return boolean -function ShapeIntersector.rectangleHasIntersectionWithRay(rectangle, ray) - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then - return true - end - end - return rectangle:contains(ray.point) -end - ----检查矩形与圆的相交 ----@param rectangle foundation.shape.Rectangle 矩形 ----@param circle foundation.shape.Circle 圆 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.rectangleToCircle(rectangle, circle) - local points = {} - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - points[#points + 1] = vertex - end - end - - if rectangle:contains(circle.center) then - points[#points + 1] = circle.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查矩形是否与圆相交 ----@param rectangle foundation.shape.Rectangle 矩形 ----@param circle foundation.shape.Circle 圆 ----@return boolean -function ShapeIntersector.rectangleHasIntersectionWithCircle(rectangle, circle) - if rectangle:contains(circle.center) then - return true - end - - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then - return true - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - return true - end - end - - return false -end - ----检查矩形与矩形的相交 ----@param rectangle1 foundation.shape.Rectangle 第一个矩形 ----@param rectangle2 foundation.shape.Rectangle 第二个矩形 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.rectangleToRectangle(rectangle1, rectangle2) - local points = {} - local edges1 = rectangle1:getEdges() - local edges2 = rectangle2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - - local vertices1 = rectangle1:getVertices() - for _, vertex in ipairs(vertices1) do - if rectangle2:contains(vertex) then - points[#points + 1] = vertex - end - end - - local vertices2 = rectangle2:getVertices() - for _, vertex in ipairs(vertices2) do - if rectangle1:contains(vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查矩形是否与矩形相交 ----@param rectangle1 foundation.shape.Rectangle 第一个矩形 ----@param rectangle2 foundation.shape.Rectangle 第二个矩形 ----@return boolean -function ShapeIntersector.rectangleHasIntersectionWithRectangle(rectangle1, rectangle2) - local edges1 = rectangle1:getEdges() - local edges2 = rectangle2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then - return true - end - end - end - - local vertices1 = rectangle1:getVertices() - for _, vertex in ipairs(vertices1) do - if rectangle2:contains(vertex) then - return true - end - end - - local vertices2 = rectangle2:getVertices() - for _, vertex in ipairs(vertices2) do - if rectangle1:contains(vertex) then - return true - end - end - - return false -end - ----检查扇形与线段的相交 ----@param sector foundation.shape.Sector 扇形 ----@param segment foundation.shape.Segment 线段 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.sectorToSegment(sector, segment) - Segment = Segment or require("foundation.shape.Segment") - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleToSegment(sector, segment) - end - local success, circle_points = ShapeIntersector.circleToSegment(sector, segment) - if success then - local n = 0 - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - n = n + 1 - points[#points + 1] = p - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - local success1, edge_points1 = ShapeIntersector.segmentToSegment(startSegment, segment) - if success1 then - for _, p in ipairs(edge_points1) do - points[#points + 1] = p - end - end - local success2, edge_points2 = ShapeIntersector.segmentToSegment(endSegment, segment) - if success2 then - for _, p in ipairs(edge_points2) do - points[#points + 1] = p - end - end - if ShapeIntersector.sectorContainsPoint(sector, segment.point1) then - points[#points + 1] = segment.point1:clone() - end - if segment.point1 ~= segment.point2 and ShapeIntersector.sectorContainsPoint(sector, segment.point2) then - points[#points + 1] = segment.point2:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查扇形是否与线段相交 ----@param sector foundation.shape.Sector 扇形 ----@param segment foundation.shape.Segment 线段 ----@return boolean -function ShapeIntersector.sectorHasIntersectionWithSegment(sector, segment) - Segment = Segment or require("foundation.shape.Segment") - if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleHasIntersectionWithSegment(sector, segment) - end - if ShapeIntersector.sectorContainsPoint(sector, segment.point1) or - ShapeIntersector.sectorContainsPoint(sector, segment.point2) then - return true - end - local success, circle_points = ShapeIntersector.circleToSegment(sector, segment) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - return true - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - if ShapeIntersector.segmentHasIntersectionWithSegment(startSegment, segment) or - ShapeIntersector.segmentHasIntersectionWithSegment(endSegment, segment) then - return true - end - return false -end - ----检查扇形与直线的相交 ----@param sector foundation.shape.Sector 扇形 ----@param line foundation.shape.Line 直线 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.sectorToLine(sector, line) - Segment = Segment or require("foundation.shape.Segment") - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.lineToCircle(line, sector) - end - local success, circle_points = ShapeIntersector.lineToCircle(line, sector) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - points[#points + 1] = p - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - local success1, edge_points1 = ShapeIntersector.lineToSegment(line, startSegment) - if success1 then - for _, p in ipairs(edge_points1) do - points[#points + 1] = p - end - end - local success2, edge_points2 = ShapeIntersector.lineToSegment(line, endSegment) - if success2 then - for _, p in ipairs(edge_points2) do - points[#points + 1] = p - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查扇形是否与直线相交 ----@param sector foundation.shape.Sector 扇形 ----@param line foundation.shape.Line 直线 ----@return boolean -function ShapeIntersector.sectorHasIntersectionWithLine(sector, line) - Segment = Segment or require("foundation.shape.Segment") - if math.abs(sector.range) >= 1 then - return ShapeIntersector.lineHasIntersectionWithCircle(line, sector) - end - local success, circle_points = ShapeIntersector.lineToCircle(line, sector) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - return true - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - if ShapeIntersector.lineHasIntersectionWithSegment(line, startSegment) or - ShapeIntersector.lineHasIntersectionWithSegment(line, endSegment) then - return true - end - return false -end - ----检查扇形与射线的相交 ----@param sector foundation.shape.Sector 扇形 ----@param ray foundation.shape.Ray 射线 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.sectorToRay(sector, ray) - Segment = Segment or require("foundation.shape.Segment") - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.rayToCircle(ray, sector) - end - local success, circle_points = ShapeIntersector.rayToCircle(ray, sector) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - points[#points + 1] = p - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - local success1, edge_points1 = ShapeIntersector.rayToSegment(ray, startSegment) - if success1 then - for _, p in ipairs(edge_points1) do - points[#points + 1] = p - end - end - local success2, edge_points2 = ShapeIntersector.rayToSegment(ray, endSegment) - if success2 then - for _, p in ipairs(edge_points2) do - points[#points + 1] = p - end - end - if ShapeIntersector.sectorContainsPoint(sector, ray.point) then - points[#points + 1] = ray.point:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查扇形是否与射线相交 ----@param sector foundation.shape.Sector 扇形 ----@param ray foundation.shape.Ray 射线 ----@return boolean -function ShapeIntersector.sectorHasIntersectionWithRay(sector, ray) - Segment = Segment or require("foundation.shape.Segment") - if math.abs(sector.range) >= 1 then - return ShapeIntersector.rayHasIntersectionWithCircle(ray, sector) - end - if ShapeIntersector.sectorContainsPoint(sector, ray.point) then - return true - end - local success, circle_points = ShapeIntersector.rayToCircle(ray, sector) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - return true - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - if ShapeIntersector.rayHasIntersectionWithSegment(ray, startSegment) or - ShapeIntersector.rayHasIntersectionWithSegment(ray, endSegment) then - return true - end - return false -end - ----检查扇形与三角形的相交 ----@param sector foundation.shape.Sector 扇形 ----@param triangle foundation.shape.Triangle 三角形 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.sectorToTriangle(sector, triangle) - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.triangleToCircle(triangle, sector) - end - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - points[#points + 1] = vertex - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, sector.center) then - points[#points + 1] = sector.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查扇形是否与三角形相交 ----@param sector foundation.shape.Sector 扇形 ----@param triangle foundation.shape.Triangle 三角形 ----@return boolean -function ShapeIntersector.sectorHasIntersectionWithTriangle(sector, triangle) - if math.abs(sector.range) >= 1 then - return ShapeIntersector.triangleHasIntersectionWithCircle(triangle, sector) - end - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then - return true - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - return true - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, sector.center) then - return true - end - - return false -end - ----检查扇形与圆的相交 ----@param sector foundation.shape.Sector 扇形 ----@param circle foundation.shape.Circle 圆 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.sectorToCircle(sector, circle) - Segment = Segment or require("foundation.shape.Segment") - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleToCircle(sector, circle) - end - local success, circle_points = ShapeIntersector.circleToCircle(sector, circle) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - points[#points + 1] = p - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - local success1, edge_points1 = ShapeIntersector.circleToSegment(circle, startSegment) - if success1 then - for _, p in ipairs(edge_points1) do - points[#points + 1] = p - end - end - local success2, edge_points2 = ShapeIntersector.circleToSegment(circle, endSegment) - if success2 then - for _, p in ipairs(edge_points2) do - points[#points + 1] = p - end - end - if ShapeIntersector.sectorContainsPoint(sector, circle.center) then - points[#points + 1] = circle.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查扇形是否与圆相交 ----@param sector foundation.shape.Sector 扇形 ----@param circle foundation.shape.Circle 圆 ----@return boolean -function ShapeIntersector.sectorHasIntersectionWithCircle(sector, circle) - Segment = Segment or require("foundation.shape.Segment") - if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleHasIntersectionWithCircle(sector, circle) - end - if ShapeIntersector.sectorContainsPoint(sector, circle.center) then - return true - end - local success, circle_points = ShapeIntersector.circleToCircle(sector, circle) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - return true - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - if ShapeIntersector.circleHasIntersectionWithSegment(circle, startSegment) or - ShapeIntersector.circleHasIntersectionWithSegment(circle, endSegment) then - return true - end - return false -end - ----检查扇形与矩形的相交 ----@param sector foundation.shape.Sector 扇形 ----@param rectangle foundation.shape.Rectangle 矩形 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.sectorToRectangle(sector, rectangle) - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.rectangleToCircle(rectangle, sector) - end - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - points[#points + 1] = vertex - end - end - - if ShapeIntersector.rectangleContainsPoint(rectangle, sector.center) then - points[#points + 1] = sector.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查扇形是否与矩形相交 ----@param sector foundation.shape.Sector 扇形 ----@param rectangle foundation.shape.Rectangle 矩形 ----@return boolean -function ShapeIntersector.sectorHasIntersectionWithRectangle(sector, rectangle) - if math.abs(sector.range) >= 1 then - return ShapeIntersector.rectangleHasIntersectionWithCircle(rectangle, sector) - end - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then - return true - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - return true - end - end - - if ShapeIntersector.rectangleContainsPoint(rectangle, sector.center) then - return true - end - - return false -end - ----检查扇形与扇形的相交 ----@param sector1 foundation.shape.Sector 第一个扇形 ----@param sector2 foundation.shape.Sector 第二个扇形 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.sectorToSector(sector1, sector2) - Segment = Segment or require("foundation.shape.Segment") - local points = {} - if math.abs(sector1.range) >= 1 and math.abs(sector2.range) >= 1 then - return ShapeIntersector.circleToCircle(sector1, sector2) - end - local success, circle_points = ShapeIntersector.circleToCircle(sector1, sector2) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then - points[#points + 1] = p - end - end - end - if math.abs(sector1.range) < 1 then - local startDir1 = sector1.direction - local endDir1 = sector1.direction:rotated(sector1.range * 2 * math.pi) - local startPoint1 = sector1.center + startDir1 * sector1.radius - local endPoint1 = sector1.center + endDir1 * sector1.radius - local startSegment1 = Segment.create(sector1.center, startPoint1) - local endSegment1 = Segment.create(sector1.center, endPoint1) - local success1, edge_points1 = ShapeIntersector.sectorToSegment(sector2, startSegment1) - if success1 then - for _, p in ipairs(edge_points1) do - points[#points + 1] = p - end - end - local success2, edge_points2 = ShapeIntersector.sectorToSegment(sector2, endSegment1) - if success2 then - for _, p in ipairs(edge_points2) do - points[#points + 1] = p - end - end - end - if math.abs(sector2.range) < 1 then - local startDir2 = sector2.direction - local endDir2 = sector2.direction:rotated(sector2.range * 2 * math.pi) - local startPoint2 = sector2.center + startDir2 * sector2.radius - local endPoint2 = sector2.center + endDir2 * sector2.radius - local startSegment2 = Segment.create(sector2.center, startPoint2) - local endSegment2 = Segment.create(sector2.center, endPoint2) - local success3, edge_points3 = ShapeIntersector.sectorToSegment(sector1, startSegment2) - if success3 then - for _, p in ipairs(edge_points3) do - points[#points + 1] = p - end - end - local success4, edge_points4 = ShapeIntersector.sectorToSegment(sector1, endSegment2) - if success4 then - for _, p in ipairs(edge_points4) do - points[#points + 1] = p - end - end - end - if ShapeIntersector.sectorContainsPoint(sector1, sector2.center) then - points[#points + 1] = sector2.center:clone() - end - if ShapeIntersector.sectorContainsPoint(sector2, sector1.center) then - points[#points + 1] = sector1.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points -end - ----仅检查扇形是否与扇形相交 ----@param sector1 foundation.shape.Sector 第一个扇形 ----@param sector2 foundation.shape.Sector 第二个扇形 ----@return boolean -function ShapeIntersector.sectorHasIntersectionWithSector(sector1, sector2) - Segment = Segment or require("foundation.shape.Segment") - if math.abs(sector1.range) >= 1 and math.abs(sector2.range) >= 1 then - return ShapeIntersector.circleHasIntersectionWithCircle(sector1, sector2) - end - if ShapeIntersector.sectorContainsPoint(sector1, sector2.center) or - ShapeIntersector.sectorContainsPoint(sector2, sector1.center) then - return true - end - local success, circle_points = ShapeIntersector.circleToCircle(sector1, sector2) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then - return true - end - end - end - if math.abs(sector1.range) < 1 then - local startDir1 = sector1.direction - local endDir1 = sector1.direction:rotated(sector1.range * 2 * math.pi) - local startPoint1 = sector1.center + startDir1 * sector1.radius - local endPoint1 = sector1.center + endDir1 * sector1.radius - local startSegment1 = Segment.create(sector1.center, startPoint1) - local endSegment1 = Segment.create(sector1.center, endPoint1) - if ShapeIntersector.sectorHasIntersectionWithSegment(sector2, startSegment1) or - ShapeIntersector.sectorHasIntersectionWithSegment(sector2, endSegment1) then - return true - end - end - if math.abs(sector2.range) < 1 then - local startDir2 = sector2.direction - local endDir2 = sector2.direction:rotated(sector2.range * 2 * math.pi) - local startPoint2 = sector2.center + startDir2 * sector2.radius - local endPoint2 = sector2.center + endDir2 * sector2.radius - local startSegment2 = Segment.create(sector2.center, startPoint2) - local endSegment2 = Segment.create(sector2.center, endPoint2) - if ShapeIntersector.sectorHasIntersectionWithSegment(sector1, startSegment2) or - ShapeIntersector.sectorHasIntersectionWithSegment(sector1, endSegment2) then - return true - end - end - return false -end - ----检查多边形与扇形的相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param sector foundation.shape.Sector 扇形 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.polygonToSector(polygon, sector) - local points = {} - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - points[#points + 1] = vertex - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, sector.center) then - points[#points + 1] = sector.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) +---@class foundation.shape.ShapeIntersector +local ShapeIntersector = {} - if #unique_points == 0 then - return false, nil - end - return true, unique_points +local ShapeIntersectorList = { + "ContainPoint", + "CircleIntersector", + "LineIntersector", + "PolygonIntersector", + "RectangleIntersector", + "SectorIntersector", + "TriangleIntersector", + "SegmentIntersector", + "RayIntersector", +} +for _, moduleName in ipairs(ShapeIntersectorList) do + local module = require(string.format("foundation.shape.ShapeIntersector.%s", moduleName)) + module(ShapeIntersector) end ----仅检查多边形是否与扇形相交 ----@param polygon foundation.shape.Polygon 多边形 ----@param sector foundation.shape.Sector 扇形 ----@return boolean -function ShapeIntersector.polygonHasIntersectionWithSector(polygon, sector) - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then - return true - end - end - - local vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - return true +---整理相交点,去除重复点 +---@param points foundation.math.Vector2[] 原始点列表 +---@return foundation.math.Vector2[] 去重后的点列表 +function ShapeIntersector.getUniquePoints(points) + local unique_points = {} + local seen = {} + for _, p in ipairs(points) do + local key = tostring(p.x) .. "," .. tostring(p.y) + if not seen[key] then + seen[key] = true + unique_points[#unique_points + 1] = p end end - - return ShapeIntersector.polygonContainsPoint(polygon, sector.center) + return unique_points end ---@type table> local intersectionMap = { ["foundation.shape.Polygon"] = { - ["foundation.shape.Segment"] = ShapeIntersector.polygonToSegment, - ["foundation.shape.Line"] = ShapeIntersector.polygonToLine, - ["foundation.shape.Ray"] = ShapeIntersector.polygonToRay, + ["foundation.shape.Polygon"] = ShapeIntersector.polygonToPolygon, ["foundation.shape.Triangle"] = ShapeIntersector.polygonToTriangle, - ["foundation.shape.Circle"] = ShapeIntersector.polygonToCircle, ["foundation.shape.Rectangle"] = ShapeIntersector.polygonToRectangle, + ["foundation.shape.Circle"] = ShapeIntersector.polygonToCircle, + ["foundation.shape.Line"] = ShapeIntersector.polygonToLine, + ["foundation.shape.Ray"] = ShapeIntersector.polygonToRay, + ["foundation.shape.Segment"] = ShapeIntersector.polygonToSegment, ["foundation.shape.Sector"] = ShapeIntersector.polygonToSector, - ["foundation.shape.Polygon"] = ShapeIntersector.polygonToPolygon, }, ["foundation.shape.Sector"] = { - ["foundation.shape.Segment"] = ShapeIntersector.sectorToSegment, - ["foundation.shape.Line"] = ShapeIntersector.sectorToLine, - ["foundation.shape.Ray"] = ShapeIntersector.sectorToRay, + ["foundation.shape.Sector"] = ShapeIntersector.sectorToSector, + ["foundation.shape.Rectangle"] = ShapeIntersector.sectorToRectangle, ["foundation.shape.Triangle"] = ShapeIntersector.sectorToTriangle, ["foundation.shape.Circle"] = ShapeIntersector.sectorToCircle, - ["foundation.shape.Rectangle"] = ShapeIntersector.sectorToRectangle, - ["foundation.shape.Sector"] = ShapeIntersector.sectorToSector, + ["foundation.shape.Line"] = ShapeIntersector.sectorToLine, + ["foundation.shape.Ray"] = ShapeIntersector.sectorToRay, + ["foundation.shape.Segment"] = ShapeIntersector.sectorToSegment, }, ["foundation.shape.Rectangle"] = { + ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleToRectangle, ["foundation.shape.Triangle"] = ShapeIntersector.rectangleToTriangle, - ["foundation.shape.Segment"] = ShapeIntersector.rectangleToSegment, + ["foundation.shape.Circle"] = ShapeIntersector.rectangleToCircle, ["foundation.shape.Line"] = ShapeIntersector.rectangleToLine, ["foundation.shape.Ray"] = ShapeIntersector.rectangleToRay, - ["foundation.shape.Circle"] = ShapeIntersector.rectangleToCircle, - ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleToRectangle, + ["foundation.shape.Segment"] = ShapeIntersector.rectangleToSegment, }, ["foundation.shape.Triangle"] = { - ["foundation.shape.Segment"] = ShapeIntersector.triangleToSegment, ["foundation.shape.Triangle"] = ShapeIntersector.triangleToTriangle, + ["foundation.shape.Circle"] = ShapeIntersector.triangleToCircle, ["foundation.shape.Line"] = ShapeIntersector.triangleToLine, ["foundation.shape.Ray"] = ShapeIntersector.triangleToRay, - ["foundation.shape.Circle"] = ShapeIntersector.triangleToCircle, + ["foundation.shape.Segment"] = ShapeIntersector.triangleToSegment, }, ["foundation.shape.Line"] = { - ["foundation.shape.Segment"] = ShapeIntersector.lineToSegment, ["foundation.shape.Line"] = ShapeIntersector.lineToLine, ["foundation.shape.Ray"] = ShapeIntersector.lineToRay, + ["foundation.shape.Segment"] = ShapeIntersector.lineToSegment, ["foundation.shape.Circle"] = ShapeIntersector.lineToCircle, }, ["foundation.shape.Ray"] = { - ["foundation.shape.Segment"] = ShapeIntersector.rayToSegment, ["foundation.shape.Ray"] = ShapeIntersector.rayToRay, + ["foundation.shape.Segment"] = ShapeIntersector.rayToSegment, ["foundation.shape.Circle"] = ShapeIntersector.rayToCircle, }, ["foundation.shape.Circle"] = { - ["foundation.shape.Segment"] = ShapeIntersector.circleToSegment, ["foundation.shape.Circle"] = ShapeIntersector.circleToCircle, + ["foundation.shape.Segment"] = ShapeIntersector.circleToSegment, }, ["foundation.shape.Segment"] = { ["foundation.shape.Segment"] = ShapeIntersector.segmentToSegment, }, } ----检查与其他形状的相交 ----@param shape1 any 第一个形状 ----@param shape2 any 第二个形状 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.intersect(shape1, shape2) - local type1 = shape1.__type - local type2 = shape2.__type - - local intersectionFunc = intersectionMap[type1] and intersectionMap[type1][type2] - if intersectionFunc then - return intersectionFunc(shape1, shape2) - end - - intersectionFunc = intersectionMap[type2] and intersectionMap[type2][type1] - if intersectionFunc then - return intersectionFunc(shape2, shape1) - end - - return false, nil -end - ---@type table> local hasIntersectionMap = { ["foundation.shape.Polygon"] = { - ["foundation.shape.Segment"] = ShapeIntersector.polygonHasIntersectionWithSegment, - ["foundation.shape.Line"] = ShapeIntersector.polygonHasIntersectionWithLine, - ["foundation.shape.Ray"] = ShapeIntersector.polygonHasIntersectionWithRay, + ["foundation.shape.Polygon"] = ShapeIntersector.polygonHasIntersectionWithPolygon, ["foundation.shape.Triangle"] = ShapeIntersector.polygonHasIntersectionWithTriangle, - ["foundation.shape.Circle"] = ShapeIntersector.polygonHasIntersectionWithCircle, ["foundation.shape.Rectangle"] = ShapeIntersector.polygonHasIntersectionWithRectangle, + ["foundation.shape.Circle"] = ShapeIntersector.polygonHasIntersectionWithCircle, + ["foundation.shape.Line"] = ShapeIntersector.polygonHasIntersectionWithLine, + ["foundation.shape.Ray"] = ShapeIntersector.polygonHasIntersectionWithRay, + ["foundation.shape.Segment"] = ShapeIntersector.polygonHasIntersectionWithSegment, ["foundation.shape.Sector"] = ShapeIntersector.polygonHasIntersectionWithSector, - ["foundation.shape.Polygon"] = ShapeIntersector.polygonHasIntersectionWithPolygon, }, ["foundation.shape.Sector"] = { - ["foundation.shape.Segment"] = ShapeIntersector.sectorHasIntersectionWithSegment, - ["foundation.shape.Line"] = ShapeIntersector.sectorHasIntersectionWithLine, - ["foundation.shape.Ray"] = ShapeIntersector.sectorHasIntersectionWithRay, + ["foundation.shape.Sector"] = ShapeIntersector.sectorHasIntersectionWithSector, + ["foundation.shape.Rectangle"] = ShapeIntersector.sectorHasIntersectionWithRectangle, ["foundation.shape.Triangle"] = ShapeIntersector.sectorHasIntersectionWithTriangle, ["foundation.shape.Circle"] = ShapeIntersector.sectorHasIntersectionWithCircle, - ["foundation.shape.Rectangle"] = ShapeIntersector.sectorHasIntersectionWithRectangle, - ["foundation.shape.Sector"] = ShapeIntersector.sectorHasIntersectionWithSector, + ["foundation.shape.Line"] = ShapeIntersector.sectorHasIntersectionWithLine, + ["foundation.shape.Ray"] = ShapeIntersector.sectorHasIntersectionWithRay, + ["foundation.shape.Segment"] = ShapeIntersector.sectorHasIntersectionWithSegment, }, ["foundation.shape.Rectangle"] = { + ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleHasIntersectionWithRectangle, ["foundation.shape.Triangle"] = ShapeIntersector.rectangleHasIntersectionWithTriangle, - ["foundation.shape.Segment"] = ShapeIntersector.rectangleHasIntersectionWithSegment, + ["foundation.shape.Circle"] = ShapeIntersector.rectangleHasIntersectionWithCircle, ["foundation.shape.Line"] = ShapeIntersector.rectangleHasIntersectionWithLine, ["foundation.shape.Ray"] = ShapeIntersector.rectangleHasIntersectionWithRay, - ["foundation.shape.Circle"] = ShapeIntersector.rectangleHasIntersectionWithCircle, - ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleHasIntersectionWithRectangle, + ["foundation.shape.Segment"] = ShapeIntersector.rectangleHasIntersectionWithSegment, }, ["foundation.shape.Triangle"] = { - ["foundation.shape.Segment"] = ShapeIntersector.triangleHasIntersectionWithSegment, ["foundation.shape.Triangle"] = ShapeIntersector.triangleHasIntersectionWithTriangle, + ["foundation.shape.Circle"] = ShapeIntersector.triangleHasIntersectionWithCircle, ["foundation.shape.Line"] = ShapeIntersector.triangleHasIntersectionWithLine, ["foundation.shape.Ray"] = ShapeIntersector.triangleHasIntersectionWithRay, - ["foundation.shape.Circle"] = ShapeIntersector.triangleHasIntersectionWithCircle, + ["foundation.shape.Segment"] = ShapeIntersector.triangleHasIntersectionWithSegment, }, ["foundation.shape.Line"] = { - ["foundation.shape.Segment"] = ShapeIntersector.lineHasIntersectionWithSegment, ["foundation.shape.Line"] = ShapeIntersector.lineHasIntersectionWithLine, ["foundation.shape.Ray"] = ShapeIntersector.lineHasIntersectionWithRay, + ["foundation.shape.Segment"] = ShapeIntersector.lineHasIntersectionWithSegment, ["foundation.shape.Circle"] = ShapeIntersector.lineHasIntersectionWithCircle, }, ["foundation.shape.Ray"] = { - ["foundation.shape.Segment"] = ShapeIntersector.rayHasIntersectionWithSegment, ["foundation.shape.Ray"] = ShapeIntersector.rayHasIntersectionWithRay, + ["foundation.shape.Segment"] = ShapeIntersector.rayHasIntersectionWithSegment, ["foundation.shape.Circle"] = ShapeIntersector.rayHasIntersectionWithCircle, }, ["foundation.shape.Circle"] = { - ["foundation.shape.Segment"] = ShapeIntersector.circleHasIntersectionWithSegment, ["foundation.shape.Circle"] = ShapeIntersector.circleHasIntersectionWithCircle, + ["foundation.shape.Segment"] = ShapeIntersector.circleHasIntersectionWithSegment, }, ["foundation.shape.Segment"] = { ["foundation.shape.Segment"] = ShapeIntersector.segmentHasIntersectionWithSegment, }, } +---检查与其他形状的相交 +---@param shape1 any 第一个形状 +---@param shape2 any 第二个形状 +---@return boolean, foundation.math.Vector2[] | nil +function ShapeIntersector.intersect(shape1, shape2) + local type1 = shape1.__type + local type2 = shape2.__type + + local intersectionFunc = intersectionMap[type1] and intersectionMap[type1][type2] + if intersectionFunc then + return intersectionFunc(shape1, shape2) + end + + intersectionFunc = intersectionMap[type2] and intersectionMap[type2][type1] + if intersectionFunc then + return intersectionFunc(shape2, shape1) + end + + return false, nil +end + ---只检查是否与其他形状相交 ---@param shape1 any 第一个形状 ---@param shape2 any 第二个形状 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/CircleIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/CircleIntersector.lua new file mode 100644 index 00000000..5dbc58d6 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/CircleIntersector.lua @@ -0,0 +1,98 @@ +---@param ShapeIntersector foundation.shape.ShapeIntersector +return function(ShapeIntersector) + local math = math + + local Vector2 = require("foundation.math.Vector2") + + ---检查圆与线段的相交 + ---@param circle foundation.shape.Circle 圆 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.circleToSegment(circle, segment) + local points = {} + local dir = segment.point2 - segment.point1 + local len = dir:length() + + if len <= 1e-10 then + if ShapeIntersector.circleContainsPoint(circle, segment.point1) then + return true, { segment.point1:clone() } + end + return false, nil + end + + dir = dir / len + + local closest = segment:closestPoint(circle.center) + local dist = (closest - circle.center):length() + if dist > circle.radius + 1e-10 then + return false, nil + end + + local vector_to_start = segment.point1 - circle.center + local b = 2 * vector_to_start:dot(dir) + local c = vector_to_start:dot(vector_to_start) - circle.radius * circle.radius + local discriminant = b * b - 4 * c + + if discriminant >= -1e-10 then + local sqrt_d = math.sqrt(math.max(discriminant, 0)) + local t1 = (-b - sqrt_d) / 2 + local t2 = (-b + sqrt_d) / 2 + + if t1 >= 0 and t1 <= len then + points[#points + 1] = segment.point1 + dir * t1 + end + if t2 >= 0 and t2 <= len and discriminant > 1e-10 then + points[#points + 1] = segment.point1 + dir * t2 + end + end + + if #points <= 1e-10 then + return false, nil + end + return true, points + end + + ---检查圆是否与线段相交 + ---@param circle foundation.shape.Circle 圆 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean + function ShapeIntersector.circleHasIntersectionWithSegment(circle, segment) + local closest = segment:closestPoint(circle.center) + return (closest - circle.center):length() <= circle.radius + 1e-10 + end + + ---检查圆与圆的相交 + ---@param circle1 foundation.shape.Circle 第一个圆 + ---@param circle2 foundation.shape.Circle 第二个圆 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.circleToCircle(circle1, circle2) + local points = {} + local d = (circle1.center - circle2.center):length() + if d <= circle1.radius + circle2.radius and d >= math.abs(circle1.radius - circle2.radius) then + local a = (circle1.radius * circle1.radius - circle2.radius * circle2.radius + d * d) / (2 * d) + local h = math.sqrt(circle1.radius * circle1.radius - a * a) + local p2 = circle1.center + (circle2.center - circle1.center) * (a / d) + local perp = Vector2.create(-(circle2.center.y - circle1.center.y), circle2.center.x - circle1.center.x):normalized() * h + points[#points + 1] = p2 + perp + if math.abs(h) > 1e-10 then + points[#points + 1] = p2 - perp + end + end + + if #points <= 1e-10 then + return false, nil + end + return true, points + end + + ---检查圆是否与圆相交 + ---@param circle1 foundation.shape.Circle 第一个圆 + ---@param circle2 foundation.shape.Circle 第二个圆 + ---@return boolean + function ShapeIntersector.circleHasIntersectionWithCircle(circle1, circle2) + local d = (circle1.center - circle2.center):length() + return d <= circle1.radius + circle2.radius and d >= math.abs(circle1.radius - circle2.radius) + end + + return ShapeIntersector +end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua new file mode 100644 index 00000000..552ded8d --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua @@ -0,0 +1,138 @@ +---@param ShapeIntersector foundation.shape.ShapeIntersector +return function(ShapeIntersector) + local math = math + local require = require + + local Vector2 = require("foundation.math.Vector2") + + local Segment + + ---检查点是否在圆内或圆上 + ---@param circle foundation.shape.Circle 圆形 + ---@param point foundation.math.Vector2 点 + ---@return boolean + function ShapeIntersector.circleContainsPoint(circle, point) + return (point - circle.center):length() <= circle.radius + 1e-10 + end + + ---检查点是否在矩形内(包括边界) + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@param point foundation.math.Vector2 点 + ---@return boolean + function ShapeIntersector.rectangleContainsPoint(rectangle, point) + local p = point - rectangle.center + local dir = rectangle.direction + local perp = Vector2.create(-dir.y, dir.x) + local x = p.x * dir.x + p.y * dir.y + local y = p.x * perp.x + p.y * perp.y + local hw, hh = rectangle.width / 2, rectangle.height / 2 + return math.abs(x) <= hw + 1e-10 and math.abs(y) <= hh + 1e-10 + end + + ---检查点是否在扇形内(包括边界) + ---@param sector foundation.shape.Sector 扇形 + ---@param point foundation.math.Vector2 点 + ---@return boolean + function ShapeIntersector.sectorContainsPoint(sector, point) + local inCircle = ShapeIntersector.circleContainsPoint(sector, point) + if not inCircle then + return false + end + if math.abs(sector.range) >= 1 then + return true + end + + local range = sector.range * math.pi * 2 + local angle_begin + if range > 0 then + angle_begin = sector.direction:angle() + else + range = -range + angle_begin = sector.direction:angle() - range + end + + local vec = point - sector.center + local vec_angle = vec:angle() + vec_angle = vec_angle - 2 * math.pi * math.floor((vec_angle - angle_begin) / (2 * math.pi)) + return angle_begin <= vec_angle and vec_angle <= angle_begin + range + end + + ---检查点是否在三角形内 + ---@param triangle foundation.shape.Triangle 三角形 + ---@param point foundation.math.Vector2 点 + ---@return boolean + function ShapeIntersector.triangleContainsPoint(triangle, point) + local v1 = triangle.point1 + local v2 = triangle.point2 + local v3 = triangle.point3 + local p = point + + local v3v1 = v3 - v1 + local v2v1 = v2 - v1 + local pv1 = p - v1 + + local dot00 = v3v1:dot(v3v1) + local dot01 = v3v1:dot(v2v1) + local dot02 = v3v1:dot(pv1) + local dot11 = v2v1:dot(v2v1) + local dot12 = v2v1:dot(pv1) + + local invDenom = 1 / (dot00 * dot11 - dot01 * dot01) + local u = (dot11 * dot02 - dot01 * dot12) * invDenom + local v = (dot00 * dot12 - dot01 * dot02) * invDenom + + return (u >= 0) and (v >= 0) and (u + v <= 1) + end + + ---检查点是否在多边形内 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param point foundation.math.Vector2 点 + ---@return boolean + function ShapeIntersector.polygonContainsPoint(polygon, point) + if polygon.size < 3 then + return false + end + + Segment = Segment or require("foundation.shape.Segment") + for i = 0, polygon.size - 1 do + local j = (i + 1) % polygon.size + local segment = Segment.create(polygon.points[i], polygon.points[j]) + if ShapeIntersector.segmentContainsPoint(segment, point) then + return true + end + end + + local inside = false + for i = 0, polygon.size - 1 do + local j = (i + 1) % polygon.size + + local pi = polygon.points[i] + local pj = polygon.points[j] + + if (pi.x - point.x) * (pi.x - point.x) + (pi.y - point.y) * (pi.y - point.y) < 1e-10 then + return true + end + + if ((pi.y > point.y) ~= (pj.y > point.y)) and + (point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x) then + inside = not inside + end + end + + return inside + end + + ---判断点是否在线段上 + ---@param segment foundation.shape.Segment 线段 + ---@param point foundation.math.Vector2 点 + ---@return boolean + function ShapeIntersector.segmentContainsPoint(segment, point) + local d1 = (point - segment.point1):length() + local d2 = (point - segment.point2):length() + local lineLen = (segment.point2 - segment.point1):length() + + return math.abs(d1 + d2 - lineLen) <= 1e-10 + end + + return ShapeIntersector +end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/LineIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/LineIntersector.lua new file mode 100644 index 00000000..465d7dcd --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/LineIntersector.lua @@ -0,0 +1,206 @@ +---@param ShapeIntersector foundation.shape.ShapeIntersector +return function(ShapeIntersector) + local math = math + + local Vector2 = require("foundation.math.Vector2") + + ---检查直线与线段的相交 + ---@param line foundation.shape.Line 直线 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.lineToSegment(line, segment) + local points = {} + local a = line.point + local b = line.point + line.direction + local c = segment.point1 + local d = segment.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) <= 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if u >= 0 and u <= 1 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + end + + if #points == 0 then + return false, nil + end + return true, points + end + + ---检查直线是否与线段相交 + ---@param line foundation.shape.Line 直线 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean + function ShapeIntersector.lineHasIntersectionWithSegment(line, segment) + local a = line.point + local b = line.point + line.direction + local c = segment.point1 + local d = segment.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) <= 1e-10 then + return false + end + + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return u >= 0 and u <= 1 + end + + ---检查直线与直线的相交 + ---@param line1 foundation.shape.Line 第一条直线 + ---@param line2 foundation.shape.Line 第二条直线 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.lineToLine(line1, line2) + local points = {} + local a = line1.point + local b = line1.point + line1.direction + local c = line2.point + local d = line2.point + line2.direction + + local dir_cross = line1.direction:cross(line2.direction) + if math.abs(dir_cross) <= 1e-10 then + local point_diff = line2.point - line1.point + if math.abs(point_diff:cross(line1.direction)) <= 1e-10 then + points[#points + 1] = line1.point:clone() + points[#points + 1] = line1:getPoint(1) + return true, points + else + return false, nil + end + end + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + + return true, points + end + + ---检查直线是否与直线相交 + ---@param line1 foundation.shape.Line 第一条直线 + ---@param line2 foundation.shape.Line 第二条直线 + ---@return boolean + function ShapeIntersector.lineHasIntersectionWithLine(line1, line2) + local dir_cross = line1.direction:cross(line2.direction) + if math.abs(dir_cross) <= 1e-10 then + local point_diff = line2.point - line1.point + return math.abs(point_diff:cross(line1.direction)) <= 1e-10 + end + return true + end + + ---检查直线与射线的相交 + ---@param line foundation.shape.Line 直线 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.lineToRay(line, ray) + local points = {} + local a = line.point + local b = line.point + line.direction + local c = ray.point + local d = ray.point + ray.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) <= 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if u >= 0 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + end + + if #points == 0 then + return false, nil + end + return true, points + end + + ---检查直线是否与射线相交 + ---@param line foundation.shape.Line 直线 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean + function ShapeIntersector.lineHasIntersectionWithRay(line, ray) + local a = line.point + local b = line.point + line.direction + local c = ray.point + local d = ray.point + ray.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) <= 1e-10 then + return false + end + + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return u >= 0 + end + + ---检查直线与圆的相交 + ---@param line foundation.shape.Line 直线 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.lineToCircle(line, circle) + local points = {} + local dir = line.direction + local len = dir:length() + if len <= 1e-10 then + return false, nil + end + dir = dir / len + local L = line.point - circle.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - circle.radius * circle.radius + local discriminant = b * b - 4 * a * c + if discriminant >= 0 then + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + points[#points + 1] = line.point + dir * t1 + if math.abs(t2 - t1) > 1e-10 then + points[#points + 1] = line.point + dir * t2 + end + end + + if #points == 0 then + return false, nil + end + return true, points + end + + ---检查直线是否与圆相交 + ---@param line foundation.shape.Line 直线 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean + function ShapeIntersector.lineHasIntersectionWithCircle(line, circle) + local dir = line.direction + local len = dir:length() + if len <= 1e-10 then + return false + end + dir = dir / len + local L = line.point - circle.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - circle.radius * circle.radius + local discriminant = b * b - 4 * a * c + return discriminant >= 0 + end + return ShapeIntersector +end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/PolygonIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/PolygonIntersector.lua new file mode 100644 index 00000000..d44627c4 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/PolygonIntersector.lua @@ -0,0 +1,492 @@ +---@param ShapeIntersector foundation.shape.ShapeIntersector +return function(ShapeIntersector) + local ipairs = ipairs + + ---检查多边形与线段的相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.polygonToSegment(polygon, segment) + local points = {} + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge, segment) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, segment.point1) then + points[#points + 1] = segment.point1:clone() + end + + if segment.point1 ~= segment.point2 and ShapeIntersector.polygonContainsPoint(polygon, segment.point2) then + points[#points + 1] = segment.point2:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查多边形是否与线段相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean + function ShapeIntersector.polygonHasIntersectionWithSegment(polygon, segment) + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then + return true + end + end + + return ShapeIntersector.polygonContainsPoint(polygon, segment.point1) or + ShapeIntersector.polygonContainsPoint(polygon, segment.point2) + end + + ---检查多边形与线的相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param line foundation.shape.Line 线 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.polygonToLine(polygon, line) + local points = {} + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.lineToSegment(line, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, line.point) then + points[#points + 1] = line.point:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查多边形是否与线相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param line foundation.shape.Line 线 + ---@return boolean + function ShapeIntersector.polygonHasIntersectionWithLine(polygon, line) + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then + return true + end + end + + return ShapeIntersector.polygonContainsPoint(polygon, line.point) + end + + ---检查多边形与射线的相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.polygonToRay(polygon, ray) + local points = {} + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, ray.point) then + points[#points + 1] = ray.point:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查多边形是否与射线相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean + function ShapeIntersector.polygonHasIntersectionWithRay(polygon, ray) + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then + return true + end + end + + return ShapeIntersector.polygonContainsPoint(polygon, ray.point) + end + + ---检查多边形与圆的相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.polygonToCircle(polygon, circle) + local points = {} + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + points[#points + 1] = vertex + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, circle.center) then + points[#points + 1] = circle.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查多边形是否与圆相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean + function ShapeIntersector.polygonHasIntersectionWithCircle(polygon, circle) + if ShapeIntersector.polygonContainsPoint(polygon, circle.center) then + return true + end + + local edges = polygon:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then + return true + end + end + + local vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + return true + end + end + + return false + end + + ---检查多边形与三角形的相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param triangle foundation.shape.Triangle 三角形 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.polygonToTriangle(polygon, triangle) + local points = {} + local edges1 = polygon:getEdges() + local edges2 = triangle:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon, vertex) then + points[#points + 1] = vertex + end + end + + vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle, vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查多边形是否与三角形相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param triangle foundation.shape.Triangle 三角形 + ---@return boolean + function ShapeIntersector.polygonHasIntersectionWithTriangle(polygon, triangle) + local edges1 = polygon:getEdges() + local edges2 = triangle:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then + return true + end + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon, vertex) then + return true + end + end + + vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle, vertex) then + return true + end + end + + return false + end + + ---检查多边形与矩形的相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.polygonToRectangle(polygon, rectangle) + local points = {} + local edges1 = polygon:getEdges() + local edges2 = rectangle:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon, vertex) then + points[#points + 1] = vertex + end + end + + vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.rectangleContainsPoint(rectangle, vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查多边形是否与矩形相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@return boolean + function ShapeIntersector.polygonHasIntersectionWithRectangle(polygon, rectangle) + local edges1 = polygon:getEdges() + local edges2 = rectangle:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then + return true + end + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon, vertex) then + return true + end + end + + vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.rectangleContainsPoint(rectangle, vertex) then + return true + end + end + + return false + end + + ---检查多边形与多边形的相交 + ---@param polygon1 foundation.shape.Polygon 第一个多边形 + ---@param polygon2 foundation.shape.Polygon 第二个多边形 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.polygonToPolygon(polygon1, polygon2) + local points = {} + local edges1 = polygon1:getEdges() + local edges2 = polygon2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + end + + local vertices = polygon2:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon1, vertex) then + points[#points + 1] = vertex + end + end + + vertices = polygon1:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon2, vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查多边形是否与多边形相交 + ---@param polygon1 foundation.shape.Polygon 第一个多边形 + ---@param polygon2 foundation.shape.Polygon 第二个多边形 + ---@return boolean + function ShapeIntersector.polygonHasIntersectionWithPolygon(polygon1, polygon2) + local edges1 = polygon1:getEdges() + local edges2 = polygon2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then + return true + end + end + end + + local vertices = polygon2:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon1, vertex) then + return true + end + end + + vertices = polygon1:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.polygonContainsPoint(polygon2, vertex) then + return true + end + end + + return false + end + + ---检查多边形与扇形的相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param sector foundation.shape.Sector 扇形 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.polygonToSector(polygon, sector) + local points = {} + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + points[#points + 1] = vertex + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, sector.center) then + points[#points + 1] = sector.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查多边形是否与扇形相交 + ---@param polygon foundation.shape.Polygon 多边形 + ---@param sector foundation.shape.Sector 扇形 + ---@return boolean + function ShapeIntersector.polygonHasIntersectionWithSector(polygon, sector) + local edges = polygon:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then + return true + end + end + + local vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + return true + end + end + + return ShapeIntersector.polygonContainsPoint(polygon, sector.center) + end + + return ShapeIntersector +end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RayIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RayIntersector.lua new file mode 100644 index 00000000..7f44b863 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RayIntersector.lua @@ -0,0 +1,193 @@ +---@param ShapeIntersector foundation.shape.ShapeIntersector +return function(ShapeIntersector) + local math = math + + local Vector2 = require("foundation.math.Vector2") + + ---检查射线与线段的相交 + ---@param ray foundation.shape.Ray 射线 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.rayToSegment(ray, segment) + local points = {} + local a = ray.point + local b = ray.point + ray.direction + local c = segment.point1 + local d = segment.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) <= 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if t >= 0 and u >= 0 and u <= 1 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + end + + if #points <= 1e-10 then + return false, nil + end + return true, points + end + + ---检查射线是否与线段相交 + ---@param ray foundation.shape.Ray 射线 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean + function ShapeIntersector.rayHasIntersectionWithSegment(ray, segment) + local a = ray.point + local b = ray.point + ray.direction + local c = segment.point1 + local d = segment.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) <= 1e-10 then + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and u >= 0 and u <= 1 + end + + ---检查射线与射线的相交 + ---@param ray1 foundation.shape.Ray 第一条射线 + ---@param ray2 foundation.shape.Ray 第二条射线 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.rayToRay(ray1, ray2) + local points = {} + local a = ray1.point + local b = ray1.point + ray1.direction + local c = ray2.point + local d = ray2.point + ray2.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) < 1e-10 then + local dir_cross = ray1.direction:cross(ray2.direction) + if math.abs(dir_cross) < 1e-10 then + local point_diff = ray2.point - ray1.point + local t = point_diff:dot(ray1.direction) + if t >= 0 then + points[#points + 1] = ray1.point + ray1.direction * t + end + end + + if #points == 0 then + return false, nil + end + return true, points + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if t >= 0 and u >= 0 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + end + + if #points == 0 then + return false, nil + end + return true, points + end + + ---检查射线是否与射线相交 + ---@param ray1 foundation.shape.Ray 第一条射线 + ---@param ray2 foundation.shape.Ray 第二条射线 + ---@return boolean + function ShapeIntersector.rayHasIntersectionWithRay(ray1, ray2) + local a = ray1.point + local b = ray1.point + ray1.direction + local c = ray2.point + local d = ray2.point + ray2.direction + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) <= 1e-10 then + local dir_cross = ray1.direction:cross(ray2.direction) + if math.abs(dir_cross) <= 1e-10 then + local point_diff = ray2.point - ray1.point + local t = point_diff:dot(ray1.direction) + return t >= 0 + end + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and u >= 0 + end + + ---检查射线与圆的相交 + ---@param ray foundation.shape.Ray 射线 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.rayToCircle(ray, circle) + local points = {} + local dir = ray.direction + local len = dir:length() + if len <= 1e-10 then + return false, nil + end + dir = dir / len + local L = ray.point - circle.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - circle.radius * circle.radius + local discriminant = b * b - 4 * a * c + if discriminant >= 0 then + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + if t1 >= 0 then + points[#points + 1] = ray.point + dir * t1 + end + if t2 >= 0 and math.abs(t2 - t1) > 1e-10 then + points[#points + 1] = ray.point + dir * t2 + end + end + + if #points == 0 then + return false, nil + end + return true, points + end + + ---检查射线是否与圆相交 + ---@param ray foundation.shape.Ray 射线 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean + function ShapeIntersector.rayHasIntersectionWithCircle(ray, circle) + local dir = ray.direction + local len = dir:length() + if len <= 1e-10 then + return false + end + dir = dir / len + local L = ray.point - circle.center + local a = dir:dot(dir) + local b = 2 * L:dot(dir) + local c = L:dot(L) - circle.radius * circle.radius + local discriminant = b * b - 4 * a * c + + if discriminant <= 1e-10 then + return false + end + + local sqrt_d = math.sqrt(discriminant) + local t1 = (-b - sqrt_d) / (2 * a) + local t2 = (-b + sqrt_d) / (2 * a) + + return t1 >= 0 or t2 >= 0 + end + + return ShapeIntersector +end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RectangleIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RectangleIntersector.lua new file mode 100644 index 00000000..b65afcd3 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RectangleIntersector.lua @@ -0,0 +1,311 @@ +---@param ShapeIntersector foundation.shape.ShapeIntersector +return function(ShapeIntersector) + local ipairs = ipairs + + ---检查矩形与线段的相交 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.rectangleToSegment(rectangle, segment) + local points = {} + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.segmentToSegment(segment, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + if rectangle:contains(segment.point1) then + points[#points + 1] = segment.point1:clone() + end + if segment.point1 ~= segment.point2 and rectangle:contains(segment.point2) then + points[#points + 1] = segment.point2:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查矩形是否与线段相交 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean + function ShapeIntersector.rectangleHasIntersectionWithSegment(rectangle, segment) + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then + return true + end + end + return rectangle:contains(segment.point1) or rectangle:contains(segment.point2) + end + + ---检查矩形与三角形的相交 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@param triangle foundation.shape.Triangle 三角形 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.rectangleToTriangle(rectangle, triangle) + local points = {} + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.triangleToSegment(triangle, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if rectangle:contains(vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查矩形是否与三角形相交 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@param triangle foundation.shape.Triangle 三角形 + ---@return boolean + function ShapeIntersector.rectangleHasIntersectionWithTriangle(rectangle, triangle) + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.triangleHasIntersectionWithSegment(triangle, edge) then + return true + end + end + return rectangle:contains(triangle.point1) or rectangle:contains(triangle.point2) or rectangle:contains(triangle.point3) + end + + ---检查矩形与直线的相交 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@param line foundation.shape.Line 直线 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.rectangleToLine(rectangle, line) + local points = {} + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.lineToSegment(line, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查矩形是否与直线相交 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@param line foundation.shape.Line 直线 + ---@return boolean + function ShapeIntersector.rectangleHasIntersectionWithLine(rectangle, line) + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then + return true + end + end + return rectangle:contains(line.point) + end + + ---检查矩形与射线的相交 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.rectangleToRay(rectangle, ray) + local points = {} + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + if rectangle:contains(ray.point) then + points[#points + 1] = ray.point:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查矩形是否与射线相交 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean + function ShapeIntersector.rectangleHasIntersectionWithRay(rectangle, ray) + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then + return true + end + end + return rectangle:contains(ray.point) + end + + ---检查矩形与圆的相交 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.rectangleToCircle(rectangle, circle) + local points = {} + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + points[#points + 1] = vertex + end + end + + if rectangle:contains(circle.center) then + points[#points + 1] = circle.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查矩形是否与圆相交 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean + function ShapeIntersector.rectangleHasIntersectionWithCircle(rectangle, circle) + if rectangle:contains(circle.center) then + return true + end + + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then + return true + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + return true + end + end + + return false + end + + ---检查矩形与矩形的相交 + ---@param rectangle1 foundation.shape.Rectangle 第一个矩形 + ---@param rectangle2 foundation.shape.Rectangle 第二个矩形 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.rectangleToRectangle(rectangle1, rectangle2) + local points = {} + local edges1 = rectangle1:getEdges() + local edges2 = rectangle2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + end + + local vertices1 = rectangle1:getVertices() + for _, vertex in ipairs(vertices1) do + if rectangle2:contains(vertex) then + points[#points + 1] = vertex + end + end + + local vertices2 = rectangle2:getVertices() + for _, vertex in ipairs(vertices2) do + if rectangle1:contains(vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查矩形是否与矩形相交 + ---@param rectangle1 foundation.shape.Rectangle 第一个矩形 + ---@param rectangle2 foundation.shape.Rectangle 第二个矩形 + ---@return boolean + function ShapeIntersector.rectangleHasIntersectionWithRectangle(rectangle1, rectangle2) + local edges1 = rectangle1:getEdges() + local edges2 = rectangle2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then + return true + end + end + end + + local vertices1 = rectangle1:getVertices() + for _, vertex in ipairs(vertices1) do + if rectangle2:contains(vertex) then + return true + end + end + + local vertices2 = rectangle2:getVertices() + for _, vertex in ipairs(vertices2) do + if rectangle1:contains(vertex) then + return true + end + end + + return false + end + + return ShapeIntersector +end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SectorIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SectorIntersector.lua new file mode 100644 index 00000000..71952640 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SectorIntersector.lua @@ -0,0 +1,590 @@ +---@param ShapeIntersector foundation.shape.ShapeIntersector +return function(ShapeIntersector) + local math = math + + local ipairs = ipairs + local require = require + + local Segment + + ---检查扇形与线段的相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.sectorToSegment(sector, segment) + Segment = Segment or require("foundation.shape.Segment") + local points = {} + if math.abs(sector.range) >= 1 then + return ShapeIntersector.circleToSegment(sector, segment) + end + local success, circle_points = ShapeIntersector.circleToSegment(sector, segment) + if success then + local n = 0 + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + n = n + 1 + points[#points + 1] = p + end + end + end + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + local success1, edge_points1 = ShapeIntersector.segmentToSegment(startSegment, segment) + if success1 then + for _, p in ipairs(edge_points1) do + points[#points + 1] = p + end + end + local success2, edge_points2 = ShapeIntersector.segmentToSegment(endSegment, segment) + if success2 then + for _, p in ipairs(edge_points2) do + points[#points + 1] = p + end + end + if ShapeIntersector.sectorContainsPoint(sector, segment.point1) then + points[#points + 1] = segment.point1:clone() + end + if segment.point1 ~= segment.point2 and ShapeIntersector.sectorContainsPoint(sector, segment.point2) then + points[#points + 1] = segment.point2:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查扇形是否与线段相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean + function ShapeIntersector.sectorHasIntersectionWithSegment(sector, segment) + Segment = Segment or require("foundation.shape.Segment") + if math.abs(sector.range) >= 1 then + return ShapeIntersector.circleHasIntersectionWithSegment(sector, segment) + end + if ShapeIntersector.sectorContainsPoint(sector, segment.point1) or + ShapeIntersector.sectorContainsPoint(sector, segment.point2) then + return true + end + local success, circle_points = ShapeIntersector.circleToSegment(sector, segment) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + return true + end + end + end + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + if ShapeIntersector.segmentHasIntersectionWithSegment(startSegment, segment) or + ShapeIntersector.segmentHasIntersectionWithSegment(endSegment, segment) then + return true + end + return false + end + + ---检查扇形与直线的相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param line foundation.shape.Line 直线 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.sectorToLine(sector, line) + Segment = Segment or require("foundation.shape.Segment") + local points = {} + if math.abs(sector.range) >= 1 then + return ShapeIntersector.lineToCircle(line, sector) + end + local success, circle_points = ShapeIntersector.lineToCircle(line, sector) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + points[#points + 1] = p + end + end + end + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + local success1, edge_points1 = ShapeIntersector.lineToSegment(line, startSegment) + if success1 then + for _, p in ipairs(edge_points1) do + points[#points + 1] = p + end + end + local success2, edge_points2 = ShapeIntersector.lineToSegment(line, endSegment) + if success2 then + for _, p in ipairs(edge_points2) do + points[#points + 1] = p + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查扇形是否与直线相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param line foundation.shape.Line 直线 + ---@return boolean + function ShapeIntersector.sectorHasIntersectionWithLine(sector, line) + Segment = Segment or require("foundation.shape.Segment") + if math.abs(sector.range) >= 1 then + return ShapeIntersector.lineHasIntersectionWithCircle(line, sector) + end + local success, circle_points = ShapeIntersector.lineToCircle(line, sector) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + return true + end + end + end + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + if ShapeIntersector.lineHasIntersectionWithSegment(line, startSegment) or + ShapeIntersector.lineHasIntersectionWithSegment(line, endSegment) then + return true + end + return false + end + + ---检查扇形与射线的相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.sectorToRay(sector, ray) + Segment = Segment or require("foundation.shape.Segment") + local points = {} + if math.abs(sector.range) >= 1 then + return ShapeIntersector.rayToCircle(ray, sector) + end + local success, circle_points = ShapeIntersector.rayToCircle(ray, sector) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + points[#points + 1] = p + end + end + end + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + local success1, edge_points1 = ShapeIntersector.rayToSegment(ray, startSegment) + if success1 then + for _, p in ipairs(edge_points1) do + points[#points + 1] = p + end + end + local success2, edge_points2 = ShapeIntersector.rayToSegment(ray, endSegment) + if success2 then + for _, p in ipairs(edge_points2) do + points[#points + 1] = p + end + end + if ShapeIntersector.sectorContainsPoint(sector, ray.point) then + points[#points + 1] = ray.point:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查扇形是否与射线相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean + function ShapeIntersector.sectorHasIntersectionWithRay(sector, ray) + Segment = Segment or require("foundation.shape.Segment") + if math.abs(sector.range) >= 1 then + return ShapeIntersector.rayHasIntersectionWithCircle(ray, sector) + end + if ShapeIntersector.sectorContainsPoint(sector, ray.point) then + return true + end + local success, circle_points = ShapeIntersector.rayToCircle(ray, sector) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + return true + end + end + end + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + if ShapeIntersector.rayHasIntersectionWithSegment(ray, startSegment) or + ShapeIntersector.rayHasIntersectionWithSegment(ray, endSegment) then + return true + end + return false + end + + ---检查扇形与三角形的相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param triangle foundation.shape.Triangle 三角形 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.sectorToTriangle(sector, triangle) + local points = {} + if math.abs(sector.range) >= 1 then + return ShapeIntersector.triangleToCircle(triangle, sector) + end + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + points[#points + 1] = vertex + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, sector.center) then + points[#points + 1] = sector.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查扇形是否与三角形相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param triangle foundation.shape.Triangle 三角形 + ---@return boolean + function ShapeIntersector.sectorHasIntersectionWithTriangle(sector, triangle) + if math.abs(sector.range) >= 1 then + return ShapeIntersector.triangleHasIntersectionWithCircle(triangle, sector) + end + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then + return true + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + return true + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, sector.center) then + return true + end + + return false + end + + ---检查扇形与圆的相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.sectorToCircle(sector, circle) + Segment = Segment or require("foundation.shape.Segment") + local points = {} + if math.abs(sector.range) >= 1 then + return ShapeIntersector.circleToCircle(sector, circle) + end + local success, circle_points = ShapeIntersector.circleToCircle(sector, circle) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + points[#points + 1] = p + end + end + end + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + local success1, edge_points1 = ShapeIntersector.circleToSegment(circle, startSegment) + if success1 then + for _, p in ipairs(edge_points1) do + points[#points + 1] = p + end + end + local success2, edge_points2 = ShapeIntersector.circleToSegment(circle, endSegment) + if success2 then + for _, p in ipairs(edge_points2) do + points[#points + 1] = p + end + end + if ShapeIntersector.sectorContainsPoint(sector, circle.center) then + points[#points + 1] = circle.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查扇形是否与圆相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean + function ShapeIntersector.sectorHasIntersectionWithCircle(sector, circle) + Segment = Segment or require("foundation.shape.Segment") + if math.abs(sector.range) >= 1 then + return ShapeIntersector.circleHasIntersectionWithCircle(sector, circle) + end + if ShapeIntersector.sectorContainsPoint(sector, circle.center) then + return true + end + local success, circle_points = ShapeIntersector.circleToCircle(sector, circle) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + return true + end + end + end + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + if ShapeIntersector.circleHasIntersectionWithSegment(circle, startSegment) or + ShapeIntersector.circleHasIntersectionWithSegment(circle, endSegment) then + return true + end + return false + end + + ---检查扇形与矩形的相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.sectorToRectangle(sector, rectangle) + local points = {} + if math.abs(sector.range) >= 1 then + return ShapeIntersector.rectangleToCircle(rectangle, sector) + end + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + points[#points + 1] = vertex + end + end + + if ShapeIntersector.rectangleContainsPoint(rectangle, sector.center) then + points[#points + 1] = sector.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查扇形是否与矩形相交 + ---@param sector foundation.shape.Sector 扇形 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@return boolean + function ShapeIntersector.sectorHasIntersectionWithRectangle(sector, rectangle) + if math.abs(sector.range) >= 1 then + return ShapeIntersector.rectangleHasIntersectionWithCircle(rectangle, sector) + end + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then + return true + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.sectorContainsPoint(sector, vertex) then + return true + end + end + + if ShapeIntersector.rectangleContainsPoint(rectangle, sector.center) then + return true + end + + return false + end + + ---检查扇形与扇形的相交 + ---@param sector1 foundation.shape.Sector 第一个扇形 + ---@param sector2 foundation.shape.Sector 第二个扇形 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.sectorToSector(sector1, sector2) + Segment = Segment or require("foundation.shape.Segment") + local points = {} + if math.abs(sector1.range) >= 1 and math.abs(sector2.range) >= 1 then + return ShapeIntersector.circleToCircle(sector1, sector2) + end + local success, circle_points = ShapeIntersector.circleToCircle(sector1, sector2) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then + points[#points + 1] = p + end + end + end + if math.abs(sector1.range) < 1 then + local startDir1 = sector1.direction + local endDir1 = sector1.direction:rotated(sector1.range * 2 * math.pi) + local startPoint1 = sector1.center + startDir1 * sector1.radius + local endPoint1 = sector1.center + endDir1 * sector1.radius + local startSegment1 = Segment.create(sector1.center, startPoint1) + local endSegment1 = Segment.create(sector1.center, endPoint1) + local success1, edge_points1 = ShapeIntersector.sectorToSegment(sector2, startSegment1) + if success1 then + for _, p in ipairs(edge_points1) do + points[#points + 1] = p + end + end + local success2, edge_points2 = ShapeIntersector.sectorToSegment(sector2, endSegment1) + if success2 then + for _, p in ipairs(edge_points2) do + points[#points + 1] = p + end + end + end + if math.abs(sector2.range) < 1 then + local startDir2 = sector2.direction + local endDir2 = sector2.direction:rotated(sector2.range * 2 * math.pi) + local startPoint2 = sector2.center + startDir2 * sector2.radius + local endPoint2 = sector2.center + endDir2 * sector2.radius + local startSegment2 = Segment.create(sector2.center, startPoint2) + local endSegment2 = Segment.create(sector2.center, endPoint2) + local success3, edge_points3 = ShapeIntersector.sectorToSegment(sector1, startSegment2) + if success3 then + for _, p in ipairs(edge_points3) do + points[#points + 1] = p + end + end + local success4, edge_points4 = ShapeIntersector.sectorToSegment(sector1, endSegment2) + if success4 then + for _, p in ipairs(edge_points4) do + points[#points + 1] = p + end + end + end + if ShapeIntersector.sectorContainsPoint(sector1, sector2.center) then + points[#points + 1] = sector2.center:clone() + end + if ShapeIntersector.sectorContainsPoint(sector2, sector1.center) then + points[#points + 1] = sector1.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---仅检查扇形是否与扇形相交 + ---@param sector1 foundation.shape.Sector 第一个扇形 + ---@param sector2 foundation.shape.Sector 第二个扇形 + ---@return boolean + function ShapeIntersector.sectorHasIntersectionWithSector(sector1, sector2) + Segment = Segment or require("foundation.shape.Segment") + if math.abs(sector1.range) >= 1 and math.abs(sector2.range) >= 1 then + return ShapeIntersector.circleHasIntersectionWithCircle(sector1, sector2) + end + if ShapeIntersector.sectorContainsPoint(sector1, sector2.center) or + ShapeIntersector.sectorContainsPoint(sector2, sector1.center) then + return true + end + local success, circle_points = ShapeIntersector.circleToCircle(sector1, sector2) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then + return true + end + end + end + if math.abs(sector1.range) < 1 then + local startDir1 = sector1.direction + local endDir1 = sector1.direction:rotated(sector1.range * 2 * math.pi) + local startPoint1 = sector1.center + startDir1 * sector1.radius + local endPoint1 = sector1.center + endDir1 * sector1.radius + local startSegment1 = Segment.create(sector1.center, startPoint1) + local endSegment1 = Segment.create(sector1.center, endPoint1) + if ShapeIntersector.sectorHasIntersectionWithSegment(sector2, startSegment1) or + ShapeIntersector.sectorHasIntersectionWithSegment(sector2, endSegment1) then + return true + end + end + if math.abs(sector2.range) < 1 then + local startDir2 = sector2.direction + local endDir2 = sector2.direction:rotated(sector2.range * 2 * math.pi) + local startPoint2 = sector2.center + startDir2 * sector2.radius + local endPoint2 = sector2.center + endDir2 * sector2.radius + local startSegment2 = Segment.create(sector2.center, startPoint2) + local endSegment2 = Segment.create(sector2.center, endPoint2) + if ShapeIntersector.sectorHasIntersectionWithSegment(sector1, startSegment2) or + ShapeIntersector.sectorHasIntersectionWithSegment(sector1, endSegment2) then + return true + end + end + return false + end + + return ShapeIntersector +end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SegmentIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SegmentIntersector.lua new file mode 100644 index 00000000..72eb8de1 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SegmentIntersector.lua @@ -0,0 +1,60 @@ +---@param ShapeIntersector foundation.shape.ShapeIntersector +return function(ShapeIntersector) + local math = math + + local Vector2 = require("foundation.math.Vector2") + + ---检查两条线段的相交 + ---@param segment1 foundation.shape.Segment 第一条线段 + ---@param segment2 foundation.shape.Segment 第二条线段 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.segmentToSegment(segment1, segment2) + local points = {} + local a = segment1.point1 + local b = segment1.point2 + local c = segment2.point1 + local d = segment2.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) <= 1e-10 then + return false, nil + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + if t >= 0 and t <= 1 and u >= 0 and u <= 1 then + local x = a.x + t * (b.x - a.x) + local y = a.y + t * (b.y - a.y) + points[#points + 1] = Vector2.create(x, y) + end + + if #points <= 1e-10 then + return false, nil + end + return true, points + end + + ---检查两条线段是否相交 + ---@param segment1 foundation.shape.Segment 第一条线段 + ---@param segment2 foundation.shape.Segment 第二条线段 + ---@return boolean + function ShapeIntersector.segmentHasIntersectionWithSegment(segment1, segment2) + local a = segment1.point1 + local b = segment1.point2 + local c = segment2.point1 + local d = segment2.point2 + + local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) + if math.abs(denom) <= 1e-10 then + return false + end + + local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom + local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom + + return t >= 0 and t <= 1 and u >= 0 and u <= 1 + end + + return ShapeIntersector +end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/TriangleIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/TriangleIntersector.lua new file mode 100644 index 00000000..849fe563 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/TriangleIntersector.lua @@ -0,0 +1,280 @@ +---@param ShapeIntersector foundation.shape.ShapeIntersector +return function(ShapeIntersector) + local ipairs = ipairs + + ---检查三角形与线段的相交 + ---@param triangle foundation.shape.Triangle 三角形 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.triangleToSegment(triangle, segment) + local points = {} + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge, segment) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + if ShapeIntersector.triangleContainsPoint(triangle, segment.point1) then + points[#points + 1] = segment.point1:clone() + end + if segment.point1 ~= segment.point2 and ShapeIntersector.triangleContainsPoint(triangle, segment.point2) then + points[#points + 1] = segment.point2:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---只检查三角形与线段是否相交 + ---@param triangle foundation.shape.Triangle 三角形 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean + function ShapeIntersector.triangleHasIntersectionWithSegment(triangle, segment) + local edges = triangle:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then + return true + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, segment.point1) or + ShapeIntersector.triangleContainsPoint(triangle, segment.point2) then + return true + end + + return false + end + + ---检查三角形与另一个三角形的相交 + ---@param triangle1 foundation.shape.Triangle 第一个三角形 + ---@param triangle2 foundation.shape.Triangle 第二个三角形 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.triangleToTriangle(triangle1, triangle2) + local points = {} + local edges1 = triangle1:getEdges() + local edges2 = triangle2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + end + + local vertices = triangle2:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle1, vertex) then + points[#points + 1] = vertex + end + end + + vertices = triangle1:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle2, vertex) then + points[#points + 1] = vertex + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---只检查三角形与另一个三角形是否相交 + ---@param triangle1 foundation.shape.Triangle 第一个三角形 + ---@param triangle2 foundation.shape.Triangle 第二个三角形 + ---@return boolean + function ShapeIntersector.triangleHasIntersectionWithTriangle(triangle1, triangle2) + local edges1 = triangle1:getEdges() + local edges2 = triangle2:getEdges() + + for _, edge1 in ipairs(edges1) do + for _, edge2 in ipairs(edges2) do + if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then + return true + end + end + end + + local vertices = triangle2:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle1, vertex) then + return true + end + end + + vertices = triangle1:getVertices() + for _, vertex in ipairs(vertices) do + if ShapeIntersector.triangleContainsPoint(triangle2, vertex) then + return true + end + end + + return false + end + + ---检查三角形与直线的相交 + ---@param triangle foundation.shape.Triangle 三角形 + ---@param line foundation.shape.Line 直线 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.triangleToLine(triangle, line) + local points = {} + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.lineToSegment(line, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---只检查三角形与直线是否相交 + ---@param triangle foundation.shape.Triangle 三角形 + ---@param line foundation.shape.Line 直线 + ---@return boolean + function ShapeIntersector.triangleHasIntersectionWithLine(triangle, line) + local edges = triangle:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then + return true + end + end + + return false + end + + ---检查三角形与射线的相交 + ---@param triangle foundation.shape.Triangle 三角形 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.triangleToRay(triangle, ray) + local points = {} + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + if ShapeIntersector.triangleContainsPoint(triangle, ray.point) then + points[#points + 1] = ray.point:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---只检查三角形与射线是否相交 + ---@param triangle foundation.shape.Triangle 三角形 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean + function ShapeIntersector.triangleHasIntersectionWithRay(triangle, ray) + local edges = triangle:getEdges() + + for _, edge in ipairs(edges) do + if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then + return true + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, ray.point) then + return true + end + + return false + end + + ---检查三角形与圆的相交 + ---@param triangle foundation.shape.Triangle 三角形 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean, foundation.math.Vector2[] | nil + function ShapeIntersector.triangleToCircle(triangle, circle) + local points = {} + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + points[#points + 1] = vertex + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, circle.center) then + points[#points + 1] = circle.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---只检查三角形与圆是否相交 + ---@param triangle foundation.shape.Triangle 三角形 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean + function ShapeIntersector.triangleHasIntersectionWithCircle(triangle, circle) + if ShapeIntersector.triangleContainsPoint(triangle, circle.center) then + return true + end + + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then + return true + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if circle:contains(vertex) then + return true + end + end + + return false + end + + return ShapeIntersector +end \ No newline at end of file From cd0ccbc9141015bf832517c43adbe736aac08d57 Mon Sep 17 00:00:00 2001 From: OLC Date: Fri, 2 May 2025 02:54:06 +0800 Subject: [PATCH 59/71] feat: add Ellipse and BezierCurve classes with intersection methods --- .../foundation/shape/BezierCurve.lua | 739 ++++++++++++++++ .../foundation/shape/Ellipse.lua | 471 +++++++++++ .../foundation/shape/Polygon.lua | 2 +- .../foundation/shape/Rectangle.lua | 2 +- .../foundation/shape/Sector.lua | 31 +- .../foundation/shape/ShapeIntersector.lua | 48 ++ .../BezierCurveIntersector.lua | 451 ++++++++++ .../shape/ShapeIntersector/ContainPoint.lua | 20 + .../ShapeIntersector/EllipseIntersector.lua | 798 ++++++++++++++++++ laboratory/geometry/main.lua | 136 ++- 10 files changed, 2681 insertions(+), 17 deletions(-) create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/BezierCurveIntersector.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua diff --git a/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua b/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua new file mode 100644 index 00000000..61ca7e2b --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua @@ -0,0 +1,739 @@ +local ffi = require("ffi") + +local type = type +local tostring = tostring +local string = string +local math = math +local table = table +local ipairs = ipairs +local error = error +local rawset = rawset +local setmetatable = setmetatable + +local Vector2 = require("foundation.math.Vector2") +local Segment = require("foundation.shape.Segment") +local ShapeIntersector = require("foundation.shape.ShapeIntersector") + +ffi.cdef [[ +typedef struct { + int order; + int num_points; + foundation_math_Vector2* control_points; +} foundation_shape_BezierCurve; +]] + +---@class foundation.shape.BezierCurve +---@field order number 贝塞尔曲线的阶数 (2=二次曲线,3=三次曲线) +---@field num_points number 控制点的数量 +---@field control_points foundation.math.Vector2[] 控制点数组 +local BezierCurve = {} +BezierCurve.__type = "foundation.shape.BezierCurve" + +---@param t foundation.math.Vector2[] +---@return number, foundation.math.Vector2[] +local function buildNewVector2Array(t) + local size = #t + local points_array = ffi.new("foundation_math_Vector2[?]", size) + + for i = 1, size do + points_array[i - 1] = Vector2.create(t[i].x, t[i].y) + end + + return size, points_array +end + +---@param self foundation.shape.BezierCurve +---@param key any +---@return any +function BezierCurve.__index(self, key) + if key == "order" then + return self.__data.order + elseif key == "num_points" then + return self.__data.num_points + elseif key == "control_points" then + return self.__data.control_points + end + return BezierCurve[key] +end + +---@param self foundation.shape.BezierCurve +---@param key any +---@param value any +function BezierCurve.__newindex(self, key, value) + if key == "order" then + self.__data.order = value + elseif key == "num_points" then + error("cannot modify num_points directly") + elseif key == "control_points" then + local size, points_array = buildNewVector2Array(value) + self.__data.num_points = size + self.__data.control_points = points_array + self.__data_points_ref = points_array + else + rawset(self, key, value) + end +end + +---创建一个贝塞尔曲线 +---@param control_points foundation.math.Vector2[] 控制点数组 +---@return foundation.shape.BezierCurve +function BezierCurve.create(control_points) + if not control_points or #control_points < 2 then + error("BezierCurve requires at least 2 control points") + end + + local size, points_array = buildNewVector2Array(control_points) + local order = size - 1 + + local curve = ffi.new("foundation_shape_BezierCurve", order, size, points_array) + local result = { + __data = curve, + __data_points_ref = points_array, + } + + ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value + return setmetatable(result, BezierCurve) +end + +---创建一个二次贝塞尔曲线 +---@param p0 foundation.math.Vector2 起始点 +---@param p1 foundation.math.Vector2 控制点 +---@param p2 foundation.math.Vector2 结束点 +---@return foundation.shape.BezierCurve +function BezierCurve.createQuadratic(p0, p1, p2) + return BezierCurve.create({ p0, p1, p2 }) +end + +---创建一个三次贝塞尔曲线 +---@param p0 foundation.math.Vector2 起始点 +---@param p1 foundation.math.Vector2 控制点1 +---@param p2 foundation.math.Vector2 控制点2 +---@param p3 foundation.math.Vector2 结束点 +---@return foundation.shape.BezierCurve +function BezierCurve.createCubic(p0, p1, p2, p3) + return BezierCurve.create({ p0, p1, p2, p3 }) +end + +---贝塞尔曲线相等比较 +---@param a foundation.shape.BezierCurve 第一条贝塞尔曲线 +---@param b foundation.shape.BezierCurve 第二条贝塞尔曲线 +---@return boolean +function BezierCurve.__eq(a, b) + if a.order ~= b.order or a.num_points ~= b.num_points then + return false + end + + for i = 0, a.num_points - 1 do + if a.control_points[i] ~= b.control_points[i] then + return false + end + end + + return true +end + +---贝塞尔曲线转字符串表示 +---@param self foundation.shape.BezierCurve +---@return string +function BezierCurve.__tostring(self) + local pointsStr = {} + for i = 0, self.num_points - 1 do + pointsStr[i + 1] = tostring(self.control_points[i]) + end + return string.format("BezierCurve(%s)", table.concat(pointsStr, ", ")) +end + +---获取贝塞尔曲线上参数为t的点(t范围0到1) +---@param t number 参数值(0-1) +---@return foundation.math.Vector2 +function BezierCurve:getPoint(t) + if t <= 0 then + return self.control_points[0]:clone() + elseif t >= 1 then + return self.control_points[self.num_points - 1]:clone() + end + + local points = {} + for i = 0, self.num_points - 1 do + points[i + 1] = self.control_points[i]:clone() + end + + for r = 1, self.order do + for i = 1, self.num_points - r do + points[i] = points[i] * (1 - t) + points[i + 1] * t + end + end + + return points[1] +end + +---获取贝塞尔曲线上参数为t的切线单位向量 +---@param t number 参数值(0-1) +---@return foundation.math.Vector2 +function BezierCurve:getTangent(t) + if self.order <= 1 then + return (self.control_points[1] - self.control_points[0]):normalized() + end + + local dt = 0.0001 + local p1 = self:getPoint(t) + local p2 = self:getPoint(t + dt) + return (p2 - p1):normalized() +end + +---获取贝塞尔曲线的起点 +---@return foundation.math.Vector2 +function BezierCurve:getStartPoint() + return self.control_points[0]:clone() +end + +---获取贝塞尔曲线的终点 +---@return foundation.math.Vector2 +function BezierCurve:getEndPoint() + return self.control_points[self.num_points - 1]:clone() +end + +---转换为一系列线段的近似表示 +---@param segments number 分段数 +---@return foundation.shape.Segment[] +function BezierCurve:toSegments(segments) + segments = segments or 10 + + local points = self:discretize(segments) + local segs = {} + + for i = 1, #points - 1 do + segs[i] = Segment.create(points[i], points[i + 1]) + end + + return segs +end + +---将贝塞尔曲线离散化为一系列点 +---@param segments number 分段数 +---@return foundation.math.Vector2[] +function BezierCurve:discretize(segments) + segments = segments or 10 + local points = {} + + for i = 0, segments do + local t = i / segments + points[i + 1] = self:getPoint(t) + end + + return points +end + +---获取贝塞尔曲线的近似长度 +---@param segments number 分段数,用于近似计算 +---@return number +function BezierCurve:length(segments) + segments = segments or 20 + local points = self:discretize(segments) + local length = 0 + + for i = 2, #points do + length = length + (points[i] - points[i - 1]):length() + end + + return length +end + +---计算贝塞尔曲线的中心 +---@return foundation.math.Vector2 +function BezierCurve:getCenter() + local minX, minY = math.huge, math.huge + local maxX, maxY = -math.huge, -math.huge + + local function updateBounds(point) + minX = math.min(minX, point.x) + minY = math.min(minY, point.y) + maxX = math.max(maxX, point.x) + maxY = math.max(maxY, point.y) + end + + updateBounds(self.control_points[0]) + updateBounds(self.control_points[self.num_points - 1]) + + local n = self.order + if n > 1 then + local deriv_points = {} + for i = 0, n - 1 do + deriv_points[i + 1] = (self.control_points[i + 1] - self.control_points[i]) * n + end + + local t_values = { 0, 1 } + if n == 2 then + local p0, p1, p2 = self.control_points[0], self.control_points[1], self.control_points[2] + local dx1 = p1.x - p0.x + local dx2 = p2.x - p1.x + local dy1 = p1.y - p0.y + local dy2 = p2.y - p1.y + + if dx1 ~= dx2 then + local tx = dx1 / (dx1 - dx2) + if tx > 0 and tx < 1 then + table.insert(t_values, tx) + end + end + + if dy1 ~= dy2 then + local ty = dy1 / (dy1 - dy2) + if ty > 0 and ty < 1 then + table.insert(t_values, ty) + end + end + elseif n == 3 then + local q0 = deriv_points[1] + local q1 = deriv_points[2] + local q2 = deriv_points[3] + + local a_x = q0.x - 2 * q1.x + q2.x + local b_x = 2 * (q1.x - q0.x) + local c_x = q0.x + if a_x ~= 0 then + local discriminant_x = b_x * b_x - 4 * a_x * c_x + if discriminant_x >= 0 then + local sqrt_d = math.sqrt(discriminant_x) + local t1 = (-b_x + sqrt_d) / (2 * a_x) + local t2 = (-b_x - sqrt_d) / (2 * a_x) + if t1 > 0 and t1 < 1 then + table.insert(t_values, t1) + end + if t2 > 0 and t2 < 1 then + table.insert(t_values, t2) + end + end + elseif b_x ~= 0 then + local t = -c_x / b_x + if t > 0 and t < 1 then + table.insert(t_values, t) + end + end + + local a_y = q0.y - 2 * q1.y + q2.y + local b_y = 2 * (q1.y - q0.y) + local c_y = q0.y + if a_y ~= 0 then + local discriminant_y = b_y * b_y - 4 * a_y * c_y + if discriminant_y >= 0 then + local sqrt_d = math.sqrt(discriminant_y) + local t1 = (-b_y + sqrt_d) / (2 * a_y) + local t2 = (-b_y - sqrt_d) / (2 * a_y) + if t1 > 0 and t1 < 1 then + table.insert(t_values, t1) + end + if t2 > 0 and t2 < 1 then + table.insert(t_values, t2) + end + end + elseif b_y ~= 0 then + local t = -c_y / b_y + if t > 0 and t < 1 then + table.insert(t_values, t) + end + end + end + + for _, t in ipairs(t_values) do + updateBounds(self:getPoint(t)) + end + end + + return Vector2.create((minX + maxX) / 2, (minY + maxY) / 2) +end + +---计算贝塞尔曲线的包围盒宽高 +---@return number, number +function BezierCurve:getBoundingBoxSize() + local minX, minY = math.huge, math.huge + local maxX, maxY = -math.huge, -math.huge + + local function updateBounds(point) + minX = math.min(minX, point.x) + minY = math.min(minY, point.y) + maxX = math.max(maxX, point.x) + maxY = math.max(maxY, point.y) + end + + updateBounds(self.control_points[0]) + updateBounds(self.control_points[self.num_points - 1]) + + local n = self.order + if n > 1 then + local deriv_points = {} + for i = 0, n - 1 do + deriv_points[i + 1] = (self.control_points[i + 1] - self.control_points[i]) * n + end + + local t_values = { 0, 1 } + if n == 2 then + local p0, p1, p2 = self.control_points[0], self.control_points[1], self.control_points[2] + local dx1 = p1.x - p0.x + local dx2 = p2.x - p1.x + local dy1 = p1.y - p0.y + local dy2 = p2.y - p1.y + + if dx1 ~= dx2 then + local tx = dx1 / (dx1 - dx2) + if tx > 0 and tx < 1 then + table.insert(t_values, tx) + end + end + + if dy1 ~= dy2 then + local ty = dy1 / (dy1 - dy2) + if ty > 0 and ty < 1 then + table.insert(t_values, ty) + end + end + elseif n == 3 then + local q0 = deriv_points[1] + local q1 = deriv_points[2] + local q2 = deriv_points[3] + + local a_x = q0.x - 2 * q1.x + q2.x + local b_x = 2 * (q1.x - q0.x) + local c_x = q0.x + if a_x ~= 0 then + local discriminant_x = b_x * b_x - 4 * a_x * c_x + if discriminant_x >= 0 then + local sqrt_d = math.sqrt(discriminant_x) + local t1 = (-b_x + sqrt_d) / (2 * a_x) + local t2 = (-b_x - sqrt_d) / (2 * a_x) + if t1 > 0 and t1 < 1 then + table.insert(t_values, t1) + end + if t2 > 0 and t2 < 1 then + table.insert(t_values, t2) + end + end + elseif b_x ~= 0 then + local t = -c_x / b_x + if t > 0 and t < 1 then + table.insert(t_values, t) + end + end + + local a_y = q0.y - 2 * q1.y + q2.y + local b_y = 2 * (q1.y - q0.y) + local c_y = q0.y + if a_y ~= 0 then + local discriminant_y = b_y * b_y - 4 * a_y * c_y + if discriminant_y >= 0 then + local sqrt_d = math.sqrt(discriminant_y) + local t1 = (-b_y + sqrt_d) / (2 * a_y) + local t2 = (-b_y - sqrt_d) / (2 * a_y) + if t1 > 0 and t1 < 1 then + table.insert(t_values, t1) + end + if t2 > 0 and t2 < 1 then + table.insert(t_values, t2) + end + end + elseif b_y ~= 0 then + local t = -c_y / b_y + if t > 0 and t < 1 then + table.insert(t_values, t) + end + end + end + + for _, t in ipairs(t_values) do + updateBounds(self:getPoint(t)) + end + end + + return maxX - minX, maxY - minY +end + +---将当前贝塞尔曲线平移指定距离(更改当前曲线) +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.BezierCurve 自身引用 +function BezierCurve:move(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + + local new_points = {} + for i = 0, self.num_points - 1 do + local p = self.control_points[i] + new_points[i + 1] = Vector2.create(p.x + moveX, p.y + moveY) + end + + self.control_points = new_points + return self +end + +---获取平移后的贝塞尔曲线副本 +---@param v foundation.math.Vector2 | number 移动距离 +---@return foundation.shape.BezierCurve +function BezierCurve:moved(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + + local new_points = {} + for i = 0, self.num_points - 1 do + local p = self.control_points[i] + new_points[i + 1] = Vector2.create(p.x + moveX, p.y + moveY) + end + + return BezierCurve.create(new_points) +end + +---将当前贝塞尔曲线旋转指定弧度(更改当前曲线) +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心,默认为曲线的中心 +---@return foundation.shape.BezierCurve 自身引用 +function BezierCurve:rotate(rad, center) + center = center or self:getCenter() + local cosRad = math.cos(rad) + local sinRad = math.sin(rad) + + local new_points = {} + for i = 0, self.num_points - 1 do + local p = self.control_points[i] + local v = p - center + local x = v.x * cosRad - v.y * sinRad + center.x + local y = v.x * sinRad + v.y * cosRad + center.y + new_points[i + 1] = Vector2.create(x, y) + end + + self.control_points = new_points + return self +end + +---将当前贝塞尔曲线旋转指定角度(更改当前曲线) +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心,默认为曲线的中心 +---@return foundation.shape.BezierCurve 自身引用 +function BezierCurve:degreeRotate(angle, center) + return self:rotate(math.rad(angle), center) +end + +---获取旋转后的贝塞尔曲线副本 +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2 旋转中心,默认为曲线的中心 +---@return foundation.shape.BezierCurve +function BezierCurve:rotated(rad, center) + center = center or self:getCenter() + local cosRad = math.cos(rad) + local sinRad = math.sin(rad) + + local new_points = {} + for i = 0, self.num_points - 1 do + local p = self.control_points[i] + local v = p - center + local x = v.x * cosRad - v.y * sinRad + center.x + local y = v.x * sinRad + v.y * cosRad + center.y + new_points[i + 1] = Vector2.create(x, y) + end + + return BezierCurve.create(new_points) +end + +---获取旋转后的贝塞尔曲线副本 +---@param angle number 旋转角度 +---@param center foundation.math.Vector2 旋转中心,默认为曲线的中心 +---@return foundation.shape.BezierCurve +function BezierCurve:degreeRotated(angle, center) + return self:rotated(math.rad(angle), center) +end + +---将当前贝塞尔曲线缩放指定倍数(更改当前曲线) +---@param scale number | foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2 缩放中心,默认为曲线的中心 +---@return foundation.shape.BezierCurve 自身引用 +function BezierCurve:scale(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + + center = center or self:getCenter() + + local new_points = {} + for i = 0, self.num_points - 1 do + local p = self.control_points[i] + local x = (p.x - center.x) * scaleX + center.x + local y = (p.y - center.y) * scaleY + center.y + new_points[i + 1] = Vector2.create(x, y) + end + + self.control_points = new_points + return self +end + +---获取缩放后的贝塞尔曲线副本 +---@param scale number | foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2 缩放中心,默认为曲线的中心 +---@return foundation.shape.BezierCurve +function BezierCurve:scaled(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + + center = center or self:getCenter() + + local new_points = {} + for i = 0, self.num_points - 1 do + local p = self.control_points[i] + local x = (p.x - center.x) * scaleX + center.x + local y = (p.y - center.y) * scaleY + center.y + new_points[i + 1] = Vector2.create(x, y) + end + + return BezierCurve.create(new_points) +end + +---获取曲线上最近的点 +---@param point foundation.math.Vector2 参考点 +---@return foundation.math.Vector2 曲线上最近的点 +function BezierCurve:closestPoint(point) + return self:closestPointWithSegments(point) +end + +---获取曲线上最近的点 +---@param point foundation.math.Vector2 参考点 +---@param segments number 分段数,用于近似计算 +---@return foundation.math.Vector2 曲线上最近的点 +---@overload fun(self: foundation.shape.BezierCurve, point: foundation.math.Vector2): foundation.math.Vector2 +function BezierCurve:closestPointWithSegments(point, segments) + segments = segments or 20 + local minDist = math.huge + local closest + + local points = self:discretize(segments) + for _, p in ipairs(points) do + local dist = (p - point):length() + if dist < minDist then + minDist = dist + closest = p + end + end + + return closest +end + +---将点投影到贝塞尔曲线上 +---@param point foundation.math.Vector2 要投影的点 +---@return foundation.math.Vector2, number +function BezierCurve:projectPoint(point) + return self:projectPointWithSegments(point) +end + +---将点投影到贝塞尔曲线上 +---@param point foundation.math.Vector2 要投影的点 +---@param segments number 分段数,用于近似计算 +---@return foundation.math.Vector2, number +---@overload fun(self: foundation.shape.BezierCurve, point: foundation.math.Vector2): foundation.math.Vector2, number +function BezierCurve:projectPointWithSegments(point, segments) + segments = segments or 20 + local minDist = math.huge + local closestPoint + local closestT + + for i = 0, segments do + local t = i / segments + local p = self:getPoint(t) + local dist = (p - point):length() + if dist < minDist then + minDist = dist + closestPoint = p + closestT = t + end + end + + return closestPoint, closestT +end + +---计算点到贝塞尔曲线的距离 +---@param point foundation.math.Vector2 参考点 +---@param segments number 分段数,用于近似计算 +---@return number 距离 +function BezierCurve:distanceToPoint(point, segments) + local closestPoint = self:closestPointWithSegments(point, segments) + return (closestPoint - point):length() +end + +---拆分贝塞尔曲线为两部分 +---@param t number 分割参数(0-1) +---@return foundation.shape.BezierCurve, foundation.shape.BezierCurve 前半部分和后半部分 +function BezierCurve:split(t) + if t <= 0 then + return BezierCurve.create({ self.control_points[0] }), self + elseif t >= 1 then + return self, BezierCurve.create({ self.control_points[self.num_points - 1] }) + end + + local points = {} + for i = 0, self.num_points - 1 do + points[i + 1] = self.control_points[i]:clone() + end + + local left_points = { points[1] } + + for r = 1, self.order do + for i = 1, self.num_points - r do + points[i] = points[i] * (1 - t) + points[i + 1] * t + end + left_points[r + 1] = points[1] + end + + local right_points = {} + for i = 1, self.order + 1 do + right_points[i] = points[i] + end + + return BezierCurve.create(left_points), BezierCurve.create(right_points) +end + +---检查与其他形状的相交 +---@param other any +---@return boolean, foundation.math.Vector2[] | nil +function BezierCurve:intersects(other) + return ShapeIntersector.intersect(self, other) +end + +---仅检查是否与其他形状相交 +---@param other any +---@return boolean +function BezierCurve:hasIntersection(other) + return ShapeIntersector.hasIntersection(self, other) +end + +---检查点是否在贝塞尔曲线上 +---@param point foundation.math.Vector2 +---@param tolerance number|nil 容差,默认为1e-10 +---@return boolean +function BezierCurve:containsPoint(point, tolerance) + tolerance = tolerance or 1e-10 + local projPoint = self:closestPointWithSegments(point) + return (point - projPoint):length() <= tolerance +end + +---克隆贝塞尔曲线 +---@return foundation.shape.BezierCurve +function BezierCurve:clone() + local new_points = {} + for i = 0, self.num_points - 1 do + new_points[i + 1] = self.control_points[i]:clone() + end + return BezierCurve.create(new_points) +end + +ffi.metatype("foundation_shape_BezierCurve", BezierCurve) + +return BezierCurve \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua new file mode 100644 index 00000000..c7fea2be --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua @@ -0,0 +1,471 @@ +local ffi = require("ffi") + +local type = type +local tostring = tostring +local string = string +local math = math +local rawset = rawset +local setmetatable = setmetatable + +local Vector2 = require("foundation.math.Vector2") +local Segment = require("foundation.shape.Segment") +local ShapeIntersector = require("foundation.shape.ShapeIntersector") + +ffi.cdef [[ +typedef struct { + foundation_math_Vector2 center; + double rx; + double ry; + foundation_math_Vector2 direction; +} foundation_shape_Ellipse; +]] + +---@class foundation.shape.Ellipse +---@field center foundation.math.Vector2 椭圆中心 +---@field rx number x半轴长度(横向半径) +---@field ry number y半轴长度(纵向半径) +---@field direction foundation.math.Vector2 方向(归一化向量,指向椭圆x轴正半轴方向) +local Ellipse = {} +Ellipse.__type = "foundation.shape.Ellipse" + +---@param self foundation.shape.Ellipse +---@param key any +---@return any +function Ellipse.__index(self, key) + if key == "center" then + return self.__data.center + elseif key == "rx" then + return self.__data.rx + elseif key == "ry" then + return self.__data.ry + elseif key == "direction" then + return self.__data.direction + end + return Ellipse[key] +end + +---@param self foundation.shape.Ellipse +---@param key any +---@param value any +function Ellipse.__newindex(self, key, value) + if key == "center" then + self.__data.center = value + elseif key == "rx" then + self.__data.rx = value + elseif key == "ry" then + self.__data.ry = value + elseif key == "direction" then + self.__data.direction = value + else + rawset(self, key, value) + end +end + +---创建一个新的椭圆 +---@param center foundation.math.Vector2 椭圆中心 +---@param rx number x半轴长度(横向半径) +---@param ry number y半轴长度(纵向半径) +---@param direction foundation.math.Vector2|nil 方向向量,默认为(1,0) +---@return foundation.shape.Ellipse +function Ellipse.create(center, rx, ry, direction) + direction = direction or Vector2.create(1, 0) + direction = direction:normalized() + local ellipse = ffi.new("foundation_shape_Ellipse", center, rx, ry, direction) + local result = { + __data = ellipse, + } + ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value + return setmetatable(result, Ellipse) +end + +---使用弧度创建椭圆 +---@param center foundation.math.Vector2 椭圆中心 +---@param rx number x半轴长度(横向半径) +---@param ry number y半轴长度(纵向半径) +---@param rad number 方向弧度 +---@return foundation.shape.Ellipse +function Ellipse.createFromRad(center, rx, ry, rad) + local dir = Vector2.createFromRad(rad) + return Ellipse.create(center, rx, ry, dir) +end + +---使用角度单位创建椭圆 +---@param center foundation.math.Vector2 椭圆中心 +---@param rx number x半轴长度(横向半径) +---@param ry number y半轴长度(纵向半径) +---@param angle number 椭圆方向角度(角度) +---@return foundation.shape.Ellipse +function Ellipse.createFromAngle(center, rx, ry, angle) + return Ellipse.createFromRad(center, rx, ry, math.rad(angle)) +end + +---椭圆相等比较 +---@param a foundation.shape.Ellipse +---@param b foundation.shape.Ellipse +---@return boolean +function Ellipse.__eq(a, b) + return a.center == b.center and + math.abs(a.rx - b.rx) <= 1e-10 and + math.abs(a.ry - b.ry) <= 1e-10 and + a.direction == b.direction +end + +---椭圆的字符串表示 +---@param self foundation.shape.Ellipse +---@return string +function Ellipse.__tostring(self) + return string.format("Ellipse(center=%s, rx=%f, ry=%f, direction=%s)", + tostring(self.center), self.rx, self.ry, tostring(self.direction)) +end + +---获取椭圆上指定角度的点 +---@param angle number 角度(弧度) +---@return foundation.math.Vector2 +function Ellipse:getPointAtAngle(angle) + local baseAngle = self.direction:angle() + + local cos_angle = math.cos(angle) + local sin_angle = math.sin(angle) + + local x = self.rx * cos_angle + local y = self.ry * sin_angle + + local cos_rotation = math.cos(baseAngle) + local sin_rotation = math.sin(baseAngle) + + local px = cos_rotation * x - sin_rotation * y + self.center.x + local py = sin_rotation * x + cos_rotation * y + self.center.y + + return Vector2.create(px, py) +end + +---将椭圆离散化为一系列点 +---@param segments number 分段数 +---@return foundation.math.Vector2[] +function Ellipse:discretize(segments) + segments = segments or 30 + local points = {} + + for i = 0, segments do + local angle = 2 * math.pi * i / segments + points[i + 1] = self:getPointAtAngle(angle) + end + + return points +end + +---获取椭圆的离散顶点(重命名discretize以保持一致性) +---@param segments number 分段数 +---@return foundation.math.Vector2[] +function Ellipse:getVertices(segments) + return self:discretize(segments or 30) +end + +---获取椭圆的离散边缘线段 +---@param segments number 分段数 +---@return foundation.shape.Segment[] +function Ellipse:getEdges(segments) + segments = segments or 30 + local points = self:discretize(segments) + local segs = {} + + for i = 1, segments do + segs[i] = Segment.create(points[i], points[i + 1]) + end + + return segs +end + +---计算椭圆的内心 +---@return foundation.math.Vector2 椭圆的内心 +function Ellipse:incenter() + return self.center:clone() +end + +---计算椭圆的内切圆半径 +---@return number 椭圆的内切圆半径 +function Ellipse:inradius() + return math.min(self.rx, self.ry) +end + +---计算椭圆的外心 +---@return foundation.math.Vector2 椭圆的外心 +function Ellipse:circumcenter() + return self.center:clone() +end + +---计算椭圆的外接圆半径 +---@return number 椭圆的外接圆半径 +function Ellipse:circumradius() + return math.max(self.rx, self.ry) +end + +---移动椭圆(修改当前椭圆) +---@param v foundation.math.Vector2 | number +---@return foundation.shape.Ellipse 自身引用 +function Ellipse:move(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + self.center.x = self.center.x + moveX + self.center.y = self.center.y + moveY + return self +end + +---获取移动后的椭圆副本 +---@param v foundation.math.Vector2 | number +---@return foundation.shape.Ellipse +function Ellipse:moved(v) + local moveX, moveY + if type(v) == "number" then + moveX, moveY = v, v + else + moveX, moveY = v.x, v.y + end + return Ellipse.create( + Vector2.create(self.center.x + moveX, self.center.y + moveY), + self.rx, self.ry, self.direction + ) +end + +---旋转椭圆(修改当前椭圆) +---@param rad number 旋转弧度 +---@return foundation.shape.Ellipse 自身引用 +function Ellipse:rotate(rad) + self.direction = self.direction:rotated(rad) + return self +end + +---旋转椭圆(修改当前椭圆) +---@param angle number 旋转角度 +---@return foundation.shape.Ellipse 自身引用 +function Ellipse:degreeRotate(angle) + return self:rotate(math.rad(angle)) +end + +---获取旋转后的椭圆副本 +---@param rad number 旋转弧度 +---@return foundation.shape.Ellipse +function Ellipse:rotated(rad) + return Ellipse.create( + self.center:clone(), + self.rx, + self.ry, + self.direction:rotated(rad) + ) +end + +---获取旋转后的椭圆副本 +---@param angle number 旋转角度 +---@return foundation.shape.Ellipse +function Ellipse:degreeRotated(angle) + return self:rotated(math.rad(angle)) +end + +---缩放椭圆(修改当前椭圆) +---@param scale number|foundation.math.Vector2 缩放比例 +---@return foundation.shape.Ellipse 自身引用 +function Ellipse:scale(scale) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + self.rx = self.rx * scaleX + self.ry = self.ry * scaleY + return self +end + +---获取缩放后的椭圆副本 +---@param scale number|foundation.math.Vector2 缩放比例 +---@return foundation.shape.Ellipse +function Ellipse:scaled(scale) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + return Ellipse.create(self.center:clone(), self.rx * scaleX, self.ry * scaleY, self.direction) +end + +---检查点是否在椭圆内或椭圆上 +---@param point foundation.math.Vector2 +---@return boolean +function Ellipse:contains(point) + return ShapeIntersector.ellipseContainsPoint(self, point) +end + +---检查与其他形状的相交 +---@param other any +---@return boolean, foundation.math.Vector2[] | nil +function Ellipse:intersects(other) + return ShapeIntersector.intersect(self, other) +end + +---只检查是否与其他形状相交,不计算交点 +---@param other any +---@return boolean +function Ellipse:hasIntersection(other) + return ShapeIntersector.hasIntersection(self, other) +end + +---计算椭圆的面积 +---@return number 椭圆的面积 +function Ellipse:area() + return math.pi * self.rx * self.ry +end + +---计算椭圆的周长(近似值) +---@return number 椭圆的周长近似值 +function Ellipse:getPerimeter() + local h = (self.rx - self.ry) ^ 2 / (self.rx + self.ry) ^ 2 + return math.pi * (self.rx + self.ry) * (1 + 3 * h / (10 + math.sqrt(4 - 3 * h))) +end + +---计算椭圆的中心 +---@return foundation.math.Vector2 椭圆中心点 +function Ellipse:getCenter() + return self.center:clone() +end + +---计算椭圆的包围盒宽高 +---@return number, number +function Ellipse:getBoundingBoxSize() + local baseAngle = self.direction:angle() + local cos_angle = math.cos(baseAngle) + local sin_angle = math.sin(baseAngle) + + local x_axis = Vector2.create(self.rx * cos_angle, self.rx * sin_angle) + local y_axis = Vector2.create(-self.ry * sin_angle, self.ry * cos_angle) + + local width = 2 * math.sqrt(x_axis.x * x_axis.x + y_axis.x * y_axis.x) + local height = 2 * math.sqrt(x_axis.y * x_axis.y + y_axis.y * y_axis.y) + + return width, height +end + +---计算椭圆的重心 +---@return foundation.math.Vector2 椭圆中心点 +function Ellipse:centroid() + return self.center:clone() +end + +---计算点到椭圆的最近点 +---@param point foundation.math.Vector2 要检查的点 +---@param boundary boolean 是否限制在边界上,默认为false +---@return foundation.math.Vector2 椭圆上最近的点 +---@overload fun(self:foundation.shape.Ellipse, point:foundation.math.Vector2): foundation.math.Vector2 +function Ellipse:closestPoint(point, boundary) + if not boundary and self:contains(point) then + return point:clone() + end + + -- Transform point to ellipse's local coordinate system + local baseAngle = self.direction:angle() + local cos_rotation = math.cos(-baseAngle) + local sin_rotation = math.sin(-baseAngle) + + local dx = point.x - self.center.x + local dy = point.y - self.center.y + + local x = cos_rotation * dx - sin_rotation * dy + local y = sin_rotation * dx + cos_rotation * dy + + -- Scale to unit circle space + local px = x / self.rx + local py = y / self.ry + + local length = math.sqrt(px * px + py * py) + if length < 1e-10 then + -- Point is at center, return point on major axis + px = self.rx + py = 0 + else + -- Calculate both possible boundary points + local px1 = px / length * self.rx + local py1 = py / length * self.ry + local px2 = -px / length * self.rx + local py2 = -py / length * self.ry + + -- Transform both points back to world coordinates + local cos_world = math.cos(baseAngle) + local sin_world = math.sin(baseAngle) + + local result_x1 = cos_world * px1 - sin_world * py1 + self.center.x + local result_y1 = sin_world * px1 + cos_world * py1 + self.center.y + local point1 = Vector2.create(result_x1, result_y1) + + local result_x2 = cos_world * px2 - sin_world * py2 + self.center.x + local result_y2 = sin_world * px2 + cos_world * py2 + self.center.y + local point2 = Vector2.create(result_x2, result_y2) + + -- Return the point that's closer to the input point + local dist1 = (point - point1):length() + local dist2 = (point - point2):length() + return dist1 <= dist2 and point1 or point2 + end + + -- Transform back to world coordinates + local cos_world = math.cos(baseAngle) + local sin_world = math.sin(baseAngle) + local result_x = cos_world * px - sin_world * py + self.center.x + local result_y = sin_world * px + cos_world * py + self.center.y + + return Vector2.create(result_x, result_y) +end + +---计算点到椭圆的距离 +---@param point foundation.math.Vector2 要检查的点 +---@return number 点到椭圆的距离 +function Ellipse:distanceToPoint(point) + if self:contains(point) then + return 0 + end + + local closestPoint = self:closestPoint(point, true) + return (point - closestPoint):length() +end + +---将点投影到椭圆上 +---@param point foundation.math.Vector2 要投影的点 +---@return foundation.math.Vector2 投影点 +function Ellipse:projectPoint(point) + return self:closestPoint(point, true) +end + +---检查点是否在椭圆上 +---@param point foundation.math.Vector2 要检查的点 +---@param tolerance number|nil 容差,默认为1e-10 +---@return boolean 点是否在椭圆上 +---@overload fun(self:foundation.shape.Ellipse, point:foundation.math.Vector2): boolean +function Ellipse:containsPoint(point, tolerance) + tolerance = tolerance or 1e-10 + + local baseAngle = self.direction:angle() + local cos_rotation = math.cos(-baseAngle) + local sin_rotation = math.sin(-baseAngle) + + local dx = point.x - self.center.x + local dy = point.y - self.center.y + + local x = cos_rotation * dx - sin_rotation * dy + local y = sin_rotation * dx + cos_rotation * dy + + local value = (x * x) / (self.rx * self.rx) + (y * y) / (self.ry * self.ry) + return math.abs(1 - math.abs(value)) * 10 <= tolerance -- i don't think is correct, but it works +end + +---创建椭圆的一个副本 +---@return foundation.shape.Ellipse +function Ellipse:clone() + return Ellipse.create(self.center:clone(), self.rx, self.ry, self.direction:clone()) +end + +ffi.metatype("foundation_shape_Ellipse", Ellipse) + +return Ellipse \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index 79be5629..3c2e1dba 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -560,7 +560,7 @@ function Polygon:containsPoint(point, tolerance) end --region 三角剖分的辅助函数 ---- 获取前一个点索引 +---获取前一个点索引 ---@param points table 点数组 ---@param i number 当前索引 ---@return number 前一个点的索引 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index d53f99c5..fbe435be 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -133,7 +133,7 @@ end function Rectangle:getVertices() local hw, hh = self.width / 2, self.height / 2 local dir = self.direction - local perp = Vector2.create(-dir.y, dir.x) -- 垂直向量(高度方向) + local perp = Vector2.create(-dir.y, dir.x) local vertices = { Vector2.create(-hw, -hh), Vector2.create(hw, -hh), diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index 24c008fb..c0738ff7 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -70,9 +70,18 @@ end ---@param range number 范围(-1到1) ---@return foundation.shape.Sector function Sector.create(center, radius, direction, range) - local dir = direction:normalized() - range = math.max(-1, math.min(1, range)) -- 限制范围在-1到1 - local sector = ffi.new("foundation_shape_Sector", center, radius, dir, range) + local dist = direction and direction:length() or 0 + if dist <= 1e-10 then + direction = Vector2.create(1, 0) + elseif dist ~= 1 then + ---@diagnostic disable-next-line: need-check-nil + direction = direction:normalized() + else + ---@diagnostic disable-next-line: need-check-nil + direction = direction:clone() + end + range = math.max(-1, math.min(1, range)) + local sector = ffi.new("foundation_shape_Sector", center, radius, direction, range) local result = { __data = sector, } @@ -177,10 +186,10 @@ function Sector:getCenter() local max_angle = math.max(start_angle, end_angle) local critical_points = { - { angle = 0, point = Vector2.create(self.center.x + self.radius, self.center.y) }, -- x_max - { angle = math.pi, point = Vector2.create(self.center.x - self.radius, self.center.y) }, -- x_min - { angle = math.pi / 2, point = Vector2.create(self.center.x, self.center.y + self.radius) }, -- y_max - { angle = 3 * math.pi / 2, point = Vector2.create(self.center.x, self.center.y - self.radius) } -- y_min + { angle = 0, point = Vector2.create(self.center.x + self.radius, self.center.y) }, + { angle = math.pi, point = Vector2.create(self.center.x - self.radius, self.center.y) }, + { angle = math.pi / 2, point = Vector2.create(self.center.x, self.center.y + self.radius) }, + { angle = 3 * math.pi / 2, point = Vector2.create(self.center.x, self.center.y - self.radius) } } for _, cp in ipairs(critical_points) do @@ -224,10 +233,10 @@ function Sector:getBoundingBoxSize() local max_angle = math.max(start_angle, end_angle) local critical_points = { - { angle = 0, point = Vector2.create(self.center.x + self.radius, self.center.y) }, -- x_max - { angle = math.pi, point = Vector2.create(self.center.x - self.radius, self.center.y) }, -- x_min - { angle = math.pi / 2, point = Vector2.create(self.center.x, self.center.y + self.radius) }, -- y_max - { angle = 3 * math.pi / 2, point = Vector2.create(self.center.x, self.center.y - self.radius) } -- y_min + { angle = 0, point = Vector2.create(self.center.x + self.radius, self.center.y) }, + { angle = math.pi, point = Vector2.create(self.center.x - self.radius, self.center.y) }, + { angle = math.pi / 2, point = Vector2.create(self.center.x, self.center.y + self.radius) }, + { angle = 3 * math.pi / 2, point = Vector2.create(self.center.x, self.center.y - self.radius) } } for _, cp in ipairs(critical_points) do diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua index 30d6e68a..40e5e7b5 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua @@ -8,6 +8,7 @@ local ShapeIntersector = {} local ShapeIntersectorList = { "ContainPoint", "CircleIntersector", + "EllipseIntersector", "LineIntersector", "PolygonIntersector", "RectangleIntersector", @@ -15,6 +16,7 @@ local ShapeIntersectorList = { "TriangleIntersector", "SegmentIntersector", "RayIntersector", + "BezierCurveIntersector", } for _, moduleName in ipairs(ShapeIntersectorList) do local module = require(string.format("foundation.shape.ShapeIntersector.%s", moduleName)) @@ -39,6 +41,29 @@ end ---@type table> local intersectionMap = { + ["foundation.shape.BezierCurve"] = { + ["foundation.shape.BezierCurve"] = ShapeIntersector.bezierCurveToBezierCurve, + ["foundation.shape.Polygon"] = ShapeIntersector.bezierCurveToPolygon, + ["foundation.shape.Segment"] = ShapeIntersector.bezierCurveToSegment, + ["foundation.shape.Circle"] = ShapeIntersector.bezierCurveToCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.bezierCurveToRectangle, + ["foundation.shape.Triangle"] = ShapeIntersector.bezierCurveToTriangle, + ["foundation.shape.Line"] = ShapeIntersector.bezierCurveToLine, + ["foundation.shape.Ray"] = ShapeIntersector.bezierCurveToRay, + ["foundation.shape.Sector"] = ShapeIntersector.bezierCurveToSector, + ["foundation.shape.Ellipse"] = ShapeIntersector.bezierCurveToEllipse, + }, + ["foundation.shape.Ellipse"] = { + ["foundation.shape.Ellipse"] = ShapeIntersector.ellipseToEllipse, + ["foundation.shape.Polygon"] = ShapeIntersector.ellipseToPolygon, + ["foundation.shape.Segment"] = ShapeIntersector.ellipseToSegment, + ["foundation.shape.Circle"] = ShapeIntersector.ellipseToCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.ellipseToRectangle, + ["foundation.shape.Triangle"] = ShapeIntersector.ellipseToTriangle, + ["foundation.shape.Line"] = ShapeIntersector.ellipseToLine, + ["foundation.shape.Ray"] = ShapeIntersector.ellipseToRay, + ["foundation.shape.Sector"] = ShapeIntersector.ellipseToSector, + }, ["foundation.shape.Polygon"] = { ["foundation.shape.Polygon"] = ShapeIntersector.polygonToPolygon, ["foundation.shape.Triangle"] = ShapeIntersector.polygonToTriangle, @@ -95,6 +120,29 @@ local intersectionMap = { ---@type table> local hasIntersectionMap = { + ["foundation.shape.BezierCurve"] = { + ["foundation.shape.BezierCurve"] = ShapeIntersector.bezierCurveHasIntersectionWithBezierCurve, + ["foundation.shape.Polygon"] = ShapeIntersector.bezierCurveHasIntersectionWithPolygon, + ["foundation.shape.Segment"] = ShapeIntersector.bezierCurveHasIntersectionWithSegment, + ["foundation.shape.Circle"] = ShapeIntersector.bezierCurveHasIntersectionWithCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.bezierCurveHasIntersectionWithRectangle, + ["foundation.shape.Triangle"] = ShapeIntersector.bezierCurveHasIntersectionWithTriangle, + ["foundation.shape.Line"] = ShapeIntersector.bezierCurveHasIntersectionWithLine, + ["foundation.shape.Ray"] = ShapeIntersector.bezierCurveHasIntersectionWithRay, + ["foundation.shape.Sector"] = ShapeIntersector.bezierCurveHasIntersectionWithSector, + ["foundation.shape.Ellipse"] = ShapeIntersector.bezierCurveHasIntersectionWithEllipse, + }, + ["foundation.shape.Ellipse"] = { + ["foundation.shape.Ellipse"] = ShapeIntersector.ellipseHasIntersectionWithEllipse, + ["foundation.shape.Polygon"] = ShapeIntersector.ellipseHasIntersectionWithPolygon, + ["foundation.shape.Segment"] = ShapeIntersector.ellipseHasIntersectionWithSegment, + ["foundation.shape.Circle"] = ShapeIntersector.ellipseHasIntersectionWithCircle, + ["foundation.shape.Rectangle"] = ShapeIntersector.ellipseHasIntersectionWithRectangle, + ["foundation.shape.Triangle"] = ShapeIntersector.ellipseHasIntersectionWithTriangle, + ["foundation.shape.Line"] = ShapeIntersector.ellipseHasIntersectionWithLine, + ["foundation.shape.Ray"] = ShapeIntersector.ellipseHasIntersectionWithRay, + ["foundation.shape.Sector"] = ShapeIntersector.ellipseHasIntersectionWithSector, + }, ["foundation.shape.Polygon"] = { ["foundation.shape.Polygon"] = ShapeIntersector.polygonHasIntersectionWithPolygon, ["foundation.shape.Triangle"] = ShapeIntersector.polygonHasIntersectionWithTriangle, diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/BezierCurveIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/BezierCurveIntersector.lua new file mode 100644 index 00000000..8bdff480 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/BezierCurveIntersector.lua @@ -0,0 +1,451 @@ +---@param ShapeIntersector foundation.shape.ShapeIntersector +return function(ShapeIntersector) + local ipairs = ipairs + + ---贝塞尔曲线与线段相交 + ---@param bezier foundation.shape.BezierCurve + ---@param segment foundation.shape.Segment + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.bezierCurveToSegment(bezier, segment) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + local intersectionPoints = {} + local hasIntersection = false + + for _, seg in ipairs(bezierSegments) do + local intersect, points = ShapeIntersector.segmentToSegment(seg, segment) + if intersect and points then + hasIntersection = true + for _, p in ipairs(points) do + intersectionPoints[#intersectionPoints + 1] = p + end + end + end + + if hasIntersection then + return true, ShapeIntersector.getUniquePoints(intersectionPoints) + else + return false, nil + end + end + + ---贝塞尔曲线与线段相交检查 + ---@param bezier foundation.shape.BezierCurve + ---@param segment foundation.shape.Segment + ---@return boolean + function ShapeIntersector.bezierCurveHasIntersectionWithSegment(bezier, segment) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + + for _, seg in ipairs(bezierSegments) do + if ShapeIntersector.segmentHasIntersectionWithSegment(seg, segment) then + return true + end + end + + return false + end + + ---贝塞尔曲线与线相交 + ---@param bezier foundation.shape.BezierCurve + ---@param line foundation.shape.Line + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.bezierCurveToLine(bezier, line) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + local intersectionPoints = {} + local hasIntersection = false + + for _, seg in ipairs(bezierSegments) do + local intersect, points = ShapeIntersector.lineToSegment(line, seg) + if intersect and points then + hasIntersection = true + for _, p in ipairs(points) do + intersectionPoints[#intersectionPoints + 1] = p + end + end + end + + if hasIntersection then + return true, ShapeIntersector.getUniquePoints(intersectionPoints) + else + return false, nil + end + end + + ---贝塞尔曲线与线相交检查 + ---@param bezier foundation.shape.BezierCurve + ---@param line foundation.shape.Line + ---@return boolean + function ShapeIntersector.bezierCurveHasIntersectionWithLine(bezier, line) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + + for _, seg in ipairs(bezierSegments) do + if ShapeIntersector.lineHasIntersectionWithSegment(line, seg) then + return true + end + end + + return false + end + + ---贝塞尔曲线与射线相交 + ---@param bezier foundation.shape.BezierCurve + ---@param ray foundation.shape.Ray + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.bezierCurveToRay(bezier, ray) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + local intersectionPoints = {} + local hasIntersection = false + + for _, seg in ipairs(bezierSegments) do + local intersect, points = ShapeIntersector.rayToSegment(ray, seg) + if intersect and points then + hasIntersection = true + for _, p in ipairs(points) do + intersectionPoints[#intersectionPoints + 1] = p + end + end + end + + if hasIntersection then + return true, ShapeIntersector.getUniquePoints(intersectionPoints) + else + return false, nil + end + end + + ---贝塞尔曲线与射线相交检查 + ---@param bezier foundation.shape.BezierCurve + ---@param ray foundation.shape.Ray + ---@return boolean + function ShapeIntersector.bezierCurveHasIntersectionWithRay(bezier, ray) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + + for _, seg in ipairs(bezierSegments) do + if ShapeIntersector.rayHasIntersectionWithSegment(ray, seg) then + return true + end + end + + return false + end + + ---贝塞尔曲线与圆相交 + ---@param bezier foundation.shape.BezierCurve + ---@param circle foundation.shape.Circle + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.bezierCurveToCircle(bezier, circle) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + local intersectionPoints = {} + local hasIntersection = false + + for _, seg in ipairs(bezierSegments) do + local intersect, points = ShapeIntersector.circleToSegment(circle, seg) + if intersect and points then + hasIntersection = true + for _, p in ipairs(points) do + intersectionPoints[#intersectionPoints + 1] = p + end + end + end + + if hasIntersection then + return true, ShapeIntersector.getUniquePoints(intersectionPoints) + else + return false, nil + end + end + + ---贝塞尔曲线与圆相交检查 + ---@param bezier foundation.shape.BezierCurve + ---@param circle foundation.shape.Circle + ---@return boolean + function ShapeIntersector.bezierCurveHasIntersectionWithCircle(bezier, circle) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + + for _, seg in ipairs(bezierSegments) do + if ShapeIntersector.circleHasIntersectionWithSegment(circle, seg) then + return true + end + end + + return false + end + + ---贝塞尔曲线与矩形相交 + ---@param bezier foundation.shape.BezierCurve + ---@param rectangle foundation.shape.Rectangle + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.bezierCurveToRectangle(bezier, rectangle) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + local intersectionPoints = {} + local hasIntersection = false + + for _, seg in ipairs(bezierSegments) do + local intersect, points = ShapeIntersector.rectangleToSegment(rectangle, seg) + if intersect and points then + hasIntersection = true + for _, p in ipairs(points) do + intersectionPoints[#intersectionPoints + 1] = p + end + end + end + + if hasIntersection then + return true, ShapeIntersector.getUniquePoints(intersectionPoints) + else + return false, nil + end + end + + ---贝塞尔曲线与矩形相交检查 + ---@param bezier foundation.shape.BezierCurve + ---@param rectangle foundation.shape.Rectangle + ---@return boolean + function ShapeIntersector.bezierCurveHasIntersectionWithRectangle(bezier, rectangle) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + + for _, seg in ipairs(bezierSegments) do + if ShapeIntersector.rectangleHasIntersectionWithSegment(rectangle, seg) then + return true + end + end + + return false + end + + ---贝塞尔曲线与三角形相交 + ---@param bezier foundation.shape.BezierCurve + ---@param triangle foundation.shape.Triangle + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.bezierCurveToTriangle(bezier, triangle) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + local intersectionPoints = {} + local hasIntersection = false + + for _, seg in ipairs(bezierSegments) do + local intersect, points = ShapeIntersector.triangleToSegment(triangle, seg) + if intersect and points then + hasIntersection = true + for _, p in ipairs(points) do + intersectionPoints[#intersectionPoints + 1] = p + end + end + end + + if hasIntersection then + return true, ShapeIntersector.getUniquePoints(intersectionPoints) + else + return false, nil + end + end + + ---贝塞尔曲线与三角形相交检查 + ---@param bezier foundation.shape.BezierCurve + ---@param triangle foundation.shape.Triangle + ---@return boolean + function ShapeIntersector.bezierCurveHasIntersectionWithTriangle(bezier, triangle) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + + for _, seg in ipairs(bezierSegments) do + if ShapeIntersector.triangleHasIntersectionWithSegment(triangle, seg) then + return true + end + end + + return false + end + + ---贝塞尔曲线与多边形相交 + ---@param bezier foundation.shape.BezierCurve + ---@param polygon foundation.shape.Polygon + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.bezierCurveToPolygon(bezier, polygon) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + local intersectionPoints = {} + local hasIntersection = false + + for _, seg in ipairs(bezierSegments) do + local intersect, points = ShapeIntersector.polygonToSegment(polygon, seg) + if intersect and points then + hasIntersection = true + for _, p in ipairs(points) do + intersectionPoints[#intersectionPoints + 1] = p + end + end + end + + if hasIntersection then + return true, ShapeIntersector.getUniquePoints(intersectionPoints) + else + return false, nil + end + end + + ---贝塞尔曲线与多边形相交检查 + ---@param bezier foundation.shape.BezierCurve + ---@param polygon foundation.shape.Polygon + ---@return boolean + function ShapeIntersector.bezierCurveHasIntersectionWithPolygon(bezier, polygon) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + + for _, seg in ipairs(bezierSegments) do + if ShapeIntersector.polygonHasIntersectionWithSegment(polygon, seg) then + return true + end + end + + return false + end + + ---贝塞尔曲线与扇形相交 + ---@param bezier foundation.shape.BezierCurve + ---@param sector foundation.shape.Sector + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.bezierCurveToSector(bezier, sector) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + local intersectionPoints = {} + local hasIntersection = false + + for _, seg in ipairs(bezierSegments) do + local intersect, points = ShapeIntersector.sectorToSegment(sector, seg) + if intersect and points then + hasIntersection = true + for _, p in ipairs(points) do + intersectionPoints[#intersectionPoints + 1] = p + end + end + end + + if hasIntersection then + return true, ShapeIntersector.getUniquePoints(intersectionPoints) + else + return false, nil + end + end + + ---贝塞尔曲线与扇形相交检查 + ---@param bezier foundation.shape.BezierCurve + ---@param sector foundation.shape.Sector + ---@return boolean + function ShapeIntersector.bezierCurveHasIntersectionWithSector(bezier, sector) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + + for _, seg in ipairs(bezierSegments) do + if ShapeIntersector.sectorHasIntersectionWithSegment(sector, seg) then + return true + end + end + + return false + end + + ---贝塞尔曲线与椭圆相交 + ---@param bezier foundation.shape.BezierCurve + ---@param ellipse foundation.shape.Ellipse + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.bezierCurveToEllipse(bezier, ellipse) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + local intersectionPoints = {} + local hasIntersection = false + + for _, seg in ipairs(bezierSegments) do + local intersect, points = ShapeIntersector.ellipseToSegment(ellipse, seg) + if intersect and points then + hasIntersection = true + for _, p in ipairs(points) do + intersectionPoints[#intersectionPoints + 1] = p + end + end + end + + if hasIntersection then + return true, ShapeIntersector.getUniquePoints(intersectionPoints) + else + return false, nil + end + end + + ---贝塞尔曲线与椭圆相交检查 + ---@param bezier foundation.shape.BezierCurve + ---@param ellipse foundation.shape.Ellipse + ---@return boolean + function ShapeIntersector.bezierCurveHasIntersectionWithEllipse(bezier, ellipse) + local segments = 20 + local bezierSegments = bezier:toSegments(segments) + + for _, seg in ipairs(bezierSegments) do + if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, seg) then + return true + end + end + + return false + end + + + ---贝塞尔曲线与贝塞尔曲线相交 + ---@param bezier1 foundation.shape.BezierCurve + ---@param bezier2 foundation.shape.BezierCurve + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.bezierCurveToBezierCurve(bezier1, bezier2) + local segments = 20 + local bezier1Segments = bezier1:toSegments(segments) + local bezier2Segments = bezier2:toSegments(segments) + local intersectionPoints = {} + local hasIntersection = false + + for _, seg1 in ipairs(bezier1Segments) do + for _, seg2 in ipairs(bezier2Segments) do + local intersect, points = ShapeIntersector.segmentToSegment(seg1, seg2) + if intersect and points then + hasIntersection = true + for _, p in ipairs(points) do + intersectionPoints[#intersectionPoints + 1] = p + end + end + end + end + + if hasIntersection then + return true, ShapeIntersector.getUniquePoints(intersectionPoints) + else + return false, nil + end + end + + ---贝塞尔曲线与贝塞尔曲线相交检查 + ---@param bezier1 foundation.shape.BezierCurve + ---@param bezier2 foundation.shape.BezierCurve + ---@return boolean + function ShapeIntersector.bezierCurveHasIntersectionWithBezierCurve(bezier1, bezier2) + local segments = 20 + local bezier1Segments = bezier1:toSegments(segments) + local bezier2Segments = bezier2:toSegments(segments) + + for _, seg1 in ipairs(bezier1Segments) do + for _, seg2 in ipairs(bezier2Segments) do + if ShapeIntersector.segmentHasIntersectionWithSegment(seg1, seg2) then + return true + end + end + end + + return false + end +end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua index 552ded8d..d4f54c46 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua @@ -15,6 +15,25 @@ return function(ShapeIntersector) return (point - circle.center):length() <= circle.radius + 1e-10 end + ---检查点是否在椭圆内(包括边界) + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param point foundation.math.Vector2 点 + ---@return boolean + function ShapeIntersector.ellipseContainsPoint(ellipse, point) + local baseAngle = ellipse.direction:angle() + local cos_rotation = math.cos(-baseAngle) + local sin_rotation = math.sin(-baseAngle) + + local dx = point.x - ellipse.center.x + local dy = point.y - ellipse.center.y + + local x = cos_rotation * dx - sin_rotation * dy + local y = sin_rotation * dx + cos_rotation * dy + + local value = (x * x) / (ellipse.rx * ellipse.rx) + (y * y) / (ellipse.ry * ellipse.ry) + return math.abs(value) <= 1 + 1e-10 + end + ---检查点是否在矩形内(包括边界) ---@param rectangle foundation.shape.Rectangle 矩形 ---@param point foundation.math.Vector2 点 @@ -34,6 +53,7 @@ return function(ShapeIntersector) ---@param point foundation.math.Vector2 点 ---@return boolean function ShapeIntersector.sectorContainsPoint(sector, point) +---@diagnostic disable-next-line: param-type-mismatch local inCircle = ShapeIntersector.circleContainsPoint(sector, point) if not inCircle then return false diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua new file mode 100644 index 00000000..b2822dff --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua @@ -0,0 +1,798 @@ +---@param ShapeIntersector foundation.shape.ShapeIntersector +return function(ShapeIntersector) + local math = math + local ipairs = ipairs + local require = require + + local Vector2 = require("foundation.math.Vector2") + local Segment + + ---椭圆与椭圆相交检测 + ---@param ellipse1 foundation.shape.Ellipse 椭圆1 + ---@param ellipse2 foundation.shape.Ellipse 椭圆2 + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.ellipseToEllipse(ellipse1, ellipse2) + local segments = 36 + local points = {} + + local ellipse2Points = ellipse2:discretize(segments) + + for i = 1, #ellipse2Points - 1 do + Segment = Segment or require("foundation.shape.Segment") + local segment = Segment.create(ellipse2Points[i], ellipse2Points[i + 1]) + local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse1, segment) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + Segment = Segment or require("foundation.shape.Segment") + local lastSegment = Segment.create(ellipse2Points[#ellipse2Points], ellipse2Points[1]) + local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse1, lastSegment) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + + if ellipse1:contains(ellipse2.center) then + points[#points + 1] = ellipse2.center:clone() + end + + if ellipse2:contains(ellipse1.center) then + points[#points + 1] = ellipse1.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---椭圆与椭圆相交检测(仅判断是否相交) + ---@param ellipse1 foundation.shape.Ellipse 椭圆1 + ---@param ellipse2 foundation.shape.Ellipse 椭圆2 + ---@return boolean + function ShapeIntersector.ellipseHasIntersectionWithEllipse(ellipse1, ellipse2) + if ellipse1:contains(ellipse2.center) then + return true + end + + if ellipse2:contains(ellipse1.center) then + return true + end + + local segments = 18 + local ellipse2Points = ellipse2:discretize(segments) + + for _, p in ipairs(ellipse2Points) do + if ellipse1:contains(p) then + return true + end + end + + local ellipse1Points = ellipse1:discretize(segments) + for _, p in ipairs(ellipse1Points) do + if ellipse2:contains(p) then + return true + end + end + + return false + end + + ---椭圆与圆相交检测 + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.ellipseToCircle(ellipse, circle) + Segment = Segment or require("foundation.shape.Segment") + + local segments = 36 + local points = {} + local circlePoints = circle:discretize(segments) + + for i = 1, #circlePoints - 1 do + local segment = Segment.create(circlePoints[i], circlePoints[i + 1]) + local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse, segment) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local lastSegment = Segment.create(circlePoints[#circlePoints], circlePoints[1]) + local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse, lastSegment) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + + if ellipse:contains(circle.center) then + points[#points + 1] = circle.center:clone() + end + + if circle:contains(ellipse.center) then + points[#points + 1] = ellipse.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---椭圆与圆相交检测(仅判断是否相交) + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param circle foundation.shape.Circle 圆 + ---@return boolean + function ShapeIntersector.ellipseHasIntersectionWithCircle(ellipse, circle) + if ellipse:contains(circle.center) then + return true + end + + if circle:contains(ellipse.center) then + return true + end + + local segments = 18 + local circlePoints = circle:discretize(segments) + + for _, p in ipairs(circlePoints) do + if ellipse:contains(p) then + return true + end + end + + Segment = Segment or require("foundation.shape.Segment") + + for i = 1, #circlePoints - 1 do + local segment = Segment.create(circlePoints[i], circlePoints[i + 1]) + if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, segment) then + return true + end + end + + local lastSegment = Segment.create(circlePoints[#circlePoints], circlePoints[1]) + return ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, lastSegment) + end + + ---椭圆与矩形相交检测 + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.ellipseToRectangle(ellipse, rectangle) + local points = {} + + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if ellipse:contains(vertex) then + points[#points + 1] = vertex:clone() + end + end + + if rectangle:contains(ellipse.center) then + points[#points + 1] = ellipse.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---椭圆与矩形相交检测(仅判断是否相交) + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param rectangle foundation.shape.Rectangle 矩形 + ---@return boolean + function ShapeIntersector.ellipseHasIntersectionWithRectangle(ellipse, rectangle) + local vertices = rectangle:getVertices() + for _, vertex in ipairs(vertices) do + if ellipse:contains(vertex) then + return true + end + end + + if rectangle:contains(ellipse.center) then + return true + end + + local edges = rectangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, edge) then + return true + end + end + + return false + end + + ---椭圆与三角形相交检测 + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param triangle foundation.shape.Triangle 三角形 + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.ellipseToTriangle(ellipse, triangle) + local points = {} + + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if ellipse:contains(vertex) then + points[#points + 1] = vertex:clone() + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, ellipse.center) then + points[#points + 1] = ellipse.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---椭圆与三角形相交检测(仅判断是否相交) + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param triangle foundation.shape.Triangle 三角形 + ---@return boolean + function ShapeIntersector.ellipseHasIntersectionWithTriangle(ellipse, triangle) + local vertices = triangle:getVertices() + for _, vertex in ipairs(vertices) do + if ellipse:contains(vertex) then + return true + end + end + + if ShapeIntersector.triangleContainsPoint(triangle, ellipse.center) then + return true + end + + local edges = triangle:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, edge) then + return true + end + end + + return false + end + + ---椭圆与多边形相交检测 + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param polygon foundation.shape.Polygon 多边形 + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.ellipseToPolygon(ellipse, polygon) + local points = {} + + local edges = polygon:getEdges() + for _, edge in ipairs(edges) do + local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse, edge) + if success then + for _, p in ipairs(edge_points) do + points[#points + 1] = p + end + end + end + + local vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ellipse:contains(vertex) then + points[#points + 1] = vertex:clone() + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, ellipse.center) then + points[#points + 1] = ellipse.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---椭圆与多边形相交检测(仅判断是否相交) + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param polygon foundation.shape.Polygon 多边形 + ---@return boolean + function ShapeIntersector.ellipseHasIntersectionWithPolygon(ellipse, polygon) + local vertices = polygon:getVertices() + for _, vertex in ipairs(vertices) do + if ellipse:contains(vertex) then + return true + end + end + + if ShapeIntersector.polygonContainsPoint(polygon, ellipse.center) then + return true + end + + local edges = polygon:getEdges() + for _, edge in ipairs(edges) do + if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, edge) then + return true + end + end + + return false + end + + ---椭圆与线段相交检测 + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.ellipseToSegment(ellipse, segment) + local points = {} + + local p1 = segment.point1 - ellipse.center + local p2 = segment.point2 - ellipse.center + + local baseAngle = ellipse.direction:angle() + local cos_rotation = math.cos(-baseAngle) + local sin_rotation = math.sin(-baseAngle) + + local x1 = cos_rotation * p1.x - sin_rotation * p1.y + local y1 = sin_rotation * p1.x + cos_rotation * p1.y + + local x2 = cos_rotation * p2.x - sin_rotation * p2.y + local y2 = sin_rotation * p2.x + cos_rotation * p2.y + + x1 = x1 / ellipse.rx + y1 = y1 / ellipse.ry + + x2 = x2 / ellipse.rx + y2 = y2 / ellipse.ry + + local dx = x2 - x1 + local dy = y2 - y1 + + local a = dx * dx + dy * dy + local b = 2 * (x1 * dx + y1 * dy) + local c = x1 * x1 + y1 * y1 - 1 + + local discriminant = b * b - 4 * a * c + + if discriminant < 0 then + return false, nil + end + + local sqrt_discriminant = math.sqrt(discriminant) + local t1 = (-b - sqrt_discriminant) / (2 * a) + local t2 = (-b + sqrt_discriminant) / (2 * a) + + if (t1 >= 0 and t1 <= 1) then + local x = x1 + t1 * dx + local y = y1 + t1 * dy + + x = x * ellipse.rx + y = y * ellipse.ry + + local px = cos_rotation * x + sin_rotation * y + ellipse.center.x + local py = -sin_rotation * x + cos_rotation * y + ellipse.center.y + + points[#points + 1] = Vector2.create(px, py) + end + + if (t2 >= 0 and t2 <= 1 and math.abs(t1 - t2) > 1e-10) then + local x = x1 + t2 * dx + local y = y1 + t2 * dy + + x = x * ellipse.rx + y = y * ellipse.ry + + local px = cos_rotation * x + sin_rotation * y + ellipse.center.x + local py = -sin_rotation * x + cos_rotation * y + ellipse.center.y + + points[#points + 1] = Vector2.create(px, py) + end + + if #points == 0 then + return false, nil + end + return true, points + end + + ---椭圆与线段相交检测(仅判断是否相交) + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param segment foundation.shape.Segment 线段 + ---@return boolean + function ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, segment) + if ellipse:contains(segment.point1) or ellipse:contains(segment.point2) then + return true + end + + local p1 = segment.point1 - ellipse.center + local p2 = segment.point2 - ellipse.center + + local baseAngle = ellipse.direction:angle() + local cos_rotation = math.cos(-baseAngle) + local sin_rotation = math.sin(-baseAngle) + + local x1 = cos_rotation * p1.x - sin_rotation * p1.y + local y1 = sin_rotation * p1.x + cos_rotation * p1.y + + local x2 = cos_rotation * p2.x - sin_rotation * p2.y + local y2 = sin_rotation * p2.x + cos_rotation * p2.y + + x1 = x1 / ellipse.rx + y1 = y1 / ellipse.ry + x2 = x2 / ellipse.rx + y2 = y2 / ellipse.ry + + local dx = x2 - x1 + local dy = y2 - y1 + + local a = dx * dx + dy * dy + local b = 2 * (x1 * dx + y1 * dy) + local c = x1 * x1 + y1 * y1 - 1 + + local discriminant = b * b - 4 * a * c + + if discriminant < 0 then + return false + end + + local sqrt_discriminant = math.sqrt(discriminant) + local t1 = (-b - sqrt_discriminant) / (2 * a) + local t2 = (-b + sqrt_discriminant) / (2 * a) + + return (t1 >= 0 and t1 <= 1) or (t2 >= 0 and t2 <= 1) + end + + ---椭圆与射线相交检测 + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.ellipseToRay(ellipse, ray) + local points = {} + + local rayPoint = ray.point - ellipse.center + local rayDir = ray.direction:clone() + + local baseAngle = ellipse.direction:angle() + local cos_rotation = math.cos(-baseAngle) + local sin_rotation = math.sin(-baseAngle) + + local x0 = cos_rotation * rayPoint.x - sin_rotation * rayPoint.y + local y0 = sin_rotation * rayPoint.x + cos_rotation * rayPoint.y + + local dx = cos_rotation * rayDir.x - sin_rotation * rayDir.y + local dy = sin_rotation * rayDir.x + cos_rotation * rayDir.y + + x0 = x0 / ellipse.rx + y0 = y0 / ellipse.ry + dx = dx / ellipse.rx + dy = dy / ellipse.ry + + local a = dx * dx + dy * dy + local b = 2 * (x0 * dx + y0 * dy) + local c = x0 * x0 + y0 * y0 - 1 + + local discriminant = b * b - 4 * a * c + + if discriminant < 0 then + return false, nil + end + + local sqrt_discriminant = math.sqrt(discriminant) + local t1 = (-b - sqrt_discriminant) / (2 * a) + local t2 = (-b + sqrt_discriminant) / (2 * a) + + if t1 >= 0 then + local x1 = x0 + t1 * dx + local y1 = y0 + t1 * dy + + x1 = x1 * ellipse.rx + y1 = y1 * ellipse.ry + + local px1 = cos_rotation * x1 + sin_rotation * y1 + ellipse.center.x + local py1 = -sin_rotation * x1 + cos_rotation * y1 + ellipse.center.y + + points[#points + 1] = Vector2.create(px1, py1) + end + + if t2 >= 0 and math.abs(t1 - t2) > 1e-10 then + local x2 = x0 + t2 * dx + local y2 = y0 + t2 * dy + + x2 = x2 * ellipse.rx + y2 = y2 * ellipse.ry + + local px2 = cos_rotation * x2 + sin_rotation * y2 + ellipse.center.x + local py2 = -sin_rotation * x2 + cos_rotation * y2 + ellipse.center.y + + points[#points + 1] = Vector2.create(px2, py2) + end + + if #points == 0 then + return false, nil + end + return true, points + end + + ---椭圆与射线相交检测(仅判断是否相交) + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param ray foundation.shape.Ray 射线 + ---@return boolean + function ShapeIntersector.ellipseHasIntersectionWithRay(ellipse, ray) + if ellipse:contains(ray.point) then + return true + end + + local rayPoint = ray.point - ellipse.center + local rayDir = ray.direction:clone() + + local baseAngle = ellipse.direction:angle() + local cos_rotation = math.cos(-baseAngle) + local sin_rotation = math.sin(-baseAngle) + + local x0 = cos_rotation * rayPoint.x - sin_rotation * rayPoint.y + local y0 = sin_rotation * rayPoint.x + cos_rotation * rayPoint.y + + local dx = cos_rotation * rayDir.x - sin_rotation * rayDir.y + local dy = sin_rotation * rayDir.x + cos_rotation * rayDir.y + + x0 = x0 / ellipse.rx + y0 = y0 / ellipse.ry + dx = dx / ellipse.rx + dy = dy / ellipse.ry + + local a = dx * dx + dy * dy + local b = 2 * (x0 * dx + y0 * dy) + local c = x0 * x0 + y0 * y0 - 1 + + local discriminant = b * b - 4 * a * c + + if discriminant < 0 then + return false + end + + local sqrt_discriminant = math.sqrt(discriminant) + local t1 = (-b - sqrt_discriminant) / (2 * a) + local t2 = (-b + sqrt_discriminant) / (2 * a) + + return t1 >= 0 or t2 >= 0 + end + + ---椭圆与线相交检测 + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param line foundation.shape.Line 线 + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.ellipseToLine(ellipse, line) + local points = {} + + local linePoint = line.point - ellipse.center + local lineDir = line.direction:clone() + + local baseAngle = ellipse.direction:angle() + local cos_rotation = math.cos(-baseAngle) + local sin_rotation = math.sin(-baseAngle) + + local x0 = cos_rotation * linePoint.x - sin_rotation * linePoint.y + local y0 = sin_rotation * linePoint.x + cos_rotation * linePoint.y + + local dx = cos_rotation * lineDir.x - sin_rotation * lineDir.y + local dy = sin_rotation * lineDir.x + cos_rotation * lineDir.y + + x0 = x0 / ellipse.rx + y0 = y0 / ellipse.ry + dx = dx / ellipse.rx + dy = dy / ellipse.ry + + local a = dx * dx + dy * dy + local b = 2 * (x0 * dx + y0 * dy) + local c = x0 * x0 + y0 * y0 - 1 + + local discriminant = b * b - 4 * a * c + + if discriminant < 0 then + return false, nil + end + + local sqrt_discriminant = math.sqrt(discriminant) + local t1 = (-b - sqrt_discriminant) / (2 * a) + local t2 = (-b + sqrt_discriminant) / (2 * a) + + local x1 = x0 + t1 * dx + local y1 = y0 + t1 * dy + + x1 = x1 * ellipse.rx + y1 = y1 * ellipse.ry + + local px1 = cos_rotation * x1 + sin_rotation * y1 + ellipse.center.x + local py1 = -sin_rotation * x1 + cos_rotation * y1 + ellipse.center.y + + points[#points + 1] = Vector2.create(px1, py1) + + if math.abs(discriminant) > 1e-10 then + local x2 = x0 + t2 * dx + local y2 = y0 + t2 * dy + + x2 = x2 * ellipse.rx + y2 = y2 * ellipse.ry + + local px2 = cos_rotation * x2 + sin_rotation * y2 + ellipse.center.x + local py2 = -sin_rotation * x2 + cos_rotation * y2 + ellipse.center.y + + points[#points + 1] = Vector2.create(px2, py2) + end + + return true, points + end + + ---椭圆与线相交检测(仅判断是否相交) + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param line foundation.shape.Line 线 + ---@return boolean + function ShapeIntersector.ellipseHasIntersectionWithLine(ellipse, line) + if ellipse:contains(line.point) then + return true + end + + local linePoint = line.point - ellipse.center + local lineDir = line.direction:clone() + + local baseAngle = ellipse.direction:angle() + local cos_rotation = math.cos(-baseAngle) + local sin_rotation = math.sin(-baseAngle) + + local x0 = cos_rotation * linePoint.x - sin_rotation * linePoint.y + local y0 = sin_rotation * linePoint.x + cos_rotation * linePoint.y + + local dx = cos_rotation * lineDir.x - sin_rotation * lineDir.y + local dy = sin_rotation * lineDir.x + cos_rotation * lineDir.y + + x0 = x0 / ellipse.rx + y0 = y0 / ellipse.ry + dx = dx / ellipse.rx + dy = dy / ellipse.ry + + local a = dx * dx + dy * dy + local b = 2 * (x0 * dx + y0 * dy) + local c = x0 * x0 + y0 * y0 - 1 + + local discriminant = b * b - 4 * a * c + + return discriminant >= 0 + end + + ---椭圆与扇形相交检测 + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param sector foundation.shape.Sector 扇形 + ---@return boolean, foundation.math.Vector2[]|nil + function ShapeIntersector.ellipseToSector(ellipse, sector) + Segment = Segment or require("foundation.shape.Segment") + local points = {} + + if math.abs(sector.range) >= 1 then + return ShapeIntersector.ellipseToCircle(ellipse, sector) + end + + local success, circle_points = ShapeIntersector.ellipseToCircle(ellipse, sector) + if success then + for _, p in ipairs(circle_points) do + if ShapeIntersector.sectorContainsPoint(sector, p) then + points[#points + 1] = p + end + end + end + + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + + local success1, edge_points1 = ShapeIntersector.ellipseToSegment(ellipse, startSegment) + if success1 then + for _, p in ipairs(edge_points1) do + points[#points + 1] = p + end + end + + local success2, edge_points2 = ShapeIntersector.ellipseToSegment(ellipse, endSegment) + if success2 then + for _, p in ipairs(edge_points2) do + points[#points + 1] = p + end + end + + if ShapeIntersector.sectorContainsPoint(sector, ellipse.center) then + points[#points + 1] = ellipse.center:clone() + end + + if ellipse:contains(sector.center) then + points[#points + 1] = sector.center:clone() + end + + local unique_points = ShapeIntersector.getUniquePoints(points) + + if #unique_points == 0 then + return false, nil + end + return true, unique_points + end + + ---椭圆与扇形相交检测(仅判断是否相交) + ---@param ellipse foundation.shape.Ellipse 椭圆 + ---@param sector foundation.shape.Sector 扇形 + ---@return boolean + function ShapeIntersector.ellipseHasIntersectionWithSector(ellipse, sector) + Segment = Segment or require("foundation.shape.Segment") + + if math.abs(sector.range) >= 1 then + return ShapeIntersector.ellipseHasIntersectionWithCircle(ellipse, sector) + end + + if ShapeIntersector.sectorContainsPoint(sector, ellipse.center) then + return true + end + + if ellipse:contains(sector.center) then + return true + end + + local startDir = sector.direction + local endDir = sector.direction:rotated(sector.range * 2 * math.pi) + local startPoint = sector.center + startDir * sector.radius + local endPoint = sector.center + endDir * sector.radius + + local startSegment = Segment.create(sector.center, startPoint) + local endSegment = Segment.create(sector.center, endPoint) + + if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, startSegment) or + ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, endSegment) then + return true + end + + local segments = 18 + local arcPoints = sector:discretize(segments) + + for i = 1, #arcPoints - 1 do + local segment = Segment.create(arcPoints[i], arcPoints[i + 1]) + if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, segment) then + return true + end + end + + return false + end + + return ShapeIntersector +end \ No newline at end of file diff --git a/laboratory/geometry/main.lua b/laboratory/geometry/main.lua index fa7b5eb2..991a6e23 100644 --- a/laboratory/geometry/main.lua +++ b/laboratory/geometry/main.lua @@ -37,12 +37,14 @@ local Vector2 = require("foundation.math.Vector2") local Line = require("foundation.shape.Line") local Ray = require("foundation.shape.Ray") local Segment = require("foundation.shape.Segment") +local BezierCurve = require("foundation.shape.BezierCurve") local Triangle = require("foundation.shape.Triangle") local Rectangle = require("foundation.shape.Rectangle") local Polygon = require("foundation.shape.Polygon") local Circle = require("foundation.shape.Circle") local Sector = require("foundation.shape.Sector") +local Ellipse = require("foundation.shape.Ellipse") --endregion --region Math Method @@ -248,6 +250,10 @@ function object:draw() self:renderSector(obj, player_pos) elseif obj.__type == "foundation.shape.Polygon" then self:renderPolygon(obj, player_pos) + elseif obj.__type == "foundation.shape.Ellipse" then + self:renderEllipse(obj, player_pos) + elseif obj.__type == "foundation.shape.BezierCurve" then + self:renderBezierCurve(obj, player_pos) end if self.render_project_point then self:renderProjectPoint(obj, player_pos) @@ -531,6 +537,68 @@ function object:renderSector(sector, player_pos) renderLine(p, p + sector.direction * r1, 2) end end + +---@param bezierCurve foundation.shape.BezierCurve +---@param player_pos {x:number, y:number} +function object:renderBezierCurve(bezierCurve, player_pos) + if bezierCurve:containsPoint(player_pos, 1) then + setColor(192, 0, 0, 255) + else + setColor(192, 255, 255, 255) + end + + local vertices = bezierCurve:discretize(30) + for i = 1, #vertices - 1 do + local p1 = vertices[i] + local p2 = vertices[i % #vertices + 1] + renderLine(p1, p2, 2) + end + + if self.render_vertex then + setColor(127, 255, 0, 0) + for i = 0, bezierCurve.num_points - 1 do + renderPoint(bezierCurve.control_points[i], 4) + end + end + + if self.render_direction then + setColor(127, 255, 63, 63) + for i = 1, math.min(#vertices - 1, 5) do + local p1 = vertices[i] + local p2 = vertices[i % #vertices + 1] + renderLine(p1, p2, 2) + end + end +end + +---@param ellipse foundation.shape.Ellipse +---@param player_pos {x:number, y:number} +function object:renderEllipse(ellipse, player_pos) + if ellipse:containsPoint(player_pos, 1) then + setColor(192, 0, 0, 255) + elseif ellipse:contains(player_pos) then + setColor(192, 127, 255, 0) + else + setColor(192, 255, 255, 255) + end + + local vertices = ellipse:discretize(30) + for i = 1, #vertices do + local p1 = vertices[i] + local p2 = vertices[i % #vertices + 1] + renderLine(p1, p2, 2) + end + + if self.render_vertex then + setColor(127, 255, 0, 0) + renderPoint(ellipse.center, 4) + end + + if self.render_direction then + setColor(127, 255, 63, 63) + renderLine(ellipse.center, ellipse.center + ellipse.direction * ellipse.rx, 2) + end +end --endregion --region Scene @@ -753,8 +821,66 @@ function Scene7:draw() end local Scene8 = {} -Scene8.name = "Crazy" +Scene8.name = "Ellipse" function Scene8:create() + self.timer = -1 + object:insert( + Rectangle.create(Vector2.create(0, 0), 200, 200) + :move(Vector2.create(window.width / 2, window.height / 2)) + ) + object:insert( + Ellipse.create(Vector2.create(0, 0), 100, 50) + :move(Vector2.create(window.width / 3 * 2, window.height / 2)) + ) +end +function Scene8:destroy() + object:clear() +end +function Scene8:update() + self.timer = self.timer + 1 + for i, obj in object:enum() do + obj:degreeRotate(-1 + 2 * (i % 2)) + end + object:updateCollisionCheck() +end +function Scene8:draw() + object:draw() +end + +local Scene9 = {} +Scene9.name = "BezierCurve" +function Scene9:create() + self.timer = -1 + object:insert( + BezierCurve.create({ + Vector2.create(0, 0), + Vector2.create(100, 200), + Vector2.create(200, 100), + Vector2.create(300, 200), + }) :move(Vector2.create(window.width / 4, window.height / 3)) + ) + object:insert( + Rectangle.create(Vector2.create(0, 0), 200, 200) + :move(Vector2.create(window.width / 2, window.height / 2)) + ) +end +function Scene9:destroy() + object:clear() +end +function Scene9:update() + self.timer = self.timer + 1 + for i, obj in object:enum() do + obj:degreeRotate(-1 + 2 * (i % 2)) + end + object:updateCollisionCheck() +end +function Scene9:draw() + object:draw() +end + +local Scene10 = {} +Scene10.name = "Crazy" +function Scene10:create() self.timer = -1 local rand = lstg.Rand() rand:Seed(os.time()) @@ -805,17 +931,17 @@ function Scene8:create() :move(Vector2.create(window.width * rand:Float(0.25, 0.75), window.height * rand:Float(0.25, 0.75))) ) end -function Scene8:destroy() +function Scene10:destroy() object:clear() end -function Scene8:update() +function Scene10:update() self.timer = self.timer + 1 for i, obj in object:enum() do obj:degreeRotate(-1 + 2 * (i % 2)) end object:updateCollisionCheck() end -function Scene8:draw() +function Scene10:draw() object:draw() end --endregion @@ -838,6 +964,8 @@ local scenes = { Scene6, Scene7, Scene8, + Scene9, + Scene10, } local current_scene_index = 1 local current_scene = makeInstance(scenes[current_scene_index]) From ef13702cbd21858df63d9412e58b7a2d6dbb5625 Mon Sep 17 00:00:00 2001 From: OLC Date: Fri, 2 May 2025 03:00:13 +0800 Subject: [PATCH 60/71] feat: render BezierCurve control points in the main drawing function --- laboratory/geometry/main.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/laboratory/geometry/main.lua b/laboratory/geometry/main.lua index 991a6e23..65324d82 100644 --- a/laboratory/geometry/main.lua +++ b/laboratory/geometry/main.lua @@ -568,6 +568,12 @@ function object:renderBezierCurve(bezierCurve, player_pos) local p2 = vertices[i % #vertices + 1] renderLine(p1, p2, 2) end + setColor(127, 13, 161, 158) + for i = 0, bezierCurve.num_points - 2 do + local p1 = bezierCurve.control_points[i] + local p2 = bezierCurve.control_points[i + 1] + renderLine(p1, p2, 2) + end end end From 4548651aa983edc9ab93151733ac17f2ced0d8fe Mon Sep 17 00:00:00 2001 From: OLC Date: Fri, 2 May 2025 04:17:11 +0800 Subject: [PATCH 61/71] refactor: simplify point transformation logic in Ellipse class --- .../foundation/shape/Ellipse.lua | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua index c7fea2be..10b5a2f3 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua @@ -365,7 +365,6 @@ function Ellipse:closestPoint(point, boundary) return point:clone() end - -- Transform point to ellipse's local coordinate system local baseAngle = self.direction:angle() local cos_rotation = math.cos(-baseAngle) local sin_rotation = math.sin(-baseAngle) @@ -376,23 +375,19 @@ function Ellipse:closestPoint(point, boundary) local x = cos_rotation * dx - sin_rotation * dy local y = sin_rotation * dx + cos_rotation * dy - -- Scale to unit circle space local px = x / self.rx local py = y / self.ry local length = math.sqrt(px * px + py * py) if length < 1e-10 then - -- Point is at center, return point on major axis px = self.rx py = 0 else - -- Calculate both possible boundary points local px1 = px / length * self.rx local py1 = py / length * self.ry local px2 = -px / length * self.rx local py2 = -py / length * self.ry - -- Transform both points back to world coordinates local cos_world = math.cos(baseAngle) local sin_world = math.sin(baseAngle) @@ -404,13 +399,11 @@ function Ellipse:closestPoint(point, boundary) local result_y2 = sin_world * px2 + cos_world * py2 + self.center.y local point2 = Vector2.create(result_x2, result_y2) - -- Return the point that's closer to the input point local dist1 = (point - point1):length() local dist2 = (point - point2):length() return dist1 <= dist2 and point1 or point2 end - -- Transform back to world coordinates local cos_world = math.cos(baseAngle) local sin_world = math.sin(baseAngle) local result_x = cos_world * px - sin_world * py + self.center.x @@ -445,19 +438,9 @@ end ---@overload fun(self:foundation.shape.Ellipse, point:foundation.math.Vector2): boolean function Ellipse:containsPoint(point, tolerance) tolerance = tolerance or 1e-10 - - local baseAngle = self.direction:angle() - local cos_rotation = math.cos(-baseAngle) - local sin_rotation = math.sin(-baseAngle) - - local dx = point.x - self.center.x - local dy = point.y - self.center.y - - local x = cos_rotation * dx - sin_rotation * dy - local y = sin_rotation * dx + cos_rotation * dy - - local value = (x * x) / (self.rx * self.rx) + (y * y) / (self.ry * self.ry) - return math.abs(1 - math.abs(value)) * 10 <= tolerance -- i don't think is correct, but it works + local projPoint = self:closestPoint(point, true) + local distance = (point - projPoint):length() + return distance <= tolerance end ---创建椭圆的一个副本 From 18cd0ef7c10fd52b62168578852731541e8e182f Mon Sep 17 00:00:00 2001 From: OLC Date: Fri, 2 May 2025 04:21:04 +0800 Subject: [PATCH 62/71] feat: add segment parameter to containsPoint method for BezierCurve --- .../thlib-scripts-v2/foundation/shape/BezierCurve.lua | 7 +++++-- laboratory/geometry/main.lua | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua b/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua index 61ca7e2b..16cd0368 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua @@ -717,10 +717,13 @@ end ---检查点是否在贝塞尔曲线上 ---@param point foundation.math.Vector2 ---@param tolerance number|nil 容差,默认为1e-10 +---@param segments number|nil 分段数,用于近似计算 ---@return boolean -function BezierCurve:containsPoint(point, tolerance) +---@overload fun(self: foundation.shape.BezierCurve, point: foundation.math.Vector2, tolerance: number): boolean +---@overload fun(self: foundation.shape.BezierCurve, point: foundation.math.Vector2): boolean +function BezierCurve:containsPoint(point, tolerance, segments) tolerance = tolerance or 1e-10 - local projPoint = self:closestPointWithSegments(point) + local projPoint = self:closestPointWithSegments(point, segments) return (point - projPoint):length() <= tolerance end diff --git a/laboratory/geometry/main.lua b/laboratory/geometry/main.lua index 65324d82..02321336 100644 --- a/laboratory/geometry/main.lua +++ b/laboratory/geometry/main.lua @@ -541,7 +541,7 @@ end ---@param bezierCurve foundation.shape.BezierCurve ---@param player_pos {x:number, y:number} function object:renderBezierCurve(bezierCurve, player_pos) - if bezierCurve:containsPoint(player_pos, 1) then + if bezierCurve:containsPoint(player_pos, 1, 30) then setColor(192, 0, 0, 255) else setColor(192, 255, 255, 255) From ffbc84d2cc5f963b2811b71fadd40ea4c910521a Mon Sep 17 00:00:00 2001 From: OLC Date: Fri, 2 May 2025 15:46:26 +0800 Subject: [PATCH 63/71] feat: add methods for equal arc length points and segments in BezierCurve and Ellipse classes --- .../foundation/shape/BezierCurve.lua | 65 +++++++++++++++++ .../foundation/shape/Ellipse.lua | 70 ++++++++++++++++++- 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua b/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua index 16cd0368..ecfff96e 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua @@ -737,6 +737,71 @@ function BezierCurve:clone() return BezierCurve.create(new_points) end +---获取按弧长等分的点集 +---@param num_points number 期望的点数(包含起点和终点) +---@param tolerance number|nil 弧长误差容差,默认为1e-6 +---@return foundation.math.Vector2[] +function BezierCurve:getEqualArcLengthPoints(num_points, tolerance) + num_points = num_points or 10 + tolerance = tolerance or 1e-6 + if num_points < 2 then + error("Number of points must be at least 2") + end + + local total_length = self:length(100) + local target_segment_length = total_length / (num_points - 1) + local points = { self:getPoint(0) } + local current_length = 0 + local t = 0 + local step = 0.01 + local last_point = points[1] + local last_t = 0 + + while #points < num_points and t <= 1 do + t = math.min(t + step, 1) + local point = self:getPoint(t) + local segment_length = (point - last_point):length() + current_length = current_length + segment_length + + if current_length >= target_segment_length - tolerance or t >= 1 then + table.insert(points, point) + last_point = point + last_t = t + current_length = 0 + + local remaining_points = num_points - #points + if remaining_points > 0 then + local remaining_t = 1 - t + step = remaining_t / (remaining_points * 2) + end + else + last_point = point + last_t = t + end + end + + if math.abs(t - 1) > tolerance and #points == num_points then + points[#points] = self:getPoint(1) + end + + return points +end + +---获取按弧长等分的线段集 +---@param num_segments number 期望的线段数(包含起点和终点) +---@param tolerance number|nil 弧长误差容差,默认为1e-6 +---@return foundation.shape.Segment[] +function BezierCurve:getEqualArcLengthSegments(num_segments, tolerance) + local points = self:getEqualArcLengthPoints(num_segments + 1, tolerance) + local segments = {} + + for i = 1, #points - 1 do + segments[i] = Segment.create(points[i], points[i + 1]) + end + + return segments +end + ffi.metatype("foundation_shape_BezierCurve", BezierCurve) return BezierCurve \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua index 10b5a2f3..0601330f 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua @@ -4,6 +4,8 @@ local type = type local tostring = tostring local string = string local math = math +local table = table +local error = error local rawset = rawset local setmetatable = setmetatable @@ -170,7 +172,8 @@ function Ellipse:getEdges(segments) local segs = {} for i = 1, segments do - segs[i] = Segment.create(points[i], points[i + 1]) + local nextIndex = (i % segments) + 1 + segs[i] = Segment.create(points[i], points[nextIndex]) end return segs @@ -449,6 +452,71 @@ function Ellipse:clone() return Ellipse.create(self.center:clone(), self.rx, self.ry, self.direction:clone()) end +---获取按弧长等分的点集和线段集 +---@param num_points number 期望的点数(包含起点) +---@param tolerance number|nil 弧长误差容差,默认为1e-6 +---@return foundation.math.Vector2[] +function Ellipse:getEqualArcLengthPoints(num_points, tolerance) + num_points = num_points or 30 + tolerance = tolerance or 1e-6 + if num_points < 2 then + error("Number of points must be at least 2") + end + + local total_length = self:getPerimeter() + local target_segment_length = total_length / num_points + local points = { self:getPointAtAngle(0) } + local current_length = 0 + local angle = 0 + local step = 2 * math.pi / 100 + local last_point = points[1] + local last_angle = 0 + + while #points < num_points and angle < 2 * math.pi do + angle = math.min(angle + step, 2 * math.pi) + local point = self:getPointAtAngle(angle) + local segment_length = (point - last_point):length() + current_length = current_length + segment_length + + if current_length >= target_segment_length - tolerance or angle >= 2 * math.pi then + table.insert(points, point) + last_point = point + last_angle = angle + current_length = 0 + + local remaining_points = num_points - #points + if remaining_points > 0 then + local remaining_angle = 2 * math.pi - angle + step = remaining_angle / (remaining_points * 2) + end + else + last_point = point + last_angle = angle + end + end + + if math.abs(angle - 2 * math.pi) > tolerance and #points == num_points then + points[#points] = self:getPointAtAngle(2 * math.pi) + end + + return points +end + +---获取按弧长等分的线段集 +---@param num_segments number 期望的线段数(包含起点和终点) +---@param tolerance number|nil 弧长误差容差,默认为1e-6 +---@return foundation.shape.Segment[] +function Ellipse:getEqualArcLengthSegments(num_segments, tolerance) + local points = self:getEqualArcLengthPoints(num_segments + 1, tolerance) + local segments = {} + + for i = 1, #points - 1 do + segments[i] = Segment.create(points[i], points[i + 1]) + end + + return segments +end + ffi.metatype("foundation_shape_Ellipse", Ellipse) return Ellipse \ No newline at end of file From db468f0cc45dbffa4e69a44e89a06e8408f80499 Mon Sep 17 00:00:00 2001 From: OLC Date: Fri, 2 May 2025 15:52:08 +0800 Subject: [PATCH 64/71] fix: address parameter type mismatch warnings in ellipse intersection methods --- .../foundation/shape/ShapeIntersector/EllipseIntersector.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua index b2822dff..9b30baef 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua @@ -699,9 +699,11 @@ return function(ShapeIntersector) local points = {} if math.abs(sector.range) >= 1 then + ---@diagnostic disable-next-line: param-type-mismatch return ShapeIntersector.ellipseToCircle(ellipse, sector) end + ---@diagnostic disable-next-line: param-type-mismatch local success, circle_points = ShapeIntersector.ellipseToCircle(ellipse, sector) if success then for _, p in ipairs(circle_points) do @@ -757,6 +759,7 @@ return function(ShapeIntersector) Segment = Segment or require("foundation.shape.Segment") if math.abs(sector.range) >= 1 then + ---@diagnostic disable-next-line: param-type-mismatch return ShapeIntersector.ellipseHasIntersectionWithCircle(ellipse, sector) end From 049a6c5f6080a3d43b0372347aeff8b9b03fd0ee Mon Sep 17 00:00:00 2001 From: OLC Date: Fri, 2 May 2025 17:22:44 +0800 Subject: [PATCH 65/71] feat: add matrix and transformation classes for 2D and 3D operations --- .../foundation/math/matrix/Matrix.lua | 664 ++++++++++++++++++ .../math/matrix/MatrixTransformation.lua | 325 +++++++++ .../foundation/math/matrix/SpecialMatrix.lua | 428 +++++++++++ 3 files changed, 1417 insertions(+) create mode 100644 game/packages/thlib-scripts-v2/foundation/math/matrix/Matrix.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/math/matrix/MatrixTransformation.lua create mode 100644 game/packages/thlib-scripts-v2/foundation/math/matrix/SpecialMatrix.lua diff --git a/game/packages/thlib-scripts-v2/foundation/math/matrix/Matrix.lua b/game/packages/thlib-scripts-v2/foundation/math/matrix/Matrix.lua new file mode 100644 index 00000000..8b8795bb --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/math/matrix/Matrix.lua @@ -0,0 +1,664 @@ +local ffi = require("ffi") + +local type = type +local error = error +local rawset = rawset +local setmetatable = setmetatable +local math = math +local table = table +local string = string + +ffi.cdef [[ +typedef struct { + double* data; + int rows; + int cols; +} foundation_math_Matrix; +]] + +---@class foundation.math.Matrix +---@field data userdata 矩阵数据指针 +---@field rows number 矩阵行数 +---@field cols number 矩阵列数 +local Matrix = {} +Matrix.__type = "foundation.math.Matrix" + +---创建一个新的矩阵 +---@param rows number 行数 +---@param cols number 列数 +---@param initialValue number|nil 初始值,默认为0 +---@return foundation.math.Matrix 新创建的矩阵 +function Matrix.create(rows, cols, initialValue) + if rows <= 0 or cols <= 0 then + error("Matrix dimensions must be positive") + end + + initialValue = initialValue or 0 + + local matrix_data = ffi.new("foundation_math_Matrix") + matrix_data.rows = rows + matrix_data.cols = cols + matrix_data.data = ffi.new("double[?]", rows * cols) + + for i = 0, rows * cols - 1 do + matrix_data.data[i] = initialValue + end + + local matrix = { + __data = matrix_data, + __data_array_ref = matrix_data.data + } + + return setmetatable(matrix, Matrix) +end + +---@param self foundation.math.Matrix +---@param key any +---@return any +function Matrix.__index(self, key) + if key == "data" then + return self.__data.data + elseif key == "rows" then + return self.__data.rows + elseif key == "cols" then + return self.__data.cols + end + return Matrix[key] +end + +---@param self foundation.math.Matrix +---@param key any +---@param value any +function Matrix.__newindex(self, key, value) + if key == "data" then + error("Cannot modify data directly") + elseif key == "rows" or key == "cols" then + error("Cannot modify dimensions directly") + else + rawset(self, key, value) + end +end + +---获取指定位置的线性索引 +---@param i number 行索引 (1-based) +---@param j number 列索引 (1-based) +---@return number 线性索引 (0-based) +local function getLinearIndex(matrix, i, j) + return (i - 1) * matrix.cols + (j - 1) +end + +---创建一个方阵(行列数相等的矩阵) +---@param size number 矩阵大小 +---@param initialValue number|nil 初始值,默认为0 +---@return foundation.math.Matrix 新创建的方阵 +function Matrix.createSquare(size, initialValue) + return Matrix.create(size, size, initialValue) +end + +---创建一个单位矩阵 +---@param size number 矩阵大小 +---@return foundation.math.Matrix 新创建的单位矩阵 +function Matrix.identity(size) + local matrix = Matrix.create(size, size, 0) + for i = 1, size do + matrix:set(i, i, 1) + end + return matrix +end + +---从二维数组创建矩阵 +---@param arr table 二维数组 +---@return foundation.math.Matrix 新创建的矩阵 +function Matrix.fromArray(arr) + if not arr or #arr == 0 then + error("Input array cannot be empty") + end + + local rows = #arr + local cols = #arr[1] + + for i = 2, rows do + if #arr[i] ~= cols then + error("All rows must have the same number of columns") + end + end + + local matrix = Matrix.create(rows, cols) + for i = 1, rows do + for j = 1, cols do + matrix:set(i, j, arr[i][j]) + end + end + + return matrix +end + +---矩阵加法运算符重载 +---@param a foundation.math.Matrix|number 第一个操作数 +---@param b foundation.math.Matrix|number 第二个操作数 +---@return foundation.math.Matrix 相加后的结果 +function Matrix.__add(a, b) + if type(a) == "number" then + local result = Matrix.create(b.rows, b.cols) + for i = 1, b.rows do + for j = 1, b.cols do + result:set(i, j, a + b:get(i, j)) + end + end + return result + elseif type(b) == "number" then + local result = Matrix.create(a.rows, a.cols) + for i = 1, a.rows do + for j = 1, a.cols do + result:set(i, j, a:get(i, j) + b) + end + end + return result + else + if a.rows ~= b.rows or a.cols ~= b.cols then + error("Matrix dimensions must match for addition") + end + + local result = Matrix.create(a.rows, a.cols) + for i = 1, a.rows do + for j = 1, a.cols do + result:set(i, j, a:get(i, j) + b:get(i, j)) + end + end + return result + end +end + +---矩阵减法运算符重载 +---@param a foundation.math.Matrix|number 第一个操作数 +---@param b foundation.math.Matrix|number 第二个操作数 +---@return foundation.math.Matrix 相减后的结果 +function Matrix.__sub(a, b) + if type(a) == "number" then + local result = Matrix.create(b.rows, b.cols) + for i = 1, b.rows do + for j = 1, b.cols do + result:set(i, j, a - b:get(i, j)) + end + end + return result + elseif type(b) == "number" then + local result = Matrix.create(a.rows, a.cols) + for i = 1, a.rows do + for j = 1, a.cols do + result:set(i, j, a:get(i, j) - b) + end + end + return result + else + if a.rows ~= b.rows or a.cols ~= b.cols then + error("Matrix dimensions must match for subtraction") + end + + local result = Matrix.create(a.rows, a.cols) + for i = 1, a.rows do + for j = 1, a.cols do + result:set(i, j, a:get(i, j) - b:get(i, j)) + end + end + return result + end +end + +---矩阵乘法运算符重载 +---@param a foundation.math.Matrix|number 第一个操作数 +---@param b foundation.math.Matrix|number 第二个操作数 +---@return foundation.math.Matrix 相乘后的结果 +function Matrix.__mul(a, b) + if type(a) == "number" then + local result = Matrix.create(b.rows, b.cols) + for i = 1, b.rows do + for j = 1, b.cols do + result:set(i, j, a * b:get(i, j)) + end + end + return result + elseif type(b) == "number" then + local result = Matrix.create(a.rows, a.cols) + for i = 1, a.rows do + for j = 1, a.cols do + result:set(i, j, a:get(i, j) * b) + end + end + return result + else + if a.cols ~= b.rows then + error("Inner matrix dimensions must match for multiplication") + end + + local result = Matrix.create(a.rows, b.cols) + for i = 1, a.rows do + for j = 1, b.cols do + local sum = 0 + for k = 1, a.cols do + sum = sum + a:get(i, k) * b:get(k, j) + end + result:set(i, j, sum) + end + end + return result + end +end + +---矩阵取负运算符重载 +---@param m foundation.math.Matrix 操作数 +---@return foundation.math.Matrix 取反后的矩阵 +function Matrix.__unm(m) + local result = Matrix.create(m.rows, m.cols) + for i = 1, m.rows do + for j = 1, m.cols do + result:set(i, j, -m:get(i, j)) + end + end + return result +end + +---矩阵相等性比较运算符重载 +---@param a foundation.math.Matrix 第一个操作数 +---@param b foundation.math.Matrix 第二个操作数 +---@return boolean 两个矩阵是否相等 +function Matrix.__eq(a, b) + if a.rows ~= b.rows or a.cols ~= b.cols then + return false + end + + for i = 1, a.rows do + for j = 1, a.cols do + if math.abs(a:get(i, j) - b:get(i, j)) > 1e-10 then + return false + end + end + end + + return true +end + +---矩阵字符串表示 +---@param m foundation.math.Matrix 操作数 +---@return string 矩阵的字符串表示 +function Matrix.__tostring(m) + local lines = {} + for i = 1, m.rows do + local row = {} + for j = 1, m.cols do + row[j] = string.format("%f", m:get(i, j)) + end + lines[i] = "[" .. table.concat(row, ", ") .. "]" + end + return "[" .. table.concat(lines, "") .. "]" +end + +---获取矩阵元素值 +---@param i number 行索引 +---@param j number 列索引 +---@return number 指定位置的值 +function Matrix:get(i, j) + if i < 1 or i > self.rows or j < 1 or j > self.cols then + error("Matrix index out of bounds") + end + local idx = getLinearIndex(self, i, j) + return self.data[idx] +end + +---设置矩阵元素值 +---@param i number 行索引 +---@param j number 列索引 +---@param value number 要设置的值 +function Matrix:set(i, j, value) + if i < 1 or i > self.rows or j < 1 or j > self.cols then + error("Matrix index out of bounds") + end + local idx = getLinearIndex(self, i, j) + self.data[idx] = value +end + +---获取矩阵的副本 +---@return foundation.math.Matrix 矩阵的副本 +function Matrix:clone() + local result = Matrix.create(self.rows, self.cols) + for i = 1, self.rows do + for j = 1, self.cols do + result:set(i, j, self:get(i, j)) + end + end + return result +end + +---转置矩阵 +---@return foundation.math.Matrix 转置后的矩阵 +function Matrix:transpose() + local result = Matrix.create(self.cols, self.rows) + for i = 1, self.rows do + for j = 1, self.cols do + result:set(j, i, self:get(i, j)) + end + end + return result +end + +---计算矩阵的行列式(仅适用于方阵) +---@return number 行列式的值 +function Matrix:determinant() + if self.rows ~= self.cols then + error("Determinant can only be calculated for square matrices") + end + + local n = self.rows + if n == 1 then + return self:get(1, 1) + elseif n == 2 then + return self:get(1, 1) * self:get(2, 2) - self:get(1, 2) * self:get(2, 1) + else + local det = 0 + for j = 1, n do + det = det + self:cofactor(1, j) * self:get(1, j) + end + return det + end +end + +---计算矩阵元素的代数余子式 +---@param row number 行索引 +---@param col number 列索引 +---@return number 代数余子式的值 +function Matrix:cofactor(row, col) + local sign = ((row + col) % 2 == 0) and 1 or -1 + return sign * self:minor(row, col) +end + +---计算矩阵元素的余子式 +---@param row number 行索引 +---@param col number 列索引 +---@return number 余子式的值 +function Matrix:minor(row, col) + local subMatrix = self:subMatrix(row, col) + return subMatrix:determinant() +end + +---获取去掉指定行列的子矩阵 +---@param row number 要去掉的行索引 +---@param col number 要去掉的列索引 +---@return foundation.math.Matrix 子矩阵 +function Matrix:subMatrix(row, col) + local result = Matrix.create(self.rows - 1, self.cols - 1) + local r = 1 + for i = 1, self.rows do + if i ~= row then + local c = 1 + for j = 1, self.cols do + if j ~= col then + result:set(r, c, self:get(i, j)) + c = c + 1 + end + end + r = r + 1 + end + end + return result +end + +---计算矩阵的伴随矩阵 +---@return foundation.math.Matrix 伴随矩阵 +function Matrix:adjugate() + if self.rows ~= self.cols then + error("Adjugate can only be calculated for square matrices") + end + + local n = self.rows + local result = Matrix.create(n, n) + + for i = 1, n do + for j = 1, n do + result:set(j, i, self:cofactor(i, j)) + end + end + + return result +end + +---计算矩阵的逆(仅适用于方阵) +---@return foundation.math.Matrix|nil 逆矩阵,若不可逆则返回nil +function Matrix:inverse() + if self.rows ~= self.cols then + error("Inverse can only be calculated for square matrices") + end + + local det = self:determinant() + if math.abs(det) < 1e-10 then + return nil + end + + local adjugate = self:adjugate() + return adjugate * (1 / det) +end + +---获取矩阵的迹(仅适用于方阵) +---@return number 矩阵的迹 +function Matrix:trace() + if self.rows ~= self.cols then + error("Trace can only be calculated for square matrices") + end + + local sum = 0 + for i = 1, self.rows do + sum = sum + self:get(i, i) + end + + return sum +end + +---将矩阵扩充为增广矩阵 +---@param other foundation.math.Matrix 要扩充的矩阵 +---@return foundation.math.Matrix 扩充后的矩阵 +function Matrix:augment(other) + if self.rows ~= other.rows then + error("Matrices must have the same number of rows for augmentation") + end + + local result = Matrix.create(self.rows, self.cols + other.cols) + + for i = 1, self.rows do + for j = 1, self.cols do + result:set(i, j, self:get(i, j)) + end + + for j = 1, other.cols do + result:set(i, self.cols + j, other:get(i, j)) + end + end + + return result +end + +---获取矩阵的指定子矩阵 +---@param startRow number 起始行索引 +---@param endRow number 结束行索引 +---@param startCol number 起始列索引 +---@param endCol number 结束列索引 +---@return foundation.math.Matrix 子矩阵 +function Matrix:getSubMatrix(startRow, endRow, startCol, endCol) + if startRow < 1 or endRow > self.rows or startCol < 1 or endCol > self.cols then + error("Submatrix indices out of bounds") + end + + if startRow > endRow or startCol > endCol then + error("Invalid submatrix indices") + end + + local rows = endRow - startRow + 1 + local cols = endCol - startCol + 1 + local result = Matrix.create(rows, cols) + + for i = 1, rows do + for j = 1, cols do + result:set(i, j, self:get(startRow + i - 1, startCol + j - 1)) + end + end + + return result +end + +---高斯消元法求解线性方程组 Ax = b +---@param b foundation.math.Matrix 常数向量 +---@return foundation.math.Matrix|nil 解向量,若无解则返回nil +function Matrix:solve(b) + if self.rows ~= self.cols then + error("Coefficient matrix must be square") + end + + if self.rows ~= b.rows or b.cols ~= 1 then + error("Dimensions of right-hand side vector do not match") + end + + local augmented = self:augment(b) + local n = self.rows + + for i = 1, n do + local maxVal = math.abs(augmented:get(i, i)) + local maxRow = i + + for k = i + 1, n do + if math.abs(augmented:get(k, i)) > maxVal then + maxVal = math.abs(augmented:get(k, i)) + maxRow = k + end + end + + if maxVal < 1e-10 then + return nil + end + + if maxRow ~= i then + for j = i, n + 1 do + local temp = augmented:get(i, j) + augmented:set(i, j, augmented:get(maxRow, j)) + augmented:set(maxRow, j, temp) + end + end + + for k = i + 1, n do + local factor = augmented:get(k, i) / augmented:get(i, i) + augmented:set(k, i, 0) + + for j = i + 1, n + 1 do + augmented:set(k, j, augmented:get(k, j) - factor * augmented:get(i, j)) + end + end + end + + local solution = Matrix.create(n, 1) + for i = n, 1, -1 do + local sum = 0 + for j = i + 1, n do + sum = sum + augmented:get(i, j) * solution:get(j, 1) + end + solution:set(i, 1, (augmented:get(i, n + 1) - sum) / augmented:get(i, i)) + end + + return solution +end + +---计算两个矩阵的哈达玛积(逐元素乘积) +---@param other foundation.math.Matrix 另一个矩阵 +---@return foundation.math.Matrix 哈达玛积结果 +function Matrix:hadamard(other) + if self.rows ~= other.rows or self.cols ~= other.cols then + error("Matrix dimensions must match for Hadamard product") + end + + local result = Matrix.create(self.rows, self.cols) + for i = 1, self.rows do + for j = 1, self.cols do + result:set(i, j, self:get(i, j) * other:get(i, j)) + end + end + + return result +end + +---计算矩阵的Frobenius范数 +---@return number Frobenius范数 +function Matrix:normFrobenius() + local sum = 0 + for i = 1, self.rows do + for j = 1, self.cols do + local val = self:get(i, j) + sum = sum + val * val + end + end + return math.sqrt(sum) +end + +---检查矩阵是否为对称矩阵 +---@return boolean 是否为对称矩阵 +function Matrix:isSymmetric() + if self.rows ~= self.cols then + return false + end + + for i = 1, self.rows do + for j = i + 1, self.cols do + if math.abs(self:get(i, j) - self:get(j, i)) > 1e-10 then + return false + end + end + end + + return true +end + +---将矩阵转换为数组 +---@return table 包含矩阵元素的二维数组 +function Matrix:toArray() + local result = {} + for i = 1, self.rows do + result[i] = {} + for j = 1, self.cols do + result[i][j] = self:get(i, j) + end + end + return result +end + +---将矩阵转换为扁平数组(按行优先顺序) +---@return table 包含矩阵元素的一维数组 +function Matrix:toFlatArray() + local result = {} + local index = 1 + for i = 1, self.rows do + for j = 1, self.cols do + result[index] = self:get(i, j) + index = index + 1 + end + end + return result +end + +---从扁平数组创建矩阵(按行优先顺序) +---@param arr table 一维数组 +---@param rows number 行数 +---@param cols number 列数 +---@return foundation.math.Matrix 创建的矩阵 +function Matrix.fromFlatArray(arr, rows, cols) + if #arr ~= rows * cols then + error("Array length does not match matrix dimensions") + end + + local matrix = Matrix.create(rows, cols) + local index = 1 + for i = 1, rows do + for j = 1, cols do + matrix:set(i, j, arr[index]) + index = index + 1 + end + end + + return matrix +end + +return Matrix \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/matrix/MatrixTransformation.lua b/game/packages/thlib-scripts-v2/foundation/math/matrix/MatrixTransformation.lua new file mode 100644 index 00000000..f7d0a2e8 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/math/matrix/MatrixTransformation.lua @@ -0,0 +1,325 @@ +local Matrix = require("foundation.math.matrix.Matrix") +local Vector2 = require("foundation.math.Vector2") +local Vector3 = require("foundation.math.Vector3") + +local math = math +local error = error + +---@class foundation.math.matrix.MatrixTransformation +local MatrixTransformation = {} + +---创建2D平移矩阵 +---@param x number X方向平移量 +---@param y number Y方向平移量 +---@return foundation.math.Matrix 平移矩阵 +function MatrixTransformation.translation2D(x, y) + local m = Matrix.identity(3) + m:set(1, 3, x) + m:set(2, 3, y) + return m +end + +---创建2D缩放矩阵 +---@param sx number X方向缩放因子 +---@param sy number|nil Y方向缩放因子,如果为nil则使用sx +---@return foundation.math.Matrix 缩放矩阵 +function MatrixTransformation.scaling2D(sx, sy) + sy = sy or sx + local m = Matrix.identity(3) + m:set(1, 1, sx) + m:set(2, 2, sy) + return m +end + +---创建2D旋转矩阵(弧度) +---@param rad number 旋转角度(弧度) +---@return foundation.math.Matrix 旋转矩阵 +function MatrixTransformation.rotation2D(rad) + local c = math.cos(rad) + local s = math.sin(rad) + local m = Matrix.identity(3) + m:set(1, 1, c) + m:set(1, 2, -s) + m:set(2, 1, s) + m:set(2, 2, c) + return m +end + +---创建2D旋转矩阵(角度) +---@param angle number 旋转角度(度) +---@return foundation.math.Matrix 旋转矩阵 +function MatrixTransformation.degreeRotation2D(angle) + return MatrixTransformation.rotation2D(math.rad(angle)) +end + +---创建2D切变矩阵 +---@param shx number X方向切变系数 +---@param shy number Y方向切变系数 +---@return foundation.math.Matrix 切变矩阵 +function MatrixTransformation.shear2D(shx, shy) + local m = Matrix.identity(3) + m:set(1, 2, shy) + m:set(2, 1, shx) + return m +end + +---创建3D平移矩阵 +---@param x number X方向平移量 +---@param y number Y方向平移量 +---@param z number Z方向平移量 +---@return foundation.math.Matrix 平移矩阵 +function MatrixTransformation.translation3D(x, y, z) + local m = Matrix.identity(4) + m:set(1, 4, x) + m:set(2, 4, y) + m:set(3, 4, z) + return m +end + +---创建3D缩放矩阵 +---@param sx number X方向缩放因子 +---@param sy number|nil Y方向缩放因子,如果为nil则使用sx +---@param sz number|nil Z方向缩放因子,如果为nil则使用sx +---@return foundation.math.Matrix 缩放矩阵 +function MatrixTransformation.scaling3D(sx, sy, sz) + sy = sy or sx + sz = sz or sx + local m = Matrix.identity(4) + m:set(1, 1, sx) + m:set(2, 2, sy) + m:set(3, 3, sz) + return m +end + +---创建绕X轴旋转的3D旋转矩阵(弧度) +---@param rad number 旋转角度(弧度) +---@return foundation.math.Matrix 旋转矩阵 +function MatrixTransformation.rotationX(rad) + local c = math.cos(rad) + local s = math.sin(rad) + local m = Matrix.identity(4) + m:set(2, 2, c) + m:set(2, 3, -s) + m:set(3, 2, s) + m:set(3, 3, c) + return m +end + +---创建绕Y轴旋转的3D旋转矩阵(弧度) +---@param rad number 旋转角度(弧度) +---@return foundation.math.Matrix 旋转矩阵 +function MatrixTransformation.rotationY(rad) + local c = math.cos(rad) + local s = math.sin(rad) + local m = Matrix.identity(4) + m:set(1, 1, c) + m:set(1, 3, s) + m:set(3, 1, -s) + m:set(3, 3, c) + return m +end + +---创建绕Z轴旋转的3D旋转矩阵(弧度) +---@param rad number 旋转角度(弧度) +---@return foundation.math.Matrix 旋转矩阵 +function MatrixTransformation.rotationZ(rad) + local c = math.cos(rad) + local s = math.sin(rad) + local m = Matrix.identity(4) + m:set(1, 1, c) + m:set(1, 2, -s) + m:set(2, 1, s) + m:set(2, 2, c) + return m +end + +---创建绕任意轴旋转的3D旋转矩阵(弧度) +---@param x number 轴的X分量 +---@param y number 轴的Y分量 +---@param z number 轴的Z分量 +---@param rad number 旋转角度(弧度) +---@return foundation.math.Matrix 旋转矩阵 +function MatrixTransformation.rotationAxis(x, y, z, rad) + local length = math.sqrt(x * x + y * y + z * z) + if length < 1e-10 then + return Matrix.identity(4) + end + + x = x / length + y = y / length + z = z / length + + local c = math.cos(rad) + local s = math.sin(rad) + local t = 1 - c + + local m = Matrix.identity(4) + m:set(1, 1, t * x * x + c) + m:set(1, 2, t * x * y - s * z) + m:set(1, 3, t * x * z + s * y) + + m:set(2, 1, t * x * y + s * z) + m:set(2, 2, t * y * y + c) + m:set(2, 3, t * y * z - s * x) + + m:set(3, 1, t * x * z - s * y) + m:set(3, 2, t * y * z + s * x) + m:set(3, 3, t * z * z + c) + + return m +end + +---创建视图矩阵 +---@param eyeX number 视点X坐标 +---@param eyeY number 视点Y坐标 +---@param eyeZ number 视点Z坐标 +---@param centerX number 目标点X坐标 +---@param centerY number 目标点Y坐标 +---@param centerZ number 目标点Z坐标 +---@param upX number 上向量X分量 +---@param upY number 上向量Y分量 +---@param upZ number 上向量Z分量 +---@return foundation.math.Matrix 视图矩阵 +function MatrixTransformation.lookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) + local zx = eyeX - centerX + local zy = eyeY - centerY + local zz = eyeZ - centerZ + + local zLen = math.sqrt(zx * zx + zy * zy + zz * zz) + if zLen < 1e-10 then + return Matrix.identity(4) + end + zx = zx / zLen + zy = zy / zLen + zz = zz / zLen + + local xx = upY * zz - upZ * zy + local xy = upZ * zx - upX * zz + local xz = upX * zy - upY * zx + + local xLen = math.sqrt(xx * xx + xy * xy + xz * xz) + if xLen < 1e-10 then + upX, upY, upZ = 0, 1, 0 + if math.abs(zy) > 0.9 then + upX, upY, upZ = 1, 0, 0 + end + xx = upY * zz - upZ * zy + xy = upZ * zx - upX * zz + xz = upX * zy - upY * zx + xLen = math.sqrt(xx * xx + xy * xy + xz * xz) + end + xx = xx / xLen + xy = xy / xLen + xz = xz / xLen + + local yx = zy * xz - zz * xy + local yy = zz * xx - zx * xz + local yz = zx * xy - zy * xx + + local m = Matrix.identity(4) + m:set(1, 1, xx) + m:set(1, 2, xy) + m:set(1, 3, xz) + m:set(2, 1, yx) + m:set(2, 2, yy) + m:set(2, 3, yz) + m:set(3, 1, zx) + m:set(3, 2, zy) + m:set(3, 3, zz) + + m:set(1, 4, -(xx * eyeX + xy * eyeY + xz * eyeZ)) + m:set(2, 4, -(yx * eyeX + yy * eyeY + yz * eyeZ)) + m:set(3, 4, -(zx * eyeX + zy * eyeY + zz * eyeZ)) + + return m +end + +---创建正交投影矩阵 +---@param left number 左平面 +---@param right number 右平面 +---@param bottom number 底平面 +---@param top number 顶平面 +---@param near number 近平面 +---@param far number 远平面 +---@return foundation.math.Matrix 正交投影矩阵 +function MatrixTransformation.orthographic(left, right, bottom, top, near, far) + if math.abs(right - left) < 1e-10 or math.abs(top - bottom) < 1e-10 or math.abs(far - near) < 1e-10 then + error("Invalid orthographic projection parameters") + end + + local m = Matrix.identity(4) + m:set(1, 1, 2 / (right - left)) + m:set(2, 2, 2 / (top - bottom)) + m:set(3, 3, -2 / (far - near)) + m:set(1, 4, -(right + left) / (right - left)) + m:set(2, 4, -(top + bottom) / (top - bottom)) + m:set(3, 4, -(far + near) / (far - near)) + + return m +end + +---创建透视投影矩阵 +---@param fovy number 垂直视野角度(弧度) +---@param aspect number 宽高比 +---@param near number 近平面 +---@param far number 远平面 +---@return foundation.math.Matrix 透视投影矩阵 +function MatrixTransformation.perspective(fovy, aspect, near, far) + if math.abs(aspect) < 1e-10 or math.abs(far - near) < 1e-10 then + error("Invalid perspective projection parameters") + end + + local f = 1 / math.tan(fovy / 2) + + local m = Matrix.create(4, 4, 0) + m:set(1, 1, f / aspect) + m:set(2, 2, f) + m:set(3, 3, (far + near) / (near - far)) + m:set(3, 4, (2 * far * near) / (near - far)) + m:set(4, 3, -1) + + return m +end + +---将Vector2应用矩阵变换(假设为2D变换,使用3x3矩阵) +---@param matrix foundation.math.Matrix 变换矩阵 +---@param vector foundation.math.Vector2 待变换向量 +---@return foundation.math.Vector2 变换后的向量 +function MatrixTransformation.transformVector2(matrix, vector) + if matrix.rows ~= 3 or matrix.cols ~= 3 then + error("Expected 3x3 matrix for 2D transformation") + end + + local x = matrix:get(1, 1) * vector.x + matrix:get(1, 2) * vector.y + matrix:get(1, 3) + local y = matrix:get(2, 1) * vector.x + matrix:get(2, 2) * vector.y + matrix:get(2, 3) + local w = matrix:get(3, 1) * vector.x + matrix:get(3, 2) * vector.y + matrix:get(3, 3) + + if math.abs(w) > 1e-10 then + return Vector2.create(x / w, y / w) + else + return Vector2.create(x, y) + end +end + +---将Vector3应用矩阵变换 +---@param matrix foundation.math.Matrix 变换矩阵 +---@param vector foundation.math.Vector3 待变换向量 +---@return foundation.math.Vector3 变换后的向量 +function MatrixTransformation.transformVector3(matrix, vector) + if matrix.rows ~= 4 or matrix.cols ~= 4 then + error("Expected 4x4 matrix for 3D transformation") + end + + local x = matrix:get(1, 1) * vector.x + matrix:get(1, 2) * vector.y + matrix:get(1, 3) * vector.z + matrix:get(1, 4) + local y = matrix:get(2, 1) * vector.x + matrix:get(2, 2) * vector.y + matrix:get(2, 3) * vector.z + matrix:get(2, 4) + local z = matrix:get(3, 1) * vector.x + matrix:get(3, 2) * vector.y + matrix:get(3, 3) * vector.z + matrix:get(3, 4) + local w = matrix:get(4, 1) * vector.x + matrix:get(4, 2) * vector.y + matrix:get(4, 3) * vector.z + matrix:get(4, 4) + + if math.abs(w) > 1e-10 then + return Vector3.create(x / w, y / w, z / w) + else + return Vector3.create(x, y, z) + end +end + +return MatrixTransformation \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/matrix/SpecialMatrix.lua b/game/packages/thlib-scripts-v2/foundation/math/matrix/SpecialMatrix.lua new file mode 100644 index 00000000..b39db567 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/math/matrix/SpecialMatrix.lua @@ -0,0 +1,428 @@ +local ipairs = ipairs +local error = error +local math = math + +local Matrix = require("foundation.math.matrix.Matrix") + +---@class foundation.math.matrix.SpecialMatrix +local SpecialMatrix = {} + +---创建一个对角矩阵 +---@param diag table 对角线上的值 +---@return foundation.math.Matrix 对角矩阵 +function SpecialMatrix.diagonal(diag) + local size = #diag + local matrix = Matrix.create(size, size, 0) + + for i = 1, size do + matrix:set(i, i, diag[i]) + end + + return matrix +end + +---创建一个三对角矩阵 +---@param main table 主对角线上的值 +---@param upper table 上对角线上的值 +---@param lower table 下对角线上的值 +---@return foundation.math.Matrix 三对角矩阵 +function SpecialMatrix.tridiagonal(main, upper, lower) + local size = #main + + if #upper ~= size - 1 or #lower ~= size - 1 then + error("Invalid dimensions for tridiagonal matrix") + end + + local matrix = Matrix.create(size, size, 0) + + for i = 1, size do + matrix:set(i, i, main[i]) + end + + for i = 1, size - 1 do + matrix:set(i, i + 1, upper[i]) + matrix:set(i + 1, i, lower[i]) + end + + return matrix +end + +---创建一个Toeplitz矩阵(每一条从左上到右下的对角线上的元素相同) +---@param first_row table 第一行的元素 +---@param first_col table 第一列的元素(第一个元素应与first_row[1]相同) +---@return foundation.math.Matrix Toeplitz矩阵 +function SpecialMatrix.toeplitz(first_row, first_col) + if first_row[1] ~= first_col[1] then + error("First element of row and column must be the same") + end + + local rows = #first_col + local cols = #first_row + + local matrix = Matrix.create(rows, cols) + + for i = 1, rows do + for j = 1, cols do + if j >= i then + matrix:set(i, j, first_row[j - i + 1]) + else + matrix:set(i, j, first_col[i - j + 1]) + end + end + end + + return matrix +end + +---创建一个Vandermonde矩阵 +---@param points table 用于生成矩阵的点 +---@param order number|nil 多项式阶数(默认为points的长度) +---@return foundation.math.Matrix Vandermonde矩阵 +function SpecialMatrix.vandermonde(points, order) + local n = #points + order = order or n + + local matrix = Matrix.create(n, order) + + for i = 1, n do + matrix:set(i, 1, 1) + for j = 2, order do + matrix:set(i, j, matrix:get(i, j - 1) * points[i]) + end + end + + return matrix +end + +---创建一个Hilbert矩阵 (H[i,j] = 1/(i+j-1)) +---@param size number 矩阵大小 +---@return foundation.math.Matrix Hilbert矩阵 +function SpecialMatrix.hilbert(size) + local matrix = Matrix.create(size, size) + + for i = 1, size do + for j = 1, size do + matrix:set(i, j, 1 / (i + j - 1)) + end + end + + return matrix +end + +---创建一个循环矩阵(第一行的循环位移构成后续行) +---@param first_row table 第一行的元素 +---@return foundation.math.Matrix 循环矩阵 +function SpecialMatrix.circulant(first_row) + local size = #first_row + local matrix = Matrix.create(size, size) + + for i = 1, size do + for j = 1, size do + local index = ((j - i) % size) + 1 + if index <= 0 then + index = index + size + end + matrix:set(i, j, first_row[index]) + end + end + + return matrix +end + +---创建一个具有指定块的分块对角矩阵 +---@param blocks table 块矩阵数组 +---@return foundation.math.Matrix 分块对角矩阵 +function SpecialMatrix.blockDiagonal(blocks) + local totalRows = 0 + local totalCols = 0 + + for _, block in ipairs(blocks) do + totalRows = totalRows + block.rows + totalCols = totalCols + block.cols + end + + local matrix = Matrix.create(totalRows, totalCols, 0) + + local rowOffset = 0 + local colOffset = 0 + + for _, block in ipairs(blocks) do + for i = 1, block.rows do + for j = 1, block.cols do + matrix:set(rowOffset + i, colOffset + j, block:get(i, j)) + end + end + + rowOffset = rowOffset + block.rows + colOffset = colOffset + block.cols + end + + return matrix +end + +---创建上三角矩阵 +---@param values table 上三角部分的值(按行优先顺序排列的一维数组) +---@param size number 矩阵大小 +---@return foundation.math.Matrix 上三角矩阵 +function SpecialMatrix.upperTriangular(values, size) + local matrix = Matrix.create(size, size, 0) + local index = 1 + + for i = 1, size do + for j = i, size do + matrix:set(i, j, values[index]) + index = index + 1 + end + end + + return matrix +end + +---创建下三角矩阵 +---@param values table 下三角部分的值(按行优先顺序排列的一维数组) +---@param size number 矩阵大小 +---@return foundation.math.Matrix 下三角矩阵 +function SpecialMatrix.lowerTriangular(values, size) + local matrix = Matrix.create(size, size, 0) + local index = 1 + + for i = 1, size do + for j = 1, i do + matrix:set(i, j, values[index]) + index = index + 1 + end + end + + return matrix +end + +---创建一个对称矩阵 +---@param values table 上三角部分的值(包括对角线,按行优先顺序排列) +---@param size number 矩阵大小 +---@return foundation.math.Matrix 对称矩阵 +function SpecialMatrix.symmetric(values, size) + local matrix = Matrix.create(size, size) + local index = 1 + + for i = 1, size do + for j = i, size do + matrix:set(i, j, values[index]) + if i ~= j then + matrix:set(j, i, values[index]) + end + index = index + 1 + end + end + + return matrix +end + +---创建一个反对称矩阵(A^T = -A) +---@param values table 上三角部分的值(不包括对角线,按行优先顺序排列) +---@param size number 矩阵大小 +---@return foundation.math.Matrix 反对称矩阵 +function SpecialMatrix.skewSymmetric(values, size) + local matrix = Matrix.create(size, size, 0) + local index = 1 + + for i = 1, size do + for j = i + 1, size do + matrix:set(i, j, values[index]) + matrix:set(j, i, -values[index]) + index = index + 1 + end + end + + return matrix +end + +---创建一个Hadamard矩阵(若存在) +---@param size number 矩阵大小(必须是1,2或4的倍数以便存在) +---@return foundation.math.Matrix|nil Hadamard矩阵,若不存在则返回nil +function SpecialMatrix.hadamard(size) + if size <= 0 then + return nil + elseif size == 1 then + return Matrix.create(1, 1, 1) + elseif size == 2 then + return Matrix.fromArray({ + {1, 1}, + {1, -1} + }) + end + + if size % 4 ~= 0 then + return nil + end + + local halfSize = size / 2 + local h = SpecialMatrix.hadamard(halfSize) + if not h then + return nil + end + + local result = Matrix.create(size, size) + + for i = 1, halfSize do + for j = 1, halfSize do + result:set(i, j, h:get(i, j)) + result:set(i, j + halfSize, h:get(i, j)) + result:set(i + halfSize, j, h:get(i, j)) + result:set(i + halfSize, j + halfSize, -h:get(i, j)) + end + end + + return result +end + +---创建一个二次型矩阵 f(x) = x^T A x +---@param coef table 二次型的系数 +---@param vars number 变量个数 +---@return foundation.math.Matrix 二次型矩阵 +function SpecialMatrix.quadratic(coef, vars) + local matrix = Matrix.create(vars, vars, 0) + + local index = 1 + for i = 1, vars do + for j = i, vars do + if i == j then + matrix:set(i, j, coef[index]) + else + matrix:set(i, j, coef[index] / 2) + matrix:set(j, i, coef[index] / 2) + end + index = index + 1 + end + end + + return matrix +end + +---QR分解(使用Gram-Schmidt正交化) +---@param matrix foundation.math.Matrix 待分解的矩阵 +---@return foundation.math.Matrix Q 正交矩阵Q +---@return foundation.math.Matrix R 上三角矩阵R +function SpecialMatrix.qrDecomposition(matrix) + local m = matrix.rows + local n = matrix.cols + + local Q = Matrix.create(m, n) + local R = Matrix.create(n, n, 0) + + local columns = {} + for j = 1, n do + columns[j] = {} + for i = 1, m do + columns[j][i] = matrix:get(i, j) + end + end + + for j = 1, n do + local q = {} + for i = 1, m do + q[i] = columns[j][i] + end + + for k = 1, j - 1 do + local dot = 0 + for i = 1, m do + dot = dot + columns[j][i] * Q:get(i, k) + end + R:set(k, j, dot) + + for i = 1, m do + q[i] = q[i] - dot * Q:get(i, k) + end + end + + local norm = 0 + for i = 1, m do + norm = norm + q[i] * q[i] + end + norm = math.sqrt(norm) + + if norm < 1e-10 then + error("Matrix columns are linearly dependent") + end + + R:set(j, j, norm) + + for i = 1, m do + Q:set(i, j, q[i] / norm) + end + end + + return Q, R +end + +---特征值分解(仅适用于对称矩阵,使用幂法计算最大特征值) +---@param matrix foundation.math.Matrix 待分解的矩阵 +---@param maxIterations number|nil 最大迭代次数(默认100) +---@param tolerance number|nil 收敛容差(默认1e-6) +---@return number 最大特征值 +---@return table 对应特征向量 +function SpecialMatrix.powerMethod(matrix, maxIterations, tolerance) + if matrix.rows ~= matrix.cols then + error("Matrix must be square for eigenvalue computation") + end + + maxIterations = maxIterations or 100 + tolerance = tolerance or 1e-6 + + local n = matrix.rows + + local x = {} + for i = 1, n do + x[i] = math.random() + end + + local norm = 0 + for i = 1, n do + norm = norm + x[i] * x[i] + end + norm = math.sqrt(norm) + + for i = 1, n do + x[i] = x[i] / norm + end + + local eigenvalue = 0 + local prevEigenvalue = 0 + + for _ = 1, maxIterations do + local y = {} + for i = 1, n do + y[i] = 0 + for j = 1, n do + y[i] = y[i] + matrix:get(i, j) * x[j] + end + end + + local rayleigh = 0 + for i = 1, n do + rayleigh = rayleigh + x[i] * y[i] + end + + eigenvalue = rayleigh + + if math.abs(eigenvalue - prevEigenvalue) < tolerance then + break + end + + prevEigenvalue = eigenvalue + + norm = 0 + for i = 1, n do + norm = norm + y[i] * y[i] + end + norm = math.sqrt(norm) + + for i = 1, n do + x[i] = y[i] / norm + end + end + + return eigenvalue, x +end + +return SpecialMatrix \ No newline at end of file From cb8fc607b2a19d444d97a365946d0c7f82c01307 Mon Sep 17 00:00:00 2001 From: OLC Date: Sat, 10 May 2025 15:20:25 +0800 Subject: [PATCH 66/71] feat: add AABB bounding box methods for geometric shapes --- .../foundation/shape/BezierCurve.lua | 112 ++++-------------- .../foundation/shape/Circle.lua | 8 ++ .../foundation/shape/Ellipse.lua | 19 ++- .../foundation/shape/Line.lua | 15 +++ .../foundation/shape/Polygon.lua | 85 +++++++------ .../thlib-scripts-v2/foundation/shape/Ray.lua | 31 +++++ .../foundation/shape/Rectangle.lua | 26 ++-- .../foundation/shape/Sector.lua | 35 ++++-- .../foundation/shape/Segment.lua | 13 +- .../foundation/shape/Triangle.lua | 18 +-- 10 files changed, 192 insertions(+), 170 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua b/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua index ecfff96e..e1838bc9 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua @@ -343,107 +343,41 @@ function BezierCurve:getCenter() return Vector2.create((minX + maxX) / 2, (minY + maxY) / 2) end ----计算贝塞尔曲线的包围盒宽高 ----@return number, number -function BezierCurve:getBoundingBoxSize() +---获取贝塞尔曲线的AABB包围盒 +---@return number, number, number, number +function BezierCurve:AABB() local minX, minY = math.huge, math.huge local maxX, maxY = -math.huge, -math.huge - local function updateBounds(point) + -- 首先检查所有控制点 + for i = 0, self.num_points - 1 do + local point = self.control_points[i] minX = math.min(minX, point.x) minY = math.min(minY, point.y) maxX = math.max(maxX, point.x) maxY = math.max(maxY, point.y) end - updateBounds(self.control_points[0]) - updateBounds(self.control_points[self.num_points - 1]) - - local n = self.order - if n > 1 then - local deriv_points = {} - for i = 0, n - 1 do - deriv_points[i + 1] = (self.control_points[i + 1] - self.control_points[i]) * n - end - - local t_values = { 0, 1 } - if n == 2 then - local p0, p1, p2 = self.control_points[0], self.control_points[1], self.control_points[2] - local dx1 = p1.x - p0.x - local dx2 = p2.x - p1.x - local dy1 = p1.y - p0.y - local dy2 = p2.y - p1.y - - if dx1 ~= dx2 then - local tx = dx1 / (dx1 - dx2) - if tx > 0 and tx < 1 then - table.insert(t_values, tx) - end - end - - if dy1 ~= dy2 then - local ty = dy1 / (dy1 - dy2) - if ty > 0 and ty < 1 then - table.insert(t_values, ty) - end - end - elseif n == 3 then - local q0 = deriv_points[1] - local q1 = deriv_points[2] - local q2 = deriv_points[3] - - local a_x = q0.x - 2 * q1.x + q2.x - local b_x = 2 * (q1.x - q0.x) - local c_x = q0.x - if a_x ~= 0 then - local discriminant_x = b_x * b_x - 4 * a_x * c_x - if discriminant_x >= 0 then - local sqrt_d = math.sqrt(discriminant_x) - local t1 = (-b_x + sqrt_d) / (2 * a_x) - local t2 = (-b_x - sqrt_d) / (2 * a_x) - if t1 > 0 and t1 < 1 then - table.insert(t_values, t1) - end - if t2 > 0 and t2 < 1 then - table.insert(t_values, t2) - end - end - elseif b_x ~= 0 then - local t = -c_x / b_x - if t > 0 and t < 1 then - table.insert(t_values, t) - end - end - - local a_y = q0.y - 2 * q1.y + q2.y - local b_y = 2 * (q1.y - q0.y) - local c_y = q0.y - if a_y ~= 0 then - local discriminant_y = b_y * b_y - 4 * a_y * c_y - if discriminant_y >= 0 then - local sqrt_d = math.sqrt(discriminant_y) - local t1 = (-b_y + sqrt_d) / (2 * a_y) - local t2 = (-b_y - sqrt_d) / (2 * a_y) - if t1 > 0 and t1 < 1 then - table.insert(t_values, t1) - end - if t2 > 0 and t2 < 1 then - table.insert(t_values, t2) - end - end - elseif b_y ~= 0 then - local t = -c_y / b_y - if t > 0 and t < 1 then - table.insert(t_values, t) - end - end - end - - for _, t in ipairs(t_values) do - updateBounds(self:getPoint(t)) + -- 对于高阶贝塞尔曲线,还需要检查曲线上的极值点 + if self.order > 1 then + local segments = self.order * 10 -- 使用更多的分段来获得更精确的包围盒 + for i = 0, segments do + local t = i / segments + local point = self:getPoint(t) + minX = math.min(minX, point.x) + minY = math.min(minY, point.y) + maxX = math.max(maxX, point.x) + maxY = math.max(maxY, point.y) end end + return minX, maxX, minY, maxY +end + +---计算贝塞尔曲线的包围盒宽高 +---@return number, number +function BezierCurve:getBoundingBoxSize() + local minX, maxX, minY, maxY = self:AABB() return maxX - minX, maxY - minY end diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index 37939fb2..b627f4ed 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -186,6 +186,14 @@ function Circle:getCenter() return self.center:clone() end +---获取圆的AABB包围盒 +---@return number, number, number, number +function Circle:AABB() + local cx, cy = self.center.x, self.center.y + local r = self.radius + return cx - r, cx + r, cy - r, cy + r +end + ---计算圆的包围盒宽高 ---@return number, number function Circle:getBoundingBoxSize() diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua index 0601330f..86c1fc54 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua @@ -336,9 +336,9 @@ function Ellipse:getCenter() return self.center:clone() end ----计算椭圆的包围盒宽高 ----@return number, number -function Ellipse:getBoundingBoxSize() +---获取椭圆的AABB包围盒 +---@return number, number, number, number +function Ellipse:AABB() local baseAngle = self.direction:angle() local cos_angle = math.cos(baseAngle) local sin_angle = math.sin(baseAngle) @@ -346,10 +346,17 @@ function Ellipse:getBoundingBoxSize() local x_axis = Vector2.create(self.rx * cos_angle, self.rx * sin_angle) local y_axis = Vector2.create(-self.ry * sin_angle, self.ry * cos_angle) - local width = 2 * math.sqrt(x_axis.x * x_axis.x + y_axis.x * y_axis.x) - local height = 2 * math.sqrt(x_axis.y * x_axis.y + y_axis.y * y_axis.y) + local half_width = math.sqrt(x_axis.x * x_axis.x + y_axis.x * y_axis.x) + local half_height = math.sqrt(x_axis.y * x_axis.y + y_axis.y * y_axis.y) + + return self.center.x - half_width, self.center.x + half_width, self.center.y - half_height, self.center.y + half_height +end - return width, height +---计算椭圆的包围盒宽高 +---@return number, number +function Ellipse:getBoundingBoxSize() + local minX, maxX, minY, maxY = self:AABB() + return maxX - minX, maxY - minY end ---计算椭圆的重心 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index 648153a6..1ba5d586 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -132,6 +132,21 @@ function Line:getCenter() return self.point end +---获取直线的AABB包围盒 +---@return number, number, number, number +function Line:AABB() + if math.abs(self.direction.x) < 1e-10 then + -- 垂直线 + return self.point.x, self.point.x, -math.huge, math.huge + elseif math.abs(self.direction.y) < 1e-10 then + -- 水平线 + return -math.huge, math.huge, self.point.y, self.point.y + else + -- 斜线 + return -math.huge, math.huge, -math.huge, math.huge + end +end + ---计算直线的包围盒宽高 ---@return number, number function Line:getBoundingBoxSize() diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index 3c2e1dba..a919dedb 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -177,66 +177,63 @@ function Polygon:getVertices() return vertices end ----获取多边形的重心 ----@return foundation.math.Vector2 -function Polygon:centroid() - local sumX, sumY = 0, 0 +---获取多边形的AABB包围盒 +---@return number, number, number, number +function Polygon:AABB() + local minX, minY = math.huge, math.huge + local maxX, maxY = -math.huge, -math.huge for i = 0, self.size - 1 do - sumX = sumX + self.points[i].x - sumY = sumY + self.points[i].y + local point = self.points[i] + minX = math.min(minX, point.x) + minY = math.min(minY, point.y) + maxX = math.max(maxX, point.x) + maxY = math.max(maxY, point.y) end - return Vector2.create(sumX / self.size, sumY / self.size) + return minX, maxX, minY, maxY end ----计算多边形的中心 +---计算多边形的重心 ---@return foundation.math.Vector2 -function Polygon:getCenter() - local minX, minY = math.huge, math.huge - local maxX, maxY = -math.huge, -math.huge +function Polygon:centroid() + local totalArea = 0 + local centroidX = 0 + local centroidY = 0 - for i = 0, self.size - 1 do - local point = self.points[i] - if point.x < minX then - minX = point.x - end - if point.y < minY then - minY = point.y - end - if point.x > maxX then - maxX = point.x - end - if point.y > maxY then - maxY = point.y - end + local p0 = self.points[0] + for i = 1, self.size - 2 do + local p1 = self.points[i] + local p2 = self.points[i + 1] + + local area = math.abs((p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y)) / 2 + totalArea = totalArea + area + + local cx = (p0.x + p1.x + p2.x) / 3 + local cy = (p0.y + p1.y + p2.y) / 3 + + centroidX = centroidX + cx * area + centroidY = centroidY + cy * area end + if totalArea == 0 then + return self:getCenter() + end + + return Vector2.create(centroidX / totalArea, centroidY / totalArea) +end + +---计算多边形的中心 +---@return foundation.math.Vector2 +function Polygon:getCenter() + local minX, maxX, minY, maxY = self:AABB() return Vector2.create((minX + maxX) / 2, (minY + maxY) / 2) end ---计算多边形的包围盒宽高 ---@return number, number function Polygon:getBoundingBoxSize() - local minX, minY = math.huge, math.huge - local maxX, maxY = -math.huge, -math.huge - - for i = 0, self.size - 1 do - local point = self.points[i] - if point.x < minX then - minX = point.x - end - if point.y < minY then - minY = point.y - end - if point.x > maxX then - maxX = point.x - end - if point.y > maxY then - maxY = point.y - end - end - + local minX, maxX, minY, maxY = self:AABB() return maxX - minX, maxY - minY end diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index c280ede0..7d0b6714 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -131,6 +131,37 @@ function Ray:getCenter() end end +---获取射线的AABB包围盒 +---@return number, number, number, number +function Ray:AABB() + if math.abs(self.direction.x) < 1e-10 then + -- 垂直线 + if self.direction.y > 0 then + return self.point.x, self.point.x, self.point.y, math.huge + else + return self.point.x, self.point.x, -math.huge, self.point.y + end + elseif math.abs(self.direction.y) < 1e-10 then + -- 水平线 + if self.direction.x > 0 then + return self.point.x, math.huge, self.point.y, self.point.y + else + return -math.huge, self.point.x, self.point.y, self.point.y + end + else + -- 斜线 + if self.direction.x > 0 and self.direction.y > 0 then + return self.point.x, math.huge, self.point.y, math.huge + elseif self.direction.x > 0 and self.direction.y < 0 then + return self.point.x, math.huge, -math.huge, self.point.y + elseif self.direction.x < 0 and self.direction.y > 0 then + return -math.huge, self.point.x, self.point.y, math.huge + else + return -math.huge, self.point.x, -math.huge, self.point.y + end + end +end + ---计算射线的包围盒宽高 ---@return number, number function Ray:getBoundingBoxSize() diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index fbe435be..0d298831 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -293,18 +293,26 @@ function Rectangle:getCenter() return self.center:clone() end +---获取矩形的AABB包围盒 +---@return number, number, number, number +function Rectangle:AABB() + local vertices = self:getVertices() + local minX, maxX = vertices[1].x, vertices[1].x + local minY, maxY = vertices[1].y, vertices[1].y + for i = 2, 4 do + local v = vertices[i] + minX = math.min(minX, v.x) + maxX = math.max(maxX, v.x) + minY = math.min(minY, v.y) + maxY = math.max(maxY, v.y) + end + return minX, maxX, minY, maxY +end + ---计算矩形的包围盒宽高 ---@return number, number function Rectangle:getBoundingBoxSize() - local vertices = self:getVertices() - local minX, minY = math.huge, math.huge - local maxX, maxY = -math.huge, -math.huge - for _, vertex in ipairs(vertices) do - minX = math.min(minX, vertex.x) - minY = math.min(minY, vertex.y) - maxX = math.max(maxX, vertex.x) - maxY = math.max(maxY, vertex.y) - end + local minX, maxX, minY, maxY = self:AABB() return maxX - minX, maxY - minY end diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index c0738ff7..3d18b271 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -212,11 +212,14 @@ function Sector:getCenter() return Vector2.create((x_min + x_max) / 2, (y_min + y_max) / 2) end ----计算扇形的包围盒宽高 ----@return number, number -function Sector:getBoundingBoxSize() +---获取扇形的AABB包围盒 +---@return number, number, number, number +function Sector:AABB() if math.abs(self.range) >= 1 then - return 2 * self.radius, 2 * self.radius + -- 如果是整圆,直接返回圆的包围盒 + local cx, cy = self.center.x, self.center.y + local r = self.radius + return cx - r, cx + r, cy - r, cy + r end local points = { self.center:clone() } @@ -247,16 +250,24 @@ function Sector:getBoundingBoxSize() end end - local x_min, x_max = points[1].x, points[1].x - local y_min, y_max = points[1].y, points[1].y - for _, p in ipairs(points) do - x_min = math.min(x_min, p.x) - x_max = math.max(x_max, p.x) - y_min = math.min(y_min, p.y) - y_max = math.max(y_max, p.y) + local minX, minY = math.huge, math.huge + local maxX, maxY = -math.huge, -math.huge + + for _, point in ipairs(points) do + minX = math.min(minX, point.x) + minY = math.min(minY, point.y) + maxX = math.max(maxX, point.x) + maxY = math.max(maxY, point.y) end - return x_max - x_min, y_max - y_min + return minX, maxX, minY, maxY +end + +---计算扇形的包围盒宽高 +---@return number, number +function Sector:getBoundingBoxSize() + local minX, maxX, minY, maxY = self:AABB() + return maxX - minX, maxY - minY end ---获取扇形的重心 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index 52406ad3..ebd90c7f 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -136,13 +136,20 @@ function Segment:getCenter() return self:midpoint() end ----计算线段的包围盒宽高 ----@return number, number 线段的宽度和高度 -function Segment:getBoundingBoxSize() +---获取线段的AABB包围盒 +---@return number, number, number, number +function Segment:AABB() local minX = math.min(self.point1.x, self.point2.x) local maxX = math.max(self.point1.x, self.point2.x) local minY = math.min(self.point1.y, self.point2.y) local maxY = math.max(self.point1.y, self.point2.y) + return minX, maxX, minY, maxY +end + +---计算线段的包围盒宽高 +---@return number, number 线段的宽度和高度 +function Segment:getBoundingBoxSize() + local minX, maxX, minY, maxY = self:AABB() return maxX - minX, maxY - minY end diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua index 0f6042da..89be3f5b 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua @@ -97,23 +97,27 @@ function Triangle:centroid() return (self.point1 + self.point2 + self.point3) / 3 end ----计算三角形的中心 ----@return foundation.math.Vector2 三角形的中心 -function Triangle:getCenter() +---获取三角形的AABB包围盒 +---@return number, number, number, number +function Triangle:AABB() local minX = math.min(self.point1.x, self.point2.x, self.point3.x) local maxX = math.max(self.point1.x, self.point2.x, self.point3.x) local minY = math.min(self.point1.y, self.point2.y, self.point3.y) local maxY = math.max(self.point1.y, self.point2.y, self.point3.y) + return minX, maxX, minY, maxY +end + +---计算三角形的中心 +---@return foundation.math.Vector2 三角形的中心 +function Triangle:getCenter() + local minX, maxX, minY, maxY = self:AABB() return Vector2.create((minX + maxX) / 2, (minY + maxY) / 2) end ---计算三角形的包围盒宽高 ---@return number, number function Triangle:getBoundingBoxSize() - local minX = math.min(self.point1.x, self.point2.x, self.point3.x) - local maxX = math.max(self.point1.x, self.point2.x, self.point3.x) - local minY = math.min(self.point1.y, self.point2.y, self.point3.y) - local maxY = math.max(self.point1.y, self.point2.y, self.point3.y) + local minX, maxX, minY, maxY = self:AABB() return maxX - minX, maxY - minY end From b2c52ac6ee9b2a0ffb30f49684917257d6103e30 Mon Sep 17 00:00:00 2001 From: OLC Date: Sat, 10 May 2025 15:37:51 +0800 Subject: [PATCH 67/71] feat: enhance geometric shapes with rotation and scaling methods supporting custom centers --- .../foundation/shape/Circle.lua | 69 +++++++++++++------ .../foundation/shape/Ellipse.lua | 52 ++++++++------ .../foundation/shape/Line.lua | 31 ++++++++- .../foundation/shape/Polygon.lua | 53 +++++--------- .../thlib-scripts-v2/foundation/shape/Ray.lua | 31 ++++++++- .../foundation/shape/Rectangle.lua | 50 +++++++++----- .../foundation/shape/Sector.lua | 65 +++++++++++------ 7 files changed, 232 insertions(+), 119 deletions(-) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua index b627f4ed..6fff3911 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua @@ -111,47 +111,74 @@ function Circle:moved(v) return Circle.create(Vector2.create(self.center.x + moveX, self.center.y + moveY), self.radius) end ----旋转圆(没有实际效果) ----@param _ number 旋转弧度 +---旋转圆(修改当前圆) +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为圆心 ---@return foundation.shape.Circle 自身引用 -function Circle:rotate(_) +function Circle:rotate(rad, center) + if center then + local dx = self.center.x - center.x + local dy = self.center.y - center.y + local cosA, sinA = math.cos(rad), math.sin(rad) + self.center.x = center.x + dx * cosA - dy * sinA + self.center.y = center.y + dx * sinA + dy * cosA + end return self end ----旋转圆(没有实际效果) ----@param _ number 旋转角度 +---旋转圆(修改当前圆) +---@param angle number 旋转角度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为圆心 ---@return foundation.shape.Circle 自身引用 -function Circle:degreeRotate(_) - return self +function Circle:degreeRotate(angle, center) + return self:rotate(math.rad(angle), center) end ----获取旋转后的圆副本(没有实际效果) ----@param _ number 旋转弧度 +---获取旋转后的圆副本 +---@param rad number 旋转弧度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为圆心 ---@return foundation.shape.Circle -function Circle:rotated(_) - return Circle.create(self.center:clone(), self.radius) +function Circle:rotated(rad, center) + local result = Circle.create(self.center:clone(), self.radius) + return result:rotate(rad, center) end ----获取旋转后的圆副本(没有实际效果) ----@param _ number 旋转角度 +---获取旋转后的圆副本 +---@param angle number 旋转角度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为圆心 ---@return foundation.shape.Circle -function Circle:degreeRotated(_) - return Circle.create(self.center:clone(), self.radius) +function Circle:degreeRotated(angle, center) + return self:rotated(math.rad(angle), center) end ---缩放圆(修改当前圆) ----@param scale number 缩放比例 +---@param scale number|foundation.math.Vector2 缩放比例 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为圆心 ---@return foundation.shape.Circle 自身引用 -function Circle:scale(scale) - self.radius = self.radius * scale +function Circle:scale(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + center = center or self.center + + self.radius = self.radius * math.sqrt(scaleX * scaleY) + local dx = self.center.x - center.x + local dy = self.center.y - center.y + self.center.x = center.x + dx * scaleX + self.center.y = center.y + dy * scaleY return self end ---获取缩放后的圆副本 ----@param scale number 缩放比例 +---@param scale number|foundation.math.Vector2 缩放比例 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为圆心 ---@return foundation.shape.Circle -function Circle:scaled(scale) - return Circle.create(self.center, self.radius * scale) +function Circle:scaled(scale, center) + local result = Circle.create(self.center:clone(), self.radius) + return result:scale(scale, center) end ---检查与其他形状的相交 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua index 86c1fc54..c5bad6bf 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua @@ -236,64 +236,74 @@ end ---旋转椭圆(修改当前椭圆) ---@param rad number 旋转弧度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为椭圆中心 ---@return foundation.shape.Ellipse 自身引用 -function Ellipse:rotate(rad) +function Ellipse:rotate(rad, center) self.direction = self.direction:rotated(rad) + if center then + local dx = self.center.x - center.x + local dy = self.center.y - center.y + local cosA, sinA = math.cos(rad), math.sin(rad) + self.center.x = center.x + dx * cosA - dy * sinA + self.center.y = center.y + dx * sinA + dy * cosA + end return self end ---旋转椭圆(修改当前椭圆) ---@param angle number 旋转角度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为椭圆中心 ---@return foundation.shape.Ellipse 自身引用 -function Ellipse:degreeRotate(angle) - return self:rotate(math.rad(angle)) +function Ellipse:degreeRotate(angle, center) + return self:rotate(math.rad(angle), center) end ---获取旋转后的椭圆副本 ---@param rad number 旋转弧度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为椭圆中心 ---@return foundation.shape.Ellipse -function Ellipse:rotated(rad) - return Ellipse.create( - self.center:clone(), - self.rx, - self.ry, - self.direction:rotated(rad) - ) +function Ellipse:rotated(rad, center) + local result = Ellipse.create(self.center:clone(), self.rx, self.ry, self.direction:clone()) + return result:rotate(rad, center) end ---获取旋转后的椭圆副本 ---@param angle number 旋转角度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为椭圆中心 ---@return foundation.shape.Ellipse -function Ellipse:degreeRotated(angle) - return self:rotated(math.rad(angle)) +function Ellipse:degreeRotated(angle, center) + return self:rotated(math.rad(angle), center) end ---缩放椭圆(修改当前椭圆) ---@param scale number|foundation.math.Vector2 缩放比例 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为椭圆中心 ---@return foundation.shape.Ellipse 自身引用 -function Ellipse:scale(scale) +function Ellipse:scale(scale, center) local scaleX, scaleY if type(scale) == "number" then scaleX, scaleY = scale, scale else scaleX, scaleY = scale.x, scale.y end + center = center or self.center + self.rx = self.rx * scaleX self.ry = self.ry * scaleY + local dx = self.center.x - center.x + local dy = self.center.y - center.y + self.center.x = center.x + dx * scaleX + self.center.y = center.y + dy * scaleY return self end ---获取缩放后的椭圆副本 ---@param scale number|foundation.math.Vector2 缩放比例 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为椭圆中心 ---@return foundation.shape.Ellipse -function Ellipse:scaled(scale) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - return Ellipse.create(self.center:clone(), self.rx * scaleX, self.ry * scaleY, self.direction) +function Ellipse:scaled(scale, center) + local result = Ellipse.create(self.center:clone(), self.rx, self.ry, self.direction:clone()) + return result:scale(scale, center) end ---检查点是否在椭圆内或椭圆上 diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua index 1ba5d586..4493a3d8 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua @@ -63,7 +63,7 @@ function Line.create(point, direction) ---@diagnostic disable-next-line: need-check-nil direction = direction:clone() end - + local line = ffi.new("foundation_shape_Line", point, direction) local result = { __data = line, @@ -258,6 +258,35 @@ function Line:degreeRotated(angle, center) return self:rotated(angle, center) end +---缩放直线(更改当前直线) +---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为直线上的点 +---@return foundation.shape.Line 自身引用 +function Line:scale(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + center = center or self.point + + local dx = self.point.x - center.x + local dy = self.point.y - center.y + self.point.x = center.x + dx * scaleX + self.point.y = center.y + dy * scaleY + return self +end + +---获取缩放后的直线副本 +---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为直线上的点 +---@return foundation.shape.Line +function Line:scaled(scale, center) + local result = Line.create(self.point:clone(), self.direction:clone()) + return result:scale(scale, center) +end + ---检查与其他形状的相交 ---@param other any ---@return boolean, foundation.math.Vector2[] | nil diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua index a919dedb..443c2dae 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua @@ -346,19 +346,19 @@ end ---将当前多边形旋转指定弧度(更改当前多边形) ---@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Polygon 旋转后的多边形(自身引用) ----@overload fun(self:foundation.shape.Polygon, rad:number): foundation.shape.Polygon 将多边形绕中心点旋转指定弧度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为多边形重心 +---@return foundation.shape.Polygon 自身引用 function Polygon:rotate(rad, center) center = center or self:centroid() local cosRad = math.cos(rad) local sinRad = math.sin(rad) for i = 0, self.size - 1 do - local dx = self.points[i].x - center.x - local dy = self.points[i].y - center.y - self.points[i].x = dx * cosRad - dy * sinRad + center.x - self.points[i].y = dx * sinRad + dy * cosRad + center.y + local point = self.points[i] + local dx = point.x - center.x + local dy = point.y - center.y + point.x = center.x + dx * cosRad - dy * sinRad + point.y = center.y + dx * sinRad + dy * cosRad end return self @@ -366,45 +366,28 @@ end ---将当前多边形旋转指定角度(更改当前多边形) ---@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Polygon 旋转后的多边形(自身引用) ----@overload fun(self:foundation.shape.Polygon, angle:number): foundation.shape.Polygon 将多边形绕中心点旋转指定角度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为多边形重心 +---@return foundation.shape.Polygon 自身引用 function Polygon:degreeRotate(angle, center) - angle = math.rad(angle) - return self:rotate(angle, center) + return self:rotate(math.rad(angle), center) end ---获取当前多边形旋转指定弧度的副本 ---@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Polygon 旋转后的多边形副本 ----@overload fun(self:foundation.shape.Polygon, rad:number): foundation.shape.Polygon 将多边形绕中心点旋转指定弧度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为多边形重心 +---@return foundation.shape.Polygon function Polygon:rotated(rad, center) - center = center or self:centroid() - local cosRad = math.cos(rad) - local sinRad = math.sin(rad) - - local newPoints = {} - for i = 0, self.size - 1 do - local dx = self.points[i].x - center.x - local dy = self.points[i].y - center.y - newPoints[i + 1] = Vector2.create( - dx * cosRad - dy * sinRad + center.x, - dx * sinRad + dy * cosRad + center.y - ) - end - - return Polygon.create(newPoints) + local points = self:getVertices() + local result = Polygon.create(points) + return result:rotate(rad, center) end ---获取当前多边形旋转指定角度的副本 ---@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Polygon 旋转后的多边形副本 ----@overload fun(self:foundation.shape.Polygon, angle:number): foundation.shape.Polygon 将多边形绕中心点旋转指定角度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为多边形重心 +---@return foundation.shape.Polygon function Polygon:degreeRotated(angle, center) - angle = math.rad(angle) - return self:rotated(angle, center) + return self:rotated(math.rad(angle), center) end ---将当前多边形缩放指定倍数(更改当前多边形) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua index 7d0b6714..fa6c84e3 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua @@ -63,7 +63,7 @@ function Ray.create(point, direction) ---@diagnostic disable-next-line: need-check-nil direction = direction:clone() end - + local ray = ffi.new("foundation_shape_Ray", point, direction) local result = { __data = ray, @@ -273,6 +273,35 @@ function Ray:degreeRotated(angle, center) return self:rotated(angle, center) end +---缩放射线(更改当前射线) +---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为射线的起始点 +---@return foundation.shape.Ray 自身引用 +function Ray:scale(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + center = center or self.point + + local dx = self.point.x - center.x + local dy = self.point.y - center.y + self.point.x = center.x + dx * scaleX + self.point.y = center.y + dy * scaleY + return self +end + +---获取缩放后的射线副本 +---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为射线的起始点 +---@return foundation.shape.Ray +function Ray:scaled(scale, center) + local result = Ray.create(self.point:clone(), self.direction:clone()) + return result:scale(scale, center) +end + ---检查与其他形状的相交 ---@param other any ---@return boolean, foundation.math.Vector2[] | nil diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua index 0d298831..1d0b434e 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua @@ -193,65 +193,77 @@ end ---旋转矩形(更改当前矩形) ---@param rad number 旋转弧度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为矩形中心 ---@return foundation.shape.Rectangle 自身引用 -function Rectangle:rotate(rad) +function Rectangle:rotate(rad, center) local cosA, sinA = math.cos(rad), math.sin(rad) local x = self.direction.x * cosA - self.direction.y * sinA local y = self.direction.x * sinA + self.direction.y * cosA self.direction = Vector2.create(x, y):normalized() + + if center then + local dx = self.center.x - center.x + local dy = self.center.y - center.y + self.center.x = center.x + dx * cosA - dy * sinA + self.center.y = center.y + dx * sinA + dy * cosA + end return self end ---旋转矩形(更改当前矩形) ---@param angle number 旋转角度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为矩形中心 ---@return foundation.shape.Rectangle 自身引用 -function Rectangle:degreeRotate(angle) - return self:rotate(math.rad(angle)) +function Rectangle:degreeRotate(angle, center) + return self:rotate(math.rad(angle), center) end ---获取旋转后的矩形副本 ---@param rad number 旋转弧度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为矩形中心 ---@return foundation.shape.Rectangle -function Rectangle:rotated(rad) - local cosA, sinA = math.cos(rad), math.sin(rad) - local x = self.direction.x * cosA - self.direction.y * sinA - local y = self.direction.x * sinA + self.direction.y * cosA - return Rectangle.create(self.center, self.width, self.height, Vector2.create(x, y):normalized()) +function Rectangle:rotated(rad, center) + local result = Rectangle.create(self.center:clone(), self.width, self.height, self.direction:clone()) + return result:rotate(rad, center) end ---获取旋转后的矩形副本 ---@param angle number 旋转角度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为矩形中心 ---@return foundation.shape.Rectangle -function Rectangle:degreeRotated(angle) - return self:rotated(math.rad(angle)) +function Rectangle:degreeRotated(angle, center) + return self:rotated(math.rad(angle), center) end ---缩放矩形(更改当前矩形) ---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为矩形中心 ---@return foundation.shape.Rectangle 自身引用 -function Rectangle:scale(scale) +function Rectangle:scale(scale, center) local scaleX, scaleY if type(scale) == "number" then scaleX, scaleY = scale, scale else scaleX, scaleY = scale.x, scale.y end + center = center or self.center + self.width = self.width * scaleX self.height = self.height * scaleY + local dx = self.center.x - center.x + local dy = self.center.y - center.y + self.center.x = center.x + dx * scaleX + self.center.y = center.y + dy * scaleY return self end ---获取缩放后的矩形副本 ---@param scale number|foundation.math.Vector2 缩放倍数 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为矩形中心 ---@return foundation.shape.Rectangle -function Rectangle:scaled(scale) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - return Rectangle.create(self.center, self.width * scaleX, self.height * scaleY, self.direction) +function Rectangle:scaled(scale, center) + local result = Rectangle.create(self.center:clone(), self.width, self.height, self.direction:clone()) + return result:scale(scale, center) end ---检查点是否在矩形内(包括边界) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua index 3d18b271..a2d529ca 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua @@ -328,50 +328,73 @@ end ---旋转扇形(修改当前扇形) ---@param rad number 旋转弧度 ----@return foundation.shape.Sector -function Sector:rotate(rad) - self.direction:rotate(rad) +---@param center foundation.math.Vector2|nil 旋转中心点,默认为扇形中心 +---@return foundation.shape.Sector 自身引用 +function Sector:rotate(rad, center) + self.direction = self.direction:rotated(rad) + if center then + local dx = self.center.x - center.x + local dy = self.center.y - center.y + local cosA, sinA = math.cos(rad), math.sin(rad) + self.center.x = center.x + dx * cosA - dy * sinA + self.center.y = center.y + dx * sinA + dy * cosA + end return self end ---旋转扇形(修改当前扇形) ---@param angle number 旋转角度 ----@return foundation.shape.Sector -function Sector:degreeRotate(angle) - return self:rotate(math.rad(angle)) +---@param center foundation.math.Vector2|nil 旋转中心点,默认为扇形中心 +---@return foundation.shape.Sector 自身引用 +function Sector:degreeRotate(angle, center) + return self:rotate(math.rad(angle), center) end ---获取旋转后的扇形副本 ---@param rad number 旋转弧度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为扇形中心 ---@return foundation.shape.Sector -function Sector:rotated(rad) - return Sector.create( - self.center, self.radius, - self.direction:rotated(rad), - self.range - ) +function Sector:rotated(rad, center) + local result = Sector.create(self.center:clone(), self.radius, self.direction:clone(), self.range) + return result:rotate(rad, center) end ---获取旋转后的扇形副本 ---@param angle number 旋转角度 +---@param center foundation.math.Vector2|nil 旋转中心点,默认为扇形中心 ---@return foundation.shape.Sector -function Sector:degreeRotated(angle) - return self:rotated(math.rad(angle)) +function Sector:degreeRotated(angle, center) + return self:rotated(math.rad(angle), center) end ---缩放扇形(修改当前扇形) ----@param scale number ----@return foundation.shape.Sector -function Sector:scale(scale) - self.radius = self.radius * scale +---@param scale number|foundation.math.Vector2 缩放比例 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为扇形中心 +---@return foundation.shape.Sector 自身引用 +function Sector:scale(scale, center) + local scaleX, scaleY + if type(scale) == "number" then + scaleX, scaleY = scale, scale + else + scaleX, scaleY = scale.x, scale.y + end + center = center or self.center + + self.radius = self.radius * math.sqrt(scaleX * scaleY) + local dx = self.center.x - center.x + local dy = self.center.y - center.y + self.center.x = center.x + dx * scaleX + self.center.y = center.y + dy * scaleY return self end ---获取缩放后的扇形副本 ----@param scale number +---@param scale number|foundation.math.Vector2 缩放比例 +---@param center foundation.math.Vector2|nil 缩放中心点,默认为扇形中心 ---@return foundation.shape.Sector -function Sector:scaled(scale) - return Sector.create(self.center, self.radius * scale, self.direction, self.range) +function Sector:scaled(scale, center) + local result = Sector.create(self.center:clone(), self.radius, self.direction:clone(), self.range) + return result:scale(scale, center) end ---检查与其他形状的相交 From dec237c51c5d77a11b0840b29fcec8006520536a Mon Sep 17 00:00:00 2001 From: OLC Date: Sun, 18 May 2025 15:19:56 +0800 Subject: [PATCH 68/71] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=BA=BF?= =?UTF-8?q?=E6=AE=B5=E5=8F=96=E7=82=B9=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../packages/thlib-scripts-v2/foundation/shape/Segment.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua index ebd90c7f..234fe2a7 100644 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua @@ -136,6 +136,13 @@ function Segment:getCenter() return self:midpoint() end +---计算线段的取点 +---@param t number 取点参数,范围0到1 +---@return foundation.math.Vector2 线段上t位置的点 +function Segment:getPoint(t) + return self.point1 + (self.point2 - self.point1) * t +end + ---获取线段的AABB包围盒 ---@return number, number, number, number function Segment:AABB() From 864a842394f9e692deeb7179c5f7301cce155bb2 Mon Sep 17 00:00:00 2001 From: OLC Date: Tue, 20 May 2025 22:25:01 +0800 Subject: [PATCH 69/71] modify to git modules --- .gitmodules | 3 + game/config.json | 7 +- game/packages/lua-ffi-math | 1 + .../thlib-scripts-v2/foundation/math/LICENSE | 21 - .../foundation/math/Vector2.lua | 296 ------- .../foundation/math/Vector3.lua | 380 --------- .../foundation/math/Vector4.lua | 358 -------- .../foundation/math/matrix/Matrix.lua | 664 --------------- .../math/matrix/MatrixTransformation.lua | 325 ------- .../foundation/math/matrix/SpecialMatrix.lua | 428 ---------- .../foundation/shape/BezierCurve.lua | 741 ---------------- .../foundation/shape/Circle.lua | 289 ------- .../foundation/shape/Ellipse.lua | 539 ------------ .../thlib-scripts-v2/foundation/shape/LICENSE | 21 - .../foundation/shape/Line.lua | 350 -------- .../foundation/shape/Polygon.lua | 708 ---------------- .../thlib-scripts-v2/foundation/shape/Ray.lua | 383 --------- .../foundation/shape/Rectangle.lua | 427 ---------- .../foundation/shape/Sector.lua | 524 ------------ .../foundation/shape/Segment.lua | 378 --------- .../foundation/shape/ShapeIntersector.lua | 296 ------- .../BezierCurveIntersector.lua | 451 ---------- .../ShapeIntersector/CircleIntersector.lua | 98 --- .../shape/ShapeIntersector/ContainPoint.lua | 158 ---- .../ShapeIntersector/EllipseIntersector.lua | 801 ------------------ .../ShapeIntersector/LineIntersector.lua | 206 ----- .../ShapeIntersector/PolygonIntersector.lua | 492 ----------- .../shape/ShapeIntersector/RayIntersector.lua | 193 ----- .../ShapeIntersector/RectangleIntersector.lua | 311 ------- .../ShapeIntersector/SectorIntersector.lua | 590 ------------- .../ShapeIntersector/SegmentIntersector.lua | 60 -- .../ShapeIntersector/TriangleIntersector.lua | 280 ------ .../foundation/shape/Triangle.lua | 444 ---------- laboratory/geometry/main.lua | 2 +- 34 files changed, 11 insertions(+), 11214 deletions(-) create mode 160000 game/packages/lua-ffi-math delete mode 100644 game/packages/thlib-scripts-v2/foundation/math/LICENSE delete mode 100644 game/packages/thlib-scripts-v2/foundation/math/Vector2.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/math/Vector3.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/math/Vector4.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/math/matrix/Matrix.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/math/matrix/MatrixTransformation.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/math/matrix/SpecialMatrix.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Circle.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/LICENSE delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Line.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Ray.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Sector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Segment.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/BezierCurveIntersector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/CircleIntersector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/LineIntersector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/PolygonIntersector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RayIntersector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RectangleIntersector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SectorIntersector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SegmentIntersector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/TriangleIntersector.lua delete mode 100644 game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua diff --git a/.gitmodules b/.gitmodules index d921ba70..87cf933c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "game/packages/community-creations"] path = game/packages/community-creations url = https://github.com/Legacy-LuaSTG-Engine/Community-Creations +[submodule "game/packages/lua-ffi-math"] + path = game/packages/lua-ffi-math + url = https://github.com/BAKAOLC/Lua-FFI-Math.git diff --git a/game/config.json b/game/config.json index 7e3f3973..ff6eabb9 100644 --- a/game/config.json +++ b/game/config.json @@ -50,6 +50,11 @@ "type": "directory", "path": "packages/thlib-scripts-v2/" }, + { + "name": "lua-ffi-math", + "type": "directory", + "path": "packages/lua-ffi-math/" + }, { "name": "community-creations", "type": "directory", @@ -69,7 +74,7 @@ "window": { "title": "LuaSTG aex+", "cursor_visible": true, - "allow_window_corner": true + "allow_window_corner": false }, "graphics_system": { "preferred_device_name": "", diff --git a/game/packages/lua-ffi-math b/game/packages/lua-ffi-math new file mode 160000 index 00000000..d5899590 --- /dev/null +++ b/game/packages/lua-ffi-math @@ -0,0 +1 @@ +Subproject commit d5899590e488fa371c0d1a84395a6b2b2d0deb69 diff --git a/game/packages/thlib-scripts-v2/foundation/math/LICENSE b/game/packages/thlib-scripts-v2/foundation/math/LICENSE deleted file mode 100644 index 6e56e2e4..00000000 --- a/game/packages/thlib-scripts-v2/foundation/math/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 OLC - -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. - -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. \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua deleted file mode 100644 index 64d3046b..00000000 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector2.lua +++ /dev/null @@ -1,296 +0,0 @@ -local ffi = require("ffi") - -local type = type -local string = string -local math = math -local require = require - -local Vector3, Vector4 - -ffi.cdef [[ -typedef struct { - double x; - double y; -} foundation_math_Vector2; -]] - ----@class foundation.math.Vector2 ----@field x number X坐标分量 ----@field y number Y坐标分量 -local Vector2 = {} -Vector2.__index = Vector2 -Vector2.__type = "foundation.math.Vector2" - ----创建一个零向量 ----@return foundation.math.Vector2 零向量 -function Vector2.zero() - return Vector2.create(0, 0) -end - ----创建一个新的二维向量 ----@param x number|nil X坐标分量,默认为0 ----@param y number|nil Y坐标分量,默认为0 ----@return foundation.math.Vector2 新创建的向量 -function Vector2.create(x, y) - ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("foundation_math_Vector2", x or 0, y or 0) -end - ----通过特定结构的对象创建一个新的二维向量 ----@param tbl table|foundation.math.Vector2 表或向量 ----@return foundation.math.Vector2 新创建的向量 -function Vector2.createFromTable(tbl) - if tbl.x and tbl.y then - return Vector2.create(tbl.x, tbl.y) - end - if tbl[1] and tbl[2] then - return Vector2.create(tbl[1], tbl[2]) - end - error("Invalid table format for Vector2 creation") -end - ----通过给定的弧度和长度创建一个新的二维向量 ----@param rad number 弧度 ----@param length number 向量长度 ----@return foundation.math.Vector2 新创建的向量 ----@overload fun(rad: number): foundation.math.Vector2 -function Vector2.createFromRad(rad, length) - length = length or 1 - local x = length * math.cos(rad) - local y = length * math.sin(rad) - return Vector2.create(x, y) -end - ----通过给定的角度和长度创建一个新的二维向量 ----@param angle number 角度 ----@param length number 向量长度 ----@return foundation.math.Vector2 新创建的向量 ----@overload fun(angle: number): foundation.math.Vector2 -function Vector2.createFromAngle(angle, length) - local rad = math.rad(angle) - return Vector2.createFromRad(rad, length) -end - ----向量加法运算符重载 ----@param a foundation.math.Vector2|number 第一个操作数 ----@param b foundation.math.Vector2|number 第二个操作数 ----@return foundation.math.Vector2 相加后的结果 -function Vector2.__add(a, b) - if type(a) == "number" then - return Vector2.create(a + b.x, a + b.y) - elseif type(b) == "number" then - return Vector2.create(a.x + b, a.y + b) - else - return Vector2.create(a.x + b.x, a.y + b.y) - end -end - ----向量减法运算符重载 ----@param a foundation.math.Vector2|number 第一个操作数 ----@param b foundation.math.Vector2|number 第二个操作数 ----@return foundation.math.Vector2 相减后的结果 -function Vector2.__sub(a, b) - if type(a) == "number" then - return Vector2.create(a - b.x, a - b.y) - elseif type(b) == "number" then - return Vector2.create(a.x - b, a.y - b) - else - return Vector2.create(a.x - b.x, a.y - b.y) - end -end - ----向量乘法运算符重载 ----@param a foundation.math.Vector2|number 第一个操作数 ----@param b foundation.math.Vector2|number 第二个操作数 ----@return foundation.math.Vector2 相乘后的结果 -function Vector2.__mul(a, b) - if type(a) == "number" then - return Vector2.create(a * b.x, a * b.y) - elseif type(b) == "number" then - return Vector2.create(a.x * b, a.y * b) - else - return Vector2.create(a.x * b.x, a.y * b.y) - end -end - ----向量除法运算符重载 ----@param a foundation.math.Vector2|number 第一个操作数 ----@param b foundation.math.Vector2|number 第二个操作数 ----@return foundation.math.Vector2 相除后的结果 -function Vector2.__div(a, b) - if type(a) == "number" then - return Vector2.create(a / b.x, a / b.y) - elseif type(b) == "number" then - return Vector2.create(a.x / b, a.y / b) - else - return Vector2.create(a.x / b.x, a.y / b.y) - end -end - ----向量取负运算符重载 ----@param v foundation.math.Vector2 操作数 ----@return foundation.math.Vector2 取反后的向量 -function Vector2.__unm(v) - return Vector2.create(-v.x, -v.y) -end - ----向量相等性比较运算符重载 ----@param a foundation.math.Vector2 第一个操作数 ----@param b foundation.math.Vector2 第二个操作数 ----@return boolean 两个向量是否相等 -function Vector2.__eq(a, b) - return math.abs(a.x - b.x) <= 1e-10 and math.abs(a.y - b.y) <= 1e-10 -end - ----向量字符串表示 ----@param v foundation.math.Vector2 操作数 ----@return string 向量的字符串表示 -function Vector2.__tostring(v) - return string.format("Vector2(%f, %f)", v.x, v.y) -end - ----获取向量长度 ----@param v foundation.math.Vector2 操作数 ----@return number 向量的长度 -function Vector2.__len(v) - return math.sqrt(v.x * v.x + v.y * v.y) -end - -Vector2.length = Vector2.__len - ----获取向量的副本 ----@return foundation.math.Vector2 向量的副本 -function Vector2:clone() - return Vector2.create(self.x, self.y) -end - ----获取向量的角度(弧度) ----@return number 向量的角度,单位为弧度 -function Vector2:angle() - return math.atan2(self.y, self.x) -end - ----获取向量的角度(度) ----@return number 向量的角度,单位为度 -function Vector2:degreeAngle() - return math.deg(self:angle()) -end - ----计算两个向量的点积 ----@param other foundation.math.Vector2 另一个向量 ----@return number 两个向量的点积 -function Vector2:dot(other) - return self.x * other.x + self.y * other.y -end - ----计算两个向量的叉积 ----@param other foundation.math.Vector2 另一个向量 ----@return number 两个向量的叉积 -function Vector2:cross(other) - return self.x * other.y - self.y * other.x -end - ----将当前向量归一化(更改当前向量) ----@return foundation.math.Vector2 归一化后的向量(自身引用) -function Vector2:normalize() - local len = self:length() - if len > 1e-10 then - self.x = self.x / len - self.y = self.y / len - else - self.x = 0 - self.y = 0 - end - return self -end - ----获取向量的归一化副本 ----@return foundation.math.Vector2 归一化后的向量副本 -function Vector2:normalized() - local len = self:length() - if len <= 1e-10 then - return Vector2.zero() - end - return Vector2.create(self.x / len, self.y / len) -end - ----将当前向量旋转指定弧度(更改当前向量) ----@param rad number 旋转弧度 ----@return foundation.math.Vector2 旋转后的向量(自身引用) -function Vector2:rotate(rad) - local cos = math.cos(rad) - local sin = math.sin(rad) - local x = self.x * cos - self.y * sin - local y = self.x * sin + self.y * cos - self.x = x - self.y = y - return self -end - ----将向量旋转指定角度(更改当前向量) ----@param angle number 旋转角度 ----@return foundation.math.Vector2 旋转后的向量(自身引用) -function Vector2:degreeRotate(angle) - angle = math.rad(angle) - return self:rotate(angle) -end - ----获取向量的旋转指定弧度的副本 ----@param rad number 旋转弧度 ----@return foundation.math.Vector2 旋转后的向量副本 -function Vector2:rotated(rad) - local cos = math.cos(rad) - local sin = math.sin(rad) - local x = self.x * cos - self.y * sin - local y = self.x * sin + self.y * cos - return Vector2.create(x, y) -end - ----获取向量的旋转指定角度的副本 ----@param angle number 旋转角度 ----@return foundation.math.Vector2 旋转后的向量副本 -function Vector2:degreeRotated(angle) - angle = math.rad(angle) - return self:rotated(angle) -end - ----将Vector2转换为Vector3 ----@param z number|nil Z坐标分量,默认为0 ----@return foundation.math.Vector3 转换后的Vector3 -function Vector2:toVector3(z) - Vector3 = Vector3 or require("foundation.math.Vector3") - return Vector3.create(self.x, self.y, z or 0) -end - ----将Vector2转换为Vector4 ----@param z number|nil Z坐标分量,默认为0 ----@param w number|nil W坐标分量,默认为0 ----@return foundation.math.Vector4 转换后的Vector4 -function Vector2:toVector4(z, w) - Vector4 = Vector4 or require("foundation.math.Vector4") - return Vector4.create(self.x, self.y, z or 0, w or 0) -end - ---region LuaSTG Evo API -do - Vector2.LuaSTG = Vector2.length - Vector2.Angle = Vector2.degreeAngle - - ---归一化向量(LuaSTG Evo 兼容) - ---@return foundation.math.Vector2 归一化后的向量副本 - function Vector2:Normalize() - self:normalize() - return Vector2.create(self.x, self.y) - end - - Vector2.Normalized = Vector2.normalized - Vector2.Dot = Vector2.dot - - local lstg = require("lstg") - lstg.Vector2 = Vector2.create -end ---endregion - -ffi.metatype("foundation_math_Vector2", Vector2) - -return Vector2 \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua deleted file mode 100644 index d2d619dd..00000000 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector3.lua +++ /dev/null @@ -1,380 +0,0 @@ -local ffi = require("ffi") - -local type = type -local string = string -local math = math -local require = require - -local Vector2, Vector4 - -ffi.cdef [[ -typedef struct { - double x; - double y; - double z; -} foundation_math_Vector3; -]] - ----@class foundation.math.Vector3 ----@field x number X坐标分量 ----@field y number Y坐标分量 ----@field z number Z坐标分量 -local Vector3 = {} -Vector3.__index = Vector3 -Vector3.__type = "foundation.math.Vector3" - ----创建一个零向量 ----@return foundation.math.Vector3 零向量 -function Vector3.zero() - return Vector3.create(0, 0, 0) -end - ----创建一个新的三维向量 ----@param x number|nil X坐标分量,默认为0 ----@param y number|nil Y坐标分量,默认为0 ----@param z number|nil Z坐标分量,默认为0 ----@return foundation.math.Vector3 新创建的向量 -function Vector3.create(x, y, z) - ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("foundation_math_Vector3", x or 0, y or 0, z or 0) -end - ----通过特定结构的对象创建一个新的三维向量 ----@param tbl table|foundation.math.Vector3 表或向量 ----@return foundation.math.Vector3 新创建的向量 -function Vector3.createFromTable(tbl) - if tbl.x and tbl.y and tbl.z then - return Vector3.create(tbl.x, tbl.y, tbl.z) - end - if tbl[1] and tbl[2] and tbl[3] then - return Vector3.create(tbl[1], tbl[2], tbl[3]) - end - error("Invalid table format for Vector2 creation") -end - ----向量加法运算符重载 ----@param a foundation.math.Vector3|number 第一个操作数 ----@param b foundation.math.Vector3|number 第二个操作数 ----@return foundation.math.Vector3 相加后的结果 -function Vector3.__add(a, b) - if type(a) == "number" then - return Vector3.create(a + b.x, a + b.y, a + b.z) - elseif type(b) == "number" then - return Vector3.create(a.x + b, a.y + b, a.z + b) - else - return Vector3.create(a.x + b.x, a.y + b.y, a.z + b.z) - end -end - ----向量减法运算符重载 ----@param a foundation.math.Vector3|number 第一个操作数 ----@param b foundation.math.Vector3|number 第二个操作数 ----@return foundation.math.Vector3 相减后的结果 -function Vector3.__sub(a, b) - if type(a) == "number" then - return Vector3.create(a - b.x, a - b.y, a - b.z) - elseif type(b) == "number" then - return Vector3.create(a.x - b, a.y - b, a.z - b) - else - return Vector3.create(a.x - b.x, a.y - b.y, a.z - b.z) - end -end - ----向量乘法运算符重载 ----@param a foundation.math.Vector3|number 第一个操作数 ----@param b foundation.math.Vector3|number 第二个操作数 ----@return foundation.math.Vector3 相乘后的结果 -function Vector3.__mul(a, b) - if type(a) == "number" then - return Vector3.create(a * b.x, a * b.y, a * b.z) - elseif type(b) == "number" then - return Vector3.create(a.x * b, a.y * b, a.z * b) - else - return Vector3.create(a.x * b.x, a.y * b.y, a.z * b.z) - end -end - ----向量除法运算符重载 ----@param a foundation.math.Vector3|number 第一个操作数 ----@param b foundation.math.Vector3|number 第二个操作数 ----@return foundation.math.Vector3 相除后的结果 -function Vector3.__div(a, b) - if type(a) == "number" then - return Vector3.create(a / b.x, a / b.y, a / b.z) - elseif type(b) == "number" then - return Vector3.create(a.x / b, a.y / b, a.z / b) - else - return Vector3.create(a.x / b.x, a.y / b.y, a.z / b.z) - end -end - ----向量取负运算符重载 ----@param v foundation.math.Vector3 操作数 ----@return foundation.math.Vector3 取反后的向量 -function Vector3.__unm(v) - return Vector3.create(-v.x, -v.y, -v.z) -end - ----向量相等性比较运算符重载 ----@param a foundation.math.Vector3 第一个操作数 ----@param b foundation.math.Vector3 第二个操作数 ----@return boolean 两个向量是否相等 -function Vector3.__eq(a, b) - return math.abs(a.x - b.x) < 1e-10 and - math.abs(a.y - b.y) < 1e-10 and - math.abs(a.z - b.z) < 1e-10 -end - ----向量字符串表示 ----@param v foundation.math.Vector3 操作数 ----@return string 向量的字符串表示 -function Vector3.__tostring(v) - return string.format("Vector3(%f, %f, %f)", v.x, v.y, v.z) -end - ----获取向量长度 ----@param v foundation.math.Vector3 操作数 ----@return number 向量的长度 -function Vector3.__len(v) - return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) -end -Vector3.length = Vector3.__len - ----获取向量的副本 ----@return foundation.math.Vector3 向量的副本 -function Vector3:clone() - return Vector3.create(self.x, self.y, self.z) -end - ----将Vector3转换为Vector2 ----@return foundation.math.Vector2 转换后的Vector2 -function Vector3:toVector2() - Vector2 = Vector2 or require("foundation.math.Vector2") - return Vector2.create(self.x, self.y) -end - ----将Vector3转换为Vector4 ----@param w number|nil W坐标分量,默认为0 ----@return foundation.math.Vector4 转换后的Vector4 -function Vector3:toVector4(w) - Vector4 = Vector4 or require("foundation.math.Vector4") - return Vector4.create(self.x, self.y, self.z, w or 0) -end - ----计算两个向量的点积 ----@param other foundation.math.Vector3 另一个向量 ----@return number 两个向量的点积 -function Vector3:dot(other) - return self.x * other.x + self.y * other.y + self.z * other.z -end - ----计算两个向量的叉积 ----@param other foundation.math.Vector3 另一个向量 ----@return foundation.math.Vector3 两个向量的叉积 -function Vector3:cross(other) - return Vector3.create( - self.y * other.z - self.z * other.y, - self.z * other.x - self.x * other.z, - self.x * other.y - self.y * other.x - ) -end - ----获取向量的球坐标角度(弧度) ----@return number,number 向量的极角和方位角,单位为弧度 -function Vector3:sphericalAngle() - local len = self:length() - if len < 1e-10 then - return 0, 0 - end - local theta = math.acos(self.z / len) - local phi = math.atan2(self.y, self.x) - return theta, phi -end - ----获取向量的球坐标角度(度) ----@return number,number 向量的极角和方位角,单位为度 -function Vector3:sphericalDegreeAngle() - local theta, phi = self:sphericalAngle() - return math.deg(theta), math.deg(phi) -end - ----将当前向量归一化(更改当前向量) ----@return foundation.math.Vector3 归一化后的向量(自身引用) -function Vector3:normalize() - local len = self:length() - if len > 1e-10 then - self.x = self.x / len - self.y = self.y / len - self.z = self.z / len - else - self.x, self.y, self.z = 0, 0, 0 - end - return self -end - ----获取向量的归一化副本 ----@return foundation.math.Vector3 归一化后的向量副本 -function Vector3:normalized() - local len = self:length() - if len <= 1e-10 then - return Vector3.zero() - end - return Vector3.create(self.x / len, self.y / len, self.z / len) -end - ----将当前向量围绕任意轴旋转指定弧度(更改当前向量) ----@param axis foundation.math.Vector3 旋转轴(应为单位向量) ----@param rad number 旋转弧度 ----@return foundation.math.Vector3 旋转后的向量(自身引用) -function Vector3:rotate(axis, rad) - axis = axis:normalized() - local c = math.cos(rad) - local s = math.sin(rad) - local k = 1 - c - - local nx = self.x * (c + axis.x * axis.x * k) + - self.y * (axis.x * axis.y * k - axis.z * s) + - self.z * (axis.x * axis.z * k + axis.y * s) - - local ny = self.x * (axis.y * axis.x * k + axis.z * s) + - self.y * (c + axis.y * axis.y * k) + - self.z * (axis.y * axis.z * k - axis.x * s) - - local nz = self.x * (axis.z * axis.x * k - axis.y * s) + - self.y * (axis.z * axis.y * k + axis.x * s) + - self.z * (c + axis.z * axis.z * k) - - self.x, self.y, self.z = nx, ny, nz - return self -end - ----获取向量围绕任意轴旋转指定弧度后的副本 ----@param axis foundation.math.Vector3 旋转轴(应为单位向量) ----@param rad number 旋转弧度 ----@return foundation.math.Vector3 旋转后的向量副本 -function Vector3:rotated(axis, rad) - local result = self:clone() - return result:rotate(axis, rad) -end - ----将向量围绕任意轴旋转指定角度(更改当前向量) ----@param axis foundation.math.Vector3 旋转轴(应为单位向量) ----@param angle number 旋转角度(度) ----@return foundation.math.Vector3 旋转后的向量(自身引用) -function Vector3:degreeRotate(axis, angle) - return self:rotate(axis, math.rad(angle)) -end - ----获取向量围绕任意轴旋转指定角度后的副本 ----@param axis foundation.math.Vector3 旋转轴(应为单位向量) ----@param angle number 旋转角度(度) ----@return foundation.math.Vector3 旋转后的向量副本 -function Vector3:degreeRotated(axis, angle) - return self:rotated(axis, math.rad(angle)) -end - ----将向量围绕X轴旋转指定弧度(更改当前向量) ----@param rad number 旋转弧度 ----@return foundation.math.Vector3 旋转后的向量(自身引用) -function Vector3:rotateX(rad) - local c = math.cos(rad) - local s = math.sin(rad) - local y = self.y * c - self.z * s - local z = self.y * s + self.z * c - self.y, self.z = y, z - return self -end - ----获取向量围绕X轴旋转指定弧度后的副本 ----@param rad number 旋转弧度 ----@return foundation.math.Vector3 旋转后的向量副本 -function Vector3:rotatedX(rad) - local result = self:clone() - return result:rotateX(rad) -end - ----将向量围绕Y轴旋转指定弧度(更改当前向量) ----@param rad number 旋转弧度 ----@return foundation.math.Vector3 旋转后的向量(自身引用) -function Vector3:rotateY(rad) - local c = math.cos(rad) - local s = math.sin(rad) - local x = self.x * c + self.z * s - local z = -self.x * s + self.z * c - self.x, self.z = x, z - return self -end - ----获取向量围绕Y轴旋转指定弧度后的副本 ----@param rad number 旋转弧度 ----@return foundation.math.Vector3 旋转后的向量副本 -function Vector3:rotatedY(rad) - local result = self:clone() - return result:rotateY(rad) -end - ----将向量围绕Z轴旋转指定弧度(更改当前向量) ----@param rad number 旋转弧度 ----@return foundation.math.Vector3 旋转后的向量(自身引用) -function Vector3:rotateZ(rad) - local c = math.cos(rad) - local s = math.sin(rad) - local x = self.x * c - self.y * s - local y = self.x * s + self.y * c - self.x, self.y = x, y - return self -end - ----获取向量围绕Z轴旋转指定弧度后的副本 ----@param rad number 旋转弧度 ----@return foundation.math.Vector3 旋转后的向量副本 -function Vector3:rotatedZ(rad) - local result = self:clone() - return result:rotateZ(rad) -end - ----将向量围绕X轴旋转指定角度(更改当前向量) ----@param angle number 旋转角度(度) ----@return foundation.math.Vector3 旋转后的向量(自身引用) -function Vector3:degreeRotateX(angle) - return self:rotateX(math.rad(angle)) -end - ----获取向量围绕X轴旋转指定角度后的副本 ----@param angle number 旋转角度(度) ----@return foundation.math.Vector3 旋转后的向量副本 -function Vector3:degreeRotatedX(angle) - return self:rotatedX(math.rad(angle)) -end - ----将向量围绕Y轴旋转指定角度(更改当前向量) ----@param angle number 旋转角度(度) ----@return foundation.math.Vector3 旋转后的向量(自身引用) -function Vector3:degreeRotateY(angle) - return self:rotateY(math.rad(angle)) -end - ----获取向量围绕Y轴旋转指定角度后的副本 ----@param angle number 旋转角度(度) ----@return foundation.math.Vector3 旋转后的向量副本 -function Vector3:degreeRotatedY(angle) - return self:rotatedY(math.rad(angle)) -end - ----将向量围绕Z轴旋转指定角度(更改当前向量) ----@param angle number 旋转角度(度) ----@return foundation.math.Vector3 旋转后的向量(自身引用) -function Vector3:degreeRotateZ(angle) - return self:rotateZ(math.rad(angle)) -end - ----获取向量围绕Z轴旋转指定角度后的副本 ----@param angle number 旋转角度(度) ----@return foundation.math.Vector3 旋转后的向量副本 -function Vector3:degreeRotatedZ(angle) - return self:rotatedZ(math.rad(angle)) -end - -ffi.metatype("foundation_math_Vector3", Vector3) - -return Vector3 \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua b/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua deleted file mode 100644 index 06a441bb..00000000 --- a/game/packages/thlib-scripts-v2/foundation/math/Vector4.lua +++ /dev/null @@ -1,358 +0,0 @@ -local ffi = require("ffi") - -local type = type -local string = string -local math = math -local require = require - -local Vector2, Vector3 - -ffi.cdef [[ -typedef struct { - double x; - double y; - double z; - double w; -} foundation_math_Vector4; -]] - ----@class foundation.math.Vector4 ----@field x number X坐标分量 ----@field y number Y坐标分量 ----@field z number Z坐标分量 ----@field w number W坐标分量 -local Vector4 = {} -Vector4.__index = Vector4 -Vector4.__type = "foundation.math.Vector4" - ----创建一个零向量 ----@return foundation.math.Vector4 零向量 -function Vector4.zero() - return Vector4.create(0, 0, 0, 0) -end - ----创建一个新的四维向量 ----@param x number|nil X坐标分量,默认为0 ----@param y number|nil Y坐标分量,默认为0 ----@param z number|nil Z坐标分量,默认为0 ----@param w number|nil W坐标分量,默认为0 ----@return foundation.math.Vector4 新创建的向量 -function Vector4.create(x, y, z, w) - ---@diagnostic disable-next-line: return-type-mismatch - return ffi.new("foundation_math_Vector4", x or 0, y or 0, z or 0, w or 0) -end - ----通过特定结构的对象创建一个新的四维向量 ----@param tbl table|foundation.math.Vector4 表或向量 ----@return foundation.math.Vector4 新创建的向量 -function Vector4.createFromTable(tbl) - if tbl.x and tbl.y and tbl.z and tbl.w then - return Vector4.create(tbl.x, tbl.y, tbl.z, tbl.w) - end - if tbl[1] and tbl[2] and tbl[3] and tbl[4] then - return Vector4.create(tbl[1], tbl[2], tbl[3], tbl[4]) - end - error("Invalid table format for Vector4 creation") -end - ----向量加法运算符重载 ----@param a foundation.math.Vector4|number 第一个操作数 ----@param b foundation.math.Vector4|number 第二个操作数 ----@return foundation.math.Vector4 相加后的结果 -function Vector4.__add(a, b) - if type(a) == "number" then - return Vector4.create(a + b.x, a + b.y, a + b.z, a + b.w) - elseif type(b) == "number" then - return Vector4.create(a.x + b, a.y + b, a.z + b, a.w + b) - else - return Vector4.create(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w) - end -end - ----向量减法运算符重载 ----@param a foundation.math.Vector4|number 第一个操作数 ----@param b foundation.math.Vector4|number 第二个操作数 ----@return foundation.math.Vector4 相减后的结果 -function Vector4.__sub(a, b) - if type(a) == "number" then - return Vector4.create(a - b.x, a - b.y, a - b.z, a - b.w) - elseif type(b) == "number" then - return Vector4.create(a.x - b, a.y - b, a.z - b, a.w - b) - else - return Vector4.create(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w) - end -end - ----向量乘法运算符重载 ----@param a foundation.math.Vector4|number 第一个操作数 ----@param b foundation.math.Vector4|number 第二个操作数 ----@return foundation.math.Vector4 相乘后的结果 -function Vector4.__mul(a, b) - if type(a) == "number" then - return Vector4.create(a * b.x, a * b.y, a * b.z, a * b.w) - elseif type(b) == "number" then - return Vector4.create(a.x * b, a.y * b, a.z * b, a.w * b) - else - return Vector4.create(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w) - end -end - ----向量除法运算符重载 ----@param a foundation.math.Vector4|number 第一个操作数 ----@param b foundation.math.Vector4|number 第二个操作数 ----@return foundation.math.Vector4 相除后的结果 -function Vector4.__div(a, b) - if type(a) == "number" then - return Vector4.create(a / b.x, a / b.y, a / b.z, a / b.w) - elseif type(b) == "number" then - return Vector4.create(a.x / b, a.y / b, a.z / b, a.w / b) - else - return Vector4.create(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w) - end -end - ----向量取负运算符重载 ----@param v foundation.math.Vector4 操作数 ----@return foundation.math.Vector4 取反后的向量 -function Vector4.__unm(v) - return Vector4.create(-v.x, -v.y, -v.z, -v.w) -end - ----向量相等性比较运算符重载 ----@param a foundation.math.Vector4 第一个操作数 ----@param b foundation.math.Vector4 第二个操作数 ----@return boolean 两个向量是否相等 -function Vector4.__eq(a, b) - return math.abs(a.x - b.x) <= 1e-10 and - math.abs(a.y - b.y) <= 1e-10 and - math.abs(a.z - b.z) <= 1e-10 and - math.abs(a.w - b.w) <= 1e-10 -end - ----向量字符串表示 ----@param v foundation.math.Vector4 操作数 ----@return string 向量的字符串表示 -function Vector4.__tostring(v) - return string.format("Vector4(%f, %f, %f, %f)", v.x, v.y, v.z, v.w) -end - ----获取向量长度 ----@param v foundation.math.Vector4 操作数 ----@return number 向量的长度 -function Vector4.__len(v) - return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w) -end -Vector4.length = Vector4.__len - ----获取向量的副本 ----@return foundation.math.Vector4 向量的副本 -function Vector4:clone() - return Vector4.create(self.x, self.y, self.z, self.w) -end - ----将Vector4转换为Vector2 ----@return foundation.math.Vector2 转换后的Vector2 -function Vector4:toVector2() - Vector2 = Vector2 or require("foundation.math.Vector2") - return Vector2.create(self.x, self.y) -end - ----将Vector4转换为Vector3 ----@return foundation.math.Vector3 转换后的Vector3 -function Vector4:toVector3() - Vector3 = Vector3 or require("foundation.math.Vector3") - return Vector3.create(self.x, self.y, self.z) -end - ----计算两个向量的点积 ----@param other foundation.math.Vector4 另一个向量 ----@return number 两个向量的点积 -function Vector4:dot(other) - return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w -end - ----将当前向量归一化(更改当前向量) ----@return foundation.math.Vector4 归一化后的向量(自身引用) -function Vector4:normalize() - local len = self:length() - if len > 1e-10 then - self.x = self.x / len - self.y = self.y / len - self.z = self.z / len - self.w = self.w / len - else - self.x, self.y, self.z, self.w = 0, 0, 0, 0 - end - return self -end - ----获取向量的归一化副本 ----@return foundation.math.Vector4 归一化后的向量副本 -function Vector4:normalized() - local len = self:length() - if len <= 1e-10 then - return Vector4.zero() - end - return Vector4.create(self.x / len, self.y / len, self.z / len, self.w / len) -end - ----获取向量的齐次坐标(将w分量归一化为1) ----@return foundation.math.Vector4 归一化后的齐次坐标向量 -function Vector4:homogeneous() - if math.abs(self.w) <= 1e-10 then - return self:clone() - end - return Vector4.create(self.x / self.w, self.y / self.w, self.z / self.w, 1) -end - ----获取向量的投影坐标(将w分量归一化为1后返回一个Vector3) ----@return foundation.math.Vector3 投影后的三维向量 -function Vector4:projectTo3D() - Vector3 = Vector3 or require("foundation.math.Vector3") - if math.abs(self.w) <= 1e-10 then - return Vector3.create(self.x, self.y, self.z) - end - return Vector3.create(self.x / self.w, self.y / self.w, self.z / self.w) -end - ----将当前向量的三维部分围绕任意轴旋转指定弧度(更改当前向量) ----@param axis foundation.math.Vector3 旋转轴(应为单位向量) ----@param rad number 旋转弧度 ----@return foundation.math.Vector4 旋转后的向量(自身引用) -function Vector4:rotate(axis, rad) - local vec3 = self:toVector3() - vec3:rotate(axis, rad) - self.x, self.y, self.z = vec3.x, vec3.y, vec3.z - return self -end - ----获取向量的三维部分围绕任意轴旋转指定弧度后的副本 ----@param axis foundation.math.Vector3 旋转轴(应为单位向量) ----@param rad number 旋转弧度 ----@return foundation.math.Vector4 旋转后的向量副本 -function Vector4:rotated(axis, rad) - local result = self:clone() - return result:rotate(axis, rad) -end - ----将向量的三维部分围绕任意轴旋转指定角度(更改当前向量) ----@param axis foundation.math.Vector3 旋转轴(应为单位向量) ----@param angle number 旋转角度(度) ----@return foundation.math.Vector4 旋转后的向量(自身引用) -function Vector4:degreeRotate(axis, angle) - return self:rotate(axis, math.rad(angle)) -end - ----获取向量的三维部分围绕任意轴旋转指定角度后的副本 ----@param axis foundation.math.Vector3 旋转轴(应为单位向量) ----@param angle number 旋转角度(度) ----@return foundation.math.Vector4 旋转后的向量副本 -function Vector4:degreeRotated(axis, angle) - return self:rotated(axis, math.rad(angle)) -end - ----将向量的三维部分围绕X轴旋转指定弧度(更改当前向量) ----@param rad number 旋转弧度 ----@return foundation.math.Vector4 旋转后的向量(自身引用) -function Vector4:rotateX(rad) - local c = math.cos(rad) - local s = math.sin(rad) - local y = self.y * c - self.z * s - local z = self.y * s + self.z * c - self.y, self.z = y, z - return self -end - ----获取向量的三维部分围绕X轴旋转指定弧度后的副本 ----@param rad number 旋转弧度 ----@return foundation.math.Vector4 旋转后的向量副本 -function Vector4:rotatedX(rad) - local result = self:clone() - return result:rotateX(rad) -end - ----将向量的三维部分围绕Y轴旋转指定弧度(更改当前向量) ----@param rad number 旋转弧度 ----@return foundation.math.Vector4 旋转后的向量(自身引用) -function Vector4:rotateY(rad) - local c = math.cos(rad) - local s = math.sin(rad) - local x = self.x * c + self.z * s - local z = -self.x * s + self.z * c - self.x, self.z = x, z - return self -end - ----获取向量的三维部分围绕Y轴旋转指定弧度后的副本 ----@param rad number 旋转弧度 ----@return foundation.math.Vector4 旋转后的向量副本 -function Vector4:rotatedY(rad) - local result = self:clone() - return result:rotateY(rad) -end - ----将向量的三维部分围绕Z轴旋转指定弧度(更改当前向量) ----@param rad number 旋转弧度 ----@return foundation.math.Vector4 旋转后的向量(自身引用) -function Vector4:rotateZ(rad) - local c = math.cos(rad) - local s = math.sin(rad) - local x = self.x * c - self.y * s - local y = self.x * s + self.y * c - self.x, self.y = x, y - return self -end - ----获取向量的三维部分围绕Z轴旋转指定弧度后的副本 ----@param rad number 旋转弧度 ----@return foundation.math.Vector4 旋转后的向量副本 -function Vector4:rotatedZ(rad) - local result = self:clone() - return result:rotateZ(rad) -end - ----将向量的三维部分围绕X轴旋转指定角度(更改当前向量) ----@param angle number 旋转角度(度) ----@return foundation.math.Vector4 旋转后的向量(自身引用) -function Vector4:degreeRotateX(angle) - return self:rotateX(math.rad(angle)) -end - ----获取向量的三维部分围绕X轴旋转指定角度后的副本 ----@param angle number 旋转角度(度) ----@return foundation.math.Vector4 旋转后的向量副本 -function Vector4:degreeRotatedX(angle) - return self:rotatedX(math.rad(angle)) -end - ----将向量的三维部分围绕Y轴旋转指定角度(更改当前向量) ----@param angle number 旋转角度(度) ----@return foundation.math.Vector4 旋转后的向量(自身引用) -function Vector4:degreeRotateY(angle) - return self:rotateY(math.rad(angle)) -end - ----获取向量的三维部分围绕Y轴旋转指定角度后的副本 ----@param angle number 旋转角度(度) ----@return foundation.math.Vector4 旋转后的向量副本 -function Vector4:degreeRotatedY(angle) - return self:rotatedY(math.rad(angle)) -end - ----将向量的三维部分围绕Z轴旋转指定角度(更改当前向量) ----@param angle number 旋转角度(度) ----@return foundation.math.Vector4 旋转后的向量(自身引用) -function Vector4:degreeRotateZ(angle) - return self:rotateZ(math.rad(angle)) -end - ----获取向量的三维部分围绕Z轴旋转指定角度后的副本 ----@param angle number 旋转角度(度) ----@return foundation.math.Vector4 旋转后的向量副本 -function Vector4:degreeRotatedZ(angle) - return self:rotatedZ(math.rad(angle)) -end - -ffi.metatype("foundation_math_Vector4", Vector4) - -return Vector4 \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/matrix/Matrix.lua b/game/packages/thlib-scripts-v2/foundation/math/matrix/Matrix.lua deleted file mode 100644 index 8b8795bb..00000000 --- a/game/packages/thlib-scripts-v2/foundation/math/matrix/Matrix.lua +++ /dev/null @@ -1,664 +0,0 @@ -local ffi = require("ffi") - -local type = type -local error = error -local rawset = rawset -local setmetatable = setmetatable -local math = math -local table = table -local string = string - -ffi.cdef [[ -typedef struct { - double* data; - int rows; - int cols; -} foundation_math_Matrix; -]] - ----@class foundation.math.Matrix ----@field data userdata 矩阵数据指针 ----@field rows number 矩阵行数 ----@field cols number 矩阵列数 -local Matrix = {} -Matrix.__type = "foundation.math.Matrix" - ----创建一个新的矩阵 ----@param rows number 行数 ----@param cols number 列数 ----@param initialValue number|nil 初始值,默认为0 ----@return foundation.math.Matrix 新创建的矩阵 -function Matrix.create(rows, cols, initialValue) - if rows <= 0 or cols <= 0 then - error("Matrix dimensions must be positive") - end - - initialValue = initialValue or 0 - - local matrix_data = ffi.new("foundation_math_Matrix") - matrix_data.rows = rows - matrix_data.cols = cols - matrix_data.data = ffi.new("double[?]", rows * cols) - - for i = 0, rows * cols - 1 do - matrix_data.data[i] = initialValue - end - - local matrix = { - __data = matrix_data, - __data_array_ref = matrix_data.data - } - - return setmetatable(matrix, Matrix) -end - ----@param self foundation.math.Matrix ----@param key any ----@return any -function Matrix.__index(self, key) - if key == "data" then - return self.__data.data - elseif key == "rows" then - return self.__data.rows - elseif key == "cols" then - return self.__data.cols - end - return Matrix[key] -end - ----@param self foundation.math.Matrix ----@param key any ----@param value any -function Matrix.__newindex(self, key, value) - if key == "data" then - error("Cannot modify data directly") - elseif key == "rows" or key == "cols" then - error("Cannot modify dimensions directly") - else - rawset(self, key, value) - end -end - ----获取指定位置的线性索引 ----@param i number 行索引 (1-based) ----@param j number 列索引 (1-based) ----@return number 线性索引 (0-based) -local function getLinearIndex(matrix, i, j) - return (i - 1) * matrix.cols + (j - 1) -end - ----创建一个方阵(行列数相等的矩阵) ----@param size number 矩阵大小 ----@param initialValue number|nil 初始值,默认为0 ----@return foundation.math.Matrix 新创建的方阵 -function Matrix.createSquare(size, initialValue) - return Matrix.create(size, size, initialValue) -end - ----创建一个单位矩阵 ----@param size number 矩阵大小 ----@return foundation.math.Matrix 新创建的单位矩阵 -function Matrix.identity(size) - local matrix = Matrix.create(size, size, 0) - for i = 1, size do - matrix:set(i, i, 1) - end - return matrix -end - ----从二维数组创建矩阵 ----@param arr table 二维数组 ----@return foundation.math.Matrix 新创建的矩阵 -function Matrix.fromArray(arr) - if not arr or #arr == 0 then - error("Input array cannot be empty") - end - - local rows = #arr - local cols = #arr[1] - - for i = 2, rows do - if #arr[i] ~= cols then - error("All rows must have the same number of columns") - end - end - - local matrix = Matrix.create(rows, cols) - for i = 1, rows do - for j = 1, cols do - matrix:set(i, j, arr[i][j]) - end - end - - return matrix -end - ----矩阵加法运算符重载 ----@param a foundation.math.Matrix|number 第一个操作数 ----@param b foundation.math.Matrix|number 第二个操作数 ----@return foundation.math.Matrix 相加后的结果 -function Matrix.__add(a, b) - if type(a) == "number" then - local result = Matrix.create(b.rows, b.cols) - for i = 1, b.rows do - for j = 1, b.cols do - result:set(i, j, a + b:get(i, j)) - end - end - return result - elseif type(b) == "number" then - local result = Matrix.create(a.rows, a.cols) - for i = 1, a.rows do - for j = 1, a.cols do - result:set(i, j, a:get(i, j) + b) - end - end - return result - else - if a.rows ~= b.rows or a.cols ~= b.cols then - error("Matrix dimensions must match for addition") - end - - local result = Matrix.create(a.rows, a.cols) - for i = 1, a.rows do - for j = 1, a.cols do - result:set(i, j, a:get(i, j) + b:get(i, j)) - end - end - return result - end -end - ----矩阵减法运算符重载 ----@param a foundation.math.Matrix|number 第一个操作数 ----@param b foundation.math.Matrix|number 第二个操作数 ----@return foundation.math.Matrix 相减后的结果 -function Matrix.__sub(a, b) - if type(a) == "number" then - local result = Matrix.create(b.rows, b.cols) - for i = 1, b.rows do - for j = 1, b.cols do - result:set(i, j, a - b:get(i, j)) - end - end - return result - elseif type(b) == "number" then - local result = Matrix.create(a.rows, a.cols) - for i = 1, a.rows do - for j = 1, a.cols do - result:set(i, j, a:get(i, j) - b) - end - end - return result - else - if a.rows ~= b.rows or a.cols ~= b.cols then - error("Matrix dimensions must match for subtraction") - end - - local result = Matrix.create(a.rows, a.cols) - for i = 1, a.rows do - for j = 1, a.cols do - result:set(i, j, a:get(i, j) - b:get(i, j)) - end - end - return result - end -end - ----矩阵乘法运算符重载 ----@param a foundation.math.Matrix|number 第一个操作数 ----@param b foundation.math.Matrix|number 第二个操作数 ----@return foundation.math.Matrix 相乘后的结果 -function Matrix.__mul(a, b) - if type(a) == "number" then - local result = Matrix.create(b.rows, b.cols) - for i = 1, b.rows do - for j = 1, b.cols do - result:set(i, j, a * b:get(i, j)) - end - end - return result - elseif type(b) == "number" then - local result = Matrix.create(a.rows, a.cols) - for i = 1, a.rows do - for j = 1, a.cols do - result:set(i, j, a:get(i, j) * b) - end - end - return result - else - if a.cols ~= b.rows then - error("Inner matrix dimensions must match for multiplication") - end - - local result = Matrix.create(a.rows, b.cols) - for i = 1, a.rows do - for j = 1, b.cols do - local sum = 0 - for k = 1, a.cols do - sum = sum + a:get(i, k) * b:get(k, j) - end - result:set(i, j, sum) - end - end - return result - end -end - ----矩阵取负运算符重载 ----@param m foundation.math.Matrix 操作数 ----@return foundation.math.Matrix 取反后的矩阵 -function Matrix.__unm(m) - local result = Matrix.create(m.rows, m.cols) - for i = 1, m.rows do - for j = 1, m.cols do - result:set(i, j, -m:get(i, j)) - end - end - return result -end - ----矩阵相等性比较运算符重载 ----@param a foundation.math.Matrix 第一个操作数 ----@param b foundation.math.Matrix 第二个操作数 ----@return boolean 两个矩阵是否相等 -function Matrix.__eq(a, b) - if a.rows ~= b.rows or a.cols ~= b.cols then - return false - end - - for i = 1, a.rows do - for j = 1, a.cols do - if math.abs(a:get(i, j) - b:get(i, j)) > 1e-10 then - return false - end - end - end - - return true -end - ----矩阵字符串表示 ----@param m foundation.math.Matrix 操作数 ----@return string 矩阵的字符串表示 -function Matrix.__tostring(m) - local lines = {} - for i = 1, m.rows do - local row = {} - for j = 1, m.cols do - row[j] = string.format("%f", m:get(i, j)) - end - lines[i] = "[" .. table.concat(row, ", ") .. "]" - end - return "[" .. table.concat(lines, "") .. "]" -end - ----获取矩阵元素值 ----@param i number 行索引 ----@param j number 列索引 ----@return number 指定位置的值 -function Matrix:get(i, j) - if i < 1 or i > self.rows or j < 1 or j > self.cols then - error("Matrix index out of bounds") - end - local idx = getLinearIndex(self, i, j) - return self.data[idx] -end - ----设置矩阵元素值 ----@param i number 行索引 ----@param j number 列索引 ----@param value number 要设置的值 -function Matrix:set(i, j, value) - if i < 1 or i > self.rows or j < 1 or j > self.cols then - error("Matrix index out of bounds") - end - local idx = getLinearIndex(self, i, j) - self.data[idx] = value -end - ----获取矩阵的副本 ----@return foundation.math.Matrix 矩阵的副本 -function Matrix:clone() - local result = Matrix.create(self.rows, self.cols) - for i = 1, self.rows do - for j = 1, self.cols do - result:set(i, j, self:get(i, j)) - end - end - return result -end - ----转置矩阵 ----@return foundation.math.Matrix 转置后的矩阵 -function Matrix:transpose() - local result = Matrix.create(self.cols, self.rows) - for i = 1, self.rows do - for j = 1, self.cols do - result:set(j, i, self:get(i, j)) - end - end - return result -end - ----计算矩阵的行列式(仅适用于方阵) ----@return number 行列式的值 -function Matrix:determinant() - if self.rows ~= self.cols then - error("Determinant can only be calculated for square matrices") - end - - local n = self.rows - if n == 1 then - return self:get(1, 1) - elseif n == 2 then - return self:get(1, 1) * self:get(2, 2) - self:get(1, 2) * self:get(2, 1) - else - local det = 0 - for j = 1, n do - det = det + self:cofactor(1, j) * self:get(1, j) - end - return det - end -end - ----计算矩阵元素的代数余子式 ----@param row number 行索引 ----@param col number 列索引 ----@return number 代数余子式的值 -function Matrix:cofactor(row, col) - local sign = ((row + col) % 2 == 0) and 1 or -1 - return sign * self:minor(row, col) -end - ----计算矩阵元素的余子式 ----@param row number 行索引 ----@param col number 列索引 ----@return number 余子式的值 -function Matrix:minor(row, col) - local subMatrix = self:subMatrix(row, col) - return subMatrix:determinant() -end - ----获取去掉指定行列的子矩阵 ----@param row number 要去掉的行索引 ----@param col number 要去掉的列索引 ----@return foundation.math.Matrix 子矩阵 -function Matrix:subMatrix(row, col) - local result = Matrix.create(self.rows - 1, self.cols - 1) - local r = 1 - for i = 1, self.rows do - if i ~= row then - local c = 1 - for j = 1, self.cols do - if j ~= col then - result:set(r, c, self:get(i, j)) - c = c + 1 - end - end - r = r + 1 - end - end - return result -end - ----计算矩阵的伴随矩阵 ----@return foundation.math.Matrix 伴随矩阵 -function Matrix:adjugate() - if self.rows ~= self.cols then - error("Adjugate can only be calculated for square matrices") - end - - local n = self.rows - local result = Matrix.create(n, n) - - for i = 1, n do - for j = 1, n do - result:set(j, i, self:cofactor(i, j)) - end - end - - return result -end - ----计算矩阵的逆(仅适用于方阵) ----@return foundation.math.Matrix|nil 逆矩阵,若不可逆则返回nil -function Matrix:inverse() - if self.rows ~= self.cols then - error("Inverse can only be calculated for square matrices") - end - - local det = self:determinant() - if math.abs(det) < 1e-10 then - return nil - end - - local adjugate = self:adjugate() - return adjugate * (1 / det) -end - ----获取矩阵的迹(仅适用于方阵) ----@return number 矩阵的迹 -function Matrix:trace() - if self.rows ~= self.cols then - error("Trace can only be calculated for square matrices") - end - - local sum = 0 - for i = 1, self.rows do - sum = sum + self:get(i, i) - end - - return sum -end - ----将矩阵扩充为增广矩阵 ----@param other foundation.math.Matrix 要扩充的矩阵 ----@return foundation.math.Matrix 扩充后的矩阵 -function Matrix:augment(other) - if self.rows ~= other.rows then - error("Matrices must have the same number of rows for augmentation") - end - - local result = Matrix.create(self.rows, self.cols + other.cols) - - for i = 1, self.rows do - for j = 1, self.cols do - result:set(i, j, self:get(i, j)) - end - - for j = 1, other.cols do - result:set(i, self.cols + j, other:get(i, j)) - end - end - - return result -end - ----获取矩阵的指定子矩阵 ----@param startRow number 起始行索引 ----@param endRow number 结束行索引 ----@param startCol number 起始列索引 ----@param endCol number 结束列索引 ----@return foundation.math.Matrix 子矩阵 -function Matrix:getSubMatrix(startRow, endRow, startCol, endCol) - if startRow < 1 or endRow > self.rows or startCol < 1 or endCol > self.cols then - error("Submatrix indices out of bounds") - end - - if startRow > endRow or startCol > endCol then - error("Invalid submatrix indices") - end - - local rows = endRow - startRow + 1 - local cols = endCol - startCol + 1 - local result = Matrix.create(rows, cols) - - for i = 1, rows do - for j = 1, cols do - result:set(i, j, self:get(startRow + i - 1, startCol + j - 1)) - end - end - - return result -end - ----高斯消元法求解线性方程组 Ax = b ----@param b foundation.math.Matrix 常数向量 ----@return foundation.math.Matrix|nil 解向量,若无解则返回nil -function Matrix:solve(b) - if self.rows ~= self.cols then - error("Coefficient matrix must be square") - end - - if self.rows ~= b.rows or b.cols ~= 1 then - error("Dimensions of right-hand side vector do not match") - end - - local augmented = self:augment(b) - local n = self.rows - - for i = 1, n do - local maxVal = math.abs(augmented:get(i, i)) - local maxRow = i - - for k = i + 1, n do - if math.abs(augmented:get(k, i)) > maxVal then - maxVal = math.abs(augmented:get(k, i)) - maxRow = k - end - end - - if maxVal < 1e-10 then - return nil - end - - if maxRow ~= i then - for j = i, n + 1 do - local temp = augmented:get(i, j) - augmented:set(i, j, augmented:get(maxRow, j)) - augmented:set(maxRow, j, temp) - end - end - - for k = i + 1, n do - local factor = augmented:get(k, i) / augmented:get(i, i) - augmented:set(k, i, 0) - - for j = i + 1, n + 1 do - augmented:set(k, j, augmented:get(k, j) - factor * augmented:get(i, j)) - end - end - end - - local solution = Matrix.create(n, 1) - for i = n, 1, -1 do - local sum = 0 - for j = i + 1, n do - sum = sum + augmented:get(i, j) * solution:get(j, 1) - end - solution:set(i, 1, (augmented:get(i, n + 1) - sum) / augmented:get(i, i)) - end - - return solution -end - ----计算两个矩阵的哈达玛积(逐元素乘积) ----@param other foundation.math.Matrix 另一个矩阵 ----@return foundation.math.Matrix 哈达玛积结果 -function Matrix:hadamard(other) - if self.rows ~= other.rows or self.cols ~= other.cols then - error("Matrix dimensions must match for Hadamard product") - end - - local result = Matrix.create(self.rows, self.cols) - for i = 1, self.rows do - for j = 1, self.cols do - result:set(i, j, self:get(i, j) * other:get(i, j)) - end - end - - return result -end - ----计算矩阵的Frobenius范数 ----@return number Frobenius范数 -function Matrix:normFrobenius() - local sum = 0 - for i = 1, self.rows do - for j = 1, self.cols do - local val = self:get(i, j) - sum = sum + val * val - end - end - return math.sqrt(sum) -end - ----检查矩阵是否为对称矩阵 ----@return boolean 是否为对称矩阵 -function Matrix:isSymmetric() - if self.rows ~= self.cols then - return false - end - - for i = 1, self.rows do - for j = i + 1, self.cols do - if math.abs(self:get(i, j) - self:get(j, i)) > 1e-10 then - return false - end - end - end - - return true -end - ----将矩阵转换为数组 ----@return table 包含矩阵元素的二维数组 -function Matrix:toArray() - local result = {} - for i = 1, self.rows do - result[i] = {} - for j = 1, self.cols do - result[i][j] = self:get(i, j) - end - end - return result -end - ----将矩阵转换为扁平数组(按行优先顺序) ----@return table 包含矩阵元素的一维数组 -function Matrix:toFlatArray() - local result = {} - local index = 1 - for i = 1, self.rows do - for j = 1, self.cols do - result[index] = self:get(i, j) - index = index + 1 - end - end - return result -end - ----从扁平数组创建矩阵(按行优先顺序) ----@param arr table 一维数组 ----@param rows number 行数 ----@param cols number 列数 ----@return foundation.math.Matrix 创建的矩阵 -function Matrix.fromFlatArray(arr, rows, cols) - if #arr ~= rows * cols then - error("Array length does not match matrix dimensions") - end - - local matrix = Matrix.create(rows, cols) - local index = 1 - for i = 1, rows do - for j = 1, cols do - matrix:set(i, j, arr[index]) - index = index + 1 - end - end - - return matrix -end - -return Matrix \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/matrix/MatrixTransformation.lua b/game/packages/thlib-scripts-v2/foundation/math/matrix/MatrixTransformation.lua deleted file mode 100644 index f7d0a2e8..00000000 --- a/game/packages/thlib-scripts-v2/foundation/math/matrix/MatrixTransformation.lua +++ /dev/null @@ -1,325 +0,0 @@ -local Matrix = require("foundation.math.matrix.Matrix") -local Vector2 = require("foundation.math.Vector2") -local Vector3 = require("foundation.math.Vector3") - -local math = math -local error = error - ----@class foundation.math.matrix.MatrixTransformation -local MatrixTransformation = {} - ----创建2D平移矩阵 ----@param x number X方向平移量 ----@param y number Y方向平移量 ----@return foundation.math.Matrix 平移矩阵 -function MatrixTransformation.translation2D(x, y) - local m = Matrix.identity(3) - m:set(1, 3, x) - m:set(2, 3, y) - return m -end - ----创建2D缩放矩阵 ----@param sx number X方向缩放因子 ----@param sy number|nil Y方向缩放因子,如果为nil则使用sx ----@return foundation.math.Matrix 缩放矩阵 -function MatrixTransformation.scaling2D(sx, sy) - sy = sy or sx - local m = Matrix.identity(3) - m:set(1, 1, sx) - m:set(2, 2, sy) - return m -end - ----创建2D旋转矩阵(弧度) ----@param rad number 旋转角度(弧度) ----@return foundation.math.Matrix 旋转矩阵 -function MatrixTransformation.rotation2D(rad) - local c = math.cos(rad) - local s = math.sin(rad) - local m = Matrix.identity(3) - m:set(1, 1, c) - m:set(1, 2, -s) - m:set(2, 1, s) - m:set(2, 2, c) - return m -end - ----创建2D旋转矩阵(角度) ----@param angle number 旋转角度(度) ----@return foundation.math.Matrix 旋转矩阵 -function MatrixTransformation.degreeRotation2D(angle) - return MatrixTransformation.rotation2D(math.rad(angle)) -end - ----创建2D切变矩阵 ----@param shx number X方向切变系数 ----@param shy number Y方向切变系数 ----@return foundation.math.Matrix 切变矩阵 -function MatrixTransformation.shear2D(shx, shy) - local m = Matrix.identity(3) - m:set(1, 2, shy) - m:set(2, 1, shx) - return m -end - ----创建3D平移矩阵 ----@param x number X方向平移量 ----@param y number Y方向平移量 ----@param z number Z方向平移量 ----@return foundation.math.Matrix 平移矩阵 -function MatrixTransformation.translation3D(x, y, z) - local m = Matrix.identity(4) - m:set(1, 4, x) - m:set(2, 4, y) - m:set(3, 4, z) - return m -end - ----创建3D缩放矩阵 ----@param sx number X方向缩放因子 ----@param sy number|nil Y方向缩放因子,如果为nil则使用sx ----@param sz number|nil Z方向缩放因子,如果为nil则使用sx ----@return foundation.math.Matrix 缩放矩阵 -function MatrixTransformation.scaling3D(sx, sy, sz) - sy = sy or sx - sz = sz or sx - local m = Matrix.identity(4) - m:set(1, 1, sx) - m:set(2, 2, sy) - m:set(3, 3, sz) - return m -end - ----创建绕X轴旋转的3D旋转矩阵(弧度) ----@param rad number 旋转角度(弧度) ----@return foundation.math.Matrix 旋转矩阵 -function MatrixTransformation.rotationX(rad) - local c = math.cos(rad) - local s = math.sin(rad) - local m = Matrix.identity(4) - m:set(2, 2, c) - m:set(2, 3, -s) - m:set(3, 2, s) - m:set(3, 3, c) - return m -end - ----创建绕Y轴旋转的3D旋转矩阵(弧度) ----@param rad number 旋转角度(弧度) ----@return foundation.math.Matrix 旋转矩阵 -function MatrixTransformation.rotationY(rad) - local c = math.cos(rad) - local s = math.sin(rad) - local m = Matrix.identity(4) - m:set(1, 1, c) - m:set(1, 3, s) - m:set(3, 1, -s) - m:set(3, 3, c) - return m -end - ----创建绕Z轴旋转的3D旋转矩阵(弧度) ----@param rad number 旋转角度(弧度) ----@return foundation.math.Matrix 旋转矩阵 -function MatrixTransformation.rotationZ(rad) - local c = math.cos(rad) - local s = math.sin(rad) - local m = Matrix.identity(4) - m:set(1, 1, c) - m:set(1, 2, -s) - m:set(2, 1, s) - m:set(2, 2, c) - return m -end - ----创建绕任意轴旋转的3D旋转矩阵(弧度) ----@param x number 轴的X分量 ----@param y number 轴的Y分量 ----@param z number 轴的Z分量 ----@param rad number 旋转角度(弧度) ----@return foundation.math.Matrix 旋转矩阵 -function MatrixTransformation.rotationAxis(x, y, z, rad) - local length = math.sqrt(x * x + y * y + z * z) - if length < 1e-10 then - return Matrix.identity(4) - end - - x = x / length - y = y / length - z = z / length - - local c = math.cos(rad) - local s = math.sin(rad) - local t = 1 - c - - local m = Matrix.identity(4) - m:set(1, 1, t * x * x + c) - m:set(1, 2, t * x * y - s * z) - m:set(1, 3, t * x * z + s * y) - - m:set(2, 1, t * x * y + s * z) - m:set(2, 2, t * y * y + c) - m:set(2, 3, t * y * z - s * x) - - m:set(3, 1, t * x * z - s * y) - m:set(3, 2, t * y * z + s * x) - m:set(3, 3, t * z * z + c) - - return m -end - ----创建视图矩阵 ----@param eyeX number 视点X坐标 ----@param eyeY number 视点Y坐标 ----@param eyeZ number 视点Z坐标 ----@param centerX number 目标点X坐标 ----@param centerY number 目标点Y坐标 ----@param centerZ number 目标点Z坐标 ----@param upX number 上向量X分量 ----@param upY number 上向量Y分量 ----@param upZ number 上向量Z分量 ----@return foundation.math.Matrix 视图矩阵 -function MatrixTransformation.lookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) - local zx = eyeX - centerX - local zy = eyeY - centerY - local zz = eyeZ - centerZ - - local zLen = math.sqrt(zx * zx + zy * zy + zz * zz) - if zLen < 1e-10 then - return Matrix.identity(4) - end - zx = zx / zLen - zy = zy / zLen - zz = zz / zLen - - local xx = upY * zz - upZ * zy - local xy = upZ * zx - upX * zz - local xz = upX * zy - upY * zx - - local xLen = math.sqrt(xx * xx + xy * xy + xz * xz) - if xLen < 1e-10 then - upX, upY, upZ = 0, 1, 0 - if math.abs(zy) > 0.9 then - upX, upY, upZ = 1, 0, 0 - end - xx = upY * zz - upZ * zy - xy = upZ * zx - upX * zz - xz = upX * zy - upY * zx - xLen = math.sqrt(xx * xx + xy * xy + xz * xz) - end - xx = xx / xLen - xy = xy / xLen - xz = xz / xLen - - local yx = zy * xz - zz * xy - local yy = zz * xx - zx * xz - local yz = zx * xy - zy * xx - - local m = Matrix.identity(4) - m:set(1, 1, xx) - m:set(1, 2, xy) - m:set(1, 3, xz) - m:set(2, 1, yx) - m:set(2, 2, yy) - m:set(2, 3, yz) - m:set(3, 1, zx) - m:set(3, 2, zy) - m:set(3, 3, zz) - - m:set(1, 4, -(xx * eyeX + xy * eyeY + xz * eyeZ)) - m:set(2, 4, -(yx * eyeX + yy * eyeY + yz * eyeZ)) - m:set(3, 4, -(zx * eyeX + zy * eyeY + zz * eyeZ)) - - return m -end - ----创建正交投影矩阵 ----@param left number 左平面 ----@param right number 右平面 ----@param bottom number 底平面 ----@param top number 顶平面 ----@param near number 近平面 ----@param far number 远平面 ----@return foundation.math.Matrix 正交投影矩阵 -function MatrixTransformation.orthographic(left, right, bottom, top, near, far) - if math.abs(right - left) < 1e-10 or math.abs(top - bottom) < 1e-10 or math.abs(far - near) < 1e-10 then - error("Invalid orthographic projection parameters") - end - - local m = Matrix.identity(4) - m:set(1, 1, 2 / (right - left)) - m:set(2, 2, 2 / (top - bottom)) - m:set(3, 3, -2 / (far - near)) - m:set(1, 4, -(right + left) / (right - left)) - m:set(2, 4, -(top + bottom) / (top - bottom)) - m:set(3, 4, -(far + near) / (far - near)) - - return m -end - ----创建透视投影矩阵 ----@param fovy number 垂直视野角度(弧度) ----@param aspect number 宽高比 ----@param near number 近平面 ----@param far number 远平面 ----@return foundation.math.Matrix 透视投影矩阵 -function MatrixTransformation.perspective(fovy, aspect, near, far) - if math.abs(aspect) < 1e-10 or math.abs(far - near) < 1e-10 then - error("Invalid perspective projection parameters") - end - - local f = 1 / math.tan(fovy / 2) - - local m = Matrix.create(4, 4, 0) - m:set(1, 1, f / aspect) - m:set(2, 2, f) - m:set(3, 3, (far + near) / (near - far)) - m:set(3, 4, (2 * far * near) / (near - far)) - m:set(4, 3, -1) - - return m -end - ----将Vector2应用矩阵变换(假设为2D变换,使用3x3矩阵) ----@param matrix foundation.math.Matrix 变换矩阵 ----@param vector foundation.math.Vector2 待变换向量 ----@return foundation.math.Vector2 变换后的向量 -function MatrixTransformation.transformVector2(matrix, vector) - if matrix.rows ~= 3 or matrix.cols ~= 3 then - error("Expected 3x3 matrix for 2D transformation") - end - - local x = matrix:get(1, 1) * vector.x + matrix:get(1, 2) * vector.y + matrix:get(1, 3) - local y = matrix:get(2, 1) * vector.x + matrix:get(2, 2) * vector.y + matrix:get(2, 3) - local w = matrix:get(3, 1) * vector.x + matrix:get(3, 2) * vector.y + matrix:get(3, 3) - - if math.abs(w) > 1e-10 then - return Vector2.create(x / w, y / w) - else - return Vector2.create(x, y) - end -end - ----将Vector3应用矩阵变换 ----@param matrix foundation.math.Matrix 变换矩阵 ----@param vector foundation.math.Vector3 待变换向量 ----@return foundation.math.Vector3 变换后的向量 -function MatrixTransformation.transformVector3(matrix, vector) - if matrix.rows ~= 4 or matrix.cols ~= 4 then - error("Expected 4x4 matrix for 3D transformation") - end - - local x = matrix:get(1, 1) * vector.x + matrix:get(1, 2) * vector.y + matrix:get(1, 3) * vector.z + matrix:get(1, 4) - local y = matrix:get(2, 1) * vector.x + matrix:get(2, 2) * vector.y + matrix:get(2, 3) * vector.z + matrix:get(2, 4) - local z = matrix:get(3, 1) * vector.x + matrix:get(3, 2) * vector.y + matrix:get(3, 3) * vector.z + matrix:get(3, 4) - local w = matrix:get(4, 1) * vector.x + matrix:get(4, 2) * vector.y + matrix:get(4, 3) * vector.z + matrix:get(4, 4) - - if math.abs(w) > 1e-10 then - return Vector3.create(x / w, y / w, z / w) - else - return Vector3.create(x, y, z) - end -end - -return MatrixTransformation \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/math/matrix/SpecialMatrix.lua b/game/packages/thlib-scripts-v2/foundation/math/matrix/SpecialMatrix.lua deleted file mode 100644 index b39db567..00000000 --- a/game/packages/thlib-scripts-v2/foundation/math/matrix/SpecialMatrix.lua +++ /dev/null @@ -1,428 +0,0 @@ -local ipairs = ipairs -local error = error -local math = math - -local Matrix = require("foundation.math.matrix.Matrix") - ----@class foundation.math.matrix.SpecialMatrix -local SpecialMatrix = {} - ----创建一个对角矩阵 ----@param diag table 对角线上的值 ----@return foundation.math.Matrix 对角矩阵 -function SpecialMatrix.diagonal(diag) - local size = #diag - local matrix = Matrix.create(size, size, 0) - - for i = 1, size do - matrix:set(i, i, diag[i]) - end - - return matrix -end - ----创建一个三对角矩阵 ----@param main table 主对角线上的值 ----@param upper table 上对角线上的值 ----@param lower table 下对角线上的值 ----@return foundation.math.Matrix 三对角矩阵 -function SpecialMatrix.tridiagonal(main, upper, lower) - local size = #main - - if #upper ~= size - 1 or #lower ~= size - 1 then - error("Invalid dimensions for tridiagonal matrix") - end - - local matrix = Matrix.create(size, size, 0) - - for i = 1, size do - matrix:set(i, i, main[i]) - end - - for i = 1, size - 1 do - matrix:set(i, i + 1, upper[i]) - matrix:set(i + 1, i, lower[i]) - end - - return matrix -end - ----创建一个Toeplitz矩阵(每一条从左上到右下的对角线上的元素相同) ----@param first_row table 第一行的元素 ----@param first_col table 第一列的元素(第一个元素应与first_row[1]相同) ----@return foundation.math.Matrix Toeplitz矩阵 -function SpecialMatrix.toeplitz(first_row, first_col) - if first_row[1] ~= first_col[1] then - error("First element of row and column must be the same") - end - - local rows = #first_col - local cols = #first_row - - local matrix = Matrix.create(rows, cols) - - for i = 1, rows do - for j = 1, cols do - if j >= i then - matrix:set(i, j, first_row[j - i + 1]) - else - matrix:set(i, j, first_col[i - j + 1]) - end - end - end - - return matrix -end - ----创建一个Vandermonde矩阵 ----@param points table 用于生成矩阵的点 ----@param order number|nil 多项式阶数(默认为points的长度) ----@return foundation.math.Matrix Vandermonde矩阵 -function SpecialMatrix.vandermonde(points, order) - local n = #points - order = order or n - - local matrix = Matrix.create(n, order) - - for i = 1, n do - matrix:set(i, 1, 1) - for j = 2, order do - matrix:set(i, j, matrix:get(i, j - 1) * points[i]) - end - end - - return matrix -end - ----创建一个Hilbert矩阵 (H[i,j] = 1/(i+j-1)) ----@param size number 矩阵大小 ----@return foundation.math.Matrix Hilbert矩阵 -function SpecialMatrix.hilbert(size) - local matrix = Matrix.create(size, size) - - for i = 1, size do - for j = 1, size do - matrix:set(i, j, 1 / (i + j - 1)) - end - end - - return matrix -end - ----创建一个循环矩阵(第一行的循环位移构成后续行) ----@param first_row table 第一行的元素 ----@return foundation.math.Matrix 循环矩阵 -function SpecialMatrix.circulant(first_row) - local size = #first_row - local matrix = Matrix.create(size, size) - - for i = 1, size do - for j = 1, size do - local index = ((j - i) % size) + 1 - if index <= 0 then - index = index + size - end - matrix:set(i, j, first_row[index]) - end - end - - return matrix -end - ----创建一个具有指定块的分块对角矩阵 ----@param blocks table 块矩阵数组 ----@return foundation.math.Matrix 分块对角矩阵 -function SpecialMatrix.blockDiagonal(blocks) - local totalRows = 0 - local totalCols = 0 - - for _, block in ipairs(blocks) do - totalRows = totalRows + block.rows - totalCols = totalCols + block.cols - end - - local matrix = Matrix.create(totalRows, totalCols, 0) - - local rowOffset = 0 - local colOffset = 0 - - for _, block in ipairs(blocks) do - for i = 1, block.rows do - for j = 1, block.cols do - matrix:set(rowOffset + i, colOffset + j, block:get(i, j)) - end - end - - rowOffset = rowOffset + block.rows - colOffset = colOffset + block.cols - end - - return matrix -end - ----创建上三角矩阵 ----@param values table 上三角部分的值(按行优先顺序排列的一维数组) ----@param size number 矩阵大小 ----@return foundation.math.Matrix 上三角矩阵 -function SpecialMatrix.upperTriangular(values, size) - local matrix = Matrix.create(size, size, 0) - local index = 1 - - for i = 1, size do - for j = i, size do - matrix:set(i, j, values[index]) - index = index + 1 - end - end - - return matrix -end - ----创建下三角矩阵 ----@param values table 下三角部分的值(按行优先顺序排列的一维数组) ----@param size number 矩阵大小 ----@return foundation.math.Matrix 下三角矩阵 -function SpecialMatrix.lowerTriangular(values, size) - local matrix = Matrix.create(size, size, 0) - local index = 1 - - for i = 1, size do - for j = 1, i do - matrix:set(i, j, values[index]) - index = index + 1 - end - end - - return matrix -end - ----创建一个对称矩阵 ----@param values table 上三角部分的值(包括对角线,按行优先顺序排列) ----@param size number 矩阵大小 ----@return foundation.math.Matrix 对称矩阵 -function SpecialMatrix.symmetric(values, size) - local matrix = Matrix.create(size, size) - local index = 1 - - for i = 1, size do - for j = i, size do - matrix:set(i, j, values[index]) - if i ~= j then - matrix:set(j, i, values[index]) - end - index = index + 1 - end - end - - return matrix -end - ----创建一个反对称矩阵(A^T = -A) ----@param values table 上三角部分的值(不包括对角线,按行优先顺序排列) ----@param size number 矩阵大小 ----@return foundation.math.Matrix 反对称矩阵 -function SpecialMatrix.skewSymmetric(values, size) - local matrix = Matrix.create(size, size, 0) - local index = 1 - - for i = 1, size do - for j = i + 1, size do - matrix:set(i, j, values[index]) - matrix:set(j, i, -values[index]) - index = index + 1 - end - end - - return matrix -end - ----创建一个Hadamard矩阵(若存在) ----@param size number 矩阵大小(必须是1,2或4的倍数以便存在) ----@return foundation.math.Matrix|nil Hadamard矩阵,若不存在则返回nil -function SpecialMatrix.hadamard(size) - if size <= 0 then - return nil - elseif size == 1 then - return Matrix.create(1, 1, 1) - elseif size == 2 then - return Matrix.fromArray({ - {1, 1}, - {1, -1} - }) - end - - if size % 4 ~= 0 then - return nil - end - - local halfSize = size / 2 - local h = SpecialMatrix.hadamard(halfSize) - if not h then - return nil - end - - local result = Matrix.create(size, size) - - for i = 1, halfSize do - for j = 1, halfSize do - result:set(i, j, h:get(i, j)) - result:set(i, j + halfSize, h:get(i, j)) - result:set(i + halfSize, j, h:get(i, j)) - result:set(i + halfSize, j + halfSize, -h:get(i, j)) - end - end - - return result -end - ----创建一个二次型矩阵 f(x) = x^T A x ----@param coef table 二次型的系数 ----@param vars number 变量个数 ----@return foundation.math.Matrix 二次型矩阵 -function SpecialMatrix.quadratic(coef, vars) - local matrix = Matrix.create(vars, vars, 0) - - local index = 1 - for i = 1, vars do - for j = i, vars do - if i == j then - matrix:set(i, j, coef[index]) - else - matrix:set(i, j, coef[index] / 2) - matrix:set(j, i, coef[index] / 2) - end - index = index + 1 - end - end - - return matrix -end - ----QR分解(使用Gram-Schmidt正交化) ----@param matrix foundation.math.Matrix 待分解的矩阵 ----@return foundation.math.Matrix Q 正交矩阵Q ----@return foundation.math.Matrix R 上三角矩阵R -function SpecialMatrix.qrDecomposition(matrix) - local m = matrix.rows - local n = matrix.cols - - local Q = Matrix.create(m, n) - local R = Matrix.create(n, n, 0) - - local columns = {} - for j = 1, n do - columns[j] = {} - for i = 1, m do - columns[j][i] = matrix:get(i, j) - end - end - - for j = 1, n do - local q = {} - for i = 1, m do - q[i] = columns[j][i] - end - - for k = 1, j - 1 do - local dot = 0 - for i = 1, m do - dot = dot + columns[j][i] * Q:get(i, k) - end - R:set(k, j, dot) - - for i = 1, m do - q[i] = q[i] - dot * Q:get(i, k) - end - end - - local norm = 0 - for i = 1, m do - norm = norm + q[i] * q[i] - end - norm = math.sqrt(norm) - - if norm < 1e-10 then - error("Matrix columns are linearly dependent") - end - - R:set(j, j, norm) - - for i = 1, m do - Q:set(i, j, q[i] / norm) - end - end - - return Q, R -end - ----特征值分解(仅适用于对称矩阵,使用幂法计算最大特征值) ----@param matrix foundation.math.Matrix 待分解的矩阵 ----@param maxIterations number|nil 最大迭代次数(默认100) ----@param tolerance number|nil 收敛容差(默认1e-6) ----@return number 最大特征值 ----@return table 对应特征向量 -function SpecialMatrix.powerMethod(matrix, maxIterations, tolerance) - if matrix.rows ~= matrix.cols then - error("Matrix must be square for eigenvalue computation") - end - - maxIterations = maxIterations or 100 - tolerance = tolerance or 1e-6 - - local n = matrix.rows - - local x = {} - for i = 1, n do - x[i] = math.random() - end - - local norm = 0 - for i = 1, n do - norm = norm + x[i] * x[i] - end - norm = math.sqrt(norm) - - for i = 1, n do - x[i] = x[i] / norm - end - - local eigenvalue = 0 - local prevEigenvalue = 0 - - for _ = 1, maxIterations do - local y = {} - for i = 1, n do - y[i] = 0 - for j = 1, n do - y[i] = y[i] + matrix:get(i, j) * x[j] - end - end - - local rayleigh = 0 - for i = 1, n do - rayleigh = rayleigh + x[i] * y[i] - end - - eigenvalue = rayleigh - - if math.abs(eigenvalue - prevEigenvalue) < tolerance then - break - end - - prevEigenvalue = eigenvalue - - norm = 0 - for i = 1, n do - norm = norm + y[i] * y[i] - end - norm = math.sqrt(norm) - - for i = 1, n do - x[i] = y[i] / norm - end - end - - return eigenvalue, x -end - -return SpecialMatrix \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua b/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua deleted file mode 100644 index e1838bc9..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/BezierCurve.lua +++ /dev/null @@ -1,741 +0,0 @@ -local ffi = require("ffi") - -local type = type -local tostring = tostring -local string = string -local math = math -local table = table -local ipairs = ipairs -local error = error -local rawset = rawset -local setmetatable = setmetatable - -local Vector2 = require("foundation.math.Vector2") -local Segment = require("foundation.shape.Segment") -local ShapeIntersector = require("foundation.shape.ShapeIntersector") - -ffi.cdef [[ -typedef struct { - int order; - int num_points; - foundation_math_Vector2* control_points; -} foundation_shape_BezierCurve; -]] - ----@class foundation.shape.BezierCurve ----@field order number 贝塞尔曲线的阶数 (2=二次曲线,3=三次曲线) ----@field num_points number 控制点的数量 ----@field control_points foundation.math.Vector2[] 控制点数组 -local BezierCurve = {} -BezierCurve.__type = "foundation.shape.BezierCurve" - ----@param t foundation.math.Vector2[] ----@return number, foundation.math.Vector2[] -local function buildNewVector2Array(t) - local size = #t - local points_array = ffi.new("foundation_math_Vector2[?]", size) - - for i = 1, size do - points_array[i - 1] = Vector2.create(t[i].x, t[i].y) - end - - return size, points_array -end - ----@param self foundation.shape.BezierCurve ----@param key any ----@return any -function BezierCurve.__index(self, key) - if key == "order" then - return self.__data.order - elseif key == "num_points" then - return self.__data.num_points - elseif key == "control_points" then - return self.__data.control_points - end - return BezierCurve[key] -end - ----@param self foundation.shape.BezierCurve ----@param key any ----@param value any -function BezierCurve.__newindex(self, key, value) - if key == "order" then - self.__data.order = value - elseif key == "num_points" then - error("cannot modify num_points directly") - elseif key == "control_points" then - local size, points_array = buildNewVector2Array(value) - self.__data.num_points = size - self.__data.control_points = points_array - self.__data_points_ref = points_array - else - rawset(self, key, value) - end -end - ----创建一个贝塞尔曲线 ----@param control_points foundation.math.Vector2[] 控制点数组 ----@return foundation.shape.BezierCurve -function BezierCurve.create(control_points) - if not control_points or #control_points < 2 then - error("BezierCurve requires at least 2 control points") - end - - local size, points_array = buildNewVector2Array(control_points) - local order = size - 1 - - local curve = ffi.new("foundation_shape_BezierCurve", order, size, points_array) - local result = { - __data = curve, - __data_points_ref = points_array, - } - - ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return setmetatable(result, BezierCurve) -end - ----创建一个二次贝塞尔曲线 ----@param p0 foundation.math.Vector2 起始点 ----@param p1 foundation.math.Vector2 控制点 ----@param p2 foundation.math.Vector2 结束点 ----@return foundation.shape.BezierCurve -function BezierCurve.createQuadratic(p0, p1, p2) - return BezierCurve.create({ p0, p1, p2 }) -end - ----创建一个三次贝塞尔曲线 ----@param p0 foundation.math.Vector2 起始点 ----@param p1 foundation.math.Vector2 控制点1 ----@param p2 foundation.math.Vector2 控制点2 ----@param p3 foundation.math.Vector2 结束点 ----@return foundation.shape.BezierCurve -function BezierCurve.createCubic(p0, p1, p2, p3) - return BezierCurve.create({ p0, p1, p2, p3 }) -end - ----贝塞尔曲线相等比较 ----@param a foundation.shape.BezierCurve 第一条贝塞尔曲线 ----@param b foundation.shape.BezierCurve 第二条贝塞尔曲线 ----@return boolean -function BezierCurve.__eq(a, b) - if a.order ~= b.order or a.num_points ~= b.num_points then - return false - end - - for i = 0, a.num_points - 1 do - if a.control_points[i] ~= b.control_points[i] then - return false - end - end - - return true -end - ----贝塞尔曲线转字符串表示 ----@param self foundation.shape.BezierCurve ----@return string -function BezierCurve.__tostring(self) - local pointsStr = {} - for i = 0, self.num_points - 1 do - pointsStr[i + 1] = tostring(self.control_points[i]) - end - return string.format("BezierCurve(%s)", table.concat(pointsStr, ", ")) -end - ----获取贝塞尔曲线上参数为t的点(t范围0到1) ----@param t number 参数值(0-1) ----@return foundation.math.Vector2 -function BezierCurve:getPoint(t) - if t <= 0 then - return self.control_points[0]:clone() - elseif t >= 1 then - return self.control_points[self.num_points - 1]:clone() - end - - local points = {} - for i = 0, self.num_points - 1 do - points[i + 1] = self.control_points[i]:clone() - end - - for r = 1, self.order do - for i = 1, self.num_points - r do - points[i] = points[i] * (1 - t) + points[i + 1] * t - end - end - - return points[1] -end - ----获取贝塞尔曲线上参数为t的切线单位向量 ----@param t number 参数值(0-1) ----@return foundation.math.Vector2 -function BezierCurve:getTangent(t) - if self.order <= 1 then - return (self.control_points[1] - self.control_points[0]):normalized() - end - - local dt = 0.0001 - local p1 = self:getPoint(t) - local p2 = self:getPoint(t + dt) - return (p2 - p1):normalized() -end - ----获取贝塞尔曲线的起点 ----@return foundation.math.Vector2 -function BezierCurve:getStartPoint() - return self.control_points[0]:clone() -end - ----获取贝塞尔曲线的终点 ----@return foundation.math.Vector2 -function BezierCurve:getEndPoint() - return self.control_points[self.num_points - 1]:clone() -end - ----转换为一系列线段的近似表示 ----@param segments number 分段数 ----@return foundation.shape.Segment[] -function BezierCurve:toSegments(segments) - segments = segments or 10 - - local points = self:discretize(segments) - local segs = {} - - for i = 1, #points - 1 do - segs[i] = Segment.create(points[i], points[i + 1]) - end - - return segs -end - ----将贝塞尔曲线离散化为一系列点 ----@param segments number 分段数 ----@return foundation.math.Vector2[] -function BezierCurve:discretize(segments) - segments = segments or 10 - local points = {} - - for i = 0, segments do - local t = i / segments - points[i + 1] = self:getPoint(t) - end - - return points -end - ----获取贝塞尔曲线的近似长度 ----@param segments number 分段数,用于近似计算 ----@return number -function BezierCurve:length(segments) - segments = segments or 20 - local points = self:discretize(segments) - local length = 0 - - for i = 2, #points do - length = length + (points[i] - points[i - 1]):length() - end - - return length -end - ----计算贝塞尔曲线的中心 ----@return foundation.math.Vector2 -function BezierCurve:getCenter() - local minX, minY = math.huge, math.huge - local maxX, maxY = -math.huge, -math.huge - - local function updateBounds(point) - minX = math.min(minX, point.x) - minY = math.min(minY, point.y) - maxX = math.max(maxX, point.x) - maxY = math.max(maxY, point.y) - end - - updateBounds(self.control_points[0]) - updateBounds(self.control_points[self.num_points - 1]) - - local n = self.order - if n > 1 then - local deriv_points = {} - for i = 0, n - 1 do - deriv_points[i + 1] = (self.control_points[i + 1] - self.control_points[i]) * n - end - - local t_values = { 0, 1 } - if n == 2 then - local p0, p1, p2 = self.control_points[0], self.control_points[1], self.control_points[2] - local dx1 = p1.x - p0.x - local dx2 = p2.x - p1.x - local dy1 = p1.y - p0.y - local dy2 = p2.y - p1.y - - if dx1 ~= dx2 then - local tx = dx1 / (dx1 - dx2) - if tx > 0 and tx < 1 then - table.insert(t_values, tx) - end - end - - if dy1 ~= dy2 then - local ty = dy1 / (dy1 - dy2) - if ty > 0 and ty < 1 then - table.insert(t_values, ty) - end - end - elseif n == 3 then - local q0 = deriv_points[1] - local q1 = deriv_points[2] - local q2 = deriv_points[3] - - local a_x = q0.x - 2 * q1.x + q2.x - local b_x = 2 * (q1.x - q0.x) - local c_x = q0.x - if a_x ~= 0 then - local discriminant_x = b_x * b_x - 4 * a_x * c_x - if discriminant_x >= 0 then - local sqrt_d = math.sqrt(discriminant_x) - local t1 = (-b_x + sqrt_d) / (2 * a_x) - local t2 = (-b_x - sqrt_d) / (2 * a_x) - if t1 > 0 and t1 < 1 then - table.insert(t_values, t1) - end - if t2 > 0 and t2 < 1 then - table.insert(t_values, t2) - end - end - elseif b_x ~= 0 then - local t = -c_x / b_x - if t > 0 and t < 1 then - table.insert(t_values, t) - end - end - - local a_y = q0.y - 2 * q1.y + q2.y - local b_y = 2 * (q1.y - q0.y) - local c_y = q0.y - if a_y ~= 0 then - local discriminant_y = b_y * b_y - 4 * a_y * c_y - if discriminant_y >= 0 then - local sqrt_d = math.sqrt(discriminant_y) - local t1 = (-b_y + sqrt_d) / (2 * a_y) - local t2 = (-b_y - sqrt_d) / (2 * a_y) - if t1 > 0 and t1 < 1 then - table.insert(t_values, t1) - end - if t2 > 0 and t2 < 1 then - table.insert(t_values, t2) - end - end - elseif b_y ~= 0 then - local t = -c_y / b_y - if t > 0 and t < 1 then - table.insert(t_values, t) - end - end - end - - for _, t in ipairs(t_values) do - updateBounds(self:getPoint(t)) - end - end - - return Vector2.create((minX + maxX) / 2, (minY + maxY) / 2) -end - ----获取贝塞尔曲线的AABB包围盒 ----@return number, number, number, number -function BezierCurve:AABB() - local minX, minY = math.huge, math.huge - local maxX, maxY = -math.huge, -math.huge - - -- 首先检查所有控制点 - for i = 0, self.num_points - 1 do - local point = self.control_points[i] - minX = math.min(minX, point.x) - minY = math.min(minY, point.y) - maxX = math.max(maxX, point.x) - maxY = math.max(maxY, point.y) - end - - -- 对于高阶贝塞尔曲线,还需要检查曲线上的极值点 - if self.order > 1 then - local segments = self.order * 10 -- 使用更多的分段来获得更精确的包围盒 - for i = 0, segments do - local t = i / segments - local point = self:getPoint(t) - minX = math.min(minX, point.x) - minY = math.min(minY, point.y) - maxX = math.max(maxX, point.x) - maxY = math.max(maxY, point.y) - end - end - - return minX, maxX, minY, maxY -end - ----计算贝塞尔曲线的包围盒宽高 ----@return number, number -function BezierCurve:getBoundingBoxSize() - local minX, maxX, minY, maxY = self:AABB() - return maxX - minX, maxY - minY -end - ----将当前贝塞尔曲线平移指定距离(更改当前曲线) ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.BezierCurve 自身引用 -function BezierCurve:move(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - - local new_points = {} - for i = 0, self.num_points - 1 do - local p = self.control_points[i] - new_points[i + 1] = Vector2.create(p.x + moveX, p.y + moveY) - end - - self.control_points = new_points - return self -end - ----获取平移后的贝塞尔曲线副本 ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.BezierCurve -function BezierCurve:moved(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - - local new_points = {} - for i = 0, self.num_points - 1 do - local p = self.control_points[i] - new_points[i + 1] = Vector2.create(p.x + moveX, p.y + moveY) - end - - return BezierCurve.create(new_points) -end - ----将当前贝塞尔曲线旋转指定弧度(更改当前曲线) ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心,默认为曲线的中心 ----@return foundation.shape.BezierCurve 自身引用 -function BezierCurve:rotate(rad, center) - center = center or self:getCenter() - local cosRad = math.cos(rad) - local sinRad = math.sin(rad) - - local new_points = {} - for i = 0, self.num_points - 1 do - local p = self.control_points[i] - local v = p - center - local x = v.x * cosRad - v.y * sinRad + center.x - local y = v.x * sinRad + v.y * cosRad + center.y - new_points[i + 1] = Vector2.create(x, y) - end - - self.control_points = new_points - return self -end - ----将当前贝塞尔曲线旋转指定角度(更改当前曲线) ----@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心,默认为曲线的中心 ----@return foundation.shape.BezierCurve 自身引用 -function BezierCurve:degreeRotate(angle, center) - return self:rotate(math.rad(angle), center) -end - ----获取旋转后的贝塞尔曲线副本 ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心,默认为曲线的中心 ----@return foundation.shape.BezierCurve -function BezierCurve:rotated(rad, center) - center = center or self:getCenter() - local cosRad = math.cos(rad) - local sinRad = math.sin(rad) - - local new_points = {} - for i = 0, self.num_points - 1 do - local p = self.control_points[i] - local v = p - center - local x = v.x * cosRad - v.y * sinRad + center.x - local y = v.x * sinRad + v.y * cosRad + center.y - new_points[i + 1] = Vector2.create(x, y) - end - - return BezierCurve.create(new_points) -end - ----获取旋转后的贝塞尔曲线副本 ----@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心,默认为曲线的中心 ----@return foundation.shape.BezierCurve -function BezierCurve:degreeRotated(angle, center) - return self:rotated(math.rad(angle), center) -end - ----将当前贝塞尔曲线缩放指定倍数(更改当前曲线) ----@param scale number | foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2 缩放中心,默认为曲线的中心 ----@return foundation.shape.BezierCurve 自身引用 -function BezierCurve:scale(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - - center = center or self:getCenter() - - local new_points = {} - for i = 0, self.num_points - 1 do - local p = self.control_points[i] - local x = (p.x - center.x) * scaleX + center.x - local y = (p.y - center.y) * scaleY + center.y - new_points[i + 1] = Vector2.create(x, y) - end - - self.control_points = new_points - return self -end - ----获取缩放后的贝塞尔曲线副本 ----@param scale number | foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2 缩放中心,默认为曲线的中心 ----@return foundation.shape.BezierCurve -function BezierCurve:scaled(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - - center = center or self:getCenter() - - local new_points = {} - for i = 0, self.num_points - 1 do - local p = self.control_points[i] - local x = (p.x - center.x) * scaleX + center.x - local y = (p.y - center.y) * scaleY + center.y - new_points[i + 1] = Vector2.create(x, y) - end - - return BezierCurve.create(new_points) -end - ----获取曲线上最近的点 ----@param point foundation.math.Vector2 参考点 ----@return foundation.math.Vector2 曲线上最近的点 -function BezierCurve:closestPoint(point) - return self:closestPointWithSegments(point) -end - ----获取曲线上最近的点 ----@param point foundation.math.Vector2 参考点 ----@param segments number 分段数,用于近似计算 ----@return foundation.math.Vector2 曲线上最近的点 ----@overload fun(self: foundation.shape.BezierCurve, point: foundation.math.Vector2): foundation.math.Vector2 -function BezierCurve:closestPointWithSegments(point, segments) - segments = segments or 20 - local minDist = math.huge - local closest - - local points = self:discretize(segments) - for _, p in ipairs(points) do - local dist = (p - point):length() - if dist < minDist then - minDist = dist - closest = p - end - end - - return closest -end - ----将点投影到贝塞尔曲线上 ----@param point foundation.math.Vector2 要投影的点 ----@return foundation.math.Vector2, number -function BezierCurve:projectPoint(point) - return self:projectPointWithSegments(point) -end - ----将点投影到贝塞尔曲线上 ----@param point foundation.math.Vector2 要投影的点 ----@param segments number 分段数,用于近似计算 ----@return foundation.math.Vector2, number ----@overload fun(self: foundation.shape.BezierCurve, point: foundation.math.Vector2): foundation.math.Vector2, number -function BezierCurve:projectPointWithSegments(point, segments) - segments = segments or 20 - local minDist = math.huge - local closestPoint - local closestT - - for i = 0, segments do - local t = i / segments - local p = self:getPoint(t) - local dist = (p - point):length() - if dist < minDist then - minDist = dist - closestPoint = p - closestT = t - end - end - - return closestPoint, closestT -end - ----计算点到贝塞尔曲线的距离 ----@param point foundation.math.Vector2 参考点 ----@param segments number 分段数,用于近似计算 ----@return number 距离 -function BezierCurve:distanceToPoint(point, segments) - local closestPoint = self:closestPointWithSegments(point, segments) - return (closestPoint - point):length() -end - ----拆分贝塞尔曲线为两部分 ----@param t number 分割参数(0-1) ----@return foundation.shape.BezierCurve, foundation.shape.BezierCurve 前半部分和后半部分 -function BezierCurve:split(t) - if t <= 0 then - return BezierCurve.create({ self.control_points[0] }), self - elseif t >= 1 then - return self, BezierCurve.create({ self.control_points[self.num_points - 1] }) - end - - local points = {} - for i = 0, self.num_points - 1 do - points[i + 1] = self.control_points[i]:clone() - end - - local left_points = { points[1] } - - for r = 1, self.order do - for i = 1, self.num_points - r do - points[i] = points[i] * (1 - t) + points[i + 1] * t - end - left_points[r + 1] = points[1] - end - - local right_points = {} - for i = 1, self.order + 1 do - right_points[i] = points[i] - end - - return BezierCurve.create(left_points), BezierCurve.create(right_points) -end - ----检查与其他形状的相交 ----@param other any ----@return boolean, foundation.math.Vector2[] | nil -function BezierCurve:intersects(other) - return ShapeIntersector.intersect(self, other) -end - ----仅检查是否与其他形状相交 ----@param other any ----@return boolean -function BezierCurve:hasIntersection(other) - return ShapeIntersector.hasIntersection(self, other) -end - ----检查点是否在贝塞尔曲线上 ----@param point foundation.math.Vector2 ----@param tolerance number|nil 容差,默认为1e-10 ----@param segments number|nil 分段数,用于近似计算 ----@return boolean ----@overload fun(self: foundation.shape.BezierCurve, point: foundation.math.Vector2, tolerance: number): boolean ----@overload fun(self: foundation.shape.BezierCurve, point: foundation.math.Vector2): boolean -function BezierCurve:containsPoint(point, tolerance, segments) - tolerance = tolerance or 1e-10 - local projPoint = self:closestPointWithSegments(point, segments) - return (point - projPoint):length() <= tolerance -end - ----克隆贝塞尔曲线 ----@return foundation.shape.BezierCurve -function BezierCurve:clone() - local new_points = {} - for i = 0, self.num_points - 1 do - new_points[i + 1] = self.control_points[i]:clone() - end - return BezierCurve.create(new_points) -end - ----获取按弧长等分的点集 ----@param num_points number 期望的点数(包含起点和终点) ----@param tolerance number|nil 弧长误差容差,默认为1e-6 ----@return foundation.math.Vector2[] -function BezierCurve:getEqualArcLengthPoints(num_points, tolerance) - num_points = num_points or 10 - tolerance = tolerance or 1e-6 - if num_points < 2 then - error("Number of points must be at least 2") - end - - local total_length = self:length(100) - local target_segment_length = total_length / (num_points - 1) - local points = { self:getPoint(0) } - local current_length = 0 - local t = 0 - local step = 0.01 - local last_point = points[1] - local last_t = 0 - - while #points < num_points and t <= 1 do - t = math.min(t + step, 1) - local point = self:getPoint(t) - local segment_length = (point - last_point):length() - current_length = current_length + segment_length - - if current_length >= target_segment_length - tolerance or t >= 1 then - table.insert(points, point) - last_point = point - last_t = t - current_length = 0 - - local remaining_points = num_points - #points - if remaining_points > 0 then - local remaining_t = 1 - t - step = remaining_t / (remaining_points * 2) - end - else - last_point = point - last_t = t - end - end - - if math.abs(t - 1) > tolerance and #points == num_points then - points[#points] = self:getPoint(1) - end - - return points -end - ----获取按弧长等分的线段集 ----@param num_segments number 期望的线段数(包含起点和终点) ----@param tolerance number|nil 弧长误差容差,默认为1e-6 ----@return foundation.shape.Segment[] -function BezierCurve:getEqualArcLengthSegments(num_segments, tolerance) - local points = self:getEqualArcLengthPoints(num_segments + 1, tolerance) - local segments = {} - - for i = 1, #points - 1 do - segments[i] = Segment.create(points[i], points[i + 1]) - end - - return segments -end - -ffi.metatype("foundation_shape_BezierCurve", BezierCurve) - -return BezierCurve \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua deleted file mode 100644 index 6fff3911..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/Circle.lua +++ /dev/null @@ -1,289 +0,0 @@ -local ffi = require("ffi") - -local math = math -local type = type -local tostring = tostring -local string = string -local rawset = rawset -local setmetatable = setmetatable - -local Vector2 = require("foundation.math.Vector2") -local ShapeIntersector = require("foundation.shape.ShapeIntersector") - -ffi.cdef [[ -typedef struct { - foundation_math_Vector2 center; - double radius; -} foundation_shape_Circle; -]] - ----@class foundation.shape.Circle ----@field center foundation.math.Vector2 圆心位置 ----@field radius number 圆的半径 -local Circle = {} -Circle.__type = "foundation.shape.Circle" - ----@param self foundation.shape.Circle ----@param key any ----@return any -function Circle.__index(self, key) - if key == "center" then - return self.__data.center - elseif key == "radius" then - return self.__data.radius - end - return Circle[key] -end - ----@param self foundation.shape.Circle ----@param key any ----@param value any -function Circle.__newindex(self, key, value) - if key == "center" then - self.__data.center = value - elseif key == "radius" then - self.__data.radius = value - else - rawset(self, key, value) - end -end - ----创建一个新的圆 ----@param center foundation.math.Vector2 圆心位置 ----@param radius number 半径 ----@return foundation.shape.Circle -function Circle.create(center, radius) - local circle = ffi.new("foundation_shape_Circle", center, radius) - local result = { - __data = circle, - } - ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return setmetatable(result, Circle) -end - ----圆相等比较 ----@param a foundation.shape.Circle ----@param b foundation.shape.Circle ----@return boolean -function Circle.__eq(a, b) - return a.center == b.center and math.abs(a.radius - b.radius) <= 1e-10 -end - ----圆的字符串表示 ----@param self foundation.shape.Circle ----@return string -function Circle.__tostring(self) - return string.format("Circle(center=%s, radius=%f)", tostring(self.center), self.radius) -end - ----检查点是否在圆内或圆上 ----@param point foundation.math.Vector2 ----@return boolean -function Circle:contains(point) - return ShapeIntersector.circleContainsPoint(self, point) -end - ----移动圆(修改当前圆) ----@param v foundation.math.Vector2 | number ----@return foundation.shape.Circle 自身引用 -function Circle:move(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - self.center.x = self.center.x + moveX - self.center.y = self.center.y + moveY - return self -end - ----获取移动后的圆副本 ----@param v foundation.math.Vector2 | number ----@return foundation.shape.Circle -function Circle:moved(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - return Circle.create(Vector2.create(self.center.x + moveX, self.center.y + moveY), self.radius) -end - ----旋转圆(修改当前圆) ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为圆心 ----@return foundation.shape.Circle 自身引用 -function Circle:rotate(rad, center) - if center then - local dx = self.center.x - center.x - local dy = self.center.y - center.y - local cosA, sinA = math.cos(rad), math.sin(rad) - self.center.x = center.x + dx * cosA - dy * sinA - self.center.y = center.y + dx * sinA + dy * cosA - end - return self -end - ----旋转圆(修改当前圆) ----@param angle number 旋转角度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为圆心 ----@return foundation.shape.Circle 自身引用 -function Circle:degreeRotate(angle, center) - return self:rotate(math.rad(angle), center) -end - ----获取旋转后的圆副本 ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为圆心 ----@return foundation.shape.Circle -function Circle:rotated(rad, center) - local result = Circle.create(self.center:clone(), self.radius) - return result:rotate(rad, center) -end - ----获取旋转后的圆副本 ----@param angle number 旋转角度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为圆心 ----@return foundation.shape.Circle -function Circle:degreeRotated(angle, center) - return self:rotated(math.rad(angle), center) -end - ----缩放圆(修改当前圆) ----@param scale number|foundation.math.Vector2 缩放比例 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为圆心 ----@return foundation.shape.Circle 自身引用 -function Circle:scale(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self.center - - self.radius = self.radius * math.sqrt(scaleX * scaleY) - local dx = self.center.x - center.x - local dy = self.center.y - center.y - self.center.x = center.x + dx * scaleX - self.center.y = center.y + dy * scaleY - return self -end - ----获取缩放后的圆副本 ----@param scale number|foundation.math.Vector2 缩放比例 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为圆心 ----@return foundation.shape.Circle -function Circle:scaled(scale, center) - local result = Circle.create(self.center:clone(), self.radius) - return result:scale(scale, center) -end - ----检查与其他形状的相交 ----@param other any ----@return boolean, foundation.math.Vector2[] | nil -function Circle:intersects(other) - return ShapeIntersector.intersect(self, other) -end - ----只检查是否与其他形状相交,不计算交点 ----@param other any ----@return boolean -function Circle:hasIntersection(other) - return ShapeIntersector.hasIntersection(self, other) -end - ----计算圆的面积 ----@return number 圆的面积 -function Circle:area() - return math.pi * self.radius * self.radius -end - ----计算圆的周长 ----@return number 圆的周长 -function Circle:getPerimeter() - return 2 * math.pi * self.radius -end - ----计算圆的中心 ----@return foundation.math.Vector2 圆心位置 -function Circle:getCenter() - return self.center:clone() -end - ----获取圆的AABB包围盒 ----@return number, number, number, number -function Circle:AABB() - local cx, cy = self.center.x, self.center.y - local r = self.radius - return cx - r, cx + r, cy - r, cy + r -end - ----计算圆的包围盒宽高 ----@return number, number -function Circle:getBoundingBoxSize() - return self.radius * 2, self.radius * 2 -end - ----计算圆的重心 ----@return foundation.math.Vector2 圆心位置 -function Circle:centroid() - return self.center:clone() -end - ----计算点到圆的最近点 ----@param point foundation.math.Vector2 要检查的点 ----@param boundary boolean 是否限制在边界内,默认为false ----@return foundation.math.Vector2 圆上最近的点 ----@overload fun(self:foundation.shape.Circle, point:foundation.math.Vector2): foundation.math.Vector2 -function Circle:closestPoint(point, boundary) - local dir = point - self.center - local dist = dir:length() - if boundary then - if dist <= 1e-10 then - return Vector2.create(self.center.x + self.radius, self.center.y) - end - elseif dist <= self.radius then - return point:clone() - end - local normalized_dir = dir / dist - return self.center + normalized_dir * self.radius -end - ----计算点到圆的距离 ----@param point foundation.math.Vector2 要检查的点 ----@return number 点到圆的距离 -function Circle:distanceToPoint(point) - local dist = (point - self.center):length() - return math.abs(dist - self.radius) -end - ----将点投影到圆上 ----@param point foundation.math.Vector2 要投影的点 ----@return foundation.math.Vector2 投影点 -function Circle:projectPoint(point) - return self:closestPoint(point, true) -end - ----检查点是否在圆上 ----@param point foundation.math.Vector2 要检查的点 ----@param tolerance number|nil 容差,默认为1e-10 ----@return boolean 点是否在圆上 ----@overload fun(self:foundation.shape.Circle, point:foundation.math.Vector2): boolean -function Circle:containsPoint(point, tolerance) - tolerance = tolerance or 1e-10 - local dist = (point - self.center):length() - return math.abs(dist - self.radius) <= tolerance -end - ----复制圆 ----@return foundation.shape.Circle 圆的副本 -function Circle:clone() - return Circle.create(self.center:clone(), self.radius) -end - -ffi.metatype("foundation_shape_Circle", Circle) - -return Circle \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua deleted file mode 100644 index c5bad6bf..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ellipse.lua +++ /dev/null @@ -1,539 +0,0 @@ -local ffi = require("ffi") - -local type = type -local tostring = tostring -local string = string -local math = math -local table = table -local error = error -local rawset = rawset -local setmetatable = setmetatable - -local Vector2 = require("foundation.math.Vector2") -local Segment = require("foundation.shape.Segment") -local ShapeIntersector = require("foundation.shape.ShapeIntersector") - -ffi.cdef [[ -typedef struct { - foundation_math_Vector2 center; - double rx; - double ry; - foundation_math_Vector2 direction; -} foundation_shape_Ellipse; -]] - ----@class foundation.shape.Ellipse ----@field center foundation.math.Vector2 椭圆中心 ----@field rx number x半轴长度(横向半径) ----@field ry number y半轴长度(纵向半径) ----@field direction foundation.math.Vector2 方向(归一化向量,指向椭圆x轴正半轴方向) -local Ellipse = {} -Ellipse.__type = "foundation.shape.Ellipse" - ----@param self foundation.shape.Ellipse ----@param key any ----@return any -function Ellipse.__index(self, key) - if key == "center" then - return self.__data.center - elseif key == "rx" then - return self.__data.rx - elseif key == "ry" then - return self.__data.ry - elseif key == "direction" then - return self.__data.direction - end - return Ellipse[key] -end - ----@param self foundation.shape.Ellipse ----@param key any ----@param value any -function Ellipse.__newindex(self, key, value) - if key == "center" then - self.__data.center = value - elseif key == "rx" then - self.__data.rx = value - elseif key == "ry" then - self.__data.ry = value - elseif key == "direction" then - self.__data.direction = value - else - rawset(self, key, value) - end -end - ----创建一个新的椭圆 ----@param center foundation.math.Vector2 椭圆中心 ----@param rx number x半轴长度(横向半径) ----@param ry number y半轴长度(纵向半径) ----@param direction foundation.math.Vector2|nil 方向向量,默认为(1,0) ----@return foundation.shape.Ellipse -function Ellipse.create(center, rx, ry, direction) - direction = direction or Vector2.create(1, 0) - direction = direction:normalized() - local ellipse = ffi.new("foundation_shape_Ellipse", center, rx, ry, direction) - local result = { - __data = ellipse, - } - ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return setmetatable(result, Ellipse) -end - ----使用弧度创建椭圆 ----@param center foundation.math.Vector2 椭圆中心 ----@param rx number x半轴长度(横向半径) ----@param ry number y半轴长度(纵向半径) ----@param rad number 方向弧度 ----@return foundation.shape.Ellipse -function Ellipse.createFromRad(center, rx, ry, rad) - local dir = Vector2.createFromRad(rad) - return Ellipse.create(center, rx, ry, dir) -end - ----使用角度单位创建椭圆 ----@param center foundation.math.Vector2 椭圆中心 ----@param rx number x半轴长度(横向半径) ----@param ry number y半轴长度(纵向半径) ----@param angle number 椭圆方向角度(角度) ----@return foundation.shape.Ellipse -function Ellipse.createFromAngle(center, rx, ry, angle) - return Ellipse.createFromRad(center, rx, ry, math.rad(angle)) -end - ----椭圆相等比较 ----@param a foundation.shape.Ellipse ----@param b foundation.shape.Ellipse ----@return boolean -function Ellipse.__eq(a, b) - return a.center == b.center and - math.abs(a.rx - b.rx) <= 1e-10 and - math.abs(a.ry - b.ry) <= 1e-10 and - a.direction == b.direction -end - ----椭圆的字符串表示 ----@param self foundation.shape.Ellipse ----@return string -function Ellipse.__tostring(self) - return string.format("Ellipse(center=%s, rx=%f, ry=%f, direction=%s)", - tostring(self.center), self.rx, self.ry, tostring(self.direction)) -end - ----获取椭圆上指定角度的点 ----@param angle number 角度(弧度) ----@return foundation.math.Vector2 -function Ellipse:getPointAtAngle(angle) - local baseAngle = self.direction:angle() - - local cos_angle = math.cos(angle) - local sin_angle = math.sin(angle) - - local x = self.rx * cos_angle - local y = self.ry * sin_angle - - local cos_rotation = math.cos(baseAngle) - local sin_rotation = math.sin(baseAngle) - - local px = cos_rotation * x - sin_rotation * y + self.center.x - local py = sin_rotation * x + cos_rotation * y + self.center.y - - return Vector2.create(px, py) -end - ----将椭圆离散化为一系列点 ----@param segments number 分段数 ----@return foundation.math.Vector2[] -function Ellipse:discretize(segments) - segments = segments or 30 - local points = {} - - for i = 0, segments do - local angle = 2 * math.pi * i / segments - points[i + 1] = self:getPointAtAngle(angle) - end - - return points -end - ----获取椭圆的离散顶点(重命名discretize以保持一致性) ----@param segments number 分段数 ----@return foundation.math.Vector2[] -function Ellipse:getVertices(segments) - return self:discretize(segments or 30) -end - ----获取椭圆的离散边缘线段 ----@param segments number 分段数 ----@return foundation.shape.Segment[] -function Ellipse:getEdges(segments) - segments = segments or 30 - local points = self:discretize(segments) - local segs = {} - - for i = 1, segments do - local nextIndex = (i % segments) + 1 - segs[i] = Segment.create(points[i], points[nextIndex]) - end - - return segs -end - ----计算椭圆的内心 ----@return foundation.math.Vector2 椭圆的内心 -function Ellipse:incenter() - return self.center:clone() -end - ----计算椭圆的内切圆半径 ----@return number 椭圆的内切圆半径 -function Ellipse:inradius() - return math.min(self.rx, self.ry) -end - ----计算椭圆的外心 ----@return foundation.math.Vector2 椭圆的外心 -function Ellipse:circumcenter() - return self.center:clone() -end - ----计算椭圆的外接圆半径 ----@return number 椭圆的外接圆半径 -function Ellipse:circumradius() - return math.max(self.rx, self.ry) -end - ----移动椭圆(修改当前椭圆) ----@param v foundation.math.Vector2 | number ----@return foundation.shape.Ellipse 自身引用 -function Ellipse:move(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - self.center.x = self.center.x + moveX - self.center.y = self.center.y + moveY - return self -end - ----获取移动后的椭圆副本 ----@param v foundation.math.Vector2 | number ----@return foundation.shape.Ellipse -function Ellipse:moved(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - return Ellipse.create( - Vector2.create(self.center.x + moveX, self.center.y + moveY), - self.rx, self.ry, self.direction - ) -end - ----旋转椭圆(修改当前椭圆) ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为椭圆中心 ----@return foundation.shape.Ellipse 自身引用 -function Ellipse:rotate(rad, center) - self.direction = self.direction:rotated(rad) - if center then - local dx = self.center.x - center.x - local dy = self.center.y - center.y - local cosA, sinA = math.cos(rad), math.sin(rad) - self.center.x = center.x + dx * cosA - dy * sinA - self.center.y = center.y + dx * sinA + dy * cosA - end - return self -end - ----旋转椭圆(修改当前椭圆) ----@param angle number 旋转角度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为椭圆中心 ----@return foundation.shape.Ellipse 自身引用 -function Ellipse:degreeRotate(angle, center) - return self:rotate(math.rad(angle), center) -end - ----获取旋转后的椭圆副本 ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为椭圆中心 ----@return foundation.shape.Ellipse -function Ellipse:rotated(rad, center) - local result = Ellipse.create(self.center:clone(), self.rx, self.ry, self.direction:clone()) - return result:rotate(rad, center) -end - ----获取旋转后的椭圆副本 ----@param angle number 旋转角度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为椭圆中心 ----@return foundation.shape.Ellipse -function Ellipse:degreeRotated(angle, center) - return self:rotated(math.rad(angle), center) -end - ----缩放椭圆(修改当前椭圆) ----@param scale number|foundation.math.Vector2 缩放比例 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为椭圆中心 ----@return foundation.shape.Ellipse 自身引用 -function Ellipse:scale(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self.center - - self.rx = self.rx * scaleX - self.ry = self.ry * scaleY - local dx = self.center.x - center.x - local dy = self.center.y - center.y - self.center.x = center.x + dx * scaleX - self.center.y = center.y + dy * scaleY - return self -end - ----获取缩放后的椭圆副本 ----@param scale number|foundation.math.Vector2 缩放比例 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为椭圆中心 ----@return foundation.shape.Ellipse -function Ellipse:scaled(scale, center) - local result = Ellipse.create(self.center:clone(), self.rx, self.ry, self.direction:clone()) - return result:scale(scale, center) -end - ----检查点是否在椭圆内或椭圆上 ----@param point foundation.math.Vector2 ----@return boolean -function Ellipse:contains(point) - return ShapeIntersector.ellipseContainsPoint(self, point) -end - ----检查与其他形状的相交 ----@param other any ----@return boolean, foundation.math.Vector2[] | nil -function Ellipse:intersects(other) - return ShapeIntersector.intersect(self, other) -end - ----只检查是否与其他形状相交,不计算交点 ----@param other any ----@return boolean -function Ellipse:hasIntersection(other) - return ShapeIntersector.hasIntersection(self, other) -end - ----计算椭圆的面积 ----@return number 椭圆的面积 -function Ellipse:area() - return math.pi * self.rx * self.ry -end - ----计算椭圆的周长(近似值) ----@return number 椭圆的周长近似值 -function Ellipse:getPerimeter() - local h = (self.rx - self.ry) ^ 2 / (self.rx + self.ry) ^ 2 - return math.pi * (self.rx + self.ry) * (1 + 3 * h / (10 + math.sqrt(4 - 3 * h))) -end - ----计算椭圆的中心 ----@return foundation.math.Vector2 椭圆中心点 -function Ellipse:getCenter() - return self.center:clone() -end - ----获取椭圆的AABB包围盒 ----@return number, number, number, number -function Ellipse:AABB() - local baseAngle = self.direction:angle() - local cos_angle = math.cos(baseAngle) - local sin_angle = math.sin(baseAngle) - - local x_axis = Vector2.create(self.rx * cos_angle, self.rx * sin_angle) - local y_axis = Vector2.create(-self.ry * sin_angle, self.ry * cos_angle) - - local half_width = math.sqrt(x_axis.x * x_axis.x + y_axis.x * y_axis.x) - local half_height = math.sqrt(x_axis.y * x_axis.y + y_axis.y * y_axis.y) - - return self.center.x - half_width, self.center.x + half_width, self.center.y - half_height, self.center.y + half_height -end - ----计算椭圆的包围盒宽高 ----@return number, number -function Ellipse:getBoundingBoxSize() - local minX, maxX, minY, maxY = self:AABB() - return maxX - minX, maxY - minY -end - ----计算椭圆的重心 ----@return foundation.math.Vector2 椭圆中心点 -function Ellipse:centroid() - return self.center:clone() -end - ----计算点到椭圆的最近点 ----@param point foundation.math.Vector2 要检查的点 ----@param boundary boolean 是否限制在边界上,默认为false ----@return foundation.math.Vector2 椭圆上最近的点 ----@overload fun(self:foundation.shape.Ellipse, point:foundation.math.Vector2): foundation.math.Vector2 -function Ellipse:closestPoint(point, boundary) - if not boundary and self:contains(point) then - return point:clone() - end - - local baseAngle = self.direction:angle() - local cos_rotation = math.cos(-baseAngle) - local sin_rotation = math.sin(-baseAngle) - - local dx = point.x - self.center.x - local dy = point.y - self.center.y - - local x = cos_rotation * dx - sin_rotation * dy - local y = sin_rotation * dx + cos_rotation * dy - - local px = x / self.rx - local py = y / self.ry - - local length = math.sqrt(px * px + py * py) - if length < 1e-10 then - px = self.rx - py = 0 - else - local px1 = px / length * self.rx - local py1 = py / length * self.ry - local px2 = -px / length * self.rx - local py2 = -py / length * self.ry - - local cos_world = math.cos(baseAngle) - local sin_world = math.sin(baseAngle) - - local result_x1 = cos_world * px1 - sin_world * py1 + self.center.x - local result_y1 = sin_world * px1 + cos_world * py1 + self.center.y - local point1 = Vector2.create(result_x1, result_y1) - - local result_x2 = cos_world * px2 - sin_world * py2 + self.center.x - local result_y2 = sin_world * px2 + cos_world * py2 + self.center.y - local point2 = Vector2.create(result_x2, result_y2) - - local dist1 = (point - point1):length() - local dist2 = (point - point2):length() - return dist1 <= dist2 and point1 or point2 - end - - local cos_world = math.cos(baseAngle) - local sin_world = math.sin(baseAngle) - local result_x = cos_world * px - sin_world * py + self.center.x - local result_y = sin_world * px + cos_world * py + self.center.y - - return Vector2.create(result_x, result_y) -end - ----计算点到椭圆的距离 ----@param point foundation.math.Vector2 要检查的点 ----@return number 点到椭圆的距离 -function Ellipse:distanceToPoint(point) - if self:contains(point) then - return 0 - end - - local closestPoint = self:closestPoint(point, true) - return (point - closestPoint):length() -end - ----将点投影到椭圆上 ----@param point foundation.math.Vector2 要投影的点 ----@return foundation.math.Vector2 投影点 -function Ellipse:projectPoint(point) - return self:closestPoint(point, true) -end - ----检查点是否在椭圆上 ----@param point foundation.math.Vector2 要检查的点 ----@param tolerance number|nil 容差,默认为1e-10 ----@return boolean 点是否在椭圆上 ----@overload fun(self:foundation.shape.Ellipse, point:foundation.math.Vector2): boolean -function Ellipse:containsPoint(point, tolerance) - tolerance = tolerance or 1e-10 - local projPoint = self:closestPoint(point, true) - local distance = (point - projPoint):length() - return distance <= tolerance -end - ----创建椭圆的一个副本 ----@return foundation.shape.Ellipse -function Ellipse:clone() - return Ellipse.create(self.center:clone(), self.rx, self.ry, self.direction:clone()) -end - ----获取按弧长等分的点集和线段集 ----@param num_points number 期望的点数(包含起点) ----@param tolerance number|nil 弧长误差容差,默认为1e-6 ----@return foundation.math.Vector2[] -function Ellipse:getEqualArcLengthPoints(num_points, tolerance) - num_points = num_points or 30 - tolerance = tolerance or 1e-6 - if num_points < 2 then - error("Number of points must be at least 2") - end - - local total_length = self:getPerimeter() - local target_segment_length = total_length / num_points - local points = { self:getPointAtAngle(0) } - local current_length = 0 - local angle = 0 - local step = 2 * math.pi / 100 - local last_point = points[1] - local last_angle = 0 - - while #points < num_points and angle < 2 * math.pi do - angle = math.min(angle + step, 2 * math.pi) - local point = self:getPointAtAngle(angle) - local segment_length = (point - last_point):length() - current_length = current_length + segment_length - - if current_length >= target_segment_length - tolerance or angle >= 2 * math.pi then - table.insert(points, point) - last_point = point - last_angle = angle - current_length = 0 - - local remaining_points = num_points - #points - if remaining_points > 0 then - local remaining_angle = 2 * math.pi - angle - step = remaining_angle / (remaining_points * 2) - end - else - last_point = point - last_angle = angle - end - end - - if math.abs(angle - 2 * math.pi) > tolerance and #points == num_points then - points[#points] = self:getPointAtAngle(2 * math.pi) - end - - return points -end - ----获取按弧长等分的线段集 ----@param num_segments number 期望的线段数(包含起点和终点) ----@param tolerance number|nil 弧长误差容差,默认为1e-6 ----@return foundation.shape.Segment[] -function Ellipse:getEqualArcLengthSegments(num_segments, tolerance) - local points = self:getEqualArcLengthPoints(num_segments + 1, tolerance) - local segments = {} - - for i = 1, #points - 1 do - segments[i] = Segment.create(points[i], points[i + 1]) - end - - return segments -end - -ffi.metatype("foundation_shape_Ellipse", Ellipse) - -return Ellipse \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/LICENSE b/game/packages/thlib-scripts-v2/foundation/shape/LICENSE deleted file mode 100644 index 6e56e2e4..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 OLC - -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. - -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. \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua b/game/packages/thlib-scripts-v2/foundation/shape/Line.lua deleted file mode 100644 index 4493a3d8..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/Line.lua +++ /dev/null @@ -1,350 +0,0 @@ -local ffi = require("ffi") - -local type = type -local math = math -local tostring = tostring -local string = string -local rawset = rawset -local setmetatable = setmetatable - -local Vector2 = require("foundation.math.Vector2") -local ShapeIntersector = require("foundation.shape.ShapeIntersector") - -ffi.cdef [[ -typedef struct { - foundation_math_Vector2 point; - foundation_math_Vector2 direction; -} foundation_shape_Line; -]] - ----@class foundation.shape.Line ----@field point foundation.math.Vector2 直线上的一点 ----@field direction foundation.math.Vector2 直线的方向向量 -local Line = {} -Line.__type = "foundation.shape.Line" - ----@param self foundation.shape.Line ----@param key any ----@return any -function Line.__index(self, key) - if key == "point" then - return self.__data.point - elseif key == "direction" then - return self.__data.direction - end - return Line[key] -end - ----@param self foundation.shape.Line ----@param key any ----@param value any -function Line.__newindex(self, key, value) - if key == "point" then - self.__data.point = value - elseif key == "direction" then - self.__data.direction = value - else - rawset(self, key, value) - end -end - ----创建一条新的直线,由一个点和方向向量确定 ----@param point foundation.math.Vector2 直线上的点 ----@param direction foundation.math.Vector2 方向向量 ----@return foundation.shape.Line -function Line.create(point, direction) - local dist = direction and direction:length() or 0 - if dist <= 1e-10 then - direction = Vector2.create(1, 0) - elseif dist ~= 1 then - ---@diagnostic disable-next-line: need-check-nil - direction = direction:normalized() - else - ---@diagnostic disable-next-line: need-check-nil - direction = direction:clone() - end - - local line = ffi.new("foundation_shape_Line", point, direction) - local result = { - __data = line, - } - ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return setmetatable(result, Line) -end - ----根据两个点创建一条直线 ----@param p1 foundation.math.Vector2 第一个点 ----@param p2 foundation.math.Vector2 第二个点 ----@return foundation.shape.Line -function Line.createFromPoints(p1, p2) - local direction = p2 - p1 - return Line.create(p1, direction) -end - ----根据一个点、弧度创建一条直线 ----@param point foundation.math.Vector2 起始点 ----@param rad number 弧度 ----@return foundation.shape.Line -function Line.createFromPointAndRad(point, rad) - local direction = Vector2.createFromRad(rad) - return Line.create(point, direction) -end - ----根据一个点、角度创建一条直线 ----@param point foundation.math.Vector2 起始点 ----@param angle number 角度 ----@return foundation.shape.Line -function Line.createFromPointAndAngle(point, angle) - local direction = Vector2.createFromAngle(angle) - return Line.create(point, direction) -end - ----直线相等比较 ----@param a foundation.shape.Line ----@param b foundation.shape.Line ----@return boolean -function Line.__eq(a, b) - local dir_cross = a.direction:cross(b.direction) - if math.abs(dir_cross) > 1e-10 then - return false - end - local point_diff = b.point - a.point - return math.abs(point_diff:cross(a.direction)) <= 1e-10 -end - ----直线的字符串表示 ----@param self foundation.shape.Line ----@return string -function Line.__tostring(self) - return string.format("Line(%s, dir=%s)", tostring(self.point), tostring(self.direction)) -end - ----获取直线上相对 point 指定距离的点 ----@param length number 距离 ----@return foundation.math.Vector2 -function Line:getPoint(length) - return self.point + self.direction * length -end - ----计算直线的中心 ----@return foundation.math.Vector2 -function Line:getCenter() - return self.point -end - ----获取直线的AABB包围盒 ----@return number, number, number, number -function Line:AABB() - if math.abs(self.direction.x) < 1e-10 then - -- 垂直线 - return self.point.x, self.point.x, -math.huge, math.huge - elseif math.abs(self.direction.y) < 1e-10 then - -- 水平线 - return -math.huge, math.huge, self.point.y, self.point.y - else - -- 斜线 - return -math.huge, math.huge, -math.huge, math.huge - end -end - ----计算直线的包围盒宽高 ----@return number, number -function Line:getBoundingBoxSize() - if math.abs(self.direction.x) < 1e-10 then - return 0, math.huge - elseif math.abs(self.direction.y) < 1e-10 then - return math.huge, 0 - end - return math.huge, math.huge -end - ----获取直线的角度(弧度) ----@return number 直线的角度,单位为弧度 -function Line:angle() - return math.atan2(self.direction.y, self.direction.x) -end - ----获取直线的角度(度) ----@return number 直线的角度,单位为度 -function Line:degreeAngle() - return math.deg(self:angle()) -end - ----平移直线(更改当前直线) ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Line 平移后的直线(自身引用) -function Line:move(v) - local moveX, moveY - if type(v) == "number" then - moveX = v - moveY = v - else - moveX = v.x - moveY = v.y - end - self.point.x = self.point.x + moveX - self.point.y = self.point.y + moveY - return self -end - ----获取当前直线平移指定距离的副本 ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Line 移动后的直线副本 -function Line:moved(v) - local moveX, moveY - if type(v) == "number" then - moveX = v - moveY = v - else - moveX = v.x - moveY = v.y - end - return Line.create( - Vector2.create(self.point.x + moveX, self.point.y + moveY), - self.direction:clone() - ) -end - ----将当前直线旋转指定弧度(更改当前直线) ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Line 旋转后的直线(自身引用) ----@overload fun(self: foundation.shape.Line, rad: number): foundation.shape.Line 将当前直线绕定义的点旋转指定弧度(更改当前直线) -function Line:rotate(rad, center) - center = center or self.point - local cosRad = math.cos(rad) - local sinRad = math.sin(rad) - local v = self.direction - local x = v.x * cosRad - v.y * sinRad - local y = v.x * sinRad + v.y * cosRad - self.direction.x = x - self.direction.y = y - return self -end - ----将当前直线旋转指定角度(更改当前直线) ----@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Line 旋转后的直线(自身引用) ----@overload fun(self: foundation.shape.Line, angle: number): foundation.shape.Line 将当前直线绕定义的点旋转指定角度(更改当前直线) -function Line:degreeRotate(angle, center) - angle = math.rad(angle) - return self:rotate(angle, center) -end - ----获取当前直线旋转指定弧度的副本 ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Line 旋转后的直线副本 ----@overload fun(self: foundation.shape.Line, rad: number): foundation.shape.Line 获取当前直线绕定义的点旋转指定弧度的副本 -function Line:rotated(rad, center) - center = center or self.point - local cosRad = math.cos(rad) - local sinRad = math.sin(rad) - local v = self.direction - return Line.create( - Vector2.create(v.x * cosRad - v.y * sinRad + center.x, v.x * sinRad + v.y * cosRad + center.y), - Vector2.create(v.x * cosRad - v.y * sinRad, v.x * sinRad + v.y * cosRad) - ) -end - ----获取当前直线旋转指定角度的副本 ----@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Line 旋转后的直线副本 -------@overload fun(self: foundation.shape.Line, angle: number): foundation.shape.Line 获取当前直线绕定义的点旋转指定角度的副本 -function Line:degreeRotated(angle, center) - angle = math.rad(angle) - return self:rotated(angle, center) -end - ----缩放直线(更改当前直线) ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为直线上的点 ----@return foundation.shape.Line 自身引用 -function Line:scale(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self.point - - local dx = self.point.x - center.x - local dy = self.point.y - center.y - self.point.x = center.x + dx * scaleX - self.point.y = center.y + dy * scaleY - return self -end - ----获取缩放后的直线副本 ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为直线上的点 ----@return foundation.shape.Line -function Line:scaled(scale, center) - local result = Line.create(self.point:clone(), self.direction:clone()) - return result:scale(scale, center) -end - ----检查与其他形状的相交 ----@param other any ----@return boolean, foundation.math.Vector2[] | nil -function Line:intersects(other) - return ShapeIntersector.intersect(self, other) -end - ----检查是否与其他形状相交,只返回是否相交的布尔值 ----@param other any ----@return boolean -function Line:hasIntersection(other) - return ShapeIntersector.hasIntersection(self, other) -end - ----计算点到直线的最近点 ----@param point foundation.math.Vector2 点 ----@return foundation.math.Vector2 最近点 -function Line:closestPoint(point) - return self:projectPoint(point) -end - ----计算点到直线的距离 ----@param point foundation.math.Vector2 点 ----@return number 距离 -function Line:distanceToPoint(point) - local point_vec = point - self.point - local proj_length = point_vec:dot(self.direction) - local proj_point = self.point + self.direction * proj_length - return (point - proj_point):length() -end - ----检查点是否在直线上 ----@param point foundation.math.Vector2 点 ----@param tolerance number 误差容忍度,默认为1e-10 ----@return boolean -function Line:containsPoint(point, tolerance) - tolerance = tolerance or 1e-10 - local point_vec = point - self.point - local cross = point_vec:cross(self.direction) - return math.abs(cross) <= tolerance -end - ----获取点在直线上的投影 ----@param point foundation.math.Vector2 点 ----@return foundation.math.Vector2 投影点 -function Line:projectPoint(point) - local dir = self.direction - local point_vec = point - self.point - local proj_length = point_vec:dot(dir) - return self.point + dir * proj_length -end - ----复制当前直线 ----@return foundation.shape.Line 复制的直线 -function Line:clone() - return Line.create(self.point:clone(), self.direction:clone()) -end - -ffi.metatype("foundation_shape_Line", Line) - -return Line diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua b/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua deleted file mode 100644 index 443c2dae..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/Polygon.lua +++ /dev/null @@ -1,708 +0,0 @@ -local ffi = require("ffi") - -local type = type -local ipairs = ipairs -local table = table -local math = math -local tostring = tostring -local string = string -local error = error -local rawset = rawset -local setmetatable = setmetatable - -local Vector2 = require("foundation.math.Vector2") -local Segment = require("foundation.shape.Segment") -local Triangle = require("foundation.shape.Triangle") -local ShapeIntersector = require("foundation.shape.ShapeIntersector") - -ffi.cdef [[ -typedef struct { - int size; - foundation_math_Vector2* points; -} foundation_shape_Polygon; -]] - ----@class foundation.shape.Polygon ----@field size number 多边形顶点数量 ----@field points foundation.math.Vector2[] 多边形的顶点数组 -local Polygon = {} -Polygon.__type = "foundation.shape.Polygon" - ----@param self foundation.shape.Polygon ----@param key string ----@return any -function Polygon.__index(self, key) - if key == "size" then - return self.__data.size - elseif key == "points" then - return self.__data.points - end - return Polygon[key] -end - ----@param t foundation.math.Vector2[] ----@return number, foundation.math.Vector2[] -local function buildNewVector2Array(t) - local size = #t - local points_array = ffi.new("foundation_math_Vector2[?]", size) - - for i = 1, size do - points_array[i - 1] = Vector2.create(t[i].x, t[i].y) - end - - return size, points_array -end - ----@param self foundation.shape.Polygon ----@param key any ----@param value any -function Polygon.__newindex(self, key, value) - if key == "size" then - error("cannot modify size directly") - elseif key == "points" then - local size, points_array = buildNewVector2Array(value) - self.__data.size = size - self.__data.points = points_array - self.__data_points_ref = points_array - else - rawset(self, key, value) - end -end - ----创建一个多边形 ----@param points foundation.math.Vector2[] 多边形的顶点数组,按顺序连线并首尾相接 ----@return foundation.shape.Polygon -function Polygon.create(points) - if not points or #points < 3 then - error("Polygon must have at least 3 points") - end - - local size, points_array = buildNewVector2Array(points) - - local polygon = ffi.new("foundation_shape_Polygon", size, points_array) - local result = { - __data = polygon, - __data_points_ref = points_array, - } - ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return setmetatable(result, Polygon) -end - ----创建一个正多边形 ----@param center foundation.math.Vector2 多边形的中心 ----@param radius number 外接圆半径 ----@param numSides number 边数 ----@param startRad number 起始角度(弧度) ----@return foundation.shape.Polygon -function Polygon.createRegularRad(center, radius, numSides, startRad) - startRad = startRad or 0 - local points = {} - local angleStep = 2 * math.pi / numSides - - for i = 1, numSides do - local angle = startRad + (i - 1) * angleStep - local x = center.x + radius * math.cos(angle) - local y = center.y + radius * math.sin(angle) - points[i] = Vector2.create(x, y) - end - - return Polygon.create(points) -end - ----创建一个正多边形 ----@param center foundation.math.Vector2 多边形的中心 ----@param radius number 外接圆半径 ----@param numSides number 边数 ----@param startAngle number 起始角度(角度) ----@return foundation.shape.Polygon -function Polygon.createRegularDegree(center, radius, numSides, startAngle) - return Polygon.createRegularRad(center, radius, numSides, math.rad(startAngle)) -end - ----多边形相等比较 ----@param a foundation.shape.Polygon 第一个多边形 ----@param b foundation.shape.Polygon 第二个多边形 ----@return boolean -function Polygon.__eq(a, b) - if a.size ~= b.size then - return false - end - - for i = 0, a.size - 1 do - if a.points[i] ~= b.points[i] then - return false - end - end - - return true -end - ----多边形转字符串表示 ----@param self foundation.shape.Polygon ----@return string -function Polygon.__tostring(self) - local pointsStr = {} - for i = 0, self.size - 1 do - pointsStr[i + 1] = tostring(self.points[i]) - end - return string.format("Polygon(%s)", table.concat(pointsStr, ", ")) -end - ----获取多边形的边数 ----@return number -function Polygon:getEdgeCount() - return self.size -end - ----获取多边形的所有边(线段表示) ----@return foundation.shape.Segment[] -function Polygon:getEdges() - local edges = {} - - for i = 0, self.size - 1 do - local nextIdx = (i + 1) % self.size - edges[i + 1] = Segment.create(self.points[i], self.points[nextIdx]) - end - - return edges -end - ----获取多边形的顶点数组 ----@return foundation.math.Vector2[] -function Polygon:getVertices() - local vertices = {} - for i = 0, self.size - 1 do - vertices[i + 1] = self.points[i]:clone() - end - return vertices -end - ----获取多边形的AABB包围盒 ----@return number, number, number, number -function Polygon:AABB() - local minX, minY = math.huge, math.huge - local maxX, maxY = -math.huge, -math.huge - - for i = 0, self.size - 1 do - local point = self.points[i] - minX = math.min(minX, point.x) - minY = math.min(minY, point.y) - maxX = math.max(maxX, point.x) - maxY = math.max(maxY, point.y) - end - - return minX, maxX, minY, maxY -end - ----计算多边形的重心 ----@return foundation.math.Vector2 -function Polygon:centroid() - local totalArea = 0 - local centroidX = 0 - local centroidY = 0 - - local p0 = self.points[0] - for i = 1, self.size - 2 do - local p1 = self.points[i] - local p2 = self.points[i + 1] - - local area = math.abs((p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y)) / 2 - totalArea = totalArea + area - - local cx = (p0.x + p1.x + p2.x) / 3 - local cy = (p0.y + p1.y + p2.y) / 3 - - centroidX = centroidX + cx * area - centroidY = centroidY + cy * area - end - - if totalArea == 0 then - return self:getCenter() - end - - return Vector2.create(centroidX / totalArea, centroidY / totalArea) -end - ----计算多边形的中心 ----@return foundation.math.Vector2 -function Polygon:getCenter() - local minX, maxX, minY, maxY = self:AABB() - return Vector2.create((minX + maxX) / 2, (minY + maxY) / 2) -end - ----计算多边形的包围盒宽高 ----@return number, number -function Polygon:getBoundingBoxSize() - local minX, maxX, minY, maxY = self:AABB() - return maxX - minX, maxY - minY -end - ----计算多边形的面积 ----@return number -function Polygon:getArea() - local area = 0 - - for i = 0, self.size - 1 do - local j = (i + 1) % self.size - area = area + (self.points[i].x * self.points[j].y) - (self.points[j].x * self.points[i].y) - end - - return math.abs(area) / 2 -end - ----计算多边形的周长 ----@return number -function Polygon:getPerimeter() - local perimeter = 0 - - for i = 0, self.size - 1 do - local nextIdx = (i + 1) % self.size - perimeter = perimeter + (self.points[i] - self.points[nextIdx]):length() - end - - return perimeter -end - ----判断多边形是否为凸多边形 ----@return boolean -function Polygon:isConvex() - if self.size < 3 then - return false - end - - local sign = 0 - local allCollinear = true - - for i = 0, self.size - 1 do - local j = (i + 1) % self.size - local k = (j + 1) % self.size - - local dx1 = self.points[j].x - self.points[i].x - local dy1 = self.points[j].y - self.points[i].y - local dx2 = self.points[k].x - self.points[j].x - local dy2 = self.points[k].y - self.points[j].y - - local cross = dx1 * dy2 - dy1 * dx2 - local absCross = math.abs(cross) - - if absCross > 1e-10 then - allCollinear = false - end - - if i == 0 then - if absCross > 1e-10 then - sign = cross > 0 and 1 or -1 - end - elseif (cross > 1e-10 and sign < 0) or (cross < -1e-10 and sign > 0) or (sign == 0 and absCross > 1e-10) then - return false - end - end - - -- 如果所有点共线,不是合法的凸多边形 - if allCollinear then - return false - end - - return true -end - ----平移多边形(更改当前多边形) ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Polygon 平移后的多边形(自身引用) -function Polygon:move(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - - for i = 0, self.size - 1 do - self.points[i].x = self.points[i].x + moveX - self.points[i].y = self.points[i].y + moveY - end - - return self -end - ----获取当前多边形平移指定距离的副本 ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Polygon 移动后的多边形副本 -function Polygon:moved(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - - local newPoints = {} - for i = 0, self.size - 1 do - newPoints[i + 1] = Vector2.create(self.points[i].x + moveX, self.points[i].y + moveY) - end - - return Polygon.create(newPoints) -end - ----将当前多边形旋转指定弧度(更改当前多边形) ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为多边形重心 ----@return foundation.shape.Polygon 自身引用 -function Polygon:rotate(rad, center) - center = center or self:centroid() - local cosRad = math.cos(rad) - local sinRad = math.sin(rad) - - for i = 0, self.size - 1 do - local point = self.points[i] - local dx = point.x - center.x - local dy = point.y - center.y - point.x = center.x + dx * cosRad - dy * sinRad - point.y = center.y + dx * sinRad + dy * cosRad - end - - return self -end - ----将当前多边形旋转指定角度(更改当前多边形) ----@param angle number 旋转角度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为多边形重心 ----@return foundation.shape.Polygon 自身引用 -function Polygon:degreeRotate(angle, center) - return self:rotate(math.rad(angle), center) -end - ----获取当前多边形旋转指定弧度的副本 ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为多边形重心 ----@return foundation.shape.Polygon -function Polygon:rotated(rad, center) - local points = self:getVertices() - local result = Polygon.create(points) - return result:rotate(rad, center) -end - ----获取当前多边形旋转指定角度的副本 ----@param angle number 旋转角度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为多边形重心 ----@return foundation.shape.Polygon -function Polygon:degreeRotated(angle, center) - return self:rotated(math.rad(angle), center) -end - ----将当前多边形缩放指定倍数(更改当前多边形) ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2 缩放中心 ----@return foundation.shape.Polygon 缩放后的多边形(自身引用) ----@overload fun(self: foundation.shape.Polygon, scale: number): foundation.shape.Polygon 相对多边形中心点缩放指定倍数 -function Polygon:scale(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self:centroid() - - for i = 0, self.size - 1 do - self.points[i].x = (self.points[i].x - center.x) * scaleX + center.x - self.points[i].y = (self.points[i].y - center.y) * scaleY + center.y - end - - return self -end - ----获取当前多边形缩放指定倍数的副本 ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2 缩放中心 ----@return foundation.shape.Polygon 缩放后的多边形副本 ----@overload fun(self: foundation.shape.Polygon, scale: number): foundation.shape.Polygon 相对多边形中心点缩放指定倍数 -function Polygon:scaled(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self:centroid() - - local newPoints = {} - for i = 0, self.size - 1 do - newPoints[i + 1] = Vector2.create( - (self.points[i].x - center.x) * scaleX + center.x, - (self.points[i].y - center.y) * scaleY + center.y - ) - end - - return Polygon.create(newPoints) -end - ----判断点是否在多边形内(射线法) ----@param point foundation.math.Vector2 要判断的点 ----@return boolean 如果点在多边形内或边上则返回true,否则返回false -function Polygon:contains(point) - return ShapeIntersector.polygonContainsPoint(self, point) -end - ----检查与其他形状的相交 ----@param other any ----@return boolean, foundation.math.Vector2[] | nil -function Polygon:intersects(other) - return ShapeIntersector.intersect(self, other) -end - ----仅检查是否与其他形状相交 ----@param other any ----@return boolean -function Polygon:hasIntersection(other) - return ShapeIntersector.hasIntersection(self, other) -end - ----判断多边形是否包含另一个多边形 ----@param other foundation.shape.Polygon 另一个多边形 ----@return boolean 如果当前多边形完全包含另一个多边形则返回true,否则返回false -function Polygon:containsPolygon(other) - for i = 0, other.size - 1 do - if not self:contains(other.points[i]) then - return false - end - end - - return true -end - ----计算点到多边形的最近点 ----@param point foundation.math.Vector2 要检查的点 ----@param boundary boolean 是否限制在边界内,默认为false ----@return foundation.math.Vector2 多边形上最近的点 ----@overload fun(self: foundation.shape.Polygon, point: foundation.math.Vector2): foundation.math.Vector2 -function Polygon:closestPoint(point, boundary) - if not boundary and self:contains(point) then - return point:clone() - end - - local edges = self:getEdges() - local minDistance = math.huge - local closestPoint - - for _, edge in ipairs(edges) do - local edgeClosest = edge:closestPoint(point) - local distance = (point - edgeClosest):length() - - if distance < minDistance then - minDistance = distance - closestPoint = edgeClosest - end - end - - return closestPoint -end - ----计算点到多边形的距离 ----@param point foundation.math.Vector2 要检查的点 ----@return number 点到多边形的距离 -function Polygon:distanceToPoint(point) - if self:contains(point) then - return 0 - end - - local closestPoint = self:closestPoint(point) - return (point - closestPoint):length() -end - ----将点投影到多边形上(2D中与closest相同) ----@param point foundation.math.Vector2 要投影的点 ----@return foundation.math.Vector2 投影点 -function Polygon:projectPoint(point) - return self:closestPoint(point, true) -end - ----检查点是否在多边形上 ----@param point foundation.math.Vector2 要检查的点 ----@param tolerance number|nil 容差,默认为1e-10 ----@return boolean 点是否在多边形上 ----@overload fun(self: foundation.shape.Polygon, point: foundation.math.Vector2): boolean -function Polygon:containsPoint(point, tolerance) - tolerance = tolerance or 1e-10 - local dist = self:distanceToPoint(point) - if dist > tolerance then - return false - end - - for i = 0, self.size - 1 do - local edge = Segment.create(self.points[i], self.points[(i + 1) % self.size]) - if edge:containsPoint(point, tolerance) then - return true - end - end - - return false -end - ---region 三角剖分的辅助函数 ----获取前一个点索引 ----@param points table 点数组 ----@param i number 当前索引 ----@return number 前一个点的索引 -local function getPrev(points, i) - return i == 1 and #points or i - 1 -end - ----获取后一个点索引 ----@param points table 点数组 ----@param i number 当前索引 ----@return number 后一个点的索引 -local function getNext(points, i) - return i == #points and 1 or i + 1 -end - ----检查点是否为凸点 ----@param points table 点数组 ----@param i number 当前索引 ----@param isClockwise boolean 是否顺时针 ----@return boolean 是否为凸点 -local function isConvex(points, i, isClockwise) - local prev = getPrev(points, i) - local next = getNext(points, i) - - local v1 = points[prev].point - points[i].point - local v2 = points[next].point - points[i].point - - local cross = v1:cross(v2) - return (isClockwise and cross < 0) or (not isClockwise and cross > 0) -end - ----检查点是否在三角形内部 ----@param p foundation.math.Vector2 要检查的点 ----@param a foundation.math.Vector2 三角形顶点1 ----@param b foundation.math.Vector2 三角形顶点2 ----@param c foundation.math.Vector2 三角形顶点3 ----@param isClockwise boolean 是否顺时针 ----@return boolean 点是否在三角形内部 -local function isPointInTriangle(p, a, b, c, isClockwise) - local ab = b - a - local bc = c - b - local ca = a - c - - local ap = p - a - local bp = p - b - local cp = p - c - - local cross1 = ab:cross(ap) - local cross2 = bc:cross(bp) - local cross3 = ca:cross(cp) - - if isClockwise then - return cross1 >= 0 and cross2 >= 0 and cross3 >= 0 - else - return cross1 <= 0 and cross2 <= 0 and cross3 <= 0 - end -end - ----检查耳朵是否有效 ----@param points table 点数组 ----@param i number 当前索引 ----@param isClockwise boolean 是否顺时针 ----@return boolean 是否为有效的耳朵 -local function isEar(points, i, isClockwise) - if not isConvex(points, i, isClockwise) then - return false - end - - local prev = getPrev(points, i) - local next = getNext(points, i) - - local a = points[prev].point - local b = points[i].point - local c = points[next].point - - for j = 1, #points do - if j ~= prev and j ~= i and j ~= next then - if isPointInTriangle(points[j].point, a, b, c, isClockwise) then - return false - end - end - end - - return true -end ---endregion - ----将多边形三角剖分(简单多边形的三角剖分,基于耳切法) ----@return foundation.shape.Triangle[] 三角形数组 -function Polygon:triangulate() - local points = {} - for i = 0, self.size - 1 do - points[i + 1] = { index = i, point = self.points[i]:clone() } - end - local area = 0 - for i = 0, self.size - 1 do - local j = (i + 1) % self.size - area = area + (points[i + 1].point.x * points[j + 1].point.y) - (points[j + 1].point.x * points[i + 1].point.y) - end - local isClockwise = area < 0 - local triangles = {} - local remainingPoints = self.size - - local isConvexCache = {} - local isEarCache = {} - for i = 1, #points do - isConvexCache[i] = isConvex(points, i, isClockwise) - isEarCache[i] = isConvexCache[i] and isEar(points, i, isClockwise) - end - - while remainingPoints > 3 do - local foundEar = false - for i = 1, #points do - if points[i] and isEarCache[i] then - local prev, next = getPrev(points, i), getNext(points, i) - local a, b, c = points[prev].point, points[i].point, points[next].point - triangles[#triangles + 1] = Triangle.create(a, b, c) - points[i] = nil - - local newPoints = {} - for j = 1, #points do - if points[j] then - newPoints[#newPoints + 1] = points[j] - end - end - points = newPoints - remainingPoints = remainingPoints - 1 - - if points[prev] then - isConvexCache[prev] = isConvex(points, prev, isClockwise) - isEarCache[prev] = isConvexCache[prev] and isEar(points, prev, isClockwise) - end - - if points[next] then - isConvexCache[next] = isConvex(points, next, isClockwise) - isEarCache[next] = isConvexCache[next] and isEar(points, next, isClockwise) - end - - foundEar = true - break - end - end - if not foundEar then - break - end - end - if #points == 3 then - triangles[#triangles + 1] = Triangle.create(points[1].point, points[2].point, points[3].point) - end - return triangles -end - ----复制当前多边形 ----@return foundation.shape.Polygon 复制后的多边形 -function Polygon:clone() - local newPoints = {} - for i = 0, self.size - 1 do - newPoints[i + 1] = self.points[i]:clone() - end - return Polygon.create(newPoints) -end - -ffi.metatype("foundation_shape_Polygon", Polygon) - -return Polygon \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua b/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua deleted file mode 100644 index fa6c84e3..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/Ray.lua +++ /dev/null @@ -1,383 +0,0 @@ -local ffi = require("ffi") - -local type = type -local math = math -local tostring = tostring -local string = string -local rawset = rawset -local setmetatable = setmetatable - -local Vector2 = require("foundation.math.Vector2") -local ShapeIntersector = require("foundation.shape.ShapeIntersector") - -ffi.cdef [[ -typedef struct { - foundation_math_Vector2 point; - foundation_math_Vector2 direction; -} foundation_shape_Ray; -]] - ----@class foundation.shape.Ray ----@field point foundation.math.Vector2 射线的起始点 ----@field direction foundation.math.Vector2 射线的方向向量 -local Ray = {} -Ray.__type = "foundation.shape.Ray" - ----@param self foundation.shape.Ray ----@param key any ----@return any -function Ray.__index(self, key) - if key == "point" then - return self.__data.point - elseif key == "direction" then - return self.__data.direction - end - return Ray[key] -end - ----@param self foundation.shape.Ray ----@param key any ----@param value any -function Ray.__newindex(self, key, value) - if key == "point" then - self.__data.point = value - elseif key == "direction" then - self.__data.direction = value - else - rawset(self, key, value) - end -end - ----创建一条新的射线,由起始点和方向向量确定 ----@param point foundation.math.Vector2 起始点 ----@param direction foundation.math.Vector2 方向向量 ----@return foundation.shape.Ray -function Ray.create(point, direction) - local dist = direction and direction:length() or 0 - if dist <= 1e-10 then - direction = Vector2.create(1, 0) - elseif dist ~= 1 then - ---@diagnostic disable-next-line: need-check-nil - direction = direction:normalized() - else - ---@diagnostic disable-next-line: need-check-nil - direction = direction:clone() - end - - local ray = ffi.new("foundation_shape_Ray", point, direction) - local result = { - __data = ray, - } - ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return setmetatable(result, Ray) -end - ----根据起始点、弧度创建射线 ----@param point foundation.math.Vector2 起始点 ----@param radian number 弧度 ----@return foundation.shape.Ray -function Ray.createFromRad(point, radian) - local direction = Vector2.createFromRad(radian) - return Ray.create(point, direction) -end - ----根据起始点、角度创建射线 ----@param point foundation.math.Vector2 起始点 ----@param angle number 角度 ----@return foundation.shape.Ray -function Ray.createFromAngle(point, angle) - local direction = Vector2.createFromAngle(angle) - return Ray.create(point, direction) -end - ----射线相等比较 ----@param a foundation.shape.Ray ----@param b foundation.shape.Ray ----@return boolean -function Ray.__eq(a, b) - return a.point == b.point and a.direction == b.direction -end - ----射线的字符串表示 ----@param self foundation.shape.Ray ----@return string -function Ray.__tostring(self) - return string.format("Ray(point=%s, direction=%s)", tostring(self.point), tostring(self.direction)) -end - ----获取射线上相对起始点指定距离的点 ----@param length number 距离 ----@return foundation.math.Vector2 -function Ray:getPoint(length) - return self.point + self.direction * length -end - ----计算射线的中心 ----@return foundation.math.Vector2 -function Ray:getCenter() - if math.abs(self.direction.x) < 1e-10 then - return Vector2.create(0, math.huge) - elseif math.abs(self.direction.y) < 1e-10 then - return Vector2.create(math.huge, 0) - end - if self.direction.x > 0 and self.direction.y > 0 then - return Vector2.create(math.huge, math.huge) - elseif self.direction.x > 0 and self.direction.y < 0 then - return Vector2.create(math.huge, -math.huge) - elseif self.direction.x < 0 and self.direction.y > 0 then - return Vector2.create(-math.huge, math.huge) - else - return Vector2.create(-math.huge, -math.huge) - end -end - ----获取射线的AABB包围盒 ----@return number, number, number, number -function Ray:AABB() - if math.abs(self.direction.x) < 1e-10 then - -- 垂直线 - if self.direction.y > 0 then - return self.point.x, self.point.x, self.point.y, math.huge - else - return self.point.x, self.point.x, -math.huge, self.point.y - end - elseif math.abs(self.direction.y) < 1e-10 then - -- 水平线 - if self.direction.x > 0 then - return self.point.x, math.huge, self.point.y, self.point.y - else - return -math.huge, self.point.x, self.point.y, self.point.y - end - else - -- 斜线 - if self.direction.x > 0 and self.direction.y > 0 then - return self.point.x, math.huge, self.point.y, math.huge - elseif self.direction.x > 0 and self.direction.y < 0 then - return self.point.x, math.huge, -math.huge, self.point.y - elseif self.direction.x < 0 and self.direction.y > 0 then - return -math.huge, self.point.x, self.point.y, math.huge - else - return -math.huge, self.point.x, -math.huge, self.point.y - end - end -end - ----计算射线的包围盒宽高 ----@return number, number -function Ray:getBoundingBoxSize() - if math.abs(self.direction.x) < 1e-10 then - return 0, math.huge - elseif math.abs(self.direction.y) < 1e-10 then - return math.huge, 0 - end - return math.huge, math.huge -end - ----获取射线的角度(弧度) ----@return number 射线的角度,单位为弧度 -function Ray:angle() - return math.atan2(self.direction.y, self.direction.x) -end - ----获取射线的角度(度) ----@return number 射线的角度,单位为度 -function Ray:degreeAngle() - return math.deg(self:angle()) -end - ----平移射线(更改当前射线) ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Ray 平移后的射线(自身引用) -function Ray:move(v) - local moveX, moveY - if type(v) == "number" then - moveX = v - moveY = v - else - moveX = v.x - moveY = v.y - end - self.point.x = self.point.x + moveX - self.point.y = self.point.y + moveY - return self -end - ----获取当前射线平移指定距离的副本 ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Ray 移动后的射线副本 -function Ray:moved(v) - local moveX, moveY - if type(v) == "number" then - moveX = v - moveY = v - else - moveX = v.x - moveY = v.y - end - return Ray.create( - Vector2.create(self.point.x + moveX, self.point.y + moveY), - self.direction:clone() - ) -end - ----将当前射线旋转指定弧度(更改当前射线) ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Ray 旋转后的射线(自身引用) ----@overload fun(self:foundation.shape.Ray, rad:number): foundation.shape.Ray 将射线绕起始点旋转指定弧度 -function Ray:rotate(rad, center) - center = center or self.point - local cosRad = math.cos(rad) - local sinRad = math.sin(rad) - local v = self.direction - local x = v.x * cosRad - v.y * sinRad - local y = v.x * sinRad + v.y * cosRad - self.direction.x = x - self.direction.y = y - return self -end - ----将当前射线旋转指定角度(更改当前射线) ----@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Ray 旋转后的射线(自身引用) ----@overload fun(self:foundation.shape.Ray, angle:number): foundation.shape.Ray 将射线绕起始点旋转指定角度 -function Ray:degreeRotate(angle, center) - angle = math.rad(angle) - return self:rotate(angle, center) -end - ----获取当前射线旋转指定弧度的副本 ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Ray 旋转后的射线副本 ----@overload fun(self:foundation.shape.Ray, rad:number): foundation.shape.Ray 将射线绕起始点旋转指定弧度 -function Ray:rotated(rad, center) - center = center or self.point - local cosRad = math.cos(rad) - local sinRad = math.sin(rad) - local v = self.direction - return Ray.create( - Vector2.create(v.x * cosRad - v.y * sinRad + center.x, v.x * sinRad + v.y * cosRad + center.y), - Vector2.create(v.x * cosRad - v.y * sinRad, v.x * sinRad + v.y * cosRad) - ) -end - ----获取当前射线旋转指定角度的副本 ----@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Ray 旋转后的射线副本 ----@overload fun(self:foundation.shape.Ray, angle:number): foundation.shape.Ray 将射线绕起始点旋转指定角度 -function Ray:degreeRotated(angle, center) - angle = math.rad(angle) - return self:rotated(angle, center) -end - ----缩放射线(更改当前射线) ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为射线的起始点 ----@return foundation.shape.Ray 自身引用 -function Ray:scale(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self.point - - local dx = self.point.x - center.x - local dy = self.point.y - center.y - self.point.x = center.x + dx * scaleX - self.point.y = center.y + dy * scaleY - return self -end - ----获取缩放后的射线副本 ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为射线的起始点 ----@return foundation.shape.Ray -function Ray:scaled(scale, center) - local result = Ray.create(self.point:clone(), self.direction:clone()) - return result:scale(scale, center) -end - ----检查与其他形状的相交 ----@param other any ----@return boolean, foundation.math.Vector2[] | nil -function Ray:intersects(other) - return ShapeIntersector.intersect(self, other) -end - ----检查是否与其他形状相交,只返回是否相交的布尔值 ----@param other any ----@return boolean -function Ray:hasIntersection(other) - return ShapeIntersector.hasIntersection(self, other) -end - ----计算点到射线的最近点 ----@param point foundation.math.Vector2 点 ----@return foundation.math.Vector2 最近点 -function Ray:closestPoint(point) - local dir = self.direction:normalized() - local point_vec = point - self.point - local proj_length = point_vec:dot(dir) - - if proj_length <= 0 then - return self.point:clone() - else - return self.point + dir * proj_length - end -end - ----计算点到射线的距离 ----@param point foundation.math.Vector2 点 ----@return number 距离 -function Ray:distanceToPoint(point) - local point_vec = point - self.point - local proj_length = point_vec:dot(self.direction) - - if proj_length <= 1e-10 then - return (point - self.point):length() - else - local proj_point = self.point + self.direction * proj_length - return (point - proj_point):length() - end -end - ----检查点是否在射线上 ----@param point foundation.math.Vector2 点 ----@param tolerance number 误差容忍度,默认为1e-10 ----@return boolean -function Ray:containsPoint(point, tolerance) - tolerance = tolerance or 1e-10 - local point_vec = point - self.point - - local proj_length = point_vec:dot(self.direction) - if proj_length <= 1e-10 then - return false - end - - local cross = point_vec:cross(self.direction) - return math.abs(cross) <= tolerance -end - ----获取点在射线所在直线上的投影 ----@param point foundation.math.Vector2 点 ----@return foundation.math.Vector2 投影点 -function Ray:projectPoint(point) - local dir = self.direction - local t = ((point.x - self.point.x) * dir.x + (point.y - self.point.y) * dir.y) - return Vector2.create(self.point.x + t * dir.x, self.point.y + t * dir.y) -end - ----复制射线 ----@return foundation.shape.Ray 射线副本 -function Ray:clone() - return Ray.create(self.point:clone(), self.direction:clone()) -end - -ffi.metatype("foundation_shape_Ray", Ray) - -return Ray diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua deleted file mode 100644 index 1d0b434e..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/Rectangle.lua +++ /dev/null @@ -1,427 +0,0 @@ -local ffi = require("ffi") - -local type = type -local ipairs = ipairs -local tostring = tostring -local string = string -local math = math -local rawset = rawset -local setmetatable = setmetatable - -local Vector2 = require("foundation.math.Vector2") -local Segment = require("foundation.shape.Segment") -local ShapeIntersector = require("foundation.shape.ShapeIntersector") - -ffi.cdef [[ -typedef struct { - foundation_math_Vector2 center; - double width; - double height; - foundation_math_Vector2 direction; -} foundation_shape_Rectangle; -]] - ----@class foundation.shape.Rectangle ----@field center foundation.math.Vector2 矩形的中心点 ----@field width number 矩形的宽度 ----@field height number 矩形的高度 ----@field direction foundation.math.Vector2 矩形的宽度轴方向(归一化向量) -local Rectangle = {} -Rectangle.__type = "foundation.shape.Rectangle" - ----@param self foundation.shape.Rectangle ----@param key any ----@return any -function Rectangle.__index(self, key) - if key == "center" then - return self.__data.center - elseif key == "width" then - return self.__data.width - elseif key == "height" then - return self.__data.height - elseif key == "direction" then - return self.__data.direction - end - return Rectangle[key] -end - ----@param self foundation.shape.Rectangle ----@param key string ----@param value any -function Rectangle.__newindex(self, key, value) - if key == "center" then - self.__data.center = value - elseif key == "width" then - self.__data.width = value - elseif key == "height" then - self.__data.height = value - elseif key == "direction" then - self.__data.direction = value - else - rawset(self, key, value) - end -end - ----创建一个新的矩形 ----@param center foundation.math.Vector2 中心点 ----@param width number 宽度 ----@param height number 高度 ----@param direction foundation.math.Vector2|nil 宽度轴方向(归一化向量),默认为(1,0) ----@return foundation.shape.Rectangle -function Rectangle.create(center, width, height, direction) - local dist = direction and direction:length() or 0 - if dist <= 1e-10 then - direction = Vector2.create(1, 0) - elseif dist ~= 1 then - ---@diagnostic disable-next-line: need-check-nil - direction = direction:normalized() - else - ---@diagnostic disable-next-line: need-check-nil - direction = direction:clone() - end - local rectangle = ffi.new("foundation_shape_Rectangle", center, width, height, direction) - local result = { - __data = rectangle - } - ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return setmetatable(result, Rectangle) -end - ----使用给定的弧度创建矩形 ----@param center foundation.math.Vector2 中心点 ----@param width number 宽度 ----@param height number 高度 ----@param rad number 旋转弧度 ----@return foundation.shape.Rectangle -function Rectangle.createFromRad(center, width, height, rad) - local direction = Vector2.createFromRad(rad) - return Rectangle.create(center, width, height, direction) -end - ----使用给定的角度创建矩形 ----@param center foundation.math.Vector2 中心点 ----@param width number 宽度 ----@param height number 高度 ----@param angle number 旋转角度 ----@return foundation.shape.Rectangle -function Rectangle.createFromAngle(center, width, height, angle) - local direction = Vector2.createFromAngle(angle) - return Rectangle.create(center, width, height, direction) -end - ----矩形相等比较 ----@param a foundation.shape.Rectangle ----@param b foundation.shape.Rectangle ----@return boolean -function Rectangle.__eq(a, b) - return a.center == b.center and - math.abs(a.width - b.width) <= 1e-10 and - math.abs(a.height - b.height) <= 1e-10 and - a.direction == b.direction -end - ----矩形的字符串表示 ----@param self foundation.shape.Rectangle ----@return string -function Rectangle.__tostring(self) - return string.format("Rectangle(center=%s, width=%f, height=%f, direction=%s)", - tostring(self.center), self.width, self.height, tostring(self.direction)) -end - ----获取矩形的四个顶点 ----@return foundation.math.Vector2[] -function Rectangle:getVertices() - local hw, hh = self.width / 2, self.height / 2 - local dir = self.direction - local perp = Vector2.create(-dir.y, dir.x) - local vertices = { - Vector2.create(-hw, -hh), - Vector2.create(hw, -hh), - Vector2.create(hw, hh), - Vector2.create(-hw, hh) - } - for i, v in ipairs(vertices) do - local x = v.x * dir.x + v.y * perp.x - local y = v.x * dir.y + v.y * perp.y - vertices[i] = self.center + Vector2.create(x, y) - end - return vertices -end - ----获取矩形的四条边(线段) ----@return foundation.shape.Segment[] -function Rectangle:getEdges() - local vertices = self:getVertices() - return { - Segment.create(vertices[1], vertices[2]), - Segment.create(vertices[2], vertices[3]), - Segment.create(vertices[3], vertices[4]), - Segment.create(vertices[4], vertices[1]) - } -end - ----平移矩形(更改当前矩形) ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Rectangle 自身引用 -function Rectangle:move(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - self.center.x = self.center.x + moveX - self.center.y = self.center.y + moveY - return self -end - ----获取平移后的矩形副本 ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Rectangle -function Rectangle:moved(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - return Rectangle.create( - Vector2.create(self.center.x + moveX, self.center.y + moveY), - self.width, self.height, self.direction - ) -end - ----旋转矩形(更改当前矩形) ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为矩形中心 ----@return foundation.shape.Rectangle 自身引用 -function Rectangle:rotate(rad, center) - local cosA, sinA = math.cos(rad), math.sin(rad) - local x = self.direction.x * cosA - self.direction.y * sinA - local y = self.direction.x * sinA + self.direction.y * cosA - self.direction = Vector2.create(x, y):normalized() - - if center then - local dx = self.center.x - center.x - local dy = self.center.y - center.y - self.center.x = center.x + dx * cosA - dy * sinA - self.center.y = center.y + dx * sinA + dy * cosA - end - return self -end - ----旋转矩形(更改当前矩形) ----@param angle number 旋转角度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为矩形中心 ----@return foundation.shape.Rectangle 自身引用 -function Rectangle:degreeRotate(angle, center) - return self:rotate(math.rad(angle), center) -end - ----获取旋转后的矩形副本 ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为矩形中心 ----@return foundation.shape.Rectangle -function Rectangle:rotated(rad, center) - local result = Rectangle.create(self.center:clone(), self.width, self.height, self.direction:clone()) - return result:rotate(rad, center) -end - ----获取旋转后的矩形副本 ----@param angle number 旋转角度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为矩形中心 ----@return foundation.shape.Rectangle -function Rectangle:degreeRotated(angle, center) - return self:rotated(math.rad(angle), center) -end - ----缩放矩形(更改当前矩形) ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为矩形中心 ----@return foundation.shape.Rectangle 自身引用 -function Rectangle:scale(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self.center - - self.width = self.width * scaleX - self.height = self.height * scaleY - local dx = self.center.x - center.x - local dy = self.center.y - center.y - self.center.x = center.x + dx * scaleX - self.center.y = center.y + dy * scaleY - return self -end - ----获取缩放后的矩形副本 ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为矩形中心 ----@return foundation.shape.Rectangle -function Rectangle:scaled(scale, center) - local result = Rectangle.create(self.center:clone(), self.width, self.height, self.direction:clone()) - return result:scale(scale, center) -end - ----检查点是否在矩形内(包括边界) ----@param point foundation.math.Vector2 ----@return boolean -function Rectangle:contains(point) - return ShapeIntersector.rectangleContainsPoint(self, point) -end - ----检查与其他形状的相交 ----@param other any ----@return boolean, foundation.math.Vector2[] | nil -function Rectangle:intersects(other) - return ShapeIntersector.intersect(self, other) -end - ----仅检查是否与其他形状相交 ----@param other any ----@return boolean -function Rectangle:hasIntersection(other) - return ShapeIntersector.hasIntersection(self, other) -end - ----计算矩形的面积 ----@return number 矩形的面积 -function Rectangle:area() - return self.width * self.height -end - ----计算矩形的周长 ----@return number 矩形的周长 -function Rectangle:getPerimeter() - return 2 * (self.width + self.height) -end - ----计算矩形的中心 ----@return foundation.math.Vector2 矩形的中心 -function Rectangle:getCenter() - return self.center:clone() -end - ----获取矩形的AABB包围盒 ----@return number, number, number, number -function Rectangle:AABB() - local vertices = self:getVertices() - local minX, maxX = vertices[1].x, vertices[1].x - local minY, maxY = vertices[1].y, vertices[1].y - for i = 2, 4 do - local v = vertices[i] - minX = math.min(minX, v.x) - maxX = math.max(maxX, v.x) - minY = math.min(minY, v.y) - maxY = math.max(maxY, v.y) - end - return minX, maxX, minY, maxY -end - ----计算矩形的包围盒宽高 ----@return number, number -function Rectangle:getBoundingBoxSize() - local minX, maxX, minY, maxY = self:AABB() - return maxX - minX, maxY - minY -end - ----计算矩形的内心 ----@return foundation.math.Vector2 矩形的内心 -function Rectangle:incenter() - return self.center:clone() -end - ----计算矩形的内切圆半径 ----@return number 矩形的内切圆半径 -function Rectangle:inradius() - local min = math.min(self.width, self.height) / 2 - return min -end - ----计算矩形的外心 ----@return foundation.math.Vector2 矩形的外心 -function Rectangle:circumcenter() - return self.center:clone() -end - ----计算矩形的外接圆半径 ----@return number 矩形的外接圆半径 -function Rectangle:circumradius() - return math.sqrt((self.width / 2) ^ 2 + (self.height / 2) ^ 2) -end - ----计算点到矩形的最近点 ----@param point foundation.math.Vector2 ----@param boundary boolean 是否限制在边界内,默认为false ----@return foundation.math.Vector2 ----@overload fun(self: foundation.shape.Rectangle, point: foundation.math.Vector2): foundation.math.Vector2 -function Rectangle:closestPoint(point, boundary) - if not boundary and self:contains(point) then - return point:clone() - end - local edges = self:getEdges() - local minDistance = math.huge - local closestPoint - for _, edge in ipairs(edges) do - local edgeClosest = edge:closestPoint(point) - local distance = (point - edgeClosest):length() - if distance < minDistance then - minDistance = distance - closestPoint = edgeClosest - end - end - return closestPoint -end - ----计算点到矩形的距离 ----@param point foundation.math.Vector2 ----@return number -function Rectangle:distanceToPoint(point) - if self:contains(point) then - return 0 - end - local edges = self:getEdges() - local minDistance = math.huge - for _, edge in ipairs(edges) do - local distance = edge:distanceToPoint(point) - if distance < minDistance then - minDistance = distance - end - end - return minDistance -end - ----将点投影到矩形上 ----@param point foundation.math.Vector2 ----@return foundation.math.Vector2 -function Rectangle:projectPoint(point) - return self:closestPoint(point, true) -end - ----检查点是否在矩形边界上 ----@param point foundation.math.Vector2 ----@param tolerance number|nil 默认为1e-10 ----@return boolean -function Rectangle:containsPoint(point, tolerance) - tolerance = tolerance or 1e-10 - local edges = self:getEdges() - for _, edge in ipairs(edges) do - if edge:containsPoint(point, tolerance) then - return true - end - end - return false -end - ----复制矩形 ----@return foundation.shape.Rectangle -function Rectangle:clone() - return Rectangle.create(self.center:clone(), self.width, self.height, self.direction:clone()) -end - -ffi.metatype("foundation_shape_Rectangle", Rectangle) - -return Rectangle \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua b/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua deleted file mode 100644 index a2d529ca..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/Sector.lua +++ /dev/null @@ -1,524 +0,0 @@ -local ffi = require("ffi") - -local math = math -local type = type -local ipairs = ipairs -local tostring = tostring -local string = string -local rawset = rawset -local setmetatable = setmetatable - -local Vector2 = require("foundation.math.Vector2") -local Segment = require("foundation.shape.Segment") -local Circle = require("foundation.shape.Circle") -local ShapeIntersector = require("foundation.shape.ShapeIntersector") - -ffi.cdef [[ -typedef struct { - foundation_math_Vector2 center; - double radius; - foundation_math_Vector2 direction; - double range; -} foundation_shape_Sector; -]] - ----@class foundation.shape.Sector ----@field center foundation.math.Vector2 扇形的中心点 ----@field radius number 扇形的半径 ----@field direction foundation.math.Vector2 方向(归一化向量) ----@field range number 扇形范围(-1到1,1或-1为整圆,0.5或-0.5为半圆) -local Sector = {} -Sector.__type = "foundation.shape.Sector" - ----@param self foundation.shape.Sector ----@param key any ----@return any -function Sector.__index(self, key) - if key == "center" then - return self.__data.center - elseif key == "radius" then - return self.__data.radius - elseif key == "direction" then - return self.__data.direction - elseif key == "range" then - return self.__data.range - end - return Sector[key] -end - ----@param self foundation.shape.Sector ----@param key any ----@param value any -function Sector.__newindex(self, key, value) - if key == "center" then - self.__data.center = value - elseif key == "radius" then - self.__data.radius = value - elseif key == "direction" then - self.__data.direction = value - elseif key == "range" then - self.__data.range = value - else - rawset(self, key, value) - end -end - ----创建一个新的扇形 ----@param center foundation.math.Vector2 中心点 ----@param radius number 半径 ----@param direction foundation.math.Vector2 方向(将归一化) ----@param range number 范围(-1到1) ----@return foundation.shape.Sector -function Sector.create(center, radius, direction, range) - local dist = direction and direction:length() or 0 - if dist <= 1e-10 then - direction = Vector2.create(1, 0) - elseif dist ~= 1 then - ---@diagnostic disable-next-line: need-check-nil - direction = direction:normalized() - else - ---@diagnostic disable-next-line: need-check-nil - direction = direction:clone() - end - range = math.max(-1, math.min(1, range)) - local sector = ffi.new("foundation_shape_Sector", center, radius, direction, range) - local result = { - __data = sector, - } - ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return setmetatable(result, Sector) -end - ----使用弧度创建扇形 ----@param center foundation.math.Vector2 中心点 ----@param radius number 半径 ----@param rad number 方向弧度 ----@param range number 范围(-1到1) ----@return foundation.shape.Sector -function Sector.createFromRad(center, radius, rad, range) - local dir = Vector2.createFromRad(rad) - return Sector.create(center, radius, dir, range) -end - ----使用角度创建扇形 ----@param center foundation.math.Vector2 中心点 ----@param radius number 半径 ----@param angle number 方向角度 ----@param range number 范围(-1到1) ----@return foundation.shape.Sector -function Sector.createFromAngle(center, radius, angle, range) - local rad = math.rad(angle) - return Sector.createFromRad(center, radius, rad, range) -end - ----扇形相等比较 ----@param a foundation.shape.Sector ----@param b foundation.shape.Sector ----@return boolean -function Sector.__eq(a, b) - return a.center == b.center and - math.abs(a.radius - b.radius) <= 1e-10 and - a.direction == b.direction and - math.abs(a.range - b.range) <= 1e-10 -end - ----扇形的字符串表示 ----@param self foundation.shape.Sector ----@return string -function Sector.__tostring(self) - return string.format("Sector(center=%s, radius=%f, direction=%s, range=%f)", - tostring(self.center), self.radius, tostring(self.direction), self.range) -end - ----转为圆形 ----@return foundation.shape.Circle -function Sector:toCircle() - return Circle.create(self.center, self.radius) -end - ----计算扇形角度(弧度) ----@return number -function Sector:getAngle() - return math.abs(self.range) * 2 * math.pi -end - ----计算扇形角度(角度) ----@return number -function Sector:getDegreeAngle() - return math.deg(self:getAngle()) -end - ----计算扇形的面积 ----@return number -function Sector:area() - return 0.5 * self.radius * self.radius * self:getAngle() -end - ----计算扇形的周长 ----@return number 扇形的周长(弧长+两条半径) -function Sector:getPerimeter() - if math.abs(self.range) >= 1 then - return 2 * math.pi * self.radius - end - - local arcLength = self.radius * self:getAngle() - return arcLength + 2 * self.radius -end - ----计算扇形的中心点 ----@return foundation.math.Vector2 -function Sector:getCenter() - if math.abs(self.range) >= 1 then - return self.center:clone() - end - - local points = { self.center:clone() } - local start_dir = self.direction - local end_dir = self.direction:rotated(self.range * 2 * math.pi) - local start_point = self.center + start_dir * self.radius - local end_point = self.center + end_dir * self.radius - points[#points + 1] = start_point - points[#points + 1] = end_point - - local start_angle = self.direction:angle() - local end_angle = start_angle + self.range * 2 * math.pi - local min_angle = math.min(start_angle, end_angle) - local max_angle = math.max(start_angle, end_angle) - - local critical_points = { - { angle = 0, point = Vector2.create(self.center.x + self.radius, self.center.y) }, - { angle = math.pi, point = Vector2.create(self.center.x - self.radius, self.center.y) }, - { angle = math.pi / 2, point = Vector2.create(self.center.x, self.center.y + self.radius) }, - { angle = 3 * math.pi / 2, point = Vector2.create(self.center.x, self.center.y - self.radius) } - } - - for _, cp in ipairs(critical_points) do - local angle = cp.angle - angle = angle - 2 * math.pi * math.floor((angle - min_angle) / (2 * math.pi)) - if min_angle <= angle and angle <= max_angle then - points[#points + 1] = cp.point - end - end - - local x_min, x_max = points[1].x, points[1].x - local y_min, y_max = points[1].y, points[1].y - for _, p in ipairs(points) do - x_min = math.min(x_min, p.x) - x_max = math.max(x_max, p.x) - y_min = math.min(y_min, p.y) - y_max = math.max(y_max, p.y) - end - - return Vector2.create((x_min + x_max) / 2, (y_min + y_max) / 2) -end - ----获取扇形的AABB包围盒 ----@return number, number, number, number -function Sector:AABB() - if math.abs(self.range) >= 1 then - -- 如果是整圆,直接返回圆的包围盒 - local cx, cy = self.center.x, self.center.y - local r = self.radius - return cx - r, cx + r, cy - r, cy + r - end - - local points = { self.center:clone() } - local start_dir = self.direction - local end_dir = self.direction:rotated(self.range * 2 * math.pi) - local start_point = self.center + start_dir * self.radius - local end_point = self.center + end_dir * self.radius - points[#points + 1] = start_point - points[#points + 1] = end_point - - local start_angle = self.direction:angle() - local end_angle = start_angle + self.range * 2 * math.pi - local min_angle = math.min(start_angle, end_angle) - local max_angle = math.max(start_angle, end_angle) - - local critical_points = { - { angle = 0, point = Vector2.create(self.center.x + self.radius, self.center.y) }, - { angle = math.pi, point = Vector2.create(self.center.x - self.radius, self.center.y) }, - { angle = math.pi / 2, point = Vector2.create(self.center.x, self.center.y + self.radius) }, - { angle = 3 * math.pi / 2, point = Vector2.create(self.center.x, self.center.y - self.radius) } - } - - for _, cp in ipairs(critical_points) do - local angle = cp.angle - angle = angle - 2 * math.pi * math.floor((angle - min_angle) / (2 * math.pi)) - if min_angle <= angle and angle <= max_angle then - points[#points + 1] = cp.point - end - end - - local minX, minY = math.huge, math.huge - local maxX, maxY = -math.huge, -math.huge - - for _, point in ipairs(points) do - minX = math.min(minX, point.x) - minY = math.min(minY, point.y) - maxX = math.max(maxX, point.x) - maxY = math.max(maxY, point.y) - end - - return minX, maxX, minY, maxY -end - ----计算扇形的包围盒宽高 ----@return number, number -function Sector:getBoundingBoxSize() - local minX, maxX, minY, maxY = self:AABB() - return maxX - minX, maxY - minY -end - ----获取扇形的重心 ----@return foundation.math.Vector2 -function Sector:centroid() - if math.abs(self.range) >= 1 then - return self.center:clone() - end - if self.range <= 1e-10 then - return self.center + self.direction * (self.radius / 2) - end - local theta = self:getAngle() - local half_theta = theta / 2 - local mid_angle = self.direction:angle() + self.range * math.pi - local factor = (2 * self.radius / 3) * (math.sin(half_theta) / half_theta) - local x_c = self.center.x + factor * math.cos(mid_angle) - local y_c = self.center.y + factor * math.sin(mid_angle) - return Vector2.create(x_c, y_c) -end - ----检查点是否在扇形内(包括边界) ----@param point foundation.math.Vector2 ----@return boolean -function Sector:contains(point) - return ShapeIntersector.sectorContainsPoint(self, point) -end - ----移动扇形(修改当前扇形) ----@param v foundation.math.Vector2 | number ----@return foundation.shape.Sector -function Sector:move(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - self.center.x = self.center.x + moveX - self.center.y = self.center.y + moveY - return self -end - ----获取移动后的扇形副本 ----@param v foundation.math.Vector2 | number ----@return foundation.shape.Sector -function Sector:moved(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - return Sector.create( - Vector2.create(self.center.x + moveX, self.center.y + moveY), - self.radius, self.direction, self.range - ) -end - ----旋转扇形(修改当前扇形) ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为扇形中心 ----@return foundation.shape.Sector 自身引用 -function Sector:rotate(rad, center) - self.direction = self.direction:rotated(rad) - if center then - local dx = self.center.x - center.x - local dy = self.center.y - center.y - local cosA, sinA = math.cos(rad), math.sin(rad) - self.center.x = center.x + dx * cosA - dy * sinA - self.center.y = center.y + dx * sinA + dy * cosA - end - return self -end - ----旋转扇形(修改当前扇形) ----@param angle number 旋转角度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为扇形中心 ----@return foundation.shape.Sector 自身引用 -function Sector:degreeRotate(angle, center) - return self:rotate(math.rad(angle), center) -end - ----获取旋转后的扇形副本 ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为扇形中心 ----@return foundation.shape.Sector -function Sector:rotated(rad, center) - local result = Sector.create(self.center:clone(), self.radius, self.direction:clone(), self.range) - return result:rotate(rad, center) -end - ----获取旋转后的扇形副本 ----@param angle number 旋转角度 ----@param center foundation.math.Vector2|nil 旋转中心点,默认为扇形中心 ----@return foundation.shape.Sector -function Sector:degreeRotated(angle, center) - return self:rotated(math.rad(angle), center) -end - ----缩放扇形(修改当前扇形) ----@param scale number|foundation.math.Vector2 缩放比例 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为扇形中心 ----@return foundation.shape.Sector 自身引用 -function Sector:scale(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self.center - - self.radius = self.radius * math.sqrt(scaleX * scaleY) - local dx = self.center.x - center.x - local dy = self.center.y - center.y - self.center.x = center.x + dx * scaleX - self.center.y = center.y + dy * scaleY - return self -end - ----获取缩放后的扇形副本 ----@param scale number|foundation.math.Vector2 缩放比例 ----@param center foundation.math.Vector2|nil 缩放中心点,默认为扇形中心 ----@return foundation.shape.Sector -function Sector:scaled(scale, center) - local result = Sector.create(self.center:clone(), self.radius, self.direction:clone(), self.range) - return result:scale(scale, center) -end - ----检查与其他形状的相交 ----@param other any ----@return boolean, foundation.math.Vector2[] | nil -function Sector:intersects(other) - return ShapeIntersector.intersect(self, other) -end - ----仅检查是否与其他形状相交 ----@param other any ----@return boolean -function Sector:hasIntersection(other) - return ShapeIntersector.hasIntersection(self, other) -end - ----计算点到扇形的最近点 ----@param point foundation.math.Vector2 ----@param boundary boolean 是否限制在边界内,默认为false ----@return foundation.math.Vector2 ----@overload fun(self: foundation.shape.Sector, point: foundation.math.Vector2): foundation.math.Vector2 -function Sector:closestPoint(point, boundary) - if math.abs(self.range) >= 1 then - return Circle.closestPoint(self, point, boundary) - end - if not boundary and self:contains(point) then - return point:clone() - end - local circle_closest = Circle.closestPoint(self, point, boundary) - local contains = self:contains(circle_closest) - if not boundary and contains then - return circle_closest - end - local startDir = self.direction - local endDir = self.direction:rotated(self.range * 2 * math.pi) - local start_point = self.center + startDir * self.radius - local end_point = self.center + endDir * self.radius - local start_segment = Segment.create(self.center, start_point) - local end_segment = Segment.create(self.center, end_point) - local candidates = { - start_segment:closestPoint(point, boundary), - end_segment:closestPoint(point, boundary), - boundary and contains and circle_closest or nil, - } - local min_distance = math.huge - local closest_point = candidates[1] - for _, candidate in ipairs(candidates) do - local distance = (point - candidate):length() - if distance < min_distance then - min_distance = distance - closest_point = candidate - end - end - return closest_point -end - ----计算点到扇形的距离 ----@param point foundation.math.Vector2 ----@return number -function Sector:distanceToPoint(point) - if self:contains(point) then - return 0 - end - return (point - self:closestPoint(point)):length() -end - ----将点投影到扇形上 ----@param point foundation.math.Vector2 ----@return foundation.math.Vector2 -function Sector:projectPoint(point) - return self:closestPoint(point, true) -end - ----检查点是否在扇形边界上 ----@param point foundation.math.Vector2 ----@param tolerance number|nil ----@return boolean -function Sector:containsPoint(point, tolerance) - tolerance = tolerance or 1e-10 - - local vec = point - self.center - local range = self.range * 2 * math.pi - if math.abs(self.range) >= 1 then - local dist = (point - self.center):length() - return math.abs(dist - self.radius) <= tolerance - end - - local segment1 = Segment.create(self.center, self.center + self.direction * self.radius) - if segment1:containsPoint(point, tolerance) then - return true - end - - local segment2 = Segment.create(self.center, self.center + self.direction:rotated(range) * self.radius) - if segment2:containsPoint(point, tolerance) then - return true - end - - local distance = vec:length() - if math.abs(distance - self.radius) > tolerance then - return false - end - if distance <= tolerance then - return true - end - - local angle_begin - if range > 0 then - angle_begin = self.direction:angle() - else - range = -range - angle_begin = self.direction:angle() - range - end - - local vec_angle = vec:angle() - vec_angle = vec_angle - 2 * math.pi * math.floor((vec_angle - angle_begin) / (2 * math.pi)) - return angle_begin <= vec_angle and vec_angle <= angle_begin + range -end - ----复制扇形 ----@return foundation.shape.Sector -function Sector:clone() - return Sector.create(self.center:clone(), self.radius, self.direction:clone(), self.range) -end - -ffi.metatype("foundation_shape_Sector", Sector) - -return Sector \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua b/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua deleted file mode 100644 index 234fe2a7..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/Segment.lua +++ /dev/null @@ -1,378 +0,0 @@ -local ffi = require("ffi") - -local type = type -local tostring = tostring -local string = string -local math = math -local rawset = rawset -local setmetatable = setmetatable - -local Vector2 = require("foundation.math.Vector2") -local ShapeIntersector = require("foundation.shape.ShapeIntersector") - -ffi.cdef [[ -typedef struct { - foundation_math_Vector2 point1, point2; -} foundation_shape_Segment; -]] - ----@class foundation.shape.Segment ----@field point1 foundation.math.Vector2 ----@field point2 foundation.math.Vector2 -local Segment = {} -Segment.__type = "foundation.shape.Segment" - ----@param self foundation.shape.Segment ----@param key any ----@return any -function Segment.__index(self, key) - if key == "point1" then - return self.__data.point1 - elseif key == "point2" then - return self.__data.point2 - end - return Segment[key] -end - ----@param self foundation.shape.Segment ----@param key any ----@param value any -function Segment.__newindex(self, key, value) - if key == "point1" then - self.__data.point1 = value - elseif key == "point2" then - self.__data.point2 = value - else - rawset(self, key, value) - end -end - ----创建一个线段 ----@param point1 foundation.math.Vector2 线段的起点 ----@param point2 foundation.math.Vector2 线段的终点 ----@return foundation.shape.Segment -function Segment.create(point1, point2) - local segment = ffi.new("foundation_shape_Segment", point1, point2) - local result = { - __data = segment, - } - ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return setmetatable(result, Segment) -end - ----根据给定点和弧度与长度创建线段 ----@param point foundation.math.Vector2 线段的起点 ----@param rad number 线段的弧度 ----@param length number 线段的长度 ----@return foundation.shape.Segment -function Segment.createFromRad(point, rad, length) - local v2 = Vector2.create(point.x + math.cos(rad) * length, point.y + math.sin(rad) * length) - return Segment.create(point, v2) -end - ----根据给定点和角度与长度创建线段 ----@param point foundation.math.Vector2 线段的起点 ----@param angle number 线段的角度 ----@param length number 线段的长度 ----@return foundation.shape.Segment -function Segment.createFromAngle(point, angle, length) - local rad = math.rad(angle) - return Segment.createFromRad(point, rad, length) -end - ----线段相等比较 ----@param a foundation.shape.Segment 第一个线段 ----@param b foundation.shape.Segment 第二个线段 ----@return boolean 如果两个线段的所有顶点都相等则返回true,否则返回false -function Segment.__eq(a, b) - return a.point1 == b.point1 and a.point2 == b.point2 -end - ----线段转字符串表示 ----@param self foundation.shape.Segment ----@return string 线段的字符串表示 -function Segment.__tostring(self) - return string.format("Line(%s, %s)", tostring(self.point1), tostring(self.point2)) -end - ----将线段转换为向量 ----@return foundation.math.Vector2 从起点到终点的向量 -function Segment:toVector2() - return self.point2 - self.point1 -end - ----获取线段的法向量 ----@return foundation.math.Vector2 线段的单位法向量 -function Segment:normal() - local dir = self.point2 - self.point1 - local len = dir:length() - if len <= 1e-10 then - return Vector2.zero() - end - return Vector2.create(-dir.y / len, dir.x / len) -end - ----获取线段的长度 ----@return number 线段的长度 -function Segment:length() - return self:toVector2():length() -end - ----获取线段的中点 ----@return foundation.math.Vector2 线段的中点 -function Segment:midpoint() - return Vector2.create((self.point1.x + self.point2.x) / 2, (self.point1.y + self.point2.y) / 2) -end - ----获取线段的角度(弧度) ----@return number 线段的角度,单位为弧度 -function Segment:angle() - return math.atan2(self.point2.y - self.point1.y, self.point2.x - self.point1.x) -end - ----计算线段的中心 ----@return foundation.math.Vector2 线段的中心 -function Segment:getCenter() - return self:midpoint() -end - ----计算线段的取点 ----@param t number 取点参数,范围0到1 ----@return foundation.math.Vector2 线段上t位置的点 -function Segment:getPoint(t) - return self.point1 + (self.point2 - self.point1) * t -end - ----获取线段的AABB包围盒 ----@return number, number, number, number -function Segment:AABB() - local minX = math.min(self.point1.x, self.point2.x) - local maxX = math.max(self.point1.x, self.point2.x) - local minY = math.min(self.point1.y, self.point2.y) - local maxY = math.max(self.point1.y, self.point2.y) - return minX, maxX, minY, maxY -end - ----计算线段的包围盒宽高 ----@return number, number 线段的宽度和高度 -function Segment:getBoundingBoxSize() - local minX, maxX, minY, maxY = self:AABB() - return maxX - minX, maxY - minY -end - ----获取线段的角度(度) ----@return number 线段的角度,单位为度 -function Segment:degreeAngle() - return math.deg(self:angle()) -end - ----平移线段(更改当前线段) ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Segment 平移后的线段(自身引用) -function Segment:move(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - self.point1.x = self.point1.x + moveX - self.point1.y = self.point1.y + moveY - self.point2.x = self.point2.x + moveX - self.point2.y = self.point2.y + moveY - return self -end - ----获取当前线段平移指定距离的副本 ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Segment 移动后的线段副本 -function Segment:moved(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - return Segment.create( - Vector2.create(self.point1.x + moveX, self.point1.y + moveY), - Vector2.create(self.point2.x + moveX, self.point2.y + moveY) - ) -end - ----将当前线段旋转指定弧度(更改当前线段) ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Segment 旋转后的线段(自身引用) ----@overload fun(self:foundation.shape.Segment, rad:number): foundation.shape.Segment 将线段绕中点旋转指定弧度 -function Segment:rotate(rad, center) - center = center or self:midpoint() - local cosRad = math.cos(rad) - local sinRad = math.sin(rad) - local v1 = self.point1 - center - local v2 = self.point2 - center - self.point1.x = v1.x * cosRad - v1.y * sinRad + center.x - self.point1.y = v1.x * sinRad + v1.y * cosRad + center.y - self.point2.x = v2.x * cosRad - v2.y * sinRad + center.x - self.point2.y = v2.x * sinRad + v2.y * cosRad + center.y - return self -end - ----将当前线段旋转指定角度(更改当前线段) ----@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Segment 旋转后的线段(自身引用) ----@overload fun(self:foundation.shape.Segment, angle:number): foundation.shape.Segment 将线段绕中点旋转指定角度 -function Segment:degreeRotate(angle, center) - angle = math.rad(angle) - return self:rotate(angle, center) -end - ----获取当前线段旋转指定弧度的副本 ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Segment 旋转后的线段副本 ----@overload fun(self:foundation.shape.Segment, rad:number): foundation.shape.Segment 将线段绕中点旋转指定弧度 -function Segment:rotated(rad, center) - center = center or self:midpoint() - local cosRad = math.cos(rad) - local sinRad = math.sin(rad) - local v1 = self.point1 - center - local v2 = self.point2 - center - return Segment.create( - Vector2.create(v1.x * cosRad - v1.y * sinRad + center.x, v1.x * sinRad + v1.y * cosRad + center.y), - Vector2.create(v2.x * cosRad - v2.y * sinRad + center.x, v2.x * sinRad + v2.y * cosRad + center.y) - ) -end - ----获取当前线段旋转指定角度的副本 ----@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Segment 旋转后的线段副本 ----@overload fun(self:foundation.shape.Segment, angle:number): foundation.shape.Segment 将线段绕中点旋转指定角度 -function Segment:degreeRotated(angle, center) - angle = math.rad(angle) - return self:rotated(angle, center) -end - ----将当前线段缩放指定倍数(更改当前线段) ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2 缩放中心 ----@return foundation.shape.Segment 缩放后的线段(自身引用) ----@overload fun(self: foundation.shape.Segment, scale: number): foundation.shape.Segment 相对线段中点缩放指定倍数 -function Segment:scale(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self:midpoint() - self.point1.x = (self.point1.x - center.x) * scaleX + center.x - self.point1.y = (self.point1.y - center.y) * scaleY + center.y - self.point2.x = (self.point2.x - center.x) * scaleX + center.x - self.point2.y = (self.point2.y - center.y) * scaleY + center.y - return self -end - ----获取线段的缩放指定倍数的副本 ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2 缩放中心 ----@return foundation.shape.Segment 缩放后的线段副本 ----@overload fun(self: foundation.shape.Segment, scale: number): foundation.shape.Segment 相对线段中点缩放指定倍数 -function Segment:scaled(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self:midpoint() - return Segment.create( - Vector2.create((self.point1.x - center.x) * scaleX + center.x, (self.point1.y - center.y) * scaleY + center.y), - Vector2.create((self.point2.x - center.x) * scaleX + center.x, (self.point2.y - center.y) * scaleY + center.y) - ) -end - ----检查与其他形状的相交 ----@param other any ----@return boolean, foundation.math.Vector2[] | nil -function Segment:intersects(other) - return ShapeIntersector.intersect(self, other) -end - ----仅检查线段是否与其他形状相交,不返回相交点 ----@param other any 其他的形状 ----@return boolean -function Segment:hasIntersection(other) - return ShapeIntersector.hasIntersection(self, other) -end - ----计算点到线段的最近点 ----@param point foundation.math.Vector2 要检查的点 ----@return foundation.math.Vector2 线段上最近的点 -function Segment:closestPoint(point) - local dir = self.point2 - self.point1 - local len = dir:length() - if len <= 1e-10 then - return self.point1:clone() - end - - local t = ((point.x - self.point1.x) * dir.x + (point.y - self.point1.y) * dir.y) / (len * len) - t = math.max(0, math.min(1, t)) - - return Vector2.create(self.point1.x + t * dir.x, self.point1.y + t * dir.y) -end - ----计算点到线段的距离 ----@param point foundation.math.Vector2 要检查的点 ----@return number 点到线段的距离 -function Segment:distanceToPoint(point) - local closest = self:closestPoint(point) - return (point - closest):length() -end - ----将点投影到线段所在的直线上 ----@param point foundation.math.Vector2 要投影的点 ----@return foundation.math.Vector2 投影点 -function Segment:projectPoint(point) - local dir = self.point2 - self.point1 - local len = dir:length() - if len <= 1e-10 then - return self.point1:clone() - end - - local t = ((point.x - self.point1.x) * dir.x + (point.y - self.point1.y) * dir.y) / (len * len) - return Vector2.create(self.point1.x + t * dir.x, self.point1.y + t * dir.y) -end - ----检查点是否在线段上 ----@param point foundation.math.Vector2 要检查的点 ----@param tolerance number|nil 容差,默认为1e-10 ----@return boolean 点是否在线段上 ----@overload fun(self:foundation.shape.Segment, point:foundation.math.Vector2): boolean -function Segment:containsPoint(point, tolerance) - tolerance = tolerance or 1e-10 - local dist = self:distanceToPoint(point) - if dist > tolerance then - return false - end - - local dir = self.point2 - self.point1 - local len = dir:length() - if len <= 1e-10 then - return point == self.point1 - end - - local t = ((point.x - self.point1.x) * dir.x + (point.y - self.point1.y) * dir.y) / (len * len) - return t >= 0 and t <= 1 -end - ----复制线段 ----@return foundation.shape.Segment 线段的副本 -function Segment:clone() - return Segment.create(self.point1:clone(), self.point2:clone()) -end - -ffi.metatype("foundation_shape_Segment", Segment) - -return Segment diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua deleted file mode 100644 index 40e5e7b5..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector.lua +++ /dev/null @@ -1,296 +0,0 @@ -local tostring = tostring -local ipairs = ipairs -local require = require - ----@class foundation.shape.ShapeIntersector -local ShapeIntersector = {} - -local ShapeIntersectorList = { - "ContainPoint", - "CircleIntersector", - "EllipseIntersector", - "LineIntersector", - "PolygonIntersector", - "RectangleIntersector", - "SectorIntersector", - "TriangleIntersector", - "SegmentIntersector", - "RayIntersector", - "BezierCurveIntersector", -} -for _, moduleName in ipairs(ShapeIntersectorList) do - local module = require(string.format("foundation.shape.ShapeIntersector.%s", moduleName)) - module(ShapeIntersector) -end - ----整理相交点,去除重复点 ----@param points foundation.math.Vector2[] 原始点列表 ----@return foundation.math.Vector2[] 去重后的点列表 -function ShapeIntersector.getUniquePoints(points) - local unique_points = {} - local seen = {} - for _, p in ipairs(points) do - local key = tostring(p.x) .. "," .. tostring(p.y) - if not seen[key] then - seen[key] = true - unique_points[#unique_points + 1] = p - end - end - return unique_points -end - ----@type table> -local intersectionMap = { - ["foundation.shape.BezierCurve"] = { - ["foundation.shape.BezierCurve"] = ShapeIntersector.bezierCurveToBezierCurve, - ["foundation.shape.Polygon"] = ShapeIntersector.bezierCurveToPolygon, - ["foundation.shape.Segment"] = ShapeIntersector.bezierCurveToSegment, - ["foundation.shape.Circle"] = ShapeIntersector.bezierCurveToCircle, - ["foundation.shape.Rectangle"] = ShapeIntersector.bezierCurveToRectangle, - ["foundation.shape.Triangle"] = ShapeIntersector.bezierCurveToTriangle, - ["foundation.shape.Line"] = ShapeIntersector.bezierCurveToLine, - ["foundation.shape.Ray"] = ShapeIntersector.bezierCurveToRay, - ["foundation.shape.Sector"] = ShapeIntersector.bezierCurveToSector, - ["foundation.shape.Ellipse"] = ShapeIntersector.bezierCurveToEllipse, - }, - ["foundation.shape.Ellipse"] = { - ["foundation.shape.Ellipse"] = ShapeIntersector.ellipseToEllipse, - ["foundation.shape.Polygon"] = ShapeIntersector.ellipseToPolygon, - ["foundation.shape.Segment"] = ShapeIntersector.ellipseToSegment, - ["foundation.shape.Circle"] = ShapeIntersector.ellipseToCircle, - ["foundation.shape.Rectangle"] = ShapeIntersector.ellipseToRectangle, - ["foundation.shape.Triangle"] = ShapeIntersector.ellipseToTriangle, - ["foundation.shape.Line"] = ShapeIntersector.ellipseToLine, - ["foundation.shape.Ray"] = ShapeIntersector.ellipseToRay, - ["foundation.shape.Sector"] = ShapeIntersector.ellipseToSector, - }, - ["foundation.shape.Polygon"] = { - ["foundation.shape.Polygon"] = ShapeIntersector.polygonToPolygon, - ["foundation.shape.Triangle"] = ShapeIntersector.polygonToTriangle, - ["foundation.shape.Rectangle"] = ShapeIntersector.polygonToRectangle, - ["foundation.shape.Circle"] = ShapeIntersector.polygonToCircle, - ["foundation.shape.Line"] = ShapeIntersector.polygonToLine, - ["foundation.shape.Ray"] = ShapeIntersector.polygonToRay, - ["foundation.shape.Segment"] = ShapeIntersector.polygonToSegment, - ["foundation.shape.Sector"] = ShapeIntersector.polygonToSector, - }, - ["foundation.shape.Sector"] = { - ["foundation.shape.Sector"] = ShapeIntersector.sectorToSector, - ["foundation.shape.Rectangle"] = ShapeIntersector.sectorToRectangle, - ["foundation.shape.Triangle"] = ShapeIntersector.sectorToTriangle, - ["foundation.shape.Circle"] = ShapeIntersector.sectorToCircle, - ["foundation.shape.Line"] = ShapeIntersector.sectorToLine, - ["foundation.shape.Ray"] = ShapeIntersector.sectorToRay, - ["foundation.shape.Segment"] = ShapeIntersector.sectorToSegment, - }, - ["foundation.shape.Rectangle"] = { - ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleToRectangle, - ["foundation.shape.Triangle"] = ShapeIntersector.rectangleToTriangle, - ["foundation.shape.Circle"] = ShapeIntersector.rectangleToCircle, - ["foundation.shape.Line"] = ShapeIntersector.rectangleToLine, - ["foundation.shape.Ray"] = ShapeIntersector.rectangleToRay, - ["foundation.shape.Segment"] = ShapeIntersector.rectangleToSegment, - }, - ["foundation.shape.Triangle"] = { - ["foundation.shape.Triangle"] = ShapeIntersector.triangleToTriangle, - ["foundation.shape.Circle"] = ShapeIntersector.triangleToCircle, - ["foundation.shape.Line"] = ShapeIntersector.triangleToLine, - ["foundation.shape.Ray"] = ShapeIntersector.triangleToRay, - ["foundation.shape.Segment"] = ShapeIntersector.triangleToSegment, - }, - ["foundation.shape.Line"] = { - ["foundation.shape.Line"] = ShapeIntersector.lineToLine, - ["foundation.shape.Ray"] = ShapeIntersector.lineToRay, - ["foundation.shape.Segment"] = ShapeIntersector.lineToSegment, - ["foundation.shape.Circle"] = ShapeIntersector.lineToCircle, - }, - ["foundation.shape.Ray"] = { - ["foundation.shape.Ray"] = ShapeIntersector.rayToRay, - ["foundation.shape.Segment"] = ShapeIntersector.rayToSegment, - ["foundation.shape.Circle"] = ShapeIntersector.rayToCircle, - }, - ["foundation.shape.Circle"] = { - ["foundation.shape.Circle"] = ShapeIntersector.circleToCircle, - ["foundation.shape.Segment"] = ShapeIntersector.circleToSegment, - }, - ["foundation.shape.Segment"] = { - ["foundation.shape.Segment"] = ShapeIntersector.segmentToSegment, - }, -} - ----@type table> -local hasIntersectionMap = { - ["foundation.shape.BezierCurve"] = { - ["foundation.shape.BezierCurve"] = ShapeIntersector.bezierCurveHasIntersectionWithBezierCurve, - ["foundation.shape.Polygon"] = ShapeIntersector.bezierCurveHasIntersectionWithPolygon, - ["foundation.shape.Segment"] = ShapeIntersector.bezierCurveHasIntersectionWithSegment, - ["foundation.shape.Circle"] = ShapeIntersector.bezierCurveHasIntersectionWithCircle, - ["foundation.shape.Rectangle"] = ShapeIntersector.bezierCurveHasIntersectionWithRectangle, - ["foundation.shape.Triangle"] = ShapeIntersector.bezierCurveHasIntersectionWithTriangle, - ["foundation.shape.Line"] = ShapeIntersector.bezierCurveHasIntersectionWithLine, - ["foundation.shape.Ray"] = ShapeIntersector.bezierCurveHasIntersectionWithRay, - ["foundation.shape.Sector"] = ShapeIntersector.bezierCurveHasIntersectionWithSector, - ["foundation.shape.Ellipse"] = ShapeIntersector.bezierCurveHasIntersectionWithEllipse, - }, - ["foundation.shape.Ellipse"] = { - ["foundation.shape.Ellipse"] = ShapeIntersector.ellipseHasIntersectionWithEllipse, - ["foundation.shape.Polygon"] = ShapeIntersector.ellipseHasIntersectionWithPolygon, - ["foundation.shape.Segment"] = ShapeIntersector.ellipseHasIntersectionWithSegment, - ["foundation.shape.Circle"] = ShapeIntersector.ellipseHasIntersectionWithCircle, - ["foundation.shape.Rectangle"] = ShapeIntersector.ellipseHasIntersectionWithRectangle, - ["foundation.shape.Triangle"] = ShapeIntersector.ellipseHasIntersectionWithTriangle, - ["foundation.shape.Line"] = ShapeIntersector.ellipseHasIntersectionWithLine, - ["foundation.shape.Ray"] = ShapeIntersector.ellipseHasIntersectionWithRay, - ["foundation.shape.Sector"] = ShapeIntersector.ellipseHasIntersectionWithSector, - }, - ["foundation.shape.Polygon"] = { - ["foundation.shape.Polygon"] = ShapeIntersector.polygonHasIntersectionWithPolygon, - ["foundation.shape.Triangle"] = ShapeIntersector.polygonHasIntersectionWithTriangle, - ["foundation.shape.Rectangle"] = ShapeIntersector.polygonHasIntersectionWithRectangle, - ["foundation.shape.Circle"] = ShapeIntersector.polygonHasIntersectionWithCircle, - ["foundation.shape.Line"] = ShapeIntersector.polygonHasIntersectionWithLine, - ["foundation.shape.Ray"] = ShapeIntersector.polygonHasIntersectionWithRay, - ["foundation.shape.Segment"] = ShapeIntersector.polygonHasIntersectionWithSegment, - ["foundation.shape.Sector"] = ShapeIntersector.polygonHasIntersectionWithSector, - }, - ["foundation.shape.Sector"] = { - ["foundation.shape.Sector"] = ShapeIntersector.sectorHasIntersectionWithSector, - ["foundation.shape.Rectangle"] = ShapeIntersector.sectorHasIntersectionWithRectangle, - ["foundation.shape.Triangle"] = ShapeIntersector.sectorHasIntersectionWithTriangle, - ["foundation.shape.Circle"] = ShapeIntersector.sectorHasIntersectionWithCircle, - ["foundation.shape.Line"] = ShapeIntersector.sectorHasIntersectionWithLine, - ["foundation.shape.Ray"] = ShapeIntersector.sectorHasIntersectionWithRay, - ["foundation.shape.Segment"] = ShapeIntersector.sectorHasIntersectionWithSegment, - }, - ["foundation.shape.Rectangle"] = { - ["foundation.shape.Rectangle"] = ShapeIntersector.rectangleHasIntersectionWithRectangle, - ["foundation.shape.Triangle"] = ShapeIntersector.rectangleHasIntersectionWithTriangle, - ["foundation.shape.Circle"] = ShapeIntersector.rectangleHasIntersectionWithCircle, - ["foundation.shape.Line"] = ShapeIntersector.rectangleHasIntersectionWithLine, - ["foundation.shape.Ray"] = ShapeIntersector.rectangleHasIntersectionWithRay, - ["foundation.shape.Segment"] = ShapeIntersector.rectangleHasIntersectionWithSegment, - }, - ["foundation.shape.Triangle"] = { - ["foundation.shape.Triangle"] = ShapeIntersector.triangleHasIntersectionWithTriangle, - ["foundation.shape.Circle"] = ShapeIntersector.triangleHasIntersectionWithCircle, - ["foundation.shape.Line"] = ShapeIntersector.triangleHasIntersectionWithLine, - ["foundation.shape.Ray"] = ShapeIntersector.triangleHasIntersectionWithRay, - ["foundation.shape.Segment"] = ShapeIntersector.triangleHasIntersectionWithSegment, - }, - ["foundation.shape.Line"] = { - ["foundation.shape.Line"] = ShapeIntersector.lineHasIntersectionWithLine, - ["foundation.shape.Ray"] = ShapeIntersector.lineHasIntersectionWithRay, - ["foundation.shape.Segment"] = ShapeIntersector.lineHasIntersectionWithSegment, - ["foundation.shape.Circle"] = ShapeIntersector.lineHasIntersectionWithCircle, - }, - ["foundation.shape.Ray"] = { - ["foundation.shape.Ray"] = ShapeIntersector.rayHasIntersectionWithRay, - ["foundation.shape.Segment"] = ShapeIntersector.rayHasIntersectionWithSegment, - ["foundation.shape.Circle"] = ShapeIntersector.rayHasIntersectionWithCircle, - }, - ["foundation.shape.Circle"] = { - ["foundation.shape.Circle"] = ShapeIntersector.circleHasIntersectionWithCircle, - ["foundation.shape.Segment"] = ShapeIntersector.circleHasIntersectionWithSegment, - }, - ["foundation.shape.Segment"] = { - ["foundation.shape.Segment"] = ShapeIntersector.segmentHasIntersectionWithSegment, - }, -} - ----检查与其他形状的相交 ----@param shape1 any 第一个形状 ----@param shape2 any 第二个形状 ----@return boolean, foundation.math.Vector2[] | nil -function ShapeIntersector.intersect(shape1, shape2) - local type1 = shape1.__type - local type2 = shape2.__type - - local intersectionFunc = intersectionMap[type1] and intersectionMap[type1][type2] - if intersectionFunc then - return intersectionFunc(shape1, shape2) - end - - intersectionFunc = intersectionMap[type2] and intersectionMap[type2][type1] - if intersectionFunc then - return intersectionFunc(shape2, shape1) - end - - return false, nil -end - ----只检查是否与其他形状相交 ----@param shape1 any 第一个形状 ----@param shape2 any 第二个形状 ----@return boolean -function ShapeIntersector.hasIntersection(shape1, shape2) - local type1 = shape1.__type - local type2 = shape2.__type - - local intersectionFunc = hasIntersectionMap[type1] and hasIntersectionMap[type1][type2] - if intersectionFunc then - return intersectionFunc(shape1, shape2) - end - - intersectionFunc = hasIntersectionMap[type2] and hasIntersectionMap[type2][type1] - if intersectionFunc then - return intersectionFunc(shape2, shape1) - end - - return false -end - -function ShapeIntersector.checkMissingIntersection() - local keys = {} - for k, _ in pairs(intersectionMap) do - keys[#keys + 1] = k - end - - local missing = {} - for i = 1, #keys do - local key1 = keys[i] - for j = i, #keys do - local key2 = keys[j] - if not intersectionMap[key1][key2] and not intersectionMap[key2][key1] then - missing[#missing + 1] = { key1, key2 } - end - end - end - - if #missing > 0 then - print("Missing intersections:") - for _, pair in ipairs(missing) do - print(pair[1], pair[2]) - end - else - print("No missing intersections found.") - end - - local hasIntersectionKeys = {} - for k, _ in pairs(hasIntersectionMap) do - hasIntersectionKeys[#hasIntersectionKeys + 1] = k - end - - local missingHasIntersection = {} - for i = 1, #hasIntersectionKeys do - local key1 = hasIntersectionKeys[i] - for j = i, #hasIntersectionKeys do - local key2 = hasIntersectionKeys[j] - if not hasIntersectionMap[key1][key2] and not hasIntersectionMap[key2][key1] then - missingHasIntersection[#missingHasIntersection + 1] = { key1, key2 } - end - end - end - - if #missingHasIntersection > 0 then - print("Missing hasIntersection:") - for _, pair in ipairs(missingHasIntersection) do - print(pair[1], pair[2]) - end - else - print("No missing hasIntersection found.") - end -end - -ShapeIntersector.checkMissingIntersection() - -return ShapeIntersector \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/BezierCurveIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/BezierCurveIntersector.lua deleted file mode 100644 index 8bdff480..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/BezierCurveIntersector.lua +++ /dev/null @@ -1,451 +0,0 @@ ----@param ShapeIntersector foundation.shape.ShapeIntersector -return function(ShapeIntersector) - local ipairs = ipairs - - ---贝塞尔曲线与线段相交 - ---@param bezier foundation.shape.BezierCurve - ---@param segment foundation.shape.Segment - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.bezierCurveToSegment(bezier, segment) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - local intersectionPoints = {} - local hasIntersection = false - - for _, seg in ipairs(bezierSegments) do - local intersect, points = ShapeIntersector.segmentToSegment(seg, segment) - if intersect and points then - hasIntersection = true - for _, p in ipairs(points) do - intersectionPoints[#intersectionPoints + 1] = p - end - end - end - - if hasIntersection then - return true, ShapeIntersector.getUniquePoints(intersectionPoints) - else - return false, nil - end - end - - ---贝塞尔曲线与线段相交检查 - ---@param bezier foundation.shape.BezierCurve - ---@param segment foundation.shape.Segment - ---@return boolean - function ShapeIntersector.bezierCurveHasIntersectionWithSegment(bezier, segment) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - - for _, seg in ipairs(bezierSegments) do - if ShapeIntersector.segmentHasIntersectionWithSegment(seg, segment) then - return true - end - end - - return false - end - - ---贝塞尔曲线与线相交 - ---@param bezier foundation.shape.BezierCurve - ---@param line foundation.shape.Line - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.bezierCurveToLine(bezier, line) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - local intersectionPoints = {} - local hasIntersection = false - - for _, seg in ipairs(bezierSegments) do - local intersect, points = ShapeIntersector.lineToSegment(line, seg) - if intersect and points then - hasIntersection = true - for _, p in ipairs(points) do - intersectionPoints[#intersectionPoints + 1] = p - end - end - end - - if hasIntersection then - return true, ShapeIntersector.getUniquePoints(intersectionPoints) - else - return false, nil - end - end - - ---贝塞尔曲线与线相交检查 - ---@param bezier foundation.shape.BezierCurve - ---@param line foundation.shape.Line - ---@return boolean - function ShapeIntersector.bezierCurveHasIntersectionWithLine(bezier, line) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - - for _, seg in ipairs(bezierSegments) do - if ShapeIntersector.lineHasIntersectionWithSegment(line, seg) then - return true - end - end - - return false - end - - ---贝塞尔曲线与射线相交 - ---@param bezier foundation.shape.BezierCurve - ---@param ray foundation.shape.Ray - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.bezierCurveToRay(bezier, ray) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - local intersectionPoints = {} - local hasIntersection = false - - for _, seg in ipairs(bezierSegments) do - local intersect, points = ShapeIntersector.rayToSegment(ray, seg) - if intersect and points then - hasIntersection = true - for _, p in ipairs(points) do - intersectionPoints[#intersectionPoints + 1] = p - end - end - end - - if hasIntersection then - return true, ShapeIntersector.getUniquePoints(intersectionPoints) - else - return false, nil - end - end - - ---贝塞尔曲线与射线相交检查 - ---@param bezier foundation.shape.BezierCurve - ---@param ray foundation.shape.Ray - ---@return boolean - function ShapeIntersector.bezierCurveHasIntersectionWithRay(bezier, ray) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - - for _, seg in ipairs(bezierSegments) do - if ShapeIntersector.rayHasIntersectionWithSegment(ray, seg) then - return true - end - end - - return false - end - - ---贝塞尔曲线与圆相交 - ---@param bezier foundation.shape.BezierCurve - ---@param circle foundation.shape.Circle - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.bezierCurveToCircle(bezier, circle) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - local intersectionPoints = {} - local hasIntersection = false - - for _, seg in ipairs(bezierSegments) do - local intersect, points = ShapeIntersector.circleToSegment(circle, seg) - if intersect and points then - hasIntersection = true - for _, p in ipairs(points) do - intersectionPoints[#intersectionPoints + 1] = p - end - end - end - - if hasIntersection then - return true, ShapeIntersector.getUniquePoints(intersectionPoints) - else - return false, nil - end - end - - ---贝塞尔曲线与圆相交检查 - ---@param bezier foundation.shape.BezierCurve - ---@param circle foundation.shape.Circle - ---@return boolean - function ShapeIntersector.bezierCurveHasIntersectionWithCircle(bezier, circle) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - - for _, seg in ipairs(bezierSegments) do - if ShapeIntersector.circleHasIntersectionWithSegment(circle, seg) then - return true - end - end - - return false - end - - ---贝塞尔曲线与矩形相交 - ---@param bezier foundation.shape.BezierCurve - ---@param rectangle foundation.shape.Rectangle - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.bezierCurveToRectangle(bezier, rectangle) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - local intersectionPoints = {} - local hasIntersection = false - - for _, seg in ipairs(bezierSegments) do - local intersect, points = ShapeIntersector.rectangleToSegment(rectangle, seg) - if intersect and points then - hasIntersection = true - for _, p in ipairs(points) do - intersectionPoints[#intersectionPoints + 1] = p - end - end - end - - if hasIntersection then - return true, ShapeIntersector.getUniquePoints(intersectionPoints) - else - return false, nil - end - end - - ---贝塞尔曲线与矩形相交检查 - ---@param bezier foundation.shape.BezierCurve - ---@param rectangle foundation.shape.Rectangle - ---@return boolean - function ShapeIntersector.bezierCurveHasIntersectionWithRectangle(bezier, rectangle) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - - for _, seg in ipairs(bezierSegments) do - if ShapeIntersector.rectangleHasIntersectionWithSegment(rectangle, seg) then - return true - end - end - - return false - end - - ---贝塞尔曲线与三角形相交 - ---@param bezier foundation.shape.BezierCurve - ---@param triangle foundation.shape.Triangle - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.bezierCurveToTriangle(bezier, triangle) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - local intersectionPoints = {} - local hasIntersection = false - - for _, seg in ipairs(bezierSegments) do - local intersect, points = ShapeIntersector.triangleToSegment(triangle, seg) - if intersect and points then - hasIntersection = true - for _, p in ipairs(points) do - intersectionPoints[#intersectionPoints + 1] = p - end - end - end - - if hasIntersection then - return true, ShapeIntersector.getUniquePoints(intersectionPoints) - else - return false, nil - end - end - - ---贝塞尔曲线与三角形相交检查 - ---@param bezier foundation.shape.BezierCurve - ---@param triangle foundation.shape.Triangle - ---@return boolean - function ShapeIntersector.bezierCurveHasIntersectionWithTriangle(bezier, triangle) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - - for _, seg in ipairs(bezierSegments) do - if ShapeIntersector.triangleHasIntersectionWithSegment(triangle, seg) then - return true - end - end - - return false - end - - ---贝塞尔曲线与多边形相交 - ---@param bezier foundation.shape.BezierCurve - ---@param polygon foundation.shape.Polygon - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.bezierCurveToPolygon(bezier, polygon) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - local intersectionPoints = {} - local hasIntersection = false - - for _, seg in ipairs(bezierSegments) do - local intersect, points = ShapeIntersector.polygonToSegment(polygon, seg) - if intersect and points then - hasIntersection = true - for _, p in ipairs(points) do - intersectionPoints[#intersectionPoints + 1] = p - end - end - end - - if hasIntersection then - return true, ShapeIntersector.getUniquePoints(intersectionPoints) - else - return false, nil - end - end - - ---贝塞尔曲线与多边形相交检查 - ---@param bezier foundation.shape.BezierCurve - ---@param polygon foundation.shape.Polygon - ---@return boolean - function ShapeIntersector.bezierCurveHasIntersectionWithPolygon(bezier, polygon) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - - for _, seg in ipairs(bezierSegments) do - if ShapeIntersector.polygonHasIntersectionWithSegment(polygon, seg) then - return true - end - end - - return false - end - - ---贝塞尔曲线与扇形相交 - ---@param bezier foundation.shape.BezierCurve - ---@param sector foundation.shape.Sector - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.bezierCurveToSector(bezier, sector) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - local intersectionPoints = {} - local hasIntersection = false - - for _, seg in ipairs(bezierSegments) do - local intersect, points = ShapeIntersector.sectorToSegment(sector, seg) - if intersect and points then - hasIntersection = true - for _, p in ipairs(points) do - intersectionPoints[#intersectionPoints + 1] = p - end - end - end - - if hasIntersection then - return true, ShapeIntersector.getUniquePoints(intersectionPoints) - else - return false, nil - end - end - - ---贝塞尔曲线与扇形相交检查 - ---@param bezier foundation.shape.BezierCurve - ---@param sector foundation.shape.Sector - ---@return boolean - function ShapeIntersector.bezierCurveHasIntersectionWithSector(bezier, sector) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - - for _, seg in ipairs(bezierSegments) do - if ShapeIntersector.sectorHasIntersectionWithSegment(sector, seg) then - return true - end - end - - return false - end - - ---贝塞尔曲线与椭圆相交 - ---@param bezier foundation.shape.BezierCurve - ---@param ellipse foundation.shape.Ellipse - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.bezierCurveToEllipse(bezier, ellipse) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - local intersectionPoints = {} - local hasIntersection = false - - for _, seg in ipairs(bezierSegments) do - local intersect, points = ShapeIntersector.ellipseToSegment(ellipse, seg) - if intersect and points then - hasIntersection = true - for _, p in ipairs(points) do - intersectionPoints[#intersectionPoints + 1] = p - end - end - end - - if hasIntersection then - return true, ShapeIntersector.getUniquePoints(intersectionPoints) - else - return false, nil - end - end - - ---贝塞尔曲线与椭圆相交检查 - ---@param bezier foundation.shape.BezierCurve - ---@param ellipse foundation.shape.Ellipse - ---@return boolean - function ShapeIntersector.bezierCurveHasIntersectionWithEllipse(bezier, ellipse) - local segments = 20 - local bezierSegments = bezier:toSegments(segments) - - for _, seg in ipairs(bezierSegments) do - if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, seg) then - return true - end - end - - return false - end - - - ---贝塞尔曲线与贝塞尔曲线相交 - ---@param bezier1 foundation.shape.BezierCurve - ---@param bezier2 foundation.shape.BezierCurve - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.bezierCurveToBezierCurve(bezier1, bezier2) - local segments = 20 - local bezier1Segments = bezier1:toSegments(segments) - local bezier2Segments = bezier2:toSegments(segments) - local intersectionPoints = {} - local hasIntersection = false - - for _, seg1 in ipairs(bezier1Segments) do - for _, seg2 in ipairs(bezier2Segments) do - local intersect, points = ShapeIntersector.segmentToSegment(seg1, seg2) - if intersect and points then - hasIntersection = true - for _, p in ipairs(points) do - intersectionPoints[#intersectionPoints + 1] = p - end - end - end - end - - if hasIntersection then - return true, ShapeIntersector.getUniquePoints(intersectionPoints) - else - return false, nil - end - end - - ---贝塞尔曲线与贝塞尔曲线相交检查 - ---@param bezier1 foundation.shape.BezierCurve - ---@param bezier2 foundation.shape.BezierCurve - ---@return boolean - function ShapeIntersector.bezierCurveHasIntersectionWithBezierCurve(bezier1, bezier2) - local segments = 20 - local bezier1Segments = bezier1:toSegments(segments) - local bezier2Segments = bezier2:toSegments(segments) - - for _, seg1 in ipairs(bezier1Segments) do - for _, seg2 in ipairs(bezier2Segments) do - if ShapeIntersector.segmentHasIntersectionWithSegment(seg1, seg2) then - return true - end - end - end - - return false - end -end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/CircleIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/CircleIntersector.lua deleted file mode 100644 index 5dbc58d6..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/CircleIntersector.lua +++ /dev/null @@ -1,98 +0,0 @@ ----@param ShapeIntersector foundation.shape.ShapeIntersector -return function(ShapeIntersector) - local math = math - - local Vector2 = require("foundation.math.Vector2") - - ---检查圆与线段的相交 - ---@param circle foundation.shape.Circle 圆 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.circleToSegment(circle, segment) - local points = {} - local dir = segment.point2 - segment.point1 - local len = dir:length() - - if len <= 1e-10 then - if ShapeIntersector.circleContainsPoint(circle, segment.point1) then - return true, { segment.point1:clone() } - end - return false, nil - end - - dir = dir / len - - local closest = segment:closestPoint(circle.center) - local dist = (closest - circle.center):length() - if dist > circle.radius + 1e-10 then - return false, nil - end - - local vector_to_start = segment.point1 - circle.center - local b = 2 * vector_to_start:dot(dir) - local c = vector_to_start:dot(vector_to_start) - circle.radius * circle.radius - local discriminant = b * b - 4 * c - - if discriminant >= -1e-10 then - local sqrt_d = math.sqrt(math.max(discriminant, 0)) - local t1 = (-b - sqrt_d) / 2 - local t2 = (-b + sqrt_d) / 2 - - if t1 >= 0 and t1 <= len then - points[#points + 1] = segment.point1 + dir * t1 - end - if t2 >= 0 and t2 <= len and discriminant > 1e-10 then - points[#points + 1] = segment.point1 + dir * t2 - end - end - - if #points <= 1e-10 then - return false, nil - end - return true, points - end - - ---检查圆是否与线段相交 - ---@param circle foundation.shape.Circle 圆 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean - function ShapeIntersector.circleHasIntersectionWithSegment(circle, segment) - local closest = segment:closestPoint(circle.center) - return (closest - circle.center):length() <= circle.radius + 1e-10 - end - - ---检查圆与圆的相交 - ---@param circle1 foundation.shape.Circle 第一个圆 - ---@param circle2 foundation.shape.Circle 第二个圆 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.circleToCircle(circle1, circle2) - local points = {} - local d = (circle1.center - circle2.center):length() - if d <= circle1.radius + circle2.radius and d >= math.abs(circle1.radius - circle2.radius) then - local a = (circle1.radius * circle1.radius - circle2.radius * circle2.radius + d * d) / (2 * d) - local h = math.sqrt(circle1.radius * circle1.radius - a * a) - local p2 = circle1.center + (circle2.center - circle1.center) * (a / d) - local perp = Vector2.create(-(circle2.center.y - circle1.center.y), circle2.center.x - circle1.center.x):normalized() * h - points[#points + 1] = p2 + perp - if math.abs(h) > 1e-10 then - points[#points + 1] = p2 - perp - end - end - - if #points <= 1e-10 then - return false, nil - end - return true, points - end - - ---检查圆是否与圆相交 - ---@param circle1 foundation.shape.Circle 第一个圆 - ---@param circle2 foundation.shape.Circle 第二个圆 - ---@return boolean - function ShapeIntersector.circleHasIntersectionWithCircle(circle1, circle2) - local d = (circle1.center - circle2.center):length() - return d <= circle1.radius + circle2.radius and d >= math.abs(circle1.radius - circle2.radius) - end - - return ShapeIntersector -end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua deleted file mode 100644 index d4f54c46..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/ContainPoint.lua +++ /dev/null @@ -1,158 +0,0 @@ ----@param ShapeIntersector foundation.shape.ShapeIntersector -return function(ShapeIntersector) - local math = math - local require = require - - local Vector2 = require("foundation.math.Vector2") - - local Segment - - ---检查点是否在圆内或圆上 - ---@param circle foundation.shape.Circle 圆形 - ---@param point foundation.math.Vector2 点 - ---@return boolean - function ShapeIntersector.circleContainsPoint(circle, point) - return (point - circle.center):length() <= circle.radius + 1e-10 - end - - ---检查点是否在椭圆内(包括边界) - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param point foundation.math.Vector2 点 - ---@return boolean - function ShapeIntersector.ellipseContainsPoint(ellipse, point) - local baseAngle = ellipse.direction:angle() - local cos_rotation = math.cos(-baseAngle) - local sin_rotation = math.sin(-baseAngle) - - local dx = point.x - ellipse.center.x - local dy = point.y - ellipse.center.y - - local x = cos_rotation * dx - sin_rotation * dy - local y = sin_rotation * dx + cos_rotation * dy - - local value = (x * x) / (ellipse.rx * ellipse.rx) + (y * y) / (ellipse.ry * ellipse.ry) - return math.abs(value) <= 1 + 1e-10 - end - - ---检查点是否在矩形内(包括边界) - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@param point foundation.math.Vector2 点 - ---@return boolean - function ShapeIntersector.rectangleContainsPoint(rectangle, point) - local p = point - rectangle.center - local dir = rectangle.direction - local perp = Vector2.create(-dir.y, dir.x) - local x = p.x * dir.x + p.y * dir.y - local y = p.x * perp.x + p.y * perp.y - local hw, hh = rectangle.width / 2, rectangle.height / 2 - return math.abs(x) <= hw + 1e-10 and math.abs(y) <= hh + 1e-10 - end - - ---检查点是否在扇形内(包括边界) - ---@param sector foundation.shape.Sector 扇形 - ---@param point foundation.math.Vector2 点 - ---@return boolean - function ShapeIntersector.sectorContainsPoint(sector, point) ----@diagnostic disable-next-line: param-type-mismatch - local inCircle = ShapeIntersector.circleContainsPoint(sector, point) - if not inCircle then - return false - end - if math.abs(sector.range) >= 1 then - return true - end - - local range = sector.range * math.pi * 2 - local angle_begin - if range > 0 then - angle_begin = sector.direction:angle() - else - range = -range - angle_begin = sector.direction:angle() - range - end - - local vec = point - sector.center - local vec_angle = vec:angle() - vec_angle = vec_angle - 2 * math.pi * math.floor((vec_angle - angle_begin) / (2 * math.pi)) - return angle_begin <= vec_angle and vec_angle <= angle_begin + range - end - - ---检查点是否在三角形内 - ---@param triangle foundation.shape.Triangle 三角形 - ---@param point foundation.math.Vector2 点 - ---@return boolean - function ShapeIntersector.triangleContainsPoint(triangle, point) - local v1 = triangle.point1 - local v2 = triangle.point2 - local v3 = triangle.point3 - local p = point - - local v3v1 = v3 - v1 - local v2v1 = v2 - v1 - local pv1 = p - v1 - - local dot00 = v3v1:dot(v3v1) - local dot01 = v3v1:dot(v2v1) - local dot02 = v3v1:dot(pv1) - local dot11 = v2v1:dot(v2v1) - local dot12 = v2v1:dot(pv1) - - local invDenom = 1 / (dot00 * dot11 - dot01 * dot01) - local u = (dot11 * dot02 - dot01 * dot12) * invDenom - local v = (dot00 * dot12 - dot01 * dot02) * invDenom - - return (u >= 0) and (v >= 0) and (u + v <= 1) - end - - ---检查点是否在多边形内 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param point foundation.math.Vector2 点 - ---@return boolean - function ShapeIntersector.polygonContainsPoint(polygon, point) - if polygon.size < 3 then - return false - end - - Segment = Segment or require("foundation.shape.Segment") - for i = 0, polygon.size - 1 do - local j = (i + 1) % polygon.size - local segment = Segment.create(polygon.points[i], polygon.points[j]) - if ShapeIntersector.segmentContainsPoint(segment, point) then - return true - end - end - - local inside = false - for i = 0, polygon.size - 1 do - local j = (i + 1) % polygon.size - - local pi = polygon.points[i] - local pj = polygon.points[j] - - if (pi.x - point.x) * (pi.x - point.x) + (pi.y - point.y) * (pi.y - point.y) < 1e-10 then - return true - end - - if ((pi.y > point.y) ~= (pj.y > point.y)) and - (point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x) then - inside = not inside - end - end - - return inside - end - - ---判断点是否在线段上 - ---@param segment foundation.shape.Segment 线段 - ---@param point foundation.math.Vector2 点 - ---@return boolean - function ShapeIntersector.segmentContainsPoint(segment, point) - local d1 = (point - segment.point1):length() - local d2 = (point - segment.point2):length() - local lineLen = (segment.point2 - segment.point1):length() - - return math.abs(d1 + d2 - lineLen) <= 1e-10 - end - - return ShapeIntersector -end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua deleted file mode 100644 index 9b30baef..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/EllipseIntersector.lua +++ /dev/null @@ -1,801 +0,0 @@ ----@param ShapeIntersector foundation.shape.ShapeIntersector -return function(ShapeIntersector) - local math = math - local ipairs = ipairs - local require = require - - local Vector2 = require("foundation.math.Vector2") - local Segment - - ---椭圆与椭圆相交检测 - ---@param ellipse1 foundation.shape.Ellipse 椭圆1 - ---@param ellipse2 foundation.shape.Ellipse 椭圆2 - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.ellipseToEllipse(ellipse1, ellipse2) - local segments = 36 - local points = {} - - local ellipse2Points = ellipse2:discretize(segments) - - for i = 1, #ellipse2Points - 1 do - Segment = Segment or require("foundation.shape.Segment") - local segment = Segment.create(ellipse2Points[i], ellipse2Points[i + 1]) - local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse1, segment) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - Segment = Segment or require("foundation.shape.Segment") - local lastSegment = Segment.create(ellipse2Points[#ellipse2Points], ellipse2Points[1]) - local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse1, lastSegment) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - - if ellipse1:contains(ellipse2.center) then - points[#points + 1] = ellipse2.center:clone() - end - - if ellipse2:contains(ellipse1.center) then - points[#points + 1] = ellipse1.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---椭圆与椭圆相交检测(仅判断是否相交) - ---@param ellipse1 foundation.shape.Ellipse 椭圆1 - ---@param ellipse2 foundation.shape.Ellipse 椭圆2 - ---@return boolean - function ShapeIntersector.ellipseHasIntersectionWithEllipse(ellipse1, ellipse2) - if ellipse1:contains(ellipse2.center) then - return true - end - - if ellipse2:contains(ellipse1.center) then - return true - end - - local segments = 18 - local ellipse2Points = ellipse2:discretize(segments) - - for _, p in ipairs(ellipse2Points) do - if ellipse1:contains(p) then - return true - end - end - - local ellipse1Points = ellipse1:discretize(segments) - for _, p in ipairs(ellipse1Points) do - if ellipse2:contains(p) then - return true - end - end - - return false - end - - ---椭圆与圆相交检测 - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.ellipseToCircle(ellipse, circle) - Segment = Segment or require("foundation.shape.Segment") - - local segments = 36 - local points = {} - local circlePoints = circle:discretize(segments) - - for i = 1, #circlePoints - 1 do - local segment = Segment.create(circlePoints[i], circlePoints[i + 1]) - local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse, segment) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local lastSegment = Segment.create(circlePoints[#circlePoints], circlePoints[1]) - local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse, lastSegment) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - - if ellipse:contains(circle.center) then - points[#points + 1] = circle.center:clone() - end - - if circle:contains(ellipse.center) then - points[#points + 1] = ellipse.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---椭圆与圆相交检测(仅判断是否相交) - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean - function ShapeIntersector.ellipseHasIntersectionWithCircle(ellipse, circle) - if ellipse:contains(circle.center) then - return true - end - - if circle:contains(ellipse.center) then - return true - end - - local segments = 18 - local circlePoints = circle:discretize(segments) - - for _, p in ipairs(circlePoints) do - if ellipse:contains(p) then - return true - end - end - - Segment = Segment or require("foundation.shape.Segment") - - for i = 1, #circlePoints - 1 do - local segment = Segment.create(circlePoints[i], circlePoints[i + 1]) - if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, segment) then - return true - end - end - - local lastSegment = Segment.create(circlePoints[#circlePoints], circlePoints[1]) - return ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, lastSegment) - end - - ---椭圆与矩形相交检测 - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.ellipseToRectangle(ellipse, rectangle) - local points = {} - - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if ellipse:contains(vertex) then - points[#points + 1] = vertex:clone() - end - end - - if rectangle:contains(ellipse.center) then - points[#points + 1] = ellipse.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---椭圆与矩形相交检测(仅判断是否相交) - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@return boolean - function ShapeIntersector.ellipseHasIntersectionWithRectangle(ellipse, rectangle) - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if ellipse:contains(vertex) then - return true - end - end - - if rectangle:contains(ellipse.center) then - return true - end - - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, edge) then - return true - end - end - - return false - end - - ---椭圆与三角形相交检测 - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param triangle foundation.shape.Triangle 三角形 - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.ellipseToTriangle(ellipse, triangle) - local points = {} - - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if ellipse:contains(vertex) then - points[#points + 1] = vertex:clone() - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, ellipse.center) then - points[#points + 1] = ellipse.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---椭圆与三角形相交检测(仅判断是否相交) - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param triangle foundation.shape.Triangle 三角形 - ---@return boolean - function ShapeIntersector.ellipseHasIntersectionWithTriangle(ellipse, triangle) - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if ellipse:contains(vertex) then - return true - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, ellipse.center) then - return true - end - - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, edge) then - return true - end - end - - return false - end - - ---椭圆与多边形相交检测 - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param polygon foundation.shape.Polygon 多边形 - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.ellipseToPolygon(ellipse, polygon) - local points = {} - - local edges = polygon:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.ellipseToSegment(ellipse, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ellipse:contains(vertex) then - points[#points + 1] = vertex:clone() - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, ellipse.center) then - points[#points + 1] = ellipse.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---椭圆与多边形相交检测(仅判断是否相交) - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param polygon foundation.shape.Polygon 多边形 - ---@return boolean - function ShapeIntersector.ellipseHasIntersectionWithPolygon(ellipse, polygon) - local vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ellipse:contains(vertex) then - return true - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, ellipse.center) then - return true - end - - local edges = polygon:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, edge) then - return true - end - end - - return false - end - - ---椭圆与线段相交检测 - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.ellipseToSegment(ellipse, segment) - local points = {} - - local p1 = segment.point1 - ellipse.center - local p2 = segment.point2 - ellipse.center - - local baseAngle = ellipse.direction:angle() - local cos_rotation = math.cos(-baseAngle) - local sin_rotation = math.sin(-baseAngle) - - local x1 = cos_rotation * p1.x - sin_rotation * p1.y - local y1 = sin_rotation * p1.x + cos_rotation * p1.y - - local x2 = cos_rotation * p2.x - sin_rotation * p2.y - local y2 = sin_rotation * p2.x + cos_rotation * p2.y - - x1 = x1 / ellipse.rx - y1 = y1 / ellipse.ry - - x2 = x2 / ellipse.rx - y2 = y2 / ellipse.ry - - local dx = x2 - x1 - local dy = y2 - y1 - - local a = dx * dx + dy * dy - local b = 2 * (x1 * dx + y1 * dy) - local c = x1 * x1 + y1 * y1 - 1 - - local discriminant = b * b - 4 * a * c - - if discriminant < 0 then - return false, nil - end - - local sqrt_discriminant = math.sqrt(discriminant) - local t1 = (-b - sqrt_discriminant) / (2 * a) - local t2 = (-b + sqrt_discriminant) / (2 * a) - - if (t1 >= 0 and t1 <= 1) then - local x = x1 + t1 * dx - local y = y1 + t1 * dy - - x = x * ellipse.rx - y = y * ellipse.ry - - local px = cos_rotation * x + sin_rotation * y + ellipse.center.x - local py = -sin_rotation * x + cos_rotation * y + ellipse.center.y - - points[#points + 1] = Vector2.create(px, py) - end - - if (t2 >= 0 and t2 <= 1 and math.abs(t1 - t2) > 1e-10) then - local x = x1 + t2 * dx - local y = y1 + t2 * dy - - x = x * ellipse.rx - y = y * ellipse.ry - - local px = cos_rotation * x + sin_rotation * y + ellipse.center.x - local py = -sin_rotation * x + cos_rotation * y + ellipse.center.y - - points[#points + 1] = Vector2.create(px, py) - end - - if #points == 0 then - return false, nil - end - return true, points - end - - ---椭圆与线段相交检测(仅判断是否相交) - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean - function ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, segment) - if ellipse:contains(segment.point1) or ellipse:contains(segment.point2) then - return true - end - - local p1 = segment.point1 - ellipse.center - local p2 = segment.point2 - ellipse.center - - local baseAngle = ellipse.direction:angle() - local cos_rotation = math.cos(-baseAngle) - local sin_rotation = math.sin(-baseAngle) - - local x1 = cos_rotation * p1.x - sin_rotation * p1.y - local y1 = sin_rotation * p1.x + cos_rotation * p1.y - - local x2 = cos_rotation * p2.x - sin_rotation * p2.y - local y2 = sin_rotation * p2.x + cos_rotation * p2.y - - x1 = x1 / ellipse.rx - y1 = y1 / ellipse.ry - x2 = x2 / ellipse.rx - y2 = y2 / ellipse.ry - - local dx = x2 - x1 - local dy = y2 - y1 - - local a = dx * dx + dy * dy - local b = 2 * (x1 * dx + y1 * dy) - local c = x1 * x1 + y1 * y1 - 1 - - local discriminant = b * b - 4 * a * c - - if discriminant < 0 then - return false - end - - local sqrt_discriminant = math.sqrt(discriminant) - local t1 = (-b - sqrt_discriminant) / (2 * a) - local t2 = (-b + sqrt_discriminant) / (2 * a) - - return (t1 >= 0 and t1 <= 1) or (t2 >= 0 and t2 <= 1) - end - - ---椭圆与射线相交检测 - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.ellipseToRay(ellipse, ray) - local points = {} - - local rayPoint = ray.point - ellipse.center - local rayDir = ray.direction:clone() - - local baseAngle = ellipse.direction:angle() - local cos_rotation = math.cos(-baseAngle) - local sin_rotation = math.sin(-baseAngle) - - local x0 = cos_rotation * rayPoint.x - sin_rotation * rayPoint.y - local y0 = sin_rotation * rayPoint.x + cos_rotation * rayPoint.y - - local dx = cos_rotation * rayDir.x - sin_rotation * rayDir.y - local dy = sin_rotation * rayDir.x + cos_rotation * rayDir.y - - x0 = x0 / ellipse.rx - y0 = y0 / ellipse.ry - dx = dx / ellipse.rx - dy = dy / ellipse.ry - - local a = dx * dx + dy * dy - local b = 2 * (x0 * dx + y0 * dy) - local c = x0 * x0 + y0 * y0 - 1 - - local discriminant = b * b - 4 * a * c - - if discriminant < 0 then - return false, nil - end - - local sqrt_discriminant = math.sqrt(discriminant) - local t1 = (-b - sqrt_discriminant) / (2 * a) - local t2 = (-b + sqrt_discriminant) / (2 * a) - - if t1 >= 0 then - local x1 = x0 + t1 * dx - local y1 = y0 + t1 * dy - - x1 = x1 * ellipse.rx - y1 = y1 * ellipse.ry - - local px1 = cos_rotation * x1 + sin_rotation * y1 + ellipse.center.x - local py1 = -sin_rotation * x1 + cos_rotation * y1 + ellipse.center.y - - points[#points + 1] = Vector2.create(px1, py1) - end - - if t2 >= 0 and math.abs(t1 - t2) > 1e-10 then - local x2 = x0 + t2 * dx - local y2 = y0 + t2 * dy - - x2 = x2 * ellipse.rx - y2 = y2 * ellipse.ry - - local px2 = cos_rotation * x2 + sin_rotation * y2 + ellipse.center.x - local py2 = -sin_rotation * x2 + cos_rotation * y2 + ellipse.center.y - - points[#points + 1] = Vector2.create(px2, py2) - end - - if #points == 0 then - return false, nil - end - return true, points - end - - ---椭圆与射线相交检测(仅判断是否相交) - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean - function ShapeIntersector.ellipseHasIntersectionWithRay(ellipse, ray) - if ellipse:contains(ray.point) then - return true - end - - local rayPoint = ray.point - ellipse.center - local rayDir = ray.direction:clone() - - local baseAngle = ellipse.direction:angle() - local cos_rotation = math.cos(-baseAngle) - local sin_rotation = math.sin(-baseAngle) - - local x0 = cos_rotation * rayPoint.x - sin_rotation * rayPoint.y - local y0 = sin_rotation * rayPoint.x + cos_rotation * rayPoint.y - - local dx = cos_rotation * rayDir.x - sin_rotation * rayDir.y - local dy = sin_rotation * rayDir.x + cos_rotation * rayDir.y - - x0 = x0 / ellipse.rx - y0 = y0 / ellipse.ry - dx = dx / ellipse.rx - dy = dy / ellipse.ry - - local a = dx * dx + dy * dy - local b = 2 * (x0 * dx + y0 * dy) - local c = x0 * x0 + y0 * y0 - 1 - - local discriminant = b * b - 4 * a * c - - if discriminant < 0 then - return false - end - - local sqrt_discriminant = math.sqrt(discriminant) - local t1 = (-b - sqrt_discriminant) / (2 * a) - local t2 = (-b + sqrt_discriminant) / (2 * a) - - return t1 >= 0 or t2 >= 0 - end - - ---椭圆与线相交检测 - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param line foundation.shape.Line 线 - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.ellipseToLine(ellipse, line) - local points = {} - - local linePoint = line.point - ellipse.center - local lineDir = line.direction:clone() - - local baseAngle = ellipse.direction:angle() - local cos_rotation = math.cos(-baseAngle) - local sin_rotation = math.sin(-baseAngle) - - local x0 = cos_rotation * linePoint.x - sin_rotation * linePoint.y - local y0 = sin_rotation * linePoint.x + cos_rotation * linePoint.y - - local dx = cos_rotation * lineDir.x - sin_rotation * lineDir.y - local dy = sin_rotation * lineDir.x + cos_rotation * lineDir.y - - x0 = x0 / ellipse.rx - y0 = y0 / ellipse.ry - dx = dx / ellipse.rx - dy = dy / ellipse.ry - - local a = dx * dx + dy * dy - local b = 2 * (x0 * dx + y0 * dy) - local c = x0 * x0 + y0 * y0 - 1 - - local discriminant = b * b - 4 * a * c - - if discriminant < 0 then - return false, nil - end - - local sqrt_discriminant = math.sqrt(discriminant) - local t1 = (-b - sqrt_discriminant) / (2 * a) - local t2 = (-b + sqrt_discriminant) / (2 * a) - - local x1 = x0 + t1 * dx - local y1 = y0 + t1 * dy - - x1 = x1 * ellipse.rx - y1 = y1 * ellipse.ry - - local px1 = cos_rotation * x1 + sin_rotation * y1 + ellipse.center.x - local py1 = -sin_rotation * x1 + cos_rotation * y1 + ellipse.center.y - - points[#points + 1] = Vector2.create(px1, py1) - - if math.abs(discriminant) > 1e-10 then - local x2 = x0 + t2 * dx - local y2 = y0 + t2 * dy - - x2 = x2 * ellipse.rx - y2 = y2 * ellipse.ry - - local px2 = cos_rotation * x2 + sin_rotation * y2 + ellipse.center.x - local py2 = -sin_rotation * x2 + cos_rotation * y2 + ellipse.center.y - - points[#points + 1] = Vector2.create(px2, py2) - end - - return true, points - end - - ---椭圆与线相交检测(仅判断是否相交) - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param line foundation.shape.Line 线 - ---@return boolean - function ShapeIntersector.ellipseHasIntersectionWithLine(ellipse, line) - if ellipse:contains(line.point) then - return true - end - - local linePoint = line.point - ellipse.center - local lineDir = line.direction:clone() - - local baseAngle = ellipse.direction:angle() - local cos_rotation = math.cos(-baseAngle) - local sin_rotation = math.sin(-baseAngle) - - local x0 = cos_rotation * linePoint.x - sin_rotation * linePoint.y - local y0 = sin_rotation * linePoint.x + cos_rotation * linePoint.y - - local dx = cos_rotation * lineDir.x - sin_rotation * lineDir.y - local dy = sin_rotation * lineDir.x + cos_rotation * lineDir.y - - x0 = x0 / ellipse.rx - y0 = y0 / ellipse.ry - dx = dx / ellipse.rx - dy = dy / ellipse.ry - - local a = dx * dx + dy * dy - local b = 2 * (x0 * dx + y0 * dy) - local c = x0 * x0 + y0 * y0 - 1 - - local discriminant = b * b - 4 * a * c - - return discriminant >= 0 - end - - ---椭圆与扇形相交检测 - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param sector foundation.shape.Sector 扇形 - ---@return boolean, foundation.math.Vector2[]|nil - function ShapeIntersector.ellipseToSector(ellipse, sector) - Segment = Segment or require("foundation.shape.Segment") - local points = {} - - if math.abs(sector.range) >= 1 then - ---@diagnostic disable-next-line: param-type-mismatch - return ShapeIntersector.ellipseToCircle(ellipse, sector) - end - - ---@diagnostic disable-next-line: param-type-mismatch - local success, circle_points = ShapeIntersector.ellipseToCircle(ellipse, sector) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - points[#points + 1] = p - end - end - end - - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - - local success1, edge_points1 = ShapeIntersector.ellipseToSegment(ellipse, startSegment) - if success1 then - for _, p in ipairs(edge_points1) do - points[#points + 1] = p - end - end - - local success2, edge_points2 = ShapeIntersector.ellipseToSegment(ellipse, endSegment) - if success2 then - for _, p in ipairs(edge_points2) do - points[#points + 1] = p - end - end - - if ShapeIntersector.sectorContainsPoint(sector, ellipse.center) then - points[#points + 1] = ellipse.center:clone() - end - - if ellipse:contains(sector.center) then - points[#points + 1] = sector.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---椭圆与扇形相交检测(仅判断是否相交) - ---@param ellipse foundation.shape.Ellipse 椭圆 - ---@param sector foundation.shape.Sector 扇形 - ---@return boolean - function ShapeIntersector.ellipseHasIntersectionWithSector(ellipse, sector) - Segment = Segment or require("foundation.shape.Segment") - - if math.abs(sector.range) >= 1 then - ---@diagnostic disable-next-line: param-type-mismatch - return ShapeIntersector.ellipseHasIntersectionWithCircle(ellipse, sector) - end - - if ShapeIntersector.sectorContainsPoint(sector, ellipse.center) then - return true - end - - if ellipse:contains(sector.center) then - return true - end - - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - - if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, startSegment) or - ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, endSegment) then - return true - end - - local segments = 18 - local arcPoints = sector:discretize(segments) - - for i = 1, #arcPoints - 1 do - local segment = Segment.create(arcPoints[i], arcPoints[i + 1]) - if ShapeIntersector.ellipseHasIntersectionWithSegment(ellipse, segment) then - return true - end - end - - return false - end - - return ShapeIntersector -end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/LineIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/LineIntersector.lua deleted file mode 100644 index 465d7dcd..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/LineIntersector.lua +++ /dev/null @@ -1,206 +0,0 @@ ----@param ShapeIntersector foundation.shape.ShapeIntersector -return function(ShapeIntersector) - local math = math - - local Vector2 = require("foundation.math.Vector2") - - ---检查直线与线段的相交 - ---@param line foundation.shape.Line 直线 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.lineToSegment(line, segment) - local points = {} - local a = line.point - local b = line.point + line.direction - local c = segment.point1 - local d = segment.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if u >= 0 and u <= 1 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points - end - - ---检查直线是否与线段相交 - ---@param line foundation.shape.Line 直线 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean - function ShapeIntersector.lineHasIntersectionWithSegment(line, segment) - local a = line.point - local b = line.point + line.direction - local c = segment.point1 - local d = segment.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false - end - - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return u >= 0 and u <= 1 - end - - ---检查直线与直线的相交 - ---@param line1 foundation.shape.Line 第一条直线 - ---@param line2 foundation.shape.Line 第二条直线 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.lineToLine(line1, line2) - local points = {} - local a = line1.point - local b = line1.point + line1.direction - local c = line2.point - local d = line2.point + line2.direction - - local dir_cross = line1.direction:cross(line2.direction) - if math.abs(dir_cross) <= 1e-10 then - local point_diff = line2.point - line1.point - if math.abs(point_diff:cross(line1.direction)) <= 1e-10 then - points[#points + 1] = line1.point:clone() - points[#points + 1] = line1:getPoint(1) - return true, points - else - return false, nil - end - end - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - - return true, points - end - - ---检查直线是否与直线相交 - ---@param line1 foundation.shape.Line 第一条直线 - ---@param line2 foundation.shape.Line 第二条直线 - ---@return boolean - function ShapeIntersector.lineHasIntersectionWithLine(line1, line2) - local dir_cross = line1.direction:cross(line2.direction) - if math.abs(dir_cross) <= 1e-10 then - local point_diff = line2.point - line1.point - return math.abs(point_diff:cross(line1.direction)) <= 1e-10 - end - return true - end - - ---检查直线与射线的相交 - ---@param line foundation.shape.Line 直线 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.lineToRay(line, ray) - local points = {} - local a = line.point - local b = line.point + line.direction - local c = ray.point - local d = ray.point + ray.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if u >= 0 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points - end - - ---检查直线是否与射线相交 - ---@param line foundation.shape.Line 直线 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean - function ShapeIntersector.lineHasIntersectionWithRay(line, ray) - local a = line.point - local b = line.point + line.direction - local c = ray.point - local d = ray.point + ray.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false - end - - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return u >= 0 - end - - ---检查直线与圆的相交 - ---@param line foundation.shape.Line 直线 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.lineToCircle(line, circle) - local points = {} - local dir = line.direction - local len = dir:length() - if len <= 1e-10 then - return false, nil - end - dir = dir / len - local L = line.point - circle.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - circle.radius * circle.radius - local discriminant = b * b - 4 * a * c - if discriminant >= 0 then - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - points[#points + 1] = line.point + dir * t1 - if math.abs(t2 - t1) > 1e-10 then - points[#points + 1] = line.point + dir * t2 - end - end - - if #points == 0 then - return false, nil - end - return true, points - end - - ---检查直线是否与圆相交 - ---@param line foundation.shape.Line 直线 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean - function ShapeIntersector.lineHasIntersectionWithCircle(line, circle) - local dir = line.direction - local len = dir:length() - if len <= 1e-10 then - return false - end - dir = dir / len - local L = line.point - circle.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - circle.radius * circle.radius - local discriminant = b * b - 4 * a * c - return discriminant >= 0 - end - return ShapeIntersector -end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/PolygonIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/PolygonIntersector.lua deleted file mode 100644 index d44627c4..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/PolygonIntersector.lua +++ /dev/null @@ -1,492 +0,0 @@ ----@param ShapeIntersector foundation.shape.ShapeIntersector -return function(ShapeIntersector) - local ipairs = ipairs - - ---检查多边形与线段的相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.polygonToSegment(polygon, segment) - local points = {} - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge, segment) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, segment.point1) then - points[#points + 1] = segment.point1:clone() - end - - if segment.point1 ~= segment.point2 and ShapeIntersector.polygonContainsPoint(polygon, segment.point2) then - points[#points + 1] = segment.point2:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查多边形是否与线段相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean - function ShapeIntersector.polygonHasIntersectionWithSegment(polygon, segment) - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then - return true - end - end - - return ShapeIntersector.polygonContainsPoint(polygon, segment.point1) or - ShapeIntersector.polygonContainsPoint(polygon, segment.point2) - end - - ---检查多边形与线的相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param line foundation.shape.Line 线 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.polygonToLine(polygon, line) - local points = {} - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.lineToSegment(line, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, line.point) then - points[#points + 1] = line.point:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查多边形是否与线相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param line foundation.shape.Line 线 - ---@return boolean - function ShapeIntersector.polygonHasIntersectionWithLine(polygon, line) - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then - return true - end - end - - return ShapeIntersector.polygonContainsPoint(polygon, line.point) - end - - ---检查多边形与射线的相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.polygonToRay(polygon, ray) - local points = {} - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, ray.point) then - points[#points + 1] = ray.point:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查多边形是否与射线相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean - function ShapeIntersector.polygonHasIntersectionWithRay(polygon, ray) - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then - return true - end - end - - return ShapeIntersector.polygonContainsPoint(polygon, ray.point) - end - - ---检查多边形与圆的相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.polygonToCircle(polygon, circle) - local points = {} - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - points[#points + 1] = vertex - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, circle.center) then - points[#points + 1] = circle.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查多边形是否与圆相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean - function ShapeIntersector.polygonHasIntersectionWithCircle(polygon, circle) - if ShapeIntersector.polygonContainsPoint(polygon, circle.center) then - return true - end - - local edges = polygon:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then - return true - end - end - - local vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - return true - end - end - - return false - end - - ---检查多边形与三角形的相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param triangle foundation.shape.Triangle 三角形 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.polygonToTriangle(polygon, triangle) - local points = {} - local edges1 = polygon:getEdges() - local edges2 = triangle:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon, vertex) then - points[#points + 1] = vertex - end - end - - vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle, vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查多边形是否与三角形相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param triangle foundation.shape.Triangle 三角形 - ---@return boolean - function ShapeIntersector.polygonHasIntersectionWithTriangle(polygon, triangle) - local edges1 = polygon:getEdges() - local edges2 = triangle:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then - return true - end - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon, vertex) then - return true - end - end - - vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle, vertex) then - return true - end - end - - return false - end - - ---检查多边形与矩形的相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.polygonToRectangle(polygon, rectangle) - local points = {} - local edges1 = polygon:getEdges() - local edges2 = rectangle:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon, vertex) then - points[#points + 1] = vertex - end - end - - vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.rectangleContainsPoint(rectangle, vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查多边形是否与矩形相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@return boolean - function ShapeIntersector.polygonHasIntersectionWithRectangle(polygon, rectangle) - local edges1 = polygon:getEdges() - local edges2 = rectangle:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then - return true - end - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon, vertex) then - return true - end - end - - vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.rectangleContainsPoint(rectangle, vertex) then - return true - end - end - - return false - end - - ---检查多边形与多边形的相交 - ---@param polygon1 foundation.shape.Polygon 第一个多边形 - ---@param polygon2 foundation.shape.Polygon 第二个多边形 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.polygonToPolygon(polygon1, polygon2) - local points = {} - local edges1 = polygon1:getEdges() - local edges2 = polygon2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - - local vertices = polygon2:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon1, vertex) then - points[#points + 1] = vertex - end - end - - vertices = polygon1:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon2, vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查多边形是否与多边形相交 - ---@param polygon1 foundation.shape.Polygon 第一个多边形 - ---@param polygon2 foundation.shape.Polygon 第二个多边形 - ---@return boolean - function ShapeIntersector.polygonHasIntersectionWithPolygon(polygon1, polygon2) - local edges1 = polygon1:getEdges() - local edges2 = polygon2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then - return true - end - end - end - - local vertices = polygon2:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon1, vertex) then - return true - end - end - - vertices = polygon1:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.polygonContainsPoint(polygon2, vertex) then - return true - end - end - - return false - end - - ---检查多边形与扇形的相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param sector foundation.shape.Sector 扇形 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.polygonToSector(polygon, sector) - local points = {} - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - points[#points + 1] = vertex - end - end - - if ShapeIntersector.polygonContainsPoint(polygon, sector.center) then - points[#points + 1] = sector.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查多边形是否与扇形相交 - ---@param polygon foundation.shape.Polygon 多边形 - ---@param sector foundation.shape.Sector 扇形 - ---@return boolean - function ShapeIntersector.polygonHasIntersectionWithSector(polygon, sector) - local edges = polygon:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then - return true - end - end - - local vertices = polygon:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - return true - end - end - - return ShapeIntersector.polygonContainsPoint(polygon, sector.center) - end - - return ShapeIntersector -end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RayIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RayIntersector.lua deleted file mode 100644 index 7f44b863..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RayIntersector.lua +++ /dev/null @@ -1,193 +0,0 @@ ----@param ShapeIntersector foundation.shape.ShapeIntersector -return function(ShapeIntersector) - local math = math - - local Vector2 = require("foundation.math.Vector2") - - ---检查射线与线段的相交 - ---@param ray foundation.shape.Ray 射线 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.rayToSegment(ray, segment) - local points = {} - local a = ray.point - local b = ray.point + ray.direction - local c = segment.point1 - local d = segment.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if t >= 0 and u >= 0 and u <= 1 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points <= 1e-10 then - return false, nil - end - return true, points - end - - ---检查射线是否与线段相交 - ---@param ray foundation.shape.Ray 射线 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean - function ShapeIntersector.rayHasIntersectionWithSegment(ray, segment) - local a = ray.point - local b = ray.point + ray.direction - local c = segment.point1 - local d = segment.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and u >= 0 and u <= 1 - end - - ---检查射线与射线的相交 - ---@param ray1 foundation.shape.Ray 第一条射线 - ---@param ray2 foundation.shape.Ray 第二条射线 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.rayToRay(ray1, ray2) - local points = {} - local a = ray1.point - local b = ray1.point + ray1.direction - local c = ray2.point - local d = ray2.point + ray2.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) < 1e-10 then - local dir_cross = ray1.direction:cross(ray2.direction) - if math.abs(dir_cross) < 1e-10 then - local point_diff = ray2.point - ray1.point - local t = point_diff:dot(ray1.direction) - if t >= 0 then - points[#points + 1] = ray1.point + ray1.direction * t - end - end - - if #points == 0 then - return false, nil - end - return true, points - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if t >= 0 and u >= 0 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points == 0 then - return false, nil - end - return true, points - end - - ---检查射线是否与射线相交 - ---@param ray1 foundation.shape.Ray 第一条射线 - ---@param ray2 foundation.shape.Ray 第二条射线 - ---@return boolean - function ShapeIntersector.rayHasIntersectionWithRay(ray1, ray2) - local a = ray1.point - local b = ray1.point + ray1.direction - local c = ray2.point - local d = ray2.point + ray2.direction - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - local dir_cross = ray1.direction:cross(ray2.direction) - if math.abs(dir_cross) <= 1e-10 then - local point_diff = ray2.point - ray1.point - local t = point_diff:dot(ray1.direction) - return t >= 0 - end - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and u >= 0 - end - - ---检查射线与圆的相交 - ---@param ray foundation.shape.Ray 射线 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.rayToCircle(ray, circle) - local points = {} - local dir = ray.direction - local len = dir:length() - if len <= 1e-10 then - return false, nil - end - dir = dir / len - local L = ray.point - circle.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - circle.radius * circle.radius - local discriminant = b * b - 4 * a * c - if discriminant >= 0 then - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - if t1 >= 0 then - points[#points + 1] = ray.point + dir * t1 - end - if t2 >= 0 and math.abs(t2 - t1) > 1e-10 then - points[#points + 1] = ray.point + dir * t2 - end - end - - if #points == 0 then - return false, nil - end - return true, points - end - - ---检查射线是否与圆相交 - ---@param ray foundation.shape.Ray 射线 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean - function ShapeIntersector.rayHasIntersectionWithCircle(ray, circle) - local dir = ray.direction - local len = dir:length() - if len <= 1e-10 then - return false - end - dir = dir / len - local L = ray.point - circle.center - local a = dir:dot(dir) - local b = 2 * L:dot(dir) - local c = L:dot(L) - circle.radius * circle.radius - local discriminant = b * b - 4 * a * c - - if discriminant <= 1e-10 then - return false - end - - local sqrt_d = math.sqrt(discriminant) - local t1 = (-b - sqrt_d) / (2 * a) - local t2 = (-b + sqrt_d) / (2 * a) - - return t1 >= 0 or t2 >= 0 - end - - return ShapeIntersector -end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RectangleIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RectangleIntersector.lua deleted file mode 100644 index b65afcd3..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/RectangleIntersector.lua +++ /dev/null @@ -1,311 +0,0 @@ ----@param ShapeIntersector foundation.shape.ShapeIntersector -return function(ShapeIntersector) - local ipairs = ipairs - - ---检查矩形与线段的相交 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.rectangleToSegment(rectangle, segment) - local points = {} - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.segmentToSegment(segment, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if rectangle:contains(segment.point1) then - points[#points + 1] = segment.point1:clone() - end - if segment.point1 ~= segment.point2 and rectangle:contains(segment.point2) then - points[#points + 1] = segment.point2:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查矩形是否与线段相交 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean - function ShapeIntersector.rectangleHasIntersectionWithSegment(rectangle, segment) - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then - return true - end - end - return rectangle:contains(segment.point1) or rectangle:contains(segment.point2) - end - - ---检查矩形与三角形的相交 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@param triangle foundation.shape.Triangle 三角形 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.rectangleToTriangle(rectangle, triangle) - local points = {} - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.triangleToSegment(triangle, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if rectangle:contains(vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查矩形是否与三角形相交 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@param triangle foundation.shape.Triangle 三角形 - ---@return boolean - function ShapeIntersector.rectangleHasIntersectionWithTriangle(rectangle, triangle) - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.triangleHasIntersectionWithSegment(triangle, edge) then - return true - end - end - return rectangle:contains(triangle.point1) or rectangle:contains(triangle.point2) or rectangle:contains(triangle.point3) - end - - ---检查矩形与直线的相交 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@param line foundation.shape.Line 直线 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.rectangleToLine(rectangle, line) - local points = {} - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.lineToSegment(line, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查矩形是否与直线相交 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@param line foundation.shape.Line 直线 - ---@return boolean - function ShapeIntersector.rectangleHasIntersectionWithLine(rectangle, line) - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then - return true - end - end - return rectangle:contains(line.point) - end - - ---检查矩形与射线的相交 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.rectangleToRay(rectangle, ray) - local points = {} - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - if rectangle:contains(ray.point) then - points[#points + 1] = ray.point:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查矩形是否与射线相交 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean - function ShapeIntersector.rectangleHasIntersectionWithRay(rectangle, ray) - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then - return true - end - end - return rectangle:contains(ray.point) - end - - ---检查矩形与圆的相交 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.rectangleToCircle(rectangle, circle) - local points = {} - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - points[#points + 1] = vertex - end - end - - if rectangle:contains(circle.center) then - points[#points + 1] = circle.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查矩形是否与圆相交 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean - function ShapeIntersector.rectangleHasIntersectionWithCircle(rectangle, circle) - if rectangle:contains(circle.center) then - return true - end - - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then - return true - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - return true - end - end - - return false - end - - ---检查矩形与矩形的相交 - ---@param rectangle1 foundation.shape.Rectangle 第一个矩形 - ---@param rectangle2 foundation.shape.Rectangle 第二个矩形 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.rectangleToRectangle(rectangle1, rectangle2) - local points = {} - local edges1 = rectangle1:getEdges() - local edges2 = rectangle2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - - local vertices1 = rectangle1:getVertices() - for _, vertex in ipairs(vertices1) do - if rectangle2:contains(vertex) then - points[#points + 1] = vertex - end - end - - local vertices2 = rectangle2:getVertices() - for _, vertex in ipairs(vertices2) do - if rectangle1:contains(vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查矩形是否与矩形相交 - ---@param rectangle1 foundation.shape.Rectangle 第一个矩形 - ---@param rectangle2 foundation.shape.Rectangle 第二个矩形 - ---@return boolean - function ShapeIntersector.rectangleHasIntersectionWithRectangle(rectangle1, rectangle2) - local edges1 = rectangle1:getEdges() - local edges2 = rectangle2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then - return true - end - end - end - - local vertices1 = rectangle1:getVertices() - for _, vertex in ipairs(vertices1) do - if rectangle2:contains(vertex) then - return true - end - end - - local vertices2 = rectangle2:getVertices() - for _, vertex in ipairs(vertices2) do - if rectangle1:contains(vertex) then - return true - end - end - - return false - end - - return ShapeIntersector -end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SectorIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SectorIntersector.lua deleted file mode 100644 index 71952640..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SectorIntersector.lua +++ /dev/null @@ -1,590 +0,0 @@ ----@param ShapeIntersector foundation.shape.ShapeIntersector -return function(ShapeIntersector) - local math = math - - local ipairs = ipairs - local require = require - - local Segment - - ---检查扇形与线段的相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.sectorToSegment(sector, segment) - Segment = Segment or require("foundation.shape.Segment") - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleToSegment(sector, segment) - end - local success, circle_points = ShapeIntersector.circleToSegment(sector, segment) - if success then - local n = 0 - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - n = n + 1 - points[#points + 1] = p - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - local success1, edge_points1 = ShapeIntersector.segmentToSegment(startSegment, segment) - if success1 then - for _, p in ipairs(edge_points1) do - points[#points + 1] = p - end - end - local success2, edge_points2 = ShapeIntersector.segmentToSegment(endSegment, segment) - if success2 then - for _, p in ipairs(edge_points2) do - points[#points + 1] = p - end - end - if ShapeIntersector.sectorContainsPoint(sector, segment.point1) then - points[#points + 1] = segment.point1:clone() - end - if segment.point1 ~= segment.point2 and ShapeIntersector.sectorContainsPoint(sector, segment.point2) then - points[#points + 1] = segment.point2:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查扇形是否与线段相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean - function ShapeIntersector.sectorHasIntersectionWithSegment(sector, segment) - Segment = Segment or require("foundation.shape.Segment") - if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleHasIntersectionWithSegment(sector, segment) - end - if ShapeIntersector.sectorContainsPoint(sector, segment.point1) or - ShapeIntersector.sectorContainsPoint(sector, segment.point2) then - return true - end - local success, circle_points = ShapeIntersector.circleToSegment(sector, segment) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - return true - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - if ShapeIntersector.segmentHasIntersectionWithSegment(startSegment, segment) or - ShapeIntersector.segmentHasIntersectionWithSegment(endSegment, segment) then - return true - end - return false - end - - ---检查扇形与直线的相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param line foundation.shape.Line 直线 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.sectorToLine(sector, line) - Segment = Segment or require("foundation.shape.Segment") - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.lineToCircle(line, sector) - end - local success, circle_points = ShapeIntersector.lineToCircle(line, sector) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - points[#points + 1] = p - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - local success1, edge_points1 = ShapeIntersector.lineToSegment(line, startSegment) - if success1 then - for _, p in ipairs(edge_points1) do - points[#points + 1] = p - end - end - local success2, edge_points2 = ShapeIntersector.lineToSegment(line, endSegment) - if success2 then - for _, p in ipairs(edge_points2) do - points[#points + 1] = p - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查扇形是否与直线相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param line foundation.shape.Line 直线 - ---@return boolean - function ShapeIntersector.sectorHasIntersectionWithLine(sector, line) - Segment = Segment or require("foundation.shape.Segment") - if math.abs(sector.range) >= 1 then - return ShapeIntersector.lineHasIntersectionWithCircle(line, sector) - end - local success, circle_points = ShapeIntersector.lineToCircle(line, sector) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - return true - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - if ShapeIntersector.lineHasIntersectionWithSegment(line, startSegment) or - ShapeIntersector.lineHasIntersectionWithSegment(line, endSegment) then - return true - end - return false - end - - ---检查扇形与射线的相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.sectorToRay(sector, ray) - Segment = Segment or require("foundation.shape.Segment") - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.rayToCircle(ray, sector) - end - local success, circle_points = ShapeIntersector.rayToCircle(ray, sector) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - points[#points + 1] = p - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - local success1, edge_points1 = ShapeIntersector.rayToSegment(ray, startSegment) - if success1 then - for _, p in ipairs(edge_points1) do - points[#points + 1] = p - end - end - local success2, edge_points2 = ShapeIntersector.rayToSegment(ray, endSegment) - if success2 then - for _, p in ipairs(edge_points2) do - points[#points + 1] = p - end - end - if ShapeIntersector.sectorContainsPoint(sector, ray.point) then - points[#points + 1] = ray.point:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查扇形是否与射线相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean - function ShapeIntersector.sectorHasIntersectionWithRay(sector, ray) - Segment = Segment or require("foundation.shape.Segment") - if math.abs(sector.range) >= 1 then - return ShapeIntersector.rayHasIntersectionWithCircle(ray, sector) - end - if ShapeIntersector.sectorContainsPoint(sector, ray.point) then - return true - end - local success, circle_points = ShapeIntersector.rayToCircle(ray, sector) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - return true - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - if ShapeIntersector.rayHasIntersectionWithSegment(ray, startSegment) or - ShapeIntersector.rayHasIntersectionWithSegment(ray, endSegment) then - return true - end - return false - end - - ---检查扇形与三角形的相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param triangle foundation.shape.Triangle 三角形 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.sectorToTriangle(sector, triangle) - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.triangleToCircle(triangle, sector) - end - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - points[#points + 1] = vertex - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, sector.center) then - points[#points + 1] = sector.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查扇形是否与三角形相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param triangle foundation.shape.Triangle 三角形 - ---@return boolean - function ShapeIntersector.sectorHasIntersectionWithTriangle(sector, triangle) - if math.abs(sector.range) >= 1 then - return ShapeIntersector.triangleHasIntersectionWithCircle(triangle, sector) - end - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then - return true - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - return true - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, sector.center) then - return true - end - - return false - end - - ---检查扇形与圆的相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.sectorToCircle(sector, circle) - Segment = Segment or require("foundation.shape.Segment") - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleToCircle(sector, circle) - end - local success, circle_points = ShapeIntersector.circleToCircle(sector, circle) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - points[#points + 1] = p - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - local success1, edge_points1 = ShapeIntersector.circleToSegment(circle, startSegment) - if success1 then - for _, p in ipairs(edge_points1) do - points[#points + 1] = p - end - end - local success2, edge_points2 = ShapeIntersector.circleToSegment(circle, endSegment) - if success2 then - for _, p in ipairs(edge_points2) do - points[#points + 1] = p - end - end - if ShapeIntersector.sectorContainsPoint(sector, circle.center) then - points[#points + 1] = circle.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查扇形是否与圆相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean - function ShapeIntersector.sectorHasIntersectionWithCircle(sector, circle) - Segment = Segment or require("foundation.shape.Segment") - if math.abs(sector.range) >= 1 then - return ShapeIntersector.circleHasIntersectionWithCircle(sector, circle) - end - if ShapeIntersector.sectorContainsPoint(sector, circle.center) then - return true - end - local success, circle_points = ShapeIntersector.circleToCircle(sector, circle) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector, p) then - return true - end - end - end - local startDir = sector.direction - local endDir = sector.direction:rotated(sector.range * 2 * math.pi) - local startPoint = sector.center + startDir * sector.radius - local endPoint = sector.center + endDir * sector.radius - local startSegment = Segment.create(sector.center, startPoint) - local endSegment = Segment.create(sector.center, endPoint) - if ShapeIntersector.circleHasIntersectionWithSegment(circle, startSegment) or - ShapeIntersector.circleHasIntersectionWithSegment(circle, endSegment) then - return true - end - return false - end - - ---检查扇形与矩形的相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.sectorToRectangle(sector, rectangle) - local points = {} - if math.abs(sector.range) >= 1 then - return ShapeIntersector.rectangleToCircle(rectangle, sector) - end - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.sectorToSegment(sector, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - points[#points + 1] = vertex - end - end - - if ShapeIntersector.rectangleContainsPoint(rectangle, sector.center) then - points[#points + 1] = sector.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查扇形是否与矩形相交 - ---@param sector foundation.shape.Sector 扇形 - ---@param rectangle foundation.shape.Rectangle 矩形 - ---@return boolean - function ShapeIntersector.sectorHasIntersectionWithRectangle(sector, rectangle) - if math.abs(sector.range) >= 1 then - return ShapeIntersector.rectangleHasIntersectionWithCircle(rectangle, sector) - end - local edges = rectangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.sectorHasIntersectionWithSegment(sector, edge) then - return true - end - end - - local vertices = rectangle:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.sectorContainsPoint(sector, vertex) then - return true - end - end - - if ShapeIntersector.rectangleContainsPoint(rectangle, sector.center) then - return true - end - - return false - end - - ---检查扇形与扇形的相交 - ---@param sector1 foundation.shape.Sector 第一个扇形 - ---@param sector2 foundation.shape.Sector 第二个扇形 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.sectorToSector(sector1, sector2) - Segment = Segment or require("foundation.shape.Segment") - local points = {} - if math.abs(sector1.range) >= 1 and math.abs(sector2.range) >= 1 then - return ShapeIntersector.circleToCircle(sector1, sector2) - end - local success, circle_points = ShapeIntersector.circleToCircle(sector1, sector2) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then - points[#points + 1] = p - end - end - end - if math.abs(sector1.range) < 1 then - local startDir1 = sector1.direction - local endDir1 = sector1.direction:rotated(sector1.range * 2 * math.pi) - local startPoint1 = sector1.center + startDir1 * sector1.radius - local endPoint1 = sector1.center + endDir1 * sector1.radius - local startSegment1 = Segment.create(sector1.center, startPoint1) - local endSegment1 = Segment.create(sector1.center, endPoint1) - local success1, edge_points1 = ShapeIntersector.sectorToSegment(sector2, startSegment1) - if success1 then - for _, p in ipairs(edge_points1) do - points[#points + 1] = p - end - end - local success2, edge_points2 = ShapeIntersector.sectorToSegment(sector2, endSegment1) - if success2 then - for _, p in ipairs(edge_points2) do - points[#points + 1] = p - end - end - end - if math.abs(sector2.range) < 1 then - local startDir2 = sector2.direction - local endDir2 = sector2.direction:rotated(sector2.range * 2 * math.pi) - local startPoint2 = sector2.center + startDir2 * sector2.radius - local endPoint2 = sector2.center + endDir2 * sector2.radius - local startSegment2 = Segment.create(sector2.center, startPoint2) - local endSegment2 = Segment.create(sector2.center, endPoint2) - local success3, edge_points3 = ShapeIntersector.sectorToSegment(sector1, startSegment2) - if success3 then - for _, p in ipairs(edge_points3) do - points[#points + 1] = p - end - end - local success4, edge_points4 = ShapeIntersector.sectorToSegment(sector1, endSegment2) - if success4 then - for _, p in ipairs(edge_points4) do - points[#points + 1] = p - end - end - end - if ShapeIntersector.sectorContainsPoint(sector1, sector2.center) then - points[#points + 1] = sector2.center:clone() - end - if ShapeIntersector.sectorContainsPoint(sector2, sector1.center) then - points[#points + 1] = sector1.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---仅检查扇形是否与扇形相交 - ---@param sector1 foundation.shape.Sector 第一个扇形 - ---@param sector2 foundation.shape.Sector 第二个扇形 - ---@return boolean - function ShapeIntersector.sectorHasIntersectionWithSector(sector1, sector2) - Segment = Segment or require("foundation.shape.Segment") - if math.abs(sector1.range) >= 1 and math.abs(sector2.range) >= 1 then - return ShapeIntersector.circleHasIntersectionWithCircle(sector1, sector2) - end - if ShapeIntersector.sectorContainsPoint(sector1, sector2.center) or - ShapeIntersector.sectorContainsPoint(sector2, sector1.center) then - return true - end - local success, circle_points = ShapeIntersector.circleToCircle(sector1, sector2) - if success then - for _, p in ipairs(circle_points) do - if ShapeIntersector.sectorContainsPoint(sector1, p) and ShapeIntersector.sectorContainsPoint(sector2, p) then - return true - end - end - end - if math.abs(sector1.range) < 1 then - local startDir1 = sector1.direction - local endDir1 = sector1.direction:rotated(sector1.range * 2 * math.pi) - local startPoint1 = sector1.center + startDir1 * sector1.radius - local endPoint1 = sector1.center + endDir1 * sector1.radius - local startSegment1 = Segment.create(sector1.center, startPoint1) - local endSegment1 = Segment.create(sector1.center, endPoint1) - if ShapeIntersector.sectorHasIntersectionWithSegment(sector2, startSegment1) or - ShapeIntersector.sectorHasIntersectionWithSegment(sector2, endSegment1) then - return true - end - end - if math.abs(sector2.range) < 1 then - local startDir2 = sector2.direction - local endDir2 = sector2.direction:rotated(sector2.range * 2 * math.pi) - local startPoint2 = sector2.center + startDir2 * sector2.radius - local endPoint2 = sector2.center + endDir2 * sector2.radius - local startSegment2 = Segment.create(sector2.center, startPoint2) - local endSegment2 = Segment.create(sector2.center, endPoint2) - if ShapeIntersector.sectorHasIntersectionWithSegment(sector1, startSegment2) or - ShapeIntersector.sectorHasIntersectionWithSegment(sector1, endSegment2) then - return true - end - end - return false - end - - return ShapeIntersector -end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SegmentIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SegmentIntersector.lua deleted file mode 100644 index 72eb8de1..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/SegmentIntersector.lua +++ /dev/null @@ -1,60 +0,0 @@ ----@param ShapeIntersector foundation.shape.ShapeIntersector -return function(ShapeIntersector) - local math = math - - local Vector2 = require("foundation.math.Vector2") - - ---检查两条线段的相交 - ---@param segment1 foundation.shape.Segment 第一条线段 - ---@param segment2 foundation.shape.Segment 第二条线段 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.segmentToSegment(segment1, segment2) - local points = {} - local a = segment1.point1 - local b = segment1.point2 - local c = segment2.point1 - local d = segment2.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false, nil - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - if t >= 0 and t <= 1 and u >= 0 and u <= 1 then - local x = a.x + t * (b.x - a.x) - local y = a.y + t * (b.y - a.y) - points[#points + 1] = Vector2.create(x, y) - end - - if #points <= 1e-10 then - return false, nil - end - return true, points - end - - ---检查两条线段是否相交 - ---@param segment1 foundation.shape.Segment 第一条线段 - ---@param segment2 foundation.shape.Segment 第二条线段 - ---@return boolean - function ShapeIntersector.segmentHasIntersectionWithSegment(segment1, segment2) - local a = segment1.point1 - local b = segment1.point2 - local c = segment2.point1 - local d = segment2.point2 - - local denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) - if math.abs(denom) <= 1e-10 then - return false - end - - local t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom - local u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom - - return t >= 0 and t <= 1 and u >= 0 and u <= 1 - end - - return ShapeIntersector -end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/TriangleIntersector.lua b/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/TriangleIntersector.lua deleted file mode 100644 index 849fe563..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/ShapeIntersector/TriangleIntersector.lua +++ /dev/null @@ -1,280 +0,0 @@ ----@param ShapeIntersector foundation.shape.ShapeIntersector -return function(ShapeIntersector) - local ipairs = ipairs - - ---检查三角形与线段的相交 - ---@param triangle foundation.shape.Triangle 三角形 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.triangleToSegment(triangle, segment) - local points = {} - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge, segment) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if ShapeIntersector.triangleContainsPoint(triangle, segment.point1) then - points[#points + 1] = segment.point1:clone() - end - if segment.point1 ~= segment.point2 and ShapeIntersector.triangleContainsPoint(triangle, segment.point2) then - points[#points + 1] = segment.point2:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---只检查三角形与线段是否相交 - ---@param triangle foundation.shape.Triangle 三角形 - ---@param segment foundation.shape.Segment 线段 - ---@return boolean - function ShapeIntersector.triangleHasIntersectionWithSegment(triangle, segment) - local edges = triangle:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge, segment) then - return true - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, segment.point1) or - ShapeIntersector.triangleContainsPoint(triangle, segment.point2) then - return true - end - - return false - end - - ---检查三角形与另一个三角形的相交 - ---@param triangle1 foundation.shape.Triangle 第一个三角形 - ---@param triangle2 foundation.shape.Triangle 第二个三角形 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.triangleToTriangle(triangle1, triangle2) - local points = {} - local edges1 = triangle1:getEdges() - local edges2 = triangle2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - local success, edge_points = ShapeIntersector.segmentToSegment(edge1, edge2) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - end - - local vertices = triangle2:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle1, vertex) then - points[#points + 1] = vertex - end - end - - vertices = triangle1:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle2, vertex) then - points[#points + 1] = vertex - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---只检查三角形与另一个三角形是否相交 - ---@param triangle1 foundation.shape.Triangle 第一个三角形 - ---@param triangle2 foundation.shape.Triangle 第二个三角形 - ---@return boolean - function ShapeIntersector.triangleHasIntersectionWithTriangle(triangle1, triangle2) - local edges1 = triangle1:getEdges() - local edges2 = triangle2:getEdges() - - for _, edge1 in ipairs(edges1) do - for _, edge2 in ipairs(edges2) do - if ShapeIntersector.segmentHasIntersectionWithSegment(edge1, edge2) then - return true - end - end - end - - local vertices = triangle2:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle1, vertex) then - return true - end - end - - vertices = triangle1:getVertices() - for _, vertex in ipairs(vertices) do - if ShapeIntersector.triangleContainsPoint(triangle2, vertex) then - return true - end - end - - return false - end - - ---检查三角形与直线的相交 - ---@param triangle foundation.shape.Triangle 三角形 - ---@param line foundation.shape.Line 直线 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.triangleToLine(triangle, line) - local points = {} - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.lineToSegment(line, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---只检查三角形与直线是否相交 - ---@param triangle foundation.shape.Triangle 三角形 - ---@param line foundation.shape.Line 直线 - ---@return boolean - function ShapeIntersector.triangleHasIntersectionWithLine(triangle, line) - local edges = triangle:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.lineHasIntersectionWithSegment(line, edge) then - return true - end - end - - return false - end - - ---检查三角形与射线的相交 - ---@param triangle foundation.shape.Triangle 三角形 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.triangleToRay(triangle, ray) - local points = {} - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.rayToSegment(ray, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - if ShapeIntersector.triangleContainsPoint(triangle, ray.point) then - points[#points + 1] = ray.point:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---只检查三角形与射线是否相交 - ---@param triangle foundation.shape.Triangle 三角形 - ---@param ray foundation.shape.Ray 射线 - ---@return boolean - function ShapeIntersector.triangleHasIntersectionWithRay(triangle, ray) - local edges = triangle:getEdges() - - for _, edge in ipairs(edges) do - if ShapeIntersector.rayHasIntersectionWithSegment(ray, edge) then - return true - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, ray.point) then - return true - end - - return false - end - - ---检查三角形与圆的相交 - ---@param triangle foundation.shape.Triangle 三角形 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean, foundation.math.Vector2[] | nil - function ShapeIntersector.triangleToCircle(triangle, circle) - local points = {} - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - local success, edge_points = ShapeIntersector.circleToSegment(circle, edge) - if success then - for _, p in ipairs(edge_points) do - points[#points + 1] = p - end - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - points[#points + 1] = vertex - end - end - - if ShapeIntersector.triangleContainsPoint(triangle, circle.center) then - points[#points + 1] = circle.center:clone() - end - - local unique_points = ShapeIntersector.getUniquePoints(points) - - if #unique_points == 0 then - return false, nil - end - return true, unique_points - end - - ---只检查三角形与圆是否相交 - ---@param triangle foundation.shape.Triangle 三角形 - ---@param circle foundation.shape.Circle 圆 - ---@return boolean - function ShapeIntersector.triangleHasIntersectionWithCircle(triangle, circle) - if ShapeIntersector.triangleContainsPoint(triangle, circle.center) then - return true - end - - local edges = triangle:getEdges() - for _, edge in ipairs(edges) do - if ShapeIntersector.circleHasIntersectionWithSegment(circle, edge) then - return true - end - end - - local vertices = triangle:getVertices() - for _, vertex in ipairs(vertices) do - if circle:contains(vertex) then - return true - end - end - - return false - end - - return ShapeIntersector -end \ No newline at end of file diff --git a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua b/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua deleted file mode 100644 index 89be3f5b..00000000 --- a/game/packages/thlib-scripts-v2/foundation/shape/Triangle.lua +++ /dev/null @@ -1,444 +0,0 @@ -local ffi = require("ffi") - -local type = type -local ipairs = ipairs -local tostring = tostring -local string = string -local math = math -local rawset = rawset -local setmetatable = setmetatable - -local Vector2 = require("foundation.math.Vector2") -local Segment = require("foundation.shape.Segment") -local ShapeIntersector = require("foundation.shape.ShapeIntersector") - -ffi.cdef [[ -typedef struct { - foundation_math_Vector2 point1, point2, point3; -} foundation_shape_Triangle; -]] - ----@class foundation.shape.Triangle ----@field point1 foundation.math.Vector2 三角形的第一个顶点 ----@field point2 foundation.math.Vector2 三角形的第二个顶点 ----@field point3 foundation.math.Vector2 三角形的第三个顶点 -local Triangle = {} -Triangle.__type = "foundation.shape.Triangle" - ----@param self foundation.shape.Triangle ----@param key string ----@return any -function Triangle.__index(self, key) - if key == "point1" then - return self.__data.point1 - elseif key == "point2" then - return self.__data.point2 - elseif key == "point3" then - return self.__data.point3 - end - return Triangle[key] -end - ----@param self foundation.shape.Triangle ----@param key string ----@param value any -function Triangle.__newindex(self, key, value) - if key == "point1" then - self.__data.point1 = value - elseif key == "point2" then - self.__data.point2 = value - elseif key == "point3" then - self.__data.point3 = value - else - rawset(self, key, value) - end -end - ----创建一个新的三角形 ----@param v1 foundation.math.Vector2 三角形的第一个顶点 ----@param v2 foundation.math.Vector2 三角形的第二个顶点 ----@param v3 foundation.math.Vector2 三角形的第三个顶点 ----@return foundation.shape.Triangle 新创建的三角形 -function Triangle.create(v1, v2, v3) - local triangle = ffi.new("foundation_shape_Triangle", v1, v2, v3) - local result = { - __data = triangle, - } - ---@diagnostic disable-next-line: return-type-mismatch, missing-return-value - return setmetatable(result, Triangle) -end - ----三角形相等比较 ----@param a foundation.shape.Triangle 第一个三角形 ----@param b foundation.shape.Triangle 第二个三角形 ----@return boolean 如果两个三角形的所有顶点都相等则返回true,否则返回false -function Triangle.__eq(a, b) - return a.point1 == b.point1 and a.point2 == b.point2 and a.point3 == b.point3 -end - ----三角形转字符串表示 ----@param t foundation.shape.Triangle 要转换的三角形 ----@return string 三角形的字符串表示 -function Triangle.__tostring(t) - return string.format("Triangle(%s, %s, %s)", tostring(t.point1), tostring(t.point2), tostring(t.point3)) -end - ----计算三角形的面积 ----@return number 三角形的面积 -function Triangle:area() - local v2v1 = self.point2 - self.point1 - local v3v1 = self.point3 - self.point1 - return 0.5 * math.abs(v2v1:cross(v3v1)) -end - ----计算三角形的重心 ----@return foundation.math.Vector2 三角形的重心 -function Triangle:centroid() - return (self.point1 + self.point2 + self.point3) / 3 -end - ----获取三角形的AABB包围盒 ----@return number, number, number, number -function Triangle:AABB() - local minX = math.min(self.point1.x, self.point2.x, self.point3.x) - local maxX = math.max(self.point1.x, self.point2.x, self.point3.x) - local minY = math.min(self.point1.y, self.point2.y, self.point3.y) - local maxY = math.max(self.point1.y, self.point2.y, self.point3.y) - return minX, maxX, minY, maxY -end - ----计算三角形的中心 ----@return foundation.math.Vector2 三角形的中心 -function Triangle:getCenter() - local minX, maxX, minY, maxY = self:AABB() - return Vector2.create((minX + maxX) / 2, (minY + maxY) / 2) -end - ----计算三角形的包围盒宽高 ----@return number, number -function Triangle:getBoundingBoxSize() - local minX, maxX, minY, maxY = self:AABB() - return maxX - minX, maxY - minY -end - ----计算三角形的外接圆半径 ----@return number 三角形的外接圆半径 -function Triangle:circumradius() - local center = self:circumcenter() - if center then - return (center - self.point1):length() - end - return 0 -end - ----计算三角形的内切圆半径 ----@return number 三角形的内切圆半径 -function Triangle:inradius() - local a = (self.point2 - self.point3):length() - local b = (self.point1 - self.point3):length() - local c = (self.point1 - self.point2):length() - return self:area() / ((a + b + c) / 2) -end - ----计算三角形的外心 ----@return foundation.math.Vector2 | nil 三角形的外心 -function Triangle:circumcenter() - local x1, y1 = self.point1.x, self.point1.y - local x2, y2 = self.point2.x, self.point2.y - local x3, y3 = self.point3.x, self.point3.y - - local D = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2) - if math.abs(D) <= 1e-10 then - return nil - end - - local x0_num = (x1 * x1 + y1 * y1) * (y2 - y3) + (x2 * x2 + y2 * y2) * (y3 - y1) + (x3 * x3 + y3 * y3) * (y1 - y2) - local x0 = x0_num / (2 * D) - - local y0_num = (x1 * x1 + y1 * y1) * (x3 - x2) + (x2 * x2 + y2 * y2) * (x1 - x3) + (x3 * x3 + y3 * y3) * (x2 - x1) - local y0 = y0_num / (2 * D) - - return Vector2.create(x0, y0) -end - ----计算三角形的内心 ----@return foundation.math.Vector2 三角形的内心 -function Triangle:incenter() - local a = (self.point2 - self.point3):length() - local b = (self.point1 - self.point3):length() - local c = (self.point1 - self.point2):length() - - local p = (a * self.point1 + b * self.point2 + c * self.point3) / (a + b + c) - return p -end - ----将当前三角形平移指定距离(更改当前三角形) ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Triangle 移动后的三角形(自身引用) -function Triangle:move(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - self.point1.x = self.point1.x + moveX - self.point1.y = self.point1.y + moveY - self.point2.x = self.point2.x + moveX - self.point2.y = self.point2.y + moveY - self.point3.x = self.point3.x + moveX - self.point3.y = self.point3.y + moveY - return self -end - ----获取三角形平移指定距离的副本 ----@param v foundation.math.Vector2 | number 移动距离 ----@return foundation.shape.Triangle 移动后的三角形副本 -function Triangle:moved(v) - local moveX, moveY - if type(v) == "number" then - moveX, moveY = v, v - else - moveX, moveY = v.x, v.y - end - return Triangle.create( - Vector2.create(self.point1.x + moveX, self.point1.y + moveY), - Vector2.create(self.point2.x + moveX, self.point2.y + moveY), - Vector2.create(self.point3.x + moveX, self.point3.y + moveY) - ) -end - ----将当前三角形旋转指定弧度(更改当前三角形) ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Triangle 旋转后的三角形(自身引用) ----@overload fun(self: foundation.shape.Triangle, rad: number): foundation.shape.Triangle 绕三角形重心旋转指定弧度 -function Triangle:rotate(rad, center) - center = center or self:centroid() - local cosAngle = math.cos(rad) - local sinAngle = math.sin(rad) - local v1 = self.point1 - center - local v2 = self.point2 - center - local v3 = self.point3 - center - self.point1.x = v1.x * cosAngle - v1.y * sinAngle + center.x - self.point1.y = v1.x * sinAngle + v1.y * cosAngle + center.y - self.point2.x = v2.x * cosAngle - v2.y * sinAngle + center.x - self.point2.y = v2.x * sinAngle + v2.y * cosAngle + center.y - self.point3.x = v3.x * cosAngle - v3.y * sinAngle + center.x - self.point3.y = v3.x * sinAngle + v3.y * cosAngle + center.y - return self -end - ----将当前三角形旋转指定角度(更改当前三角形) ----@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Triangle 旋转后的三角形(自身引用) ----@overload fun(self: foundation.shape.Triangle, angle: number): foundation.shape.Triangle 绕三角形重心旋转指定角度 -function Triangle:degreeRotate(angle, center) - angle = math.rad(angle) - return self:rotate(angle, center) -end - ----获取三角形的旋转指定弧度的副本 ----@param rad number 旋转弧度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Triangle 旋转后的三角形副本 ----@overload fun(self: foundation.shape.Triangle, rad: number): foundation.shape.Triangle 绕三角形重心旋转指定弧度 -function Triangle:rotated(rad, center) - center = center or self:centroid() - local cosAngle = math.cos(rad) - local sinAngle = math.sin(rad) - local v1 = self.point1 - center - local v2 = self.point2 - center - local v3 = self.point3 - center - return Triangle.create( - Vector2.create(v1.x * cosAngle - v1.y * sinAngle + center.x, v1.x * sinAngle + v1.y * cosAngle + center.y), - Vector2.create(v2.x * cosAngle - v2.y * sinAngle + center.x, v2.x * sinAngle + v2.y * cosAngle + center.y), - Vector2.create(v3.x * cosAngle - v3.y * sinAngle + center.x, v3.x * sinAngle + v3.y * cosAngle + center.y) - ) -end - ----获取三角形的旋转指定角度的副本 ----@param angle number 旋转角度 ----@param center foundation.math.Vector2 旋转中心 ----@return foundation.shape.Triangle 旋转后的三角形副本 ----@overload fun(self: foundation.shape.Triangle, angle: number): foundation.shape.Triangle 绕三角形重心旋转指定角度 -function Triangle:degreeRotated(angle, center) - angle = math.rad(angle) - return self:rotated(angle, center) -end - ----将当前三角形缩放指定倍数(更改当前三角形) ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2 缩放中心 ----@return foundation.shape.Triangle 缩放后的三角形(自身引用) ----@overload fun(self: foundation.shape.Triangle, scale: number): foundation.shape.Triangle 相对三角形重心缩放指定倍数 -function Triangle:scale(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self:centroid() - self.point1.x = (self.point1.x - center.x) * scaleX + center.x - self.point1.y = (self.point1.y - center.y) * scaleY + center.y - self.point2.x = (self.point2.x - center.x) * scaleX + center.x - self.point2.y = (self.point2.y - center.y) * scaleY + center.y - self.point3.x = (self.point3.x - center.x) * scaleX + center.x - self.point3.y = (self.point3.y - center.y) * scaleY + center.y - return self -end - ----获取三角形的缩放指定倍数的副本 ----@param scale number|foundation.math.Vector2 缩放倍数 ----@param center foundation.math.Vector2 缩放中心 ----@return foundation.shape.Triangle 缩放后的三角形副本 ----@overload fun(self: foundation.shape.Triangle, scale: number): foundation.shape.Triangle 相对三角形重心缩放指定倍数 -function Triangle:scaled(scale, center) - local scaleX, scaleY - if type(scale) == "number" then - scaleX, scaleY = scale, scale - else - scaleX, scaleY = scale.x, scale.y - end - center = center or self:centroid() - return Triangle.create( - Vector2.create((self.point1.x - center.x) * scaleX + center.x, (self.point1.y - center.y) * scaleY + center.y), - Vector2.create((self.point2.x - center.x) * scaleX + center.x, (self.point2.y - center.y) * scaleY + center.y), - Vector2.create((self.point3.x - center.x) * scaleX + center.x, (self.point3.y - center.y) * scaleY + center.y) - ) -end - -function Triangle:contains(point) - return ShapeIntersector.triangleContainsPoint(self, point) -end - ----检查三角形是否与其他形状相交 ----@param other any 其他的形状 ----@return boolean, foundation.math.Vector2[] | nil -function Triangle:intersects(other) - return ShapeIntersector.intersect(self, other) -end - ----仅检查三角形是否与其他形状相交,不返回相交点 ----@param other any 其他的形状 ----@return boolean -function Triangle:hasIntersection(other) - return ShapeIntersector.hasIntersection(self, other) -end - ----获取三角形的顶点 ----@return foundation.math.Vector2[] -function Triangle:getVertices() - return { - self.point1:clone(), - self.point2:clone(), - self.point3:clone() - } -end - ----获取三角形的边(线段) ----@return foundation.shape.Segment[] -function Triangle:getEdges() - return { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } -end - ----计算三角形的周长 ----@return number 三角形的周长 -function Triangle:getPerimeter() - local a = (self.point2 - self.point3):length() - local b = (self.point1 - self.point3):length() - local c = (self.point1 - self.point2):length() - return a + b + c -end - ----计算点到三角形的最近点 ----@param point foundation.math.Vector2 要检查的点 ----@param boundary boolean 是否限制在边界内,默认为false ----@return foundation.math.Vector2 三角形上最近的点 ----@overload fun(self: foundation.shape.Triangle, point: foundation.math.Vector2): foundation.math.Vector2 -function Triangle:closestPoint(point, boundary) - if not boundary and self:contains(point) then - return point:clone() - end - - local edges = self:getEdges() - local minDistance = math.huge - local closestPoint - - for _, edge in ipairs(edges) do - local edgeClosest = edge:closestPoint(point) - local distance = (point - edgeClosest):length() - - if distance < minDistance then - minDistance = distance - closestPoint = edgeClosest - end - end - - return closestPoint -end - ----计算点到三角形的距离 ----@param point foundation.math.Vector2 要检查的点 ----@return number 点到三角形的距离 -function Triangle:distanceToPoint(point) - if self:contains(point) then - return 0 - end - - local edges = { - Segment.create(self.point1, self.point2), - Segment.create(self.point2, self.point3), - Segment.create(self.point3, self.point1) - } - - local minDistance = math.huge - - for i = 1, #edges do - local distance = edges[i]:distanceToPoint(point) - if distance < minDistance then - minDistance = distance - end - end - - return minDistance -end - ----将点投影到三角形平面上(2D中与closest相同) ----@param point foundation.math.Vector2 要投影的点 ----@return foundation.math.Vector2 投影点 -function Triangle:projectPoint(point) - return self:closestPoint(point, true) -end - ----检查点是否在三角形上 ----@param point foundation.math.Vector2 要检查的点 ----@param tolerance number|nil 容差,默认为1e-10 ----@return boolean 点是否在三角形上 ----@overload fun(self:foundation.shape.Triangle, point:foundation.math.Vector2): boolean -function Triangle:containsPoint(point, tolerance) - tolerance = tolerance or 1e-10 - local edges = self:getEdges() - for _, edge in ipairs(edges) do - if edge:containsPoint(point, tolerance) then - return true - end - end - return false -end - ----复制三角形 ----@return foundation.shape.Triangle 三角形的副本 -function Triangle:clone() - return Triangle.create(self.point1:clone(), self.point2:clone(), self.point3:clone()) -end - -ffi.metatype("foundation_shape_Triangle", Triangle) - -return Triangle diff --git a/laboratory/geometry/main.lua b/laboratory/geometry/main.lua index 02321336..a7557124 100644 --- a/laboratory/geometry/main.lua +++ b/laboratory/geometry/main.lua @@ -1,6 +1,6 @@ local lstg = require("lstg") lstg.FileManager.AddSearchPath("../../game/packages/thlib-scripts/") -lstg.FileManager.AddSearchPath("../../game/packages/thlib-scripts-v2/") +lstg.FileManager.AddSearchPath("../../game/packages/lua-ffi-math/") lstg.FileManager.AddSearchPath("../../game/packages/thlib-resources/") local Keyboard = lstg.Input.Keyboard From 95da691dba1c0c440df5321b171854b0c719af77 Mon Sep 17 00:00:00 2001 From: OLC Date: Wed, 21 May 2025 01:18:17 +0800 Subject: [PATCH 70/71] update --- game/packages/lua-ffi-math | 2 +- laboratory/geometry/main.lua | 48 ++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/game/packages/lua-ffi-math b/game/packages/lua-ffi-math index d5899590..f8db6164 160000 --- a/game/packages/lua-ffi-math +++ b/game/packages/lua-ffi-math @@ -1 +1 @@ -Subproject commit d5899590e488fa371c0d1a84395a6b2b2d0deb69 +Subproject commit f8db61646c0fee344cc4eaed0d35af131be27b6a diff --git a/laboratory/geometry/main.lua b/laboratory/geometry/main.lua index a7557124..643b6c2c 100644 --- a/laboratory/geometry/main.lua +++ b/laboratory/geometry/main.lua @@ -950,6 +950,53 @@ end function Scene10:draw() object:draw() end + +local Vector3 = require("foundation.math.Vector3") +local Triangle3D = require("foundation.shape3D.Triangle3D") + +local Scene11 = {} +Scene11.name = "3D Triangle" +function Scene11:create() + self.timer = -1 + + self.triangle = Triangle3D.create( + Vector3.create(-100, -100, 100), + Vector3.create(100, -100, 100), + Vector3.create(0, 100, 100) + ):move(Vector3.create(window.width / 2, window.height / 2, 0)) + + self.rotateAxis = Vector3.create(1, 1, 1):normalize() + self.rotateAngle = 1 + self.rotateCenter = Vector3.create(window.width / 2, window.height / 2, 0) +end + +function Scene11:destroy() + object:clear() +end + +function Scene11:update() + self.timer = self.timer + 1 + self.triangleRotated = self.triangle:degreeRotated(self.rotateAxis, self.rotateAngle * self.timer, self.rotateCenter) +end + +function Scene11:draw() + local vertices = self.triangleRotated:getVertices() + + setColor(192, 255, 255, 255) + for i = 1, #vertices do + local p1 = vertices[i] + local p2 = vertices[i % #vertices + 1] + renderLine(p1, p2, 2) + end + if self.render_vertex then + setColor(127, 255, 0, 0) + for _, vertex in ipairs(vertices) do + renderPoint(vertex, 4) + end + setColor(127, 255, 0, 0) + renderPoint(self.triangle:centroid(), 4) + end +end --endregion ---@generic T @@ -962,6 +1009,7 @@ local function makeInstance(class) end local scenes = { + Scene11, Scene1, Scene2, Scene3, From d795063c688f321eb6d7d76b697b2501fb00b2b3 Mon Sep 17 00:00:00 2001 From: OLC Date: Thu, 22 May 2025 00:37:32 +0800 Subject: [PATCH 71/71] update module --- game/packages/lua-ffi-math | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/packages/lua-ffi-math b/game/packages/lua-ffi-math index f8db6164..adaa76fa 160000 --- a/game/packages/lua-ffi-math +++ b/game/packages/lua-ffi-math @@ -1 +1 @@ -Subproject commit f8db61646c0fee344cc4eaed0d35af131be27b6a +Subproject commit adaa76faedd2946c390f3e33fecf42dfa7f8a485