|
|||||
|
-- user variables
local mesh = "my_mesh_collection\\test.nif"
-- internal variables
local path = lfs.currentdir() .. "\\data files\\meshes\\"
|
|
Live Mesh Editing script:
main.lua
|
|
--[[
Live Mesh Editing.
Usage:
update the path variable to point your file
go in-game and press CTRL+X to spawn your object
the game object will refresh when file is edited
--]]
-- file path of the target mesh
local relpath = "g7\\export.nif"
-- absolute path (in lowercase)
local abspath = string.lower(tes3.installDirectory .. "\\data files\\meshes\\" .. relpath)
local filewatcher = require("filewatcher")
filewatcher.registerWatcher(abspath)
local function update()
timer.delayOneFrame(function()
timer.delayOneFrame(function()
local ref = tes3.getReference(relpath)
if not ref then return end
local src = tes3.loadMesh(relpath, false)
local dst = ref.sceneNode
if not (src and dst) then return end
-- transfer properties
dst.fogProperty = src.fogProperty
dst.alphaProperty = src.alphaProperty
dst.stencilProperty = src.stencilProperty
dst.zBufferProperty = src.zBufferProperty
dst.materialProperty = src.materialProperty
dst.texturingProperty = src.texturingProperty
dst.vertexColorProperty = src.vertexColorProperty
-- transfer children
dst:detachAllChildren()
for i, child in ipairs(src.children) do
if child then
dst:attachChild(child, true)
end
end
dst:update()
dst:updateEffects()
dst:updateProperties()
tes3.messageBox("MESH RELOADED")
end)
end)
end
event.register("fileWatcher:fileModified", update, { filter = abspath })
local function onKeyDown(e)
if not e.isControlDown then return end
local ref = tes3.player.tempData.liveMeshEditingRef
if ref then
return
end
local rayhit = tes3.rayTest{
position = tes3.getPlayerEyePosition(),
direction = tes3.getPlayerEyeVector(),
ignore = {tes3.player},
}
if not rayhit then return end
local cell = tes3.player.cell
cell_modified = cell.modified
-- create the reference
ref = tes3.createReference({
cell = cell,
position = rayhit.intersection,
object = tes3.createObject({
objectType = tes3.objectType.activator,
id = relpath,
name = relpath,
mesh = relpath,
getIfExists = true,
}),
})
tes3.player.tempData.liveMeshEditingRef = ref
-- avoid zfighting with ground
local z = math.max(math.abs(ref.object.boundingBox.min.z), 1.0)
ref.position = ref.position + tes3vector3.new(0.0, 0.0, z)
ref.object.sourceless = true
ref.hasNoCollision = true
ref.modified = false
tes3.player.cell.modified = cell_modified
end
event.register("keyDown", onKeyDown, {filter=tes3.scanCode.x})
|
|
filewatcher.lua
|
|
--[[
filewatcher.lua
Example:
local filewatcher = require("filewatcher")
local file = string.lower(lfs.currentdir() .. "\\test.txt")
filewatcher.registerWatcher(file)
local function onFileModified(e)
tes3ui.log(io.open(e.path, "rb"):read("*a"))
end
event.register("fileWatcher:fileModified", onFileModified, { filter = file })
--]]
local this = {}
--- @alias Path string
--- @alias TimeStamp number
--- @alias NotificationHandle number
--- @class RegistryEntry
--- @field handle NotificationHandle
--- @field mtimes table<Path, TimeStamp>
--- @type table<Path, RegistryEntry>
this.registry = {}
local ffi = require("ffi")
ffi.cdef[[
typedef int BOOL;
typedef unsigned long DWORD;
typedef void HANDLE;
DWORD GetLastError();
HANDLE* FindFirstChangeNotificationA(const char* path, BOOL recursive, DWORD filter);
BOOL FindNextChangeNotification(HANDLE*);
BOOL FindCloseChangeNotification(HANDLE*);
DWORD WaitForSingleObject(HANDLE*, DWORD);
]]
--- @param dir Path
--- @return NotificationHandle
local function openNotificationHandle(dir)
return ffi.C.FindFirstChangeNotificationA(dir, false, 0x11F)
end
--- @param handle NotificationHandle
--- @return boolean
local function closeNotificatioNHandle(handle)
return ffi.C.FindCloseChangeNotification(handle)
end
--- @param handle NotificationHandle
--- @return boolean
local function getDirectoryModified(handle)
if ffi.C.WaitForSingleObject(handle, 0) == 0 then
ffi.C.FindNextChangeNotification(handle)
return true
end
return false
end
local function dispatchEvents()
for _, entry in pairs(this.registry) do
-- use windows api to check if any directory contents have been modified
if getDirectoryModified(entry.handle) then
-- the directory was modified, now figure out which particuler files
for path, prevTime in pairs(entry.mtimes) do
local currTime = lfs.attributes(path, "modification")
if currTime ~= prevTime then
entry.mtimes[path] = currTime
--- @diagnostic disable-next-line: undefined-field
timer.frame.delayOneFrame(function()
event.trigger("fileWatcher:fileModified", { path = path }, { filter = path })
end)
end
end
end
end
end
--- @param abspath Path
--- @return Path
function this.registerWatcher(abspath)
abspath = abspath:lower()
local dir = abspath:match("(.-)([^\\]+)$")
local mtime = lfs.attributes(abspath, "modification")
if this.registry[dir] then
this.registry[dir].mtimes[abspath] = mtime
else
this.registry[dir] = {
handle = openNotificationHandle(dir),
mtimes = { [abspath] = mtime }
}
end
if not event.isRegistered("simulate", dispatchEvents) then
event.register("simulate", dispatchEvents)
end
return abspath
end
--- @param abspath Path
--- @return Path
function this.unregisterWatcher(abspath)
abspath = abspath:lower()
-- make sure no other file is using hte same directory before clearing it
local dir = abspath:match("(.-)[^\\]+$")
local entry = this.registry[dir]
if entry then
entry.mtimes[abspath] = nil
-- close the handle if no other files in this directory are being watched
if table.empty(entry.mtimes) then
closeNotificatioNHandle(entry.handle)
this.registry[dir] = nil
end
if table.empty(this.registry) then
if event.isRegistered("simulate", dispatchEvents) then
event.unregister("simulate", dispatchEvents)
end
end
end
return abspath
end
return this
|