Compare commits

...

15 Commits

12 changed files with 1478 additions and 38 deletions

BIN
data/images/measure002a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

38
data/models/unit_cube.obj Normal file
View File

@ -0,0 +1,38 @@
# Blender 4.0.2
# www.blender.org
o Cube
v -0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 0.500000 -0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.500000 -0.500000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vt 0.375000 0.000000
vt 0.625000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.625000 0.500000
vt 0.375000 0.500000
vt 0.625000 0.750000
vt 0.375000 0.750000
vt 0.625000 1.000000
vt 0.375000 1.000000
vt 0.125000 0.500000
vt 0.125000 0.750000
vt 0.875000 0.500000
vt 0.875000 0.750000
s 0
f 1/1/1 2/2/1 4/3/1 3/4/1
f 3/4/2 4/3/2 8/5/2 7/6/2
f 7/6/3 8/5/3 6/7/3 5/8/3
f 5/8/4 6/7/4 2/9/4 1/10/4
f 3/11/5 7/6/5 5/8/5 1/12/5
f 8/5/6 4/13/6 2/14/6 6/7/6

BIN
data/music/bg1.xm Normal file

Binary file not shown.

View File

@ -99,7 +99,7 @@ end
function Duck:start_chase(feed)
if self.state == States.CHASE then return end
print("duck " .. self.index .. " starting chase")
-- print("duck " .. self.index .. " starting chase")
self.state = States.CHASE
self.target = feed
feed.occupied = true

View File

@ -0,0 +1,133 @@
---@class Obj
---@field model string
---@field texture string
---@field texture_size integer
---@field position Vector3
---@field rotation Vector3
---@field triangles table
local Obj = {}
local Vector3 = require "types.vector3"
Obj.__index = Obj
local function string_split(str, delimiter)
local res = {}
local i = 0
local f
local match = '(.-)' .. delimiter .. '()'
if string.find(str, delimiter) == nil then
return {str}
end
for sub, j in string.gmatch(str, match) do
i = i + 1
res[i] = sub
f = j
end
if i ~= 0 then
res[i+1] = string.sub(str, f)
end
return res
end
---parses the obj file and triangulates it for drawing
---@param model string path to obj model
---@param texture {file: string, size: number}
---@param position Vector3?
---@return Obj
function Obj.create(model, texture, position)
local pos
if position ~= nil then
pos = position
else
pos = Vector3()
end
local obj = {
model = model,
texture = texture.file,
texture_size = texture.size,
position = pos:copy(),
rotation = Vector3(),
triangles = {}
}
obj.position = obj.position:copy()
setmetatable(obj, Obj)
-- adapted from https://github.com/karai17/lua-obj/blob/master/obj_loader.lua
-- Copyright (c) 2014 Landon Manning - LManning17@gmail.com - LandonManning.com
-- MIT
local file = file_read{file = obj.model}
local lines = string_split(file, "\r?\n")
local obj_data = {
v = {},
vt = {},
f = {},
}
for _, line in ipairs(lines) do
local l = string_split(line, "%s+")
if l[1] == "v" then
local v = {
x = tonumber(l[2]),
y = tonumber(l[3]),
z = tonumber(l[4]),
}
table.insert(obj_data.v, v)
elseif l[1] == "vt" then
local vt = {
x = tonumber(l[2]) * obj.texture_size,
y = tonumber(l[3]) * obj.texture_size,
}
table.insert(obj_data.vt, vt)
elseif l[1] == "f" then
local f = {}
for i = 2, #l do
local split = string_split(l[i], "/")
local v = {}
v.v = tonumber(split[1])
if split[2] ~= "" then v.vt = tonumber(split[2]) end
-- v.vn = tonumber(split[3])
table.insert(f, v)
end
table.insert(obj_data.f, f)
end
end
for _, face in ipairs(obj_data.f) do
for i = 2, #face - 1 do
local triangle = {
v0 = obj_data.v[face[1].v],
v1 = obj_data.v[face[i].v],
v2 = obj_data.v[face[i + 1].v],
uv0 = obj_data.vt[face[1].vt],
uv1 = obj_data.vt[face[i].vt],
uv2 = obj_data.vt[face[i + 1].vt],
texture = obj.texture
}
table.insert(obj.triangles, triangle)
end
end
return obj
end
local function rotate_vertex(obj, v)
local vec = Vector3(v)
--- YXZ (yaw pitch roll) minimizes gimbal lock
-- return vec:rotate(Vector3.UP, obj.rotation.y):rotate(Vector3.LEFT, obj.rotation.x):rotate(Vector3.FORWARD, obj.rotation.z)
return vec:rotate(Vector3.UP, obj.rotation.y):rotate(Vector3.LEFT, obj.rotation.x):rotate(Vector3.FORWARD, obj.rotation.z)
end
function Obj:draw()
for _, triangle in ipairs(self.triangles) do
local newt = util.shallow_copy(triangle)
newt.v0 = rotate_vertex(self, triangle.v0) + self.position
newt.v1 = rotate_vertex(self, triangle.v1) + self.position
newt.v2 = rotate_vertex(self, triangle.v2) + self.position
draw_triangle(newt)
end
end
return Obj

