853 lines
No EOL
28 KiB
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 |