diff --git a/README.md b/README.md index 30fd1b7..f9367b6 100644 --- a/README.md +++ b/README.md @@ -34,4 +34,6 @@ These new details will be listed under the `"sable"` key and have the following - `transitionSpeed: number` - `preventSelfLift: boolean` - `scaleWithGravity: boolean` - - `scaleWithPressure: boolean` \ No newline at end of file + - `scaleWithPressure: boolean` + + \ No newline at end of file diff --git a/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java b/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java index 4a38ec6..3c050ce 100644 --- a/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java +++ b/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java @@ -4,8 +4,10 @@ import dan200.computercraft.api.detail.VanillaDetailRegistries; import dev.ryanhcode.sable.physics.config.block_properties.PhysicsBlockPropertyHelper; import dev.ryanhcode.sable.physics.floating_block.FloatingBlockMaterial; +import dev.ryanhcode.sable.platform.SableEventPlatform; import io.github.techtastic.cc_sable.apis.AerodynamicsAPI; import io.github.techtastic.cc_sable.apis.SubLevelAPI; +import io.github.techtastic.cc_sable.util.Physicker; import java.util.Map; @@ -16,6 +18,8 @@ public static void init() { ComputerCraftAPI.registerAPIFactory(SubLevelAPI::new); ComputerCraftAPI.registerAPIFactory(AerodynamicsAPI::new); + SableEventPlatform.INSTANCE.onPhysicsTick(Physicker::onPhysicsTick); + VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider((data, object) -> data.put("sable", Map.of( "mass", PhysicsBlockPropertyHelper.getMass(object.level(), object.pos(), object.state()), "friction", PhysicsBlockPropertyHelper.getFriction(object.state()), diff --git a/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java b/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java index 6cbf206..fceede7 100644 --- a/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java +++ b/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java @@ -5,16 +5,11 @@ import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaFunction; import dev.ryanhcode.sable.Sable; -import dev.ryanhcode.sable.api.SubLevelHelper; -import dev.ryanhcode.sable.api.sublevel.SubLevelContainer; import dev.ryanhcode.sable.companion.SableCompanion; -import dev.ryanhcode.sable.companion.SubLevelAccess; -import dev.ryanhcode.sable.physics.config.dimension_physics.DimensionPhysicsData; import dev.ryanhcode.sable.sublevel.ServerSubLevel; import dev.ryanhcode.sable.sublevel.SubLevel; import io.github.techtastic.cc_sable.util.CCSableUtils; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.Level; +import io.github.techtastic.cc_sable.util.Physicker; import org.joml.Vector3d; import org.joml.Vector3dc; import org.jspecify.annotations.NonNull; @@ -36,7 +31,7 @@ private ServerSubLevel getSublevel() throws LuaException { @Override public final String @NonNull [] getNames() { - return new String[] {"sublevel"}; + return new String[]{"sublevel"}; } @LuaFunction(mainThread = true) @@ -111,4 +106,16 @@ public final Map> getInertiaTensor() throws LuaExcep public final Map> getInverseInertiaTensor() throws LuaException { return CCSableUtils.toLua(getSublevel().getMassTracker().getInverseInertiaTensor()); } + + @LuaFunction + public final Object getForces() throws LuaException { + try { + Object o = Physicker.requestForces(system, getSublevel()); + if (o == null) + throw new LuaException("timeout"); + return o; + } catch (InterruptedException e) { + throw new LuaException("interrupted"); + } + } } diff --git a/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java new file mode 100644 index 0000000..f6e0d61 --- /dev/null +++ b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java @@ -0,0 +1,103 @@ +package io.github.techtastic.cc_sable.util; + +import dan200.computercraft.api.lua.IComputerSystem; +import dev.ryanhcode.sable.Sable; +import dev.ryanhcode.sable.api.physics.force.ForceGroup; +import dev.ryanhcode.sable.api.physics.force.ForceGroups; +import dev.ryanhcode.sable.api.physics.force.QueuedForceGroup; +import dev.ryanhcode.sable.companion.math.Pose3d; +import dev.ryanhcode.sable.physics.config.dimension_physics.DimensionPhysicsData; +import dev.ryanhcode.sable.sublevel.ServerSubLevel; +import dev.ryanhcode.sable.sublevel.SubLevel; +import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem; +import net.minecraft.resources.ResourceLocation; +import org.joml.Vector3d; +import org.joml.Vector3dc; +import org.jspecify.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.*; + +/** + * Track "force requests" for sublevels because Sable be like "waaah no you cant request forces mid-tick" + */ +public class Physicker { + private static ConcurrentMap REQUESTS = new ConcurrentHashMap<>(); + + public static void onPhysicsTick(SubLevelPhysicsSystem activeSystem, double v) { + for (Map.Entry entry : REQUESTS.entrySet()) { + if(!entry.getValue().subLevel().getLevel().equals(activeSystem.getLevel())) + continue; + REQUESTS.remove(entry.getKey()); + try { + entry.getValue().receiver.put(createValueMap(entry.getValue().subLevel)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + + public static Object requestForces(IComputerSystem system, ServerSubLevel sublevel) throws InterruptedException { + int id = system.getID(); + BlockingQueue receiver = new ArrayBlockingQueue<>(1); + REQUESTS.put(id, new Request(sublevel, receiver)); + sublevel.enableIndividualQueuedForcesTracking(true); + Object retVal = receiver.poll(100, TimeUnit.MILLISECONDS); + sublevel.enableIndividualQueuedForcesTracking(false); + return retVal; + } + + private record Request(ServerSubLevel subLevel, BlockingQueue receiver) { } + + private static Map>> createValueMap(ServerSubLevel level) { + Map>> returnValue = new HashMap<>(); + Map forceGroups = level.getQueuedForceGroups(); + + { + final Vector3dc centerOfMass = level.getMassTracker().getCenterOfMass(); + final Pose3d pose = level.logicalPose(); + final Vector3d localGravity = pose.transformNormalInverse(DimensionPhysicsData.getGravity(level.getLevel())).mul(level.getMassTracker().getMass()); + + Map> force = new HashMap<>(); + Map gravityMap = new HashMap<>(); + gravityMap.put("position", CCSableUtils.toLua(centerOfMass)); + gravityMap.put("force", CCSableUtils.toLua(localGravity)); + + force.put(1, gravityMap); + returnValue.put(Sable.sablePath("gravity").toString() /* thanks veil (?) for making life difficult @todo: hardcoded because veil is being annoying */, force); + } + + if (forceGroups == null || forceGroups.isEmpty()) + return returnValue; + + SubLevelPhysicsSystem physicsSystem = SubLevelPhysicsSystem.get(level.getLevel()); + final double timeStep = 1.0 / 20.0 / physicsSystem.getConfig().substepsPerTick; // see DiagramEntity of aeronautics + + for (Map.Entry set : forceGroups.entrySet()) { + Map> force = new HashMap<>(); + + List pointForces = set.getValue().getRecordedPointForces(); + if (pointForces.isEmpty()) + continue; + + + int index = 1; // not a big fan of for loops + for (QueuedForceGroup.PointForce pointForce : pointForces) { + + Map pointForceLua = new HashMap<>(); + + pointForceLua.put("position", CCSableUtils.toLua(pointForce.point())); + pointForceLua.put("force", CCSableUtils.toLua(new Vector3d(pointForce.force()).div(timeStep))); + + force.put(index, pointForceLua); + index++; + } + + ResourceLocation key = ForceGroups.REGISTRY.getKey(set.getKey()); + returnValue.put(key.toString(), force); + } + + return returnValue; + } +} diff --git a/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua b/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua index d950f53..dcfc3f5 100644 --- a/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua +++ b/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua @@ -75,6 +75,12 @@ -- @treturn matrix the inverse inertia tensor of the Sub-Level -- @raise This method errors if there is no Sub-Level associated with the computer. +--- Gets all forces acting upon the sublevel. +-- @function getForces +-- @treturn table All forces in the format { TYPE = { [1] = {position, force}, ... }, ... } +-- @raise This method errors if there is no Sub-Level associated with the computer. + + if not sublevel then error("Cannot load Sub-Level API on computer") end @@ -111,6 +117,22 @@ for k,v in pairs(native) do end return matrix.from2DArray(result) end + elseif k == "getForces" then + env[k] = function() + local result, err = v() + if err then + error(err) + end + + for name, force in pairs(result) do + for i, point in ipairs(force) do + point.position = vector.new(point.position.x, point.position.y, point.position.z) + point.force = vector.new(point.force.x, point.force.y, point.force.z) + end + end + + return result + end else env[k] = v end