View File

@ -13,9 +13,20 @@ local Player = {
accel = 0.3,
world = nil,
ThrowPressed = Signal.new(),
}
local CAMERA_OFFSET = Vector3(0, -1, 0)
function Player:init(world)
local x,y,z = ((self.position - Vector3(0.25, 1.0, 0.25)) * UNIT_SIZE):decomposed()
local w,h,d = (Vector3(0.25, 1.1, 0.25) * UNIT_SIZE):decomposed()
world:add(self, x,y,z,w,h,d)
self.world = world
end
function Player:tick(ctx)
input_action{name = "left", control = "A"}
input_action{name = "right", control = "D"}
@ -34,17 +45,22 @@ function Player:tick(ctx)
local direction = ((camera_forward * forward_input) + (camera_right * strafe_input)):normalized()
local target_vel = direction * self.speed
self.velocity = self.velocity:lerp(target_vel, self.accel)
if input_action_just_pressed{name = "throw"} then
self.ThrowPressed:emit(self.position:copy(), camera_forward:copy())
end
self.velocity = self.velocity:lerp(target_vel, self.accel)
local goal = ((self.position + CAMERA_OFFSET) + self.velocity) * UNIT_SIZE
local actual_x, actual_y, actual_z, cols, len = self.world:move(self, goal.x, goal.y, goal.z)
-- for i = 1, len do
-- print(util.printt(cols[i].other))
-- end
self.position = Vector3(actual_x / UNIT_SIZE, actual_y / UNIT_SIZE, actual_z / UNIT_SIZE) - CAMERA_OFFSET
if ctx.mouse_capture then
self.yaw = self.yaw + self.mouse_sensitivity * ctx.mouse_movement.x
end
self.position = self.position + self.velocity
draw_text{font = "fonts/Lunchtype21_Regular.ttf", position = {x = 0, y = 0}, string = table.concat(table.pack(self.world:getCube(self)), ";"), height = 14}
draw_text{font = "fonts/Lunchtype21_Regular.ttf", position = {x = 0, y = 14}, string = tostring(self.position), height = 14}
end
return Player

View File

