1
0
Fork 0
made-in-akira/3d_models/psdx/model 2.2/physBoneAPI.lua

853 lines
No EOL
28 KiB
Lua

-- Physbone 2.0 by ChloeSpacedOut <3
-- Some funny additions made by Superpowers04 :3
local physBone = {
-- DO NOT ENABLE THIS UNLESS YOU KNOW WHAT YOU'RE DOING, THIS APPENDS THE INDEX OF THE PHYSBONE TO IT'S NAME IF THERE'S A DUPLICATE AND CAN CAUSE ISSUES
allowDuplicates = false,
-- Diabled debug mode
disableDebugMode = false,
children = {},
collider = {},
index = {},
}
local doDebugMode = host:isHost() and not physBone.disableDebugMode
local physBoneIndex = physBone.index
local boneID = 0
local lastDeltaTime,lasterDeltaTime,lastestDeltaTime,lastDelta = 1,1,1,1
local physBonePresets = {}
local debugMode = false
local whiteTexture = textures:newTexture("white",1,1)
:setPixel(0,0,vec(1,1,1))
local colliderTexture = textures:newTexture("collider",1,1)
:setPixel(0,0,vec(0,0,0,0))
local nodeTexture = textures:newTexture("node",1,1)
:setPixel(0,0,vec(0,0.7,1))
physBone.getVals = function(val1,val2,val3,val4)
local type = type(val1)
if type == "Vector2" or type == "Vector3" then
return val1
elseif val3 then
return vec(val1,val2,val3)
else
return vec(val2,val2)
end
end
physBone.vecToRot = function(vec3)
vec3 = vec3:copy():normalize()
local pitch = math.deg(math.asin(-vec3.y))
local yaw = math.deg(math.atan2(vec3.x,vec3.z))
return pitch,yaw
end
physBone.vecToRotMat = function(vec3)
local w = vec3:copy():normalize()
local u = vec(1,0,0)
if math.abs(u:copy():dot(w)) > 0.7 then
u = vec(0,1,0)
end
local v = w:copy():cross(u)
u = v:copy():cross(w)
return matrices.mat4(vec(u.x,u.y,u.z,0),vec(v.x,v.y,v.z,0),vec(w.x,w.y,w.z,0),vec(0,0,0,1))
end
-- Physbone metatable
local physBoneBase = {
setMass =
function(self,data)
self.mass = data
return self
end,
getMass =
function(self)
return self.mass
end,
setLength =
function(self,data)
self.length = data
if doDebugMode and self.path.PB_Debug_Direction then
self.path.PB_Debug_Direction.child:setScale(1,data,1)
end
return self
end,
getLength =
function(self)
return self.length
end,
setGravity =
function(self,data)
self.gravity = data
return self
end,
getGravity =
function(self)
return self.gravity
end,
setAirResistance =
function(self,data)
self.airResistance = data
return self
end,
getAirResistance =
function(self)
return self.airResistance
end,
setSimSpeed =
function(self,data)
self.simSpeed = data
return self
end,
getSimSpeed =
function(self)
return self.simSpeed
end,
setSpringForce =
function(self,data)
self.springForce = data
if doDebugMode and self.path.PB_Debug_SpringForce then
local springForceGroup = self.path.PB_Debug_SpringForce
springForceGroup:setScale(1,self.springForce/50,1)
end
return self
end,
getSpringForce =
function(self)
return self.springForce
end,
setEquilibrium =
function(self,val1,val2,val3)
local data = physBone.getVals(val1,val2,val3)
self.equilibrium = data
if doDebugMode and self.path.PB_Debug_SpringForce then
local springForceGroup = self.path.PB_Debug_SpringForce
local equilib = vectors.rotateAroundAxis(90,data,vec(0,-1,0))
equilib = vectors.rotateAroundAxis(90,equilib,vec(-1,0,0))
local pitch,yaw = physBone.vecToRot(equilib)
springForceGroup:setRot(pitch,0,yaw)
end
return self
end,
getEquilibrium =
function(self)
return self.equilibrium
end,
setForce =
function(self,val1,val2,val3)
local data = physBone.getVals(val1,val2,val3)
self.force = data
return self
end,
getForce =
function(self)
return self.force
end,
setRotMod =
function(self,val1,val2,val3)
local data = physBone.getVals(val1,val2,val3)
self.rotMod = data
return self
end,
getRotMod =
function(self)
return self.upsideDown
end,
setVecMod =
function(self,val1,val2,val3)
local data = physBone.getVals(val1,val2,val3)
self.vecMod = data
return self
end,
getVecMod =
function(self)
return self.vecMod
end,
setRollMod =
function(self,data)
self.rollMod = data
return self
end,
getRollMod =
function(self)
return self.rollMod
end,
setNodeStart =
function(self,data)
self.nodeStart = data
if doDebugMode and self.path.PB_Debug_NodeRadius then
self.path.PB_Debug_NodeRadius:remove()
physBone.addDebugNodes(self.path,data,self.nodeEnd,self.nodeRadius,self.nodeDensity)
self.path.PB_Debug_NodeRadius:setVisible(debugMode)
end
return self
end,
getNodeStart =
function(self)
return self.nodeStart
end,
setNodeEnd =
function(self,data)
self.nodeEnd = data
if doDebugMode and self.path.PB_Debug_NodeRadius then
self.path.PB_Debug_NodeRadius:remove()
physBone.addDebugNodes(self.path,self.nodeStart,data,self.nodeRadius,self.nodeDensity)
self.path.PB_Debug_NodeRadius:setVisible(debugMode)
end
return self
end,
getNodeEnd =
function(self)
return self.nodeEnd
end,
setNodeDensity =
function(self,data)
self.nodeDensity = data
if doDebugMode and self.path.PB_Debug_NodeRadius then
self.path.PB_Debug_NodeRadius:remove()
physBone.addDebugNodes(self.path,self.nodeStart,self.nodeEnd,self.nodeRadius,data)
self.path.PB_Debug_NodeRadius:setVisible(debugMode)
end
return self
end,
getNodeDensity =
function(self)
return self.nodeDensity
end,
setNodeRadius =
function(self,data)
self.nodeRadius = data
if doDebugMode and self.path.PB_Debug_NodeRadius then
self.path.PB_Debug_NodeRadius:remove()
physBone.addDebugNodes(self.path,self.nodeStart,self.nodeEnd,data,self.nodeDensity)
self.path.PB_Debug_NodeRadius:setVisible(debugMode)
end
return self
end,
getNodeRadius =
function(self)
return self.nodeRadius
end,
setBounce =
function(self,data)
self.bounce = data
return self
end,
getBounce =
function(self)
return self.bounce
end,
updateWithPreset =
function(self,presetID)
assert(presetID,'error making physBone: your preset can not be nil')
local preset = type(presetID) == "table" and presetID or physBonePresets[presetID]
assert(preset,'error making physBone: preset "'..tostring(presetID)..'" does not exist')
for k,v in pairs {"mass","length","gravity","airResistance","simSpeed","equilibrium","springForce","force","rotMod","vecMod","rollMod","nodeStart","nodeEnd","nodeDensity","nodeRadius","bounce"} do
if preset[v] then
local funct = "set"..string.upper(string.sub(v,0,1))..string.sub(v,2,-1)
self[funct](self,preset[v])
end
end
return self
end,
remove =
function(self)
local path = self.path
local ID = self.ID
boneID = 0
local newIndex = {}
for k,v in pairs(physBoneIndex) do
if v ~= ID then
boneID = boneID + 1
newIndex[boneID] = v
end
end
physBoneIndex = newIndex
physBone[ID] = nil
for k,v in pairs(path:getChildren()) do
v:setRot(v:getRot())
end
path:setRot(0,0,0)
end
}
local physBoneMT = {__index=physBoneBase}
-- Internal Function: Returns physbone metatable from set values
physBone.newPhysBoneFromValues = function(self,path,mass,length,gravity,airResistance,simSpeed,equilibrium,springForce,force,rotMod,vecMod,rollMod,nodeStart,nodeEnd,nodeDensity,nodeRadius,bounce,id,name)
if(self ~= physBone) then
-- Literally just offsets everything so self is used as the base
path,mass,length,gravity,airResistance,simSpeed,equilibrium,springForce,force,rotMod,vecMod,rollMod,nodeStart,nodeEnd,nodeDensity,nodeRadius,bounce,id,name = self,path,mass,length,gravity,airResistance,simSpeed,equilibrium,springForce,force,rotMod,vecMod,rollMod,nodeStart,nodeEnd,nodeDensity,nodeRadius,bounce,id,name
end
assert(user:isLoaded(),'error making physBone: attempt to create part before entity init')
assert(path,'error making physBone: part is null!')
local ID = name or path:getName()
local pos = path:partToWorldMatrix():apply()
local velocity = vec(0,0,0)
return setmetatable({
index=nil,
ID = ID,
path = path,
pos = pos,
velocity = velocity,
mass = mass,
length = length,
gravity = gravity,
airResistance = airResistance,
simSpeed = simSpeed,
equilibrium = equilibrium,
springForce = springForce,
force = force,
rotMod = rotMod,
vecMod = vecMod,
rollMod = rollMod,
nodeStart = nodeStart,
nodeEnd = nodeEnd,
nodeDensity = nodeDensity,
nodeRadius = nodeRadius,
bounce = bounce
},physBoneMT)
end
-- Internal Function: Creates a physbone based on the provided metatable
physBone.addPhysBone = function(self,part,index)
if self ~= physBone then
index,part=part,index
end
assert(part,'error making physBone: part is null!')
local ID = part.ID
if(index == nil) then
boneID = boneID + 1
index = boneID
end
assert(not physBoneIndex[index],'error adding physBone: a physBone with index "'..index..'" already exists')
physBoneIndex[index] = ID
part.index = index
physBone[ID] = part
return part
end
-- Creates a new physBone
physBone.newPhysBone = function(self,part,physBonePreset)
if self ~= physBone then
physBonePreset,part=part,self
end
assert(part,'error making physBone: part is null!')
local ID = part:getName()
if(physBone.allowDuplicates and physBone[ID]) then
ID = ID..boneID+1
end
assert(not physBone[ID],'error making physBone: this physBone "'..ID..'" already exists')
assert(physBonePreset,'error making physBone: your preset can not be nil')
local preset = type(physBonePreset) == "table" and physBonePreset or physBonePresets[physBonePreset]
assert(preset,'error making physBone: preset "'..tostring(physBonePreset)..'" does not exist')
part:setRot(0,90,0)
local p = physBone:addPhysBone(
physBone.newPhysBoneFromValues(part,preset.mass,preset.length,preset.gravity,preset.airResistance,preset.simSpeed,preset.equilibrium,preset.springForce,preset.force,preset.rotMod,preset.vecMod,preset.rollMod,preset.nodeStart,preset.nodeEnd,preset.nodeDensity,preset.nodeRadius,preset.bounce,boneID,ID)
)
if doDebugMode then
physBone.addDebugParts(part,preset)
end
return p
end
-- Returns your physBone
physBone.getPhysBone = function(self,part)
if self ~= physBone then
part = self
end
assert(part,'cannot get physBone: part is null!')
local ID = self:getName()
assert(physBone[ID],('cannot get physBone: this part does not have a physBone'))
return physBone[ID]
end
-- Internal function to get part parents
local function getParents(part,parentsTable)
local parent = part:getParent()
if not parent then return parentsTable end
parentsTable[#parentsTable + 1] = parent
getParents(parent,parentsTable)
return parentsTable
end
-- Creates a new collider
physBone.newCollider = function(self,part)
assert(part,'error making collider: part is null!')
local ID = part:getName()
assert(not physBone.collider[ID],'error making collider: this collider "'..ID..'" already exists')
local parents = getParents(part,{part})
local nbtIndex = avatar:getNBT()
for i = #parents, 1, -1 do
for k,v in pairs(nbtIndex) do
if v.name == parents[i]:getName() then
if v.chld then
nbtIndex = v.chld
else
nbtIndex = v
end
end
end
end
assert(nbtIndex.cube_data,"error making collider '"..ID.."'. This part isn't a cube")
if next(nbtIndex.cube_data) == nil then
error("error making collider '"..ID.."'. This cube either has no texture applied to it in Blockbench or has all faces disabled")
end
part:setVisible(true)
part:setPrimaryTexture("CUSTOM", colliderTexture)
local t = nbtIndex.t
local f = nbtIndex.f
local piv = nbtIndex.piv
local rot = nbtIndex.rot
if not t then t = {0,0,0} end
if not f then f = {0,0,0} end
if not piv then piv = {0,0,0} end
if not rot then rot = {0,0,0} end
t = vec(t[1],t[2],t[3])
f = vec(f[1],f[2],f[3])
piv = vec(piv[1],piv[2],piv[3])
local offset = t - piv
local size = t - f
local faces = {}
for k,v in pairs(nbtIndex.cube_data) do
faces[k] = true
end
local pivot = part:getPivot()
part:setMatrix(matrices.mat4():translate(-pivot):rotate(part:getRot()):translate(pivot) * 0.15)
:setLight(15)
-- temp code for steph to replace
physBone.collider[ID] = {
ID = ID,
part = part,
offset = offset,
size = size,
faces = faces
}
end
-- Creates a new or sets the value of an existing preset
physBone.setPreset = function(self,ID,mass,length,gravity,airResistance,simSpeed,equilibrium,springForce,force,rotMod,vecMod,rollMod,nodeStart,nodeEnd,nodeDensity,nodeRadius,bounce)
local presetCache = {}
local references = {mass = mass, length = length, gravity = gravity, airResistance = airResistance, simSpeed = simSpeed, equilibrium = equilibrium, springForce = springForce, force = force, rotMod = rotMod, vecMod = vecMod, rollMod = rollMod, nodeStart = nodeStart, nodeEnd = nodeEnd, nodeDensity = nodeDensity, nodeRadius = nodeRadius, bounce = bounce}
local fallbacks = {mass = 1, length = 16, gravity = -9.81, airResistance = 0.1, simSpeed = 1, equilibrium = vec(0,-1,0), springForce = 0, force = vec(0,0,0), rotMod = vec(0,0,0), vecMod = vec(1,1,1), rollMod = 0, nodeStart = 0, nodeEnd = 16, nodeDensity = 1, nodeRadius = 0, bounce = 0.75}
for valID, fallbackVal in pairs(fallbacks) do
local presetVal = references[valID]
if presetVal then
presetCache[valID] = presetVal
else
presetCache[valID] = fallbackVal
end
end
physBonePresets[ID] = presetCache
end
-- Removes an existing preset
physBone.removePreset = function(self,ID)
if not physBonePresets[ID] then error('error removing preset: preset "'..ID..'" does not exist') end
physBonePresets[ID] = nil
end
-- Default presets
physBone:setPreset("physBone")
physBone:setPreset("PhysBone")
physBone:setPreset("physBoob",2,nil,nil,0.5,nil,vec(0,0,-1),200,nil,vec(-90,0,0),nil,nil,nil,nil,0)
physBone:setPreset("PhysBoob",2,nil,nil,0.5,nil,vec(0,0,-1),200,nil,vec(-90,0,0),nil,nil,nil,nil,0)
-- models API function: method by GS
local old_class_index = figuraMetatables.ModelPart.__index
local class_methods = {
newPhysBone = function(self,physBonePreset)
return physBone:newPhysBone(self,physBonePreset)
end,
getPhysBone = function(self)
return physBone:getPhysBone(self)
end
}
function figuraMetatables.ModelPart:__index(key)
if class_methods[key] then
return class_methods[key]
else
return old_class_index(self, key)
end
end
--
-- Generates sphere mesh
function physBone.newSphere(part,ID)
for i = 0, 9 do
local faces = {}
for j = 1,5 do
faces["face"..j] = part:newSprite(ID..i..j)
:setTexture(nodeTexture,1,1)
:setUVPixels(2,2)
end
local face1,face2,face3,face4,face5 = faces.face1,faces.face2,faces.face3,faces.face4,faces.face5
face1:getVertices()[1]:setPos(0,8,0)
face1:getVertices()[2]:setPos(0,8,0)
face1:getVertices()[3]:setPos(-1.5279,6.4721,-4.7023)
face1:getVertices()[4]:setPos(1.5279,6.4721,-4.7023)
face1:setRot(0,i*36,0):setRenderType("EMISSIVE_SOLID")
face2:getVertices()[1]:setPos(1.5279,6.4721,-4.7023)
face2:getVertices()[2]:setPos(-1.5279,6.4721,-4.7023)
face2:getVertices()[3]:setPos(-2.4721,2.4721,-7.6085)
face2:getVertices()[4]:setPos(2.4721,2.4721,-7.6085)
face2:setRot(0,i*36,0):setRenderType("EMISSIVE_SOLID")
face3:getVertices()[1]:setPos(2.4721,2.4721,-7.6085)
face3:getVertices()[2]:setPos(-2.4721,2.4721,-7.6085)
face3:getVertices()[3]:setPos(-2.4721,-2.4721,-7.6085)
face3:getVertices()[4]:setPos(2.4721,-2.4721,-7.6085)
face3:setRot(0,i*36,0):setRenderType("EMISSIVE_SOLID")
face4:getVertices()[1]:setPos(2.4721,-2.4721,-7.6085)
face4:getVertices()[2]:setPos(-2.4721,-2.4721,-7.6085)
face4:getVertices()[3]:setPos(-1.5279,-6.4721,-4.7023)
face4:getVertices()[4]:setPos(1.5279,-6.4721,-4.7023)
face4:setRot(0,i*36,0):setRenderType("EMISSIVE_SOLID")
face5:getVertices()[1]:setPos(1.5279,-6.4721,-4.7023)
face5:getVertices()[2]:setPos(-1.5279,-6.4721,-4.7023)
face5:getVertices()[3]:setPos(0,-8,0)
face5:getVertices()[4]:setPos(0,-8,0)
face5:setRot(0,i*36,0):setRenderType("EMISSIVE_SOLID")
end
end
-- Generates physbone debug nodes
function physBone.addDebugNodes(part,nodeStart,nodeEnd,nodeRadius,nodeDensity)
local nodeRadiusGroup = part:newPart("PB_Debug_NodeRadius")
if nodeRadius == 0 then
for i = 1, nodeDensity do
local nodeParent = nodeRadiusGroup:newPart("nodeParent"..i)
local nodeLength = nodeEnd * ((nodeEnd - nodeStart) / nodeEnd) * (i / nodeDensity) + nodeStart
nodeParent:setPos(0,-nodeLength,0)
local node = nodeParent:newPart("node"..i,"CAMERA")
node:newSprite("nodeRadius")
:setTexture(nodeTexture,1,1)
:setRenderType("EMISSIVE_SOLID")
:setMatrix(matrices.mat4():translate(0.5,0.5,0.5):scale(0.5,0.5,0.5):rotate(0,0,0) * 0.1)
end
else
for i = 1, nodeDensity do
local nodeParent = nodeRadiusGroup:newPart("nodeParent"..i)
local nodeLength = nodeEnd * ((nodeEnd - nodeStart) / nodeEnd) * (i / nodeDensity) + nodeStart
nodeParent:setPos(0,-nodeLength,0)
local node = nodeParent:newPart("node"..i)
physBone.newSphere(node,"sphere"..i)
local pivot = node:getPivot()
node:setMatrix(matrices.mat4():translate(-pivot):scale(0.125 * nodeRadius):translate(pivot) * 0.1)
end
end
end
-- Generates a physBone's debug model
function physBone.addDebugParts(part,preset)
local pivotGroup = part:newPart("PB_Debug_Pivot","Camera")
pivotGroup:newSprite("pivot")
:setTexture(whiteTexture,1,1)
:setColor(1,0,0)
:setRenderType("EMISSIVE_SOLID")
:setMatrix(matrices.mat4():translate(0.5,0.5,0.5):scale(0.5,0.5,0.5):rotate(0,0,0) * 0.1)
local directionGroup = part:newPart("PB_Debug_Direction"):newPart("child")
for k = 0, 3 do
directionGroup:newSprite("line"..k)
:setTexture(whiteTexture,1,1)
:setRenderType("EMISSIVE_SOLID")
:setMatrix(matrices.mat4():translate(0.5,0,0.5):scale(0.5,1,0.5):rotate(0,k*90,0) * 0.12)
end
directionGroup:setScale(1,preset.length,1)
local springForceGroup = part:newPart("PB_Debug_SpringForce")
for k = 0, 3 do
springForceGroup:newSprite("line"..k)
:setTexture(whiteTexture,1,1)
:setColor(0,0,1)
:setRenderType("EMISSIVE_SOLID")
:setMatrix(matrices.mat4():translate(0.5,0,0.5):scale(0.25,3,0.25):rotate(0,k*90,0) * 0.11)
end
local equilib = vectors.rotateAroundAxis(90,preset.equilibrium,vec(0,-1,0))
equilib = vectors.rotateAroundAxis(90,equilib,vec(-1,0,0))
local pitch,yaw = physBone.vecToRot(equilib)
springForceGroup:setRot(pitch,0,yaw)
:setScale(1,preset.springForce/50,1)
physBone.addDebugNodes(part,preset.nodeStart,preset.nodeEnd,preset.nodeRadius,preset.nodeDensity)
for k,v in pairs({"PB_Debug_Pivot","PB_Debug_Direction","PB_Debug_SpringForce","PB_Debug_NodeRadius"}) do
part[v]:setVisible(false)
end
end
-- Generate physBones and colliders from model parts
events.entity_init:register(function()
local function findCustomParentTypes(path)
for _,part in pairs(path:getChildren()) do
local ID = part:getName()
local ID_sub = ID:sub(0,8)
if ID_sub == "collider" or ID_sub == "Collider" then
physBone:newCollider(part)
end
for presetID,preset in pairs(physBonePresets) do
if ID:sub(0,#presetID) == presetID then
physBone.newPhysBone(part,preset)
for _,child in pairs(part:getChildren()) do
local ID_child = child:getName()
local ID_child_BEsub = ID_child:sub(0,7)
local ID_child_SFsub = ID_child:sub(0,11)
if ID_child_BEsub == "boneEnd" or ID_child_BEsub == "BoneEnd" then
local childPos = child:getPivot() - part:getPivot()
local rotModVec = vectors.rotateAroundAxis(90,childPos:normalized(),vec(-1,0,0))
local pitch,yaw = physBone.vecToRot(rotModVec)
local length = childPos:length()
physBone[ID]:setRotMod(vec(-pitch,0,-yaw))
physBone[ID]:setRollMod(child:getRot().y)
physBone[ID]:setLength(length)
physBone[ID]:setNodeEnd(length)
elseif ID_child_SFsub == "springForce" or ID_child_SFsub == "SpringForce" then
local childPos = child:getPivot() - part:getPivot()
local equalibVec = childPos:normalized()
physBone[ID]:setEquilibrium(equalibVec)
physBone[ID]:setSpringForce(child:getRot().y)
end
end
part:setRot(0,90,0)
break
end
end
findCustomParentTypes(part)
end
end
findCustomParentTypes(models)
end,'PHYSBONE.generateFromModelParts')
-- Debug keybind
local debugKeybind = keybinds:newKeybind("Toggle PhysBone Debug Mode","key.keyboard.grave.accent")
function debugKeybind.press(mod)
if not (mod == 1) or not doDebugMode then return end
debugMode = not debugMode
for _,boneID in pairs(physBoneIndex) do
physBone[boneID].path.PB_Debug_Pivot:setVisible(debugMode)
physBone[boneID].path.PB_Debug_Direction:setVisible(debugMode)
physBone[boneID].path.PB_Debug_SpringForce:setVisible(debugMode)
physBone[boneID].path.PB_Debug_NodeRadius:setVisible(debugMode)
end
if debugMode then
colliderTexture:setPixel(0,0,vec(1,0.5,0)):update()
else
colliderTexture:setPixel(0,0,vec(0,0,0,0)):update()
end
end
-- Simple clock
local physClock = 0
events.tick:register(function()
physClock = physClock + 1
end,'PHYSBONE.physClock')
-- Render function prep
local deg = math.deg
local atan2 = math.atan2
local asin = math.asin
local zeroVec = vec(0,0,0)
local invalidContexts = {
PAPERDOLL = true,
MINECRAFT_GUI = true,
FIGURA_GUI = true
}
-- Render function
events.RENDER:register(function (delta,context)
if(invalidContexts[context] or client:isPaused()) then
return
end
-- Time calculations
local time = (physClock + delta)
local deltaTime = time - lastDelta
-- If world time / render somehow runs twice, don't run
if deltaTime == 0 then return end
-- Collider setup
local colliderGroups = {}
for colID,collider in pairs(physBone.collider) do
colliderGroups[colID] = {}
local colGroup = colliderGroups[colID]
local colMatrix = collider.part:partToWorldMatrix() * (1/0.15)
local partPos = colMatrix:apply()
local size = collider.size
local colTransMat = colMatrix:copy():translate(-partPos)
local offsetMat = matrices.mat4():translate(collider.offset)
local colNormalX = colMatrix:applyDir(vec(1,0,0)):normalize()
local colNormalY = colMatrix:applyDir(vec(0,1,0)):normalize()
local colNormalZ = colMatrix:applyDir(vec(0,0,1)):normalize()
local faces = collider.faces
if faces.s then
local colPos = (colTransMat * offsetMat:copy()):translate(partPos):apply()
colGroup.s = {pos = colPos, normals = {colNormalX,colNormalY,colNormalZ}, size = vec(size.x,size.y,size.z)}
end
if faces.n then
local colPos = (colTransMat * offsetMat:copy():translate(-size)):translate(partPos):apply()
colGroup.n = {pos = colPos, normals = {-colNormalX,-colNormalY,-colNormalZ}, size = vec(size.x,size.y,size.z)}
end
if faces.e then
local colPos = (colTransMat * offsetMat:copy():translate(-size * vec(0,1,0))):translate(partPos):apply()
colGroup.e = {pos = colPos, normals = {colNormalZ,-colNormalY,colNormalX}, size = vec(size.z,size.y,size.x)}
end
if faces.w then
local colPos = (colTransMat * offsetMat:copy():translate(-size * vec(1,0,1))):translate(partPos):apply()
colGroup.w = {pos = colPos, normals = {-colNormalZ,colNormalY,-colNormalX}, size = vec(size.z,size.y,size.x)}
end
if faces.u then
local colPos = (colTransMat * offsetMat:copy():translate(-size * vec(0,0,1))):translate(partPos):apply()
colGroup.u = {pos = colPos, normals = {colNormalX,-colNormalZ,colNormalY}, size = vec(size.x,size.z,size.y)}
end
if faces.d then
local colPos = (colTransMat * offsetMat:copy():translate(-size * vec(1,1,0))):translate(partPos):apply()
colGroup.d = {pos = colPos, normals = {-colNormalX,colNormalZ,-colNormalY}, size = vec(size.x,size.z,size.y)}
end
end
for _,curPhysBoneID in pairs(physBoneIndex) do
local curPhysBone = physBone[curPhysBoneID]
local worldPartMat = curPhysBone.path:partToWorldMatrix()
-- Pendulum logic
local pendulumBase = worldPartMat:apply()
if pendulumBase.x ~= pendulumBase.x then return end -- avoid physics breaking if partToWorldMatrix returns NaN
local velocity = curPhysBone.velocity / lastestDeltaTime / ((curPhysBone.simSpeed * curPhysBone.mass)/100)
-- Air resistance
local airResistanceFactor = curPhysBone.airResistance
if airResistanceFactor ~= 0 then
local airResistance = velocity * (-airResistanceFactor)
velocity = velocity + airResistance * lasterDeltaTime / curPhysBone.mass
end
-- Spring force
if curPhysBone.springForce ~= 0 then
local equilib = physBone.vecToRotMat(-curPhysBone.equilibrium)
local relativeDirMat = worldPartMat:copy() * equilib:rotate(0,-90,0)
local relativeDir = relativeDirMat:applyDir(0,0,-1):normalized()
local springForce = relativeDir * curPhysBone.springForce
velocity = velocity + springForce * lasterDeltaTime / curPhysBone.mass^2
end
-- Custom force
if curPhysBone.force ~= zeroVec then
velocity = velocity + curPhysBone.force * lasterDeltaTime / curPhysBone.mass^2
end
-- Gravity
velocity = velocity + vec(0, curPhysBone.gravity,0) * lasterDeltaTime / curPhysBone.mass
-- Collisions
local direction = ((curPhysBone.pos + velocity * lasterDeltaTime * ((curPhysBone.simSpeed * curPhysBone.mass)/100)) - pendulumBase):normalized()
local nodeDir = direction
local hasCollided = false
local planeNormal
local distance
local colNodePos
for node = 1, curPhysBone.nodeDensity do
local nodeLength = (curPhysBone.nodeEnd * ((curPhysBone.nodeEnd - curPhysBone.nodeStart) / curPhysBone.nodeEnd) * (node / curPhysBone.nodeDensity) + curPhysBone.nodeStart) / math.worldScale
local nodePos = pendulumBase + nodeDir * (nodeLength / 16)
for groupID,group in pairs(colliderGroups) do
for _,face in pairs(group) do
local normalX = face.normals[1]
local normalY = face.normals[2]
local normalZ = face.normals[3]
local diff = nodePos - face.pos
local distanceX = diff:dot(normalX) / normalX:length()
local distanceY = diff:dot(normalY) / normalY:length()
local distanceZ = diff:dot(normalZ) / normalZ:length()
local worldScale = 16*math.worldScale
local pendulumThickness = 0/worldScale
local size = vec(face.size.x,face.size.y,face.size.z) / worldScale
local penetration = (distanceZ + pendulumThickness) / size.z
local radius = curPhysBone.nodeRadius / worldScale
local isXCollided = (distanceZ - radius) <= 0 and -size.z <= distanceZ
local isYCollided = distanceY <= penetration * size.y and (penetration * -size.y) - size.y <= distanceY and penetration >= -0.5
local isZCollided = distanceX <= penetration * size.x and (penetration * -size.x) - size.x <= distanceX and penetration >= -0.5
if isXCollided and isYCollided and isZCollided then
planeNormal = normalZ
distance = distanceZ - radius
hasCollided = true
nodeDir = (nodePos - pendulumBase):normalized()
colNodePos = nodeLength
end
end
end
end
-- Finalise physics
if not hasCollided then
local nextPos = pendulumBase + direction * (curPhysBone.length / 16 / math.worldScale)
curPhysBone.velocity = nextPos - curPhysBone.pos
curPhysBone.pos = nextPos
else
local bounce = curPhysBone.bounce * 2.61
local colNextPos = direction * (colNodePos / 16 / math.worldScale) - distance * planeNormal
local nextPos = pendulumBase + colNextPos:normalized() * (curPhysBone.length / 16 / math.worldScale)
curPhysBone.velocity = (velocity - bounce * velocity:dot(planeNormal) * planeNormal) * lasterDeltaTime * ((curPhysBone.simSpeed * curPhysBone.mass)/100)
curPhysBone.pos = nextPos
end
-- Rotation calcualtion
local relativeVec = (worldPartMat:copy()):invert():apply(pendulumBase + (curPhysBone.pos - pendulumBase)):normalize()
relativeVec = (relativeVec * curPhysBone.vecMod.zyx):normalized()
relativeVec = vectors.rotateAroundAxis(90,relativeVec,vec(-1,0,0))
local pitch,yaw = physBone.vecToRot(relativeVec)
-- Transform matrix
if curPhysBone.path:getVisible() then
local parentPivot = curPhysBone.path:getPivot()
for _,part in pairs(curPhysBone.path:getChildren()) do
if part:getVisible() then
local partID = part:getName()
if partID ~= "PB_Debug_Pivot" and partID ~= "PB_Debug_SpringForce" then
local pivot = part:getPivot()
local mat = matrices.mat4()
local rot = part:getRot()
mat:translate(-pivot)
:rotate(rot.x,rot.y,rot.z)
:translate(pivot)
:translate(-parentPivot)
if partID ~= "PB_Debug_Direction" and partID ~= "PB_Debug_NodeRadius" then
mat:rotate(vec(0,0,curPhysBone.rotMod.z))
:rotate(vec(0,curPhysBone.rotMod.y,0))
:rotate(vec(curPhysBone.rotMod.x,0,0))
:rotate(vec(0,curPhysBone.rollMod,0))
end
mat:rotate(0,-90,0)
:rotate(pitch,0,yaw)
:translate(parentPivot)
part:setMatrix(mat)
end
end
end
end
end
-- Store deltaTime values
lastestDeltaTime,lasterDeltaTime,lastDeltaTime,lastDelta = lasterDeltaTime,lastDeltaTime,deltaTime,time
end,'PHYSBONE.RENDER')
setmetatable(physBone,{__index=physBone.children,__newindex=function(this,key,value)
rawget(this,'children')[key]= value
end})
return physBone