local isOwner = E2Lib.isOwner local validEntity = E2Lib.validEntity registerCallback("e2lib_replace_function", function(funcname, func, oldfunc) if funcname == "isOwner" then isOwner = func elseif funcname == "validEntity" then validEntity = func end end) local bone2entity = {} local bone2index = {} local entity2bone = {} hook.Add("EntityRemoved", "wire_expression2_bone", function(ent) if not entity2bone[ent] then return end for index,bone in pairs(entity2bone[ent]) do bone2entity[bone] = nil bone2index[bone] = nil end entity2bone[ent] = nil end) -- faster access to some math library functions local abs = math.abs local atan2 = math.atan2 local sqrt = math.sqrt local asin = math.asin local Clamp = math.Clamp local rad2deg = 180 / math.pi function getBone(entity, index) if not entity2bone[entity] then entity2bone[entity] = {} end local bone = entity2bone[entity][index] if not bone then bone = entity:GetPhysicsObjectNum(index) entity2bone[entity][index] = bone end if not bone then return nil end if not bone:IsValid() then return nil end bone2entity[bone] = entity bone2index[bone] = index return bone end E2Lib.getBone = getBone local function removeBone(bone) bone2entity[bone] = nil bone2index[bone] = nil end -- checks whether the bone is valid. if yes, returns the bone's entity; otherwise, returns nil. local function isValidBone(b) if not b then return nil end if not b:IsValid() then return nil end local ent = bone2entity[b] if not validEntity(ent) then removeBone(b) return nil end return ent end E2Lib.isValidBone = isValidBone -- Same as isValidBone, except that it returns the bone index as well. local function isValidBone2(b) if not b then return nil end if not b:IsValid() then return nil end local ent = bone2entity[b] if not validEntity(ent) then removeBone(b) return nil, 0 end return ent, bone2index[b] end E2Lib.isValidBone2 = isValidBone2 -- checks whether the bone is valid. if yes, returns false; otherwise, returns true. local function isInvalidBone(b) if not b then return true end if not b:IsValid() then return true end if not validEntity(bone2entity[b]) then removeBone(b) return true end return false end E2Lib.isInvalidBone = isInvalidBone --[[************************************************************************]]-- registerType("bone", "b", nil, nil, nil, function(retval) if retval == nil then return end if type(retval) ~= "PhysObj" then error("Return value is neither nil nor a PhysObj, but a "..type(retval).."!",0) end if not bone2entity[retval] then error("Return value is not a registered bone!",0) end end, isInvalidBone ) --[[************************************************************************]]-- --- if (B) e2function number operator_is(bone b) if isInvalidBone(b) then return 0 else return 1 end end --- B = B e2function bone operator=(bone lhs, bone rhs) self.vars[lhs] = rhs self.vclk[lhs] = true return rhs end --- B == B e2function number operator==(bone lhs, bone rhs) if lhs == rhs then return 1 else return 0 end end --- B != B e2function number operator!=(bone lhs, bone rhs) if lhs ~= rhs then return 1 else return 0 end end --[[************************************************************************]]-- --- Returns 's th bone. e2function bone entity:bone(index) if not validEntity(this) then return nil end if index < 0 then return nil end if index >= this:GetPhysicsObjectCount() then return nil end return getBone(this, index) end --- Returns an array containing all of 's bones. This array's first element has the index 0! e2function array entity:bones() if not validEntity(this) then return {} end local ret = {} local maxn = this:GetPhysicsObjectCount()-1 for i = 0,maxn do ret[i] = getBone(this, i) end return ret end --- Returns 's number of bones. e2function number entity:boneCount() if not validEntity(this) then return 0 end return this:GetPhysicsObjectCount() end --- Returns an invalid bone. e2function bone nobone() return nil end --- Returns the entity belongs to e2function entity bone:entity() return isValidBone(this) end --- Returns 's index in the entity it belongs to. Returns -1 if the bone is invalid or an error occured. e2function number bone:index() if isInvalidBone(this) then return -1 end --[[local ent = this:GetEntity() if not validEntity(ent) then return -1 end local maxn = ent:GetPhysicsObjectCount()-1 for i = 0,maxn do if this == ent:GetPhysicsObjectNum(i) then return i end end return -1]] return bone2index[this] or -1 end --[[************************************************************************]]-- --- Returns 's position. e2function vector bone:pos() if isInvalidBone(this) then return {0, 0, 0} end return this:GetPos() end --- Returns a vector describing 's forward direction. e2function vector bone:forward() if isInvalidBone(this) then return {0, 0, 0} end return this:LocalToWorld(Vector(1,0,0))-this:GetPos() end --- Returns a vector describing 's right direction. e2function vector bone:right() if isInvalidBone(this) then return {0, 0, 0} end return this:LocalToWorld(Vector(0,-1,0))-this:GetPos() -- the y coordinate in local coords is left, not right. hence -1 end --- Returns a vector describing 's up direction. e2function vector bone:up() if isInvalidBone(this) then return {0, 0, 0} end return this:LocalToWorld(Vector(0,0,1))-this:GetPos() end --- Returns 's velocity. e2function vector bone:vel() if isInvalidBone(this) then return {0, 0, 0} end return this:GetVelocity() end --- Returns 's velocity in local coordinates. e2function vector bone:velL() if isInvalidBone(this) then return {0, 0, 0} end return this:WorldtoLocal(this:GetVelocity() + this:GetPos()) end --[[************************************************************************]]-- --- Transforms from local coordinates (as seen from ) to world coordinates. e2function vector bone:toWorld(vector pos) if isInvalidBone(this) then return {0, 0, 0} end return this:LocalToWorld(Vector(pos[1],pos[2],pos[3])) end --- Transforms from world coordinates to local coordinates (as seen from ). e2function vector bone:toLocal(vector pos) if isInvalidBone(this) then return {0, 0, 0} end return this:WorldToLocal(Vector(pos[1],pos[2],pos[3])) end --[[************************************************************************]]-- --- Returns 's angular velocity. e2function angle bone:angVel() if isInvalidBone(this) then return {0, 0, 0} end local vec = this:GetAngleVelocity() return { vec.y, vec.z, vec.x } end --- Returns a vector describing rotation axis, magnitude and sense given as the vector's direction, magnitude and orientation. e2function vector bone:angVelVector() if isInvalidBone(this) then return {0, 0, 0} end return this:GetAngleVelocity() end --- Returns 's pitch, yaw and roll angles. e2function angle bone:angles() if isInvalidBone(this) then return {0, 0, 0} end local ang = this:GetAngles() return { ang.p, ang.y, ang.r } end --[[************************************************************************]]-- --- Returns the bearing (yaw) from to . e2function number bone:bearing(vector pos) if isInvalidBone(this) then return 0 end pos = this:WorldToLocal(Vector(pos[1],pos[2],pos[3])) return rad2deg*-atan2(pos.y, pos.x) end --- Returns the elevation (pitch) from to . e2function number bone:elevation(vector pos) if isInvalidBone(this) then return 0 end pos = this:WorldToLocal(Vector(pos[1],pos[2],pos[3])) local len = pos:Length() if len < delta then return 0 end return rad2deg*asin(pos.z / len) end --- Returns the elevation (pitch) and bearing (yaw) from to e2function angle bone:heading(vector pos) if isInvalidBone(this) then return {0, 0, 0} end pos = this:WorldToLocal(Vector(pos[1],pos[2],pos[3])) -- bearing local bearing = rad2deg*-atan2(pos.y, pos.x) -- elevation local len = pos:Length()--sqrt(x*x + y*y + z*z) if len < delta then return { 0, bearing, 0 } end local elevation = rad2deg*asin(pos.z / len) return { elevation, bearing, 0 } end --- Returns 's mass. e2function number bone:mass() if isInvalidBone(this) then return 0 end return this:GetMass() end --- Returns 's Center of Mass. e2function vector bone:massCenter() if isInvalidBone(this) then return {0, 0, 0} end return this:LocalToWorld(this:GetMassCenter()) end --- Returns 's Center of Mass in local coordinates. e2function vector bone:massCenterL() if isInvalidBone(this) then return {0, 0, 0} end return this:GetMassCenter() end --- Sets 's mass (between 0.001 and 50,000) e2function void bone:setMass(mass) local ent = isValidBone(this) if not ent then return end if not isOwner(self, ent) then return end mass = Clamp(mass, 0.001, 50000) this:SetMass(mass) end --- Gets the principal components of 's inertia tensor in the form vec(Ixx, Iyy, Izz) e2function vector bone:inertia() if isInvalidBone(this) then return {0, 0, 0} end return this:GetInertia() end --[[************************************************************************]]-- --- Applies force to according to 's direction and magnitude e2function void bone:applyForce(vector force) local ent = isValidBone(this) if not ent then return end if not isOwner(self, ent) then return end this:ApplyForceCenter(Vector(force[1], force[2], force[3])) end --- Applies force to according to from the location of e2function void bone:applyOffsetForce(vector force, vector pos) local ent = isValidBone(this) if not ent then return end if not isOwner(self, ent) then return end this:ApplyForceOffset(Vector(force[1], force[2], force[3]), Vector(pos[1], pos[2], pos[3])) end --- Applies torque to according to e2function void bone:applyAngForce(angle angForce) local ent = isValidBone(this) if not ent then return end if not isOwner(self, ent) then return end if angForce[1] == 0 and angForce[2] == 0 and angForce[3] == 0 then return end -- assign vectors local pos = this:GetPos() local forward = this:LocalToWorld(Vector(1,0,0)) - pos local left = this:LocalToWorld(Vector(0,1,0)) - pos -- the y coordinate in local coords is left, not right local up = this:LocalToWorld(Vector(0,0,1)) - pos -- apply pitch force if angForce[1] ~= 0 then local pitch = up * (angForce[1] * 0.5) this:ApplyForceOffset( forward, pitch ) this:ApplyForceOffset( forward * -1, pitch * -1 ) end -- apply yaw force if angForce[2] ~= 0 then local yaw = forward * (angForce[2] * 0.5) this:ApplyForceOffset( left, yaw ) this:ApplyForceOffset( left * -1, yaw * -1 ) end -- apply roll force if angForce[3] ~= 0 then local roll = left * (angForce[3] * 0.5) this:ApplyForceOffset( up, roll ) this:ApplyForceOffset( up * -1, roll * -1 ) end end --- Applies torque according to the axis, magnitude and sense given by the vector's direction, magnitude and orientation. e2function void bone:applyTorque(vector torque) local ent = isValidBone(this) if not ent then return end if not isOwner(self, ent) then return end local phys = this if torque[1] == 0 and torque[2] == 0 and torque[3] == 0 then return end local tq = Vector(torque[1], torque[2], torque[3]) local torqueamount = tq:Length() -- Convert torque from local to world axis tq = phys:LocalToWorld( tq ) - phys:GetPos() -- Find two vectors perpendicular to the torque axis local off if abs(tq.x) > torqueamount * 0.1 or abs(tq.z) > torqueamount * 0.1 then off = Vector(-tq.z, 0, tq.x) else off = Vector(-tq.y, tq.x, 0) end off = off:GetNormal() * torqueamount * 0.5 local dir = ( tq:Cross(off) ):GetNormal() phys:ApplyForceOffset( dir, off ) phys:ApplyForceOffset( dir * -1, off * -1 ) end --[[************************************************************************]]-- --- Returns 1 if is frozen, 0 otherwise e2function number bone:isFrozen() if isInvalidBone(this) then return end if this:IsMoveable() then return 0 else return 1 end end -- helper function for invert(T) in table.lua function e2_tostring_bone(b) local ent = isValidBone(b) if not ent then return "(null)" end return string.format("%s:bone(%d)", tostring(ent), bone2index[b]) end --- Returns formatted as a string. Returns "(null)" for invalid bones. e2function string toString(bone b) local ent = isValidBone(b) if not ent then return "(null)" end return string.format("%s:bone(%d)", tostring(ent), bone2index[b]) end WireLib.registerDebuggerFormat("BONE", e2_tostring_bone) --[[************************************************************************]]-- -- TODO: constraints