@ -1,25 +1,28 @@
util = require "util"
UNIT_SIZE = 1
local player = require "classes.player"
local Vector3 = require "types.vector3"
local List = require "types.list"
local bump = require "lib.bump-3dpd"
local AABB = require "types.aabb"
local world = bump.newWorld()
local Obj = require "classes.obj"
local cube = Obj.create("models/unit_cube.obj", {file = "images/measure002a.png", size = 512}, Vector3(0, 1, 1))
local Feed = require "classes.feed"
---@type List
local feed = List()
local Duck = require "classes.duck"
---@type List
local ducks = List()
local function create_feed(position, direction)
print("?")
local f = Feed.new(position, direction)
feed:push(f)
local eligible_ducks = ducks:filter(
function (duck)
return duck.state ~= duck.STATES.CHASE
end
)
if eligible_ducks:is_empty() then return end
eligible_ducks[1]:start_chase(f)
end
local function delete_feed(f)
@ -29,21 +32,32 @@ end
local function duck_seek_feed(duck)
local eligible_feeds = feed:filter(
function (f)
return feed.occupied == false
return f.occupied == false
end
)
if eligible_feeds:is_empty() then return end
duck:start_chase(eligible_feeds[1])
duck:start_chase(
eligible_feeds:sorted(
function(a, b)
return a.position:distance_squared_to(duck.position) < b.position:distance_squared_to(duck.position)
end
):pop_front()
)
end
local test_aabb = AABB.new(Vector3(0, 0, 2), Vector3(1, 1, 1))
-- called every frame, with constant delta time
function game_tick()
-- ctx.initialization_needed is true first frame and every time dynamic reload is performed
if ctx.initialization_needed then
-- ctx.udata persists on reload
ctx.udata = {
capture = false,
}
player:init(world)
local x,y,z = (Vector3(0, 0, 2) * UNIT_SIZE):decomposed()
local w,h,d = (Vector3(1, 1, 1) * UNIT_SIZE):decomposed()
world:add(test_aabb, x,y,z,w,h,d)
print(world:getCube(test_aabb))
print(world:getCube(player))
-- audio_play{audio = "music/bg1.xm", loops = true, channel = "music"}
player.ThrowPressed:connect(create_feed)
-- spawn some ducks
@ -54,9 +68,12 @@ function game_tick()
duck.SeekFeed:connect(duck_seek_feed)
duck.index = i
end
print(ducks[1].AteFeed._connections)
print(ducks[2].AteFeed._connections)
end
-- ctx.udata persists on reload
if ctx.udata == nil then
ctx.udata = {
capture = false,
}
end
ctx.mouse_capture = ctx.udata.capture
@ -80,5 +97,12 @@ function game_tick()
texture = "images/measure001a.png",
texture_region = { x = 0, y = 0, w = 512, h = 512 },
}
draw_quad(util.merge(q, params))
-- draw_quad(util.merge(q, params))
cube.position.y = math.sin(ctx.frame_number * 0.05)
-- cube.position.z = math.cos(ctx.frame_number * 0.01)
cube.rotation.x = cube.rotation.x + 0.01
cube.rotation.z = cube.rotation.z + 0.01
cube:draw()
test_aabb:draw()
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,85 @@
local Vector3 = require "types.vector3"
---@class AABB
local AABB = {
min = Vector3(),
size = Vector3(),
}
local RED = {
r = 255,
g = 0,
b = 0,
a = 255,
}
setmetatable(AABB, AABB)
AABB.__index = AABB
---@param position Vector3
---@param size Vector3
---@return AABB
function AABB.new(position, size)
position = position or Vector3()
size = size or Vector3(1, 1, 1)
local aabb = {
min = position,
size = size,
}
return setmetatable(aabb, AABB)
end
function AABB:get_max()
return self.min + self.size
end
function AABB:draw()
local max = self:get_max()
-- bottom rectangle
draw_line_3d{start = self.min, finish = Vector3(max.x, self.min.y, self.min.z)}
draw_line_3d{start = self.min, finish = Vector3(self.min.x, self.min.y, max.z)}
draw_line_3d{start = Vector3(max.x, self.min.y, max.z), finish = Vector3(self.min.x, self.min.y, max.z)}
draw_line_3d{start = Vector3(max.x, self.min.y, max.z), finish = Vector3(max.x, self.min.y, self.min.z)}
-- bottom rectangle diagonal
draw_line_3d{start = self.min, finish = Vector3(max.x, self.min.y, max.z), color = RED}
-- top rectangle
draw_line_3d{start = Vector3(self.min.x, max.y, self.min.z), finish = Vector3(max.x, max.y, self.min.z)}
draw_line_3d{start = Vector3(self.min.x, max.y, self.min.z), finish = Vector3(self.min.x, max.y, max.z)}
draw_line_3d{start = Vector3(max.x, max.y, max.z), finish = Vector3(self.min.x, max.y, max.z)}
draw_line_3d{start = Vector3(max.x, max.y, max.z), finish = Vector3(max.x, max.y, self.min.z)}
-- top rectangle diagonal
draw_line_3d{start = Vector3(max.x, max.y, max.z), finish = Vector3(self.min.x, max.y, self.min.z), color = RED}
-- hull
draw_line_3d{start = self.min, finish = Vector3(self.min.x, max.y, self.min.z)}
draw_line_3d{start = self.min, finish = Vector3(max.x, max.y, self.min.z), color = RED}
draw_line_3d{start = Vector3(max.x, self.min.y, self.min.z), finish = Vector3(max.x, max.y, self.min.z)}
draw_line_3d{start = Vector3(max.x, self.min.y, self.min.z), finish = Vector3(max.x, max.y, max.z), color = RED}
draw_line_3d{start = Vector3(max.x, self.min.y, max.z), finish = Vector3(max.x, max.y, max.z)}
draw_line_3d{start = Vector3(max.x, self.min.y, max.z), finish = Vector3(self.min.x, max.y, max.z), color = RED}
draw_line_3d{start = Vector3(self.min.x, self.min.y, max.z), finish = Vector3(self.min.x, max.y, max.z)}
draw_line_3d{start = Vector3(self.min.x, self.min.y, max.z), finish = Vector3(self.min.x, max.y, self.min.z), color = RED}
end
---returns true if the point is inside this aabb
---@param point Vector3
---@return boolean
function AABB:has_point(point)
local max = self:get_max()
return point > self.min and point < max
end
---returns true if `other` intersects this AABB
---@param other AABB
---@return boolean
function AABB:intersects(other)
local my_max = self:get_max()
local other_max = other:get_max()
return self.min <= other_max and my_max >= other.min
end
return AABB

