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..adaa76fa --- /dev/null +++ b/game/packages/lua-ffi-math @@ -0,0 +1 @@ +Subproject commit adaa76faedd2946c390f3e33fecf42dfa7f8a485 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..ec591b80 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/struct/array/BooleanArray.lua @@ -0,0 +1,240 @@ +local ffi = require("ffi") + +local type = type +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 新创建的布尔数组 +---@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 +end + +---索引元方法,获取数组元素 +---@param self foundation.struct.array.BooleanArray +---@param key number|string 索引或方法名 +---@return boolean|function 数组元素值或方法 +function BooleanArray.__index(self, key) + if type(key) == "number" 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[c_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 + 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[c_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 + +---获取数组索引的偏移量 +---@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 value boolean 填充值 +function BooleanArray:fill(value) + for i = 0, self.size - 1 do + self.data[i] = value + end +end + +---获取数组的迭代器 +---@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 + +---克隆数组 +---@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..22e98c5d --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/struct/array/DoubleArray.lua @@ -0,0 +1,299 @@ +local ffi = require("ffi") + +local type = type +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 新创建的双精度浮点数数组 +---@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 +end + +---索引元方法,获取数组元素 +---@param self foundation.struct.array.DoubleArray +---@param key number|string 索引或方法名 +---@return number|function 数组元素值或方法 +function DoubleArray.__index(self, key) + if type(key) == "number" 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[c_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 + 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[c_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 + +---获取数组索引的偏移量 +---@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 value number 填充值 +function DoubleArray:fill(value) + for i = 0, self.size - 1 do + self.data[i] = value + end +end + +---获取数组的迭代器 +---@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 + +---克隆数组 +---@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..05dd05b1 --- /dev/null +++ b/game/packages/thlib-scripts-v2/foundation/struct/array/IntArray.lua @@ -0,0 +1,299 @@ +local ffi = require("ffi") + +local type = type +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 新创建的整型数组 +---@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 +end + +---索引元方法,获取数组元素 +---@param self foundation.struct.array.IntArray +---@param key number|string 索引或方法名 +---@return number|function 数组元素值或方法 +function IntArray.__index(self, key) + if type(key) == "number" 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[c_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 + 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[c_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 + +---获取数组索引的偏移量 +---@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 value number 填充值 +function IntArray:fill(value) + for i = 0, self.size - 1 do + self.data[i] = value + end +end + +---获取数组的迭代器 +---@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 + +---克隆数组 +---@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 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..643b6c2c --- /dev/null +++ b/laboratory/geometry/main.lua @@ -0,0 +1,1119 @@ +local lstg = require("lstg") +lstg.FileManager.AddSearchPath("../../game/packages/thlib-scripts/") +lstg.FileManager.AddSearchPath("../../game/packages/lua-ffi-math/") +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 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 +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() + 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) +end +--endregion + +--region Object Method +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) +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() + 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 + 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) + 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) + end + if self.render_closest_point then + self:renderClosestPoint(obj, player_pos) + end + end + if self.render_collision_result then + self:renderCollisionResult() + end +end + +---@param player_pos {x:number, y:number} +function object:renderPlayerPoint(player_pos) + setColor(255, 255, 255, 255) + 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) + 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 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 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) + 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) + 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 +---@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) + 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 +---@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) + 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) + 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 + 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) + 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 +---@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() + 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 + 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) + 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 +---@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 + 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 +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) + 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 +---@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) + 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 + +---@param bezierCurve foundation.shape.BezierCurve +---@param player_pos {x:number, y:number} +function object:renderBezierCurve(bezierCurve, player_pos) + if bezierCurve:containsPoint(player_pos, 1, 30) 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 + 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 + +---@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 +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 = "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({ + 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 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 + +local Scene8 = {} +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()) + --每种形状都随机创建一个 + 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 Scene10:destroy() + object:clear() +end +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 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 +---@param class T +---@return T +local function makeInstance(class) + local instance = {} + setmetatable(instance, { __index = class }) + return instance +end + +local scenes = { + Scene11, + Scene1, + Scene2, + Scene3, + Scene4, + Scene5, + Scene6, + Scene7, + Scene8, + Scene9, + Scene10, +} +local current_scene_index = 1 +local current_scene = makeInstance(scenes[current_scene_index]) +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() + lstg.LoadTTF("Sans", "assets/font/SourceHanSansCN-Bold.otf", 0, 24) + current_scene:create() +end + +function GameExit() + current_scene:destroy() +end + +function FrameFunc() + UpdateKeyState() + local change = 0 + if KeyIsPressed(Keyboard.Left) then + if current_scene_index > 1 then + change = -1 + end + end + if KeyIsPressed(Keyboard.Right) then + if current_scene_index < #scenes then + change = 1 + end + 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() + 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 00000000..4ae1d8e5 Binary files /dev/null and b/laboratory/geometry/white.png differ