diff --git a/assets/data/classes/BallSocketConstraint.luau b/assets/data/classes/BallSocketConstraint.luau new file mode 100644 index 0000000..91c44d8 --- /dev/null +++ b/assets/data/classes/BallSocketConstraint.luau @@ -0,0 +1,53 @@ +const c, t, p = require("@scripts/classes").unpack() + +return c({ + name = "BallSocketConstraint", + extends = "Constraint", + description = " Forces its two attachments into the same position and allows them to freely rotate about all three axes, with optional limits to restrict both tilt and twist. ", + since = "0.1.0", + + properties = { + MaxFrictionTorque = { + description = "Sets the maximum frictional torque applied to keep its Attachments aligned.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + Restitution = { + description = "How elastic Attachments connected by a BallSocketConstraint will be when they reach the end of the range specified by UpperAngle when LimitsEnabled is true.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + LimitsEnabled = { + description = "Sets whether the BallSocketConstraint sets a limit on rotation based on UpperAngle.", + type = t.Bool, + unmodified = "false", + serializable = true, + }, + UpperAngle = { + description = "Sets the upper rotation limit of the BallSocketConstraint, as long as LimitsEnabled is true.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + TwistLimitsEnabled = { + description = "Sets whether the BallSocketConstraint sets a limit on twist rotation based on TwistUpperAngle and TwistLowerAngle.", + type = t.Bool, + unmodified = "false", + serializable = true, + }, + TwistLowerAngle = { + description = "Sets the lower twist rotation limit of the BallSocketConstraint, as long as TwistLimitsEnabled is true.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + TwistUpperAngle = { + description = "Sets the upper twist rotation limit of the BallSocketConstraint, as long as TwistLimitsEnabled is true.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + }, +}) diff --git a/assets/data/classes/BasePart.luau b/assets/data/classes/BasePart.luau index a42d5fa..f25b237 100644 --- a/assets/data/classes/BasePart.luau +++ b/assets/data/classes/BasePart.luau @@ -87,5 +87,9 @@ return c({ description = "Applies an impulse onto this instance.", params = { p("impulse", t.Vector3) }, }, + ApplyAngularImpulse = { + description = "Applies an angular impulse onto this instance.", + params = { p("impulse", t.Vector3) }, + }, }, }) diff --git a/assets/data/classes/HingeConstraint.luau b/assets/data/classes/HingeConstraint.luau new file mode 100644 index 0000000..301ce21 --- /dev/null +++ b/assets/data/classes/HingeConstraint.luau @@ -0,0 +1,77 @@ +const c, t, p = require("@scripts/classes").unpack() + +return c({ + name = "HingeConstraint", + extends = "Constraint", + description = " Constrains its attachments to rotate about a single axis. ", + since = "0.1.0", + + properties = { + ActuatorType = { + description = "Sets whether the rotation of the HingeConstraint is actuated and, if so, what kind of actuation it uses.", + type = t.EnumItem("ActuatorType"), + unmodified = "Enums::ActuatorType::None", + }, + CurrentAngle = { + description = "Read only property that contains the current angle of the constriant.", + type = t.Float, + unmodified = "0", + serializable = false, + writeLevel = "Never", + }, + LimitsEnabled = { + description = "Sets wheter the HingeConstraint will limit the range of rotation.", + type = t.Bool, + unmodified = "false", + serializable = true, + }, + UpperAngle = { + description = "Minimum rotation angle the HingeConstraint will allow if LimitsEnabled is true.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + LowerAngle = { + description = "Maximum rotation angle the HingeConstraint will allow if LimitsEnabled is true.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + TargetAngle = { + description = "The target angle a HingeConstraint will attempt to rotate to if its ActuatorType is set to Servo. Measured in degrees.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + AngularSpeed = { + description = "The desired angular speed a HingeConstraint with ActuatorType set to Servo will attempt to maintain while rotating towards its TargetAngle. Measured in radians/second.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + ServoMaxTorque = { + description = "The maximum torque a HingeConstraint with ActuatorType set to Servo can apply when trying to reach its desired TargetAngle.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + AngularVelocity = { + description = "The angular velocity a HingeConstraint with ActuatorType set to Motor will attempt to achieve. Measured in radians/second.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + MotorMaxAcceleration = { + description = "The maximum angular acceleration a HingeConstraint with ActuatorType set to Motor can apply to achieve its AngularVelocity. Measured in radians/second^2.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + MotorMaxTorque = { + description = "The maximum torque a HingeConstraint with ActuatorType set to Motor can apply when trying to reach its desired AngularVelocity.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + }, +}) diff --git a/assets/data/classes/PrismaticConstraint.luau b/assets/data/classes/PrismaticConstraint.luau new file mode 100644 index 0000000..6b297e1 --- /dev/null +++ b/assets/data/classes/PrismaticConstraint.luau @@ -0,0 +1,8 @@ +const c, t, p = require("@scripts/classes").unpack() + +return c({ + name = "PrismaticConstraint", + extends = "SlidingBallConstraint", + description = " Constraint its attachments to slide along one axis but not rotate. ", + since = "0.1.0", +}) diff --git a/assets/data/classes/RodConstraint.luau b/assets/data/classes/RodConstraint.luau new file mode 100644 index 0000000..bf00b52 --- /dev/null +++ b/assets/data/classes/RodConstraint.luau @@ -0,0 +1,42 @@ +const c, t, p = require("@scripts/classes").unpack() + +return c({ + name = "RodConstraint", + extends = "Constraint", + description = " Constrains its attachments to maintain a fixed distance. ", + since = "0.1.0", + + properties = { + CurrentDistance = { + description = "Read only property that contains the current distance of the constriant.", + type = t.Float, + unmodified = "0", + serializable = false, + writeLevel = "Never", + }, + Length = { + description = "The target distance. Measured in units.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + LimitsEnabled = { + description = "Sets wheter the RodConstraint will limit the angles of rod and respective attachments.", + type = t.Bool, + unmodified = "false", + serializable = true, + }, + LimitAngle0 = { + description = "Maximum angle the RodConstraint will allow between the Rod and Attachment0.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + LimitAngle1 = { + description = "Maximum angle the RodConstraint will allow between the Rod and Attachment1.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + }, +}) diff --git a/assets/data/classes/SlidingBallConstraint.luau b/assets/data/classes/SlidingBallConstraint.luau new file mode 100644 index 0000000..f7ef4e0 --- /dev/null +++ b/assets/data/classes/SlidingBallConstraint.luau @@ -0,0 +1,77 @@ +const c, t, p = require("@scripts/classes").unpack() + +return c({ + name = "SlidingBallConstraint", + extends = "Constraint", + description = " The base class for constraints that allow their attachments to slide along an axis but not rotate. ", + since = "0.1.0", + + properties = { + ActuatorType = { + description = "Sets whether the rotation of the PrismaticConstraint is actuated and, if so, what kind of actuation it uses.", + type = t.EnumItem("ActuatorType"), + unmodified = "Enums::ActuatorType::None", + }, + CurrentPosition = { + description = "Read only property that contains the current position of the constriant.", + type = t.Float, + unmodified = "0", + serializable = false, + writeLevel = "Never", + }, + LimitsEnabled = { + description = "Sets wheter the PrismaticConstraint will limit the range of rotation.", + type = t.Bool, + unmodified = "false", + serializable = true, + }, + UpperLimit = { + description = "The higher positional limit the PrismaticConstraint will allow if LimitsEnabled is true.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + LowerLimit = { + description = "The lower positional limit the PrismaticConstraint will allow if LimitsEnabled is true.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + TargetPosition = { + description = "The PrismaticConstraint's attempted target position when ActuatorType is set to Servo.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + Speed = { + description = "The desired speed a PrismaticConstraint with ActuatorType set to Servo will attempt to maintain while moving towards its TargetPosition. Measured in units per second.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + ServoMaxForce = { + description = "The PrismaticConstraint's maximum force when ActuatorType is set to Servo, as the constraint attempts to reach its desired Speed.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + Velocity = { + description = "The PrismaticConstraint's attempted velocity when ActuatorType is set to Motor. Measured in units per second.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + MotorMaxAcceleration = { + description = "The PrismaticConstraint's maximum acceleration when ActuatorType is set to Motor as the constraint attempts to reach its desired Velocity.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + MotorMaxForce = { + description = "The maximum force a PrismaticConstraint with ActuatorType set to Motor can apply when trying to reach its desired Velocity.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + }, +}) diff --git a/assets/data/classes/SpringConstraint.luau b/assets/data/classes/SpringConstraint.luau new file mode 100644 index 0000000..6b7e10b --- /dev/null +++ b/assets/data/classes/SpringConstraint.luau @@ -0,0 +1,60 @@ +const c, t, p = require("@scripts/classes").unpack() + +return c({ + name = "SpringConstraint", + extends = "Constraint", + description = " Constrains its attachments to maintain a fixed distance using a spring. ", + since = "0.1.0", + + properties = { + CurrentLength = { + description = "Read only property that contains the current distance of the constriant.", + type = t.Float, + unmodified = "0", + serializable = false, + writeLevel = "Never", + }, + FreeLength = { + description = "Natural resting length of the spring.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + MaxForce = { + description = "The maximum force the SpringConstraint can apply on its Attachments.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + Stiffness = { + description = "The strength of the spring. The higher this value the more force will be applied when the attachments are separated a different length than the FreeLength.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + Damping = { + description = "Damping constant for the SpringConstraint. Multiplied to the velocity of the constraint's Attachments to reduce the spring force applied.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + LimitsEnabled = { + description = "Sets wheter the RodConstraint will limit the angles of rod and respective attachments.", + type = t.Bool, + unmodified = "false", + serializable = true, + }, + MinLength = { + description = "Maximum angle the RodConstraint will allow between the Rod and Attachment0.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + MaxLength = { + description = "Maximum angle the RodConstraint will allow between the Rod and Attachment1.", + type = t.Float, + unmodified = "0", + serializable = true, + }, + }, +}) diff --git a/examples/constraints.luau b/examples/constraints.luau new file mode 100644 index 0000000..8439c43 --- /dev/null +++ b/examples/constraints.luau @@ -0,0 +1,142 @@ +local uis = game:GetService("UserInputService") +local w = game:GetService("Workspace") + +local connection: RBXScriptConnection + +local function makeBaseplate() + local baseplate = Instance.new("Part") + baseplate.Anchored = true + baseplate.Color = Color3.fromHSV(0, 0, 0.5) + baseplate.Position = Vector3.new(0, -8, 0) + baseplate.Size = Vector3.new(1000, 16, 1000) + baseplate.Parent = w +end + +local function weldConstraintScene() + local part0 = Instance.new("Part") + part0.Anchored = false + part0.Color = Color3.fromRGB(150, 0, 0) + part0.Position = Vector3.new(-5, 0, 0) + part0.Size = Vector3.new(2, 2, 2) + part0.Parent = w + + local part1 = Instance.new("Part") + part1.Anchored = false + part1.Color = Color3.fromRGB(0, 0, 150) + part1.Position = Vector3.new(2, 1, 0) + part1.Size = Vector3.new(2, 2, 2) + part1.Parent = w + + local wc = Instance.new("WeldConstraint") + wc.Part0 = part0 + wc.Part1 = part1 + wc.Enabled = true + wc.Parent = w + + connection = uis.JumpRequest:Connect(function() + part0:ApplyImpulse(Vector3.new(0, 5, 0)) + end) +end + +local function prismaticConstraintScene() + local part0 = Instance.new("Part") + part0.Anchored = true + part0.Color = Color3.fromRGB(150, 0, 0) + part0.Position = Vector3.new(-5, 0, 0) + part0.Size = Vector3.new(2, 2, 2) + part0.Parent = w + + local a0 = Instance.new("Attachment") + a0.Parent = part0 + + local part1 = Instance.new("Part") + part1.Anchored = false + part1.Color = Color3.fromRGB(0, 0, 150) + part1.Position = Vector3.new(2, 1, 0) + part1.Size = Vector3.new(2, 2, 2) + part1.Parent = w + + local a1 = Instance.new("Attachment") + a1.Parent = part1 + + local prisma = Instance.new("PrismaticConstraint") + prisma.Attachment0 = a0 + prisma.Attachment1 = a1 + prisma.Enabled = true + prisma.LimitsEnabled = true + prisma.ActuatorType = Enum.ActuatorType.Servo + prisma.TargetPosition = 5 + prisma.UpperLimit = 10 + prisma.Parent = w + + connection = uis.JumpRequest:Connect(function() + part1:ApplyImpulse(Vector3.new(5, 0, 0)) + end) +end + +local function hingeConstraintScene() + local part0 = Instance.new("Part") + part0.Anchored = true + part0.Color = Color3.fromRGB(150, 0, 0) + part0.Position = Vector3.new(-5, 0, 0) + part0.Size = Vector3.new(2, 2, 2) + part0.Parent = w + + local a0 = Instance.new("Attachment") + a0.CFrame = CFrame.new(0, 1, 0) + a0.Parent = part0 + + local part1 = Instance.new("Part") + part1.Anchored = false + part1.Color = Color3.fromRGB(0, 0, 150) + part1.Position = Vector3.new(-5, 2, 0) + part1.Size = Vector3.new(10, 2, 2) + part1.Parent = w + + local a1 = Instance.new("Attachment") + a1.CFrame = CFrame.new(0, -1, 0) + a1.Parent = part1 + + local hinge = Instance.new("HingeConstraint") + hinge.Attachment0 = a0 + hinge.Attachment1 = a1 + hinge.Enabled = true + hinge.ActuatorType = Enum.ActuatorType.Motor + hinge.AngularVelocity = math.pi + hinge.Parent = w + + connection = uis.JumpRequest:Connect(function() + part1:ApplyAngularImpulse(Vector3.new(5, 0, 0)) + end) +end + +local scenes = { + [Enum.KeyCode.One] = weldConstraintScene, + [Enum.KeyCode.Two] = prismaticConstraintScene, + [Enum.KeyCode.Three] = hingeConstraintScene, +} + +local readableScenes: { [string]: string } = { + ["1"] = "Weld Constraint Example", + ["2"] = "Prismatic Constraint Example", + ["3"] = "Hinge Constraint Example", +} + +uis.InputBegan:Connect(function(inp) + if scenes[inp.KeyCode] then + connection:Disconnect() + w:ClearAllChildren() + makeBaseplate() + w.CurrentCamera.CameraType = Enum.CameraType.Scriptable + w.CurrentCamera.CFrame = CFrame.new(0, 0, 0) + w.CurrentCamera.CameraType = Enum.CameraType.Custom + scenes[inp.KeyCode]() + end +end) + +print("=====================WELCOME TO THE CONSTRAINT EXAMPLES!=====================") +print("Scenes: ") +for key: string, title: string in pairs(readableScenes) do + print(key .. ": " .. title) +end +print("You can interact with each scene by pressing space.") diff --git a/include/gargantuan/classes/BallSocketConstraint.hpp b/include/gargantuan/classes/BallSocketConstraint.hpp new file mode 100644 index 0000000..0837e30 --- /dev/null +++ b/include/gargantuan/classes/BallSocketConstraint.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "gargantuan/classes/generated/BallSocketConstraint.hpp" + +namespace gargantuan { + class BallSocketConstraint : public Constraint { + I_BallSocketConstraint; + + BallSocketConstraint(); + + b3JointId CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) override; + void UpdateJoint() override; + }; +} diff --git a/include/gargantuan/classes/BasePart.hpp b/include/gargantuan/classes/BasePart.hpp index f127d50..60e2b58 100644 --- a/include/gargantuan/classes/BasePart.hpp +++ b/include/gargantuan/classes/BasePart.hpp @@ -11,6 +11,7 @@ namespace gargantuan { I_BasePart; glm::vec3 AccumulatedImpulse = {0.0f, 0.0f, 0.0f}; + glm::vec3 AccumulatedAngularImpulse = {0.0f, 0.0f, 0.0f}; virtual void CreateBodyShape(b3BodyId bodyId, b3ShapeDef &bodyShape) = 0; glm::mat4 GetModelMatrix(); diff --git a/include/gargantuan/classes/Constraint.hpp b/include/gargantuan/classes/Constraint.hpp index 7dba0ed..199872b 100644 --- a/include/gargantuan/classes/Constraint.hpp +++ b/include/gargantuan/classes/Constraint.hpp @@ -4,14 +4,31 @@ #include "gargantuan/classes/generated/Constraint.hpp" #include +#include +#include +#include #include +#include #include namespace gargantuan { class Constraint : public Instance { I_Constraint; + Constraint(); + + b3JointId LeJoint = b3_nullJointId; + + std::function RequestRebuild; + virtual std::tuple, std::shared_ptr> GetActiveParts() const; virtual b3JointId CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) = 0; + virtual void UpdateJoint() = 0; + + protected: + void BindJointProperties(std::initializer_list propertyNames); + void BindStructuralProperties(std::initializer_list propertyNames); + + void UpdateJointFrames(); }; } diff --git a/include/gargantuan/classes/HingeConstraint.hpp b/include/gargantuan/classes/HingeConstraint.hpp new file mode 100644 index 0000000..434c7fe --- /dev/null +++ b/include/gargantuan/classes/HingeConstraint.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "gargantuan/classes/generated/HingeConstraint.hpp" + +namespace gargantuan { + G_ENUM( + ActuatorType, + + None, + Motor, + Servo, + ) + + class HingeConstraint : public Constraint { + I_HingeConstraint; + + HingeConstraint(); + + b3JointId CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) override; + void UpdateJoint() override; + }; +} diff --git a/include/gargantuan/classes/PrismaticConstraint.hpp b/include/gargantuan/classes/PrismaticConstraint.hpp new file mode 100644 index 0000000..d9d7645 --- /dev/null +++ b/include/gargantuan/classes/PrismaticConstraint.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "gargantuan/classes/generated/PrismaticConstraint.hpp" + +namespace gargantuan { + class PrismaticConstraint : public SlidingBallConstraint { + I_PrismaticConstraint; + + PrismaticConstraint(); + + b3JointId CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) override; + void UpdateJoint() override; + }; +} diff --git a/include/gargantuan/classes/RodConstraint.hpp b/include/gargantuan/classes/RodConstraint.hpp new file mode 100644 index 0000000..ab876d7 --- /dev/null +++ b/include/gargantuan/classes/RodConstraint.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "gargantuan/classes/generated/RodConstraint.hpp" + +namespace gargantuan { + class RodConstraint : public Constraint { + I_RodConstraint; + + RodConstraint(); + + b3JointId CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) override; + void UpdateJoint() override; + }; +} diff --git a/include/gargantuan/classes/SlidingBallConstraint.hpp b/include/gargantuan/classes/SlidingBallConstraint.hpp new file mode 100644 index 0000000..7005659 --- /dev/null +++ b/include/gargantuan/classes/SlidingBallConstraint.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "gargantuan/classes/generated/SlidingBallConstraint.hpp" + +namespace gargantuan { + G_ENUM( + ActuatorType, + + None, + Motor, + Servo, + ) + + class SlidingBallConstraint : public Constraint { + I_SlidingBallConstraint; + }; +} diff --git a/include/gargantuan/classes/SpringConstraint.hpp b/include/gargantuan/classes/SpringConstraint.hpp new file mode 100644 index 0000000..f854be9 --- /dev/null +++ b/include/gargantuan/classes/SpringConstraint.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "gargantuan/classes/generated/SpringConstraint.hpp" + +namespace gargantuan { + class SpringConstraint : public Constraint { + I_SpringConstraint; + + SpringConstraint(); + + b3JointId CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) override; + void UpdateJoint() override; + }; +} diff --git a/include/gargantuan/classes/WeldConstraint.hpp b/include/gargantuan/classes/WeldConstraint.hpp index e9e1779..97a439e 100644 --- a/include/gargantuan/classes/WeldConstraint.hpp +++ b/include/gargantuan/classes/WeldConstraint.hpp @@ -7,6 +7,9 @@ namespace gargantuan { I_WeldConstraint; virtual std::tuple, std::shared_ptr> GetActiveParts() const override; + WeldConstraint(); + b3JointId CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) override; + void UpdateJoint() override; }; } diff --git a/include/gargantuan/classes/WorldRoot.hpp b/include/gargantuan/classes/WorldRoot.hpp index 6bd6d81..5b32a49 100644 --- a/include/gargantuan/classes/WorldRoot.hpp +++ b/include/gargantuan/classes/WorldRoot.hpp @@ -28,5 +28,7 @@ namespace gargantuan { private: b3BodyId CreatePartBody(std::shared_ptr it); b3JointId CreateConstraintJoint(std::shared_ptr it); + void DestroyConstraintJoint(Constraint *it); + void RebuildConstraintJoint(std::shared_ptr it); }; } diff --git a/scripts/classgen.luau b/scripts/classgen.luau index 8ae3cf8..e506e73 100644 --- a/scripts/classgen.luau +++ b/scripts/classgen.luau @@ -83,8 +83,8 @@ const function assembleProperties(state: GenerationState) :newline() :line(`void {setIdentifier}({property.type.cType} value) \{`) :tab() - :line(`GetPropertyChangedSignal({keyString})->Fire(\{});`) :line(`{key} = value;`) + :line(`GetPropertyChangedSignal({keyString})->Fire(\{});`) :untab() :line(`}`) :newline() @@ -282,7 +282,8 @@ end const function assembleFile(target: Target, declarationContents: fs.FileEntry) const classContents = fs.readfile(declarationContents.path) - const classResult = luau.eval(classContents, { stdlib = "Seal", name = declarationContents.path }) + const classResult = + luau.eval(classContents, { stdlib = "Seal", name = path.absolutize(declarationContents.path) }) -- the path.absolutize is more windows bs if typeof(classResult) == "error" then print(tostring(classResult)) return diff --git a/scripts/doctor.luau b/scripts/doctor.luau index bce4742..1bbdab5 100644 --- a/scripts/doctor.luau +++ b/scripts/doctor.luau @@ -6,7 +6,7 @@ local process = require("@std/process") const parsed = args.parse("doctor", "checks for dependencies required to compile the engine") :simple(args.flag("--fix", "try to fix these issues")) -const hasHomebrew = process.run({ program = "brew", args = { "--version" } }).ok +const hasHomebrew = process.run({ program = "brew", args = { "--version" } }).ok --TODO: make this work on windows const function tryInstall(package: string) if not parsed.flags.fix then diff --git a/src/classes/BallSocketConstraint.cpp b/src/classes/BallSocketConstraint.cpp new file mode 100644 index 0000000..9f2f949 --- /dev/null +++ b/src/classes/BallSocketConstraint.cpp @@ -0,0 +1,33 @@ +#include "gargantuan/classes/BallSocketConstraint.hpp" +#include +#include + +namespace gargantuan { + BallSocketConstraint::BallSocketConstraint() { + BindJointProperties({ + "LimitsEnabled", + "TwistLimitsEnabled", + "TwistLowerAngle", + "TwistUpperAngle", + "UpperAngle", + }); + } + + b3JointId BallSocketConstraint::CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) { + b3SphericalJointDef jointDefinition = b3DefaultSphericalJointDef(); + jointDefinition.base.bodyIdA = body0; + jointDefinition.base.bodyIdB = body1; + jointDefinition.base.collideConnected = true; + return b3CreateSphericalJoint(*world, &jointDefinition); + } + + void BallSocketConstraint::UpdateJoint() { + UpdateJointFrames(); + + b3SphericalJoint_EnableConeLimit(LeJoint, this->LimitsEnabled); + b3SphericalJoint_SetConeLimit(LeJoint, this->UpperAngle); + + b3SphericalJoint_EnableTwistLimit(LeJoint, this->TwistLimitsEnabled); + b3SphericalJoint_SetTwistLimits(LeJoint, this->TwistLowerAngle, this->TwistUpperAngle); + } +} diff --git a/src/classes/Constraint.cpp b/src/classes/Constraint.cpp index 2e3f1cf..4be4567 100644 --- a/src/classes/Constraint.cpp +++ b/src/classes/Constraint.cpp @@ -1,24 +1,68 @@ #include "gargantuan/classes/Constraint.hpp" #include "gargantuan/classes/BasePart.hpp" +#include "gargantuan/math/Conversions.hpp" +#include +#include #include +#include +#include namespace gargantuan { std::tuple, std::shared_ptr> Constraint::GetActiveParts() const { + if (!Attachment0.has_value() || !Attachment1.has_value()) return {nullptr, nullptr}; + auto maybePart0 = Attachment0.value()->ParentPointer; - if (!maybePart0->FindFirstAncestorWhichIsA("WorldRoot")) return {nullptr, nullptr}; + if (!maybePart0 || !maybePart0->FindFirstAncestorWhichIsA("WorldRoot")) return {nullptr, nullptr}; auto part0 = std::dynamic_pointer_cast(maybePart0->shared_from_this()); if (!part0) return {nullptr, nullptr}; auto maybePart1 = Attachment1.value()->ParentPointer; - if (!maybePart1->FindFirstAncestorWhichIsA("WorldRoot")) return {nullptr, nullptr}; - auto part1 = std::dynamic_pointer_cast(maybePart0->shared_from_this()); + if (!maybePart1 || !maybePart1->FindFirstAncestorWhichIsA("WorldRoot")) return {nullptr, nullptr}; + auto part1 = std::dynamic_pointer_cast(maybePart1->shared_from_this()); if (!part1) return {nullptr, nullptr}; return {part0, part1}; }; + Constraint::Constraint() { + BindStructuralProperties({"Attachment0", "Attachment1", "Enabled"}); + } + + void Constraint::BindJointProperties(std::initializer_list propertyNames) { + auto onChanged = [this](std::monostate) { + if (!b3Joint_IsValid(LeJoint)) return; + UpdateJoint(); + }; + + for (const auto &propertyName : propertyNames) { + GetPropertyChangedSignal(propertyName)->Connect(onChanged); + } + } + + void Constraint::BindStructuralProperties(std::initializer_list propertyNames) { + auto onChanged = [this](std::monostate) { + if (RequestRebuild) RequestRebuild(); + }; + + for (const auto &propertyName : propertyNames) { + GetPropertyChangedSignal(propertyName)->Connect(onChanged); + } + } + + void Constraint::UpdateJointFrames() { + if (Attachment0.has_value()) { + auto cframe = Attachment0.value()->GetCFrame(); + b3Joint_SetLocalFrameA(LeJoint, {AsB3Vec3(cframe.Position), AsB3Quat(cframe.ToQuaternion())}); + } + + if (Attachment1.has_value()) { + auto cframe = Attachment1.value()->GetCFrame(); + b3Joint_SetLocalFrameB(LeJoint, {AsB3Vec3(cframe.Position), AsB3Quat(cframe.ToQuaternion())}); + } + } + bool Constraint::GetActive() const { - if (!GetEnabled() || !Attachment0.has_value() || !Attachment1.has_value()) return false; + if (!GetEnabled()) return false; auto [part0, part1] = GetActiveParts(); return part0 && part1; diff --git a/src/classes/HingeConstraint.cpp b/src/classes/HingeConstraint.cpp new file mode 100644 index 0000000..0131fea --- /dev/null +++ b/src/classes/HingeConstraint.cpp @@ -0,0 +1,47 @@ +#include "gargantuan/classes/HingeConstraint.hpp" +#include +#include +#include +#include + +namespace gargantuan { + HingeConstraint::HingeConstraint() { + BindJointProperties({ + "ActuatorType", + "AngularVelocity", + "LimitsEnabled", + "LowerAngle", + "MotorMaxTorque", + "TargetAngle", + "UpperAngle", + }); + } + + b3JointId HingeConstraint::CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) { + b3RevoluteJointDef jointDefinition = b3DefaultRevoluteJointDef(); + jointDefinition.base.bodyIdA = body0; + jointDefinition.base.bodyIdB = body1; + jointDefinition.base.collideConnected = true; + return b3CreateRevoluteJoint(*world, &jointDefinition); + } + + void HingeConstraint::UpdateJoint() { + UpdateJointFrames(); + + b3RevoluteJoint_EnableLimit(LeJoint, this->LimitsEnabled); + b3RevoluteJoint_SetLimits(LeJoint, this->LowerAngle, this->UpperAngle); + + bool isMotor = this->ActuatorType == Enums::ActuatorType::Motor; + b3RevoluteJoint_EnableMotor(LeJoint, isMotor); + if (isMotor) { + b3RevoluteJoint_SetMaxMotorTorque(LeJoint, this->MotorMaxTorque); + b3RevoluteJoint_SetMotorSpeed(LeJoint, this->AngularVelocity); + } + + bool isServo = this->ActuatorType == Enums::ActuatorType::Servo; + b3RevoluteJoint_EnableSpring(LeJoint, isServo); + if (isServo) { + b3RevoluteJoint_SetTargetAngle(LeJoint, B3_DEG_TO_RAD * this->TargetAngle); + } + } +} diff --git a/src/classes/PrismaticConstraint.cpp b/src/classes/PrismaticConstraint.cpp new file mode 100644 index 0000000..2b0aa6d --- /dev/null +++ b/src/classes/PrismaticConstraint.cpp @@ -0,0 +1,45 @@ +#include "gargantuan/classes/PrismaticConstraint.hpp" +#include +#include + +namespace gargantuan { + PrismaticConstraint::PrismaticConstraint() { + BindJointProperties({ + "ActuatorType", + "LimitsEnabled", + "LowerLimit", + "MotorMaxForce", + "TargetPosition", + "UpperLimit", + "Velocity", + }); + } + + b3JointId PrismaticConstraint::CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) { + b3PrismaticJointDef jointDefinition = b3DefaultPrismaticJointDef(); + jointDefinition.base.bodyIdA = body0; + jointDefinition.base.bodyIdB = body1; + jointDefinition.base.collideConnected = true; + return b3CreatePrismaticJoint(*world, &jointDefinition); + } + + void PrismaticConstraint::UpdateJoint() { + UpdateJointFrames(); + + b3PrismaticJoint_EnableLimit(LeJoint, this->LimitsEnabled); + b3PrismaticJoint_SetLimits(LeJoint, this->LowerLimit, this->UpperLimit); + + bool isMotor = this->ActuatorType == Enums::ActuatorType::Motor; + b3PrismaticJoint_EnableMotor(LeJoint, isMotor); + if (isMotor) { + b3PrismaticJoint_SetMaxMotorForce(LeJoint, this->MotorMaxForce); + b3PrismaticJoint_SetMotorSpeed(LeJoint, this->Velocity); + } + + bool isServo = this->ActuatorType == Enums::ActuatorType::Servo; + b3PrismaticJoint_EnableSpring(LeJoint, isServo); + if (isServo) { + b3PrismaticJoint_SetTargetTranslation(LeJoint, this->TargetPosition); + } + } +} diff --git a/src/classes/RodConstraint.cpp b/src/classes/RodConstraint.cpp new file mode 100644 index 0000000..ddd1015 --- /dev/null +++ b/src/classes/RodConstraint.cpp @@ -0,0 +1,28 @@ +#include "gargantuan/classes/RodConstraint.hpp" +#include +#include + +namespace gargantuan { + RodConstraint::RodConstraint() { + BindJointProperties({ + "LimitsEnabled", + "Length", + }); + } + + b3JointId RodConstraint::CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) { + b3DistanceJointDef jointDefinition = b3DefaultDistanceJointDef(); + jointDefinition.base.bodyIdA = body0; + jointDefinition.base.bodyIdB = body1; + jointDefinition.base.collideConnected = true; + return b3CreateDistanceJoint(*world, &jointDefinition); + } + + void RodConstraint::UpdateJoint() { + UpdateJointFrames(); + + b3DistanceJoint_SetLength(LeJoint, this->Length); + b3DistanceJoint_EnableLimit(LeJoint, this->LimitsEnabled); + b3DistanceJoint_SetLengthRange(LeJoint, this->Length, this->Length); + } +} diff --git a/src/classes/SpringConstraint.cpp b/src/classes/SpringConstraint.cpp new file mode 100644 index 0000000..fef590f --- /dev/null +++ b/src/classes/SpringConstraint.cpp @@ -0,0 +1,36 @@ +#include "gargantuan/classes/SpringConstraint.hpp" +#include +#include + +namespace gargantuan { + SpringConstraint::SpringConstraint() { + BindJointProperties({ + "Damping", + "FreeLength", + "LimitsEnabled", + "MaxLength", + "MinLength", + "Stiffness", + }); + } + + b3JointId SpringConstraint::CreateJoint(b3WorldId *world, b3BodyId body0, b3BodyId body1) { + b3DistanceJointDef jointDefinition = b3DefaultDistanceJointDef(); + jointDefinition.base.bodyIdA = body0; + jointDefinition.base.bodyIdB = body1; + jointDefinition.base.collideConnected = true; + return b3CreateDistanceJoint(*world, &jointDefinition); + } + + void SpringConstraint::UpdateJoint() { + UpdateJointFrames(); + + b3DistanceJoint_EnableSpring(LeJoint, true); + b3DistanceJoint_SetLength(LeJoint, this->FreeLength); + b3DistanceJoint_SetSpringDampingRatio(LeJoint, this->Damping); + b3DistanceJoint_SetSpringForceRange(LeJoint, this->Stiffness, this->Stiffness); + + b3DistanceJoint_EnableLimit(LeJoint, this->LimitsEnabled); + b3DistanceJoint_SetLengthRange(LeJoint, this->MinLength, this->MaxLength); + } +} diff --git a/src/classes/WeldConstraint.cpp b/src/classes/WeldConstraint.cpp index e289c27..a9093cd 100644 --- a/src/classes/WeldConstraint.cpp +++ b/src/classes/WeldConstraint.cpp @@ -9,7 +9,7 @@ namespace gargantuan { if (!part0->FindFirstAncestorWhichIsA("WorldRoot")) return {nullptr, nullptr}; if (!Part1.has_value()) return {nullptr, nullptr}; - auto part1 = Part0.value(); + auto part1 = Part1.value(); if (!part1->FindFirstAncestorWhichIsA("WorldRoot")) return {nullptr, nullptr}; return {part0, part1}; diff --git a/src/classes/WorldRoot.cpp b/src/classes/WorldRoot.cpp index 46747a1..366bf74 100644 --- a/src/classes/WorldRoot.cpp +++ b/src/classes/WorldRoot.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -57,17 +56,36 @@ namespace gargantuan { } b3JointId WorldRoot::CreateConstraintJoint(std::shared_ptr it) { + if (!it->GetActive()) return b3_nullJointId; + auto [part0, part1] = it->GetActiveParts(); - if (!part0 && !part1) return b3_nullJointId; + if (!part0 || !part1) return b3_nullJointId; b3BodyId body0 = PartBodies.contains(part0.get()) ? PartBodies[part0.get()] : CreatePartBody(part0); b3BodyId body1 = PartBodies.contains(part1.get()) ? PartBodies[part1.get()] : CreatePartBody(part1); b3JointId joint = it->CreateJoint(&World, body0, body1); ConstraintJoints[it.get()] = joint; + it->LeJoint = joint; + + it->UpdateJoint(); return joint; } + void WorldRoot::DestroyConstraintJoint(Constraint *it) { + auto found = ConstraintJoints.find(it); + if (found == ConstraintJoints.end()) return; + + b3DestroyJoint(found->second, true); + ConstraintJoints.erase(found); + it->LeJoint = b3_nullJointId; + } + + void WorldRoot::RebuildConstraintJoint(std::shared_ptr it) { + DestroyConstraintJoint(it.get()); + CreateConstraintJoint(it); + } + WorldRoot::WorldRoot() { b3WorldDef worldDefinition = b3DefaultWorldDef(); worldDefinition.enableSleep = true; @@ -76,8 +94,10 @@ namespace gargantuan { World = b3CreateWorld(&worldDefinition); Destroying->Once([this](std::monostate _) { - for (auto &[_, joint] : ConstraintJoints) { + for (auto &[constraint, joint] : ConstraintJoints) { b3DestroyJoint(joint, false); + constraint->LeJoint = b3_nullJointId; + constraint->RequestRebuild = nullptr; }; ConstraintJoints.clear(); @@ -93,19 +113,23 @@ namespace gargantuan { if (auto it = std::dynamic_pointer_cast(instance)) { CreatePartBody(it); } else if (auto it = std::dynamic_pointer_cast(instance)) { + std::weak_ptr weak = it; + it->RequestRebuild = [this, weak]() { + if (auto constraint = weak.lock()) RebuildConstraintJoint(constraint); + }; + CreateConstraintJoint(it); } }); DescendantRemoved->Connect([this](std::shared_ptr instance) { - if (auto it = std::static_pointer_cast(instance); it && PartBodies.contains(it.get())) { + if (auto it = std::dynamic_pointer_cast(instance); it && PartBodies.contains(it.get())) { erase(Parts, it); b3DestroyBody(this->PartBodies[it.get()]); this->PartBodies.erase(it.get()); - } else if (auto it = std::static_pointer_cast(instance); - it && ConstraintJoints.contains(it.get())) { - b3DestroyJoint(this->ConstraintJoints[it.get()], true); - this->ConstraintJoints.erase(it.get()); + } else if (auto it = std::dynamic_pointer_cast(instance)) { + DestroyConstraintJoint(it.get()); + it->RequestRebuild = nullptr; } }); }; @@ -134,6 +158,10 @@ namespace gargantuan { b3Body_ApplyLinearImpulseToCenter(body, AsB3Vec3(part->AccumulatedImpulse), true); part->AccumulatedImpulse = {}; } + if (auto body = PartBodies[part]; part->AccumulatedAngularImpulse.value > ZERO_VEC3) { + b3Body_ApplyAngularImpulse(body, AsB3Vec3(part->AccumulatedAngularImpulse), true); + part->AccumulatedAngularImpulse = {}; + } } for (int i = 0; i < contactEvents.beginCount; ++i) {