View File

@ -68,16 +68,23 @@ function List:pop()
return table.remove(self, #self)
end
---Reduce.
---@param f function called with element, accumulator, index
---@param init any initial value of accumulator
---Removes the first element in the list and returns it.
---@return any
function List:pop_front()
return table.remove(self, 1)
end
---Reduce.
---@generic T
---@param f fun(element: any, accumulator: T, index: integer): T
---@param init T|nil initial value of accumulator
---@return T
function List:reduce(f, init)
return reduce(self, f, init)
end
---Returns a new List of all elements of this list that match the predicate function.
---@param predicate function called with element
---@param predicate fun(element: any): boolean
---@return List
function List:filter(predicate)
return filter(self, predicate)
@ -126,4 +133,33 @@ function List:is_empty()
return #self == 0
end
---Returns a (shallow) copy of this list.
---@return List
function List:copy()
return List.create(self)
end
---Returns a new copy of this list with the order of the elements shuffled around.
---@return List
function List:shuffled()
-- https://gist.github.com/Uradamus/10323382
local list = self:copy()
for i = #list, 2, -1 do
local j = math.random(i)
list[i], list[j] = list[j], list[i]
end
return list
end
---Returns a sorted copy of this list.
---@param f? fun(a: any, b: any): boolean
---@return List
function List:sorted(f)
local list = self:copy()
table.sort(list, f)
return list
end
return List

View File

@ -1,8 +1,5 @@
--- @class Vector3
--- @field x number
--- @field y number
--- @field z number
--- @alias vectorlike number[] | Vector3
--- @class Vector3
local Vector3 = {
x = 0,
y = 0,
@ -158,8 +155,39 @@ function Vector3:__tostring()
return "Vector3(" .. tostring(self.x) .. ", " .. tostring(self.y) .. ", " .. tostring(self.z) .. ")"
end
Vector3.__index = Vector3
-- note: the < and <= operators of this class are component-wise rather than lexicographic (that is, htey are useful for bounds checking but are not suitable for sorting.)
function Vector3:__lt(b)
local err, other = coerce(b, true)
if err then return nil end
return self.x < other.x and self.y < other.y and self.z < other.z
end
function Vector3:__le(b)
local err, other = coerce(b, true)
if err then return nil end
return self.x <= other.x and self.y <= other.y and self.z <= other.z
end
local NUMK = {"x", "y", "z"}
function Vector3:__index(key)
-- this allows constructs like `for i, v in ipairs(Vector3(3, 2, 1))` to iterate over components
if type(key) == "number" then
return rawget(self, NUMK[key])
end
return rawget(Vector3, key)
end
function Vector3:__newindex(key, value)
if NUMK[key] then
rawset(self, NUMK[key], value)
return
end
rawset(self, key, value)
end
--------API--------
function Vector3:length_squared()
@ -223,14 +251,48 @@ function Vector3:rotated(axis, angle)
return vaxis
end
---@diagnostic disable-next-line: undefined-field
vaxis = vaxis:normalized()
local cosa = math.cos(angle)
local sina = math.sin(angle)
-- __mul is only defined for the left operand (table), numbers don't get metatables.
-- as such, the ordering of operations here is specific
local v = (self * cosa) + (vaxis * ((1 - cosa) * self:dot(vaxis))) + (vaxis:cross(self) * sina)
return Vector3(v)
return (self * cosa) + (vaxis * ((1 - cosa) * self:dot(vaxis))) + (vaxis:cross(self) * sina)
end
---In-place version of rotated.
---@param axis Vector3
---@param angle number
---@return Vector3
function Vector3:rotate(axis, angle)
local cosa = math.cos(angle)
local sina = math.sin(angle)
local dot = self:dot(axis)
local cross = axis:cross(self)
self.x = self.x * cosa + axis.x * ((1 - cosa) * dot) + cross.x * sina
self.y = self.y * cosa + axis.y * ((1 - cosa) * dot) + cross.y * sina
self.z = self.z * cosa + axis.z * ((1 - cosa) * dot) + cross.z * sina
return self
end
---In place set.
---@param x number
---@param y number
---@param z number
---@return Vector3
function Vector3:set(x, y, z)
self.x, self.y, self.z = x, y, z
return self
end
---In place set.
---@param t vectorlike
---@return Vector3
function Vector3:sett(t)
self.x, self.y, self.z = t.x, t.y, t.z
return self
end
---Returns a copy of this vector.
@ -294,14 +356,29 @@ end
function Vector3:horizontal()
return Vector3(self.x, 0.0, self.z)
end
---Returns the components of the vector individually.
---@return number
---@return number
---@return number
function Vector3:decomposed()
return self.x, self.y, self.z
end
---- CONSTANTS
---@type Vector3
Vector3.UP = Vector3(0, 1, 0)
Vector3.DOWN = -Vector3.UP
---@type Vector3
Vector3.DOWN = Vector3(0, -1, 0)
---@type Vector3
Vector3.FORWARD = Vector3(0, 0, -1)
Vector3.BACK = -Vector3.FORWARD
---@type Vector3
Vector3.BACK = Vector3(0, 0, 1)
---@type Vector3
Vector3.RIGHT = Vector3(1, 0, 0)
Vector3.LEFT = -Vector3.RIGHT
---@type Vector3
Vector3.LEFT = Vector3(-1, 0, 0)
-------------------
return Vector3

21
data/scripts/v3test.lua Normal file
View File

@ -0,0 +1,21 @@
local Vector3 = require "types.vector3"
local function pf(x, y, z)
print(x, y, z)
end
local v1 = Vector3(2, 1, 1)
local v2 = Vector3(3, 4, 4)
pf(v1:decomposed())
-- print(v1)
-- print(v2)
-- v1[1] = 383838
-- v1.y = 8858
-- print(v1)
-- print(v1:normalized())
-- print(v2)
-- for i, v in ipairs(v2) do
-- print(i, v)
-- end