diff --git a/Dockerfile b/Dockerfile old mode 100644 new mode 100755 index adbc671..e995579 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN apt-get update && apt-get install -y wget COPY build/libs/*-all.jar /app/parkourtag.jar COPY run/maps /app/maps +COPY run/natives /app/natives ENTRYPOINT ["java"] CMD ["-jar", "/app/parkourtag.jar"] diff --git a/build.gradle.kts b/build.gradle.kts old mode 100644 new mode 100755 index 152afaf..aa74ce5 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,8 @@ dependencies { implementation("dev.hollowcube:polar:1.11.1") implementation("net.kyori:adventure-text-minimessage:4.16.0") + + implementation("com.github.stephengold:Libbulletjme:21.2.1") } java { diff --git a/run/natives/Linux64ReleaseSp_libbulletjme.so b/run/natives/Linux64ReleaseSp_libbulletjme.so new file mode 100755 index 0000000..641abe6 Binary files /dev/null and b/run/natives/Linux64ReleaseSp_libbulletjme.so differ diff --git a/src/main/java/dev/emortal/minestom/parkourtag/Main.java b/src/main/java/dev/emortal/minestom/parkourtag/Main.java old mode 100644 new mode 100755 index 67903df..2264f8e --- a/src/main/java/dev/emortal/minestom/parkourtag/Main.java +++ b/src/main/java/dev/emortal/minestom/parkourtag/Main.java @@ -1,14 +1,19 @@ package dev.emortal.minestom.parkourtag; +import com.jme3.system.NativeLibraryLoader; import dev.emortal.minestom.gamesdk.MinestomGameServer; import dev.emortal.minestom.gamesdk.config.GameSdkConfig; import dev.emortal.minestom.parkourtag.commands.CreditsCommand; import dev.emortal.minestom.parkourtag.map.MapManager; import net.minestom.server.MinecraftServer; +import java.io.File; + public final class Main { public static void main(String[] args) { + NativeLibraryLoader.loadLibbulletjme(true, new File("natives/"), "Release", "Sp"); + MinestomGameServer server = MinestomGameServer.create(() -> { MapManager mapManager = new MapManager(); diff --git a/src/main/java/dev/emortal/minestom/parkourtag/ParkourTagGame.java b/src/main/java/dev/emortal/minestom/parkourtag/ParkourTagGame.java old mode 100644 new mode 100755 index 0488b79..39be5cf --- a/src/main/java/dev/emortal/minestom/parkourtag/ParkourTagGame.java +++ b/src/main/java/dev/emortal/minestom/parkourtag/ParkourTagGame.java @@ -5,11 +5,12 @@ import dev.emortal.minestom.gamesdk.config.GameCreationInfo; import dev.emortal.minestom.gamesdk.game.Game; import dev.emortal.minestom.gamesdk.util.GameWinLoseMessages; -import dev.emortal.minestom.parkourtag.listeners.parkourtag.ParkourTagAttackListener; -import dev.emortal.minestom.parkourtag.listeners.parkourtag.ParkourTagDoubleJumpListener; -import dev.emortal.minestom.parkourtag.listeners.parkourtag.ParkourTagTickListener; +import dev.emortal.minestom.parkourtag.listeners.ParkourTagAttackListener; +import dev.emortal.minestom.parkourtag.listeners.ParkourTagDoubleJumpListener; +import dev.emortal.minestom.parkourtag.listeners.ParkourTagTickListener; import dev.emortal.minestom.parkourtag.map.LoadedMap; import dev.emortal.minestom.parkourtag.map.MapData; +import dev.emortal.minestom.parkourtag.physics.MinecraftPhysicsHandler; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.Metrics; import net.kyori.adventure.bossbar.BossBar; @@ -454,10 +455,16 @@ private void victory(Set winners) { return map; } + public MinecraftPhysicsHandler getPhysicsHandler() { + return getMap().physicsHandler(); + } + @Override public void cleanUp() { this.map.instance().scheduleNextTick(MinecraftServer.getInstanceManager()::unregisterInstance); + this.getPhysicsHandler().cleanup(); + MinecraftServer.getBossBarManager().destroyBossBar(this.bossBar); if (this.gameTimerTask != null) this.gameTimerTask.cancel(); } diff --git a/src/main/java/dev/emortal/minestom/parkourtag/listeners/parkourtag/ParkourTagAttackListener.java b/src/main/java/dev/emortal/minestom/parkourtag/listeners/ParkourTagAttackListener.java old mode 100644 new mode 100755 similarity index 85% rename from src/main/java/dev/emortal/minestom/parkourtag/listeners/parkourtag/ParkourTagAttackListener.java rename to src/main/java/dev/emortal/minestom/parkourtag/listeners/ParkourTagAttackListener.java index d4cddd0..d66894b --- a/src/main/java/dev/emortal/minestom/parkourtag/listeners/parkourtag/ParkourTagAttackListener.java +++ b/src/main/java/dev/emortal/minestom/parkourtag/listeners/ParkourTagAttackListener.java @@ -1,8 +1,8 @@ -package dev.emortal.minestom.parkourtag.listeners.parkourtag; +package dev.emortal.minestom.parkourtag.listeners; import dev.emortal.minestom.parkourtag.GameStage; import dev.emortal.minestom.parkourtag.ParkourTagGame; -import dev.emortal.minestom.parkourtag.utils.FireworkUtils; +import dev.emortal.minestom.parkourtag.physics.PlayerRagdoll; import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -10,13 +10,12 @@ import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.title.Title; -import net.minestom.server.color.Color; +import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.GameMode; import net.minestom.server.entity.Player; import net.minestom.server.event.Event; import net.minestom.server.event.EventNode; import net.minestom.server.event.entity.EntityAttackEvent; -import net.minestom.server.item.component.FireworkExplosion; import net.minestom.server.network.packet.server.play.HitAnimationPacket; import net.minestom.server.sound.SoundEvent; import net.minestom.server.tag.Tag; @@ -89,6 +88,9 @@ public static void registerListener(EventNode eventNode, ParkourTagGame g target.setAutoViewable(false); target.setGlowing(false); + Vec impulse = attacker.getPosition().direction().mul(60); + PlayerRagdoll.spawnRagdollWithImpulse(game.getPhysicsHandler(), target, target.getPosition(), impulse); + game.getTaggers().remove(target); game.getGoons().remove(target); target.setTeam(ParkourTagGame.DEAD_TEAM); @@ -105,14 +107,14 @@ public static void registerListener(EventNode eventNode, ParkourTagGame g target.setVelocity(attacker.getPosition().direction().mul(15.0)); // Firework death effect - FireworkExplosion randomColorEffect = new FireworkExplosion( - FireworkExplosion.Shape.LARGE_BALL, - List.of(new Color(java.awt.Color.HSBtoRGB(random.nextFloat(), 1f, 1f))), - List.of(), - false, - false - ); - FireworkUtils.showFirework(game.getPlayers(), e.getInstance(), target.getPosition().add(0, 1.5, 0), List.of(randomColorEffect)); +// FireworkExplosion randomColorEffect = new FireworkExplosion( +// FireworkExplosion.Shape.LARGE_BALL, +// List.of(new Color(java.awt.Color.HSBtoRGB(random.nextFloat(), 1f, 1f))), +// List.of(), +// false, +// false +// ); +// FireworkUtils.showFirework(game.getPlayers(), e.getInstance(), target.getPosition().add(0, 1.5, 0), List.of(randomColorEffect)); // Check for win with new alive count game.checkPlayerCounts(); diff --git a/src/main/java/dev/emortal/minestom/parkourtag/listeners/parkourtag/ParkourTagDoubleJumpListener.java b/src/main/java/dev/emortal/minestom/parkourtag/listeners/ParkourTagDoubleJumpListener.java old mode 100644 new mode 100755 similarity index 97% rename from src/main/java/dev/emortal/minestom/parkourtag/listeners/parkourtag/ParkourTagDoubleJumpListener.java rename to src/main/java/dev/emortal/minestom/parkourtag/listeners/ParkourTagDoubleJumpListener.java index 5304fa6..c773e91 --- a/src/main/java/dev/emortal/minestom/parkourtag/listeners/parkourtag/ParkourTagDoubleJumpListener.java +++ b/src/main/java/dev/emortal/minestom/parkourtag/listeners/ParkourTagDoubleJumpListener.java @@ -1,4 +1,4 @@ -package dev.emortal.minestom.parkourtag.listeners.parkourtag; +package dev.emortal.minestom.parkourtag.listeners; import dev.emortal.minestom.parkourtag.GameStage; import dev.emortal.minestom.parkourtag.ParkourTagGame; diff --git a/src/main/java/dev/emortal/minestom/parkourtag/listeners/parkourtag/ParkourTagTickListener.java b/src/main/java/dev/emortal/minestom/parkourtag/listeners/ParkourTagTickListener.java old mode 100644 new mode 100755 similarity index 95% rename from src/main/java/dev/emortal/minestom/parkourtag/listeners/parkourtag/ParkourTagTickListener.java rename to src/main/java/dev/emortal/minestom/parkourtag/listeners/ParkourTagTickListener.java index 0e9f7a4..364be16 --- a/src/main/java/dev/emortal/minestom/parkourtag/listeners/parkourtag/ParkourTagTickListener.java +++ b/src/main/java/dev/emortal/minestom/parkourtag/listeners/ParkourTagTickListener.java @@ -1,10 +1,10 @@ -package dev.emortal.minestom.parkourtag.listeners.parkourtag; +package dev.emortal.minestom.parkourtag.listeners; import dev.emortal.minestom.parkourtag.GameStage; import dev.emortal.minestom.parkourtag.ParkourTagGame; import dev.emortal.minestom.parkourtag.map.MapManager; import dev.emortal.minestom.parkourtag.utils.NoTickEntity; -import dev.emortal.minestom.parkourtag.utils.Quaternion; +import dev.emortal.minestom.parkourtag.utils.PTQuaternion; import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -84,6 +84,7 @@ private static void createRailParticle(double x, double y, double z, ParkourTagG ParticlePacket packet = new ParticlePacket(Particle.SNOWFLAKE, true, x + Math.sin(tick * spinSpeed) * spinScale, y, z + Math.cos(tick * spinSpeed) * spinScale, 0f, 0.5f, 0f, 1, 0); + if (game.getGameStage() == GameStage.PRE_GAME) return; game.sendGroupedPacket(packet); }).repeat(TaskSchedule.tick(1)).schedule(); } @@ -100,7 +101,7 @@ private static void createRailTextDisplay(double x, double y, double z, int dela meta.setText(off); meta.setBackgroundColor(0); meta.setScale(new Vec(7.5)); - Quaternion quaternion = new Quaternion(new Vec(1, 0, 0), Math.toRadians(90)); + PTQuaternion quaternion = new PTQuaternion(new Vec(1, 0, 0), Math.toRadians(90)); meta.setLeftRotation(new float[]{(float) quaternion.getW(), (float) quaternion.getX(), (float) quaternion.getY(), (float) quaternion.getZ()}); entity.setTag(onTag, false); diff --git a/src/main/java/dev/emortal/minestom/parkourtag/map/LoadedMap.java b/src/main/java/dev/emortal/minestom/parkourtag/map/LoadedMap.java old mode 100644 new mode 100755 index 4127a61..6b3a0b7 --- a/src/main/java/dev/emortal/minestom/parkourtag/map/LoadedMap.java +++ b/src/main/java/dev/emortal/minestom/parkourtag/map/LoadedMap.java @@ -1,7 +1,8 @@ package dev.emortal.minestom.parkourtag.map; +import dev.emortal.minestom.parkourtag.physics.MinecraftPhysicsHandler; import net.minestom.server.instance.Instance; import org.jetbrains.annotations.NotNull; -public record LoadedMap(@NotNull Instance instance, @NotNull MapData mapData) { +public record LoadedMap(@NotNull Instance instance, @NotNull MapData mapData, @NotNull MinecraftPhysicsHandler physicsHandler) { } diff --git a/src/main/java/dev/emortal/minestom/parkourtag/map/MapManager.java b/src/main/java/dev/emortal/minestom/parkourtag/map/MapManager.java old mode 100644 new mode 100755 index 75fb5fa..f458cae --- a/src/main/java/dev/emortal/minestom/parkourtag/map/MapManager.java +++ b/src/main/java/dev/emortal/minestom/parkourtag/map/MapManager.java @@ -2,10 +2,13 @@ import com.google.gson.Gson; import com.google.gson.stream.JsonReader; -import dev.emortal.minestom.parkourtag.utils.NoopChunkLoader; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import dev.emortal.minestom.parkourtag.physics.MinecraftPhysicsHandler; +import dev.emortal.minestom.parkourtag.physics.worldmesh.ChunkMesher; import net.hollowcube.polar.PolarLoader; import net.minestom.server.MinecraftServer; import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.IChunkLoader; import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceContainer; import net.minestom.server.registry.DynamicRegistry; @@ -67,18 +70,25 @@ public MapManager() { instance.setTimeSynchronizationTicks(0); // Do some preloading! + List chunkMeshes = new ArrayList<>(); List> futures = new ArrayList<>(); for (int x = -CHUNK_LOADING_RADIUS; x < CHUNK_LOADING_RADIUS; x++) { for (int z = -CHUNK_LOADING_RADIUS; z < CHUNK_LOADING_RADIUS; z++) { - futures.add(instance.loadChunk(x, z)); + CompletableFuture future = instance.loadChunk(x, z); + futures.add(future); + future.thenAccept((chunk) -> { + PhysicsCollisionObject obj = ChunkMesher.createChunk(chunk); + if (obj != null) chunkMeshes.add(obj); + }); } } + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenRun(() -> { // Replace polar loader after all chunks are loaded to save memory - instance.setChunkLoader(new NoopChunkLoader()); + instance.setChunkLoader(IChunkLoader.noop()); }); - maps.put(mapName, new PreLoadedMap(instance, spawns)); + maps.put(mapName, new PreLoadedMap(instance, spawns, chunkMeshes)); } catch (IOException exception) { throw new UncheckedIOException(exception); } @@ -108,16 +118,18 @@ public MapManager() { return map.load(randomMapId); } - private record PreLoadedMap(@NotNull InstanceContainer rootInstance, @NotNull MapData mapData) { + private record PreLoadedMap(@NotNull InstanceContainer rootInstance, @NotNull MapData mapData, @NotNull List chunkMeshes) { @NotNull LoadedMap load(@NotNull String mapId) { Instance shared = MinecraftServer.getInstanceManager().createSharedInstance(this.rootInstance()); + MinecraftPhysicsHandler physicsHandler = new MinecraftPhysicsHandler(shared, chunkMeshes); + shared.setTag(MAP_ID_TAG, mapId); shared.setTimeRate(0); shared.setTimeSynchronizationTicks(0); - return new LoadedMap(shared, this.mapData()); + return new LoadedMap(shared, this.mapData(), physicsHandler); } } } diff --git a/src/main/java/dev/emortal/minestom/parkourtag/physics/MinecraftPhysicsHandler.java b/src/main/java/dev/emortal/minestom/parkourtag/physics/MinecraftPhysicsHandler.java new file mode 100755 index 0000000..d94bfb8 --- /dev/null +++ b/src/main/java/dev/emortal/minestom/parkourtag/physics/MinecraftPhysicsHandler.java @@ -0,0 +1,75 @@ +package dev.emortal.minestom.parkourtag.physics; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.math.Vector3f; +import dev.emortal.minestom.parkourtag.physics.objects.MinecraftPhysicsObject; +import net.minestom.server.instance.Instance; +import net.minestom.server.timer.TaskSchedule; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public class MinecraftPhysicsHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(MinecraftPhysicsHandler.class); + + private @Nullable PhysicsSpace physicsSpace; + + private final @NotNull List objects = new CopyOnWriteArrayList<>(); + + public MinecraftPhysicsHandler(@NotNull Instance instance, @NotNull List chunkMeshes) { + + // Wrap in scheduler to avoid "WARNING: invoked from wrong thread" + instance.scheduleNextTick((i) -> { + System.out.println("Physics space no longer null"); + this.physicsSpace = new PhysicsSpace(PhysicsSpace.BroadphaseType.DBVT); + + // Default: -9.81f + // Minecraft: -31.36f + this.physicsSpace.setGravity(new Vector3f(0, -17f, 0)); + + for (PhysicsCollisionObject chunkMesh : chunkMeshes) { + this.physicsSpace.add(chunkMesh); + } + }); + + instance.scheduler().buildTask(new Runnable() { + long lastRan = System.nanoTime(); + @Override + public void run() { + long diff = System.nanoTime() - lastRan; + float deltaTime = diff / 1_000_000_000f; + + lastRan = System.nanoTime(); + update(deltaTime); + } + }).delay(TaskSchedule.tick(1)).repeat(TaskSchedule.tick(1)).schedule(); + } + + public void update(float delta) { + if (physicsSpace == null) return; + + physicsSpace.update(delta); + for (MinecraftPhysicsObject object : objects) { + object.updateEntity(); + } + } + + public void cleanup() { + if (physicsSpace == null) return; + physicsSpace.destroy(); + physicsSpace = null; + } + + public void addObject(MinecraftPhysicsObject object) { + objects.add(object); + } + + public PhysicsSpace getPhysicsSpace() { + return physicsSpace; + } +} diff --git a/src/main/java/dev/emortal/minestom/parkourtag/physics/PlayerDisplayPart.java b/src/main/java/dev/emortal/minestom/parkourtag/physics/PlayerDisplayPart.java new file mode 100644 index 0000000..793ad51 --- /dev/null +++ b/src/main/java/dev/emortal/minestom/parkourtag/physics/PlayerDisplayPart.java @@ -0,0 +1,34 @@ +package dev.emortal.minestom.parkourtag.physics; + +import com.jme3.math.Vector3f; +import dev.emortal.minestom.parkourtag.physics.objects.RagdollPhysics; + +public enum PlayerDisplayPart { + HEAD(0, 9, RagdollPhysics.HEAD_SIZE), + RIGHT_ARM(-1024, 12, RagdollPhysics.LIMB_SIZE), + LEFT_ARM(-2048, 11, RagdollPhysics.LIMB_SIZE), + TORSO(-3072, 4, RagdollPhysics.TORSO_SIZE), + RIGHT_LEG(-4096, 12, RagdollPhysics.LIMB_SIZE), + LEFT_LEG(-5120, 13, RagdollPhysics.LIMB_SIZE); + + private final double yTranslation; + private final int customModelData; + private final Vector3f size; + PlayerDisplayPart(double yTranslation, int customModelData, Vector3f size) { + this.yTranslation = yTranslation; + this.customModelData = customModelData; + this.size = size; + } + + public double getYTranslation() { + return yTranslation; + } + + public int getCustomModelData() { + return customModelData; + } + + public Vector3f getSize() { + return size; + } +} diff --git a/src/main/java/dev/emortal/minestom/parkourtag/physics/PlayerRagdoll.java b/src/main/java/dev/emortal/minestom/parkourtag/physics/PlayerRagdoll.java new file mode 100644 index 0000000..7ded0c3 --- /dev/null +++ b/src/main/java/dev/emortal/minestom/parkourtag/physics/PlayerRagdoll.java @@ -0,0 +1,50 @@ +package dev.emortal.minestom.parkourtag.physics; + +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import dev.emortal.minestom.parkourtag.physics.objects.MinecraftPhysicsObject; +import dev.emortal.minestom.parkourtag.physics.objects.RagdollPhysics; +import dev.emortal.minestom.parkourtag.utils.PTQuaternion; +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.Player; +import net.minestom.server.instance.Instance; +import net.minestom.server.timer.TaskSchedule; + +import java.util.concurrent.ThreadLocalRandom; + +public class PlayerRagdoll { + + public static void spawnRagdollWithImpulse(MinecraftPhysicsHandler physicsHandler, Player player, Pos pos, Point impulse) { + Instance instance = player.getInstance(); + + pos = pos.add(0, 1.5, 0); + + float yaw = -pos.yaw() + 180; + double yawRad = Math.toRadians(yaw); + PTQuaternion yawQuat = new PTQuaternion(new Vec(0, 1, 0), yawRad); + Quaternion yawQuat2 = new Quaternion((float) yawQuat.getX(), (float) yawQuat.getY(), (float) yawQuat.getZ(), (float) yawQuat.getW()); + + MinecraftPhysicsObject torso = new RagdollPhysics(physicsHandler, player,null, PlayerDisplayPart.TORSO, instance, MinecraftPhysicsObject.toVector3(pos), yawQuat2, 1); + MinecraftPhysicsObject head = new RagdollPhysics(physicsHandler, player, torso.getRigidBody(), PlayerDisplayPart.HEAD, instance, MinecraftPhysicsObject.toVector3(pos.add(new Vec(0, 0.62, 0).rotateAroundY(yawRad).mul(RagdollPhysics.PLAYER_SIZE))), yawQuat2, 1); + MinecraftPhysicsObject rightArm = new RagdollPhysics(physicsHandler, player, torso.getRigidBody(), PlayerDisplayPart.RIGHT_ARM, instance, MinecraftPhysicsObject.toVector3(pos.add(new Vec(0.37, 0, 0).rotateAroundY(yawRad).mul(RagdollPhysics.PLAYER_SIZE))), yawQuat2, 1); + MinecraftPhysicsObject leftArm = new RagdollPhysics(physicsHandler, player, torso.getRigidBody(), PlayerDisplayPart.LEFT_ARM, instance, MinecraftPhysicsObject.toVector3(pos.add(new Vec(-0.37, 0, 0).rotateAroundY(yawRad).mul(RagdollPhysics.PLAYER_SIZE))), yawQuat2, 1); + MinecraftPhysicsObject rightLeg = new RagdollPhysics(physicsHandler, player, torso.getRigidBody(), PlayerDisplayPart.RIGHT_LEG, instance, MinecraftPhysicsObject.toVector3(pos.add(new Vec(0.13, -0.72, 0).rotateAroundY(yawRad).mul(RagdollPhysics.PLAYER_SIZE))), yawQuat2, 1); + MinecraftPhysicsObject leftLeg = new RagdollPhysics(physicsHandler, player, torso.getRigidBody(), PlayerDisplayPart.LEFT_LEG, instance, MinecraftPhysicsObject.toVector3(pos.add(new Vec(-0.13, -0.72, 0).rotateAroundY(yawRad).mul(RagdollPhysics.PLAYER_SIZE))), yawQuat2, 1); + + ThreadLocalRandom rand = ThreadLocalRandom.current(); + torso.getRigidBody().applyImpulse(MinecraftPhysicsObject.toVector3(impulse), new Vector3f(rand.nextFloat(-1f, 1f), rand.nextFloat(-1.5f, 1.5f), rand.nextFloat(-1f, 1f))); + + instance.scheduler().buildTask(() -> { + torso.destroy(); + head.destroy(); + rightArm.destroy(); + leftLeg.destroy(); + rightLeg.destroy(); + leftArm.destroy(); + }).delay(TaskSchedule.tick(20 * 10)).schedule(); + + } + +} diff --git a/src/main/java/dev/emortal/minestom/parkourtag/physics/objects/MinecraftPhysicsObject.java b/src/main/java/dev/emortal/minestom/parkourtag/physics/objects/MinecraftPhysicsObject.java new file mode 100755 index 0000000..bf6bbcf --- /dev/null +++ b/src/main/java/dev/emortal/minestom/parkourtag/physics/objects/MinecraftPhysicsObject.java @@ -0,0 +1,37 @@ +package dev.emortal.minestom.parkourtag.physics.objects; + +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface MinecraftPhysicsObject { + + PhysicsRigidBody getRigidBody(); + + void updateEntity(); + + @Nullable Entity getEntity(); + + void destroy(); + + static @NotNull Vector3f toVector3(Point vec) { + return new Vector3f((float)vec.x(), (float)vec.y(), (float)vec.z()); + } + static float[] toFloats(Quaternion rotation) { + return new float[] { rotation.getX(), rotation.getY(), rotation.getZ(), rotation.getW() }; + } + + static @NotNull Vec toVec(Vector3f vector3) { + return new Vec(vector3.x, vector3.y, vector3.z); + } + static @NotNull Pos toPos(Vector3f vector3) { + return new Pos(vector3.x, vector3.y, vector3.z); + } + +} diff --git a/src/main/java/dev/emortal/minestom/parkourtag/physics/objects/RagdollPhysics.java b/src/main/java/dev/emortal/minestom/parkourtag/physics/objects/RagdollPhysics.java new file mode 100755 index 0000000..61f5761 --- /dev/null +++ b/src/main/java/dev/emortal/minestom/parkourtag/physics/objects/RagdollPhysics.java @@ -0,0 +1,161 @@ +package dev.emortal.minestom.parkourtag.physics.objects; + +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.joints.ConeJoint; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import dev.emortal.minestom.parkourtag.physics.MinecraftPhysicsHandler; +import dev.emortal.minestom.parkourtag.physics.PlayerDisplayPart; +import dev.emortal.minestom.parkourtag.utils.NoTickEntity; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EntityType; +import net.minestom.server.entity.Player; +import net.minestom.server.entity.PlayerSkin; +import net.minestom.server.entity.metadata.display.ItemDisplayMeta; +import net.minestom.server.instance.Instance; +import net.minestom.server.item.ItemComponent; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.minestom.server.item.component.HeadProfile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class RagdollPhysics implements MinecraftPhysicsObject { + private static final PlayerSkin PLACEHOLDER_SKIN = new PlayerSkin("ewogICJ0aW1lc3RhbXAiIDogMTcyNDgwNjQxNDU1OCwKICAicHJvZmlsZUlkIiA6ICI3YmQ1YjQ1OTFlNmI0NzUzODI3NDFmYmQyZmU5YTRkNSIsCiAgInByb2ZpbGVOYW1lIiA6ICJlbW9ydGFsZGV2IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2Q1YmQ1MDViMTBkM2I2YWZjOGY3NTI1OGIwMWE3YzQwMjFjNjFkODFkMjA1M2I4MDg4ZWUyYjhjMTA0NDE4OTMiCiAgICB9LAogICAgIkNBUEUiIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2NkOWQ4MmFiMTdmZDkyMDIyZGJkNGE4NmNkZTRjMzgyYTc1NDBlMTE3ZmFlN2I5YTI4NTM2NTg1MDVhODA2MjUiCiAgICB9CiAgfQp9", "vuqasGHA0u5lehBFVjdYLgFaQvxrLpOS2MO0XH+ATrLhv68022JiTHGnXdz1QFN5kXWQoo4IOoTFC2fAxcPctSFjxL7WTnPsQlwHAZgOuWA0xbyS/m9/2Lmz+bLaL1+dSe6rWxI/j2gGjMgw7Ugy2RsBmg2yTHu4vIT081ntuRjFCn3UaOzRvgbYXGJDnMs3C5nVKKtvbvzJXBonxZHqznJeY7dnRxK2G9Hp0Uu++cPcJ2G3nJRbtAuh4jn3v4gDhA+hbLLzonebiqaE7BNcqjKHfShcbPoZlq4FWnJdFVDeQ8WIP7sseAqcW3iRT4noIU5AKOqFt54ejgh1INVK2TquiVIbIsGTbh6tDR1mmMxYa7v0WFINInK7uAZbOvxejfCVJIJUh0/SsP4Wxeg9g8NzutdyKtuMcE3i/2FebPBTm1Mys+zUK5lmKOnQfDkxv1Te9LhR59NOIxROWUTbwhwcLiDyAn3MbjQzvFB1E4uvm92a3u8MjpJvqb2U4OMTkeb4Bx9MNqLSxmhizbrKjBRexRUQKMRBQpEdE6o2oFVax55AKF5GUeQ87LQ1AOeYpvqYllt/ihbVk6aiNApAoVIviUZ01X/Q5h4hGxtnztLa0NatjY09hRRj5/7h/mwSuPRVD46rJUrirU5Doh9uTIQAVYaptwhFqJESw9Jb8OI="); + + public static final Vector3f TORSO_SIZE = new Vector3f(8.0f/16.0f, 12.0f/16.0f, 4.0f/16.0f); + public static final Vector3f HEAD_SIZE = new Vector3f(8.0f/16.0f, 8.0f/16.0f, 8.0f/16.0f); + public static final Vector3f LIMB_SIZE = new Vector3f(4.0f/16.0f, 12.0f/16.0f, 4.0f/16.0f); + public static final double PLAYER_SIZE = 1.0; + + private final Vector3f size; + private Entity entity; + + + private final @NotNull MinecraftPhysicsHandler physicsHandler; + private final @NotNull PhysicsRigidBody rigidBody; + private final @Nullable PhysicsJoint joint; + + private final @NotNull PlayerSkin playerSkin; + + private final @NotNull PlayerDisplayPart part; + + public RagdollPhysics(MinecraftPhysicsHandler physicsHandler, Player spawner, @Nullable PhysicsRigidBody torso, PlayerDisplayPart part, Instance instance, Vector3f position, Quaternion quaternion, float mass) { + this.size = part.getSize().mult(0.5f); + this.physicsHandler = physicsHandler; + this.part = part; + + PlayerSkin playerSkin1; + playerSkin1 = spawner.getSkin(); + if (playerSkin1 == null) playerSkin1 = PLACEHOLDER_SKIN; + this.playerSkin = playerSkin1; + + physicsHandler.addObject(this); + + BoxCollisionShape boxShape = new BoxCollisionShape(size.x, size.y, size.z); + rigidBody = new PhysicsRigidBody(boxShape, mass); + physicsHandler.getPhysicsSpace().addCollisionObject(rigidBody); + rigidBody.setAngularDamping(0.1f); + rigidBody.setLinearDamping(0.3f); + rigidBody.setPhysicsRotation(quaternion); + rigidBody.setPhysicsLocation(position); + + if (torso != null) { + assert (part != PlayerDisplayPart.TORSO); + + // From torso + Vector3f firstThing = switch (part) { + case HEAD -> new Vector3f(0f, TORSO_SIZE.y / 2f, 0f); + case RIGHT_ARM -> new Vector3f(TORSO_SIZE.x / 1.35f, TORSO_SIZE.y / 2f, 0f); + case LEFT_ARM -> new Vector3f(-TORSO_SIZE.x / 1.35f, TORSO_SIZE.y / 2f, 0f); + case RIGHT_LEG -> new Vector3f(0.13f, -TORSO_SIZE.y / 2f, 0f); + case LEFT_LEG -> new Vector3f(-0.13f, -TORSO_SIZE.y / 2f, 0f); + default -> throw new IllegalStateException("Unexpected value: " + part); + }; + // From part + Vector3f secondThing = switch (part) { + case HEAD -> new Vector3f(0f, -HEAD_SIZE.y / 2f, 0f); + case RIGHT_ARM, LEFT_ARM, RIGHT_LEG, LEFT_LEG -> new Vector3f(0f, LIMB_SIZE.y / 2f, 0f); + default -> throw new IllegalStateException("Unexpected value: " + part); + }; + + joint = new ConeJoint(torso, rigidBody, firstThing.mult((float) PLAYER_SIZE), secondThing.mult((float) PLAYER_SIZE)); + physicsHandler.getPhysicsSpace().addJoint(joint); + } else { + joint = null; + } + + entity = spawnEntity(instance); + } + + private Entity spawnEntity(Instance instance) { + Entity entity = new NoTickEntity(EntityType.ITEM_DISPLAY); + + entity.setBoundingBox(size.y, size.y, size.y); + + Transform transform = new Transform(); + rigidBody.getTransform(transform); + + entity.editEntityMeta(ItemDisplayMeta.class, meta -> { + meta.setWidth(2); + meta.setHeight(2); + meta.setDisplayContext(ItemDisplayMeta.DisplayContext.THIRD_PERSON_RIGHT_HAND); + meta.setItemStack(ItemStack.builder(Material.PLAYER_HEAD) + .set(ItemComponent.PROFILE, new HeadProfile(this.playerSkin)) + .customModelData(this.part.getCustomModelData()).build()); + meta.setScale(new Vec(PLAYER_SIZE)); + meta.setTranslation(new Vec(0, this.part.getYTranslation(), 0)); + + meta.setLeftRotation(MinecraftPhysicsObject.toFloats(transform.getRotation())); + meta.setPosRotInterpolationDuration(2); + meta.setTransformationInterpolationDuration(2); + }); + + entity.setInstance(instance, MinecraftPhysicsObject.toVec(transform.getTranslation())); + + return entity; + } + + @Override + public void updateEntity() { + if (entity == null) return; + + Transform transform = new Transform(); + rigidBody.getTransform(transform); + + entity.editEntityMeta(ItemDisplayMeta.class, meta -> { + meta.setNotifyAboutChanges(false); + + meta.setTransformationInterpolationStartDelta(0); + + // size not updated as it doesn't change + meta.setLeftRotation(MinecraftPhysicsObject.toFloats(transform.getRotation())); + meta.setNotifyAboutChanges(true); + }); + + entity.teleport(MinecraftPhysicsObject.toPos(transform.getTranslation())); + } + + @Override + public @NotNull PhysicsRigidBody getRigidBody() { + return rigidBody; + } + @Override + public @Nullable Entity getEntity() { + return entity; + } + + @Override + public void destroy() { + if (entity != null) entity.remove(); + entity = null; + + physicsHandler.getPhysicsSpace().removeCollisionObject(rigidBody); + if (joint != null) physicsHandler.getPhysicsSpace().removeJoint(joint); + } + +} diff --git a/src/main/java/dev/emortal/minestom/parkourtag/physics/worldmesh/ChunkMesher.java b/src/main/java/dev/emortal/minestom/parkourtag/physics/worldmesh/ChunkMesher.java new file mode 100755 index 0000000..13c609e --- /dev/null +++ b/src/main/java/dev/emortal/minestom/parkourtag/physics/worldmesh/ChunkMesher.java @@ -0,0 +1,154 @@ +package dev.emortal.minestom.parkourtag.physics.worldmesh; + +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.collision.shapes.infos.IndexedMesh; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; +import net.minestom.server.MinecraftServer; +import net.minestom.server.collision.Shape; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.Section; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class ChunkMesher { + + private static final BlockFace[] BLOCK_FACES = BlockFace.values(); + + public static @Nullable PhysicsCollisionObject createChunk(Chunk chunk) { + int minY = MinecraftServer.getDimensionTypeRegistry().get(chunk.getInstance().getDimensionType()).minY(); + int maxY = MinecraftServer.getDimensionTypeRegistry().get(chunk.getInstance().getDimensionType()).maxY(); + + return generateChunkCollisionObject(chunk, minY, maxY); + } + + private static @Nullable PhysicsCollisionObject generateChunkCollisionObject(Chunk chunk, int minY, int maxY) { + List vertices = new ArrayList<>(); + + List faces = getChunkFaces(chunk, minY, maxY); + for (Quad face : faces) { + for (Triangle triangle : face.triangles()) { + vertices.add(triangle.get1()); + vertices.add(triangle.get2()); + vertices.add(triangle.get3()); + } + } + + if (vertices.isEmpty()) return null; + + Vector3f[] array = vertices.toArray(new Vector3f[0]); + + int[] indicesArray = new int[array.length]; + for (int i = 0; i < array.length; i++) { + indicesArray[i] = i; + } + + var indexedMesh = new IndexedMesh(array, indicesArray); + var shape = new MeshCollisionShape(true, indexedMesh); + + return new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + } + + private static List getChunkFaces(Chunk chunk, int minY, int maxY) { + int bottomY = maxY; + int topY = minY; + + // Get min and max of current chunk sections to avoid computing on air + List
sections = chunk.getSections(); + for (int i = 0; i < sections.size(); i++) { + Section section = sections.get(i); + if (isEmpty(section)) continue; + int chunkBottom = minY + i * Chunk.CHUNK_SECTION_SIZE; + int chunkTop = chunkBottom + Chunk.CHUNK_SECTION_SIZE; + + if (bottomY > chunkBottom) { + bottomY = chunkBottom; + } + if (topY < chunkTop) { + topY = chunkTop; + } + } + + + List finalFaces = new ArrayList<>(); + + for (int y = bottomY; y < topY; y++) { + for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { + for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { + List faces = getQuads(chunk, x, y, z); + if (faces == null) continue; + finalFaces.addAll(faces); + } + } + } + + return finalFaces; + } + + private static @Nullable List getQuads(Chunk chunk, int x, int y, int z){ + Block block = chunk.getBlock(x, y, z, Block.Getter.Condition.TYPE); + + if (block.isAir() || block.isLiquid()) return null; + List quads = new ArrayList<>(); + + Shape shape = block.registry().collisionShape(); + Point relStart = shape.relativeStart(); + Point relEnd = shape.relativeEnd(); + + var blockX = chunk.getChunkX() * Chunk.CHUNK_SIZE_X + x; + var blockZ = chunk.getChunkZ() * Chunk.CHUNK_SIZE_Z + z; + + for (BlockFace blockFace : BLOCK_FACES) { + Face face = new Face( + blockFace, + blockFace == BlockFace.EAST ? relEnd.x() : relStart.x(), + blockFace == BlockFace.TOP ? relEnd.y() : relStart.y(), + blockFace == BlockFace.SOUTH ? relEnd.z() : relStart.z(), + blockFace == BlockFace.WEST ? relStart.x() : relEnd.x(), + blockFace == BlockFace.BOTTOM ? relStart.y() : relEnd.y(), + blockFace == BlockFace.NORTH ? relStart.z() : relEnd.z(), + blockX, + y, + blockZ + ); + + if (!face.isEdge()) { // If face isn't an edge, we don't need to check neighbours + quads.add(face.toQuad()); + continue; + } + + var dir = blockFace.toDirection(); + var neighbourBlock = chunk.getBlock(x + dir.normalX(), y + dir.normalY(), z + dir.normalZ(), Block.Getter.Condition.TYPE); + + if (!isFull(neighbourBlock)) { + quads.add(face.toQuad()); + } + } + + return quads; + } + + private static boolean isFull(Block block) { + if (block.isAir() || block.isLiquid()) return false; + + Shape shape = block.registry().collisionShape(); + Point relStart = shape.relativeStart(); + Point relEnd = shape.relativeEnd(); + + return relStart.x() == 0.0 && relStart.y() == 0.0 && relStart.z() == 0.0 && + relEnd.x() == 1.0 && relEnd.y() == 1.0 && relEnd.z() == 1.0; + } + + private static boolean isEmpty(Section section) { + return section.blockPalette().count() == 0; + } + +} diff --git a/src/main/java/dev/emortal/minestom/parkourtag/physics/worldmesh/Face.java b/src/main/java/dev/emortal/minestom/parkourtag/physics/worldmesh/Face.java new file mode 100755 index 0000000..c527008 --- /dev/null +++ b/src/main/java/dev/emortal/minestom/parkourtag/physics/worldmesh/Face.java @@ -0,0 +1,45 @@ +package dev.emortal.minestom.parkourtag.physics.worldmesh; + +import com.jme3.math.Vector3f; +import net.minestom.server.instance.block.BlockFace; + +public record Face(BlockFace blockFace, float minX, float minY, float minZ, float maxX, float maxY, float maxZ, int blockX, int blockY, int blockZ) { + + public Face(BlockFace blockFace, double minX, double minY, double minZ, double maxX, double maxY, double maxZ, int blockX, int blockY, int blockZ) { + this(blockFace, (float) minX, (float) minY, (float) minZ, (float) maxX, (float) maxY, (float) maxZ, blockX, blockY, blockZ); + } + + public boolean isEdge() { + return switch (blockFace) { + case BOTTOM -> minY == 0.0; + case TOP -> maxY == 1.0; + case NORTH -> minZ == 0.0; + case SOUTH -> maxZ == 1.0; + case WEST -> minX == 0.0; + case EAST -> maxX == 1.0; + }; + } + + public Quad toQuad() { + return switch (blockFace) { + case TOP, BOTTOM -> new Quad( + new Vector3f(minX + blockX, maxY + blockY, minZ + blockZ), + new Vector3f(maxX + blockX, maxY + blockY, minZ + blockZ), + new Vector3f(maxX + blockX, maxY + blockY, maxZ + blockZ), + new Vector3f(minX + blockX, maxY + blockY, maxZ + blockZ) + ); + case EAST, WEST -> new Quad( + new Vector3f(maxX + blockX, minY + blockY, minZ + blockZ), + new Vector3f(maxX + blockX, maxY + blockY, minZ + blockZ), + new Vector3f(maxX + blockX, maxY + blockY, maxZ + blockZ), + new Vector3f(maxX + blockX, minY + blockY, maxZ + blockZ) + ); + case NORTH, SOUTH -> new Quad( + new Vector3f(minX + blockX, minY + blockY, minZ + blockZ), + new Vector3f(maxX + blockX, minY + blockY, minZ + blockZ), + new Vector3f(maxX + blockX, maxY + blockY, minZ + blockZ), + new Vector3f(minX + blockX, maxY + blockY, minZ + blockZ) + ); + }; + } +} \ No newline at end of file diff --git a/src/main/java/dev/emortal/minestom/parkourtag/physics/worldmesh/Quad.java b/src/main/java/dev/emortal/minestom/parkourtag/physics/worldmesh/Quad.java new file mode 100755 index 0000000..f050e56 --- /dev/null +++ b/src/main/java/dev/emortal/minestom/parkourtag/physics/worldmesh/Quad.java @@ -0,0 +1,30 @@ +package dev.emortal.minestom.parkourtag.physics.worldmesh; + +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; + +public record Quad(Vector3f point1, Vector3f point2, Vector3f point3, Vector3f point4) { + public Triangle[] triangles() { + Triangle[] triangles = new Triangle[2]; + triangles[0] = new Triangle(point1, point2, point3); + triangles[1] = new Triangle(point3, point4, point1); + return triangles; + } + + public Vector3f min() { + return new Vector3f(min(point1.x, point2.x, point3.x, point4.x), min(point1.y, point2.y, point3.y, point4.y), min(point1.z, point2.z, point3.z, point4.z)); + } + + private float min(float a, float b, float c, float d) { + return Math.min(a, Math.min(b, Math.min(c, d))); + } + + public Vector3f max() { + return new Vector3f(max(point1.x, point2.x, point3.x, point4.x), max(point1.y, point2.y, point3.y, point4.y), max(point1.z, point2.z, point3.z, point4.z)); + } + + private float max(float a, float b, float c, float d) { + return Math.max(a, Math.max(b, Math.max(c, d))); + } + +} \ No newline at end of file diff --git a/src/main/java/dev/emortal/minestom/parkourtag/utils/NoTickEntity.java b/src/main/java/dev/emortal/minestom/parkourtag/utils/NoTickEntity.java old mode 100644 new mode 100755 index 5497600..5af34c2 --- a/src/main/java/dev/emortal/minestom/parkourtag/utils/NoTickEntity.java +++ b/src/main/java/dev/emortal/minestom/parkourtag/utils/NoTickEntity.java @@ -9,6 +9,7 @@ public class NoTickEntity extends Entity { public NoTickEntity(@NotNull EntityType entityType) { super(entityType); + hasCollision = false; setNoGravity(true); hasPhysics = false; } diff --git a/src/main/java/dev/emortal/minestom/parkourtag/utils/NoopChunkLoader.java b/src/main/java/dev/emortal/minestom/parkourtag/utils/NoopChunkLoader.java deleted file mode 100644 index 71ffa3b..0000000 --- a/src/main/java/dev/emortal/minestom/parkourtag/utils/NoopChunkLoader.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.emortal.minestom.parkourtag.utils; - -import net.minestom.server.instance.Chunk; -import net.minestom.server.instance.IChunkLoader; -import net.minestom.server.instance.Instance; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.concurrent.CompletableFuture; - -public class NoopChunkLoader implements IChunkLoader { - @Override - public @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) { - return CompletableFuture.completedFuture(null); - } - - @Override - public @NotNull CompletableFuture saveChunk(@NotNull Chunk chunk) { - return CompletableFuture.completedFuture(null); - } - - @Override - public boolean supportsParallelSaving() { - return true; - } - - @Override - public boolean supportsParallelLoading() { - return true; - } -} diff --git a/src/main/java/dev/emortal/minestom/parkourtag/utils/Quaternion.java b/src/main/java/dev/emortal/minestom/parkourtag/utils/PTQuaternion.java similarity index 80% rename from src/main/java/dev/emortal/minestom/parkourtag/utils/Quaternion.java rename to src/main/java/dev/emortal/minestom/parkourtag/utils/PTQuaternion.java index b14b155..4c77a7c 100644 --- a/src/main/java/dev/emortal/minestom/parkourtag/utils/Quaternion.java +++ b/src/main/java/dev/emortal/minestom/parkourtag/utils/PTQuaternion.java @@ -3,32 +3,32 @@ import net.minestom.server.coordinate.Vec; import org.jetbrains.annotations.NotNull; -public final class Quaternion { +public final class PTQuaternion { private double x; private double y; private double z; private double w; - public Quaternion(@NotNull Quaternion q) { + public PTQuaternion(@NotNull PTQuaternion q) { this(q.x, q.y, q.z, q.w); } - public Quaternion(double x, double y, double z, double w) { + public PTQuaternion(double x, double y, double z, double w) { this.x = x; this.y = y; this.z = z; this.w = w; } - public void set(@NotNull Quaternion q) { + public void set(@NotNull PTQuaternion q) { this.x = q.x; this.y = q.y; this.z = q.z; this.w = q.w; } - public Quaternion(@NotNull Vec axis, double angle) { + public PTQuaternion(@NotNull Vec axis, double angle) { this.set(axis, angle); } @@ -59,7 +59,7 @@ public double getZ() { * the rotation angle * @return this */ - public @NotNull Quaternion set(@NotNull Vec axis, double angle) { + public @NotNull PTQuaternion set(@NotNull Vec axis, double angle) { double s = Math.sin(angle / 2); this.w = Math.cos(angle / 2); this.x = axis.x() * s; @@ -68,7 +68,7 @@ public double getZ() { return this; } - public @NotNull Quaternion mulThis(@NotNull Quaternion q) { + public @NotNull PTQuaternion mulThis(@NotNull PTQuaternion q) { double nw = this.w * q.w - this.x * q.x - this.y * q.y - this.z * q.z; double nx = this.w * q.x + this.x * q.w + this.y * q.z - this.z * q.y; double ny = this.w * q.y + this.y * q.w + this.z * q.x - this.x * q.z; @@ -79,7 +79,7 @@ public double getZ() { return this; } - public @NotNull Quaternion scaleThis(double scale) { + public @NotNull PTQuaternion scaleThis(double scale) { if (scale != 1) { this.w *= scale; this.x *= scale; @@ -89,7 +89,7 @@ public double getZ() { return this; } - public @NotNull Quaternion divThis(double scale) { + public @NotNull PTQuaternion divThis(double scale) { if (scale != 1) { this.w /= scale; this.x /= scale; @@ -99,15 +99,15 @@ public double getZ() { return this; } - public double dot(@NotNull Quaternion q) { + public double dot(@NotNull PTQuaternion q) { return this.x * q.x + this.y * q.y + this.z * q.z + this.w * q.w; } - public boolean equals(@NotNull Quaternion q) { + public boolean equals(@NotNull PTQuaternion q) { return this.x == q.x && this.y == q.y && this.z == q.z && this.w == q.w; } - public @NotNull Quaternion interpolateThis(@NotNull Quaternion q, double t) { + public @NotNull PTQuaternion interpolateThis(@NotNull PTQuaternion q, double t) { if (!this.equals(q)) { double d = this.dot(q); double qx, qy, qz, qw; @@ -147,12 +147,12 @@ public boolean equals(@NotNull Quaternion q) { return this; } - public @NotNull Quaternion normalizeThis() { + public @NotNull PTQuaternion normalizeThis() { return this.divThis(this.norm()); } - public @NotNull Quaternion interpolate(@NotNull Quaternion q, double t) { - return new Quaternion(this).interpolateThis(q, t); + public @NotNull PTQuaternion interpolate(@NotNull PTQuaternion q, double t) { + return new PTQuaternion(this).interpolateThis(q, t); } /**