diff --git a/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/Interpolation.java b/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/Interpolation.java index d19e405a9..c15a767b2 100644 --- a/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/Interpolation.java +++ b/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/Interpolation.java @@ -1,20 +1,56 @@ package foundry.veil.api.client.necromancer.animation.keyframe; import net.minecraft.util.Mth; +import org.jetbrains.annotations.ApiStatus; import org.joml.Quaternionf; import org.joml.Quaternionfc; // todo: support cubic interpolation w/ derivatives public enum Interpolation { - NEAREST_NEIGHBOR( - (a, b, t) -> t < 0.5 ? a : b, - (a, b, t, result) -> result.set(t < 0.5 ? a : b) + /** + * @since 4.5.0 + */ + STEP( + (a, b, t) -> t < 1F ? a : b, + (a, b, t, result) -> result.set(t < 1 ? a : b) ), LINEAR( (a, b, t) -> Mth.lerp(t, a, b), Quaternionfc::slerp + ), + /** + * @since 4.5.0 + */ + EASE_IN( + (a, b, t) -> Mth.lerp(t * t, a, b), + (a, b, t, result) -> result.set(a).slerp(b, t * t) + ), + /** + * @since 4.5.0 + */ + EASE_OUT( + (a, b, t) -> Mth.lerp(1F - (1F - t) * (1F - t), a, b), + (a, b, t, result) -> result.set(a).slerp(b, 1F - (1F - t) * (1F - t)) + ), + /** + * @since 4.5.0 + */ + EASE_IN_OUT( + (a, b, t) -> Mth.lerp(easeInOut(t), a, b), + (a, b, t, result) -> result.set(a).slerp(b, easeInOut(t)) ); + /** + * @deprecated Use {@link #STEP} instead + */ + @ApiStatus.ScheduledForRemoval(inVersion = "5.0.0") + @Deprecated + public static final Interpolation NEAREST_NEIGHBOR = STEP; + + private static float easeInOut(float t) { + return t < 0.5F ? 2F * t * t : 1F - (float) Math.pow(-2F * t + 2F, 2) / 2F; + } + private final FloatInterpolator fInterpolator; private final QuaternionInterpolator qInterpolator; diff --git a/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/KeyframeTimeline.java b/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/KeyframeTimeline.java index 626a872e2..dbd9f5cec 100644 --- a/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/KeyframeTimeline.java +++ b/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/KeyframeTimeline.java @@ -13,11 +13,11 @@ protected float getAdjacentKeyframes(float time, boolean looped, Keyframe[] list listToPopulate[0] = keyframes[previousIndex]; int nextIndex = currentIndex + 1; - nextIndex = looped ? nextIndex % keyframes.length : Math.max(nextIndex, keyframes.length - 1); + nextIndex = looped ? nextIndex % keyframes.length : Math.min(nextIndex, keyframes.length - 1); listToPopulate[2] = keyframes[nextIndex]; int nextNextIndex = currentIndex + 2; - nextNextIndex = looped ? nextNextIndex % keyframes.length : Math.max(nextNextIndex, keyframes.length - 1); + nextNextIndex = looped ? nextNextIndex % keyframes.length : Math.min(nextNextIndex, keyframes.length - 1); listToPopulate[3] = keyframes[nextNextIndex]; // interpolation factor between the two keyframes @@ -43,12 +43,11 @@ private int findKeyframeIndex(float time, boolean looped) { float t1 = keyframes[mid].time(); float t2 = keyframes[mid + 1].time(); - // current time is between these two keyframes! - if (time > t1 && time < t2) { + if (time >= t1 && time < t2) { return mid; } else if (time > t1) { low = mid + 1; - } else if (time < t1) { + } else { high = mid - 1; } } diff --git a/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/KeyframedAnimation.java b/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/KeyframedAnimation.java index 485697432..2862997c1 100644 --- a/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/KeyframedAnimation.java +++ b/common/src/main/java/foundry/veil/api/client/necromancer/animation/keyframe/KeyframedAnimation.java @@ -61,7 +61,7 @@ public void apply(P parent, S skeleton, float mixFactor, float time) { Mth.lerp(mixFactor, 1, interpolation.interpolate(a.transform().sz(), b.transform().sz(), t)) ); } else { - bone.size.set( + bone.position.set( Mth.lerp(mixFactor, bone.position.x, interpolation.interpolate(a.transform().px(), b.transform().px(), t)), Mth.lerp(mixFactor, bone.position.y, interpolation.interpolate(a.transform().py(), b.transform().py(), t)), Mth.lerp(mixFactor, bone.position.z, interpolation.interpolate(a.transform().pz(), b.transform().pz(), t)) @@ -79,16 +79,16 @@ public void apply(P parent, S skeleton, float mixFactor, float time) { } } - public static class Builder { + public static class Builder

