newdark: (ughhhh) binding physics states, ambient sounds, doors, triggers, songs (missing things like XXMIX_S), to-do: more I/O, fix trigger bounds being weird, optimize physics bodies because it eats half my frametime | added dir listing for VFS, added queueing pending sounds to seamlessly transition (streamed audio only), probably a lot of other things just to make the former features happen..............
This commit is contained in:
parent
bd6b41a7a7
commit
7bd018dae0
@ -356,8 +356,8 @@
|
||||
"max": 0.1 // 0.2
|
||||
},
|
||||
"debug draw": {
|
||||
"static": true,
|
||||
"dynamic": true,
|
||||
"static": false,
|
||||
"dynamic": false,
|
||||
"trigger": false,
|
||||
"contacts": false,
|
||||
"constraints": false,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"assets": ["./scripts/door.lua"],
|
||||
"assets": ["./scripts/valve/door.lua"],
|
||||
"behaviors": [
|
||||
"AudioEmitterBehavior"
|
||||
],
|
||||
|
||||
86
bin/data/entities/scripts/dark/ambient.lua
Normal file
86
bin/data/entities/scripts/dark/ambient.lua
Normal file
@ -0,0 +1,86 @@
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local darkMeta = metadata["dark"] or {}
|
||||
local soundMeta = darkMeta["sound"] or {}
|
||||
|
||||
local schemaName = soundMeta["schema"] or ""
|
||||
local flags = soundMeta["flags"] or 0
|
||||
local baseVolume = tonumber(soundMeta["volume"]) or 1.0
|
||||
local radius = tonumber(soundMeta["radius"]) or 0.0
|
||||
|
||||
local startOn = (math.floor(flags / 1) % 2) ~= 0
|
||||
local environmental = (math.floor(flags / 2) % 2) ~= 0
|
||||
local isMusic = (math.floor(flags / 16) % 2) ~= 0
|
||||
|
||||
local isPlaying = false
|
||||
|
||||
local function playSound()
|
||||
if isPlaying or schemaName == "" then return end
|
||||
local wavList = soundMeta["wavs"]
|
||||
local url = ""
|
||||
|
||||
if wavList and #wavList > 0 then
|
||||
local totalWeight = 0
|
||||
for _, wavData in ipairs(wavList) do
|
||||
totalWeight = totalWeight + (tonumber(wavData.weight) or 1)
|
||||
end
|
||||
|
||||
local roll = math.random() * totalWeight
|
||||
local current = 0
|
||||
for _, wavData in ipairs(wavList) do
|
||||
current = current + (tonumber(wavData.weight) or 1)
|
||||
if roll <= current then
|
||||
url = wavData.uri
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if url == "" then return end
|
||||
|
||||
isPlaying = true
|
||||
local resolvedUrl = string.resolveURI(url, metadata["system"]["root"])
|
||||
|
||||
local schemaVol = tonumber(soundMeta["schema_volume"]) or 0
|
||||
local overrideVol = tonumber(soundMeta["volume"]) or 0
|
||||
|
||||
local baseVolume = 1.0
|
||||
if overrideVol ~= 0 then baseVolume = math.pow(10, overrideVol / 2000.0) end
|
||||
local finalVolume = baseVolume * math.pow(10, schemaVol / 2000.0)
|
||||
|
||||
local payload = {
|
||||
filename = resolvedUrl,
|
||||
spatial = not environmental,
|
||||
streamed = soundMeta["stream"] == true,
|
||||
volume = finalVolume,
|
||||
unique = true,
|
||||
loop = not (soundMeta["play_once"] == true)
|
||||
}
|
||||
|
||||
if radius > 0 and not environmental then
|
||||
payload.maxDistance = radius
|
||||
payload.rolloffFactor = 1.0
|
||||
end
|
||||
|
||||
ent:callHook("sound:Emit.%UID%", payload)
|
||||
end
|
||||
|
||||
local function stopSound()
|
||||
if not isPlaying or schemaName == "" then return end
|
||||
isPlaying = false
|
||||
|
||||
ent:callHook("sound:Stop.%UID%", {
|
||||
})
|
||||
end
|
||||
|
||||
ent:addHook("dark:Message.%UID%", function(payload)
|
||||
local msg = payload.message
|
||||
|
||||
if msg == "TurnOn" then
|
||||
playSound()
|
||||
elseif msg == "TurnOff" then
|
||||
stopSound()
|
||||
end
|
||||
end)
|
||||
|
||||
if startOn then
|
||||
playSound()
|
||||
end
|
||||
209
bin/data/entities/scripts/dark/door.lua
Normal file
209
bin/data/entities/scripts/dark/door.lua
Normal file
@ -0,0 +1,209 @@
|
||||
local ent = ent
|
||||
local scene = entities.currentScene()
|
||||
|
||||
local transform = ent:getComponent("Transform")
|
||||
local physicsBody = ent:getComponent("PhysicsBody")
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local darkMeta = metadata["dark"] or {}
|
||||
local doorMeta = darkMeta["door"] or {}
|
||||
local schemaDb = doorMeta["schema_db"] or (darkMeta["schema_db"] or {})
|
||||
local classTags = doorMeta["class_tags"] or (darkMeta["class_tags"] or "")
|
||||
|
||||
local state = 0
|
||||
|
||||
local isRotating = doorMeta["is_rotating"] == true
|
||||
local closedPos = tonumber(doorMeta["closed"]) or 0.0
|
||||
local openPos = tonumber(doorMeta["open"]) or 90.0
|
||||
local speed = tonumber(doorMeta["speed"]) or 2.0
|
||||
local axisRaw = tonumber(doorMeta["axis"]) or 2
|
||||
|
||||
local localAxis = Vector3f(0, 1, 0)
|
||||
if axisRaw == 0 then
|
||||
localAxis = Vector3f(1, 0, 0)
|
||||
elseif axisRaw == 1 then
|
||||
localAxis = Vector3f(0, 0, 1)
|
||||
end
|
||||
|
||||
local currentWav = ""
|
||||
local currentSchemaName = ""
|
||||
|
||||
local moveAxis = transform.orientation:rotate( localAxis )
|
||||
|
||||
if isRotating then
|
||||
closedPos = math.rad(closedPos)
|
||||
openPos = math.rad(openPos)
|
||||
speed = math.rad(speed)
|
||||
else
|
||||
closedPos = closedPos * 0.7
|
||||
openPos = openPos * 0.7
|
||||
end
|
||||
|
||||
local targetDistance = math.abs(openPos - closedPos)
|
||||
local currentDistance = 0.0
|
||||
|
||||
local status = tonumber(doorMeta["status"]) or 0
|
||||
if status == 1 then
|
||||
state = 2
|
||||
currentDistance = targetDistance
|
||||
end
|
||||
|
||||
local polarity = (openPos > closedPos) and 1 or -1
|
||||
|
||||
local function stopCurrentSound()
|
||||
if currentWav ~= "" then
|
||||
ent:callHook("sound:Stop.%UID%", { filename = currentWav })
|
||||
currentWav = ""
|
||||
end
|
||||
end
|
||||
|
||||
local function playEnvSchema(newState)
|
||||
local bestMatch = nil
|
||||
local bestScore = -1
|
||||
|
||||
|
||||
local myDoorType = nil
|
||||
if classTags and classTags ~= "" then
|
||||
myDoorType = string.match(classTags, "DoorType%s+([^%s,]+)")
|
||||
end
|
||||
|
||||
|
||||
for _, schema in ipairs(schemaDb) do
|
||||
local sTags = schema.tags or ""
|
||||
|
||||
local isStateChange = string.find(sTags, "Event StateChange")
|
||||
local openStateVals = string.match(sTags, "OpenState%s+([^,]+)")
|
||||
local matchesState = openStateVals and string.find(openStateVals, newState)
|
||||
|
||||
if isStateChange and matchesState then
|
||||
local score = 0
|
||||
|
||||
if myDoorType then
|
||||
if string.find(sTags, myDoorType) then
|
||||
score = score + 10
|
||||
elseif string.find(sTags, "DoorType") then
|
||||
score = score - 10
|
||||
end
|
||||
else
|
||||
if string.find(sTags, "DoorType") then
|
||||
score = score - 5
|
||||
end
|
||||
end
|
||||
|
||||
if score > bestScore then
|
||||
bestScore = score
|
||||
bestMatch = schema
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if bestMatch then
|
||||
if (newState == "Open" or newState == "Closed") and bestMatch.name == currentSchemaName then
|
||||
return
|
||||
end
|
||||
|
||||
currentSchemaName = bestMatch.name
|
||||
|
||||
if bestMatch.wavs and #bestMatch.wavs > 0 then
|
||||
local pick = bestMatch.wavs[math.random(#bestMatch.wavs)]
|
||||
local resolvedUrl = string.resolveURI(pick, metadata["system"]["root"])
|
||||
|
||||
stopCurrentSound()
|
||||
currentWav = resolvedUrl
|
||||
|
||||
ent:callHook("sound:Emit.%UID%", {
|
||||
filename = resolvedUrl,
|
||||
spatial = true,
|
||||
volume = 1.0,
|
||||
maxDistance = 15.0,
|
||||
rolloffFactor = 1.0,
|
||||
unique = true
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function setDoorState(newState)
|
||||
if newState == "Open" and (state == 0 or state == 3) then
|
||||
state = 1
|
||||
playEnvSchema("Opening")
|
||||
elseif newState == "Close" and (state == 2 or state == 1) then
|
||||
state = 3
|
||||
playEnvSchema("Closing")
|
||||
end
|
||||
end
|
||||
-- tick
|
||||
ent:bind("tick", function(self)
|
||||
local deltaMove = 0
|
||||
local step = time.delta() * speed
|
||||
|
||||
if state == 1 then
|
||||
deltaMove = step
|
||||
currentDistance = currentDistance + step
|
||||
|
||||
if currentDistance >= targetDistance then
|
||||
deltaMove = deltaMove - (currentDistance - targetDistance)
|
||||
currentDistance = targetDistance
|
||||
state = 2
|
||||
|
||||
-- Stop the "Opening" sound
|
||||
ent:callHook("sound:Stop.%UID%", {})
|
||||
-- Play the "Open" (Latch) sound!
|
||||
playEnvSchema("Open")
|
||||
end
|
||||
|
||||
elseif state == 3 then
|
||||
deltaMove = -step
|
||||
currentDistance = currentDistance - step
|
||||
|
||||
if currentDistance <= 0 then
|
||||
deltaMove = deltaMove - currentDistance
|
||||
currentDistance = 0
|
||||
state = 0
|
||||
|
||||
-- Stop the "Closing" sound
|
||||
ent:callHook("sound:Stop.%UID%", {})
|
||||
-- Play the "Closed" (Latch) sound!
|
||||
playEnvSchema("Closed")
|
||||
end
|
||||
end
|
||||
|
||||
if deltaMove ~= 0 then
|
||||
local finalMove = deltaMove * polarity
|
||||
|
||||
if isRotating then
|
||||
local rot = Quaternion.axisAngle(localAxis, finalMove)
|
||||
if physicsBody:initialized() then
|
||||
physicsBody:applyRotation(rot)
|
||||
else
|
||||
transform:rotate(rot)
|
||||
end
|
||||
else
|
||||
local vec = moveAxis * finalMove
|
||||
if physicsBody:initialized() then
|
||||
physicsBody:setVelocity(moveAxis * (finalMove / time.delta()))
|
||||
else
|
||||
transform.position = transform.position + vec
|
||||
end
|
||||
end
|
||||
else
|
||||
if not isRotating and physicsBody:initialized() then
|
||||
physicsBody:setVelocity(Vector3f(0, 0, 0))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
ent:addHook("dark:Message.%UID%", function(payload)
|
||||
local msg = payload.message
|
||||
|
||||
if msg == "TurnOn" then
|
||||
setDoorState("Open")
|
||||
elseif msg == "TurnOff" then
|
||||
setDoorState("Close")
|
||||
elseif msg == "Toggle" then
|
||||
if state == 0 or state == 3 then
|
||||
setDoorState("Open")
|
||||
else
|
||||
setDoorState("Close")
|
||||
end
|
||||
end
|
||||
end)
|
||||
62
bin/data/entities/scripts/dark/io.lua
Normal file
62
bin/data/entities/scripts/dark/io.lua
Normal file
@ -0,0 +1,62 @@
|
||||
local ent = ent
|
||||
local scene = entities.currentScene()
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
|
||||
local FLAVOR_CONTROL_DEVICE = 1
|
||||
|
||||
_G.DarkTargets = _G.DarkTargets or {}
|
||||
local darkMeta = metadata["dark"] or {}
|
||||
if darkMeta["id"] then
|
||||
_G.DarkTargets[darkMeta["id"]] = ent:uid()
|
||||
end
|
||||
|
||||
ent:addHook("dark:Message.%UID%", function(payload)
|
||||
local msg = payload.message
|
||||
local caller = payload.caller
|
||||
|
||||
if msg == "TurnOn" then
|
||||
print("Dark I/O [".. ent:name() .. "]: Received TurnOn from " .. tostring(caller))
|
||||
elseif msg == "TurnOff" then
|
||||
print("Dark I/O [".. ent:name() .. "]: Received TurnOff from " .. tostring(caller))
|
||||
end
|
||||
end)
|
||||
|
||||
local connections = metadata["connections"]
|
||||
if not connections then return end
|
||||
|
||||
ent:addHook("dark:Broadcast.%UID%", function(payload)
|
||||
local msg = payload.message
|
||||
local validFlavors = payload.flavors or { "ControlDevice", "SwitchLink" }
|
||||
|
||||
for i = 1, #connections do
|
||||
local conn = connections[i]
|
||||
local isValid = false
|
||||
for _, flav in ipairs(validFlavors) do
|
||||
if conn.flavor == flav then isValid = true break end
|
||||
end
|
||||
|
||||
if isValid then
|
||||
local targetUID = _G.DarkTargets[conn.target_id]
|
||||
if targetUID then
|
||||
local targetEnt = entities.get(targetUID)
|
||||
if targetEnt and targetEnt:uid() then
|
||||
targetEnt:callHook("dark:Message." .. targetUID, {
|
||||
message = msg,
|
||||
caller = ent:uid()
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
ent:addHook("entity:Destroy.%UID%", function()
|
||||
if darkMeta["id"] and _G.DarkTargets[darkMeta["id"]] == ent:uid() then
|
||||
_G.DarkTargets[darkMeta["id"]] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
ent:addHook("entity:Use.%UID%", function(payload)
|
||||
if payload.user == ent:uid() then return end
|
||||
ent:callHook("dark:Broadcast.%UID%", { message = "TurnOn" })
|
||||
end)
|
||||
197
bin/data/entities/scripts/dark/music.lua
Normal file
197
bin/data/entities/scripts/dark/music.lua
Normal file
@ -0,0 +1,197 @@
|
||||
local scene = entities.currentScene()
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local activeSongId = metadata["dark"]["mission song"] or "song02"
|
||||
local songs = metadata["dark"]["songs"] or {}
|
||||
local songData = songs[activeSongId]
|
||||
|
||||
local bakedMarkers = metadata["dark"]["music markers"] or {}
|
||||
local musicMarkers = {}
|
||||
local activeMarkers = {}
|
||||
|
||||
local currentSectionIdx = 0
|
||||
local currentLoopCount = 0
|
||||
local initializedPlayer = false
|
||||
|
||||
if not songData then
|
||||
print("[Music Manager] ERROR: Active song '" .. tostring(activeSongId) .. "' not found!")
|
||||
return
|
||||
end
|
||||
|
||||
print("[Music Manager] Bound Mission Song: " .. activeSongId)
|
||||
|
||||
for _, markerData in ipairs(bakedMarkers) do
|
||||
table.insert(musicMarkers, {
|
||||
position = Vector3f(markerData.position[1], markerData.position[2], markerData.position[3]),
|
||||
radius = tonumber(markerData.radius) or 0,
|
||||
theme = string.lower(markerData.theme or "")
|
||||
})
|
||||
end
|
||||
|
||||
print("[Music Manager] Pre-caching song assets...")
|
||||
for _, section in ipairs(songData.sections or {}) do
|
||||
if section.samples then
|
||||
for _, sampleName in ipairs(section.samples) do
|
||||
local url = "SND://songs/" .. string.lower(sampleName)
|
||||
if not string.match(url, "%.wav$") then url = url .. ".wav" end
|
||||
ent:callHook("asset:QueueLoad.%UID%", { uri = url })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function getSampleUrl(section)
|
||||
if section.samples and #section.samples > 0 then
|
||||
local url = "SND://songs/" .. string.lower(section.samples[1])
|
||||
if not string.match(url, "%.wav$") then url = url .. ".wav" end
|
||||
return url
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function predictNextSection(sectionIdx, loopCount)
|
||||
local section = songData.sections[sectionIdx + 1]
|
||||
local maxLoops = section.loopCount or 0
|
||||
|
||||
if maxLoops > 0 and loopCount < maxLoops then
|
||||
return sectionIdx, loopCount + 1
|
||||
end
|
||||
|
||||
local eventFound = nil
|
||||
if section and section.events then
|
||||
for _, evt in ipairs(section.events) do
|
||||
if evt.event == "" then eventFound = evt; break end
|
||||
end
|
||||
end
|
||||
if not eventFound and songData.events then
|
||||
for _, evt in ipairs(songData.events) do
|
||||
if evt.event == "" then eventFound = evt; break end
|
||||
end
|
||||
end
|
||||
|
||||
if eventFound and eventFound.gotos and #eventFound.gotos > 0 then
|
||||
return eventFound.gotos[1].section, 0
|
||||
end
|
||||
|
||||
return nil, 0
|
||||
end
|
||||
|
||||
local function playSection(sectionIdx, isQueue)
|
||||
if not songData.sections or not songData.sections[sectionIdx + 1] then return end
|
||||
|
||||
local section = songData.sections[sectionIdx + 1]
|
||||
currentSectionIdx = sectionIdx
|
||||
|
||||
local url = getSampleUrl(section)
|
||||
if url then
|
||||
if isQueue then
|
||||
print(string.format("[Music Manager] Queuing section: %s", section.id))
|
||||
ent:callHook("sound:QueueTrack.%UID%", { filename = url, layer = 1 })
|
||||
else
|
||||
print(string.format("[Music Manager] Forcing immediate section: %s", section.id))
|
||||
ent:callHook("sound:PlayTrack.%UID%", { filename = url, layer = 1, loop = false })
|
||||
|
||||
-- Immediately predict and queue the next track to prevent gaps!
|
||||
local nextIdx, nextLoop = predictNextSection(currentSectionIdx, currentLoopCount)
|
||||
if nextIdx then
|
||||
local nextSec = songData.sections[nextIdx + 1]
|
||||
local nextUrl = getSampleUrl(nextSec)
|
||||
if nextUrl then
|
||||
print(string.format("[Music Manager] Pre-queuing next section: %s", nextSec.id))
|
||||
ent:callHook("sound:QueueTrack.%UID%", { filename = nextUrl, layer = 1 })
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
processEvent("")
|
||||
end
|
||||
end
|
||||
|
||||
local function processEvent(eventName)
|
||||
local altEventName = "theme " .. eventName
|
||||
local section = songData.sections[currentSectionIdx + 1]
|
||||
local eventFound = nil
|
||||
|
||||
if section and section.events then
|
||||
for _, evt in ipairs(section.events) do
|
||||
if evt.event == eventName or evt.event == altEventName then
|
||||
eventFound = evt
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not eventFound and songData.events then
|
||||
for _, evt in ipairs(songData.events) do
|
||||
if evt.event == eventName or evt.event == altEventName then
|
||||
eventFound = evt
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if eventFound and eventFound.gotos and #eventFound.gotos > 0 then
|
||||
local randNum = math.random(0, 99)
|
||||
local totalProbability = 0
|
||||
local targetSection = eventFound.gotos[1].section
|
||||
|
||||
for _, gotoData in ipairs(eventFound.gotos) do
|
||||
totalProbability = totalProbability + (gotoData.probability or 100)
|
||||
if randNum < totalProbability then
|
||||
targetSection = gotoData.section
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
print(string.format("[Music Manager] Event '%s' triggered transition to section %d", eventFound.event, targetSection))
|
||||
|
||||
if eventName ~= "" then
|
||||
currentLoopCount = 0
|
||||
playSection(targetSection, false)
|
||||
else
|
||||
playSection(targetSection, true)
|
||||
end
|
||||
else
|
||||
print(string.format("[Music Manager] WARNING: Event '%s' ignored (not valid from section %d)", eventName, currentSectionIdx))
|
||||
end
|
||||
end
|
||||
|
||||
ent:addHook( "sound:TrackEnded.%UID%", function(self, payload)
|
||||
local section = songData.sections[currentSectionIdx + 1]
|
||||
local maxLoops = section.loopCount or 0
|
||||
|
||||
if maxLoops == 0 or currentLoopCount >= maxLoops then
|
||||
currentLoopCount = 0
|
||||
processEvent("")
|
||||
else
|
||||
currentLoopCount = currentLoopCount + 1
|
||||
playSection(currentSectionIdx, true)
|
||||
end
|
||||
end)
|
||||
|
||||
local function updateMusicState(playerPos, forceInitialization)
|
||||
for id, marker in ipairs(musicMarkers) do
|
||||
local dist = playerPos:distance(marker.position)
|
||||
local isInside = (dist <= marker.radius)
|
||||
|
||||
if isInside and (not activeMarkers[id] or forceInitialization) then
|
||||
activeMarkers[id] = true
|
||||
processEvent(marker.theme)
|
||||
elseif not isInside and activeMarkers[id] then
|
||||
activeMarkers[id] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ent:bind( "tick", function(self)
|
||||
local player = scene:findByName("Player")
|
||||
if not player then return end
|
||||
|
||||
local playerPos = player:getComponent("Transform").position
|
||||
|
||||
if not initializedPlayer then
|
||||
initializedPlayer = true
|
||||
processEvent("begin")
|
||||
updateMusicState(playerPos, true)
|
||||
else
|
||||
updateMusicState(playerPos, false)
|
||||
end
|
||||
end)
|
||||
26
bin/data/entities/scripts/dark/trap.lua
Normal file
26
bin/data/entities/scripts/dark/trap.lua
Normal file
@ -0,0 +1,26 @@
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local darkMeta = metadata["dark"] or {}
|
||||
local connections = darkMeta["connections"] or {}
|
||||
|
||||
ent:addHook("dark:Message.%UID%", function(payload)
|
||||
local msg = payload.message
|
||||
|
||||
if msg == "TurnOn" then
|
||||
for _, conn in ipairs(connections) do
|
||||
if conn.flavor == "SoundDescription" and conn.wavs and #conn.wavs > 0 then
|
||||
local pick = conn.wavs[math.random(#conn.wavs)]
|
||||
local resolvedUrl = string.resolveURI(pick, metadata["system"]["root"])
|
||||
|
||||
ent:callHook("sound:Emit.%UID%", {
|
||||
filename = resolvedUrl,
|
||||
spatial = true,
|
||||
streamed = false,
|
||||
volume = 1.0,
|
||||
unique = true,
|
||||
loop = false
|
||||
})
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
38
bin/data/entities/scripts/dark/trigger.lua
Normal file
38
bin/data/entities/scripts/dark/trigger.lua
Normal file
@ -0,0 +1,38 @@
|
||||
local ent = ent
|
||||
local scene = entities.currentScene()
|
||||
local physicsBody = ent:getComponent("PhysicsBody")
|
||||
|
||||
local touching = {}
|
||||
|
||||
ent:bind( "tick", function(self)
|
||||
if not physicsBody:initialized() then return end
|
||||
|
||||
local currentCollisions = {}
|
||||
local collisionEvents = physicsBody:getCollisionEvents()
|
||||
|
||||
for i, event in ipairs(collisionEvents) do
|
||||
local other = nil
|
||||
if event.a:getObject():uid() == ent:uid() then
|
||||
other = event.b
|
||||
elseif event.b:getObject():uid() == ent:uid() then
|
||||
other = event.a
|
||||
end
|
||||
|
||||
if other then
|
||||
local uid = other:getObject():uid()
|
||||
currentCollisions[uid] = true
|
||||
|
||||
if not touching[uid] then
|
||||
touching[uid] = true
|
||||
ent:queueHook("dark:Broadcast.%UID%", { message = "TurnOn" }, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for uid, _ in pairs(touching) do
|
||||
if not currentCollisions[uid] then
|
||||
touching[uid] = nil
|
||||
ent:queueHook("dark:Broadcast.%UID%", { message = "TurnOff" }, 0)
|
||||
end
|
||||
end
|
||||
end )
|
||||
@ -20,14 +20,14 @@ local function playSound()
|
||||
|
||||
local url = "valve://sound/" .. soundFile
|
||||
|
||||
local payload = {
|
||||
filename = string.resolveURI(url, metadata["system"]["root"]),
|
||||
spatial = not playEverywhere,
|
||||
streamed = true,
|
||||
volume = volume,
|
||||
unique = true
|
||||
}
|
||||
payload["wants loops"] = not isNotLooped
|
||||
local payload = {
|
||||
filename = string.resolveURI(url, metadata["system"]["root"]),
|
||||
spatial = not playEverywhere,
|
||||
streamed = true,
|
||||
volume = volume,
|
||||
unique = true
|
||||
}
|
||||
payload["wants loop"] = not isNotLooped
|
||||
ent:callHook("sound:Emit.%UID%", payload)
|
||||
end
|
||||
|
||||
@ -58,7 +58,7 @@ ent:addHook("io:Input.%UID%", function(payload)
|
||||
local newVol = tonumber(payload.parameter)
|
||||
if newVol then
|
||||
volume = newVol / 10.0
|
||||
-- to-do: update volume of currently playing sound
|
||||
-- to-do: update volume of currently playing sound
|
||||
end
|
||||
end
|
||||
end)
|
||||
@ -73,7 +73,7 @@ ent:bind("tick", function(self)
|
||||
|
||||
for i = #pendingOutputs, 1, -1 do
|
||||
local job = pendingOutputs[i]
|
||||
if currentTime >= job.fireTime then
|
||||
if currentTime >= job.fireTime then
|
||||
local targetUIDs = _G.IOTargets[job.target]
|
||||
if targetUIDs then
|
||||
for _, targetUID in ipairs(targetUIDs) do
|
||||
@ -139,5 +139,5 @@ ent:addHook("entity:Use.%UID%", function(payload)
|
||||
ent:callHook("io:FireOutput.%UID%", { output = "OnPlayerUse" })
|
||||
ent:callHook("io:FireOutput.%UID%", { output = "OnUse" })
|
||||
ent:callHook("io:FireOutput.%UID%", { output = "OnPressed" })
|
||||
ent:callHook("io:FireOutput.%UID%", { output = "OnIn" })
|
||||
ent:callHook("io:FireOutput.%UID%", { output = "OnIn" })
|
||||
end)
|
||||
@ -521,6 +521,12 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) {
|
||||
#elif BARYCENTRIC
|
||||
ivec2 size = textureSize(samplerId, 0).xy;
|
||||
int sampleIdx = 0;
|
||||
#elif DEFERRED && MULTISAMPLING
|
||||
ivec2 size = textureSize(samplerUv).xy;
|
||||
int sampleIdx = msaa.currentID;
|
||||
#elif DEFERRED
|
||||
ivec2 size = textureSize(samplerUv, 0).xy;
|
||||
int sampleIdx = 0;
|
||||
#else
|
||||
ivec2 size = imageSize(outImage).xy;
|
||||
int sampleIdx = 0;
|
||||
|
||||
@ -83,7 +83,7 @@
|
||||
#endif
|
||||
#if BARYCENTRIC
|
||||
#ifndef BARYCENTRIC_CALCULATE
|
||||
#define BARYCENTRIC_CALCULATE 0
|
||||
#define BARYCENTRIC_CALCULATE 1
|
||||
#endif
|
||||
#ifndef BUFFER_REFERENCE
|
||||
#define BUFFER_REFERENCE 1
|
||||
|
||||
@ -5,15 +5,20 @@
|
||||
#include <uf/engine/graph/graph.h>
|
||||
|
||||
namespace impl {
|
||||
const float darkToMeters = 0.75f;
|
||||
const float darkToMeters = 0.7f;
|
||||
typedef uf::Meshlet_T<uf::graph::mesh::Skinned, uint32_t> Meshlet;
|
||||
|
||||
// could be dedicated functions in the engine
|
||||
template<typename T>
|
||||
inline bool readStruct(const uf::stl::vector<uint8_t>& buffer, uint32_t& offset, T& outValue) {
|
||||
if (offset + sizeof(T) > buffer.size()) return false;
|
||||
std::memcpy(&outValue, buffer.data() + offset, sizeof(T));
|
||||
offset += sizeof(T);
|
||||
inline bool readStruct(const uf::stl::vector<uint8_t>& buffer, uint32_t& offset, T& outValue, size_t readSize = sizeof(T)) {
|
||||
if (offset + readSize > buffer.size()) return false;
|
||||
|
||||
if ( readSize < sizeof(T) ) std::memset(&outValue, 0, sizeof(T));
|
||||
|
||||
size_t copySize = std::min(sizeof(T), readSize);
|
||||
std::memcpy(&outValue, buffer.data() + offset, copySize);
|
||||
|
||||
offset += readSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -47,6 +47,9 @@ namespace uf {
|
||||
void UF_API destroy( pod::AudioClip& clip );
|
||||
void UF_API bind( pod::AudioSource& source, pod::AudioClip* clip );
|
||||
void UF_API play( pod::AudioSource& source );
|
||||
void UF_API queue( pod::AudioSource& source, const uf::stl::string& );
|
||||
void UF_API pause( pod::AudioSource& source );
|
||||
bool UF_API paused( const pod::AudioSource& source );
|
||||
void UF_API stop( pod::AudioSource& source );
|
||||
void UF_API update( pod::AudioSource& source );
|
||||
void UF_API update( pod::AudioSource& source, const pod::Vector3f& position, const pod::Quaternion<>& orientation );
|
||||
@ -56,6 +59,7 @@ namespace uf {
|
||||
void UF_API loop( pod::AudioSource& source, bool state );
|
||||
void UF_API position( pod::AudioSource& source, const pod::Vector3f& v );
|
||||
void UF_API orientation( pod::AudioSource& source, const pod::Quaternion<>& q );
|
||||
float UF_API time( pod::AudioSource& source ); // non-const because apparently it needs to mutate the timer?
|
||||
float UF_API time( const pod::AudioSource& source );
|
||||
void UF_API time( pod::AudioSource& source, float v );
|
||||
float UF_API pitch( const pod::AudioSource& source );
|
||||
@ -67,6 +71,7 @@ namespace uf {
|
||||
float UF_API maxDistance( const pod::AudioSource& source );
|
||||
void UF_API maxDistance( pod::AudioSource& source, float v );
|
||||
|
||||
float UF_API distance( const pod::Vector3f& position );
|
||||
float UF_API occlusion( const pod::Vector3f& position );
|
||||
void UF_API occlude( pod::AudioSource& source, float factor );
|
||||
void UF_API acoustics( const pod::Vector3f&, const pod::Quaternion<>&, float&, float&, int& );
|
||||
|
||||
@ -62,6 +62,7 @@ namespace pod {
|
||||
struct {
|
||||
uf::Timer<> timer;
|
||||
float elapsed = 0;
|
||||
uf::stl::vector<uf::stl::string> pending;
|
||||
} info;
|
||||
|
||||
struct {
|
||||
|
||||
@ -29,6 +29,7 @@ namespace uf {
|
||||
uf::stl::string UF_API directory( const uf::stl::string& );
|
||||
uf::stl::string UF_API normalize( const uf::stl::string& );
|
||||
size_t UF_API size( const uf::stl::string& );
|
||||
uf::stl::vector<uf::stl::string> list( const uf::stl::string&, const uf::stl::string& = "" );
|
||||
|
||||
bool UF_API readAsString( uf::stl::string&, const uf::stl::string&, const uf::stl::string& = "" );
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ namespace pod {
|
||||
std::function<bool(pod::Mount&, const uf::stl::string&, uf::stl::vector<uint8_t>&)> read;
|
||||
std::function<size_t(pod::Mount&, const uf::stl::string&, const void*, size_t)> write;
|
||||
std::function<bool(pod::Mount&, const uf::stl::string&)> mkdir;
|
||||
std::function<uf::stl::vector<uf::stl::string>(pod::Mount&, const uf::stl::string&, const uf::stl::string&, bool)> list;
|
||||
|
||||
std::function<bool(pod::Mount&, const uf::stl::string&, size_t, size_t, uf::stl::vector<uint8_t>&)> readRange;
|
||||
std::function<bool(pod::Mount&, const uf::stl::string&, const uf::stl::vector<pod::Range>&, uf::stl::vector<uint8_t>&)> readRanges;
|
||||
@ -35,6 +36,7 @@ namespace uf {
|
||||
extern UF_API uf::stl::vector<pod::Mount> mounts;
|
||||
struct UF_API Mount {
|
||||
size_t hash = {};
|
||||
const pod::Mount* ptr = NULL;
|
||||
bool temp = false;
|
||||
~Mount();
|
||||
};
|
||||
@ -52,6 +54,7 @@ namespace uf {
|
||||
size_t UF_API write( const uf::stl::string& path, const void* data, size_t len );
|
||||
size_t UF_API write( const uf::stl::string& path, uf::stl::vector<uint8_t>& buffer );
|
||||
bool UF_API mkdir( const uf::stl::string& path );
|
||||
uf::stl::vector<uf::stl::string> UF_API list( const uf::stl::string& path, const uf::stl::string& extension = "", bool recursive = false );
|
||||
|
||||
bool UF_API readRange( const uf::stl::string& path, size_t start, size_t len, uf::stl::vector<uint8_t>& buffer );
|
||||
bool UF_API readRanges( const uf::stl::string& path, const uf::stl::vector<pod::Range>& ranges, uf::stl::vector<uint8_t>& buffer );
|
||||
|
||||
@ -19,9 +19,6 @@ void ext::AudioEmitterBehavior::initialize( uf::Object& self ) {
|
||||
|
||||
UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson);
|
||||
|
||||
if ( !metadataJson["audio"]["epsilon"].is<float>() )
|
||||
metadataJson["audio"]["epsilon"] = 0.001f;
|
||||
|
||||
this->addHook( "sound:Stop.%UID%", [&](ext::json::Value& json){
|
||||
uf::stl::string filename = json["filename"].as<uf::stl::string>();
|
||||
auto& pools = emitter.get();
|
||||
@ -71,11 +68,12 @@ void ext::AudioEmitterBehavior::initialize( uf::Object& self ) {
|
||||
if ( json["gain"].is<double>() ) uf::audio::gain(source, json["gain"].as<float>());
|
||||
if ( json["rolloffFactor"].is<double>() ) uf::audio::rolloff(source, json["rolloffFactor"].as<float>());
|
||||
if ( json["maxDistance"].is<double>() ) uf::audio::maxDistance(source, json["maxDistance"].as<float>());
|
||||
|
||||
if ( json["spatial"].is<bool>() ) source.settings.spatial = json["spatial"].as<bool>();
|
||||
|
||||
if ( json["loop"].is<bool>() ) uf::audio::loop(source, json["loop"].as<bool>());
|
||||
if ( json["wants loop"].is<bool>() ) {
|
||||
uf::audio::loop(source, json["wants loop"].as<bool>(true) && clip && clip->info.loop.has);
|
||||
else if ( json["wants loop"].is<bool>() ) {
|
||||
auto wants = json["wants loop"].as<bool>(true);
|
||||
uf::audio::loop(source, wants && clip && clip->info.loop.has);
|
||||
}
|
||||
|
||||
float volume = 1.0f;
|
||||
@ -95,18 +93,41 @@ void ext::AudioEmitterBehavior::initialize( uf::Object& self ) {
|
||||
uf::audio::play(source);
|
||||
});
|
||||
|
||||
this->addHook( "sound:QueueTrack.%UID%", [&](ext::json::Value& payload){
|
||||
uf::stl::string filename = payload["filename"].as<uf::stl::string>();
|
||||
int layer = payload["layer"].as<int>(1);
|
||||
uf::stl::string channelName = "managed_bgm_channel_" + std::to_string(layer);
|
||||
UF_MSG_DEBUG("filename={}, channelName={}", filename, channelName);
|
||||
|
||||
if ( emitter.has( channelName ) ) {
|
||||
auto& source = emitter.get(channelName);
|
||||
uf::audio::queue( source, filename );
|
||||
}
|
||||
});
|
||||
|
||||
this->addHook( "sound:PlayTrack.%UID%", [&](ext::json::Value& payload){
|
||||
auto filename = payload["filename"].as<uf::stl::string>();
|
||||
|
||||
if ( filename == "" && !metadata.tracks.empty() ) {
|
||||
filename = uf::stl::random_it( metadata.tracks.begin(), metadata.tracks.end() )->first;
|
||||
}
|
||||
if ( metadata.tracks.count( filename ) == 0 && filename != "" ) return;
|
||||
|
||||
if ( metadata.tracks.count( filename ) == 0 && filename != "" ) {
|
||||
metadata.tracks[filename] = {};
|
||||
metadata.tracks[filename].filename = filename;
|
||||
}
|
||||
|
||||
auto& track = metadata.tracks[filename];
|
||||
if ( track.intro != "" ) filename = track.intro;
|
||||
track.epsilon = metadataJson["audio"]["epsilon"].as<float>(2.5f);
|
||||
|
||||
this->callHook( "asset:QueueLoad.%UID%", this->resolveToPayload( filename ) );
|
||||
auto pload = this->resolveToPayload( filename );
|
||||
pload.metadata["layer"] = payload["layer"];
|
||||
|
||||
if ( payload["loop"].is<bool>() ) pload.metadata["loop"] = payload["loop"];
|
||||
if ( payload["notify"].is<bool>() ) pload.metadata["notify"] = payload["notify"];
|
||||
|
||||
this->callHook( "asset:QueueLoad.%UID%", pload );
|
||||
});
|
||||
|
||||
this->addHook( "asset:Load.%UID%", [&](pod::payloads::assetLoad& payload){
|
||||
@ -116,8 +137,14 @@ void ext::AudioEmitterBehavior::initialize( uf::Object& self ) {
|
||||
if ( metadata.tracks.count(payload.uri) > 0 || metadata.tracks.count(metadata.current) > 0 ) {
|
||||
auto& track = metadata.tracks[payload.uri];
|
||||
pod::AudioClip* clip = &uf::asset::get<pod::AudioClip>( payload.filename );
|
||||
|
||||
int layer = payload.metadata["layer"].as<int>(1);
|
||||
uf::stl::string channelName = "managed_bgm_channel_" + std::to_string(layer);
|
||||
|
||||
pod::AudioSource& source = emitter.emit( "managed_bgm_channel", clip, true );
|
||||
if ( emitter.has( channelName ) ) {
|
||||
uf::audio::stop( emitter.get( channelName ) );
|
||||
}
|
||||
pod::AudioSource& source = emitter.emit( channelName, clip, true );
|
||||
|
||||
#if UF_AUDIO_MAPPED_VOLUMES
|
||||
auto volume = uf::audio::volumes.count("bgm") > 0 ? uf::audio::volumes.at("bgm") : 1.0f;
|
||||
@ -125,12 +152,15 @@ void ext::AudioEmitterBehavior::initialize( uf::Object& self ) {
|
||||
auto volume = uf::audio::volumes::bgm;
|
||||
#endif
|
||||
|
||||
bool shouldLoop = payload.metadata["loop"].as<bool>(payload.uri != track.intro);
|
||||
uf::audio::gain(source, track.fade.x > 0 ? 0 : volume);
|
||||
uf::audio::loop(source, payload.uri != track.intro);
|
||||
uf::audio::loop(source, shouldLoop);
|
||||
uf::audio::play(source);
|
||||
|
||||
metadata.current = payload.uri;
|
||||
track.active = true;
|
||||
|
||||
UF_MSG_DEBUG("Playing: {} (epsilon: {})", metadata.current, track.epsilon);
|
||||
} else {
|
||||
ext::json::Value json = metadataJson["audio"];
|
||||
json["filename"] = payload.filename;
|
||||
@ -154,34 +184,92 @@ void ext::AudioEmitterBehavior::tick( uf::Object& self ) {
|
||||
metadata.deserialize(self, metadataJson);
|
||||
#endif
|
||||
|
||||
if ( !emitter.has("managed_bgm_channel") ) return;
|
||||
pod::AudioSource& source = emitter.get("managed_bgm_channel");
|
||||
auto& transform = this->getComponent<pod::Transform<>>();
|
||||
float distance = uf::audio::distance( transform.position );
|
||||
|
||||
#if UF_AUDIO_MAPPED_VOLUMES
|
||||
auto volume = uf::audio::volumes.count("bgm") > 0 ? uf::audio::volumes.at("bgm") : 1.0f;
|
||||
#else
|
||||
auto volume = uf::audio::volumes::bgm;
|
||||
#endif
|
||||
bool bgmFound = false;
|
||||
|
||||
for ( auto& [ name, sources ] : emitter.get() ) {
|
||||
for ( auto& source : sources ) {
|
||||
if ( name.starts_with("managed_bgm_channel") ) {
|
||||
bgmFound = true;
|
||||
#if UF_AUDIO_MAPPED_VOLUMES
|
||||
auto volume = uf::audio::volumes.count("bgm") > 0 ? uf::audio::volumes.at("bgm") : 1.0f;
|
||||
#else
|
||||
auto volume = uf::audio::volumes::bgm;
|
||||
#endif
|
||||
|
||||
if ( source.clip ) {
|
||||
float current = uf::audio::time(source);
|
||||
float end = source.clip->info.duration;
|
||||
|
||||
bool fileChanged = (source.clip->filename != metadata.current);
|
||||
bool timerWrapped = (metadata.tracks.count(metadata.current) && !metadata.tracks[metadata.current].active && current < 1.0f);
|
||||
|
||||
if ( fileChanged || timerWrapped ) {
|
||||
metadata.current = source.clip->filename;
|
||||
metadata.tracks[metadata.current].active = true;
|
||||
}
|
||||
|
||||
if ( metadata.tracks.count( metadata.current ) > 0 ) {
|
||||
auto& track = metadata.tracks[metadata.current];
|
||||
|
||||
if ( track.active ) {
|
||||
bool isIntro = metadata.current == track.intro;
|
||||
|
||||
float a = volume;
|
||||
if ( track.fade.x > 0.0f && current < track.fade.x ) {
|
||||
a *= current / track.fade.x;
|
||||
} else if ( track.fade.y > 0.0f && !source.settings.loop && end - current < track.fade.y ) {
|
||||
a *= 1.0f - (end - current) / track.fade.y;
|
||||
}
|
||||
uf::audio::gain(source, a);
|
||||
|
||||
bool timeReached = (current + track.epsilon >= end);
|
||||
bool stoppedPlaying = (!source.settings.loop && !source.alSource.playing());
|
||||
|
||||
if ( end > 0 && (timeReached || stoppedPlaying) ) {
|
||||
track.active = false;
|
||||
if ( isIntro ) {
|
||||
auto payload = this->resolveToPayload( track.filename );
|
||||
this->callHook( "asset:QueueLoad.%UID%", payload );
|
||||
} else if ( !source.settings.loop ) {
|
||||
ext::json::Value msg;
|
||||
msg["filename"] = metadata.current;
|
||||
this->callHook("sound:TrackEnded.%UID%", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( source.settings.spatial ) {
|
||||
float maxDist = uf::audio::maxDistance(source);
|
||||
if ( distance > maxDist * 1.1f ) {
|
||||
if ( source.alSource.playing() ) uf::audio::pause( source );
|
||||
} else {
|
||||
if ( uf::audio::paused(source) ) uf::audio::play( source );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( metadata.tracks.count( metadata.current ) > 0 ) {
|
||||
auto& track = metadata.tracks[metadata.current];
|
||||
if ( track.active && source.clip ) {
|
||||
float current = uf::audio::time(source);
|
||||
float end = source.clip->info.duration;
|
||||
if ( track.active && !bgmFound ) {
|
||||
UF_MSG_DEBUG("BGM source vanished! Firing TrackEnded natively.");
|
||||
track.active = false;
|
||||
|
||||
bool isIntro = metadata.current == track.intro;
|
||||
|
||||
float a = volume;
|
||||
if ( current < track.fade.x ) {
|
||||
a *= current / track.fade.x;
|
||||
} else if ( !source.settings.loop && end - current < track.fade.y ) {
|
||||
a *= 1.0f - (end - current) / track.fade.y;
|
||||
}
|
||||
|
||||
uf::audio::gain(source, a);
|
||||
|
||||
if ( isIntro && end > 0 && (current + track.epsilon >= end || !source.alSource.playing()) ) {
|
||||
if ( isIntro ) {
|
||||
auto payload = this->resolveToPayload( track.filename );
|
||||
this->callHook( "asset:QueueLoad.%UID%", payload );
|
||||
} else {
|
||||
ext::json::Value msg;
|
||||
msg["filename"] = metadata.current;
|
||||
this->callHook("sound:TrackEnded.%UID%", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ namespace ext {
|
||||
uf::stl::string intro;
|
||||
float epsilon = 0.0001f;
|
||||
pod::Vector2f fade = {};
|
||||
float prevTime = 0.0f;
|
||||
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
@ -1167,8 +1167,10 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
|
||||
if ( image.viewType == uf::renderer::enums::Image::VIEW_TYPE_CUBE ) {
|
||||
gpuIndexCube[i] = countCube++;
|
||||
} else {
|
||||
} else if ( image.viewType == uf::renderer::enums::Image::VIEW_TYPE_2D ) {
|
||||
gpuIndex2D[i] = count2D++;
|
||||
} else {
|
||||
UF_MSG_DEBUG("Invalid view type 0x{:x} for: {}", image.viewType, key );
|
||||
}
|
||||
}
|
||||
|
||||
@ -1359,12 +1361,12 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
// bind io connectivity
|
||||
if ( ext::json::isArray( metadataValve["connections"] ) || metadataValve["targetname"].is<uf::stl::string>() ) {
|
||||
node.metadata["connections"] = metadataValve["connections"];
|
||||
loadJson["assets"].emplace_back("ent://scripts/io.lua");
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/io.lua");
|
||||
}
|
||||
|
||||
// bind ambient
|
||||
if ( node.name.starts_with("ambient_") ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/ambient_generic.lua");
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/ambient_generic.lua");
|
||||
loadJson["behaviors"].emplace_back("AudioEmitterBehavior");
|
||||
// bind door script
|
||||
} else if ( ext::json::isObject( metadataValve["door"] ) ) {
|
||||
@ -1404,7 +1406,7 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
|
||||
// check if trigger
|
||||
if ( node.name.starts_with("trigger_") ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/trigger.lua");
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/trigger.lua");
|
||||
// signal to assign a physics body
|
||||
if ( ext::json::isNull( node.metadata["physics"] ) ) {
|
||||
node.metadata["physics"]["type"] = "bounding box";
|
||||
@ -1418,7 +1420,7 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
auto& materialName = graph.materials[materialID];
|
||||
// attach trigger script + physics body
|
||||
if ( materialName == "tools/toolstrigger" ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/trigger.lua");
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/trigger.lua");
|
||||
// signal to assign a physics body
|
||||
if ( ext::json::isNull( node.metadata["physics"] ) ) {
|
||||
node.metadata["physics"]["type"] = "bounding box";
|
||||
@ -1431,6 +1433,49 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
}
|
||||
}
|
||||
|
||||
// convert metadata["dark"] into internal values:
|
||||
auto& metadataDark = node.metadata["dark"];
|
||||
if ( ext::json::isObject( metadataDark ) ) {
|
||||
|
||||
// bind io connectivity
|
||||
if ( ext::json::isArray( metadataDark["connections"] ) || metadataDark["id"].is<int>() ) {
|
||||
node.metadata["connections"] = metadataDark["connections"];
|
||||
loadJson["assets"].emplace_back("ent://scripts/dark/io.lua");
|
||||
}
|
||||
|
||||
// bind door
|
||||
if ( ext::json::isObject( metadataDark["door"] ) ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/dark/door.lua");
|
||||
loadJson["behaviors"].emplace_back("AudioEmitterBehavior");
|
||||
|
||||
node.metadata["dark"]["schema_db"] = graph.metadata["dark"]["schema_db"];
|
||||
}
|
||||
|
||||
// bind songs
|
||||
if ( ext::json::isObject( metadataDark["songs"] ) ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/dark/music.lua");
|
||||
loadJson["behaviors"].emplace_back("AudioEmitterBehavior");
|
||||
}
|
||||
|
||||
// bind ambient
|
||||
if ( ext::json::isObject( metadataDark["sound"] ) ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/dark/ambient.lua");
|
||||
loadJson["behaviors"].emplace_back("AudioEmitterBehavior");
|
||||
}
|
||||
|
||||
// bind trap
|
||||
if ( node.name.find("SoundTrap") != uf::stl::string::npos || node.name.find("Votrap") != uf::stl::string::npos ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/dark/trap.lua");
|
||||
loadJson["behaviors"].emplace_back("AudioEmitterBehavior");
|
||||
}
|
||||
|
||||
// bind trigger
|
||||
auto& physMeta = node.metadata["physics"];
|
||||
if ( ext::json::isObject( physMeta ) && physMeta["category"].as<uf::stl::string>("") == "trigger" ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/dark/trigger.lua");
|
||||
}
|
||||
}
|
||||
|
||||
if ( ext::json::isObject( tag ) ) {
|
||||
if ( tag["action"].as<uf::stl::string>() == "load" ) {
|
||||
if ( tag["filename"].is<uf::stl::string>() ) {
|
||||
|
||||
@ -219,52 +219,113 @@ void ext::vorbis::update( pod::AudioSource& source ) {
|
||||
pod::AudioClip* clip = source.clip;
|
||||
if ( !clip || !clip->streamed || !source.streamState.handle ) return;
|
||||
|
||||
if ( source.settings.loopMode == 1 ) source.alSource.set(AL_LOOPING, AL_FALSE);
|
||||
|
||||
OggVorbis_File* vorbisFile = (OggVorbis_File*) source.streamState.handle;
|
||||
VorbisVfsContext* ctx = (VorbisVfsContext*) source.streamState.context;
|
||||
|
||||
ALint state;
|
||||
ALint state, processed, queued;
|
||||
source.alSource.get(AL_SOURCE_STATE, state);
|
||||
if ( state != AL_PLAYING ) {
|
||||
if ( !source.settings.loop && ctx && ctx->currentOffset >= ctx->totalSize ) return;
|
||||
source.alSource.play();
|
||||
}
|
||||
|
||||
ALint processed = 0;
|
||||
source.alSource.get(AL_BUFFERS_PROCESSED, processed);
|
||||
if ( processed <= 0 ) return;
|
||||
source.alSource.get(AL_BUFFERS_QUEUED, queued);
|
||||
|
||||
ALuint index;
|
||||
char buffer[uf::audio::bufferSize];
|
||||
|
||||
while ( processed-- ) {
|
||||
memset(buffer, 0, uf::audio::bufferSize);
|
||||
AL_CHECK_RESULT(alSourceUnqueueBuffers(source.alSource.getIndex(), 1, &index));
|
||||
bool hasData = (ctx->currentOffset < ctx->totalSize) || !source.info.pending.empty() || source.settings.loop;
|
||||
#if !NO_FUN // if the engine cannot feed buffers, OpenAL will continue to loop the buffers
|
||||
source.alSource.set(AL_LOOPING, hasData ? AL_TRUE : AL_FALSE);
|
||||
#else // consume buffers automatically
|
||||
if ( source.settings.loopMode == 1 ) {
|
||||
source.alSource.set(AL_LOOPING, AL_FALSE);
|
||||
source.settings.loopMode = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto fillAndQueueBuffer = [&](ALuint index) -> bool {
|
||||
char buffer[uf::audio::bufferSize];
|
||||
int totalRead = 0;
|
||||
while (totalRead < uf::audio::bufferSize) {
|
||||
|
||||
while ( totalRead < uf::audio::bufferSize ) {
|
||||
int result = OV_READ(vorbisFile, buffer + totalRead, uf::audio::bufferSize - totalRead, endian, 2, 1, &source.streamState.bitStream);
|
||||
if (result <= 0) {
|
||||
if (result == 0 && source.settings.loop) {
|
||||
uint32_t seekTarget = clip->info.loop.has ? clip->info.loop.start : 0;
|
||||
ov_pcm_seek(vorbisFile, seekTarget);
|
||||
continue;
|
||||
|
||||
if ( result <= 0 ) {
|
||||
if ( result == 0 ) {
|
||||
if ( !source.info.pending.empty() ) {
|
||||
|
||||
uf::stl::string nextFile = source.info.pending.front();
|
||||
source.info.pending.erase(source.info.pending.begin());
|
||||
|
||||
ov_clear(vorbisFile);
|
||||
|
||||
VorbisVfsContext* nextCtx = new VorbisVfsContext();
|
||||
nextCtx->filename = uf::io::resolveURI(nextFile);
|
||||
nextCtx->currentOffset = 0;
|
||||
nextCtx->totalSize = uf::vfs::size(nextCtx->filename);
|
||||
|
||||
ov_callbacks callbacks = { funs::read, funs::seek, funs::close, funs::tell };
|
||||
|
||||
if ( ov_open_callbacks((void*) nextCtx, vorbisFile, NULL, -1, callbacks) < 0 ) {
|
||||
UF_MSG_ERROR("Gapless Vorbis transition failed! Could not open: {}", nextCtx->filename);
|
||||
delete nextCtx;
|
||||
break;
|
||||
}
|
||||
|
||||
clip->filename = nextFile;
|
||||
clip->info.size = nextCtx->totalSize;
|
||||
clip->info.duration = ov_time_total(vorbisFile, -1);
|
||||
|
||||
source.info.elapsed = 0.0f;
|
||||
source.info.timer.reset();
|
||||
source.info.timer.start();
|
||||
|
||||
ctx = nextCtx;
|
||||
source.streamState.context = (void*) ctx;
|
||||
continue;
|
||||
}
|
||||
else if ( source.settings.loop ) {
|
||||
uint32_t seekTarget = clip->info.loop.has ? clip->info.loop.start : 0;
|
||||
ov_pcm_seek(vorbisFile, seekTarget);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
totalRead += result;
|
||||
}
|
||||
|
||||
if (totalRead > 0) {
|
||||
if ( totalRead > 0 ) {
|
||||
AL_CHECK_RESULT(alBufferData(index, clip->info.format, buffer, totalRead, clip->info.frequency));
|
||||
AL_CHECK_RESULT(alSourceQueueBuffers(source.alSource.getIndex(), 1, &index));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if ( queued == 0 ) {
|
||||
if ( hasData ) {
|
||||
for ( int i = 0; i < source.settings.buffers; ++i ) {
|
||||
if ( !fillAndQueueBuffer(source.streamBuffers.getIndex(i)) ) break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while ( processed > 0 ) {
|
||||
ALuint index;
|
||||
AL_CHECK_RESULT(alSourceUnqueueBuffers(source.alSource.getIndex(), 1, &index));
|
||||
processed--;
|
||||
|
||||
bool hasData = (ctx->currentOffset < ctx->totalSize) || !source.info.pending.empty() || source.settings.loop;
|
||||
|
||||
if ( hasData ) {
|
||||
fillAndQueueBuffer(index);
|
||||
} else {
|
||||
// ensure AL_LOOPING is off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( source.settings.loopMode == 1 ) source.alSource.set(AL_LOOPING, AL_TRUE);
|
||||
}
|
||||
source.alSource.get(AL_SOURCE_STATE, state);
|
||||
source.alSource.get(AL_BUFFERS_QUEUED, queued);
|
||||
|
||||
if ( state != AL_PLAYING && queued > 0 ) {
|
||||
source.alSource.play();
|
||||
}
|
||||
}
|
||||
void ext::vorbis::close( pod::AudioClip& clip ) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@ -59,13 +59,12 @@ namespace {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// default to 16-bit audio
|
||||
inline bool format( pod::AudioClip& clip, int channels, int bitDepth ) {
|
||||
if (channels == 1 && bitDepth == 8) clip.info.format = AL_FORMAT_MONO8;
|
||||
else if (channels == 1 && bitDepth == 16) clip.info.format = AL_FORMAT_MONO16;
|
||||
else if (channels == 2 && bitDepth == 8) clip.info.format = AL_FORMAT_STEREO8;
|
||||
else if (channels == 2 && bitDepth == 16) clip.info.format = AL_FORMAT_STEREO16;
|
||||
if (channels == 1) clip.info.format = AL_FORMAT_MONO16;
|
||||
else if (channels == 2) clip.info.format = AL_FORMAT_STEREO16;
|
||||
else {
|
||||
UF_MSG_ERROR("WAV: unrecognized format: {} channels, {} bps", channels, bitDepth);
|
||||
UF_MSG_ERROR("WAV: unrecognized format: {} channels", channels);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -85,9 +84,9 @@ void ext::wav::load( pod::AudioClip& clip ) {
|
||||
}
|
||||
|
||||
drwav* wav = &ctx->wav;
|
||||
clip.info.size = wav->totalPCMFrameCount * wav->channels * (wav->bitsPerSample / 8);
|
||||
clip.info.channels = wav->channels;
|
||||
clip.info.bitDepth = wav->bitsPerSample;
|
||||
clip.info.bitDepth = 16; // wav->bitsPerSample;
|
||||
clip.info.size = wav->totalPCMFrameCount * wav->channels * sizeof(uint16_t); // (wav->bitsPerSample / 8);
|
||||
clip.info.frequency = wav->sampleRate;
|
||||
clip.info.duration = (double) wav->totalPCMFrameCount / wav->sampleRate;
|
||||
clip.info.loop.has = false;
|
||||
@ -107,16 +106,15 @@ void ext::wav::load( pod::AudioClip& clip ) {
|
||||
}
|
||||
}
|
||||
|
||||
if ( !format(clip, wav->channels, wav->bitsPerSample) ) {
|
||||
if ( !format( clip, wav->channels, wav->bitsPerSample ) ) {
|
||||
drwav_uninit(wav); delete ctx;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!clip.streamed) {
|
||||
uf::stl::vector<uint8_t> bytes(clip.info.size);
|
||||
drwav_read_pcm_frames(wav, wav->totalPCMFrameCount, bytes.data());
|
||||
|
||||
clip.alBuffer.buffer(clip.info.format, bytes.data(), (ALsizei)bytes.size(), clip.info.frequency);
|
||||
uf::stl::vector<int16_t> pcm(wav->totalPCMFrameCount * wav->channels);
|
||||
drwav_read_pcm_frames_s16(wav, wav->totalPCMFrameCount, pcm.data());
|
||||
clip.alBuffer.buffer(clip.info.format, pcm.data(), (ALsizei)(pcm.size() * sizeof(int16_t)), clip.info.frequency);
|
||||
if ( clip.info.loop.has ) {
|
||||
ALint loopPoints[2] = { (ALint) clip.info.loop.start, (ALint) clip.info.loop.end };
|
||||
alBufferiv(clip.alBuffer.getIndex(0), 0x2015 /* AL_LOOP_POINTS_SOFT */, loopPoints);
|
||||
@ -145,19 +143,19 @@ void ext::wav::open( pod::AudioSource& source ) {
|
||||
source.streamState.handle = (void*) ctx;
|
||||
drwav* wav = &ctx->wav;
|
||||
|
||||
size_t frameSize = wav->channels * (wav->bitsPerSample / 8);
|
||||
size_t frameSize = wav->channels * sizeof(uint16_t); // (wav->bitsPerSample / 8);
|
||||
size_t bufferFrames = uf::audio::bufferSize / frameSize;
|
||||
|
||||
char buffer[uf::audio::bufferSize];
|
||||
int16_t buffer[uf::audio::bufferSize / 2];
|
||||
uint8_t queuedBuffers = 0;
|
||||
|
||||
for ( ; queuedBuffers < source.settings.buffers; ++queuedBuffers ) {
|
||||
drwav_uint64 framesRead = drwav_read_pcm_frames(wav, bufferFrames, buffer);
|
||||
drwav_uint64 framesRead = drwav_read_pcm_frames_s16(wav, bufferFrames, buffer);
|
||||
|
||||
if ( framesRead == 0 ) {
|
||||
if ( source.settings.loop ) {
|
||||
drwav_seek_to_pcm_frame(wav, clip->info.loop.has ? clip->info.loop.start : 0);
|
||||
framesRead = drwav_read_pcm_frames(wav, bufferFrames, buffer);
|
||||
framesRead = drwav_read_pcm_frames_s16(wav, bufferFrames, buffer);
|
||||
}
|
||||
}
|
||||
if ( framesRead == 0 ) break;
|
||||
@ -176,45 +174,105 @@ void ext::wav::update( pod::AudioSource& source ) {
|
||||
pod::AudioClip* clip = source.clip;
|
||||
if ( !clip || !clip->streamed || !source.streamState.handle ) return;
|
||||
|
||||
if ( source.settings.loopMode == 1 ) source.alSource.set(AL_LOOPING, AL_FALSE);
|
||||
|
||||
DrWavVfsContext* ctx = (DrWavVfsContext*) source.streamState.handle;
|
||||
drwav* wav = &ctx->wav;
|
||||
|
||||
ALint state;
|
||||
ALint state, processed, queued;
|
||||
source.alSource.get(AL_SOURCE_STATE, state);
|
||||
if ( state != AL_PLAYING ) {
|
||||
if ( !source.settings.loop && ctx->currentOffset >= ctx->totalSize ) return;
|
||||
source.alSource.get(AL_BUFFERS_PROCESSED, processed);
|
||||
source.alSource.get(AL_BUFFERS_QUEUED, queued);
|
||||
|
||||
bool hasData = (ctx->currentOffset < ctx->totalSize) || !source.info.pending.empty() || source.settings.loop;
|
||||
#if 0 && !NO_FUN // if the engine cannot feed buffers, OpenAL will continue to loop the buffers
|
||||
source.alSource.set(AL_LOOPING, hasData ? AL_TRUE : AL_FALSE);
|
||||
#else // consume buffers automatically
|
||||
if ( source.settings.loopMode == 1 ) {
|
||||
source.alSource.set(AL_LOOPING, AL_FALSE);
|
||||
source.settings.loopMode = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t frameSize = wav->channels * sizeof(uint16_t);
|
||||
size_t bufferFrames = uf::audio::bufferSize / frameSize;
|
||||
int16_t buffer[uf::audio::bufferSize / 2];
|
||||
|
||||
auto fillAndQueueBuffer = [&](ALuint index) -> bool {
|
||||
drwav_uint64 totalFramesRead = 0;
|
||||
int16_t* bufferPtr = buffer;
|
||||
|
||||
while ( totalFramesRead < bufferFrames ) {
|
||||
drwav_uint64 framesToRead = bufferFrames - totalFramesRead;
|
||||
drwav_uint64 framesRead = drwav_read_pcm_frames_s16(wav, framesToRead, bufferPtr);
|
||||
|
||||
totalFramesRead += framesRead;
|
||||
bufferPtr += (framesRead * wav->channels);
|
||||
|
||||
if ( framesRead == 0 ) {
|
||||
if ( !source.info.pending.empty() ) {
|
||||
uf::stl::string nextFile = source.info.pending.front();
|
||||
source.info.pending.erase(source.info.pending.begin());
|
||||
|
||||
drwav_uninit(wav);
|
||||
ctx->filename = uf::io::resolveURI(nextFile);
|
||||
ctx->currentOffset = 0;
|
||||
ctx->totalSize = uf::vfs::size(ctx->filename);
|
||||
|
||||
if ( !drwav_init(wav, drwav_vfs_read, drwav_vfs_seek, drwav_vfs_tell, ctx, nullptr) ) {
|
||||
UF_MSG_ERROR("Transition failed! Could not open: {}", ctx->filename);
|
||||
break;
|
||||
}
|
||||
|
||||
clip->filename = nextFile;
|
||||
clip->info.size = ctx->totalSize;
|
||||
clip->info.duration = (double)wav->totalPCMFrameCount / wav->sampleRate;
|
||||
|
||||
source.info.elapsed = 0.0f;
|
||||
source.info.timer.reset();
|
||||
source.info.timer.start();
|
||||
} else if ( source.settings.loop ) {
|
||||
drwav_seek_to_pcm_frame(wav, clip->info.loop.has ? clip->info.loop.start : 0);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( totalFramesRead > 0 ) {
|
||||
AL_CHECK_RESULT(alBufferData(index, clip->info.format, buffer, (ALsizei)(totalFramesRead * frameSize), clip->info.frequency));
|
||||
AL_CHECK_RESULT(alSourceQueueBuffers(source.alSource.getIndex(), 1, &index));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if ( queued == 0 ) {
|
||||
if ( hasData ) {
|
||||
for ( int i = 0; i < source.settings.buffers; ++i ) {
|
||||
if ( !fillAndQueueBuffer(source.streamBuffers.getIndex(i)) ) break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while ( processed > 0 ) {
|
||||
ALuint index;
|
||||
AL_CHECK_RESULT(alSourceUnqueueBuffers(source.alSource.getIndex(), 1, &index));
|
||||
processed--;
|
||||
|
||||
bool hasData = (ctx->currentOffset < ctx->totalSize) || !source.info.pending.empty() || source.settings.loop;
|
||||
|
||||
if ( hasData ) {
|
||||
fillAndQueueBuffer(index);
|
||||
} else {
|
||||
// ensure AL_LOOPING is off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
source.alSource.get(AL_SOURCE_STATE, state);
|
||||
source.alSource.get(AL_BUFFERS_QUEUED, queued);
|
||||
|
||||
if ( state != AL_PLAYING && queued > 0 ) {
|
||||
source.alSource.play();
|
||||
}
|
||||
|
||||
ALint processed = 0;
|
||||
source.alSource.get(AL_BUFFERS_PROCESSED, processed);
|
||||
if ( processed <= 0 ) return;
|
||||
|
||||
size_t frameSize = wav->channels * (wav->bitsPerSample / 8);
|
||||
size_t bufferFrames = uf::audio::bufferSize / frameSize;
|
||||
|
||||
ALuint index;
|
||||
char buffer[uf::audio::bufferSize];
|
||||
|
||||
while ( processed-- ) {
|
||||
AL_CHECK_RESULT(alSourceUnqueueBuffers(source.alSource.getIndex(), 1, &index));
|
||||
|
||||
drwav_uint64 framesRead = drwav_read_pcm_frames(wav, bufferFrames, buffer);
|
||||
|
||||
if ( framesRead == 0 ) {
|
||||
if ( !source.settings.loop ) break;
|
||||
drwav_seek_to_pcm_frame(wav, clip->info.loop.has ? clip->info.loop.start : 0);
|
||||
framesRead = drwav_read_pcm_frames(wav, bufferFrames, buffer);
|
||||
}
|
||||
|
||||
if ( framesRead > 0 ) {
|
||||
AL_CHECK_RESULT(alBufferData(index, clip->info.format, buffer, (ALsizei)(framesRead * frameSize), clip->info.frequency));
|
||||
AL_CHECK_RESULT(alSourceQueueBuffers(source.alSource.getIndex(), 1, &index));
|
||||
}
|
||||
}
|
||||
if ( source.settings.loopMode == 1 ) source.alSource.set(AL_LOOPING, AL_TRUE);
|
||||
}
|
||||
|
||||
void ext::wav::close( pod::AudioClip& clip ) {
|
||||
|
||||
@ -202,6 +202,9 @@ namespace binds {
|
||||
// object.destroy();
|
||||
// delete &object;
|
||||
};
|
||||
uf::stl::vector<uf::Entity*> all() {
|
||||
return uf::scene::getCurrentScene().getGraph();
|
||||
}
|
||||
}
|
||||
namespace string {
|
||||
uf::stl::string extension( const uf::stl::string& filename ) {
|
||||
@ -323,6 +326,7 @@ void ext::lua::initialize() {
|
||||
entities.set("currentScene", UF_LUA_C_FUN(::binds::entities::currentScene));
|
||||
entities.set("controller", UF_LUA_C_FUN(::binds::entities::controller));
|
||||
entities.set("destroy", UF_LUA_C_FUN(::binds::entities::destroy));
|
||||
entities.set("all", UF_LUA_C_FUN(::binds::entities::all));
|
||||
}
|
||||
// `string` table
|
||||
{
|
||||
@ -331,7 +335,7 @@ void ext::lua::initialize() {
|
||||
string.set("resolveURI", UF_LUA_C_FUN(::binds::string::resolveURI));
|
||||
string.set("si", UF_LUA_C_FUN(::binds::string::si));
|
||||
|
||||
string.set("match", UF_LUA_C_FUN(uf::string::match));
|
||||
// string.set("match", UF_LUA_C_FUN(uf::string::match));
|
||||
string.set("matched", UF_LUA_C_FUN(uf::string::matched));
|
||||
}
|
||||
// `io` table
|
||||
|
||||
@ -61,7 +61,7 @@ namespace impl {
|
||||
|
||||
struct BinPolyHeader {
|
||||
uint16_t index;
|
||||
int16_t data;
|
||||
uint16_t data;
|
||||
uint8_t type;
|
||||
uint8_t num_verts;
|
||||
uint16_t norm_index;
|
||||
@ -202,10 +202,12 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) {
|
||||
|
||||
|
||||
auto it = std::find(graph.materials.begin(), graph.materials.end(), matName);
|
||||
int32_t matIndex = (mainHeader.version == 4) ? i : binMats[i].slot_num;
|
||||
|
||||
if ( it != graph.materials.end() ) {
|
||||
textureToMaterialId[i] = (int32_t)(std::distance(graph.materials.begin(), it));
|
||||
textureToMaterialId[matIndex] = (int32_t)(std::distance(graph.materials.begin(), it));
|
||||
} else {
|
||||
textureToMaterialId[i] = graph.materials.size();
|
||||
textureToMaterialId[matIndex] = graph.materials.size();
|
||||
graph.materials.emplace_back(matName);
|
||||
storage.materials[matName].indexAlbedo = -1;
|
||||
}
|
||||
@ -261,23 +263,29 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) {
|
||||
offset += 19;
|
||||
} else if ( nodeType == 2 ) {
|
||||
uint16_t nf1{}, nf2{};
|
||||
impl::readStruct( buffer, offset += 17, nf1 );
|
||||
impl::readStruct( buffer, offset += 4, nf2 );
|
||||
offset += 17;
|
||||
impl::readStruct( buffer, offset, nf1 );
|
||||
offset += 2;
|
||||
impl::readStruct( buffer, offset, nf2 );
|
||||
|
||||
if ( impl::readArray( buffer, offset, nf1 + nf2, faces ) && !subObjectPolys.empty() ) {
|
||||
subObjectPolys.back().insert( subObjectPolys.back().end(), faces.begin(), faces.end() );
|
||||
}
|
||||
} else if (nodeType == 1) {
|
||||
uint16_t nf1{}, nf2{};
|
||||
impl::readStruct( buffer, offset += 17, nf1 );
|
||||
impl::readStruct( buffer, offset += 12, nf2 );
|
||||
offset += 17;
|
||||
impl::readStruct( buffer, offset, nf1 );
|
||||
offset += 10;
|
||||
impl::readStruct( buffer, offset, nf2 );
|
||||
|
||||
if ( impl::readArray( buffer, offset, nf1 + nf2, faces ) && !subObjectPolys.empty() ) {
|
||||
subObjectPolys.back().insert( subObjectPolys.back().end(), faces.begin(), faces.end() );
|
||||
}
|
||||
} else if (nodeType == 0) {
|
||||
uint16_t nf{};
|
||||
impl::readStruct( buffer, offset += 17, nf );
|
||||
offset += 17;
|
||||
impl::readStruct( buffer, offset, nf );
|
||||
|
||||
if ( impl::readArray(buffer, offset, nf, faces) && !subObjectPolys.empty() ) {
|
||||
subObjectPolys.back().insert(subObjectPolys.back().end(), faces.begin(), faces.end());
|
||||
}
|
||||
@ -316,21 +324,27 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) {
|
||||
bool hasUVs = ((polyHeader.type & 3) == 3);
|
||||
if (hasUVs) impl::readArray(buffer, dataOffset, polyHeader.num_verts, uvIndices);
|
||||
|
||||
uint32_t localMatID = 0;
|
||||
if ( mainHeader.version == 4 ) {
|
||||
uint8_t matByte = 0;
|
||||
impl::readStruct(buffer, dataOffset, matByte);
|
||||
localMatID = matByte;
|
||||
} else {
|
||||
localMatID = polyHeader.data;
|
||||
}
|
||||
|
||||
int32_t graphMatID = -1;
|
||||
int32_t localMatID = polyHeader.data > 0 ? polyHeader.data - 1 : 0;
|
||||
if (localMatID >= 0 && localMatID < textureToMaterialId.size()) {
|
||||
if ( localMatID < textureToMaterialId.size() ) {
|
||||
graphMatID = textureToMaterialId[localMatID];
|
||||
}
|
||||
if ( graphMatID == -1 ) {
|
||||
graphMatID = 0;
|
||||
}
|
||||
|
||||
auto& meshlet = meshlets[graphMatID];
|
||||
meshlet.primitive.instance.materialID = graphMatID;
|
||||
|
||||
uint32_t startVertIdx = meshlet.vertices.size();
|
||||
/*
|
||||
pod::Vector3f polyNormal = {0.f, 1.f, 0.f};
|
||||
if (polyHeader.norm_index < normals.size()) polyNormal = normals[polyHeader.norm_index];
|
||||
polyNormal = impl::convertPos_NewDark(uf::matrix::multiply<float>(transforms[objIdx], polyNormal, 0.0f));
|
||||
*/
|
||||
pod::Vector3f polyNormal = {0.f, 1.f, 0.f};
|
||||
if (polyHeader.norm_index < normals.size()) polyNormal = normals[polyHeader.norm_index];
|
||||
polyNormal = uf::matrix::multiply<float>(transforms[objIdx], polyNormal, 0.0f);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@ bool ext::ttlg::loadPalette( const uf::stl::string& family, uf::stl::vector<uint
|
||||
if ( !palette.empty() ) return true;
|
||||
|
||||
uf::stl::vector<uint8_t> buffer;
|
||||
if ( uf::io::readAsBuffer(buffer, "fam://" + family + "/full.pcx")) {
|
||||
if ( uf::io::readAsBuffer(buffer, "FAM://" + family + "/full.pcx")) {
|
||||
if ( buffer.size() >= 769 && buffer[buffer.size() - 769] == 0x0C ) {
|
||||
palette.assign(buffer.end() - 768, buffer.end());
|
||||
return true;
|
||||
@ -14,7 +14,7 @@ bool ext::ttlg::loadPalette( const uf::stl::string& family, uf::stl::vector<uint
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
if ( !uf::io::readAsBuffer(buffer, "fam://" + family + "/full.gif") )
|
||||
if ( !uf::io::readAsBuffer(buffer, "FAM://" + family + "/full.gif") )
|
||||
return false;
|
||||
if ( buffer.size() < 13 || buffer[0] != 'G' || buffer[1] != 'I' || buffer[2] != 'F')
|
||||
return false;
|
||||
|
||||
@ -114,6 +114,30 @@ namespace {
|
||||
auto archive = state.get();
|
||||
return archive && ext::valve::readVpkRange(*archive, uf::string::lowercase( path ), start, len, buffer);
|
||||
};
|
||||
uf::stl::vector<uf::stl::string> list( pod::Mount& mount, const uf::stl::string& dir, const uf::stl::string& extension = "", bool recursive = false ) {
|
||||
uf::stl::vector<uf::stl::string> results;
|
||||
auto& state = uf::pointeredUserdata::get<VpkMountState>( mount.userdata );
|
||||
auto archive = state.get();
|
||||
if ( !archive ) return results;
|
||||
|
||||
uf::stl::string searchDir = uf::string::lowercase(dir);
|
||||
uf::stl::string searchExt = uf::string::lowercase(extension);
|
||||
|
||||
for ( const auto& [filePath, entry] : archive->files ) {
|
||||
if ( !searchDir.empty() && !filePath.starts_with(searchDir) ) continue;
|
||||
|
||||
if ( !recursive ) {
|
||||
uf::stl::string remainder = filePath.substr(searchDir.length());
|
||||
if ( remainder.find('/') != uf::stl::string::npos ) continue;
|
||||
}
|
||||
|
||||
if ( !searchExt.empty() && !filePath.ends_with(searchExt) ) continue;
|
||||
|
||||
results.emplace_back(filePath);
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
}
|
||||
|
||||
pod::Mount ext::valve::createVpkMount( const uf::stl::string& uri, int priority ) {
|
||||
@ -128,6 +152,7 @@ pod::Mount ext::valve::createVpkMount( const uf::stl::string& uri, int priority
|
||||
mount.userdata = uf::pointeredUserdata::create<VpkMountState>();
|
||||
mount.exists = ::exists;
|
||||
mount.size = ::size;
|
||||
mount.list = ::list;
|
||||
mount.mtime = ::mtime;
|
||||
mount.read = ::read;
|
||||
mount.readRange = ::readRange;
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
// currently has issues with:
|
||||
// * skinned meshes because I'm not applying joint transformations
|
||||
// * this weird texture offsetting from movement despite having fixed this with the original camera buffers
|
||||
#define BARYCENTRIC_CALCULATE 0
|
||||
#define BARYCENTRIC_CALCULATE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@ -351,6 +351,25 @@ namespace {
|
||||
// ID for lz4
|
||||
return false;
|
||||
};
|
||||
uf::stl::vector<uf::stl::string> vfs_list( pod::Mount& mount, const uf::stl::string& dir, const uf::stl::string& extension = "", bool recursive = false ) {
|
||||
uf::stl::vector<uf::stl::string> files;
|
||||
auto& state = uf::pointeredUserdata::get<ZipMountState>( mount.userdata );
|
||||
|
||||
for ( const auto& [filepath, entry] : state.entries ) {
|
||||
if ( !dir.empty() && !filepath.starts_with(dir) ) continue;
|
||||
|
||||
if ( !recursive ) {
|
||||
uf::stl::string remainder = filepath.substr(dir.length());
|
||||
if ( remainder.find('/') != uf::stl::string::npos ) continue;
|
||||
}
|
||||
|
||||
if ( !extension.empty() && !filepath.ends_with(extension) ) continue;
|
||||
|
||||
files.emplace_back(filepath);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
pod::Mount ext::zlib::createZipMount( const uf::stl::string& uri, uf::stl::vector<uint8_t>& buffer, int priority ) {
|
||||
@ -370,16 +389,15 @@ pod::Mount ext::zlib::createZipMount( const uf::stl::string& uri, uf::stl::vecto
|
||||
mount.exists = ::vfs_exists;
|
||||
mount.size = ::vfs_size;
|
||||
mount.read = ::vfs_read;
|
||||
mount.list = ::vfs_list;
|
||||
|
||||
auto& state = uf::pointeredUserdata::get<ZipMountState>( mount.userdata );
|
||||
state.buffer = buffer;
|
||||
ext::zlib::directory( state.buffer, state.entries );
|
||||
|
||||
if ( mount.path.empty() ) {
|
||||
mount.path = ::fmt::format( "{}/{}", uri, (void*) state.buffer.data() );
|
||||
}
|
||||
if ( mount.path.empty() ) mount.path = ::fmt::format( "{}/{}", uri, (void*) state.buffer.data() );
|
||||
// for ( auto& [ k, v ] : state.entries ) UF_MSG_DEBUG("{} => {}/{}", mount.path, uri, k);
|
||||
|
||||
// for ( auto& [ k, v ] : state.entries ) UF_MSG_DEBUG("{} => {}{}", mount.path, uri, k);
|
||||
return mount;
|
||||
}
|
||||
|
||||
|
||||
@ -66,6 +66,10 @@ bool uf::audio::load( pod::AudioClip& clip, const uf::stl::string& filename, boo
|
||||
// to-do: PCM audio load
|
||||
|
||||
void uf::audio::bind( pod::AudioSource& source, pod::AudioClip* clip ) {
|
||||
source.info.elapsed = 0.0f;
|
||||
source.info.timer.stop();
|
||||
source.info.timer.reset();
|
||||
|
||||
source.clip = clip;
|
||||
if ( !clip ) return;
|
||||
|
||||
@ -79,6 +83,10 @@ void uf::audio::bind( pod::AudioSource& source, pod::AudioClip* clip ) {
|
||||
}
|
||||
}
|
||||
|
||||
void uf::audio::queue( pod::AudioSource& source, const uf::stl::string& filename ) {
|
||||
source.info.pending.emplace_back( filename );
|
||||
}
|
||||
|
||||
void uf::audio::play( pod::AudioSource& source ) {
|
||||
if ( !source.alSource.playing() ) {
|
||||
source.info.elapsed += source.info.timer.elapsed().asDouble();
|
||||
@ -95,6 +103,17 @@ void uf::audio::stop( pod::AudioSource& source ) {
|
||||
}
|
||||
}
|
||||
|
||||
void uf::audio::pause( pod::AudioSource& source ) {
|
||||
AL_CHECK_RESULT(alSourcePause( source.alSource.getIndex() ));
|
||||
source.info.timer.stop();
|
||||
}
|
||||
|
||||
bool uf::audio::paused( const pod::AudioSource& source ) {
|
||||
ALint state;
|
||||
AL_CHECK_RESULT(alGetSourcei( source.alSource.getIndex(), AL_SOURCE_STATE, &state ));
|
||||
return state == AL_PAUSED;
|
||||
}
|
||||
|
||||
void uf::audio::update( pod::AudioSource& source ) {
|
||||
if ( !source.clip || !source.clip->streamed ) return;
|
||||
|
||||
@ -167,6 +186,9 @@ void uf::audio::orientation( pod::AudioSource& source, const pod::Quaternion<>&
|
||||
source.transform.orientation = q;
|
||||
}
|
||||
|
||||
float uf::audio::time( pod::AudioSource& source ) {
|
||||
return source.info.elapsed + source.info.timer.elapsed().asDouble();
|
||||
}
|
||||
float uf::audio::time( const pod::AudioSource& source ) {
|
||||
return source.info.elapsed + source.info.timer.elapsed().asDouble();
|
||||
}
|
||||
@ -207,6 +229,10 @@ void uf::audio::maxDistance( pod::AudioSource& source, float v ) {
|
||||
}
|
||||
|
||||
//
|
||||
float uf::audio::distance( const pod::Vector3f& position ) {
|
||||
return uf::vector::distance( ::listener.position, position );
|
||||
}
|
||||
|
||||
float uf::audio::occlusion( const pod::Vector3f& position ) {
|
||||
return uf::physics::occlusion( ::listener.position, position );
|
||||
}
|
||||
|
||||
@ -25,12 +25,42 @@ pod::AudioSource& uf::AudioEmitter::emit( const uf::stl::string& key, pod::Audio
|
||||
uf::audio::initialize( source );
|
||||
uf::audio::bind( source, clip );
|
||||
return source;
|
||||
/*
|
||||
if ( unique && !pool.empty() ) {
|
||||
pod::AudioSource& source = pool.front();
|
||||
uf::audio::stop( source );
|
||||
uf::audio::bind( source, clip );
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
for ( auto& source : pool ) {
|
||||
if ( !source.alSource.playing() ) {
|
||||
uf::audio::stop( source );
|
||||
uf::audio::bind( source, clip );
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
pod::AudioSource& source = pool.emplace_back();
|
||||
uf::audio::initialize( source );
|
||||
uf::audio::bind( source, clip );
|
||||
return source;
|
||||
*/
|
||||
}
|
||||
|
||||
void uf::AudioEmitter::update() {
|
||||
for ( auto& pair : this->m_container ) {
|
||||
for ( auto& source : pair.second ) {
|
||||
if ( source.alSource.playing() ) uf::audio::update( source );
|
||||
bool isPlaying = source.alSource.playing();
|
||||
bool hasPending = !source.info.pending.empty();
|
||||
|
||||
ALint queued = 0;
|
||||
source.alSource.get(AL_BUFFERS_QUEUED, queued);
|
||||
|
||||
if ( isPlaying || hasPending || queued > 0 ) {
|
||||
uf::audio::update( source );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,7 +89,13 @@ void uf::AudioEmitter::update( const pod::Vector3f& position, const pod::Quatern
|
||||
|
||||
for ( auto& pair : this->m_container ) {
|
||||
for ( auto& source : pair.second ) {
|
||||
if ( !source.alSource.playing() ) continue;
|
||||
bool isPlaying = source.alSource.playing();
|
||||
bool hasPending = !source.info.pending.empty();
|
||||
|
||||
ALint queued = 0;
|
||||
source.alSource.get(AL_BUFFERS_QUEUED, queued);
|
||||
|
||||
if ( !isPlaying && !hasPending && queued == 0 ) continue;
|
||||
|
||||
if ( source.settings.spatial ) {
|
||||
uf::audio::position( source, position );
|
||||
@ -119,7 +155,12 @@ void uf::AudioEmitter::cleanup( bool purge ) {
|
||||
|
||||
for ( auto vecIt = pool.begin(); vecIt != pool.end(); ) {
|
||||
auto& source = *vecIt;
|
||||
if ( purge || (!source.alSource.playing() && !source.settings.loop) ) {
|
||||
|
||||
ALint state;
|
||||
alGetSourcei( source.alSource.getIndex(), AL_SOURCE_STATE, &state );
|
||||
bool active = (state == AL_PLAYING || state == AL_PAUSED);
|
||||
|
||||
if ( purge || (!active && !source.settings.loop) ) {
|
||||
uf::audio::destroy( source );
|
||||
vecIt = pool.erase(vecIt);
|
||||
} else {
|
||||
|
||||
@ -58,6 +58,9 @@ uf::stl::string uf::io::extension( const uf::stl::string& str, int32_t count ) {
|
||||
uf::stl::string uf::io::directory( const uf::stl::string& str ) {
|
||||
return str.substr( 0, str.find_last_of('/') ) + "/";
|
||||
}
|
||||
uf::stl::vector<uf::stl::string> uf::io::list( const uf::stl::string& dir, const uf::stl::string& extension ) {
|
||||
return uf::vfs::list( uf::io::resolveURI(dir), extension );
|
||||
}
|
||||
size_t uf::io::size( const uf::stl::string& filename ) {
|
||||
return uf::vfs::size( uf::io::resolveURI(filename) );
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#include <uf/utils/io/file.h>
|
||||
#include <uf/utils/math/hash.h>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace {
|
||||
@ -54,6 +55,38 @@ namespace {
|
||||
return size;
|
||||
}
|
||||
|
||||
uf::stl::vector<uf::stl::string> vfs_list( pod::Mount& mount, const uf::stl::string& dir, const uf::stl::string& extension = "", bool recursive = false ) {
|
||||
uf::stl::vector<uf::stl::string> files;
|
||||
uf::stl::string path = mount.path + dir;
|
||||
|
||||
if ( !std::filesystem::exists(path) ) return files;
|
||||
|
||||
if ( recursive ) {
|
||||
for ( const auto& entry : std::filesystem::recursive_directory_iterator(path) ) {
|
||||
if ( entry.is_regular_file() ) {
|
||||
uf::stl::string fname = entry.path().filename().string();
|
||||
if ( extension.empty() || fname.ends_with(extension) ) {
|
||||
uf::stl::string relPath = entry.path().string().substr(mount.path.length());
|
||||
std::replace(relPath.begin(), relPath.end(), '\\', '/');
|
||||
files.emplace_back(relPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ( const auto& entry : std::filesystem::directory_iterator(path) ) {
|
||||
if ( entry.is_regular_file() ) {
|
||||
uf::stl::string fname = entry.path().filename().string();
|
||||
if ( extension.empty() || fname.ends_with(extension) ) {
|
||||
uf::stl::string relPath = entry.path().string().substr(mount.path.length());
|
||||
std::replace(relPath.begin(), relPath.end(), '\\', '/');
|
||||
files.emplace_back(relPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
bool vfs_mkdir( pod::Mount& mount, const uf::stl::string& file ) {
|
||||
uf::stl::string path = mount.path + file;
|
||||
#if UF_ENV_DREAMCAST || UF_ENV_LINUX
|
||||
@ -132,9 +165,10 @@ pod::Mount uf::vfs::createDiskMount( const uf::stl::string& uri, int priority) {
|
||||
.read = ::vfs_read,
|
||||
.write = ::vfs_write,
|
||||
.mkdir = ::vfs_mkdir,
|
||||
.list = ::vfs_list,
|
||||
.readRange = ::vfs_readRange,
|
||||
.readRanges = ::vfs_readRanges,
|
||||
.stream = ::vfs_stream
|
||||
.stream = ::vfs_stream,
|
||||
};
|
||||
}
|
||||
|
||||
@ -150,7 +184,7 @@ uf::vfs::Mount uf::vfs::mount( const pod::Mount& mount, bool temp ) {
|
||||
uf::hash( hash2, m.prefix, m.path );
|
||||
|
||||
if ( hash == hash2 ) {
|
||||
return uf::vfs::Mount{ hash, false }; // do not honor temp request to avoid breaking mounts in the future
|
||||
return uf::vfs::Mount{ hash, NULL, false }; // do not honor temp request to avoid breaking mounts in the future
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,7 +196,7 @@ uf::vfs::Mount uf::vfs::mount( const pod::Mount& mount, bool temp ) {
|
||||
return a.priority > b.priority;
|
||||
});
|
||||
|
||||
return uf::vfs::Mount{ hash, temp };
|
||||
return uf::vfs::Mount{ hash, &mount, temp };
|
||||
}
|
||||
|
||||
bool uf::vfs::unmount( size_t hash ) {
|
||||
@ -220,6 +254,26 @@ bool uf::vfs::exists( const uf::stl::string& path ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uf::stl::vector<uf::stl::string> uf::vfs::list( const uf::stl::string& path, const uf::stl::string& extension, bool recursive ) {
|
||||
uf::stl::vector<uf::stl::string> results;
|
||||
uf::stl::string prefix, relative;
|
||||
uf::io::splitUri(path, prefix, relative);
|
||||
|
||||
if ( !relative.empty() && relative.back() != '/' ) relative += '/';
|
||||
|
||||
for ( auto& mount : mounts ) {
|
||||
if ( prefix.empty() && mount.priority < 0 ) continue;
|
||||
if ( prefix.empty() || mount.prefix == prefix ) {
|
||||
if ( mount.list ) {
|
||||
auto files = mount.list( mount, relative, extension, recursive );
|
||||
results.insert(results.end(), files.begin(), files.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
bool uf::vfs::mkdir( const uf::stl::string& path ) {
|
||||
uf::stl::string prefix, relative;
|
||||
uf::io::splitUri(path, prefix, relative);
|
||||
@ -302,7 +356,7 @@ bool uf::vfs::readRange( const uf::stl::string& path, size_t start, size_t len,
|
||||
|
||||
uf::stl::vector<uint8_t> fullBuffer;
|
||||
if ( !mount.read( mount, relative, fullBuffer ) ) continue;
|
||||
UF_MSG_DEBUG("hitting fallback: {}", path);
|
||||
//UF_MSG_DEBUG("hitting fallback: {}", path);
|
||||
|
||||
if ( start < fullBuffer.size() ) {
|
||||
size_t actualLen = std::min(len, fullBuffer.size() - start);
|
||||
@ -328,7 +382,7 @@ bool uf::vfs::readRanges( const uf::stl::string& path, const uf::stl::vector<pod
|
||||
|
||||
uf::stl::vector<uint8_t> fullBuffer;
|
||||
if ( !mount.read( mount, relative, fullBuffer ) ) continue;
|
||||
UF_MSG_DEBUG("hitting fallback: {}", path);
|
||||
//UF_MSG_DEBUG("hitting fallback: {}", path);
|
||||
|
||||
size_t totalBytes = 0;
|
||||
for ( const auto& r : ranges ) {
|
||||
@ -361,7 +415,7 @@ bool uf::vfs::stream( const uf::stl::string& path, size_t chunkSize, std::functi
|
||||
if ( mount.stream ) return mount.stream( mount, relative, chunkSize, callback );
|
||||
|
||||
if ( !mount.read ) continue;
|
||||
UF_MSG_DEBUG("hitting fallback: {}", path);
|
||||
//UF_MSG_DEBUG("hitting fallback: {}", path);
|
||||
|
||||
uf::stl::vector<uint8_t> fullBuffer;
|
||||
if ( !mount.read( mount, relative, fullBuffer ) ) continue;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user