From 993e9158a00b359f4ce65c870274753629d99256 Mon Sep 17 00:00:00 2001 From: Yancey <1709185482@qq.com> Date: Sat, 27 Apr 2024 18:14:18 +0800 Subject: [PATCH] version 2.0, use native to rewrite the mod --- gradle.properties | 2 +- .../api/common/OpenParticleAPI.java | 39 --- .../api/common/bridge/Bridge.java | 9 - .../api/common/bridge/Logger.java | 11 - .../common/controller/ParticleController.java | 21 -- .../controller/SimpleParticleController.java | 85 ------- .../api/common/data/DataParticleManager.java | 71 ------ .../api/common/data/DataRunningPerTick.java | 28 --- .../api/common/data/ParticleState.java | 12 - .../api/common/data/color/DataColor.java | 34 --- .../api/common/data/color/DataColorFree.java | 38 --- .../common/data/color/DataColorSimple.java | 54 ----- .../common/data/color/DataColorStatic.java | 38 --- .../common/data/identifier/Identifier.java | 59 ----- .../data/identifier/IdentifierCache.java | 46 ---- .../common/data/particle/DataParticle.java | 91 ------- .../data/particle/DataParticleColor.java | 54 ----- .../data/particle/DataParticleCompound.java | 51 ---- .../data/particle/DataParticleOffset.java | 57 ----- .../data/particle/DataParticleRotate.java | 57 ----- .../data/particle/DataParticleSingle.java | 69 ------ .../data/particle/DataParticleTick.java | 55 ----- .../api/common/data/vec3/DataVec3.java | 36 --- .../api/common/data/vec3/DataVec3Free.java | 41 ---- .../api/common/data/vec3/DataVec3Simple.java | 71 ------ .../api/common/data/vec3/DataVec3Static.java | 40 ---- .../api/common/math/MathUtil.java | 13 - .../openparticle/api/common/math/Matrix.java | 172 -------------- .../openparticle/api/common/math/Vec3.java | 103 -------- .../nativecore/OpenParticleProject.java | 178 ++++++++++++++ .../openparticle/api/common/node/Node.java | 141 ----------- .../api/common/util/ColorUtil.java | 22 -- .../openparticle/api/common/util/Pair.java | 35 --- .../openparticle/core/OpenParticle.java | 26 +- .../core/client/GuiProgressBar.java | 2 +- .../core/client/OpenParticleClient.java | 6 +- .../core/client/ParticleAsyncManager.java | 222 ------------------ .../openparticle/core/command/CommandPar.java | 11 +- .../core/core/OpenParticleCore.java | 216 +++++++++++------ .../core/core/RunningHandler.java | 66 ------ .../core/events/RunningEventManager.java | 47 ++-- .../core/keys/KeyboardManager.java | 3 +- .../core/mixin/BufferBuilderAccessor.java | 5 - .../core/mixin/InGameHudMixin.java | 7 +- .../core/mixin/ParticleManagerAccessor.java | 13 - .../core/mixin/ParticleManagerMixin.java | 96 +++----- .../core/network/NetworkHandler.java | 36 +-- .../core/particle/BetterParticle.java | 167 ------------- .../openparticle/core/util/MyLogger.java | 27 --- src/main/resources/native/libOpenParticle.dll | Bin 0 -> 298139 bytes 50 files changed, 438 insertions(+), 2345 deletions(-) delete mode 100644 src/main/java/yancey/openparticle/api/common/OpenParticleAPI.java delete mode 100644 src/main/java/yancey/openparticle/api/common/bridge/Bridge.java delete mode 100644 src/main/java/yancey/openparticle/api/common/bridge/Logger.java delete mode 100644 src/main/java/yancey/openparticle/api/common/controller/ParticleController.java delete mode 100644 src/main/java/yancey/openparticle/api/common/controller/SimpleParticleController.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/DataParticleManager.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/DataRunningPerTick.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/ParticleState.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/color/DataColor.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/color/DataColorFree.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/color/DataColorSimple.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/color/DataColorStatic.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/identifier/Identifier.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/identifier/IdentifierCache.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/particle/DataParticle.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/particle/DataParticleColor.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/particle/DataParticleCompound.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/particle/DataParticleOffset.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/particle/DataParticleRotate.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/particle/DataParticleSingle.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/particle/DataParticleTick.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Free.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Simple.java delete mode 100644 src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Static.java delete mode 100644 src/main/java/yancey/openparticle/api/common/math/MathUtil.java delete mode 100644 src/main/java/yancey/openparticle/api/common/math/Matrix.java delete mode 100644 src/main/java/yancey/openparticle/api/common/math/Vec3.java create mode 100644 src/main/java/yancey/openparticle/api/common/nativecore/OpenParticleProject.java delete mode 100644 src/main/java/yancey/openparticle/api/common/node/Node.java delete mode 100644 src/main/java/yancey/openparticle/api/common/util/ColorUtil.java delete mode 100644 src/main/java/yancey/openparticle/api/common/util/Pair.java delete mode 100644 src/main/java/yancey/openparticle/core/client/ParticleAsyncManager.java delete mode 100644 src/main/java/yancey/openparticle/core/core/RunningHandler.java delete mode 100644 src/main/java/yancey/openparticle/core/particle/BetterParticle.java delete mode 100644 src/main/java/yancey/openparticle/core/util/MyLogger.java create mode 100644 src/main/resources/native/libOpenParticle.dll diff --git a/gradle.properties b/gradle.properties index 3352b5a..510e250 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,6 +11,6 @@ loader_version=0.15.6 fabric_version=0.95.0+1.20.4 # Mod Properties - mod_version = 1.20.4-1.2 + mod_version = 1.20.4-2.0 maven_group = yancey.openparticle archives_base_name = OpenParticle diff --git a/src/main/java/yancey/openparticle/api/common/OpenParticleAPI.java b/src/main/java/yancey/openparticle/api/common/OpenParticleAPI.java deleted file mode 100644 index 63e5881..0000000 --- a/src/main/java/yancey/openparticle/api/common/OpenParticleAPI.java +++ /dev/null @@ -1,39 +0,0 @@ -package yancey.openparticle.api.common; - -import org.jetbrains.annotations.NotNull; -import yancey.openparticle.api.common.bridge.Bridge; -import yancey.openparticle.api.common.bridge.Logger; -import yancey.openparticle.api.common.data.DataParticleManager; -import yancey.openparticle.api.common.data.identifier.IdentifierCache; - -import java.io.*; - -public record OpenParticleAPI(Bridge bridge, Logger logger) { - - public long getParticleCount(DataParticleManager dataParticleManager) { - return dataParticleManager.getParticleCount(); - } - - public void output(@NotNull File file, @NotNull DataParticleManager dataParticleManager) { - if (!file.exists()) { - File parent = file.getParentFile(); - if (parent != null && !parent.exists() && !parent.mkdirs()) { - logger.warn("文件夹创建失败 : " + parent.getAbsolutePath()); - } - } - try (DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { - IdentifierCache.writeToFile(dataOutputStream); - dataParticleManager.writeToFile(dataOutputStream); - } catch (IOException e) { - logger.warn("文件写入失败 : " + file.getAbsolutePath(), e); - } - } - - public DataParticleManager input(@NotNull File file) throws IOException { - try (DataInputStream dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(file)))) { - return new DataParticleManager(dataInputStream); - } catch (IOException e) { - throw e; - } - } -} diff --git a/src/main/java/yancey/openparticle/api/common/bridge/Bridge.java b/src/main/java/yancey/openparticle/api/common/bridge/Bridge.java deleted file mode 100644 index 94a8d36..0000000 --- a/src/main/java/yancey/openparticle/api/common/bridge/Bridge.java +++ /dev/null @@ -1,9 +0,0 @@ -package yancey.openparticle.api.common.bridge; - -import yancey.openparticle.api.common.data.identifier.Identifier; - -public interface Bridge { - - Object getParticleSprites(Identifier identifier); - -} diff --git a/src/main/java/yancey/openparticle/api/common/bridge/Logger.java b/src/main/java/yancey/openparticle/api/common/bridge/Logger.java deleted file mode 100644 index f6ad6f2..0000000 --- a/src/main/java/yancey/openparticle/api/common/bridge/Logger.java +++ /dev/null @@ -1,11 +0,0 @@ -package yancey.openparticle.api.common.bridge; - -public interface Logger { - - void info(String str); - - void warn(String str, Throwable throwable); - - void warn(String str); - -} diff --git a/src/main/java/yancey/openparticle/api/common/controller/ParticleController.java b/src/main/java/yancey/openparticle/api/common/controller/ParticleController.java deleted file mode 100644 index f9b25ea..0000000 --- a/src/main/java/yancey/openparticle/api/common/controller/ParticleController.java +++ /dev/null @@ -1,21 +0,0 @@ -package yancey.openparticle.api.common.controller; - - -import yancey.openparticle.api.common.OpenParticleAPI; -import yancey.openparticle.api.common.data.ParticleState; - -public interface ParticleController { - - boolean isStatic(); - - void prepare(int tick); - - ParticleState getParticleState(); - - int getTickStart(); - - int getAge(); - - Object getParticleSprites(OpenParticleAPI openParticleAPI); - -} diff --git a/src/main/java/yancey/openparticle/api/common/controller/SimpleParticleController.java b/src/main/java/yancey/openparticle/api/common/controller/SimpleParticleController.java deleted file mode 100644 index 5a86d4e..0000000 --- a/src/main/java/yancey/openparticle/api/common/controller/SimpleParticleController.java +++ /dev/null @@ -1,85 +0,0 @@ -package yancey.openparticle.api.common.controller; - -import yancey.openparticle.api.common.OpenParticleAPI; -import yancey.openparticle.api.common.data.ParticleState; -import yancey.openparticle.api.common.math.Vec3; -import yancey.openparticle.api.common.node.Node; -import yancey.openparticle.api.common.util.ColorUtil; - -public class SimpleParticleController implements ParticleController { - - private final int tickStart; - private final Node node; - private final int age; - private Object particleSprites; - private final boolean isStatic; - private int lastTick = -1; - private ParticleState nextParticleState; - - public SimpleParticleController(Node node) { - this.node = node; - this.tickStart = node.getTickStart(); - this.age = node.getAge(); - isStatic = node.isStatic(); - if (isStatic) { - updateParticleState(); - } - } - - @Override - public boolean isStatic() { - return isStatic; - } - - @Override - public void prepare(int tick) { - if (isStatic) { - return; - } - if (lastTick != tick) { - if (tick >= age) { - return; - } - node.cache(tick); - updateParticleState(); - lastTick = tick; - } - } - - private void updateParticleState() { - nextParticleState = new ParticleState(); - Vec3 position = node.cachePosition.apply(Vec3.ZERO); - nextParticleState.x = position.x; - nextParticleState.y = position.y; - nextParticleState.z = position.z; - nextParticleState.r = (byte) ColorUtil.getRed(node.cacheColor); - nextParticleState.g = (byte) ColorUtil.getGreen(node.cacheColor); - nextParticleState.b = (byte) ColorUtil.getBlue(node.cacheColor); - nextParticleState.a = (byte) ColorUtil.getAlpha(node.cacheColor); - nextParticleState.bright = 15728880; - } - - @Override - public ParticleState getParticleState() { - return nextParticleState; - } - - @Override - public int getTickStart() { - return tickStart; - } - - @Override - public int getAge() { - return age; - } - - @Override - public Object getParticleSprites(OpenParticleAPI openParticleAPI) { - if (particleSprites == null) { - particleSprites = node.getParticleSprites(openParticleAPI); - } - return particleSprites; - } - -} diff --git a/src/main/java/yancey/openparticle/api/common/data/DataParticleManager.java b/src/main/java/yancey/openparticle/api/common/data/DataParticleManager.java deleted file mode 100644 index 4b55d92..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/DataParticleManager.java +++ /dev/null @@ -1,71 +0,0 @@ -package yancey.openparticle.api.common.data; - -import yancey.openparticle.api.common.controller.SimpleParticleController; -import yancey.openparticle.api.common.data.identifier.IdentifierCache; -import yancey.openparticle.api.common.data.particle.DataParticle; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - -public class DataParticleManager { - - public static List dataParticleList = new ArrayList<>(); - public final List parents; - - public DataParticleManager() { - parents = new ArrayList<>(); - } - - public DataParticleManager(DataInputStream dataInputStream) throws IOException { - IdentifierCache.readFromFile(dataInputStream); - int size1 = dataInputStream.readInt(); - dataParticleList = new ArrayList<>(size1); - for (int i = 0; i < size1; i++) { - dataParticleList.add(DataParticle.readFromFile(this, dataInputStream)); - } - int size2 = dataInputStream.readInt(); - parents = new ArrayList<>(size2); - for (int i = 0; i < size2; i++) { - parents.add(getDataParticle(dataInputStream.readInt())); - } - } - - public void add(DataParticle dataParticle) { - parents.add(dataParticle); - } - - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - dataOutputStream.writeInt(dataParticleList.size()); - for (DataParticle dataParticle : dataParticleList) { - dataParticle.writeToFile(dataOutputStream); - } - dataOutputStream.writeInt(parents.size()); - for (DataParticle parent : parents) { - dataOutputStream.writeInt(parent.id); - } - } - - public DataParticle getDataParticle(int id) { - return dataParticleList.get(id); - } - - public long getParticleCount() { - return parents.stream() - .flatMap(DataParticle::streamParticleSingle) - .count(); - } - - public DataRunningPerTick[] getDataRunningList() { - List dataRunningList = new ArrayList<>(); - parents.stream() - .flatMap(parent -> parent.getNode(null).second) - .map(SimpleParticleController::new) - .forEach(controller -> DataRunningPerTick.getFromList(controller.getTickStart(), dataRunningList).controllerList.add(controller)); - dataRunningList.sort(Comparator.comparingInt(dataRunningPerTick -> dataRunningPerTick.tick)); - return dataRunningList.toArray(new DataRunningPerTick[0]); - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/DataRunningPerTick.java b/src/main/java/yancey/openparticle/api/common/data/DataRunningPerTick.java deleted file mode 100644 index 08361aa..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/DataRunningPerTick.java +++ /dev/null @@ -1,28 +0,0 @@ -package yancey.openparticle.api.common.data; - -import yancey.openparticle.api.common.controller.SimpleParticleController; - -import java.util.ArrayList; -import java.util.List; - -public class DataRunningPerTick { - - public final int tick; - public final List controllerList = new ArrayList<>(); - - public DataRunningPerTick(int tick) { - this.tick = tick; - } - - public static DataRunningPerTick getFromList(int tick, List dataRunningList) { - for (DataRunningPerTick dataRunning : dataRunningList) { - if (dataRunning.tick == tick) { - return dataRunning; - } - } - DataRunningPerTick dataRunning = new DataRunningPerTick(tick); - dataRunningList.add(dataRunning); - return dataRunning; - } - -} diff --git a/src/main/java/yancey/openparticle/api/common/data/ParticleState.java b/src/main/java/yancey/openparticle/api/common/data/ParticleState.java deleted file mode 100644 index 31d70b3..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/ParticleState.java +++ /dev/null @@ -1,12 +0,0 @@ -package yancey.openparticle.api.common.data; - -public class ParticleState { - public float x, y, z; - public byte r, g, b, a; - public int bright; - - @Override - public String toString() { - return String.format("(x, y, z) = (%.2f, %.2f, %.2f) | (r, g, b, a) = (%d, %d, %d, %d) | bright = %d", x, y, z, r, g, b, a, bright); - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/color/DataColor.java b/src/main/java/yancey/openparticle/api/common/data/color/DataColor.java deleted file mode 100644 index 62d3bd9..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/color/DataColor.java +++ /dev/null @@ -1,34 +0,0 @@ -package yancey.openparticle.api.common.data.color; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public abstract class DataColor { - - public static final int STATIC = 0; - public static final int SIMPLE = 1; - public static final int FREE = 2; - - public static DataColor readFromFile(DataInputStream dataInputStream) throws IOException { - byte type = dataInputStream.readByte(); - return switch (type) { - case STATIC -> new DataColorStatic(dataInputStream); - case SIMPLE -> new DataColorSimple(dataInputStream); - case FREE -> new DataColorFree(dataInputStream); - default -> throw new IllegalStateException("未知的颜色类型: " + type); - }; - } - - protected abstract byte getType(); - - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - dataOutputStream.writeByte(getType()); - } - - public abstract int getColor(int tick, int age); - - public Integer getCurrentStaticColor() { - return null; - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/color/DataColorFree.java b/src/main/java/yancey/openparticle/api/common/data/color/DataColorFree.java deleted file mode 100644 index 6648db1..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/color/DataColorFree.java +++ /dev/null @@ -1,38 +0,0 @@ -package yancey.openparticle.api.common.data.color; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public class DataColorFree extends DataColor { - - private final int[] colors; - - public DataColorFree(int[] colors) { - this.colors = colors; - } - - public DataColorFree(DataInputStream dataInputStream) throws IOException { - this.colors = new int[dataInputStream.readInt()]; - for (int i = 0; i < this.colors.length; i++) { - this.colors[i] = dataInputStream.readInt(); - } - } - - protected byte getType() { - return FREE; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - for (int color : colors) { - dataOutputStream.writeInt(color); - } - } - - @Override - public int getColor(int tick, int age) { - return colors[Math.min(tick, colors.length - 1)]; - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/color/DataColorSimple.java b/src/main/java/yancey/openparticle/api/common/data/color/DataColorSimple.java deleted file mode 100644 index ed5fc10..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/color/DataColorSimple.java +++ /dev/null @@ -1,54 +0,0 @@ -package yancey.openparticle.api.common.data.color; - -import yancey.openparticle.api.common.math.MathUtil; -import yancey.openparticle.api.common.util.ColorUtil; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public class DataColorSimple extends DataColor { - - private final int[] colors; - - public DataColorSimple(int[] colors) { - this.colors = colors; - } - - public DataColorSimple(DataInputStream dataInputStream) throws IOException { - this.colors = new int[dataInputStream.readInt()]; - for (int i = 0; i < this.colors.length; i++) { - this.colors[i] = dataInputStream.readInt(); - } - } - - protected byte getType() { - return SIMPLE; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - dataOutputStream.writeInt(this.colors.length); - for (int color : colors) { - dataOutputStream.writeInt(color); - } - } - - @Override - public int getColor(int tick, int age) { - float a = (float) tick / age * (colors.length - 1); - int which = (int) a; - if (which + 1 >= colors.length) { - return colors[colors.length - 1]; - } - int start = colors[which]; - int end = colors[which + 1]; - float delta = a - which; - return (((int) MathUtil.lerp(delta, ColorUtil.getAlpha(start), ColorUtil.getAlpha(end)) & 0xFF) << 24) | - (((int) MathUtil.lerp(delta, ColorUtil.getRed(start), ColorUtil.getRed(end)) & 0xFF) << 16) | - (((int) MathUtil.lerp(delta, ColorUtil.getGreen(start), ColorUtil.getGreen(end)) & 0xFF) << 8) | - (((int) MathUtil.lerp(delta, ColorUtil.getBlue(start), ColorUtil.getBlue(end)) & 0xFF)); - } - -} diff --git a/src/main/java/yancey/openparticle/api/common/data/color/DataColorStatic.java b/src/main/java/yancey/openparticle/api/common/data/color/DataColorStatic.java deleted file mode 100644 index 1338c5e..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/color/DataColorStatic.java +++ /dev/null @@ -1,38 +0,0 @@ -package yancey.openparticle.api.common.data.color; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public class DataColorStatic extends DataColor { - - private final int color; - - public DataColorStatic(int color) { - this.color = color; - } - - public DataColorStatic(DataInputStream dataInputStream) throws IOException { - this.color = dataInputStream.readInt(); - } - - protected byte getType() { - return STATIC; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - dataOutputStream.writeInt(color); - } - - @Override - public int getColor(int tick, int age) { - return color; - } - - @Override - public Integer getCurrentStaticColor() { - return color; - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/identifier/Identifier.java b/src/main/java/yancey/openparticle/api/common/data/identifier/Identifier.java deleted file mode 100644 index dfe5c63..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/identifier/Identifier.java +++ /dev/null @@ -1,59 +0,0 @@ -package yancey.openparticle.api.common.data.identifier; - -import org.jetbrains.annotations.NotNull; -import yancey.openparticle.api.common.OpenParticleAPI; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Objects; - -public class Identifier { - - @NotNull - private final String namespace, value; - private Object particleSprites; - - public Identifier(@NotNull String namespace, @NotNull String value) { - this.namespace = namespace; - this.value = value; - } - - public Identifier(@NotNull String value) { - this("minecraft", value); - } - - public Identifier(@NotNull DataInputStream dataInputStream) throws IOException { - this(dataInputStream.readBoolean() ? "minecraft" : dataInputStream.readUTF(), dataInputStream.readUTF()); - } - - public @NotNull String getNamespace() { - return namespace; - } - - public @NotNull String getValue() { - return value; - } - - public Object getParticleSprites(OpenParticleAPI openParticleAPI) { - if (particleSprites == null) { - particleSprites = openParticleAPI.bridge().getParticleSprites(this); - } - return particleSprites; - } - - public void writeToFile(@NotNull DataOutputStream dataOutputStream) throws IOException { - if (Objects.equals(namespace, "minecraft")) { - dataOutputStream.writeBoolean(true); - } else { - dataOutputStream.writeBoolean(false); - dataOutputStream.writeUTF(namespace); - } - dataOutputStream.writeUTF(value); - } - - @Override - public String toString() { - return namespace + ':' + value; - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/identifier/IdentifierCache.java b/src/main/java/yancey/openparticle/api/common/data/identifier/IdentifierCache.java deleted file mode 100644 index 61ea5f0..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/identifier/IdentifierCache.java +++ /dev/null @@ -1,46 +0,0 @@ -package yancey.openparticle.api.common.data.identifier; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class IdentifierCache { - - public static List identifierList = new ArrayList<>(); - - public static void add(Identifier identifier) { - if (!identifierList.contains(identifier)) { - identifierList.add(identifier); - } - } - - public static int getId(Identifier identifier) { - for (int i = 0; i < identifierList.size(); i++) { - if (identifierList.get(i) == identifier) { - return i; - } - } - return -1; - } - - public static Identifier getIdentifier(int id) { - return identifierList.get(id); - } - - public static void writeToFile(DataOutputStream dataOutputStream) throws IOException { - dataOutputStream.writeInt(identifierList.size()); - for (Identifier identifier : identifierList) { - identifier.writeToFile(dataOutputStream); - } - } - - public static void readFromFile(DataInputStream dataInputStream) throws IOException { - identifierList.clear(); - int size = dataInputStream.readInt(); - for (int i = 0; i < size; i++) { - identifierList.add(new Identifier(dataInputStream)); - } - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticle.java b/src/main/java/yancey/openparticle/api/common/data/particle/DataParticle.java deleted file mode 100644 index f73e13f..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticle.java +++ /dev/null @@ -1,91 +0,0 @@ -package yancey.openparticle.api.common.data.particle; - -import yancey.openparticle.api.common.data.DataParticleManager; -import yancey.openparticle.api.common.math.Matrix; -import yancey.openparticle.api.common.node.Node; -import yancey.openparticle.api.common.util.Pair; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.List; -import java.util.stream.Stream; - -public abstract class DataParticle { - - public static final int SINGLE = 0; - public static final int COMPOUND = 1; - public static final int TICK = 2; - public static final int OFFSET = 3; - public static final int ROTATE = 4; - public static final int COLOR = 5; - public static int nextId = 0; - public final int id; - - public DataParticle() { - this.id = nextId++; - DataParticleManager.dataParticleList.add(this); - } - - public DataParticle(DataInputStream dataInputStream) throws IOException { - this.id = dataInputStream.readInt(); - } - - public static DataParticle readFromFile(DataParticleManager dataParticleManager, DataInputStream dataInputStream) throws IOException { - byte type = dataInputStream.readByte(); - return switch (type) { - case SINGLE -> new DataParticleSingle(dataInputStream); - case COMPOUND -> new DataParticleCompound(dataParticleManager, dataInputStream); - case TICK -> new DataParticleTick(dataParticleManager, dataInputStream); - case OFFSET -> new DataParticleOffset(dataParticleManager, dataInputStream); - case ROTATE -> new DataParticleRotate(dataParticleManager, dataInputStream); - default -> throw new IllegalStateException("未知的粒子类型: " + type); - }; - } - - public abstract int getType(); - - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - dataOutputStream.writeByte(getType()); - dataOutputStream.writeInt(id); - } - - public abstract Stream getChildren(); - - public Stream streamParticleSingle() { - return getChildren().flatMap(DataParticle::streamParticleSingle); - } - - public void collect(List dataParticleList) { - dataParticleList.add(this); - getChildren().forEach(dataParticle -> dataParticle.collect(dataParticleList)); - } - - /** - * 左边是当前的节点,右边是最底层的节点 - */ - public Pair> getNode(Node parentNode) { - Node node = new Node(this); - node.setParent(parentNode); - return new Pair<>(node, getChildren() - .map(dataParticle -> dataParticle.getNode(node)) - .peek(pair -> pair.first.setParent(node)) - .flatMap(pair -> pair.second)); - } - - public Matrix getPositionMatrix(int tick, int age) { - return Matrix.ZERO; - } - - public Integer getColor(int tick, int age) { - return 0; - } - - public Matrix getCurrentStaticPosition() { - return Matrix.ZERO; - } - - public Integer getCurrentStaticColor() { - return 0; - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleColor.java b/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleColor.java deleted file mode 100644 index 7eebf83..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleColor.java +++ /dev/null @@ -1,54 +0,0 @@ -package yancey.openparticle.api.common.data.particle; - -import yancey.openparticle.api.common.data.DataParticleManager; -import yancey.openparticle.api.common.data.color.DataColor; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.stream.Stream; - -public class DataParticleColor extends DataParticle { - - public final DataParticle dataParticle; - public final DataColor color; - - public DataParticleColor(DataParticle dataParticle, DataColor color) { - super(); - this.dataParticle = dataParticle; - this.color = color; - } - - public DataParticleColor(DataParticleManager dataParticleManager, DataInputStream dataInputStream) throws IOException { - super(dataInputStream); - this.dataParticle = dataParticleManager.getDataParticle(dataInputStream.readInt()); - this.color = DataColor.readFromFile(dataInputStream); - } - - @Override - public int getType() { - return COLOR; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - dataOutputStream.writeInt(dataParticle.id); - color.writeToFile(dataOutputStream); - } - - @Override - public Stream getChildren() { - return Stream.of(dataParticle); - } - - @Override - public Integer getColor(int tick, int age) { - return color.getColor(tick, age); - } - - @Override - public Integer getCurrentStaticColor() { - return color.getCurrentStaticColor(); - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleCompound.java b/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleCompound.java deleted file mode 100644 index 96457ff..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleCompound.java +++ /dev/null @@ -1,51 +0,0 @@ -package yancey.openparticle.api.common.data.particle; - -import yancey.openparticle.api.common.data.DataParticleManager; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.stream.Stream; - -public class DataParticleCompound extends DataParticle { - - //组合方式 true - 同时出现 false - 顺序出现 - public final boolean flag; - public final DataParticle[] dataParticles; - - public DataParticleCompound(boolean flag, DataParticle[] dataParticles) { - super(); - this.flag = flag; - this.dataParticles = dataParticles; - } - - public DataParticleCompound(DataParticleManager dataParticleManager, DataInputStream dataInputStream) throws IOException { - super(dataInputStream); - this.flag = dataInputStream.readBoolean(); - this.dataParticles = new DataParticle[dataInputStream.readInt()]; - for (int i = 0; i < this.dataParticles.length; i++) { - this.dataParticles[i] = dataParticleManager.getDataParticle(dataInputStream.readInt()); - } - } - - @Override - public int getType() { - return COMPOUND; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - dataOutputStream.writeBoolean(flag); - dataOutputStream.writeInt(dataParticles.length); - for (DataParticle dataParticle : dataParticles) { - dataOutputStream.writeInt(dataParticle.id); - } - } - - @Override - public Stream getChildren() { - return Arrays.stream(dataParticles); - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleOffset.java b/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleOffset.java deleted file mode 100644 index 4e1acb6..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleOffset.java +++ /dev/null @@ -1,57 +0,0 @@ -package yancey.openparticle.api.common.data.particle; - -import yancey.openparticle.api.common.data.DataParticleManager; -import yancey.openparticle.api.common.data.vec3.DataVec3; -import yancey.openparticle.api.common.math.Matrix; -import yancey.openparticle.api.common.math.Vec3; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.stream.Stream; - -public class DataParticleOffset extends DataParticle { - - public final DataParticle dataParticle; - public final DataVec3 offset; - - public DataParticleOffset(DataParticle dataParticle, DataVec3 offset) { - super(); - this.dataParticle = dataParticle; - this.offset = offset; - } - - public DataParticleOffset(DataParticleManager dataParticleManager, DataInputStream dataInputStream) throws IOException { - super(dataInputStream); - this.dataParticle = dataParticleManager.getDataParticle(dataInputStream.readInt()); - this.offset = DataVec3.readFromFile(dataInputStream); - } - - @Override - public int getType() { - return OFFSET; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - dataOutputStream.writeInt(dataParticle.id); - offset.writeToFile(dataOutputStream); - } - - @Override - public Stream getChildren() { - return Stream.of(dataParticle); - } - - @Override - public Matrix getPositionMatrix(int tick, int age) { - return Matrix.offset(offset.getVec3(tick, age)); - } - - @Override - public Matrix getCurrentStaticPosition() { - Vec3 result = offset.getCurrentStaticPosition(); - return result == null ? null : Matrix.offset(result); - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleRotate.java b/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleRotate.java deleted file mode 100644 index 5ae8b1d..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleRotate.java +++ /dev/null @@ -1,57 +0,0 @@ -package yancey.openparticle.api.common.data.particle; - -import yancey.openparticle.api.common.data.DataParticleManager; -import yancey.openparticle.api.common.data.vec3.DataVec3; -import yancey.openparticle.api.common.math.Matrix; -import yancey.openparticle.api.common.math.Vec3; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.stream.Stream; - -public class DataParticleRotate extends DataParticle { - - public final DataParticle dataParticle; - public final DataVec3 rotate; - - public DataParticleRotate(DataParticle dataParticle, DataVec3 rotate) { - super(); - this.dataParticle = dataParticle; - this.rotate = rotate; - } - - public DataParticleRotate(DataParticleManager dataParticleManager, DataInputStream dataInputStream) throws IOException { - super(dataInputStream); - this.dataParticle = dataParticleManager.getDataParticle(dataInputStream.readInt()); - this.rotate = DataVec3.readFromFile(dataInputStream); - } - - @Override - public int getType() { - return ROTATE; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - dataOutputStream.writeInt(dataParticle.id); - rotate.writeToFile(dataOutputStream); - } - - @Override - public Stream getChildren() { - return Stream.of(dataParticle); - } - - @Override - public Matrix getPositionMatrix(int tick, int age) { - return Matrix.rotateXYZ(rotate.getVec3(tick, age)); - } - - @Override - public Matrix getCurrentStaticPosition() { - Vec3 result = rotate.getCurrentStaticPosition(); - return result == null ? null : Matrix.rotateXYZ(result); - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleSingle.java b/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleSingle.java deleted file mode 100644 index a101d13..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleSingle.java +++ /dev/null @@ -1,69 +0,0 @@ -package yancey.openparticle.api.common.data.particle; - -import yancey.openparticle.api.common.data.identifier.Identifier; -import yancey.openparticle.api.common.data.identifier.IdentifierCache; -import yancey.openparticle.api.common.node.Node; -import yancey.openparticle.api.common.util.Pair; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.stream.Stream; - -public class DataParticleSingle extends DataParticle { - - public Identifier identifier; - public int age; - - public DataParticleSingle(Identifier identifier, int age) { - super(); - this.identifier = identifier; - this.age = age; - } - - public DataParticleSingle(DataInputStream dataInputStream) throws IOException { - super(dataInputStream); - this.identifier = IdentifierCache.getIdentifier(dataInputStream.readInt()); - this.age = dataInputStream.readInt(); - } - - @Override - public int getType() { - return SINGLE; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - dataOutputStream.writeInt(IdentifierCache.getId(identifier)); - dataOutputStream.writeInt(age); - } - - @Override - public Stream getChildren() { - return Stream.of(); - } - - @Override - public Stream streamParticleSingle() { - return Stream.of(this); - } - - @Override - public Pair> getNode(Node parentNode) { - Node node = new Node(this); - node.setParent(parentNode); - node.setAge(age); - return new Pair<>(node, Stream.of(node)); - } - - @Override - public Integer getColor(int tick, int age) { - return 0xFFFFFFFF; - } - - @Override - public Integer getCurrentStaticColor() { - return 0xFFFFFFFF; - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleTick.java b/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleTick.java deleted file mode 100644 index 93b492c..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/particle/DataParticleTick.java +++ /dev/null @@ -1,55 +0,0 @@ -package yancey.openparticle.api.common.data.particle; - -import yancey.openparticle.api.common.data.DataParticleManager; -import yancey.openparticle.api.common.node.Node; -import yancey.openparticle.api.common.util.Pair; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.stream.Stream; - -public class DataParticleTick extends DataParticle { - - public final DataParticle dataParticle; - public final int tickStart; - - public DataParticleTick(DataParticle dataParticle, int tickStart) { - super(); - if (tickStart < 0) { - throw new RuntimeException("粒子的时间不可以是负数"); - } - this.dataParticle = dataParticle; - this.tickStart = tickStart; - } - - public DataParticleTick(DataParticleManager dataParticleManager, DataInputStream dataInputStream) throws IOException { - super(dataInputStream); - this.dataParticle = dataParticleManager.getDataParticle(dataInputStream.readInt()); - this.tickStart = dataInputStream.readInt(); - } - - @Override - public int getType() { - return TICK; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - dataOutputStream.writeInt(dataParticle.id); - dataOutputStream.writeInt(tickStart); - } - - @Override - public Pair> getNode(Node parentNode) { - Pair> result = dataParticle.getNode(parentNode); - result.first.addTickAdd(tickStart); - return result; - } - - @Override - public Stream getChildren() { - return Stream.of(dataParticle); - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3.java b/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3.java deleted file mode 100644 index 3ba5817..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3.java +++ /dev/null @@ -1,36 +0,0 @@ -package yancey.openparticle.api.common.data.vec3; - -import yancey.openparticle.api.common.math.Vec3; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public abstract class DataVec3 { - - public static final int STATIC = 0; - public static final int SIMPLE = 1; - public static final int FREE = 2; - - public static DataVec3 readFromFile(DataInputStream dataInputStream) throws IOException { - byte type = dataInputStream.readByte(); - return switch (type) { - case STATIC -> new DataVec3Static(dataInputStream); - case SIMPLE -> new DataVec3Simple(dataInputStream); - case FREE -> new DataVec3Free(dataInputStream); - default -> throw new IllegalStateException("未知的位置类型: " + type); - }; - } - - protected abstract byte getType(); - - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - dataOutputStream.writeByte(getType()); - } - - public abstract Vec3 getVec3(int tick, int age); - - public Vec3 getCurrentStaticPosition() { - return null; - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Free.java b/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Free.java deleted file mode 100644 index 1e75c49..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Free.java +++ /dev/null @@ -1,41 +0,0 @@ -package yancey.openparticle.api.common.data.vec3; - -import yancey.openparticle.api.common.math.Vec3; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public class DataVec3Free extends DataVec3 { - - private final Vec3[] vec3List; - - public DataVec3Free(Vec3[] vec3List) { - this.vec3List = vec3List; - } - - public DataVec3Free(DataInputStream dataInputStream) throws IOException { - this.vec3List = new Vec3[dataInputStream.readInt()]; - for (int i = 0; i < this.vec3List.length; i++) { - this.vec3List[i] = new Vec3(dataInputStream); - } - } - - protected byte getType() { - return FREE; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - dataOutputStream.writeInt(vec3List.length); - for (Vec3 position : vec3List) { - position.writeToFile(dataOutputStream); - } - } - - @Override - public Vec3 getVec3(int tick, int age) { - return vec3List[Math.min(tick, vec3List.length - 1)]; - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Simple.java b/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Simple.java deleted file mode 100644 index 2c687b4..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Simple.java +++ /dev/null @@ -1,71 +0,0 @@ -package yancey.openparticle.api.common.data.vec3; - -import yancey.openparticle.api.common.math.Vec3; -import yancey.openparticle.api.common.util.Pair; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class DataVec3Simple extends DataVec3 { - - private final Vec3 vec3, speed; - private final float g, f; - /** - * 左边位置,右边速度 - */ - private final List> cache = new ArrayList<>(); - - public DataVec3Simple(Vec3 vec3, Vec3 speed, float g, float f) { - this.vec3 = vec3; - this.speed = speed; - this.g = g; - this.f = f; - } - - public DataVec3Simple(DataInputStream dataInputStream) throws IOException { - this.vec3 = new Vec3(dataInputStream); - this.speed = new Vec3(dataInputStream); - this.g = dataInputStream.readFloat(); - this.f = dataInputStream.readFloat(); - } - - protected byte getType() { - return SIMPLE; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - vec3.writeToFile(dataOutputStream); - speed.writeToFile(dataOutputStream); - dataOutputStream.writeFloat(g); - dataOutputStream.writeFloat(f); - } - - @Override - public Vec3 getVec3(int tick, int age) { - int lastMaxTick = cache.size() - 1; - if (lastMaxTick <= tick) { - Pair lastPair = lastMaxTick == -1 ? new Pair<>(vec3, speed) : cache.get(lastMaxTick); - float x = lastPair.first.x; - float y = lastPair.first.y; - float z = lastPair.first.z; - float vx = lastPair.second.x; - float vy = lastPair.second.y; - float vz = lastPair.second.z; - for (int i = lastMaxTick + 1; i <= tick; i++) { - x += vx; - y += vy; - z += vz; - vx *= f; - vy = vy * f - g; - vz *= f; - cache.add(new Pair<>(new Vec3(x, y, z), new Vec3(vx, vy, vz))); - } - } - return cache.get(tick).first; - } -} diff --git a/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Static.java b/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Static.java deleted file mode 100644 index f969a14..0000000 --- a/src/main/java/yancey/openparticle/api/common/data/vec3/DataVec3Static.java +++ /dev/null @@ -1,40 +0,0 @@ -package yancey.openparticle.api.common.data.vec3; - -import yancey.openparticle.api.common.math.Vec3; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public class DataVec3Static extends DataVec3 { - - private final Vec3 vec3; - - public DataVec3Static(Vec3 vec3) { - this.vec3 = vec3; - } - - public DataVec3Static(DataInputStream dataInputStream) throws IOException { - this.vec3 = new Vec3(dataInputStream); - } - - protected byte getType() { - return STATIC; - } - - @Override - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - super.writeToFile(dataOutputStream); - vec3.writeToFile(dataOutputStream); - } - - @Override - public Vec3 getVec3(int tick, int age) { - return vec3; - } - - @Override - public Vec3 getCurrentStaticPosition() { - return vec3; - } -} diff --git a/src/main/java/yancey/openparticle/api/common/math/MathUtil.java b/src/main/java/yancey/openparticle/api/common/math/MathUtil.java deleted file mode 100644 index f437af5..0000000 --- a/src/main/java/yancey/openparticle/api/common/math/MathUtil.java +++ /dev/null @@ -1,13 +0,0 @@ -package yancey.openparticle.api.common.math; - -public class MathUtil { - - public static float distance(float x, float y) { - return (float) Math.sqrt(x * x + y * y); - } - - public static float lerp(float delta, float start, float end) { - return start + delta * (end - start); - } - -} diff --git a/src/main/java/yancey/openparticle/api/common/math/Matrix.java b/src/main/java/yancey/openparticle/api/common/math/Matrix.java deleted file mode 100644 index d3751a3..0000000 --- a/src/main/java/yancey/openparticle/api/common/math/Matrix.java +++ /dev/null @@ -1,172 +0,0 @@ -package yancey.openparticle.api.common.math; - -public class Matrix { - - public final static Matrix ZERO = Matrix.offset(0, 0, 0); - private final float[] data; - - private Matrix(float... data) { - if (data.length != 16) { - throw new RuntimeException("暂时只支持4x4的矩阵"); - } - this.data = data; - } - - public static Matrix offset(float x, float y, float z) { - return new Matrix( - 1, 0, 0, x, - 0, 1, 0, y, - 0, 0, 1, z, - 0, 0, 0, 1 - ); - } - - public static Matrix offset(Vec3 offset) { - return offset(offset.x, offset.y, offset.z); - } - - public static Matrix scale(float scaleX, float scaleY, float scaleZ) { - return new Matrix( - scaleX, 0, 0, 0, - 0, scaleY, 0, 0, - 0, 0, scaleZ, 0, - 0, 0, 0, 1 - ); - } - - public static Matrix scale(float scale) { - return scale(scale, scale, scale); - } - - public static Matrix rotateX(float radian) { - float sin = (float) Math.sin(radian); - float cos = (float) Math.cos(radian); - return new Matrix( - 1, 0, 0, 0, - 0, cos, -sin, 0, - 0, sin, cos, 0, - 0, 0, 0, 1 - ); - } - - public static Matrix rotateY(float radian) { - float sin = (float) Math.sin(radian); - float cos = (float) Math.cos(radian); - return new Matrix( - cos, 0, -sin, 0, - 0, 1, 0, 0, - sin, 0, cos, 0, - 0, 0, 0, 1 - ); - } - - public static Matrix rotateZ(float radian) { - float sin = (float) Math.sin(radian); - float cos = (float) Math.cos(radian); - return new Matrix( - cos, -sin, 0, 0, - sin, cos, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ); - } - - public static Matrix rotateXYZ(float x, float y, float z) { - Matrix matrix = Matrix.ZERO; - if (z != 0) { - matrix = matrix.multiply(rotateZ(z)); - } - if (y != 0) { - matrix = matrix.multiply(rotateY(y)); - } - if (x != 0) { - matrix = matrix.multiply(rotateX(x)); - } - return matrix; - } - - public static Matrix rotateXYZ(Vec3 radius) { - return rotateXYZ(radius.x, radius.y, radius.z); - } - - /** - * matrix1 * matrix2 - * 相当于先做矩阵2的变换,再做矩阵1的变换 - */ - public static Matrix multiply(Matrix matrix1, Matrix matrix2) { - if (matrix1 == Matrix.ZERO) { - return matrix2; - } else if (matrix2 == Matrix.ZERO) { - return matrix1; - } - return new Matrix( - matrix1.data[0] * matrix2.data[0] + matrix1.data[1] * matrix2.data[4] + matrix1.data[2] * matrix2.data[8] + matrix1.data[3] * matrix2.data[12], - matrix1.data[0] * matrix2.data[1] + matrix1.data[1] * matrix2.data[5] + matrix1.data[2] * matrix2.data[9] + matrix1.data[3] * matrix2.data[13], - matrix1.data[0] * matrix2.data[2] + matrix1.data[1] * matrix2.data[6] + matrix1.data[2] * matrix2.data[10] + matrix1.data[3] * matrix2.data[14], - matrix1.data[0] * matrix2.data[3] + matrix1.data[1] * matrix2.data[7] + matrix1.data[2] * matrix2.data[11] + matrix1.data[3] * matrix2.data[15], - - matrix1.data[4] * matrix2.data[0] + matrix1.data[5] * matrix2.data[4] + matrix1.data[6] * matrix2.data[8] + matrix1.data[7] * matrix2.data[12], - matrix1.data[4] * matrix2.data[1] + matrix1.data[5] * matrix2.data[5] + matrix1.data[6] * matrix2.data[9] + matrix1.data[7] * matrix2.data[13], - matrix1.data[4] * matrix2.data[2] + matrix1.data[5] * matrix2.data[6] + matrix1.data[6] * matrix2.data[10] + matrix1.data[7] * matrix2.data[14], - matrix1.data[4] * matrix2.data[3] + matrix1.data[5] * matrix2.data[7] + matrix1.data[6] * matrix2.data[11] + matrix1.data[7] * matrix2.data[15], - - matrix1.data[8] * matrix2.data[0] + matrix1.data[9] * matrix2.data[4] + matrix1.data[10] * matrix2.data[8] + matrix1.data[11] * matrix2.data[12], - matrix1.data[8] * matrix2.data[1] + matrix1.data[9] * matrix2.data[5] + matrix1.data[10] * matrix2.data[9] + matrix1.data[11] * matrix2.data[13], - matrix1.data[8] * matrix2.data[2] + matrix1.data[9] * matrix2.data[6] + matrix1.data[10] * matrix2.data[10] + matrix1.data[11] * matrix2.data[14], - matrix1.data[8] * matrix2.data[3] + matrix1.data[9] * matrix2.data[7] + matrix1.data[10] * matrix2.data[11] + matrix1.data[11] * matrix2.data[15], - - matrix1.data[12] * matrix2.data[0] + matrix1.data[13] * matrix2.data[4] + matrix1.data[14] * matrix2.data[8] + matrix1.data[15] * matrix2.data[12], - matrix1.data[12] * matrix2.data[1] + matrix1.data[13] * matrix2.data[5] + matrix1.data[14] * matrix2.data[9] + matrix1.data[15] * matrix2.data[13], - matrix1.data[12] * matrix2.data[2] + matrix1.data[13] * matrix2.data[6] + matrix1.data[14] * matrix2.data[10] + matrix1.data[15] * matrix2.data[14], - matrix1.data[12] * matrix2.data[3] + matrix1.data[13] * matrix2.data[7] + matrix1.data[14] * matrix2.data[11] + matrix1.data[15] * matrix2.data[15] - ); -// float[] data = new float[16]; -// for (int i = 0; i < 4; i++) { -// for (int j = 0; j < 4; j++) { -// for (int k = 0; k < 4; k++) { -// data[4 * i + j] += matrix1.data[4 * i + k] * matrix2.data[4 * k + j]; -// } -// } -// } -// return new Matrix(data); - } - - public static Matrix multiplyAll(Matrix... matrices) { - if (matrices == null || matrices.length == 0) { - return Matrix.ZERO; - } - Matrix matrix = matrices[0]; - if (matrices.length == 1) { - return matrix; - } - for (int i = 1; i < matrices.length; i++) { - matrix = matrix.multiply(matrices[i]); - } - return matrix; - } - - public Matrix multiply(Matrix matrix) { - return multiply(matrix, this); - } - - /** - * 将坐标进行矩阵变换 - */ - public Vec3 apply(Vec3 vec3) { - float[] input = new float[]{vec3.x, vec3.y, vec3.z, 1}; - float[] output = new float[4]; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - output[i] += data[4 * i + j] * input[j]; - } - } - return new Vec3(output[0] / output[3], - output[1] / output[3], - output[2] / output[3]); - } - - public Vec3 apply(float x, float y, float z) { - return apply(new Vec3(x, y, z)); - } - -} diff --git a/src/main/java/yancey/openparticle/api/common/math/Vec3.java b/src/main/java/yancey/openparticle/api/common/math/Vec3.java deleted file mode 100644 index 0f5406d..0000000 --- a/src/main/java/yancey/openparticle/api/common/math/Vec3.java +++ /dev/null @@ -1,103 +0,0 @@ -package yancey.openparticle.api.common.math; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Objects; - -public class Vec3 { - - public static Vec3 ZERO = new Vec3(0, 0, 0); - - public final float x; - public final float y; - public final float z; - - public Vec3(float x, float y, float z) { - this.x = x; - this.y = y; - this.z = z; - } - - public Vec3(DataInputStream dataInputStream) throws IOException { - x = dataInputStream.readFloat(); - y = dataInputStream.readFloat(); - z = dataInputStream.readFloat(); - } - - public static Vec3 ofInt(int x, int y, int z) { - return new Vec3(x + 0.5F, y + 0.5F, z + 0.5F); - } - - public void writeToFile(DataOutputStream dataOutputStream) throws IOException { - dataOutputStream.writeFloat(x); - dataOutputStream.writeFloat(y); - dataOutputStream.writeFloat(z); - } - - public Vec3 add(float dx, float dy, float dz) { - return new Vec3(x + dx, y + dy, z + dz); - } - - public Vec3 add(Vec3 vec3) { - return add(vec3.x, vec3.y, vec3.z); - } - - public Vec3 remove(float dx, float dy, float dz) { - return new Vec3(x - dx, y - dy, z - dz); - } - - public Vec3 remove(Vec3 vec3) { - return remove(vec3.x, vec3.y, vec3.z); - } - - public Vec3 multiply(float num) { - return multiply(num, num, num); - } - - public Vec3 multiply(float dx, float dy, float dz) { - return new Vec3(x * dx, y * dy, z * dz); - } - - public Vec3 multiply(Vec3 vec3) { - return multiply(vec3.x, vec3.y, vec3.z); - } - - public float distanceToZero() { - return (float) Math.sqrt(x * x + y * y + z * z); - } - - public float distance(Vec3 vec3) { - return remove(vec3).distanceToZero(); - } - - public Vec3 getRadian() { - return new Vec3((float) 0, (float) Math.atan2(z, MathUtil.distance(x, y)), (float) Math.atan2(y, x)); - } - - public Vec3 toRadians() { - return new Vec3((float) Math.toRadians(x), (float) Math.toRadians(y), (float) Math.toRadians(z)); - } - - public boolean isZero() { - return x == 0 && y == 0 && z == 0; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Vec3 vec3 = (Vec3) o; - return Float.compare(x, vec3.x) == 0 && Float.compare(y, vec3.y) == 0 && Float.compare(z, vec3.z) == 0; - } - - @Override - public int hashCode() { - return Objects.hash(x, y, z); - } - - @Override - public String toString() { - return String.format("\"%.2f %.2f %.2f\"", x, y, z); - } -} diff --git a/src/main/java/yancey/openparticle/api/common/nativecore/OpenParticleProject.java b/src/main/java/yancey/openparticle/api/common/nativecore/OpenParticleProject.java new file mode 100644 index 0000000..4ad8481 --- /dev/null +++ b/src/main/java/yancey/openparticle/api/common/nativecore/OpenParticleProject.java @@ -0,0 +1,178 @@ +package yancey.openparticle.api.common.nativecore; + +import java.io.Closeable; +import java.nio.Buffer; +import java.util.Objects; +import java.util.concurrent.locks.ReentrantLock; + +public class OpenParticleProject implements Closeable { + + public final String path; + public final int tickEnd; + private final ReentrantLock lock = new ReentrantLock(); + /** + * pointer of the data in native + */ + private long particleDataPointer; + private int currentParticleCount = -1; + + public OpenParticleProject(Bridge bridge, String path) { + lock.lock(); + try { + this.path = path; + this.particleDataPointer = readFile(Objects.requireNonNull(path), Objects.requireNonNull(bridge)); + if (particleDataPointer == 0) { + throw new RuntimeException("Failed to read particle file in native: " + path); + } + tickEnd = getTickEnd(particleDataPointer); + } finally { + lock.unlock(); + } + } + + /** + * read particle file + * + * @param path file path + * @return a pointer of particle data + */ + private native static long readFile(String path, Bridge bridge); + + /** + * release particle data + * + * @param pointer a pointer of particle data + */ + private native static void release(long pointer); + + /** + * @param pointer a pointer of particle data + * @return tick end + */ + private native static int getTickEnd(long pointer); + + /** + * @param pointer a pointer of particle data + * @return particle count in current tick + */ + private native static int getParticleCount(long pointer); + + /** + * @param pointer a pointer of particle data + * @param tick current tick + */ + private native static void prepareTickCache(long pointer, int tick); + + /** + * @param pointer a pointer of particle data + */ + private native static int getVBOSize(long pointer); + + /** + * @param pointer a pointer of particle data + * @param directBuffer direct buffer + * @param isSingleThread is prepared VBO data in single thread + * @param tickDelta tick delta + * @param cameraX camera x-axis position + * @param cameraY camera y-axis position + * @param cameraZ camera z-axis position + * @param rx camera rotation x + * @param ry camera rotation y + * @param rz camera rotation z + * @param rw camera rotation w + */ + private native static void render(long pointer, Buffer directBuffer, boolean isSingleThread, float tickDelta, float cameraX, float cameraY, float cameraZ, float rx, float ry, float rz, float rw); + + public void tick(int tick) { + if (tick < 0 || tick > tickEnd) { + throw new RuntimeException("Tick out of range: " + tick); + } + lock.lock(); + try { + if (particleDataPointer == 0) { + throw new RuntimeException("open particle project native pointer is null pointer"); + } + currentParticleCount = -1; + prepareTickCache(particleDataPointer, tick); + } finally { + lock.unlock(); + } + } + + public int getParticleCount() { + lock.lock(); + try { + if (particleDataPointer == 0) { + throw new RuntimeException("open particle project native pointer is null pointer"); + } + if (currentParticleCount < 0) { + currentParticleCount = getParticleCount(particleDataPointer); + } + return currentParticleCount; + } finally { + lock.unlock(); + } + } + + // or: getParticleSize() * 112 + public int getVBOSize() { + lock.lock(); + try { + if (particleDataPointer == 0) { + throw new RuntimeException("open particle project native pointer is null pointer"); + } + return getVBOSize(particleDataPointer); + } finally { + lock.unlock(); + } + } + + public void render(Buffer directBuffer, boolean isSingleThread, float tickDelta, float cameraX, float cameraY, float cameraZ, float rx, float ry, float rz, float rw) { + if (!directBuffer.isDirect()) { + throw new RuntimeException("buffer is not direct"); + } +// if (directBuffer.capacity() < getVBOSize()) { +// throw new RuntimeException("buffer is too small"); +// } + if (tickDelta < 0 || tickDelta > 1) { + throw new RuntimeException("Tick delta out of range: " + tickDelta); + } + lock.lock(); + try { + if (particleDataPointer == 0) { + throw new RuntimeException("open particle project native pointer is null pointer"); + } + render(particleDataPointer, directBuffer, isSingleThread, tickDelta, cameraX, cameraY, cameraZ, rx, ry, rz, rw); + } finally { + lock.unlock(); + } + } + + @Override + public void close() { + lock.lock(); + try { + if (particleDataPointer != 0) { + return; + } + release(particleDataPointer); + particleDataPointer = 0; + } finally { + lock.unlock(); + } + } + + public interface Bridge { + + /** + * a method to get particle sprites data, it will be call in native + * + * @param namespace identify namespace + * @param value identify value + * @return sprites, one vertex position need four float + */ + float[] getParticleSpritesData(String namespace, String value); + + } + +} diff --git a/src/main/java/yancey/openparticle/api/common/node/Node.java b/src/main/java/yancey/openparticle/api/common/node/Node.java deleted file mode 100644 index 1d3ce43..0000000 --- a/src/main/java/yancey/openparticle/api/common/node/Node.java +++ /dev/null @@ -1,141 +0,0 @@ -package yancey.openparticle.api.common.node; - -import yancey.openparticle.api.common.OpenParticleAPI; -import yancey.openparticle.api.common.data.particle.DataParticle; -import yancey.openparticle.api.common.data.particle.DataParticleSingle; -import yancey.openparticle.api.common.math.Matrix; - -import java.util.concurrent.locks.ReentrantLock; - -public class Node { - - public final Integer currentStaticColor; - //当前节点数据 - private final DataParticle dataParticle; - //保证多线程安全 - private final ReentrantLock lock = new ReentrantLock(); - //当前节点永久不变的位置和颜色偏移(不一定存在) - private final Matrix currentStaticPosition; - //累计位置和颜色偏移(每tick更新一次) - public Matrix cachePosition; - public int cacheColor; - //父节点 - private Node parent; - //累计时间偏移,当前节点存储时间偏移,年龄 - private int tickStart = -1, tickAdd, age; - //获取上一次更新是哪个tick - private int lastCacheTick = -1; - //累计位置和颜色偏移是否永久不变 - private boolean isPositionStatic = false; - private boolean isColorStatic = false; - - //第一步:新建节点 - public Node(DataParticle dataParticle) { - this.dataParticle = dataParticle; - this.currentStaticPosition = dataParticle.getCurrentStaticPosition(); - this.currentStaticColor = dataParticle.getCurrentStaticColor(); - } - - //第二步:绑定父节点 - public void setParent(Node parent) { - this.parent = parent; - if (parent == null) { - if (currentStaticPosition != null) { - isPositionStatic = true; - cachePosition = currentStaticPosition; - } - if (currentStaticColor != null) { - isColorStatic = true; - cachePosition = currentStaticPosition; - } - } else { - if (parent.isPositionStatic && currentStaticPosition != null) { - isPositionStatic = true; - cachePosition = Matrix.multiply(parent.cachePosition, currentStaticPosition); - } - cacheColor = 0; - if (parent.isColorStatic) { - cacheColor = parent.cacheColor; - } - if (cacheColor != 0) { - isColorStatic = true; - } else if (currentStaticColor != null) { - isColorStatic = true; - cacheColor = currentStaticColor; - } - } - } - - //第三步:设置年龄 - public void setAge(int age) { - this.age = age; - if (parent != null) { - parent.age = Math.max(parent.age, age); - } - } - - //第四步:设置当前节点时间偏移 - public void addTickAdd(int tickAdd) { - this.tickAdd += tickAdd; - } - - - public int getAge() { - return age; - } - - public Object getParticleSprites(OpenParticleAPI openParticleAPI) { - return ((DataParticleSingle) dataParticle).identifier.getParticleSprites(openParticleAPI); - } - - public int getTickStart() { - lock.lock(); - try { - if (tickStart == -1) { - tickStart = parent == null ? tickAdd : tickAdd + parent.getTickStart(); - } - } finally { - lock.unlock(); - } - return tickStart; - } - - public void cache(int tick) { - if (isStatic()) { - return; - } - lock.lock(); - try { - if (tick != lastCacheTick) { - lastCacheTick = tick; - //更新父节点 - if (parent != null) { - parent.cache(tick + tickAdd); - } - //更新位置 - if (!isPositionStatic) { - cachePosition = currentStaticPosition == null ? dataParticle.getPositionMatrix(tick + tickAdd, age) : currentStaticPosition; - if (parent != null) { - cachePosition = Matrix.multiply(parent.cachePosition, cachePosition); - } - } - //更新颜色 - if (!isColorStatic) { - cacheColor = 0; - if (parent != null) { - cacheColor = parent.cacheColor; - } - if (cacheColor == 0) { - cacheColor = currentStaticColor == null ? dataParticle.getColor(tick + tickAdd, age) : currentStaticColor; - } - } - } - } finally { - lock.unlock(); - } - } - - public boolean isStatic() { - return isPositionStatic && isColorStatic; - } -} diff --git a/src/main/java/yancey/openparticle/api/common/util/ColorUtil.java b/src/main/java/yancey/openparticle/api/common/util/ColorUtil.java deleted file mode 100644 index afa957f..0000000 --- a/src/main/java/yancey/openparticle/api/common/util/ColorUtil.java +++ /dev/null @@ -1,22 +0,0 @@ -package yancey.openparticle.api.common.util; - -public class ColorUtil { - - public static int getAlpha(int color) { - return (color >> 24) & 0xFF; - } - - public static int getRed(int color) { - return (color >> 16) & 0xFF; - } - - public static int getGreen(int color) { - return (color >> 8) & 0xFF; - } - - public static int getBlue(int color) { - return color & 0xFF; - } - - -} diff --git a/src/main/java/yancey/openparticle/api/common/util/Pair.java b/src/main/java/yancey/openparticle/api/common/util/Pair.java deleted file mode 100644 index d960303..0000000 --- a/src/main/java/yancey/openparticle/api/common/util/Pair.java +++ /dev/null @@ -1,35 +0,0 @@ -package yancey.openparticle.api.common.util; - -import java.util.Objects; - -public class Pair { - - public A first; - public B second; - - public Pair(A first, B second) { - this.first = first; - this.second = second; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Pair pair = (Pair) o; - return Objects.equals(first, pair.first) && Objects.equals(second, pair.second); - } - - @Override - public int hashCode() { - return Objects.hash(first, second); - } - - @Override - public String toString() { - return "Pair{" + - "first=" + first + - ", second=" + second + - '}'; - } -} diff --git a/src/main/java/yancey/openparticle/core/OpenParticle.java b/src/main/java/yancey/openparticle/core/OpenParticle.java index 9585357..b1f65f3 100644 --- a/src/main/java/yancey/openparticle/core/OpenParticle.java +++ b/src/main/java/yancey/openparticle/core/OpenParticle.java @@ -1,22 +1,46 @@ package yancey.openparticle.core; +import com.mojang.logging.LogUtils; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.particle.v1.FabricParticleTypes; +import net.minecraft.client.MinecraftClient; import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; import net.minecraft.util.Identifier; +import org.slf4j.Logger; import yancey.openparticle.core.command.CommandPar; import yancey.openparticle.core.keys.KeyboardManager; import yancey.openparticle.core.network.NetworkHandler; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Objects; + public class OpenParticle implements ModInitializer { + public static final boolean isDebug = true; public static final String MOD_ID = "openparticle"; - public static final boolean isDebug = false; + private static final Logger LOGGER = LogUtils.getLogger(); @Override public void onInitialize() { + URL source = Objects.requireNonNull(getClass().getClassLoader().getResource("native/libOpenParticle.dll")); + Path dest = MinecraftClient.getInstance().runDirectory.toPath().resolve("openparticle").resolve("libOpenParticle.dll"); + try { + if (!Files.exists(dest.getParent())) { + Files.createDirectories(dest.getParent()); + } + Files.copy(source.openStream(), dest, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + LOGGER.error("fail to copy native core of open particle", e); + if (!Files.exists(dest)) { + throw new RuntimeException(e); + } + } + System.load(dest.toString()); NetworkHandler.initServer(); KeyboardManager.init(false); Registry.register(Registries.PARTICLE_TYPE, new Identifier(MOD_ID, "better_particle"), FabricParticleTypes.simple()); diff --git a/src/main/java/yancey/openparticle/core/client/GuiProgressBar.java b/src/main/java/yancey/openparticle/core/client/GuiProgressBar.java index 5222f80..d3f5a99 100644 --- a/src/main/java/yancey/openparticle/core/client/GuiProgressBar.java +++ b/src/main/java/yancey/openparticle/core/client/GuiProgressBar.java @@ -47,7 +47,7 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { renderBackground(context, mouseX, mouseY, delta); context.drawCenteredTextWithShadow(textRenderer, title, width / 2, height / 2 - textRenderer.fontHeight - 20, 0xFFFFFFFF); if (progress > max || progress < 0) { - logger.warn("progress bar can't show,the max progress is " + max + ",but the progress " + progress); + logger.warn("progress bar can't show,the max progress is {},but the progress {}", max, progress); close(); return; } diff --git a/src/main/java/yancey/openparticle/core/client/OpenParticleClient.java b/src/main/java/yancey/openparticle/core/client/OpenParticleClient.java index 5e6c4e9..e850116 100644 --- a/src/main/java/yancey/openparticle/core/client/OpenParticleClient.java +++ b/src/main/java/yancey/openparticle/core/client/OpenParticleClient.java @@ -1,8 +1,6 @@ package yancey.openparticle.core.client; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import yancey.openparticle.core.command.CommandPar; import yancey.openparticle.core.keys.KeyboardManager; import yancey.openparticle.core.network.NetworkHandler; @@ -11,7 +9,7 @@ public class OpenParticleClient implements ClientModInitializer { public void onInitializeClient() { NetworkHandler.initClient(); KeyboardManager.init(true); - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> - CommandPar.init(dispatcher, false)); +// ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> +// CommandPar.init(dispatcher, false)); } } diff --git a/src/main/java/yancey/openparticle/core/client/ParticleAsyncManager.java b/src/main/java/yancey/openparticle/core/client/ParticleAsyncManager.java deleted file mode 100644 index de2170a..0000000 --- a/src/main/java/yancey/openparticle/core/client/ParticleAsyncManager.java +++ /dev/null @@ -1,222 +0,0 @@ -package yancey.openparticle.core.client; - -import com.mojang.logging.LogUtils; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.particle.Particle; -import net.minecraft.client.render.Camera; -import net.minecraft.client.render.VertexConsumer; -import net.minecraft.client.world.ClientWorld; -import net.minecraft.world.World; -import org.slf4j.Logger; -import yancey.openparticle.api.common.controller.SimpleParticleController; -import yancey.openparticle.core.mixin.BufferBuilderAccessor; -import yancey.openparticle.core.mixin.ParticleManagerAccessor; -import yancey.openparticle.core.particle.BetterParticle; - -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class ParticleAsyncManager { - - private static final Logger LOGGER = LogUtils.getLogger(); - private static final int thread = 128; - private static final ExecutorService executorService = Executors.newFixedThreadPool(thread); - - public static Camera lastCamera; - public static float lastTickDelta; - private static CompletableFuture[] tickFutures, createFutures; - - @SuppressWarnings("unchecked") - public static void renderParticles(VertexConsumer vertexConsumer) { - Queue queue = ((ParticleManagerAccessor) MinecraftClient.getInstance().particleManager) - .getParticles().get(BetterParticle.BETTER_PARTICLE_SHEET); - int size = queue.size(); - int elementOffset = 112 * size; - ((BufferBuilderAccessor) vertexConsumer).invokeGrow(elementOffset); - BetterParticle.buildRenderCache(lastCamera); - CompletableFuture[] futures = new CompletableFuture[thread]; - Particle[] particles = queue.toArray(new Particle[0]); - int nums = size / thread; - for (int i = 0; i < thread; i++) { - int start = i * nums; - int end = i + 1 == thread ? size : (i + 1) * nums; - futures[i] = CompletableFuture.runAsync(() -> { - int j = start; - while (j < end) { - ((BetterParticle) particles[j]) - .buildGeometryAsync(vertexConsumer, lastCamera, lastTickDelta, j++ * 112); - } - }, executorService); - } - try { - for (CompletableFuture future : futures) { - future.get(); - } - } catch (InterruptedException | ExecutionException e) { - LOGGER.error("多线程VBO注入失败", e); - return; - } - ((BufferBuilderAccessor) vertexConsumer).setElementOffset(elementOffset); - ((BufferBuilderAccessor) vertexConsumer).setVertexCount(4 * size); - } - - @SuppressWarnings("unchecked") - public static void tick() { - //先把上次提前做的准备获取一下 - try { - if (createFutures != null) { - for (CompletableFuture future : createFutures) { - future.get(); - } - createFutures = null; - } - if (tickFutures != null) { - for (CompletableFuture future : tickFutures) { - future.get(); - } - tickFutures = null; - } - } catch (InterruptedException | ExecutionException e) { - LOGGER.error("多线程构建粒子状态缓存失败", e); - return; - } - - //计算当前tick - Queue queue = ((ParticleManagerAccessor) MinecraftClient.getInstance().particleManager) - .getParticles().get(BetterParticle.BETTER_PARTICLE_SHEET); - if (queue == null) { - return; - } - int size = queue.size(); - if (size == 0) { - return; - } - Particle[] particles = queue.toArray(new Particle[0]); - int nums = size / thread; - CompletableFuture>[] futures = new CompletableFuture[thread]; - for (int i = 0; i < thread; i++) { - int start = i * nums; - int end = i + 1 == thread ? size : (i + 1) * nums; - futures[i] = CompletableFuture.supplyAsync(() -> { - int j = start; - List needToClear = new ArrayList<>(); - while (j < end) { - particles[j].tick(); - if (!particles[j].isAlive()) { - needToClear.add(j); - } - j++; - } - return needToClear; - }, executorService); - } - List needToClear = new ArrayList<>(); - try { - for (CompletableFuture> future : futures) { - needToClear.addAll(future.get()); - } - } catch (InterruptedException | ExecutionException e) { - LOGGER.error("多线程获取粒子状态失败", e); - return; - } - - //清除粒子 - if (!needToClear.isEmpty()) { - queue.clear(); - if (needToClear.size() != size) { - Particle[] dest = new Particle[size - needToClear.size()]; - nums = needToClear.size() / thread; - CompletableFuture[] futures2 = new CompletableFuture[thread]; - for (int i = 0; i < thread; i++) { - int start = i * nums; - int end = i + 1 == thread ? needToClear.size() : (i + 1) * nums; - futures2[i] = CompletableFuture.runAsync(() -> { - int j = start; - int srcPos, destPos; - while (j < end) { - srcPos = j == 0 ? 0 : needToClear.get(j - 1) + 1; - destPos = srcPos - j; - System.arraycopy(particles, srcPos, dest, destPos, needToClear.get(j) - srcPos); - j++; - } - if (end == needToClear.size()) { - srcPos = needToClear.get(needToClear.size() - 1) + 1; - if (srcPos < particles.length) { - System.arraycopy(particles, srcPos, dest, srcPos - needToClear.size(), particles.length - srcPos); - } - } - }, executorService); - } - try { - for (CompletableFuture future : futures2) { - future.get(); - } - } catch (InterruptedException | ExecutionException e) { - LOGGER.error("多线程清除粒子失败", e); - return; - } - queue.addAll(List.of(dest)); - } - } - - //准备下一个tick - tickFutures = prepareParticles(particles); - } - - @SuppressWarnings("unchecked") - public static void addParticles(World world, List controllerList) { - //添加粒子 - int size = controllerList.size(); - Particle[] particles = new Particle[size]; - int nums = size / thread; - CompletableFuture[] futures = new CompletableFuture[thread]; - for (int i = 0; i < thread; i++) { - int start = i * nums; - int end = i + 1 == thread ? size : (i + 1) * nums; - futures[i] = CompletableFuture.runAsync(() -> { - int j = start; - while (j < end) { - particles[j] = (BetterParticle.create((ClientWorld) world, controllerList.get(j))); - j++; - } - }, executorService); - } - try { - for (CompletableFuture future : futures) { - future.get(); - } - } catch (InterruptedException | ExecutionException e) { - LOGGER.error("多线程新建粒子失败", e); - } - MinecraftClient client = MinecraftClient.getInstance(); - for (Particle particle : particles) { - client.particleManager.addParticle(particle); - } - - //构建下一个tick的缓存 - createFutures = prepareParticles(particles); - } - - @SuppressWarnings("unchecked") - private static CompletableFuture[] prepareParticles(Particle[] particles) { - CompletableFuture[] futures = new CompletableFuture[thread]; - int nums = particles.length / thread; - for (int i = 0; i < thread; i++) { - int start = i * nums; - int end = i + 1 == thread ? particles.length : (i + 1) * nums; - futures[i] = CompletableFuture.runAsync(() -> { - int j = start; - while (j < end) { - ((BetterParticle) particles[j++]).prepare(); - } - }, executorService); - } - return futures; - } - -} diff --git a/src/main/java/yancey/openparticle/core/command/CommandPar.java b/src/main/java/yancey/openparticle/core/command/CommandPar.java index 5763a1c..d6b8a1a 100644 --- a/src/main/java/yancey/openparticle/core/command/CommandPar.java +++ b/src/main/java/yancey/openparticle/core/command/CommandPar.java @@ -12,6 +12,7 @@ import net.minecraft.network.PacketByteBuf; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Identifier; import net.minecraft.world.World; import yancey.openparticle.core.core.OpenParticleCore; @@ -35,7 +36,7 @@ private static void command(CommandDispatcher dispa builder.requires(source -> source.hasPermissionLevel(2)); } else { builder.then(literal.apply("stop").executes(context -> { - OpenParticleCore.clearParticle(); + OpenParticleCore.stop(); return 1; })); } @@ -77,13 +78,17 @@ private static Command executeFile(Identifier ident } else { throw new RuntimeException("unknown command source"); } + if (world.isClient) { + return -1; + } + ServerWorld serverWorld = (ServerWorld) world; boolean isSuccess = true; if (identifier == NetworkHandler.ID_LOAD) { isSuccess = OpenParticleCore.loadFile(path); } else if (identifier == NetworkHandler.ID_RUN) { - OpenParticleCore.run(OpenParticleCore.lastPath, world); + OpenParticleCore.run(serverWorld); } else if (identifier == NetworkHandler.ID_LOAD_AND_RUN) { - isSuccess = OpenParticleCore.loadAndRun(path, world); + isSuccess = OpenParticleCore.loadAndRun(path, serverWorld); } else { throw new RuntimeException("unknown identifier -> " + identifier); } diff --git a/src/main/java/yancey/openparticle/core/core/OpenParticleCore.java b/src/main/java/yancey/openparticle/core/core/OpenParticleCore.java index 9a122a3..82c4356 100644 --- a/src/main/java/yancey/openparticle/core/core/OpenParticleCore.java +++ b/src/main/java/yancey/openparticle/core/core/OpenParticleCore.java @@ -3,32 +3,34 @@ import com.mojang.logging.LogUtils; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.fabricmc.fabric.mixin.client.particle.ParticleManagerAccessor.SimpleSpriteProviderAccessor; import net.minecraft.client.MinecraftClient; import net.minecraft.client.particle.SpriteProvider; -import net.minecraft.particle.ParticleType; -import net.minecraft.registry.Registries; -import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.client.render.BufferBuilder; +import net.minecraft.client.render.Camera; +import net.minecraft.client.texture.Sprite; +import net.minecraft.server.world.ServerWorld; import net.minecraft.text.HoverEvent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.GameRules; -import net.minecraft.world.World; +import org.joml.Quaternionf; import org.slf4j.Logger; -import yancey.openparticle.api.common.OpenParticleAPI; -import yancey.openparticle.api.common.data.DataRunningPerTick; +import yancey.openparticle.api.common.nativecore.OpenParticleProject; +import yancey.openparticle.core.events.RunningEventManager; +import yancey.openparticle.core.mixin.BufferBuilderAccessor; import yancey.openparticle.core.mixin.ParticleManagerAccessor; -import yancey.openparticle.core.util.MyLogger; +import yancey.openparticle.core.network.NetworkHandler; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.concurrent.locks.ReentrantLock; @Environment(EnvType.CLIENT) @@ -36,52 +38,41 @@ public class OpenParticleCore { private static final Map spriteAwareFactories = ((ParticleManagerAccessor) MinecraftClient.getInstance().particleManager).getSpriteAwareFactories(); private static final Logger LOGGER = LogUtils.getLogger(); - public static final OpenParticleAPI CORE = new OpenParticleAPI( - identifier -> { - Optional>> entry = Registries.PARTICLE_TYPE.getEntry(Registries.PARTICLE_TYPE.getRawId(Registries.PARTICLE_TYPE.get( - new net.minecraft.util.Identifier(identifier.getNamespace(), identifier.getValue())))); - return entry.map(particleTypeReference -> spriteAwareFactories.get(Registries.PARTICLE_TYPE.getId(particleTypeReference.value()))).orElse(null); - }, - new MyLogger(LOGGER) - ); + public static final OpenParticleProject.Bridge bridge = (namespace, value) -> { + SpriteProvider spriteProvider = spriteAwareFactories.get(new Identifier(namespace, value)); + List sprites = ((SimpleSpriteProviderAccessor) spriteProvider).getSprites(); + float[] result = new float[sprites.size() * 4]; + for (int i = 0; i < sprites.size(); i++) { + Sprite sprite = sprites.get(i); + int j = i * 4; + result[j] = sprite.getMinU(); + result[j + 1] = sprite.getMinV(); + result[j + 2] = sprite.getMaxU(); + result[j + 3] = sprite.getMaxV(); + } + return result; + }; private static final ReentrantLock LOCK = new ReentrantLock(); - public static String lastPath = null; - private static RunningHandler runningHandler = null; - public static DataRunningPerTick[] dataRunningList = null; + public static OpenParticleProject openParticleProject; + public static int nextTick = Integer.MAX_VALUE; + public static int nextRunTick = Integer.MAX_VALUE; private OpenParticleCore() { } + private static float lastRunTick = -1; + private static float lastTickDelta = -1; - public static void clearParticle() { - if (runningHandler != null) { - runningHandler.stop(); - runningHandler = null; + public static boolean loadFile0(String path) { + stop(); + if (openParticleProject != null) { + openParticleProject.close(); } - ((ParticleManagerAccessor) MinecraftClient.getInstance().particleManager).invokeClearParticles(); - } - - public static boolean loadFile(String path) { - LOCK.lock(); - dataRunningList = null; - runningHandler = null; - MinecraftClient client = MinecraftClient.getInstance(); try { - long timeStart = System.currentTimeMillis(); - dataRunningList = CORE.input(new File(path)).getDataRunningList(); - long timeEnd = System.currentTimeMillis(); - if (client.world != null) { - if (client.world.getGameRules().getBoolean(GameRules.COMMAND_BLOCK_OUTPUT)) { - client.inGameHud.getChatHud().addMessage(Text.empty() - .append(Text.literal("粒子文件加载成功(耗时")) - .append(Text.literal((timeEnd - timeStart) + "ms").formatted(Formatting.AQUA)) - .append(Text.literal(")")) - ); - } - } - lastPath = path; + openParticleProject = new OpenParticleProject(bridge, path); return true; - } catch (IOException e) { + } catch (Exception e) { + MinecraftClient client = MinecraftClient.getInstance(); if (!Files.exists(Path.of(path))) { client.inGameHud.getChatHud().addMessage(Text.empty() .append(Text.literal("粒子文件加载失败(找不到文件: ").formatted(Formatting.RED)) @@ -89,7 +80,7 @@ public static boolean loadFile(String path) { .append(Text.literal(")").formatted(Formatting.RED)) ); } else { - client.inGameHud.getChatHud().addMessage(Text.literal("未知原因").formatted(Formatting.RED).styled(style -> { + client.inGameHud.getChatHud().addMessage(Text.literal("粒子文件加载失败").formatted(Formatting.RED).styled(style -> { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); e.printStackTrace(printWriter); @@ -97,41 +88,74 @@ public static boolean loadFile(String path) { })); } return false; - } finally { - LOCK.unlock(); } } - public static void run(String path, World world) { + public static boolean loadFile(String path) { LOCK.lock(); - clearParticle(); - if (!Objects.equals(lastPath, path) && !loadFile(path)) { - return; - } - if (dataRunningList != null) { - runningHandler = new RunningHandler(world, dataRunningList); - runningHandler.run(); + try { + MinecraftClient client = MinecraftClient.getInstance(); + long timeStart = System.currentTimeMillis(); + boolean isSuccess = loadFile0(path); + long timeEnd = System.currentTimeMillis(); + if (isSuccess && client.world != null) { + if (client.world.getGameRules().getBoolean(GameRules.COMMAND_BLOCK_OUTPUT)) { + client.inGameHud.getChatHud().addMessage(Text.empty() + .append(Text.literal("粒子文件加载成功(耗时")) + .append(Text.literal((timeEnd - timeStart) + "ms").formatted(Formatting.AQUA)) + .append(Text.literal(")")) + ); + } + } + return isSuccess; + } finally { + LOCK.unlock(); } - LOCK.unlock(); } - public static void runTick(String path, World world, int tick) { - LOCK.lock(); - if (!Objects.equals(lastPath, path) && !loadFile(path)) { - return; + public static void stop() { + nextTick = Integer.MAX_VALUE; + RunningEventManager.INSTANCE.stop(); + } + + public static void run(ServerWorld world) { + if (openParticleProject != null) { + run(openParticleProject.path, world); } - if (dataRunningList != null) { - for (DataRunningPerTick dataRunning : runningHandler.dataRunningList) { - if (dataRunning.tick == tick) { - runningHandler.runTick(world, dataRunning.controllerList); - break; + } + + public static void run(String path, ServerWorld world) { + stop(); + nextTick = 0; + RunningEventManager.INSTANCE.run(() -> { + LOCK.lock(); + try { + if (openParticleProject == null || nextTick > openParticleProject.tickEnd) { + stop(); + return; } + NetworkHandler.runTick(world, path, nextTick++); + } finally { + LOCK.unlock(); } + }); + } + + public static void runTick(String path, int tick) { + LOCK.lock(); + try { + if (((openParticleProject == null || !Objects.equals(openParticleProject.path, path) && !loadFile0(path))) + || tick < 0 || tick > openParticleProject.tickEnd) { + nextRunTick = Integer.MAX_VALUE; + return; + } + nextRunTick = tick; + } finally { + LOCK.unlock(); } - LOCK.unlock(); } - public static boolean loadAndRun(String path, World world) { + public static boolean loadAndRun(String path, ServerWorld world) { if (loadFile(path)) { run(path, world); return true; @@ -140,4 +164,56 @@ public static boolean loadAndRun(String path, World world) { } } + public static int getParticleSize() { + LOCK.lock(); + try { + return openParticleProject == null ? 0 : openParticleProject.getParticleCount(); + } finally { + LOCK.unlock(); + } + } + + public static void tick() { + LOCK.lock(); + try { + if (nextRunTick != Integer.MAX_VALUE) { + openParticleProject.tick(nextRunTick); + } + } finally { + LOCK.unlock(); + } + } + + public static void render(Camera camera, float tickDelta, BufferBuilder bufferBuilder) { + LOCK.lock(); + try { + if (nextRunTick == Integer.MAX_VALUE || openParticleProject == null) { + return; + } + int particleCount = openParticleProject.getParticleCount(); + if (particleCount == 0) { + return; + } + if (nextRunTick == lastRunTick && tickDelta == lastTickDelta) { + return; + } + lastRunTick = nextRunTick; + lastTickDelta = tickDelta; + int elementOffset = 112 * particleCount; + ((BufferBuilderAccessor) bufferBuilder).invokeGrow(elementOffset); + Vec3d pos = camera.getPos(); + Quaternionf rotation = camera.getRotation(); + openParticleProject.render( + ((BufferBuilderAccessor) bufferBuilder).getBuffer(), + false, tickDelta, + (float) pos.x, (float) pos.y, (float) pos.z, + rotation.x, rotation.y, rotation.z, rotation.w + ); + ((BufferBuilderAccessor) bufferBuilder).setElementOffset(elementOffset); + ((BufferBuilderAccessor) bufferBuilder).setVertexCount(4 * particleCount); + } finally { + LOCK.unlock(); + } + } + } diff --git a/src/main/java/yancey/openparticle/core/core/RunningHandler.java b/src/main/java/yancey/openparticle/core/core/RunningHandler.java deleted file mode 100644 index 4955813..0000000 --- a/src/main/java/yancey/openparticle/core/core/RunningHandler.java +++ /dev/null @@ -1,66 +0,0 @@ -package yancey.openparticle.core.core; - -import net.minecraft.server.world.ServerWorld; -import net.minecraft.world.World; -import yancey.openparticle.api.common.controller.SimpleParticleController; -import yancey.openparticle.api.common.data.DataRunningPerTick; -import yancey.openparticle.core.client.ParticleAsyncManager; -import yancey.openparticle.core.events.RunningEventManager; -import yancey.openparticle.core.network.NetworkHandler; - -import java.util.List; - -public class RunningHandler { - public final World world; - public boolean isRunning = false; - public final DataRunningPerTick[] dataRunningList; - private int tick = 0; - private int which = 0; - - public RunningHandler(World world, DataRunningPerTick[] dataRunningList) { - this.dataRunningList = dataRunningList; - this.world = world; - } - - public void run() { - if (dataRunningList != null && dataRunningList.length != 0) { - isRunning = true; - tick = Math.min(dataRunningList[0].tick, 0); - which = 0; - RunningEventManager.INSTANCE.run(this); - } else if (isRunning) { - stop(); - } - } - - public void stop() { - isRunning = false; - tick = 0; - which = 0; - RunningEventManager.INSTANCE.stop(this); - } - - public void runPerTick() { - if (dataRunningList == null) { - stop(); - return; - } - while (which < dataRunningList.length) { - if (tick != dataRunningList[which].tick) { - tick++; - return; - } - if (world.isClient) { - runTick(world, dataRunningList[which++].controllerList); - } else { - which++; - NetworkHandler.summonParticle((ServerWorld) world, OpenParticleCore.lastPath, tick); - } - } - stop(); - } - - public void runTick(World world, List controllerList) { - ParticleAsyncManager.addParticles(world, controllerList); - } -} diff --git a/src/main/java/yancey/openparticle/core/events/RunningEventManager.java b/src/main/java/yancey/openparticle/core/events/RunningEventManager.java index 21c693b..27d1041 100644 --- a/src/main/java/yancey/openparticle/core/events/RunningEventManager.java +++ b/src/main/java/yancey/openparticle/core/events/RunningEventManager.java @@ -2,50 +2,47 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.minecraft.server.MinecraftServer; -import yancey.openparticle.core.core.RunningHandler; -import java.util.ArrayList; -import java.util.List; +import java.util.concurrent.locks.ReentrantLock; public class RunningEventManager implements ServerTickEvents.StartTick { public static final RunningEventManager INSTANCE = new RunningEventManager(); - private final List runningHandlerList = new ArrayList<>(); - private final List runningHandlerAddList = new ArrayList<>(); - private final List runningHandlerRemoveList = new ArrayList<>(); private RunningEventManager() { ServerTickEvents.START_SERVER_TICK.register(this); } - public void run(RunningHandler runningHandler) { - if (!runningHandlerAddList.contains(runningHandler)) { - runningHandlerAddList.add(runningHandler); + private final ReentrantLock lock = new ReentrantLock(); + private Runnable runnable; + + public void run(Runnable runnable) { + lock.lock(); + try { + this.runnable = runnable; + } finally { + lock.unlock(); } } - public void stop(RunningHandler runningHandler) { - if (!runningHandlerRemoveList.contains(runningHandler)) { - runningHandlerRemoveList.add(runningHandler); + public void stop() { + lock.lock(); + try { + this.runnable = null; + } finally { + lock.unlock(); } } @Override public void onStartTick(MinecraftServer server) { - if (!runningHandlerRemoveList.isEmpty()) { - runningHandlerList.removeAll(runningHandlerRemoveList); - runningHandlerRemoveList.clear(); - } - if (!runningHandlerAddList.isEmpty()) { - runningHandlerList.addAll(runningHandlerAddList); - runningHandlerAddList.clear(); - } - for (RunningHandler runningHandler : runningHandlerList) { - if (runningHandler.isRunning) { - runningHandler.runPerTick(); - } else { - runningHandler.stop(); + lock.lock(); + try { + if (runnable != null) { + runnable.run(); } + } finally { + lock.unlock(); } } } diff --git a/src/main/java/yancey/openparticle/core/keys/KeyboardManager.java b/src/main/java/yancey/openparticle/core/keys/KeyboardManager.java index 1e8b22d..ba4571d 100644 --- a/src/main/java/yancey/openparticle/core/keys/KeyboardManager.java +++ b/src/main/java/yancey/openparticle/core/keys/KeyboardManager.java @@ -4,6 +4,7 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; import org.lwjgl.glfw.GLFW; import yancey.openparticle.core.core.OpenParticleCore; import yancey.openparticle.core.mixin.KeyBindingAccessor; @@ -20,7 +21,7 @@ public class KeyboardManager { public static void init(boolean isClient) { register(isClient, "run", GLFW.GLFW_KEY_V, true, - playerEntity -> OpenParticleCore.run(OpenParticleCore.lastPath, playerEntity.getWorld()) + playerEntity -> OpenParticleCore.run((ServerWorld) playerEntity.getWorld()) ); if (!isClient) { return; diff --git a/src/main/java/yancey/openparticle/core/mixin/BufferBuilderAccessor.java b/src/main/java/yancey/openparticle/core/mixin/BufferBuilderAccessor.java index e20f092..a0042e9 100644 --- a/src/main/java/yancey/openparticle/core/mixin/BufferBuilderAccessor.java +++ b/src/main/java/yancey/openparticle/core/mixin/BufferBuilderAccessor.java @@ -9,15 +9,10 @@ @Mixin(BufferBuilder.class) public interface BufferBuilderAccessor { - @Accessor - int getElementOffset(); @Accessor void setElementOffset(int elementOffset); - @Accessor - int getVertexCount(); - @Accessor void setVertexCount(int vertexCount); diff --git a/src/main/java/yancey/openparticle/core/mixin/InGameHudMixin.java b/src/main/java/yancey/openparticle/core/mixin/InGameHudMixin.java index 077084f..dbeebab 100644 --- a/src/main/java/yancey/openparticle/core/mixin/InGameHudMixin.java +++ b/src/main/java/yancey/openparticle/core/mixin/InGameHudMixin.java @@ -8,6 +8,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import yancey.openparticle.core.OpenParticle; +import yancey.openparticle.core.core.OpenParticleCore; @Mixin(InGameHud.class) public class InGameHudMixin { @@ -16,8 +17,10 @@ public void render(DrawContext context, float tickDelta, CallbackInfo info) { if (OpenParticle.isDebug) { MinecraftClient client = MinecraftClient.getInstance(); if (!client.options.hudHidden && !client.getDebugHud().shouldShowDebugHud()) { - String text = ((MinecraftClientAccessor) client).getCurrentFps() + " FPS"; - context.drawText(client.textRenderer, text, 5, 7, 0xFFFFFFFF, false); + String textFPS = "FPS: " + ((MinecraftClientAccessor) client).getCurrentFps(); + context.drawText(client.textRenderer, textFPS, 5, 7, 0xFFFFFFFF, false); + String textParticleCount = "P: " + OpenParticleCore.getParticleSize(); + context.drawText(client.textRenderer, textParticleCount, 5, 7 + client.textRenderer.fontHeight, 0xFFFFFFFF, false); } } } diff --git a/src/main/java/yancey/openparticle/core/mixin/ParticleManagerAccessor.java b/src/main/java/yancey/openparticle/core/mixin/ParticleManagerAccessor.java index 1e5065b..dbda2a5 100644 --- a/src/main/java/yancey/openparticle/core/mixin/ParticleManagerAccessor.java +++ b/src/main/java/yancey/openparticle/core/mixin/ParticleManagerAccessor.java @@ -1,17 +1,12 @@ package yancey.openparticle.core.mixin; - -import net.minecraft.client.particle.Particle; import net.minecraft.client.particle.ParticleManager; -import net.minecraft.client.particle.ParticleTextureSheet; import net.minecraft.client.particle.SpriteProvider; import net.minecraft.util.Identifier; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; -import org.spongepowered.asm.mixin.gen.Invoker; import java.util.Map; -import java.util.Queue; @Mixin(ParticleManager.class) public interface ParticleManagerAccessor { @@ -19,12 +14,4 @@ public interface ParticleManagerAccessor { @Accessor("spriteAwareFactories") Map getSpriteAwareFactories(); - @Invoker("clearParticles") - void invokeClearParticles(); - - @Accessor - Map> getParticles(); - - @Accessor - Queue getNewParticles(); } diff --git a/src/main/java/yancey/openparticle/core/mixin/ParticleManagerMixin.java b/src/main/java/yancey/openparticle/core/mixin/ParticleManagerMixin.java index 7be57ab..85f7b95 100644 --- a/src/main/java/yancey/openparticle/core/mixin/ParticleManagerMixin.java +++ b/src/main/java/yancey/openparticle/core/mixin/ParticleManagerMixin.java @@ -1,89 +1,53 @@ package yancey.openparticle.core.mixin; -import com.google.common.collect.EvictingQueue; -import net.minecraft.client.particle.Particle; +import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.particle.ParticleManager; import net.minecraft.client.particle.ParticleTextureSheet; -import net.minecraft.client.render.Camera; -import net.minecraft.client.render.LightmapTextureManager; -import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.*; +import net.minecraft.client.texture.TextureManager; import net.minecraft.client.util.math.MatrixStack; -import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import yancey.openparticle.core.client.ParticleAsyncManager; -import yancey.openparticle.core.particle.BetterParticle; - -import java.util.*; +import yancey.openparticle.core.core.OpenParticleCore; @Mixin(ParticleManager.class) public abstract class ParticleManagerMixin { - @Mutable - @Shadow - @Final - private static List PARTICLE_TEXTURE_SHEETS; - @Shadow @Final - private Map> particles; + private TextureManager textureManager; - @Unique - private ParticleTextureSheet lastTextureSheet; - -// @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Ljava/util/Map;computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;")) -// private V redirectComputeIfAbsent(Map particles, K k, Function key) { -// return particles.computeIfAbsent(k, (sheet) -> (V) new LinkedList()); -// } - - @Redirect(method = {"method_18125"}, at = @At(value = "INVOKE", target = "Lcom/google/common/collect/EvictingQueue;create(I)Lcom/google/common/collect/EvictingQueue;", remap = false)) - private static EvictingQueue modifyArgTick(int maxSize) { - return EvictingQueue.create(500000); - } - - @Inject(method = "", at = @At(value = "RETURN")) - private static void modifyParticleTextureSheets(CallbackInfo ci) { - List particleTextureSheets = new ArrayList<>(PARTICLE_TEXTURE_SHEETS.size() + 1); - particleTextureSheets.addAll(PARTICLE_TEXTURE_SHEETS); - particleTextureSheets.add(BetterParticle.BETTER_PARTICLE_SHEET); - PARTICLE_TEXTURE_SHEETS = particleTextureSheets; - } @Inject(method = "renderParticles", at = @At(value = "HEAD")) - public void renderParticles(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers, LightmapTextureManager lightmapTextureManager, Camera camera, float tickDelta, CallbackInfo ci) { - ParticleAsyncManager.lastCamera = camera; - ParticleAsyncManager.lastTickDelta = tickDelta; - } - - @Redirect(method = "renderParticles", at = @At(value = "INVOKE", target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;")) - private Object redirectGet(Map> particles, Object particleTextureSheet) { - lastTextureSheet = (ParticleTextureSheet) particleTextureSheet; - return particles.get(lastTextureSheet); + private void injectRenderParticles(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers, LightmapTextureManager lightmapTextureManager, Camera camera, float tickDelta, CallbackInfo ci) { + lightmapTextureManager.enable(); + RenderSystem.enableDepthTest(); + MatrixStack matrixStack = RenderSystem.getModelViewStack(); + matrixStack.push(); + matrixStack.multiplyPositionMatrix(matrices.peek().getPositionMatrix()); + RenderSystem.applyModelViewMatrix(); + + RenderSystem.setShader(GameRenderer::getParticleProgram); + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferBuilder = tessellator.getBuffer(); + ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT.begin(bufferBuilder, textureManager); + OpenParticleCore.render(camera, tickDelta, bufferBuilder); + ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT.draw(tessellator); + + matrixStack.pop(); + RenderSystem.applyModelViewMatrix(); + RenderSystem.depthMask(true); + RenderSystem.disableBlend(); + lightmapTextureManager.disable(); } - /** - * @reason 因为不是通过这个方法渲染,没有必要继续循环 - */ - @Redirect(method = "renderParticles", at = @At(value = "INVOKE", target = "Ljava/util/Iterator;hasNext()Z", ordinal = 1)) - private boolean redirectHasNext(Iterator iterator) { - if (lastTextureSheet == BetterParticle.BETTER_PARTICLE_SHEET) { - return false; - } - return iterator.hasNext(); - } - - @Inject(method = "tick", at = @At(value = "HEAD")) + @Inject(method = "tick", at = @At(value = "RETURN")) private void injectTick(CallbackInfo ci) { - ParticleAsyncManager.tick(); - } - - @Inject(method = "tickParticles", at = @At(value = "HEAD"), cancellable = true) - private void injectTickParticles(Collection particles, CallbackInfo ci) { - if (this.particles.get(BetterParticle.BETTER_PARTICLE_SHEET) == particles) { - ci.cancel(); - } + OpenParticleCore.tick(); } } \ No newline at end of file diff --git a/src/main/java/yancey/openparticle/core/network/NetworkHandler.java b/src/main/java/yancey/openparticle/core/network/NetworkHandler.java index 1f10cdc..8cb1ffd 100644 --- a/src/main/java/yancey/openparticle/core/network/NetworkHandler.java +++ b/src/main/java/yancey/openparticle/core/network/NetworkHandler.java @@ -20,7 +20,7 @@ public class NetworkHandler { public static final Identifier ID_KEY_BOARD = new Identifier(MOD_ID, "key_board"); - public static final Identifier ID_SUMMON_PARTICLE = new Identifier(MOD_ID, "summon_particle"); + public static final Identifier ID_RUN_TICK = new Identifier(MOD_ID, "run_tick"); public static final Identifier ID_LOAD = new Identifier(MOD_ID, "load_in_client"); public static final Identifier ID_RUN = new Identifier(MOD_ID, "run_in_client"); public static final Identifier ID_LOAD_AND_RUN = new Identifier(MOD_ID, "load_and_run_in_client"); @@ -38,29 +38,29 @@ public static void initServer() { @Environment(EnvType.CLIENT) public static void initClient() { - //生成粒子 - ClientPlayNetworking.registerGlobalReceiver(ID_SUMMON_PARTICLE, (client, handler, buf, responseSender) -> { + //运行指定tick的粒子 + ClientPlayNetworking.registerGlobalReceiver(ID_RUN_TICK, (client, handler, buf, responseSender) -> { if (handler.getWorld() != null) { String path = buf.readString(); int tick = buf.readInt(); - OpenParticleCore.runTick(path.isEmpty() ? null : path, handler.getWorld(), tick); + OpenParticleCore.runTick(path.isEmpty() ? null : path, tick); } }); //加载并运行粒子文件 - ClientPlayNetworking.registerGlobalReceiver(ID_LOAD_AND_RUN, (client, handler, buf, responseSender) -> { - if (handler.getWorld() != null) { - OpenParticleCore.loadAndRun(buf.readString(), handler.getWorld()); - } - }); +// ClientPlayNetworking.registerGlobalReceiver(ID_LOAD_AND_RUN, (client, handler, buf, responseSender) -> { +// if (handler.getWorld() != null) { +// OpenParticleCore.loadAndRun(buf.readString(), handler.getWorld()); +// } +// }); //加载粒子文件 - ClientPlayNetworking.registerGlobalReceiver(ID_LOAD, (client, handler, buf, responseSender) -> - OpenParticleCore.loadFile(buf.readString())); +// ClientPlayNetworking.registerGlobalReceiver(ID_LOAD, (client, handler, buf, responseSender) -> +// OpenParticleCore.loadFile(buf.readString())); //运行粒子文件 - ClientPlayNetworking.registerGlobalReceiver(ID_RUN, (client, handler, buf, responseSender) -> { - if (handler.getWorld() != null) { - OpenParticleCore.run(OpenParticleCore.lastPath, handler.getWorld()); - } - }); +// ClientPlayNetworking.registerGlobalReceiver(ID_RUN, (client, handler, buf, responseSender) -> { +// if (handler.getWorld() != null) { +// OpenParticleCore.run(OpenParticleCore.lastPath, handler.getWorld()); +// } +// }); } @Environment(EnvType.CLIENT) @@ -74,13 +74,13 @@ public static void keyBoardToServer(List idList) { ClientPlayNetworking.send(ID_KEY_BOARD, buf); } - public static void summonParticle(ServerWorld world, String path, int tick) { + public static void runTick(ServerWorld world, String path, int tick) { //生成粒子 PacketByteBuf packetByteBuf = PacketByteBufs.create(); packetByteBuf.writeString(Objects.requireNonNullElse(path, "")); packetByteBuf.writeInt(tick); for (ServerPlayerEntity serverPlayerEntity : world.getServer().getPlayerManager().getPlayerList()) { - ServerPlayNetworking.send(serverPlayerEntity, ID_SUMMON_PARTICLE, packetByteBuf); + ServerPlayNetworking.send(serverPlayerEntity, ID_RUN_TICK, packetByteBuf); } } } diff --git a/src/main/java/yancey/openparticle/core/particle/BetterParticle.java b/src/main/java/yancey/openparticle/core/particle/BetterParticle.java deleted file mode 100644 index b1b3101..0000000 --- a/src/main/java/yancey/openparticle/core/particle/BetterParticle.java +++ /dev/null @@ -1,167 +0,0 @@ -package yancey.openparticle.core.particle; - -import com.mojang.blaze3d.systems.RenderSystem; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.particle.Particle; -import net.minecraft.client.particle.ParticleTextureSheet; -import net.minecraft.client.particle.SpriteProvider; -import net.minecraft.client.render.*; -import net.minecraft.client.texture.Sprite; -import net.minecraft.client.texture.SpriteAtlasTexture; -import net.minecraft.client.texture.TextureManager; -import net.minecraft.client.world.ClientWorld; -import net.minecraft.util.math.Vec3d; -import org.joml.Quaternionf; -import yancey.openparticle.api.common.controller.ParticleController; -import yancey.openparticle.api.common.data.ParticleState; -import yancey.openparticle.core.client.ParticleAsyncManager; -import yancey.openparticle.core.core.OpenParticleCore; -import yancey.openparticle.core.mixin.BufferBuilderAccessor; - -import java.nio.ByteBuffer; - -@Environment(EnvType.CLIENT) -public class BetterParticle extends Particle { - - private static float cacheX1, cacheY1, cacheZ1, cacheX2, cacheY2, cacheZ2; - - private ParticleState lastParticleState, particleState; - private Sprite nextSprite; public static final ParticleTextureSheet BETTER_PARTICLE_SHEET = new ParticleTextureSheet() { - public void begin(BufferBuilder builder, TextureManager textureManager) { - RenderSystem.depthMask(true); - RenderSystem.setShaderTexture(0, SpriteAtlasTexture.PARTICLE_ATLAS_TEXTURE); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - builder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT); - } - - public void draw(Tessellator tessellator) { - ParticleAsyncManager.renderParticles(tessellator.getBuffer()); - tessellator.draw(); - } - - public String toString() { - return "BETTER_PARTICLE_SHEET"; - } - }; - private static Vec3d lastCameraPos; - private final ParticleController particleController; - private final SpriteProvider spriteProvider; - private BetterParticle(ClientWorld world, ParticleState start, ParticleController particleController, SpriteProvider spriteProvider) { - super(world, start.x, start.y, start.z); - this.spriteProvider = spriteProvider; - this.particleController = particleController; - this.lastParticleState = start; - this.particleState = start; - this.maxAge = particleController.getAge(); - this.nextSprite = spriteProvider.getSprite(this.age, this.maxAge); - updateSprite(); - } - - public static BetterParticle create(ClientWorld world, ParticleController controller) { - controller.prepare(0); - return new BetterParticle(world, controller.getParticleState(), controller, (SpriteProvider) controller.getParticleSprites(OpenParticleCore.CORE)); - } - private float minU, maxU, minV, maxV; - - private void updateSprite() { - this.minU = this.nextSprite.getMinU(); - this.maxU = this.nextSprite.getMaxU(); - this.minV = this.nextSprite.getMinV(); - this.maxV = this.nextSprite.getMaxV(); - } - - public void prepare() { - if (age + 1 < age) { - nextSprite = spriteProvider.getSprite(this.age + 1, this.maxAge); - } - particleController.prepare(age + 1); - } - - public void tick() { - updateSprite(); - if (++this.age >= this.maxAge) { - this.markDead(); - return; - } - if (particleController.isStatic()) { - return; - } - this.lastParticleState = particleState; - this.particleState = particleController.getParticleState(); - } - - public void buildGeometryAsync(VertexConsumer vertexConsumer, Camera camera, float tickDelta, int elementOffset) { - Vec3d cameraPos = camera.getPos(); - float dx = lerp(tickDelta, this.lastParticleState.x, this.particleState.x) - (float) cameraPos.getX(); - float dy = lerp(tickDelta, this.lastParticleState.y, this.particleState.y) - (float) cameraPos.getY(); - float dz = lerp(tickDelta, this.lastParticleState.z, this.particleState.z) - (float) cameraPos.getZ(); - vertex(elementOffset, vertexConsumer, cacheX1 + dx, cacheY1 + dy, cacheZ1 + dz, maxU, maxV); - vertex(elementOffset + 28, vertexConsumer, cacheX2 + dx, cacheY2 + dy, cacheZ2 + dz, maxU, minV); - vertex(elementOffset + 56, vertexConsumer, -cacheX1 + dx, -cacheY1 + dy, -cacheZ1 + dz, minU, minV); - vertex(elementOffset + 84, vertexConsumer, -cacheX2 + dx, -cacheY2 + dy, -cacheZ2 + dz, minU, maxV); - } - - @Override - public String toString() { - return getClass().getSimpleName() + ", Pos (" + particleState.x + "," + particleState.y + "," + particleState.z + "), RGBA (" + particleState.r + "," + particleState.g + "," + particleState.b + "," + particleState.a + "), Age " + age; - } - - public static void buildRenderCache(Camera camera) { - Vec3d cameraPos = camera.getPos(); - if (cameraPos != lastCameraPos) { - Quaternionf rotation = camera.getRotation(); - float xx = rotation.x * rotation.x, yy = rotation.y * rotation.y, zz = rotation.z * rotation.z, ww = rotation.w * rotation.w; - float xy = rotation.x * rotation.y, xz = rotation.x * rotation.z, yz = rotation.y * rotation.z, xw = rotation.x * rotation.w; - float zw = rotation.z * rotation.w, yw = rotation.y * rotation.w, k = 1 / (xx + yy + zz + ww); - float a1 = (xx - yy - zz + ww) * k * -0.1F; - float b1 = 2 * (xy + zw) * k * -0.1F; - float c1 = 2 * (xz - yw) * k * -0.1F; - float a2 = 2 * (xy - zw) * k * 0.1F; - float b2 = (yy - xx - zz + ww) * k * 0.1F; - float c2 = 2 * (yz + xw) * k * 0.1F; - cacheX1 = a1 - a2; - cacheY1 = b1 - b2; - cacheZ1 = c1 - c2; - cacheX2 = a1 + a2; - cacheY2 = b1 + b2; - cacheZ2 = c1 + c2; - lastCameraPos = cameraPos; - } - } - - public static float lerp(float delta, float start, float end) { - return start + delta * (end - start); - } - - public ParticleTextureSheet getType() { - return BETTER_PARTICLE_SHEET; - } - - @Override - public void buildGeometry(VertexConsumer vertexConsumer, Camera camera, float tickDelta) { - // do nothing - } - - private void vertex(int elementOffset, VertexConsumer vertexConsumer, float x, float y, float z, float u, float v) { - ByteBuffer buffer = ((BufferBuilderAccessor) vertexConsumer).getBuffer(); - //vertex - buffer.putFloat(elementOffset, x); - buffer.putFloat(elementOffset + 4, y); - buffer.putFloat(elementOffset + 8, z); - //texture - buffer.putFloat(elementOffset + 12, u); - buffer.putFloat(elementOffset + 16, v); - //color - buffer.put(elementOffset + 20, particleState.r); - buffer.put(elementOffset + 21, particleState.g); - buffer.put(elementOffset + 22, particleState.b); - buffer.put(elementOffset + 23, particleState.a); - //light - buffer.putShort(elementOffset + 24, (short) (particleState.bright & '\uffff')); - buffer.putShort(elementOffset + 26, (short) (particleState.bright >> 16 & '\uffff')); - } - - -} diff --git a/src/main/java/yancey/openparticle/core/util/MyLogger.java b/src/main/java/yancey/openparticle/core/util/MyLogger.java deleted file mode 100644 index 9a66ac0..0000000 --- a/src/main/java/yancey/openparticle/core/util/MyLogger.java +++ /dev/null @@ -1,27 +0,0 @@ -package yancey.openparticle.core.util; - -import yancey.openparticle.api.common.bridge.Logger; - -public class MyLogger implements Logger { - - private final org.slf4j.Logger logger; - - public MyLogger(org.slf4j.Logger logger) { - this.logger = logger; - } - - @Override - public void info(String str) { - logger.info(str); - } - - @Override - public void warn(String str, Throwable throwable) { - logger.warn(str, throwable); - } - - @Override - public void warn(String str) { - logger.warn(str); - } -} diff --git a/src/main/resources/native/libOpenParticle.dll b/src/main/resources/native/libOpenParticle.dll new file mode 100644 index 0000000000000000000000000000000000000000..4be0b368cbac7762f46216d1917cde302ff522ac GIT binary patch literal 298139 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 Oo(vIC=%rEW>Hi0-wFrIy literal 0 HcmV?d00001