, S extends Skeleton> { boolean looped = false, additive = false; Map> timelines = new HashMap<>(); - Builder looped(boolean isLooped) { + public Builder looped(boolean isLooped) { this.looped = isLooped; return this; } - Builder additive(boolean isAdditive) { + public Builder additive(boolean isAdditive) { this.additive = isAdditive; return this; } @@ -104,7 +104,7 @@ public void addKeyframe(String boneId, float time, Interpolation interpolation, timelines.get(boneId).add(new Keyframe(time, interpolation, new Keyframe.KeyframeTransform(position, size, orientation))); } - public KeyframedAnimation build() { + public KeyframedAnimation build() { Map builtTimelines = new HashMap<>(); for (Map.Entry> timeline : timelines.entrySet()) { List keyframeList = timeline.getValue(); diff --git a/common/src/main/java/foundry/veil/api/client/necromancer/render/Skin.java b/common/src/main/java/foundry/veil/api/client/necromancer/render/Skin.java index 89d25643e..39365be8e 100644 --- a/common/src/main/java/foundry/veil/api/client/necromancer/render/Skin.java +++ b/common/src/main/java/foundry/veil/api/client/necromancer/render/Skin.java @@ -12,6 +12,7 @@ import foundry.veil.api.client.render.shader.block.DynamicShaderBlock; import foundry.veil.api.client.render.vertex.VertexArray; import foundry.veil.api.client.render.vertex.VertexArrayBuilder; +import foundry.veil.api.compat.ImmersivePortalsCompat; import it.unimi.dsi.fastutil.floats.FloatList; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; @@ -32,8 +33,7 @@ import java.util.List; import static org.lwjgl.opengl.ARBDirectStateAccess.glNamedBufferSubData; -import static org.lwjgl.opengl.GL15C.glBindBuffer; -import static org.lwjgl.opengl.GL15C.glBufferSubData; +import static org.lwjgl.opengl.GL15C.*; import static org.lwjgl.opengl.GL30C.glUniform1ui; import static org.lwjgl.opengl.GL31C.GL_UNIFORM_BUFFER; @@ -120,7 +120,7 @@ public void render(RenderType renderType, List transforms, List listeners = ((PipelineReloadableResourceManagerAccessor) resourceManager).getListeners(); @@ -269,6 +272,14 @@ public LightRenderer getLightRenderer() { return this.lightRenderer; } + /** + * @return The manager for screen shaking + * @since 4.5.0 + */ + public ScreenShakeManager getScreenShakeManager() { + return this.screenShakeManager; + } + /** * @return The gui info instance */ diff --git a/common/src/main/java/foundry/veil/api/client/render/ext/VeilDebug.java b/common/src/main/java/foundry/veil/api/client/render/ext/VeilDebug.java index dd8766df4..4de73825d 100644 --- a/common/src/main/java/foundry/veil/api/client/render/ext/VeilDebug.java +++ b/common/src/main/java/foundry/veil/api/client/render/ext/VeilDebug.java @@ -1,12 +1,10 @@ package foundry.veil.api.client.render.ext; import foundry.veil.Veil; +import foundry.veil.api.compat.ImmersivePortalsCompat; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import org.lwjgl.opengl.GL; -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GLCapabilities; -import org.lwjgl.opengl.KHRDebug; +import org.lwjgl.opengl.*; import java.util.concurrent.atomic.AtomicInteger; @@ -44,7 +42,9 @@ public void debugMessageInsert(int type, int id, int severity, CharSequence mess @Override public void objectLabel(int identifier, int name, @Nullable CharSequence label) { if (label != null) { - glObjectLabel(identifier, name, label); + if (!ImmersivePortalsCompat.isLoaded()) { + glObjectLabel(identifier, name, label); + } } else { nglObjectLabel(identifier, name, 0, 0L); } diff --git a/common/src/main/java/foundry/veil/api/client/render/framebuffer/AdvancedFbo.java b/common/src/main/java/foundry/veil/api/client/render/framebuffer/AdvancedFbo.java index ecd7f5ae2..914e2c412 100644 --- a/common/src/main/java/foundry/veil/api/client/render/framebuffer/AdvancedFbo.java +++ b/common/src/main/java/foundry/veil/api/client/render/framebuffer/AdvancedFbo.java @@ -580,6 +580,33 @@ static Builder copy(RenderTarget parent) { return AdvancedFboImpl.copy(parent); } + /** + * Gets the attachment type of the main depth buffer. + * @return Either {@code GL_DEPTH_ATTACHMENT} or {@code GL_DEPTH_STENCIL_ATTACHMENT} + * @apiNote If no match is found for whatever reason, {@code GL_DEPTH_ATTACHMENT} is returned. + */ + static int getDepthAttachmentType(int textureId) { + int boundTexture = glGetInteger(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, textureId); + + int depthFormat = glGetTexLevelParameteri(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT); + + glBindTexture(GL_TEXTURE_2D, boundTexture); + + for (FramebufferAttachmentDefinition.Format format : FramebufferAttachmentDefinition.Format.VALUES) { + if (format.getInternalFormat() == depthFormat) { + if (format.getFormat() == GL_DEPTH_COMPONENT) { + return GL_DEPTH_ATTACHMENT; + } else if (format.getFormat() == GL_DEPTH_STENCIL) { + return GL_DEPTH_STENCIL_ATTACHMENT; + } + } + } + + return GL_DEPTH_ATTACHMENT; + } + /** * A builder used to attach buffers to an {@link AdvancedFbo}. * @@ -992,7 +1019,7 @@ public Builder setDepthTextureWrapper(int textureId) { */ public Builder setDepthTextureWrapper(int textureId, int layer) { return this.setDepthBuffer(new AdvancedFboMutableTextureAttachment( - GL_DEPTH_ATTACHMENT, + getDepthAttachmentType(textureId), textureId, layer, this.name)); diff --git a/common/src/main/java/foundry/veil/api/client/render/light/renderer/LightRenderer.java b/common/src/main/java/foundry/veil/api/client/render/light/renderer/LightRenderer.java index fc03f029a..a9cef30e9 100644 --- a/common/src/main/java/foundry/veil/api/client/render/light/renderer/LightRenderer.java +++ b/common/src/main/java/foundry/veil/api/client/render/light/renderer/LightRenderer.java @@ -10,6 +10,7 @@ import foundry.veil.api.client.render.framebuffer.AdvancedFbo; import foundry.veil.api.client.render.light.data.LightData; import foundry.veil.api.client.render.vertex.VertexArray; +import foundry.veil.api.compat.ImmersivePortalsCompat; import foundry.veil.impl.client.render.light.VoxelShadowGrid; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import net.minecraft.resources.ResourceLocation; @@ -101,7 +102,7 @@ public boolean render(CullFrustum frustum, AdvancedFbo lightFbo, AdvancedFbo lig lightInscatteringFbo.clear(GL_COLOR_BUFFER_BIT); } - if (!hasRendered) { + if (!hasRendered && !ImmersivePortalsCompat.isLoaded()) { renderer.disableBuffers(BUFFER_ID, DynamicBufferType.ALBEDO, DynamicBufferType.NORMAL); return false; } diff --git a/common/src/main/java/foundry/veil/api/client/render/vertex/VertexArray.java b/common/src/main/java/foundry/veil/api/client/render/vertex/VertexArray.java index b2417e097..424001e93 100644 --- a/common/src/main/java/foundry/veil/api/client/render/vertex/VertexArray.java +++ b/common/src/main/java/foundry/veil/api/client/render/vertex/VertexArray.java @@ -8,6 +8,7 @@ import foundry.veil.api.client.render.VeilRenderSystem; import foundry.veil.api.client.render.rendertype.VeilRenderType; import foundry.veil.api.client.render.shader.program.ShaderProgram; +import foundry.veil.api.compat.ImmersivePortalsCompat; import foundry.veil.impl.client.render.vertex.ARBVertexArray; import foundry.veil.impl.client.render.vertex.DSAVertexArray; import foundry.veil.impl.client.render.vertex.LegacyVertexArray; @@ -79,7 +80,7 @@ protected VertexArray(int id, Function builder) private static void loadType() { if (vertexArrayType == null) { - if (VeilRenderSystem.directStateAccessSupported()) { + if (VeilRenderSystem.directStateAccessSupported() && !ImmersivePortalsCompat.isLoaded()) { vertexArrayType = VertexArrayType.DSA; } else { GLCapabilities caps = GL.getCapabilities(); @@ -219,7 +220,7 @@ public VertexFormat.Mode getDrawMode() { * @param usage The draw usage */ public static void upload(int buffer, ByteBuffer data, DrawUsage usage) { - if (VeilRenderSystem.directStateAccessSupported()) { + if (VeilRenderSystem.directStateAccessSupported() && !ImmersivePortalsCompat.isLoaded()) { glNamedBufferData(buffer, data, usage.getGlType()); } else { glBindBuffer(GL_ARRAY_BUFFER, buffer); diff --git a/common/src/main/java/foundry/veil/api/compat/ImmersivePortalsCompat.java b/common/src/main/java/foundry/veil/api/compat/ImmersivePortalsCompat.java new file mode 100644 index 000000000..79d5b49d3 --- /dev/null +++ b/common/src/main/java/foundry/veil/api/compat/ImmersivePortalsCompat.java @@ -0,0 +1,31 @@ +package foundry.veil.api.compat; + +import com.mojang.blaze3d.shaders.Program; +import foundry.veil.Veil; +import org.jetbrains.annotations.Nullable; + +import java.util.ServiceLoader; + +public interface ImmersivePortalsCompat { + + /** + * Retrieves the compat instance. This will be null if Immersive Portals is not installed. + */ + @Nullable + ImmersivePortalsCompat INSTANCE = (Veil.platform().isModLoaded("immersive_portals") || Veil.platform().isModLoaded("imm_ptl")) ? ServiceLoader.load(ImmersivePortalsCompat.class).findFirst().orElse(null) : null; + + /** + * @return Whether Immersive Portals is loaded + */ + static boolean isLoaded() { + return INSTANCE != null; + } + + void init(); + + String transform(Program.Type type, String shaderId, String inputCode); + + boolean shouldAddUniform(String shaderName); + + boolean renderingThroughPortal(); +} diff --git a/common/src/main/java/foundry/veil/api/quasar/data/EmitterShapeSettings.java b/common/src/main/java/foundry/veil/api/quasar/data/EmitterShapeSettings.java index a66b6f430..8d2c2393b 100644 --- a/common/src/main/java/foundry/veil/api/quasar/data/EmitterShapeSettings.java +++ b/common/src/main/java/foundry/veil/api/quasar/data/EmitterShapeSettings.java @@ -7,11 +7,10 @@ import net.minecraft.core.Holder; import net.minecraft.resources.RegistryFileCodec; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import org.jetbrains.annotations.Nullable; -import org.joml.Vector3d; -import org.joml.Vector3dc; -import org.joml.Vector3fc; +import org.joml.*; public record EmitterShapeSettings(EmitterShape shape, Vector3fc dimensions, @@ -30,6 +29,13 @@ public Vector3d getPos(RandomSource randomSource, Vector3dc pos) { return this.shape.getPoint(randomSource, this.dimensions, this.rotation, pos, this.fromSurface); } + /** + * @since 4.5.0 + */ + public Vector3d getPos(RandomSource randomSource, Vector3dc pos, Quaternionfc rot) { + return this.shape.getPoint(randomSource, this.dimensions, this.rotation.add(rot.getEulerAnglesXYZ(new Vector3f()).mul(Mth.RAD_TO_DEG), new Vector3f()), pos, this.fromSurface); + } + public @Nullable ResourceLocation getRegistryId() { return QuasarParticles.registryAccess().registry(QuasarParticles.EMITTER_SHAPE_SETTINGS).map(registry -> registry.getKey(this)).orElse(null); } diff --git a/common/src/main/java/foundry/veil/api/quasar/data/ParticleModuleTypeRegistry.java b/common/src/main/java/foundry/veil/api/quasar/data/ParticleModuleTypeRegistry.java index 97d77601b..93a130af3 100644 --- a/common/src/main/java/foundry/veil/api/quasar/data/ParticleModuleTypeRegistry.java +++ b/common/src/main/java/foundry/veil/api/quasar/data/ParticleModuleTypeRegistry.java @@ -11,6 +11,7 @@ import foundry.veil.api.quasar.data.module.init.*; import foundry.veil.api.quasar.data.module.render.ColorParticleModuleData; import foundry.veil.api.quasar.data.module.render.TrailParticleModuleData; +import foundry.veil.api.quasar.data.module.update.TickRotationParticleModuleData; import foundry.veil.api.quasar.data.module.update.TickSizeParticleModuleData; import foundry.veil.api.quasar.data.module.update.TickSubEmitterModuleData; import foundry.veil.api.quasar.emitters.module.init.InitRandomRotationModuleData; @@ -137,6 +138,10 @@ public class ParticleModuleTypeRegistry { // UPDATE public static final ModuleType TICK_SIZE = registerModule("size", TickSizeParticleModuleData.CODEC, () -> new TickSizeParticleModuleData(MolangExpression.of(1))); + /** + * @since 4.5.0 + */ + public static final ModuleType TICK_ROTATION = registerModule("rotation", TickRotationParticleModuleData.CODEC, () -> new TickRotationParticleModuleData(MolangExpression.ZERO, MolangExpression.ZERO, MolangExpression.ZERO)); public static final ModuleType TICK_SUB_EMITTER = registerModule("tick_sub_emitter", TickSubEmitterModuleData.CODEC, () -> new TickSubEmitterModuleData(ResourceLocation.withDefaultNamespace(""), 5)); // UPDATE - COLLISION public static final ModuleType DIE_ON_COLLISION = registerModule("die_on_collision", DieOnCollisionModuleData.CODEC, DieOnCollisionModuleData::new); diff --git a/common/src/main/java/foundry/veil/api/quasar/data/ParticleSettings.java b/common/src/main/java/foundry/veil/api/quasar/data/ParticleSettings.java index 30ec38e71..4836ccee8 100644 --- a/common/src/main/java/foundry/veil/api/quasar/data/ParticleSettings.java +++ b/common/src/main/java/foundry/veil/api/quasar/data/ParticleSettings.java @@ -7,41 +7,52 @@ import net.minecraft.resources.RegistryFileCodec; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.RandomSource; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; import org.joml.Vector3fc; public record ParticleSettings(float particleSpeed, + float particleSpeedVariation, float particleSize, float particleSizeVariation, int particleLifetime, float particleLifetimeVariation, Vector3fc initialDirection, boolean randomInitialDirection, + Vector3fc initialDirectionVariation, Vector3fc initialRotation, boolean randomInitialRotation, + Vector3fc initialRotationVariation, boolean randomSpeed, boolean randomSize, boolean randomLifetime) { public static final Codec DIRECT_CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.FLOAT.fieldOf("particle_speed").forGetter(ParticleSettings::particleSpeed), + Codec.FLOAT.optionalFieldOf("particle_speed_variation", 0f).forGetter(ParticleSettings::particleSpeedVariation), Codec.FLOAT.fieldOf("base_particle_size").forGetter(ParticleSettings::particleSize), Codec.FLOAT.optionalFieldOf("particle_size_variation", 0f).forGetter(ParticleSettings::particleSizeVariation), Codec.INT.fieldOf("particle_lifetime").forGetter(ParticleSettings::particleLifetime), Codec.FLOAT.optionalFieldOf("particle_lifetime_variation", 0f).forGetter(ParticleSettings::particleLifetimeVariation), CodecUtil.VECTOR3FC_CODEC.optionalFieldOf("initial_direction", new Vector3f(1)).forGetter(ParticleSettings::initialDirection), Codec.BOOL.optionalFieldOf("random_initial_direction", false).forGetter(ParticleSettings::randomInitialDirection), + CodecUtil.VECTOR3FC_CODEC.optionalFieldOf("initial_direction_variation", new Vector3f(0)).forGetter(ParticleSettings::initialDirectionVariation), CodecUtil.VECTOR3FC_CODEC.optionalFieldOf("initial_rotation", new Vector3f(0)).forGetter(ParticleSettings::initialRotation), Codec.BOOL.optionalFieldOf("random_initial_rotation", false).forGetter(ParticleSettings::randomInitialRotation), + CodecUtil.VECTOR3FC_CODEC.optionalFieldOf("initial_rotation_variation", new Vector3f(0)).forGetter(ParticleSettings::initialRotationVariation), Codec.BOOL.optionalFieldOf("random_speed", false).forGetter(ParticleSettings::randomSpeed), Codec.BOOL.optionalFieldOf("random_size", false).forGetter(ParticleSettings::randomSize), Codec.BOOL.optionalFieldOf("random_lifetime", false).forGetter(ParticleSettings::randomLifetime) ).apply(instance, ParticleSettings::new)); public static final Codec> CODEC = RegistryFileCodec.create(QuasarParticles.PARTICLE_SETTINGS, DIRECT_CODEC); + @ApiStatus.Internal + public ParticleSettings { + } + public float particleSpeed(RandomSource random) { - return this.randomSpeed ? this.particleSpeed + (random.nextFloat() * 0.5f - 0.5f) * this.particleSpeed : this.particleSpeed; + return this.randomSpeed ? this.particleSpeed + random.nextFloat() * this.particleSpeedVariation : this.particleSpeed; } public float particleSize(RandomSource random) { @@ -53,14 +64,14 @@ public int particleLifetime(RandomSource random) { } public Vector3fc initialDirection(RandomSource random) { - return this.randomInitialDirection ? this.initialDirection.mul(random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1, new Vector3f()) : this.initialDirection; + return this.randomInitialDirection ? this.initialDirection.add(this.initialDirectionVariation.mul(random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1, new Vector3f()), new Vector3f()) : this.initialDirection; } /** * @since 4.3.0 */ public Vector3fc initialRotation(RandomSource random) { - return this.randomInitialRotation ? this.initialRotation.mul(random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1, new Vector3f()) : this.initialRotation; + return this.randomInitialRotation ? this.initialRotation.add(this.initialRotationVariation.mul(random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1, new Vector3f()), new Vector3f()) : this.initialRotation; } public Vector3f particleDirection(RandomSource random) { diff --git a/common/src/main/java/foundry/veil/api/quasar/data/module/init/InitialVelocityModuleData.java b/common/src/main/java/foundry/veil/api/quasar/data/module/init/InitialVelocityModuleData.java index 855e998da..42b3b5b75 100644 --- a/common/src/main/java/foundry/veil/api/quasar/data/module/init/InitialVelocityModuleData.java +++ b/common/src/main/java/foundry/veil/api/quasar/data/module/init/InitialVelocityModuleData.java @@ -11,6 +11,7 @@ import foundry.veil.api.quasar.particle.ParticleModuleSet; import foundry.veil.api.util.CodecUtil; import imgui.ImGui; +import org.joml.Quaterniond; import org.joml.Vector3d; import org.joml.Vector3dc; @@ -37,7 +38,7 @@ public InitialVelocityModuleData(Vector3dc velocityDirection, @Override public void addModules(ParticleModuleSet.Builder builder) { // TODO takesParentRotation - builder.addModule((InitParticleModule) particle -> particle.getVelocity().add(this.velocityDirection.normalize(this.strength, new Vector3d()))); + builder.addModule((InitParticleModule) particle -> particle.getVelocity().add(this.velocityDirection.normalize(this.strength, new Vector3d()).rotate(particle.getEmitter().getRotation().get(new Quaterniond())))); } @Override diff --git a/common/src/main/java/foundry/veil/api/quasar/data/module/update/TickRotationParticleModuleData.java b/common/src/main/java/foundry/veil/api/quasar/data/module/update/TickRotationParticleModuleData.java new file mode 100644 index 000000000..3a48ebb1c --- /dev/null +++ b/common/src/main/java/foundry/veil/api/quasar/data/module/update/TickRotationParticleModuleData.java @@ -0,0 +1,112 @@ +package foundry.veil.api.quasar.data.module.update; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import foundry.veil.api.client.editor.EditorAttributeProvider; +import foundry.veil.api.molang.MolangExpressionCodec; +import foundry.veil.api.molang.VeilMolang; +import foundry.veil.api.quasar.data.ParticleModuleTypeRegistry; +import foundry.veil.api.quasar.data.module.ModuleType; +import foundry.veil.api.quasar.data.module.ParticleModuleData; +import foundry.veil.api.quasar.emitters.module.UpdateParticleModule; +import foundry.veil.api.quasar.particle.ParticleModuleSet; +import gg.moonflower.molangcompiler.api.MolangExpression; +import imgui.ImGui; +import imgui.type.ImString; +import net.minecraft.util.Mth; + +/** + * @since 4.5.0 + */ +public final class TickRotationParticleModuleData implements ParticleModuleData, EditorAttributeProvider { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + MolangExpressionCodec.CODEC.fieldOf("x").forGetter(TickRotationParticleModuleData::rotationX), + MolangExpressionCodec.CODEC.fieldOf("y").forGetter(TickRotationParticleModuleData::rotationY), + MolangExpressionCodec.CODEC.fieldOf("z").forGetter(TickRotationParticleModuleData::rotationZ) + ).apply(instance, TickRotationParticleModuleData::new)); + private MolangExpression rotationX, rotationY, rotationZ; + + public TickRotationParticleModuleData(MolangExpression rotationX, + MolangExpression rotationY, + MolangExpression rotationZ) { + this.rotationX = rotationX; + this.rotationY = rotationY; + this.rotationZ = rotationZ; + } + + @Override + public void renderImGuiAttributes() { + ImString rotationXInput = new ImString(); + String rotationXString = this.rotationX.toString(); + if (rotationXString.startsWith("return (")) { + rotationXInput.set(rotationXString.substring(8, rotationXString.length() - 1)); + } else { + rotationXInput.set(rotationXString); + } + + if (ImGui.inputText("x", rotationXInput)) { + try { + this.rotationX = VeilMolang.get().compile(rotationXInput.get()); + } catch (Exception ignored) { + } + } + + ImString rotationYInput = new ImString(); + String rotationYString = this.rotationY.toString(); + if (rotationYString.startsWith("return (")) { + rotationYInput.set(rotationYString.substring(8, rotationYString.length() - 1)); + } else { + rotationYInput.set(rotationYString); + } + + if (ImGui.inputText("y", rotationYInput)) { + try { + this.rotationY = VeilMolang.get().compile(rotationYInput.get()); + } catch (Exception ignored) { + } + } + + ImString rotationZInput = new ImString(); + String rotationZString = this.rotationZ.toString(); + if (rotationZString.startsWith("return (")) { + rotationZInput.set(rotationZString.substring(8, rotationZString.length() - 1)); + } else { + rotationZInput.set(rotationZString); + } + + if (ImGui.inputText("z", rotationZInput)) { + try { + this.rotationZ = VeilMolang.get().compile(rotationZInput.get()); + } catch (Exception ignored) { + } + } + } + + @Override + public void addModules(ParticleModuleSet.Builder builder) { + builder.addModule((UpdateParticleModule) particle -> { + try { + particle.setRotation(particle.getEnvironment().resolve(this.rotationX) * Mth.DEG_TO_RAD, particle.getEnvironment().resolve(this.rotationY) * Mth.DEG_TO_RAD, particle.getEnvironment().resolve(this.rotationZ) * Mth.DEG_TO_RAD); + } catch (Exception ignored) { + + } + }); + } + + @Override + public ModuleType getType() { + return ParticleModuleTypeRegistry.TICK_ROTATION; + } + + public MolangExpression rotationX() { + return rotationX; + } + + public MolangExpression rotationY() { + return rotationY; + } + + public MolangExpression rotationZ() { + return rotationZ; + } +} diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/ConstantForceModule.java b/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/ConstantForceModule.java index 6c81aeadb..3fe8435ea 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/ConstantForceModule.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/ConstantForceModule.java @@ -2,6 +2,7 @@ import foundry.veil.api.quasar.emitters.module.ForceParticleModule; import foundry.veil.api.quasar.particle.QuasarParticle; +import org.joml.Quaterniond; import org.joml.Vector3d; import org.joml.Vector3dc; @@ -21,7 +22,8 @@ public ConstantForceModule(Vector3d acceleration) { @Override public void applyForce(QuasarParticle particle) { - particle.getVelocity().add(this.acceleration.x * this.strength, this.acceleration.y * this.strength, this.acceleration.z * this.strength); + Vector3d rotatedAcceleration = this.acceleration.rotate(particle.getEmitter().getRotation().get(new Quaterniond()), new Vector3d()); + particle.getVelocity().add(rotatedAcceleration.x * this.strength, rotatedAcceleration.y * this.strength, rotatedAcceleration.z * this.strength); } @Override diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/SimplePositionedForce.java b/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/SimplePositionedForce.java index b7e46bae3..0374cdfe5 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/SimplePositionedForce.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/SimplePositionedForce.java @@ -2,6 +2,7 @@ import foundry.veil.api.quasar.emitters.module.ForceParticleModule; import foundry.veil.api.quasar.particle.QuasarParticle; +import org.joml.Quaterniond; import org.joml.Vector3d; import org.joml.Vector3dc; @@ -23,7 +24,8 @@ protected SimplePositionedForce(Vector3dc position, boolean localPosition) { protected Vector3d getDeltaPosition(QuasarParticle particle) { if (this.localPosition) { - return this.position.add(particle.getEmitter().getPosition(), this.tempPos).sub(particle.getPosition()); + Vector3d rotatedPosition = position.rotate(particle.getEmitter().getRotation().get(new Quaterniond()), new Vector3d()); + return rotatedPosition.add(particle.getEmitter().getPosition(), this.tempPos).sub(particle.getPosition()); } return this.position.sub(particle.getPosition(), this.tempPos); } diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/VortexForceModule.java b/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/VortexForceModule.java index ff63d8983..a60d9baa3 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/VortexForceModule.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/module/force/VortexForceModule.java @@ -2,6 +2,7 @@ import foundry.veil.api.quasar.data.module.force.VortexForceData; import foundry.veil.api.quasar.particle.QuasarParticle; +import org.joml.Quaterniond; import org.joml.Vector3d; import org.joml.Vector3dc; @@ -38,9 +39,11 @@ public void applyForce(QuasarParticle particle) { } // apply force to particle to move around the vortex center on the vortex axis, but do not modify outwards/inwards velocity - Vector3d particleToCenterOnAxis = diff.sub(this.vortexAxis.mul(diff.dot(this.vortexAxis), this.dot)); + Vector3d rotatedAxis = this.vortexAxis.rotate(particle.getEmitter().getRotation().get(new Quaterniond()), new Vector3d()).normalize(); + + Vector3d particleToCenterOnAxis = diff.sub(rotatedAxis.mul(diff.dot(rotatedAxis), this.dot)); particleToCenterOnAxis.normalize(); - particleToCenterOnAxis.cross(this.vortexAxis).mul(this.strength); + particleToCenterOnAxis.cross(rotatedAxis).mul(this.strength); particle.getVelocity().add(particleToCenterOnAxis); } diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Cube.java b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Cube.java index 672577268..c6c639f7f 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Cube.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Cube.java @@ -12,7 +12,7 @@ public class Cube implements EmitterShape { @Override - public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc rotation, Vector3dc position, boolean fromSurface) { + public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc shapeRotation, Vector3dc position, boolean fromSurface) { double x = randomSource.nextDouble() * 2 - 1; double y = randomSource.nextDouble() * 2 - 1; double z = randomSource.nextDouble() * 2 - 1; @@ -28,7 +28,11 @@ public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector } Vector3d normal = new Vector3d(x, y, z); Vector3d pos = normal.mul(dimensions).mul(0.5); - pos = pos.rotate(new Quaterniond().rotationXYZ((float) Math.toRadians(rotation.x()), (float) Math.toRadians(rotation.y()), (float) Math.toRadians(rotation.z()))); + new Matrix4d().rotate(new Quaternionf().rotationXYZ((float) Math.toRadians(shapeRotation.x()), (float) Math.toRadians(shapeRotation.y()), (float) Math.toRadians(shapeRotation.z()))).transformPosition(pos); + //pos.rotateAxis(Math.toRadians(shapeRotation.x()), 1, 0, 0); + //pos.rotateAxis(Math.toRadians(shapeRotation.y()), 0, 1, 0); + //pos.rotateAxis(Math.toRadians(shapeRotation.z()), 0, 0, 1); + //pos.rotate(new Quaterniond().rotateLocalX((float) Math.toRadians(shapeRotation.x())).rotateLocalY( (float) Math.toRadians(shapeRotation.y())).rotateLocalZ( (float) Math.toRadians(shapeRotation.z()))); return pos.add(position); } diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Cylinder.java b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Cylinder.java index df3336c7c..eff0c9d31 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Cylinder.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Cylinder.java @@ -10,7 +10,7 @@ public class Cylinder implements EmitterShape { @Override - public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc rotation, Vector3dc position, boolean fromSurface) { + public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc shapeRotation, Vector3dc position, boolean fromSurface) { double theta = randomSource.nextDouble() * 2 * Math.PI; double x = Math.cos(theta); double y = randomSource.nextDouble() * 2 - 1; @@ -27,7 +27,7 @@ public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector ); } Vector3d pos = normal.mul(dim); - pos = pos.rotate(new Quaterniond().rotationXYZ((float) Math.toRadians(rotation.x()), (float) Math.toRadians(rotation.y()), (float) Math.toRadians(rotation.z()))); + pos = pos.rotate(new Quaterniond().rotationXYZ((float) Math.toRadians(shapeRotation.x()), (float) Math.toRadians(shapeRotation.y()), (float) Math.toRadians(shapeRotation.z()))); pos.mul(0.5); return pos.add(position); } diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Disc.java b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Disc.java index 9056c9f79..6d626e33a 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Disc.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Disc.java @@ -10,7 +10,7 @@ public class Disc implements EmitterShape { @Override - public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc rotation, Vector3dc position, boolean fromSurface) { + public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc shapeRotation, Vector3dc position, boolean fromSurface) { double x = randomSource.nextDouble() * 2 - 1; double y = 0; double z = randomSource.nextDouble() * 2 - 1; @@ -26,7 +26,7 @@ public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector ); } Vector3d pos = normal.mul(dim).mul(0.5); - pos = pos.rotate(new Quaterniond().rotationXYZ((float) Math.toRadians(rotation.x()), (float) Math.toRadians(rotation.y()), (float) Math.toRadians(rotation.z()))); + pos = pos.rotate(new Quaterniond().rotationXYZ((float) Math.toRadians(shapeRotation.x()), (float) Math.toRadians(shapeRotation.y()), (float) Math.toRadians(shapeRotation.z()))); return pos.add(position); } diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/EmitterShape.java b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/EmitterShape.java index a9bf3ab2a..db7025d8a 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/EmitterShape.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/EmitterShape.java @@ -12,7 +12,7 @@ public interface EmitterShape { - Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc rotation, Vector3dc position, boolean fromSurface); + Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc shapeRotation, Vector3dc position, boolean fromSurface); void renderShape(PoseStack stack, VertexConsumer consumer, Vector3fc dimensions, Vector3fc rotation); diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Hemisphere.java b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Hemisphere.java index d7d9505b8..f57714773 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Hemisphere.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Hemisphere.java @@ -12,7 +12,7 @@ public class Hemisphere implements EmitterShape { @Override - public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc rotation, Vector3dc position, boolean fromSurface) { + public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc shapeRotation, Vector3dc position, boolean fromSurface) { double theta = randomSource.nextDouble() * 2 * Math.PI; double phi = randomSource.nextDouble() * Math.PI / 2; double x = Math.cos(theta) * Math.sin(phi); @@ -30,7 +30,7 @@ public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector ); } Vector3d pos = normal.mul(dim).mul(0.5); - pos = pos.rotate(new Quaterniond().rotationXYZ((float) Math.toRadians(rotation.x()), (float) Math.toRadians(rotation.y()), (float) Math.toRadians(rotation.z()))); + pos = pos.rotate(new Quaterniond().rotationXYZ((float) Math.toRadians(shapeRotation.x()), (float) Math.toRadians(shapeRotation.y()), (float) Math.toRadians(shapeRotation.z()))); return pos.add(position); } diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Plane.java b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Plane.java index 41e0f5d92..b55a69106 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Plane.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Plane.java @@ -12,7 +12,7 @@ public class Plane implements EmitterShape { @Override - public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc rotation, Vector3dc position, boolean fromSurface) { + public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc shapeRotation, Vector3dc position, boolean fromSurface) { double x = randomSource.nextDouble() - 0.5; double y = 0; double z = randomSource.nextDouble() - 0.5; @@ -25,7 +25,7 @@ public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector } Vector3d normal = new Vector3d(x, y, z); Vector3d pos = normal.mul(dimensions); - pos = pos.rotate(new Quaterniond().rotationXYZ((float) Math.toRadians(rotation.x()), (float) Math.toRadians(rotation.y()), (float) Math.toRadians(rotation.z()))); + pos = pos.rotate(new Quaterniond().rotationXYZ((float) Math.toRadians(shapeRotation.x()), (float) Math.toRadians(shapeRotation.y()), (float) Math.toRadians(shapeRotation.z()))); return pos.add(position); } diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Point.java b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Point.java index 3d501e2a0..6c5472b9f 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Point.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Point.java @@ -12,7 +12,7 @@ public class Point implements EmitterShape { @Override - public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc rotation, Vector3dc position, boolean fromSurface) { + public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc shapeRotation, Vector3dc position, boolean fromSurface) { return new Vector3d(position); } diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Sphere.java b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Sphere.java index 3eb49a674..6bd20bc2c 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Sphere.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Sphere.java @@ -11,7 +11,7 @@ public class Sphere implements EmitterShape { @Override - public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc rotation, Vector3dc position, boolean fromSurface) { + public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc shapeRotation, Vector3dc position, boolean fromSurface) { double x = randomSource.nextDouble() - 0.5; double y = randomSource.nextDouble() - 0.5; double z = randomSource.nextDouble() - 0.5; @@ -27,7 +27,7 @@ public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector ); } Vector3d pos = normal.mul(dim).mul(0.5); - pos = pos.rotate(new Quaterniond().rotationXYZ((float) Math.toRadians(rotation.x()), (float) Math.toRadians(rotation.y()), (float) Math.toRadians(rotation.z()))); + pos = pos.rotate(new Quaterniond().rotationXYZ((float) Math.toRadians(shapeRotation.x()), (float) Math.toRadians(shapeRotation.y()), (float) Math.toRadians(shapeRotation.z()))); return pos.add(position); } diff --git a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Torus.java b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Torus.java index 0aa61a816..9eb7a19b6 100644 --- a/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Torus.java +++ b/common/src/main/java/foundry/veil/api/quasar/emitters/shape/Torus.java @@ -3,15 +3,14 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; import net.minecraft.util.RandomSource; -import org.joml.Vector3d; -import org.joml.Vector3dc; -import org.joml.Vector3f; -import org.joml.Vector3fc; +import org.joml.*; + +import java.lang.Math; public class Torus implements EmitterShape { @Override - public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc rotation, Vector3dc position, boolean fromSurface) { + public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector3fc shapeRotation, Vector3dc position, boolean fromSurface) { double theta = randomSource.nextDouble() * 2 * Math.PI; double phi = randomSource.nextDouble() * 2 * Math.PI; double x = Math.cos(theta) * (1 + 0.5 * Math.cos(phi)); @@ -29,7 +28,7 @@ public Vector3d getPoint(RandomSource randomSource, Vector3fc dimensions, Vector ); } Vector3d pos = normal.mul(dim); - pos = pos.rotateX((float) Math.toRadians(rotation.x())).rotateY((float) Math.toRadians(rotation.y())).rotateZ((float) Math.toRadians(rotation.z())); + pos = pos.rotateX((float) Math.toRadians(shapeRotation.x())).rotateY((float) Math.toRadians(shapeRotation.y())).rotateZ((float) Math.toRadians(shapeRotation.z())); return pos.add(position); } diff --git a/common/src/main/java/foundry/veil/api/quasar/particle/ParticleEmitter.java b/common/src/main/java/foundry/veil/api/quasar/particle/ParticleEmitter.java index a443512de..5e3f242a0 100644 --- a/common/src/main/java/foundry/veil/api/quasar/particle/ParticleEmitter.java +++ b/common/src/main/java/foundry/veil/api/quasar/particle/ParticleEmitter.java @@ -20,11 +20,9 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnmodifiableView; -import org.joml.Vector3d; -import org.joml.Vector3dc; -import org.joml.Vector3f; -import org.joml.Vector3fc; +import org.joml.*; +import java.lang.Math; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -53,6 +51,7 @@ public class ParticleEmitter { private final List modulesView; private final RandomSource randomSource; private final Vector3d position; + private final Quaternionf rotation; private final Vector3d offset; private final List particles; @@ -80,6 +79,7 @@ protected ParticleEmitter(ParticleSystemManager particleManager, ClientLevel lev this.modulesView = Collections.unmodifiableList(this.modules); this.randomSource = RandomSource.create(); this.position = new Vector3d(); + this.rotation = new Quaternionf(); this.offset = new Vector3d(); this.particles = new LinkedList<>(); @@ -115,9 +115,9 @@ protected void spawn() { this.particleManager.reserve(count); for (int i = 0; i < count; i++) { - Vector3dc particlePos = this.emitterShapeSettings.get(i % this.emitterShapeSettings.size()).getPos(this.randomSource, this.position); - Vector3fc particleDirection = this.particleSettings.particleDirection(this.randomSource); - Vector3fc particleRotation = this.particleSettings.initialRotation(this.randomSource).mul(Mth.DEG_TO_RAD, new Vector3f()); + Vector3dc particlePos = this.emitterShapeSettings.get(i % this.emitterShapeSettings.size()).getPos(this.randomSource, this.getPosition(), this.getRotation()); + Vector3fc particleDirection = this.particleSettings.particleDirection(this.randomSource).rotate(this.getRotation()); + Vector3fc particleRotation = this.particleSettings.initialRotation(this.randomSource).mul(Mth.DEG_TO_RAD, new Vector3f()).add(this.getRotation().getEulerAnglesXYZ(new Vector3f())); // TODO // this.getParticleData().getInitModules().stream().filter(force -> force instanceof InitialVelocityForce).forEach(f -> { @@ -346,6 +346,15 @@ public Vector3d getPosition() { return this.position; } + /** + * Rotation of the emitter + * + * @since 4.5.0 + */ + public Quaternionf getRotation() { + return this.rotation; + } + public ParticleEmitterData getData() { return this.emitterData; } @@ -431,6 +440,28 @@ public void setPosition(double x, double y, double z) { } } + /** + * Sets the rotation of the particle emitter. + * + * @param x The rotation about the X axis, in radians. + * @param y The rotation about the Y axis, in radians. + * @param z The rotation about the Z axis, in radians. + * @since 4.5.0 + */ + public void setRotation(float x, float y, float z) { + this.rotation.identity().rotateLocalX(x).rotateLocalY(y).rotateLocalZ(z); + } + + /** + * Sets the rotation of the particle emitter. + * + * @param rotation The rotation of the emitter. + * @since 4.5.0 + */ + public void setRotation(Quaternionfc rotation) { + this.rotation.set(rotation); + } + public void setMaxLifetime(int maxLifetime) { this.maxLifetime = maxLifetime; } diff --git a/common/src/main/java/foundry/veil/api/quasar/particle/QuasarParticle.java b/common/src/main/java/foundry/veil/api/quasar/particle/QuasarParticle.java index 0408be00e..9a33dc8be 100644 --- a/common/src/main/java/foundry/veil/api/quasar/particle/QuasarParticle.java +++ b/common/src/main/java/foundry/veil/api/quasar/particle/QuasarParticle.java @@ -268,6 +268,13 @@ public Vector3f getRotation() { return this.rotation; } + /** + * @since 4.5.0 + */ + public void setRotation(float x, float y, float z) { + this.rotation.set(x, y, z); + } + public float getRadius() { return this.radius; } diff --git a/common/src/main/java/foundry/veil/api/quasar/particle/RenderStyle.java b/common/src/main/java/foundry/veil/api/quasar/particle/RenderStyle.java index 529fa08ff..d53ec269e 100644 --- a/common/src/main/java/foundry/veil/api/quasar/particle/RenderStyle.java +++ b/common/src/main/java/foundry/veil/api/quasar/particle/RenderStyle.java @@ -271,7 +271,7 @@ protected void putBufferData(QuasarParticle particle, Camera camera, ByteBuffer Matrix4f transformationMatrix = new Matrix4f() .translate(renderOffset.x, renderOffset.y, renderOffset.z) - .rotate(new Quaternionf().rotateLocalX(rotation.x()).rotateLocalY(rotation.y()).rotateLocalZ(rotation.z())); + .rotate(new Quaternionf().rotationXYZ(rotation.x(), rotation.y(), rotation.z())); transformationMatrix.get(buffer.position(), buffer); buffer.position(buffer.position() + Float.BYTES * 16); @@ -360,7 +360,7 @@ protected void putBufferData(QuasarParticle particle, Camera camera, ByteBuffer Matrix4f transformationMatrix = new Matrix4f() .translate(renderOffset.x, renderOffset.y, renderOffset.z) - .rotate(faceCameraRotation.rotateLocalX(rotation.x()).rotateLocalY(rotation.y()).rotateLocalZ(rotation.z())); + .rotate(faceCameraRotation.rotateXYZ(rotation.x(), rotation.y(), rotation.z())); transformationMatrix.get(buffer.position(), buffer); buffer.position(buffer.position() + Float.BYTES * 16); diff --git a/common/src/main/java/foundry/veil/api/screenshake/ScreenShakeManager.java b/common/src/main/java/foundry/veil/api/screenshake/ScreenShakeManager.java new file mode 100644 index 000000000..c68f20041 --- /dev/null +++ b/common/src/main/java/foundry/veil/api/screenshake/ScreenShakeManager.java @@ -0,0 +1,59 @@ +package foundry.veil.api.screenshake; + +import foundry.veil.api.screenshake.type.ScreenShakeType; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.joml.Vector3f; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * @since 4.5.0 + */ +public final class ScreenShakeManager { + + private final List screenShakes = new LinkedList<>(); + private final Vector3f accumulated = new Vector3f(); + private final Vector3f renderPosition = new Vector3f(); + + /** + * Add a screen shake to the camera. + */ + public void addScreenShake(ScreenShakeType instance) { + this.screenShakes.add(instance); + } + + /** + * Remove a screen shake from the camera. + */ + public void removeScreenShake(ScreenShakeType instance) { + this.screenShakes.remove(instance); + } + + @ApiStatus.Internal + public void tick() { + this.renderPosition.set(this.accumulated); + this.accumulated.set(0); + + if (this.screenShakes.isEmpty()) { + return; + } + + Iterator iterator = this.screenShakes.iterator(); + while (iterator.hasNext()) { + ScreenShakeType screenShake = iterator.next(); + screenShake.tick(); + this.accumulated.add(screenShake.getPositionOffset()); + if (screenShake.isRemoved()) { + iterator.remove(); + } + } + } + + @Contract("_->new") + public Vector3f getPosition(float partialTick) { + return this.renderPosition.lerp(this.accumulated, partialTick, new Vector3f()); + } +} diff --git a/common/src/main/java/foundry/veil/api/screenshake/type/GlobalScreenShake.java b/common/src/main/java/foundry/veil/api/screenshake/type/GlobalScreenShake.java new file mode 100644 index 000000000..9376a4e4e --- /dev/null +++ b/common/src/main/java/foundry/veil/api/screenshake/type/GlobalScreenShake.java @@ -0,0 +1,40 @@ +package foundry.veil.api.screenshake.type; + +import foundry.veil.api.molang.VeilMolang; +import gg.moonflower.molangcompiler.api.MolangExpression; +import gg.moonflower.molangcompiler.api.MolangRuntime; +import gg.moonflower.molangcompiler.api.exception.MolangSyntaxException; +import net.minecraft.util.RandomSource; + +import java.util.function.Supplier; + +/** + * A screen shake that affects the player regardless of distance. + * + * @author Neddslayer + * @since 4.5.0 + */ +public class GlobalScreenShake extends ScreenShakeType { + + private final MolangExpression expression; + private final Supplier environment; + + public GlobalScreenShake(String strengthExpression, int length) throws MolangSyntaxException { + this(VeilMolang.get().compile(strengthExpression), length); + } + + public GlobalScreenShake(MolangExpression strengthExpression, int length) { + super(RandomSource.create(), length); + this.expression = strengthExpression; + this.environment = () -> MolangRuntime.runtime() + .setQuery("age", this::age) + .setQuery("agePercent", () -> this.age() / this.length()) + .setQuery("length", this::length) + .create(); + } + + @Override + protected float getStrength() { + return this.environment.get().safeResolve(this.expression); + } +} diff --git a/common/src/main/java/foundry/veil/api/screenshake/type/LocalScreenShake.java b/common/src/main/java/foundry/veil/api/screenshake/type/LocalScreenShake.java new file mode 100644 index 000000000..ce6b81695 --- /dev/null +++ b/common/src/main/java/foundry/veil/api/screenshake/type/LocalScreenShake.java @@ -0,0 +1,44 @@ +package foundry.veil.api.screenshake.type; + +import foundry.veil.api.client.util.Easing; +import gg.moonflower.molangcompiler.api.MolangExpression; +import gg.moonflower.molangcompiler.api.exception.MolangSyntaxException; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; + +/** + * Screen shake that originates from a point in the world, strength decreasing with distance. + * + * @author Neddslayer + * @since 4.5.0 + */ +public class LocalScreenShake extends GlobalScreenShake { + + private final float radius; + private final Easing falloff; + private final Vec3 position; + + public LocalScreenShake(String expression, Vec3 position, int length, float radius, Easing falloff) throws MolangSyntaxException { + super(expression, length); + this.radius = radius; + this.falloff = falloff; + this.position = position; + } + + public LocalScreenShake(MolangExpression expression, Vec3 position, int length, float radius, Easing falloff) { + super(expression, length); + this.radius = radius; + this.falloff = falloff; + this.position = position; + } + + @Override + protected float getStrength() { + float strength = super.getStrength(); + Camera camera = Minecraft.getInstance().gameRenderer.getMainCamera(); + float distanceMultiplier = Mth.clamp((float) (1.0f - (camera.getPosition().distanceTo(this.position) / this.radius)), 0, 1); + return this.falloff.ease(distanceMultiplier) * strength; + } +} diff --git a/common/src/main/java/foundry/veil/api/screenshake/type/ScreenShakeType.java b/common/src/main/java/foundry/veil/api/screenshake/type/ScreenShakeType.java new file mode 100644 index 000000000..15b498613 --- /dev/null +++ b/common/src/main/java/foundry/veil/api/screenshake/type/ScreenShakeType.java @@ -0,0 +1,75 @@ +package foundry.veil.api.screenshake.type; + +import net.minecraft.util.RandomSource; +import org.jetbrains.annotations.ApiStatus; +import org.joml.Vector3f; + +/** + * Defines the behavior of a screenshake + * + * @author Neddslayer + * @since 4.5.0 + */ +public abstract class ScreenShakeType { + + private final Vector3f positionOffset = new Vector3f(); + private final RandomSource randomSource; + private final int length; + protected int ticksRemaining; + + public ScreenShakeType(RandomSource randomSource, int length) { + this.randomSource = randomSource; + this.length = length; + this.ticksRemaining = this.length; + } + + @ApiStatus.Internal + public void tick() { + this.ticksRemaining--; + if (!this.isRemoved()) { + float strength = this.getStrength(); + this.positionOffset.set(this.randomOffset(strength), this.randomOffset(strength), this.randomOffset(strength)); + } + } + + private float randomOffset(float strength) { + return (this.randomSource.nextFloat() - 0.5f) * strength; + } + + public boolean isRemoved() { + return this.ticksRemaining <= 0; + } + + /** + * Immediately remove the screenshake. + */ + public void remove() { + this.ticksRemaining = Integer.MIN_VALUE; + } + + /** + * @return How long the screenshake has been running for. + */ + public float age() { + return this.length - this.ticksRemaining; + } + + /** + * @return The lifetime of the screenshake. + */ + public float length() { + return this.length; + } + + /** + * @return For the current tick, where the screenshake has offset the camera. + */ + public Vector3f getPositionOffset() { + return this.positionOffset; + } + + /** + * The intensity of the screenshake, up to the implementation of the {@code ScreenShakeType}. + */ + protected abstract float getStrength(); +} diff --git a/common/src/main/java/foundry/veil/impl/client/editor/ParticleEditorInspector.java b/common/src/main/java/foundry/veil/impl/client/editor/ParticleEditorInspector.java index 2559041a0..fe67299b5 100644 --- a/common/src/main/java/foundry/veil/impl/client/editor/ParticleEditorInspector.java +++ b/common/src/main/java/foundry/veil/impl/client/editor/ParticleEditorInspector.java @@ -41,16 +41,17 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.ApiStatus; -import org.joml.Matrix4f; -import org.joml.Vector3f; -import org.joml.Vector3fc; +import org.joml.*; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.lang.Math; import java.nio.file.Path; import java.util.*; import java.util.function.Supplier; @@ -131,7 +132,7 @@ protected void renderComponents() { Integer.MAX_VALUE, new EmitterSettings( List.of(Holder.direct(new EmitterShapeSettings(EmitterShapeRegistry.POINT.get(), new Vector3f(1), new Vector3f(0), true))), - Holder.direct(new ParticleSettings(0.1f, 0.1f, 0, 60, 0, new Vector3f(1), true, new Vector3f(0), false, false, false, false)), + Holder.direct(new ParticleSettings(0.1f, 0, 0.1f, 0, 60, 0, new Vector3f(0), true, new Vector3f(1), new Vector3f(0), false, new Vector3f(0), false, false, false)), false ), Holder.direct(new QuasarParticleData(true, false, 0.0f, List.of(), null, false, RenderStyleRegistry.CUBE.get())) @@ -228,10 +229,52 @@ private void renderParticleAttributes() { ImGui.text("Particles: " + emitter.getParticleCount()); + Vector3f emitterRotationEuler = emitter.getRotationVector().mul(Mth.RAD_TO_DEG, new Vector3f()); + float[] editPos = new float[]{(float) emitter.getPosition().x(), (float) emitter.getPosition().y(), (float) emitter.getPosition().z()}; + float[] editRot = new float[]{emitterRotationEuler.x(), emitterRotationEuler.y(), emitterRotationEuler.z()}; if (ImGui.dragFloat3("position", editPos, 0.02F)) { - emitter.setPosition(editPos[0], editPos[1], editPos[2]); + Entity attached = emitter.getAttachedEntity(); + Vector3f entityPos = attached == null ? new Vector3f() : new Vector3f((float) attached.getX(), (float) attached.getY(), (float) attached.getZ()); + emitter.setAttachedEntity(null); + emitter.setPosition(editPos[0] - entityPos.x, editPos[1] - entityPos.y, editPos[2] - entityPos.z); + emitter.setAttachedEntity(attached); + } + + if (ImGui.dragFloat3("rotation", editRot, 0.1F)) { + emitter.setRotation(editRot[0] * Mth.DEG_TO_RAD, editRot[1] * Mth.DEG_TO_RAD, editRot[2] * Mth.DEG_TO_RAD); + } + + if (Minecraft.getInstance().crosshairPickEntity != null) { + if (ImGui.button("Attach to entity")) { + emitter.setAttachedEntity(Minecraft.getInstance().crosshairPickEntity); + emitter.setPosition(Vec3.ZERO); + } + } else if (Minecraft.getInstance().hitResult != null && Minecraft.getInstance().hitResult.getType() == HitResult.Type.BLOCK) { + if (ImGui.button("Place on block")) { + emitter.setAttachedEntity(null); + BlockHitResult blockHitResult = ((BlockHitResult) Minecraft.getInstance().hitResult); + emitter.setPosition(blockHitResult.getBlockPos().getCenter().add(new Vec3(blockHitResult.getDirection().step().mul(0.5f)))); + emitter.setRotation(blockHitResult.getDirection().getRotation()); + } + } else { + if (ImGui.button("Move to view")) { + emitter.setAttachedEntity(null); + Camera camera = Minecraft.getInstance().gameRenderer.getMainCamera(); + emitter.setPosition(camera.getPosition().add(new Vec3(camera.getLookVector()).scale(4))); + } + } + + if (emitter.getAttachedEntity() != null) { + ImGui.sameLine(); + if (ImGui.button("Remove from entity")) { + Entity attached = emitter.getAttachedEntity(); + Vector3d targetPosition = new Vector3d(attached.getX(), attached.getY(), attached.getZ()); + Vector3d offset = emitter.getPosition().sub(targetPosition, new Vector3d()); + emitter.setAttachedEntity(null); + emitter.setPosition(targetPosition.add(offset)); + } } // General (emitters) @@ -249,7 +292,7 @@ private void renderParticleAttributes() { float width = ImGui.getContentRegionAvailX() * 0.333f; ImGui.setNextItemWidth(width); - if (ImGui.dragScalar("rate", editRate, 0.02F)) { + if (ImGui.dragScalar("rate", editRate, 0.02F, 1, Integer.MAX_VALUE)) { emitter.setRate(editRate[0]); } ImGui.sameLine(); @@ -364,6 +407,13 @@ private void renderParticleSettings(MutableParticleEmitter emitter) { emitter.setParticleDirection(editDirection[0], editDirection[1], editDirection[2]); } + if (settings.randomInitialDirection()) { + float[] editDirectionVariation = new float[]{settings.initialDirectionVariation().x(), settings.initialDirectionVariation().y(), settings.initialDirectionVariation().z()}; + if (ImGui.dragFloat3("initial_direction_variation", editDirectionVariation, 0.01F)) { + emitter.setParticleDirectionVariation(editDirectionVariation[0], editDirectionVariation[1], editDirectionVariation[2]); + } + } + if (ImGui.checkbox("random_initial_rotation", settings.randomInitialRotation())) { emitter.toggleRandomRotation(); } @@ -373,14 +423,29 @@ private void renderParticleSettings(MutableParticleEmitter emitter) { emitter.setParticleRotation(editRotation[0], editRotation[1], editRotation[2]); } + if (settings.randomInitialRotation()) { + float[] editRotationVariation = new float[]{settings.initialRotationVariation().x(), settings.initialRotationVariation().y(), settings.initialRotationVariation().z()}; + if (ImGui.dragFloat3("initial_rotation_variation", editRotationVariation, 0.025F)) { + emitter.setParticleRotationVariation(editRotationVariation[0], editRotationVariation[1], editRotationVariation[2]); + } + } + if (ImGui.checkbox("random_speed", settings.randomSpeed())) { emitter.toggleRandomSpeed(); } - float[] editSpeed = new float[]{settings.particleSpeed()}; + if (settings.randomSpeed()) { + float[] editSpeed = new float[]{settings.particleSpeed(), settings.particleSpeed() + settings.particleSpeedVariation()}; - if (ImGui.dragScalar("particle_speed", editSpeed, 0.01F)) { - emitter.setParticleSpeed(editSpeed[0]); + if (ImGui.dragFloat2("particle_speed", editSpeed, 0.01F, 0, editSpeed[1] + 0.01F)) { + emitter.setParticleSpeed(editSpeed[0], editSpeed[1]); + } + } else { + float[] editSpeed = new float[]{settings.particleSpeed()}; + + if (ImGui.dragScalar("particle_speed", editSpeed, 0.01F)) { + emitter.setParticleSpeed(editSpeed[0], 0); + } } if (ImGui.checkbox("random_size", settings.randomSize())) { @@ -390,7 +455,7 @@ private void renderParticleSettings(MutableParticleEmitter emitter) { if (settings.randomSize()) { float[] editParticleSize = new float[]{settings.particleSize(), settings.particleSize() + settings.particleSizeVariation()}; - if (ImGui.dragFloat2("particle_size", editParticleSize, 0.01F, Math.max(editParticleSize[0], 0.001f), editParticleSize[1])) { + if (ImGui.dragFloat2("particle_size", editParticleSize, 0.01F, Math.max(editParticleSize[0] - 0.01F, 0.001f), editParticleSize[1] + 0.01F)) { emitter.setParticleSize(editParticleSize[0], editParticleSize[1]); } } else { @@ -407,7 +472,7 @@ private void renderParticleSettings(MutableParticleEmitter emitter) { if (settings.randomLifetime()) { int[] editParticleLifetime = new int[]{settings.particleLifetime(), settings.particleLifetime() + (int) settings.particleLifetimeVariation()}; - if (ImGui.dragInt2("particle_lifetime", editParticleLifetime, 0.03F, Math.max(editParticleLifetime[0], 0))) { + if (ImGui.dragInt2("particle_lifetime", editParticleLifetime, 0.03F, editParticleLifetime[0] - 1, editParticleLifetime[1] + 1)) { emitter.setParticleLifetime(editParticleLifetime[0], editParticleLifetime[1]); } } else { @@ -505,6 +570,10 @@ private void renderModules(MutableParticleEmitter emitter) { String name = String.valueOf(ParticleModuleTypeRegistry.REGISTRY.getKey(module.getType())); if (ImGui.collapsingHeader(name)) { ImGui.indent(); + ModuleType.DeprecationStatus status = module.getType().deprecationStatus(); + if (status != null) { + ImGui.textColored(0xFF00FFFF, "DEPRECATED MODULE: %s will be removed in %s (%s)".formatted(name, status.removeVersion(), status.reason())); + } if (module instanceof EditorAttributeProvider attributeProvider) { attributeProvider.renderImGuiAttributes(); } @@ -516,10 +585,6 @@ private void renderModules(MutableParticleEmitter emitter) { } ImGui.unindent(); } - ModuleType.DeprecationStatus status = module.getType().deprecationStatus(); - if (status != null && ImGui.isItemHovered()) { - ImGui.setTooltip("%s will be removed in %s".formatted(name, status.removeVersion())); - } ImGui.popID(); id++; } @@ -632,6 +697,9 @@ private static class MutableParticleEmitter extends ParticleEmitter { public boolean renderEmitterShape = false; public boolean renderDirection; + // In order to facilitate an easier use of rotation, we store this as a Vector3f instead of a Quaternion + private final Vector3f rotation = new Vector3f(); + private MutableParticleEmitter(ParticleSystemManager particleManager, ClientLevel level, ParticleEmitterData data) { super(particleManager, level, data); this.particleData = new QuasarParticleData(this.particleData.shouldCollide(), this.particleData.faceVelocity(), this.particleData.velocityStretchFactor(), this.particleData.modules(), this.particleData.spriteData(), this.particleData.additive(), this.particleData.renderStyle()); @@ -656,7 +724,7 @@ public void render(MatrixStack matrixStack, MultiBufferSource bufferSource, Came for (EmitterShapeSettings shapeSettings : this.getEmitterShapeSettings()) { matrixStack.matrixPush(); matrixStack.translate(this.getPosition()); - shapeSettings.shape().renderShape(matrixStack.toPoseStack(), debugBuilder, shapeSettings.dimensions(), shapeSettings.rotation()); + shapeSettings.shape().renderShape(matrixStack.toPoseStack(), debugBuilder, shapeSettings.dimensions(), shapeSettings.rotation().add(this.rotation.mul(Mth.RAD_TO_DEG, new Vector3f()), new Vector3f())); matrixStack.matrixPop(); } matrixStack.matrixPop(); @@ -667,12 +735,24 @@ public void render(MatrixStack matrixStack, MultiBufferSource bufferSource, Came matrixStack.translate(-camera.getPosition().x, -camera.getPosition().y, -camera.getPosition().z); matrixStack.matrixPush(); matrixStack.translate(this.getPosition()); + matrixStack.rotate(this.getRotation()); - Matrix4f matrix4f = matrixStack.position(); + Matrix4f pose = matrixStack.position(); - debugBuilder.addVertex(matrix4f, 0, 0, 0).setColor(1, 1f, 1f, 1).setNormal(0, 1, 0); Vector3f direction = this.getParticleSettings().initialDirection().normalize(new Vector3f()).mul(this.getParticleSettings().particleSpeed() * 10); - debugBuilder.addVertex(matrix4f, direction.x, direction.y, direction.z).setColor(1f, 0.15f, 0.15f, 1f).setNormal(0, 1, 0); + debugBuilder.addVertex(pose, 0, 0, 0).setColor(1, 1f, 1f, 1).setNormal(matrixStack.pose(), direction.x, direction.y, direction.z); + debugBuilder.addVertex(pose, direction.x, direction.y, direction.z).setColor(1f, 0.15f, 0.15f, 1f).setNormal(matrixStack.pose(), direction.x, direction.y, direction.z); + + if (this.getParticleSettings().randomInitialDirection()) { + Vector3f minDirection = this.getParticleSettings().initialDirection().add(this.getParticleSettings().initialDirectionVariation().mul(-1, new Vector3f()), new Vector3f()).mul(this.getParticleSettings().particleSpeed() * 10); + Vector3f maxDirection = this.getParticleSettings().initialDirection().add(this.getParticleSettings().initialDirectionVariation(), new Vector3f()).mul(this.getParticleSettings().particleSpeed() * 10); + debugBuilder.addVertex(pose, 0, 0, 0).setColor(1, 1f, 1f, 1).setNormal(matrixStack.pose(), minDirection.x, minDirection.y, minDirection.z); + debugBuilder.addVertex(pose, minDirection.x, minDirection.y, minDirection.z).setColor(0.15f, 0.15f, 1f, 1f).setNormal(matrixStack.pose(), minDirection.x, minDirection.y, minDirection.z); + + debugBuilder.addVertex(pose, 0, 0, 0).setColor(1, 1f, 1f, 1).setNormal(matrixStack.pose(), maxDirection.x, maxDirection.y, maxDirection.z); + debugBuilder.addVertex(pose, maxDirection.x, maxDirection.y, maxDirection.z).setColor(0.15f, 0.15f, 1f, 1f).setNormal(matrixStack.pose(), maxDirection.x, maxDirection.y, maxDirection.z); + } + matrixStack.matrixPop(); matrixStack.matrixPop(); } @@ -689,6 +769,25 @@ protected void tick() { } } + public Vector3f getRotationVector() { + return this.rotation; + } + + @Override + public Quaternionf getRotation() { + return new Quaternionf().rotationXYZ(this.rotation.x, this.rotation.y, this.rotation.z); + } + + @Override + public void setRotation(Quaternionfc newRot) { + newRot.getEulerAnglesXYZ(this.rotation); + } + + @Override + public void setRotation(float x, float y, float z) { + this.rotation.set(x, y, z); + } + public void setRate(int rate) { super.setRate(rate); @@ -729,9 +828,11 @@ public void resetShapeTransform(int index) { this.updateShapeSettings(index, oldShape.shape(), new Vector3f(1, 1, 1), new Vector3f(0, 0, 0), oldShape.fromSurface()); } - public void setParticleSpeed(float speed) { + public void setParticleSpeed(float min, float max) { + max = Math.max(min, max); this.setParticleSettings(new ParticleSettingsBuilder(this.getParticleSettings()) - .setParticleSpeed(speed) + .setParticleSpeed(min) + .setParticleSpeedVariation(max - min) .build()); } @@ -787,12 +888,24 @@ public void setParticleDirection(float x, float y, float z) { .build()); } + public void setParticleDirectionVariation(float x, float y, float z) { + this.setParticleSettings(new ParticleSettingsBuilder(this.getParticleSettings()) + .setInitialDirectionVariation(new Vector3f(x, y, z)) + .build()); + } + public void setParticleRotation(float x, float y, float z) { this.setParticleSettings(new ParticleSettingsBuilder(this.getParticleSettings()) .setInitialRotation(new Vector3f(x, y, z)) .build()); } + public void setParticleRotationVariation(float x, float y, float z) { + this.setParticleSettings(new ParticleSettingsBuilder(this.getParticleSettings()) + .setInitialRotationVariation(new Vector3f(x, y, z)) + .build()); + } + public void forceRemove() { this.forceRemoved = true; this.remove(); @@ -800,28 +913,34 @@ public void forceRemove() { private static class ParticleSettingsBuilder { private float particleSpeed; + private float particleSpeedVariation; private float particleSize; private float particleSizeVariation; private int particleLifetime; private float particleLifetimeVariation; private Vector3fc initialDirection; private boolean randomInitialDirection; + private Vector3fc initialDirectionVariation; private Vector3fc initialRotation; private boolean randomInitialRotation; + private Vector3fc initialRotationVariation; private boolean randomSpeed; private boolean randomSize; private boolean randomLifetime; public ParticleSettingsBuilder(ParticleSettings from) { this.particleSpeed = from.particleSpeed(); + this.particleSpeedVariation = from.particleSpeedVariation(); this.particleSize = from.particleSize(); this.particleSizeVariation = from.particleSizeVariation(); this.particleLifetime = from.particleLifetime(); this.particleLifetimeVariation = from.particleLifetimeVariation(); this.initialDirection = from.initialDirection(); this.randomInitialDirection = from.randomInitialDirection(); + this.initialDirectionVariation = from.initialDirectionVariation(); this.initialRotation = from.initialRotation(); this.randomInitialRotation = from.randomInitialRotation(); + this.initialRotationVariation = from.initialRotationVariation(); this.randomSpeed = from.randomSpeed(); this.randomSize = from.randomSize(); this.randomLifetime = from.randomLifetime(); @@ -887,8 +1006,23 @@ public ParticleSettingsBuilder setRandomLifetime(boolean randomLifetime) { return this; } + public ParticleSettingsBuilder setInitialDirectionVariation(Vector3fc initialDirectionVariation) { + this.initialDirectionVariation = initialDirectionVariation; + return this; + } + + public ParticleSettingsBuilder setInitialRotationVariation(Vector3fc initialRotationVariation) { + this.initialRotationVariation = initialRotationVariation; + return this; + } + + public ParticleSettingsBuilder setParticleSpeedVariation(float particleSpeedVariation) { + this.particleSpeedVariation = particleSpeedVariation; + return this; + } + public ParticleSettings build() { - return new ParticleSettings(this.particleSpeed, this.particleSize, this.particleSizeVariation, this.particleLifetime, this.particleLifetimeVariation, this.initialDirection, this.randomInitialDirection, this.initialRotation, this.randomInitialRotation, this.randomSpeed, this.randomSize, this.randomLifetime); + return new ParticleSettings(this.particleSpeed, this.particleSpeedVariation, this.particleSize, this.particleSizeVariation, this.particleLifetime, this.particleLifetimeVariation, this.initialDirection, this.randomInitialDirection, this.initialDirectionVariation, this.initialRotation, this.randomInitialRotation, this.initialRotationVariation, this.randomSpeed, this.randomSize, this.randomLifetime); } } } diff --git a/common/src/main/java/foundry/veil/impl/client/necromancer/render/NecromancerRenderDispatcher.java b/common/src/main/java/foundry/veil/impl/client/necromancer/render/NecromancerRenderDispatcher.java index 9212a4982..64bb73fb1 100644 --- a/common/src/main/java/foundry/veil/impl/client/necromancer/render/NecromancerRenderDispatcher.java +++ b/common/src/main/java/foundry/veil/impl/client/necromancer/render/NecromancerRenderDispatcher.java @@ -14,6 +14,7 @@ import foundry.veil.api.client.render.shader.block.DynamicShaderBlock; import foundry.veil.api.client.render.shader.block.ShaderBlock; import foundry.veil.api.client.render.vertex.VertexArray; +import foundry.veil.api.compat.ImmersivePortalsCompat; import it.unimi.dsi.fastutil.floats.FloatArrayList; import it.unimi.dsi.fastutil.floats.FloatList; import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; @@ -90,7 +91,7 @@ private static void updateBlockSize(int skeletonCount, int dataSize) { boneBlock.setSize(Skeleton.UNIFORM_STRIDE * newSize); VeilRenderSystem.renderer().getShaderDefinitions().set("NECROMANCER_BONE_BUFFER_SIZE", Long.toString(newSize)); - if (VeilRenderSystem.directStateAccessSupported()) { + if (VeilRenderSystem.directStateAccessSupported() && !ImmersivePortalsCompat.isLoaded()) { glNamedBufferData(boneBuffer, boneBlock.getSize(), GL_DYNAMIC_DRAW); } else { glBindBuffer(GL_UNIFORM_BUFFER, boneBuffer); diff --git a/common/src/main/java/foundry/veil/impl/client/render/dynamicbuffer/DynamicBufferManager.java b/common/src/main/java/foundry/veil/impl/client/render/dynamicbuffer/DynamicBufferManager.java index 071f3899c..7104f4111 100644 --- a/common/src/main/java/foundry/veil/impl/client/render/dynamicbuffer/DynamicBufferManager.java +++ b/common/src/main/java/foundry/veil/impl/client/render/dynamicbuffer/DynamicBufferManager.java @@ -10,6 +10,7 @@ import foundry.veil.api.client.render.dynamicbuffer.DynamicBuffersChange; import foundry.veil.api.client.render.framebuffer.AdvancedFbo; import foundry.veil.api.client.render.framebuffer.FramebufferManager; +import foundry.veil.api.compat.ImmersivePortalsCompat; import foundry.veil.ext.RenderTargetExtension; import foundry.veil.ext.ShaderInstanceExtension; import foundry.veil.mixin.dynamicbuffer.accessor.DynamicBufferGameRendererAccessor; @@ -30,7 +31,7 @@ import static org.lwjgl.opengl.GL11C.*; import static org.lwjgl.opengl.GL12C.*; import static org.lwjgl.opengl.GL14C.GL_TEXTURE_LOD_BIAS; -import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT1; +import static org.lwjgl.opengl.GL30C.*; @ApiStatus.Internal public class DynamicBufferManager implements NativeResource { @@ -162,7 +163,7 @@ public boolean isEnabled() { } public void setEnabled(boolean enabled) { - if (!Veil.IRIS) { + if (!Veil.IRIS && (!ImmersivePortalsCompat.isLoaded() || !ImmersivePortalsCompat.INSTANCE.renderingThroughPortal())) { this.enabled = enabled; } } @@ -331,6 +332,7 @@ public void endFrame() { if (!shaderIterator.hasNext()) { Veil.LOGGER.info("Finished uploading vanilla shaders"); + this.swapShaders.clear(); } } diff --git a/common/src/main/java/foundry/veil/impl/client/render/dynamicbuffer/VanillaShaderCompiler.java b/common/src/main/java/foundry/veil/impl/client/render/dynamicbuffer/VanillaShaderCompiler.java index 9cec7db4e..e2a286a98 100644 --- a/common/src/main/java/foundry/veil/impl/client/render/dynamicbuffer/VanillaShaderCompiler.java +++ b/common/src/main/java/foundry/veil/impl/client/render/dynamicbuffer/VanillaShaderCompiler.java @@ -5,6 +5,7 @@ import com.mojang.blaze3d.vertex.VertexFormat; import foundry.veil.Veil; import foundry.veil.api.client.render.VeilRenderSystem; +import foundry.veil.api.compat.ImmersivePortalsCompat; import foundry.veil.ext.ShaderInstanceExtension; import foundry.veil.impl.ThreadTaskScheduler; import foundry.veil.impl.client.render.shader.processor.VanillaShaderProcessor; @@ -111,6 +112,10 @@ public CompletableFuture reload(Collection shaders) { int activeBuffers = VeilRenderSystem.renderer().getDynamicBufferManger().getActiveBuffers(); GLCapabilities capabilities = GL.getCapabilities(); + if (ImmersivePortalsCompat.isLoaded()) { + ImmersivePortalsCompat.INSTANCE.init(); + } + Stopwatch stopwatch = Stopwatch.createStarted(); ThreadTaskScheduler scheduler = new ThreadTaskScheduler("VeilVanillaShaderCompile", Math.max(1, Runtime.getRuntime().availableProcessors() / 6), () -> { for (String lastFrameShader : LAST_FRAME_SHADERS) { diff --git a/common/src/main/java/foundry/veil/impl/client/render/pipeline/VeilFirstPersonRenderer.java b/common/src/main/java/foundry/veil/impl/client/render/pipeline/VeilFirstPersonRenderer.java index 862938b7a..ee3135e32 100644 --- a/common/src/main/java/foundry/veil/impl/client/render/pipeline/VeilFirstPersonRenderer.java +++ b/common/src/main/java/foundry/veil/impl/client/render/pipeline/VeilFirstPersonRenderer.java @@ -9,6 +9,7 @@ import foundry.veil.api.client.render.framebuffer.VeilFramebuffers; import foundry.veil.api.client.render.post.PostPipeline; import foundry.veil.api.client.render.post.PostProcessingManager; +import foundry.veil.api.compat.ImmersivePortalsCompat; import foundry.veil.ext.RenderTargetExtension; import foundry.veil.impl.client.render.dynamicbuffer.DynamicBufferManager; import net.minecraft.client.Minecraft; @@ -39,7 +40,7 @@ public static void bind(int mask) { free(); firstPerson = AdvancedFbo.withSize(w, h) .addColorTextureWrapper(framebufferTexture) - .setFormat(stencil ? FramebufferAttachmentDefinition.Format.DEPTH32F_STENCIL8 : FramebufferAttachmentDefinition.Format.DEPTH_COMPONENT) + .setFormat((stencil && !ImmersivePortalsCompat.isLoaded()) ? FramebufferAttachmentDefinition.Format.DEPTH32F_STENCIL8 : FramebufferAttachmentDefinition.Format.DEPTH_COMPONENT) .setDepthTextureBuffer() .setDebugLabel("Veil First Person") .build(true); diff --git a/common/src/main/java/foundry/veil/impl/client/render/shader/processor/VanillaShaderProcessor.java b/common/src/main/java/foundry/veil/impl/client/render/shader/processor/VanillaShaderProcessor.java index 061e271d1..0d738de9c 100644 --- a/common/src/main/java/foundry/veil/impl/client/render/shader/processor/VanillaShaderProcessor.java +++ b/common/src/main/java/foundry/veil/impl/client/render/shader/processor/VanillaShaderProcessor.java @@ -7,7 +7,9 @@ import foundry.veil.api.client.render.shader.processor.ShaderImporter; import foundry.veil.api.client.render.shader.processor.ShaderInjectProcessor; import foundry.veil.api.client.render.shader.processor.ShaderPreProcessor; +import foundry.veil.api.compat.ImmersivePortalsCompat; import foundry.veil.impl.client.render.dynamicbuffer.DynamicBufferProcessor; +import foundry.veil.impl.compat.ImmersivePortalsShaderPreProcessor; import io.github.ocelot.glslprocessor.api.GlslParser; import io.github.ocelot.glslprocessor.api.GlslSyntaxException; import io.github.ocelot.glslprocessor.api.node.GlslTree; @@ -34,6 +36,9 @@ public static void setup(ResourceProvider provider) { ShaderProcessorList list = new ShaderProcessorList(provider); list.addPreprocessor(new ShaderInjectProcessor(), false); list.addPreprocessor(new DynamicBufferProcessor(), false); + if (ImmersivePortalsCompat.isLoaded()) { + list.addPreprocessor(new ImmersivePortalsShaderPreProcessor(), false); + } VeilClient.clientPlatform().onRegisterShaderPreProcessors(provider, list); PROCESSOR.set(list); } diff --git a/common/src/main/java/foundry/veil/impl/compat/ImmersivePortalsShaderPreProcessor.java b/common/src/main/java/foundry/veil/impl/compat/ImmersivePortalsShaderPreProcessor.java new file mode 100644 index 000000000..3c9cec43e --- /dev/null +++ b/common/src/main/java/foundry/veil/impl/compat/ImmersivePortalsShaderPreProcessor.java @@ -0,0 +1,51 @@ +package foundry.veil.impl.compat; + +import com.mojang.blaze3d.shaders.Program; +import foundry.veil.api.client.render.shader.processor.ShaderPreProcessor; +import foundry.veil.api.compat.ImmersivePortalsCompat; +import io.github.ocelot.glslprocessor.api.GlslParser; +import io.github.ocelot.glslprocessor.api.GlslSyntaxException; +import io.github.ocelot.glslprocessor.api.node.GlslTree; +import io.github.ocelot.glslprocessor.lib.anarres.cpp.LexerException; +import org.jetbrains.annotations.ApiStatus; + +import java.io.IOException; + +import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER; +import static org.lwjgl.opengl.GL20.GL_VERTEX_SHADER; + +@ApiStatus.Internal +public class ImmersivePortalsShaderPreProcessor implements ShaderPreProcessor { + + @Override + public void modify(Context ctx, GlslTree tree) throws IOException, GlslSyntaxException, LexerException { + ImmersivePortalsCompat compat = ImmersivePortalsCompat.INSTANCE; + if (ctx.name() == null || compat == null) return; + + Program.Type type; + if (ctx.type() == GL_VERTEX_SHADER) { + type = Program.Type.VERTEX; + } else if (ctx.type() == GL_FRAGMENT_SHADER) { + type = Program.Type.FRAGMENT; + } else { + return; + } + + String key = ctx.name().toString(); + if (!compat.shouldAddUniform(key)) { + key = ctx.name().getPath(); + if (!compat.shouldAddUniform(key)) { + key = key.substring(key.lastIndexOf('/') + 1, key.length() - 4); + if (!compat.shouldAddUniform(key)) { + return; + } + } + } + + String transformed = compat.transform(type, key, tree.toSourceString()); + GlslTree newShader = GlslParser.parse(transformed); + + tree.getBody().clear(); + tree.getBody().addAll(newShader.getBody()); + } +} diff --git a/common/src/main/java/foundry/veil/mixin/screenshake/client/ScreenShakeGameRendererMixin.java b/common/src/main/java/foundry/veil/mixin/screenshake/client/ScreenShakeGameRendererMixin.java new file mode 100644 index 000000000..4e6fa9f04 --- /dev/null +++ b/common/src/main/java/foundry/veil/mixin/screenshake/client/ScreenShakeGameRendererMixin.java @@ -0,0 +1,36 @@ +package foundry.veil.mixin.screenshake.client; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import foundry.veil.api.client.render.VeilRenderSystem; +import net.minecraft.client.Camera; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.world.phys.Vec3; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GameRenderer.class) +public class ScreenShakeGameRendererMixin { + + @WrapOperation(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;getPosition()Lnet/minecraft/world/phys/Vec3;")) + private Vec3 veil$moveCameraPosition(Camera instance, Operation original, @Local(name = "f") float partialTick) { + Vector3f pos = VeilRenderSystem.renderer().getScreenShakeManager().getPosition(partialTick); + return original.call(instance).add(pos.x, pos.y, pos.z); + } + + @WrapOperation(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;rotation()Lorg/joml/Quaternionf;")) + private Quaternionf veil$offsetCameraRotation(Camera instance, Operation original, @Local(name = "f") float partialTick) { + Vector3f offset = VeilRenderSystem.renderer().getScreenShakeManager().getPosition(partialTick).mul(0.1f); + return original.call(instance).rotateXYZ(offset.x, offset.y, offset.z, new Quaternionf()); + } + + @Inject(method = "tick", at = @At("HEAD")) + private void veil$tickScreenShake(CallbackInfo ci) { + VeilRenderSystem.renderer().getScreenShakeManager().tick(); + } +} diff --git a/common/src/main/java/foundry/veil/mixin/screenshake/client/ScreenShakeLevelRendererMixin.java b/common/src/main/java/foundry/veil/mixin/screenshake/client/ScreenShakeLevelRendererMixin.java new file mode 100644 index 000000000..1eccbca01 --- /dev/null +++ b/common/src/main/java/foundry/veil/mixin/screenshake/client/ScreenShakeLevelRendererMixin.java @@ -0,0 +1,22 @@ +package foundry.veil.mixin.screenshake.client; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import foundry.veil.api.client.render.VeilRenderSystem; +import net.minecraft.client.Camera; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.world.phys.Vec3; +import org.joml.Vector3f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(LevelRenderer.class) +public class ScreenShakeLevelRendererMixin { + + @WrapOperation(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;getPosition()Lnet/minecraft/world/phys/Vec3;")) + private Vec3 veil$moveCameraPosition(Camera instance, Operation original, @Local(name = "f") float partialTick) { + Vector3f pos = VeilRenderSystem.renderer().getScreenShakeManager().getPosition(partialTick); + return original.call(instance).add(pos.x, pos.y, pos.z); + } +} diff --git a/common/src/main/resources/veil.screenshake.mixins.json b/common/src/main/resources/veil.screenshake.mixins.json new file mode 100644 index 000000000..1a5de4376 --- /dev/null +++ b/common/src/main/resources/veil.screenshake.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "foundry.veil.mixin.screenshake", + "compatibilityLevel": "JAVA_21", + "plugin": "foundry.veil.VeilMixinPluginImpl", + "client": [ + "client.ScreenShakeGameRendererMixin", + "client.ScreenShakeLevelRendererMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} + \ No newline at end of file diff --git a/fabric/build.gradle b/fabric/build.gradle index 4ae3a71ef..4506eb287 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -64,4 +64,7 @@ dependencies { include(api("io.github.ocelot:glsl-processor:${glsl_processor_version}")) modCompileOnly "foundry.imguimc:imguimc-fabric-${minecraft_version}:$imguimc_version" + + // Immersive portals compat + modCompileOnly "maven.modrinth:immersiveportals:v${immptl_version}-mc${minecraft_version}" } diff --git a/fabric/src/main/java/foundry/veil/fabric/VeilFabricClient.java b/fabric/src/main/java/foundry/veil/fabric/VeilFabricClient.java index 6c8e95b5b..8cb0ee065 100644 --- a/fabric/src/main/java/foundry/veil/fabric/VeilFabricClient.java +++ b/fabric/src/main/java/foundry/veil/fabric/VeilFabricClient.java @@ -1,11 +1,16 @@ package foundry.veil.fabric; import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.FloatArgumentType; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import foundry.veil.Veil; import foundry.veil.VeilClient; import foundry.veil.api.client.render.VeilRenderSystem; +import foundry.veil.api.client.render.VeilRenderer; import foundry.veil.api.client.render.dynamicbuffer.DynamicBufferType; +import foundry.veil.api.client.util.Easing; import foundry.veil.api.quasar.data.QuasarParticles; import foundry.veil.api.quasar.particle.ParticleEmitter; import foundry.veil.api.quasar.particle.ParticleSystemManager; @@ -15,6 +20,8 @@ import foundry.veil.impl.client.imgui.VeilImGuiCompat; import foundry.veil.impl.client.render.shader.VeilVanillaShaders; import foundry.veil.impl.network.VeilClientServerFlags; +import foundry.veil.api.screenshake.type.GlobalScreenShake; +import foundry.veil.api.screenshake.type.LocalScreenShake; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; @@ -106,6 +113,38 @@ public void onInitializeClient() { })) )); dispatcher.register(debugBuilder); + + LiteralArgumentBuilder shakeBuilder = LiteralArgumentBuilder.literal("shake"); + shakeBuilder + .then(ClientCommandManager.literal("local") + .then(ClientCommandManager.argument("expression", StringArgumentType.string()).then(ClientCommandManager.argument("position", Vec3Argument.vec3()).then(ClientCommandManager.argument("length", IntegerArgumentType.integer(0)).then(ClientCommandManager.argument("radius", FloatArgumentType.floatArg(0)) + .executes(ctx -> { + try { + VeilRenderer renderer = VeilRenderSystem.renderer(); + renderer.getScreenShakeManager().addScreenShake(new LocalScreenShake(StringArgumentType.getString(ctx, "expression"), ctx.getArgument("position", WorldCoordinates.class).getPosition(ctx.getSource().getEntity().createCommandSourceStack()), IntegerArgumentType.getInteger(ctx, "length"), FloatArgumentType.getFloat(ctx, "radius"), Easing.LINEAR)); + + return Command.SINGLE_SUCCESS; + } catch (Exception e) { + return 0; + } + }) + )))) + ) + .then(ClientCommandManager.literal("global") + .then(ClientCommandManager.argument("expression", StringArgumentType.string()).then(ClientCommandManager.argument("length", IntegerArgumentType.integer(0)) + .executes(ctx -> { + try { + VeilRenderer renderer = VeilRenderSystem.renderer(); + renderer.getScreenShakeManager().addScreenShake(new GlobalScreenShake(StringArgumentType.getString(ctx, "expression"), ctx.getArgument("length", Integer.class))); + + return Command.SINGLE_SUCCESS; + } catch (Exception e) { + return 0; + } + }) + )) + ); + dispatcher.register(shakeBuilder); } }); } diff --git a/fabric/src/main/java/foundry/veil/fabric/compat/immptl/VeilFabricImmersivePortalsCompat.java b/fabric/src/main/java/foundry/veil/fabric/compat/immptl/VeilFabricImmersivePortalsCompat.java new file mode 100644 index 000000000..64615f81a --- /dev/null +++ b/fabric/src/main/java/foundry/veil/fabric/compat/immptl/VeilFabricImmersivePortalsCompat.java @@ -0,0 +1,35 @@ +package foundry.veil.fabric.compat.immptl; + +import com.mojang.blaze3d.shaders.Program; +import foundry.veil.api.compat.ImmersivePortalsCompat; +import net.minecraft.client.Minecraft; +import qouteall.imm_ptl.core.render.ShaderCodeTransformation; + +public class VeilFabricImmersivePortalsCompat implements ImmersivePortalsCompat { + private static boolean hasInitialized = false; + public static boolean renderingPortal = false; + + @Override + public void init() { + if (!Minecraft.getInstance().getResourceManager().getNamespaces().isEmpty() && !hasInitialized) { + // prevent initializing more than once + hasInitialized = true; + ShaderCodeTransformation.init(); + } + } + + @Override + public String transform(Program.Type type, String shaderId, String inputCode) { + return ShaderCodeTransformation.transform(type, shaderId, inputCode); + } + + @Override + public boolean shouldAddUniform(String shaderName) { + return ShaderCodeTransformation.shouldAddUniform(shaderName); + } + + @Override + public boolean renderingThroughPortal() { + return renderingPortal; + } +} diff --git a/fabric/src/main/java/foundry/veil/fabric/mixin/compat/immersive_portals/MyGameRendererMixin.java b/fabric/src/main/java/foundry/veil/fabric/mixin/compat/immersive_portals/MyGameRendererMixin.java new file mode 100644 index 000000000..5cfb21201 --- /dev/null +++ b/fabric/src/main/java/foundry/veil/fabric/mixin/compat/immersive_portals/MyGameRendererMixin.java @@ -0,0 +1,42 @@ +package foundry.veil.fabric.mixin.compat.immersive_portals; + +import com.llamalad7.mixinextras.sugar.Local; +import foundry.veil.api.client.render.VeilRenderSystem; +import foundry.veil.fabric.compat.immptl.VeilFabricImmersivePortalsCompat; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import org.joml.Quaternionf; +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.callback.CallbackInfo; +import qouteall.imm_ptl.core.render.MyGameRenderer; + +import java.util.function.Consumer; + +@Mixin(MyGameRenderer.class) +public class MyGameRendererMixin { + + @Shadow + @Final + public static Minecraft client; + + @Inject(method = "switchAndRenderTheWorld", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;applyModelViewMatrix()V", ordinal = 0)) + private static void veil$prePortalRender(ClientLevel newWorld, Vec3 thisTickCameraPos, Vec3 lastTickCameraPos, Consumer invokeWrapper, int renderDistance, boolean doRenderHand, CallbackInfo ci) { + VeilFabricImmersivePortalsCompat.renderingPortal = true; + } + + @Inject(method = "switchAndRenderTheWorld", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;applyModelViewMatrix()V", ordinal = 1)) + private static void veil$postPortalRender(ClientLevel newWorld, Vec3 thisTickCameraPos, Vec3 lastTickCameraPos, Consumer invokeWrapper, int renderDistance, boolean doRenderHand, CallbackInfo ci, @Local(name = "oldProjectionMatrix") Matrix4f oldProjectionMatrix, @Local(name = "oldCamera") Camera oldCamera) { + VeilFabricImmersivePortalsCompat.renderingPortal = false; + Quaternionf quaternionf = oldCamera.rotation().conjugate(new Quaternionf()); + Matrix4f matrix4f2 = (new Matrix4f()).rotation(quaternionf); + VeilRenderSystem.renderer().getCameraMatrices().update(oldProjectionMatrix, matrix4f2, oldCamera.getPosition().x(), oldCamera.getPosition().y(), oldCamera.getPosition().z()); + } + +} diff --git a/fabric/src/main/resources/META-INF/services/foundry.veil.api.compat.ImmersivePortalsCompat b/fabric/src/main/resources/META-INF/services/foundry.veil.api.compat.ImmersivePortalsCompat new file mode 100644 index 000000000..9500c3c59 --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/foundry.veil.api.compat.ImmersivePortalsCompat @@ -0,0 +1 @@ +foundry.veil.fabric.compat.immptl.VeilFabricImmersivePortalsCompat \ No newline at end of file diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 17c9fb22d..199de3573 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -52,6 +52,7 @@ "${mod_id}.rendertype.mixins.json", "${mod_id}.resource.mixins.json", "${mod_id}.scheduler.mixins.json", + "${mod_id}.screenshake.mixins.json", "${mod_id}.shader.mixins.json", "${mod_id}.shader_recompile.mixins.json", "${mod_id}.fabric.mixins.json" diff --git a/fabric/src/main/resources/veil.fabric.mixins.json b/fabric/src/main/resources/veil.fabric.mixins.json index 01693d86b..e9bff5ba4 100644 --- a/fabric/src/main/resources/veil.fabric.mixins.json +++ b/fabric/src/main/resources/veil.fabric.mixins.json @@ -45,7 +45,8 @@ "compat.sodium.ShaderChunkRendererMixin", "compat.sodium.ShaderLoaderMixin", "compat.sodium.ShaderParserMixin", - "compat.sodium.SodiumWorldRendererAccessor" + "compat.sodium.SodiumWorldRendererAccessor", + "compat.immersive_portals.MyGameRendererMixin" ], "injectors": { "defaultRequire": 1 diff --git a/gradle.properties b/gradle.properties index 24e95d455..f210ab302 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,6 +25,7 @@ imguimc_version=2.0.0 lwjgl_version=3.3.3 # There is no 0.8.12-alpha.3 for NeoForge, so just use 0.8.12-alpha.2 sodium_version=0.8.12-alpha.2+mc1.21.1 +immptl_version=6.0.6 # Fabric ## See https://fabricmc.net/develop for new versions diff --git a/neoforge/build.gradle b/neoforge/build.gradle index 4570cbe79..c80b3af42 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -67,4 +67,7 @@ dependencies { // Allows sodium compat to compile properly compileOnly "org.sinytra.forgified-fabric-api:forgified-fabric-api:0.104.0+2.0.19+1.21.1" + + // Immersive portals compat + compileOnly "maven.modrinth:immersive-portals-neoforge:${immptl_version}" } diff --git a/neoforge/src/main/java/foundry/veil/forge/VeilForgeClientEvents.java b/neoforge/src/main/java/foundry/veil/forge/VeilForgeClientEvents.java index 93779c8cf..5e449b585 100644 --- a/neoforge/src/main/java/foundry/veil/forge/VeilForgeClientEvents.java +++ b/neoforge/src/main/java/foundry/veil/forge/VeilForgeClientEvents.java @@ -2,11 +2,15 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.FloatArgumentType; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import foundry.veil.Veil; import foundry.veil.api.client.render.VeilRenderSystem; import foundry.veil.api.client.render.VeilRenderer; import foundry.veil.api.client.render.dynamicbuffer.DynamicBufferType; +import foundry.veil.api.client.util.Easing; import foundry.veil.api.quasar.data.QuasarParticles; import foundry.veil.api.quasar.particle.ParticleEmitter; import foundry.veil.api.quasar.particle.ParticleSystemManager; @@ -15,6 +19,8 @@ import foundry.veil.impl.client.VeilClientSchedulerImpl; import foundry.veil.impl.client.imgui.VeilImGuiCompat; import foundry.veil.impl.network.VeilClientServerFlags; +import foundry.veil.api.screenshake.type.GlobalScreenShake; +import foundry.veil.api.screenshake.type.LocalScreenShake; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.ResourceLocationArgument; @@ -118,6 +124,37 @@ public static void registerClientCommands(RegisterClientCommandsEvent event) { })) )); dispatcher.register(debugBuilder); + + LiteralArgumentBuilder shakeBuilder = Commands.literal("shake"); + shakeBuilder + .then(Commands.literal("local") + .then(Commands.argument("expression", StringArgumentType.string()).then(Commands.argument("position", Vec3Argument.vec3()).then(Commands.argument("length", IntegerArgumentType.integer(0)).then(Commands.argument("radius", FloatArgumentType.floatArg(0)) + .executes(ctx -> { + try { + VeilRenderer renderer = VeilRenderSystem.renderer(); + renderer.getScreenShakeManager().addScreenShake(new LocalScreenShake(StringArgumentType.getString(ctx, "expression"), Vec3Argument.getVec3(ctx, "position"), IntegerArgumentType.getInteger(ctx, "length"), FloatArgumentType.getFloat(ctx, "radius"), Easing.LINEAR)); + + return Command.SINGLE_SUCCESS; + } catch (Exception e) { + return 0; + } + }) + )))) + ) + .then(Commands.literal("global") + .then(Commands.argument("expression", StringArgumentType.string()).then(Commands.argument("length", IntegerArgumentType.integer(0)) + .executes(ctx -> { + try { + VeilRenderer renderer = VeilRenderSystem.renderer(); + renderer.getScreenShakeManager().addScreenShake(new GlobalScreenShake(StringArgumentType.getString(ctx, "expression"), ctx.getArgument("length", Integer.class))); + + return Command.SINGLE_SUCCESS; + } catch (Exception e) { + return 0; + } + }))) + ); + dispatcher.register(shakeBuilder); } } diff --git a/neoforge/src/main/java/foundry/veil/forge/compat/immptl/VeilForgeImmersivePortalsCompat.java b/neoforge/src/main/java/foundry/veil/forge/compat/immptl/VeilForgeImmersivePortalsCompat.java new file mode 100644 index 000000000..e02c7b127 --- /dev/null +++ b/neoforge/src/main/java/foundry/veil/forge/compat/immptl/VeilForgeImmersivePortalsCompat.java @@ -0,0 +1,35 @@ +package foundry.veil.forge.compat.immptl; + +import com.mojang.blaze3d.shaders.Program; +import foundry.veil.api.compat.ImmersivePortalsCompat; +import net.minecraft.client.Minecraft; +import qouteall.imm_ptl.core.render.ShaderCodeTransformation; + +public class VeilForgeImmersivePortalsCompat implements ImmersivePortalsCompat { + private static boolean hasInitialized = false; + public static boolean renderingPortal = false; + + @Override + public void init() { + if (!Minecraft.getInstance().getResourceManager().getNamespaces().isEmpty() && !hasInitialized) { + // prevent initializing more than once + hasInitialized = true; + ShaderCodeTransformation.init(); + } + } + + @Override + public String transform(Program.Type type, String shaderId, String inputCode) { + return ShaderCodeTransformation.transform(type, shaderId, inputCode); + } + + @Override + public boolean shouldAddUniform(String shaderName) { + return ShaderCodeTransformation.shouldAddUniform(shaderName); + } + + @Override + public boolean renderingThroughPortal() { + return renderingPortal; + } +} diff --git a/neoforge/src/main/java/foundry/veil/forge/mixin/compat/imm_ptl/MyGameRendererMixin.java b/neoforge/src/main/java/foundry/veil/forge/mixin/compat/imm_ptl/MyGameRendererMixin.java new file mode 100644 index 000000000..7a5d99b6c --- /dev/null +++ b/neoforge/src/main/java/foundry/veil/forge/mixin/compat/imm_ptl/MyGameRendererMixin.java @@ -0,0 +1,42 @@ +package foundry.veil.forge.mixin.compat.imm_ptl; + +import com.llamalad7.mixinextras.sugar.Local; +import foundry.veil.api.client.render.VeilRenderSystem; +import foundry.veil.forge.compat.immptl.VeilForgeImmersivePortalsCompat; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import org.joml.Quaternionf; +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.callback.CallbackInfo; +import qouteall.imm_ptl.core.render.MyGameRenderer; + +import java.util.function.Consumer; + +@Mixin(MyGameRenderer.class) +public class MyGameRendererMixin { + + @Shadow + @Final + public static Minecraft client; + + @Inject(method = "switchAndRenderTheWorld", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;applyModelViewMatrix()V", ordinal = 0)) + private static void veil$prePortalRender(ClientLevel newWorld, Vec3 thisTickCameraPos, Vec3 lastTickCameraPos, Consumer invokeWrapper, int renderDistance, boolean doRenderHand, CallbackInfo ci) { + VeilForgeImmersivePortalsCompat.renderingPortal = true; + } + + @Inject(method = "switchAndRenderTheWorld", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;applyModelViewMatrix()V", ordinal = 1)) + private static void veil$postPortalRender(ClientLevel newWorld, Vec3 thisTickCameraPos, Vec3 lastTickCameraPos, Consumer invokeWrapper, int renderDistance, boolean doRenderHand, CallbackInfo ci, @Local(name = "oldProjectionMatrix") Matrix4f oldProjectionMatrix, @Local(name = "oldCamera") Camera oldCamera) { + VeilForgeImmersivePortalsCompat.renderingPortal = false; + Quaternionf quaternionf = oldCamera.rotation().conjugate(new Quaternionf()); + Matrix4f matrix4f2 = (new Matrix4f()).rotation(quaternionf); + VeilRenderSystem.renderer().getCameraMatrices().update(oldProjectionMatrix, matrix4f2, oldCamera.getPosition().x(), oldCamera.getPosition().y(), oldCamera.getPosition().z()); + } + +} diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml index 430bab293..f0d2cad01 100644 --- a/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -44,6 +44,8 @@ description='''${description}''' config = "${mod_id}.resource.mixins.json" [[mixins]] config = "${mod_id}.scheduler.mixins.json" +[[mixins]] + config = "${mod_id}.screenshake.mixins.json" [[mixins]] config = "${mod_id}.shader.mixins.json" [[mixins]] diff --git a/neoforge/src/main/resources/META-INF/services/foundry.veil.api.compat.ImmersivePortalsCompat b/neoforge/src/main/resources/META-INF/services/foundry.veil.api.compat.ImmersivePortalsCompat new file mode 100644 index 000000000..64e9badb4 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/foundry.veil.api.compat.ImmersivePortalsCompat @@ -0,0 +1 @@ +foundry.veil.forge.compat.immptl.VeilForgeImmersivePortalsCompat \ No newline at end of file diff --git a/neoforge/src/main/resources/veil.neoforge.mixins.json b/neoforge/src/main/resources/veil.neoforge.mixins.json index 70d47e599..9ed5a16bf 100644 --- a/neoforge/src/main/resources/veil.neoforge.mixins.json +++ b/neoforge/src/main/resources/veil.neoforge.mixins.json @@ -1,48 +1,49 @@ { - "required": true, - "minVersion": "0.8", - "package": "foundry.veil.forge.mixin", - "compatibilityLevel": "JAVA_21", - "plugin": "foundry.veil.VeilMixinPluginImpl", - "mixins": [ - "DeferredRegisterMixin", - "RegistriesMixin", - "resources.PathPackResourcesMixin" - ], - "client": [ - "client.MinecraftMixin", - "client.RenderBuffersMixin", - "client.RenderTypeMixin", - "client.command.ClientCommandSourceStackMixin", - "client.debug.vanilla.DebugLevelRendererMixin", - "client.dynamicbuffer.LevelRendererMixin", - "client.perspective.iris.PipelineManagerMixin", - "client.perspective.sodium.OcclusionCullerMixin", - "client.perspective.sodium.RenderRegionMixin", - "client.perspective.sodium.RenderSectionManagerAccessor", - "client.perspective.sodium.RenderSectionManagerMixin", - "client.perspective.sodium.RenderSectionMixin", - "client.perspective.sodium.SodiumWorldRendererMixin", - "client.perspective.vanilla.LevelRendererMixin", - "compat.iris.IrisRenderingPipelineAccessor", - "compat.iris.IrisRenderingPipelineMixin", - "compat.iris.IrisRenderTargetMixin", - "compat.iris.ShaderWrapperMixin", - "compat.sodium.BlockRendererMixin", - "compat.sodium.ChunkMeshFormatsMixin", - "compat.sodium.ChunkShaderOptionsMixin", - "compat.sodium.ChunkVertexConsumerMixin", - "compat.sodium.ChunkVertexEncoderVertexMixin", - "compat.sodium.DefaultFluidRendererMixin", - "compat.sodium.DefaultShaderInterfaceMixin", - "compat.sodium.RenderSectionManagerAccessor", - "compat.sodium.ShaderChunkRendererMixin", - "compat.sodium.ShaderLoaderMixin", - "compat.sodium.ShaderParserMixin", - "compat.sodium.SodiumWorldRendererAccessor", - "compat.sodium.SortedRenderListsAccessor" - ], - "injectors": { - "defaultRequire": 1 + "required": true, + "minVersion": "0.8", + "package": "foundry.veil.forge.mixin", + "compatibilityLevel": "JAVA_21", + "plugin": "foundry.veil.VeilMixinPluginImpl", + "mixins": [ + "DeferredRegisterMixin", + "RegistriesMixin", + "resources.PathPackResourcesMixin" + ], + "client": [ + "client.MinecraftMixin", + "client.RenderBuffersMixin", + "client.RenderTypeMixin", + "client.command.ClientCommandSourceStackMixin", + "client.debug.vanilla.DebugLevelRendererMixin", + "client.dynamicbuffer.LevelRendererMixin", + "client.perspective.iris.PipelineManagerMixin", + "client.perspective.sodium.OcclusionCullerMixin", + "client.perspective.sodium.RenderRegionMixin", + "client.perspective.sodium.RenderSectionManagerAccessor", + "client.perspective.sodium.RenderSectionManagerMixin", + "client.perspective.sodium.RenderSectionMixin", + "client.perspective.sodium.SodiumWorldRendererMixin", + "client.perspective.vanilla.LevelRendererMixin", + "compat.iris.IrisRenderingPipelineAccessor", + "compat.iris.IrisRenderingPipelineMixin", + "compat.iris.IrisRenderTargetMixin", + "compat.iris.ShaderWrapperMixin", + "compat.sodium.BlockRendererMixin", + "compat.sodium.ChunkMeshFormatsMixin", + "compat.sodium.ChunkShaderOptionsMixin", + "compat.sodium.ChunkVertexConsumerMixin", + "compat.sodium.ChunkVertexEncoderVertexMixin", + "compat.sodium.DefaultFluidRendererMixin", + "compat.sodium.DefaultShaderInterfaceMixin", + "compat.sodium.RenderSectionManagerAccessor", + "compat.sodium.ShaderChunkRendererMixin", + "compat.sodium.ShaderLoaderMixin", + "compat.sodium.ShaderParserMixin", + "compat.sodium.SodiumWorldRendererAccessor", + "compat.sodium.SortedRenderListsAccessor", + "compat.imm_ptl.MyGameRendererMixin" + ], + "injectors": { + "defaultRequire": 1 } } diff --git a/wiki/Home.md b/wiki/Home.md index 3e766096e..ec0c139fb 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -111,6 +111,7 @@ dependencies { - [Post-Processing](PostProcessing) - [Quasar (Particles)](Quasar) - [Render Type Stages](RenderTypeStage) +- [Screenshake](Screenshake) - [Custom Shaders](Shader) - [Shader Injections](ShaderInject) - [Better Vertex Buffers](VertexArray) diff --git a/wiki/Screenshake.md b/wiki/Screenshake.md new file mode 100644 index 000000000..6e9bad773 --- /dev/null +++ b/wiki/Screenshake.md @@ -0,0 +1,6 @@ +Veil comes with a built-in system to shake the screen. To access the manager, simply call `VeilRenderer#getScreenShakeManager`. + +Currently, there are two types of screen shake implemented: local and global. The `GlobalScreenShakeType` offsets the camera regardless of distance; the `LocalScreenShakeType` tapers its strength based on distance to the center. +Both currently implemented types allow for a Molang expression to be used, allowing for more complex effects. + +To implement your own screen shake type, simply extend the abstract class `ScreenShakeType`. \ No newline at end of file