diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index ae2ebf3..904712e 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -22,9 +22,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -34,9 +34,9 @@ jobs: - name: Build with Gradle uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 with: - arguments: shadowJar + arguments: build - name: Upload Nightly Build - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v6 if: success() with: name: skript-particle-nightly diff --git a/.gitignore b/.gitignore index 46dd638..965fe30 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ /build/ /.git/ /src/test/skriptparticle/ +CLAUDE.md +/.claude +/shapes-lib/build +/skript-particle/build diff --git a/gradle.properties b/gradle.properties index 749ca75..d59f37d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,2 @@ version = 1.4.0 +jomlVersion = 1.10.5 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 59bc51a..a595206 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 9f325b4..bd0f6f2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ -rootProject.name = 'skript-particle' +rootProject.name = 'skript-particle-root' +include 'shapes-lib', 'skript-particle' diff --git a/shapes-lib/build.gradle b/shapes-lib/build.gradle new file mode 100644 index 0000000..b5aef52 --- /dev/null +++ b/shapes-lib/build.gradle @@ -0,0 +1,17 @@ +plugins { + id 'java-library' +} + +group = 'com.sovdee' + +repositories { + mavenCentral() +} + +dependencies { + api "org.joml:joml:${jomlVersion}" +} + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/sampling/DefaultPointSampler.java b/shapes-lib/src/main/java/com/sovdee/shapes/sampling/DefaultPointSampler.java new file mode 100644 index 0000000..cd76bac --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/sampling/DefaultPointSampler.java @@ -0,0 +1,123 @@ +package com.sovdee.shapes.sampling; + +import com.sovdee.shapes.shapes.Shape; +import org.joml.Quaterniond; +import org.joml.Vector3d; + +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; + +/** + * Default implementation of {@link PointSampler} with hash-based caching. + */ +public class DefaultPointSampler implements PointSampler { + + private SamplingStyle style = SamplingStyle.OUTLINE; + private double density = 0.25; + private Comparator ordering; + private final UUID uuid; + private DrawContext drawContext; + + // Cache + private Set cachedPoints = new LinkedHashSet<>(); + private CacheState lastState; + private boolean needsUpdate = false; + + public DefaultPointSampler() { + this.uuid = UUID.randomUUID(); + this.lastState = new CacheState(style, 0, 1.0, 0, density, 0); + } + + @Override + public Set getPoints(Shape shape) { + return getPoints(shape, shape.getOrientation()); + } + + @Override + public Set getPoints(Shape shape, Quaterniond orientation) { + CacheState state = new CacheState(style, orientation.hashCode(), + shape.getScale(), shape.getOffset().hashCode(), density, shape.getVersion()); + if (shape.isDynamic() || needsUpdate || !state.equals(lastState) || cachedPoints.isEmpty()) { + Set points = (ordering != null) ? new TreeSet<>(ordering) : new LinkedHashSet<>(); + + shape.beforeSampling(density); + switch (style) { + case OUTLINE -> shape.generateOutline(points, density); + case SURFACE -> shape.generateSurface(points, density); + case FILL -> shape.generateFilled(points, density); + } + shape.afterSampling(points); + + for (Vector3d point : points) { + orientation.transform(point); + point.mul(shape.getScale()); + point.add(shape.getOffset()); + } + cachedPoints = points; + lastState = state; + needsUpdate = false; + } + return cachedPoints; + } + + public void markDirty() { + needsUpdate = true; + } + + @Override + public SamplingStyle getStyle() { return style; } + + @Override + public void setStyle(SamplingStyle style) { + this.style = style; + this.needsUpdate = true; + } + + @Override + public double getDensity() { return density; } + + @Override + public void setDensity(double density) { + this.density = Math.max(density, Shape.EPSILON); + this.needsUpdate = true; + } + + @Override + public Comparator getOrdering() { return ordering; } + + @Override + public void setOrdering(Comparator ordering) { + this.ordering = ordering; + this.needsUpdate = true; + } + + @Override + public UUID getUUID() { return uuid; } + + @Override + public DrawContext getDrawContext() { return drawContext; } + + @Override + public void setDrawContext(DrawContext context) { this.drawContext = context; } + + @Override + public DefaultPointSampler clone() { + try { + DefaultPointSampler copy = (DefaultPointSampler) super.clone(); + // Don't share the cache + copy.cachedPoints = new LinkedHashSet<>(); + copy.needsUpdate = true; + if (drawContext != null) + copy.drawContext = drawContext.copy(); + return copy; + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + + private record CacheState(SamplingStyle style, int orientationHash, double scale, + int offsetHash, double density, long shapeVersion) {} +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/sampling/DrawContext.java b/shapes-lib/src/main/java/com/sovdee/shapes/sampling/DrawContext.java new file mode 100644 index 0000000..19427f0 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/sampling/DrawContext.java @@ -0,0 +1,8 @@ +package com.sovdee.shapes.sampling; + +/** + * Client-provided rendering metadata. Implementations are opaque to the library. + */ +public interface DrawContext { + DrawContext copy(); +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/sampling/PointSampler.java b/shapes-lib/src/main/java/com/sovdee/shapes/sampling/PointSampler.java new file mode 100644 index 0000000..1d7221c --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/sampling/PointSampler.java @@ -0,0 +1,49 @@ +package com.sovdee.shapes.sampling; + +import com.sovdee.shapes.shapes.Shape; +import org.joml.Quaterniond; +import org.joml.Vector3d; + +import java.util.Comparator; +import java.util.Set; +import java.util.UUID; + +/** + * Responsible for sampling points from a {@link Shape}'s geometry. + * Manages sampling configuration (style, density, ordering) and caching. + */ +public interface PointSampler extends Cloneable { + + SamplingStyle getStyle(); + void setStyle(SamplingStyle style); + + double getDensity(); + void setDensity(double density); + + Comparator getOrdering(); + void setOrdering(Comparator ordering); + + UUID getUUID(); + + DrawContext getDrawContext(); + void setDrawContext(DrawContext context); + + /** + * Samples points from the given shape using the shape's own orientation. + */ + Set getPoints(Shape shape); + + /** + * Samples points from the given shape using the given orientation. + */ + Set getPoints(Shape shape, Quaterniond orientation); + + /** + * Computes and sets the density to achieve approximately the given particle count. + */ + default void setParticleCount(Shape shape, int count) { + setDensity(shape.computeDensity(getStyle(), count)); + } + + PointSampler clone(); +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/sampling/SamplingStyle.java b/shapes-lib/src/main/java/com/sovdee/shapes/sampling/SamplingStyle.java new file mode 100644 index 0000000..d65ef9b --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/sampling/SamplingStyle.java @@ -0,0 +1,15 @@ +package com.sovdee.shapes.sampling; + +/** + * Determines how a shape's geometry is sampled into points. + */ +public enum SamplingStyle { + OUTLINE, + SURFACE, + FILL; + + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/AbstractShape.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/AbstractShape.java new file mode 100644 index 0000000..1ba1478 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/AbstractShape.java @@ -0,0 +1,166 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.DefaultPointSampler; +import com.sovdee.shapes.sampling.PointSampler; +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Quaterniond; +import org.joml.Vector3d; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Base implementation of {@link Shape} providing spatial transform, versioning, + * and a default {@link PointSampler}. + */ +public abstract class AbstractShape implements Shape { + + private final Quaterniond orientation; + private double scale; + private Vector3d offset; + private boolean dynamic = false; + private long version = 0; + private PointSampler pointSampler; + + public AbstractShape() { + this.orientation = new Quaterniond(); + this.scale = 1; + this.offset = new Vector3d(0, 0, 0); + this.pointSampler = new DefaultPointSampler(); + } + + // --- Spatial transform --- + + @Override + public Quaterniond getOrientation() { + return new Quaterniond(orientation); + } + + @Override + public void setOrientation(Quaterniond orientation) { + this.orientation.set(orientation); + } + + @Override + public double getScale() { return scale; } + + @Override + public void setScale(double scale) { + this.scale = scale; + } + + @Override + public Vector3d getOffset() { + return new Vector3d(offset); + } + + @Override + public void setOffset(Vector3d offset) { + this.offset = offset; + } + + // --- Oriented axes --- + + @Override + public Vector3d getRelativeXAxis() { + return orientation.transform(new Vector3d(1, 0, 0)); + } + + @Override + public Vector3d getRelativeYAxis() { + return orientation.transform(new Vector3d(0, 1, 0)); + } + + @Override + public Vector3d getRelativeZAxis() { + return orientation.transform(new Vector3d(0, 0, 1)); + } + + // --- Change detection --- + + @Override + public long getVersion() { return version; } + + /** + * Increments the version counter, signaling that geometry has changed. + * Call from dimension setters. + */ + protected void invalidate() { + version++; + } + + // --- Dynamic support --- + + @Override + public boolean isDynamic() { return dynamic; } + + @Override + public void setDynamic(boolean dynamic) { this.dynamic = dynamic; } + + // --- Point generation defaults --- + + @Override + public void generateSurface(Set points, double density) { + generateOutline(points, density); + } + + @Override + public void generateFilled(Set points, double density) { + generateSurface(points, density); + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + // Generic fallback — subclasses should override for accuracy + return 0.25; + } + + // --- Geometry query --- + + @Override + public abstract boolean contains(Vector3d point); + + // --- PointSampler --- + + @Override + public PointSampler getPointSampler() { return pointSampler; } + + @Override + public void setPointSampler(PointSampler sampler) { this.pointSampler = sampler; } + + // --- Vertical fill helper --- + + protected static void fillVertically(Set points, double height, double density) { + Set base = new LinkedHashSet<>(points); + double heightStep = height / Math.round(height / density); + for (double y = 0; y < height; y += heightStep) { + for (Vector3d v : base) { + points.add(new Vector3d(v.x, y, v.z)); + } + } + } + + // --- Replication --- + + @Override + public abstract Shape clone(); + + @Override + public Shape copyTo(Shape shape) { + shape.setOrientation(new Quaterniond(this.orientation)); + shape.setScale(this.scale); + shape.setOffset(new Vector3d(this.offset)); + shape.setDynamic(this.dynamic); + + // Clone sampler config + PointSampler srcSampler = this.pointSampler; + PointSampler destSampler = shape.getPointSampler(); + destSampler.setStyle(srcSampler.getStyle()); + destSampler.setDensity(srcSampler.getDensity()); + destSampler.setOrdering(srcSampler.getOrdering()); + if (srcSampler.getDrawContext() != null) + destSampler.setDrawContext(srcSampler.getDrawContext().copy()); + + return shape; + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Arc.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Arc.java new file mode 100644 index 0000000..a4a8d7e --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Arc.java @@ -0,0 +1,52 @@ +package com.sovdee.shapes.shapes; + +import org.joml.Vector3d; + +import java.util.Set; + +public class Arc extends Circle implements CutoffShape { + + public Arc(double radius, double cutoffAngle) { + super(radius); + this.cutoffAngle = Math.clamp(cutoffAngle, 0, Math.PI * 2); + } + + public Arc(double radius, double height, double cutoffAngle) { + super(radius, height); + this.cutoffAngle = Math.clamp(cutoffAngle, 0, Math.PI * 2); + } + + @Override + public void generateSurface(Set points, double density) { + generateFilled(points, density); + } + + @Override + public double getCutoffAngle() { + return this.cutoffAngle; + } + + @Override + public void setCutoffAngle(double cutoffAngle) { + this.cutoffAngle = Math.clamp(cutoffAngle, 0, Math.PI * 2); + invalidate(); + } + + @Override + public boolean contains(Vector3d point) { + if (!super.contains(point)) return false; + double angle = Math.atan2(point.z, point.x); + if (angle < 0) angle += 2 * Math.PI; + return angle <= cutoffAngle; + } + + @Override + public Shape clone() { + return this.copyTo(new Arc(this.getRadius(), this.getHeight(), cutoffAngle)); + } + + @Override + public String toString() { + return "Arc{radius=" + this.getRadius() + ", cutoffAngle=" + cutoffAngle + ", height=" + this.getHeight() + '}'; + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/BezierCurve.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/BezierCurve.java new file mode 100644 index 0000000..734f01a --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/BezierCurve.java @@ -0,0 +1,138 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Vector3d; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +/** + * A bezier curve defined by control points as Vector3d. + * For dynamic (entity-following) bezier curves, use the plugin-side wrapper. + */ +public class BezierCurve extends AbstractShape { + + private List controlPoints; + private Supplier> controlPointsSupplier; + + public BezierCurve(List controlPoints) { + super(); + if (controlPoints.size() < 2) + throw new IllegalArgumentException("A bezier curve must have at least 2 control points."); + this.controlPoints = new ArrayList<>(); + for (Vector3d cp : controlPoints) + this.controlPoints.add(new Vector3d(cp)); + } + + public BezierCurve(Supplier> controlPointsSupplier) { + super(); + this.controlPointsSupplier = controlPointsSupplier; + List pts = controlPointsSupplier.get(); + if (pts.size() < 2) + throw new IllegalArgumentException("A bezier curve must have at least 2 control points."); + this.controlPoints = new ArrayList<>(); + for (Vector3d cp : pts) + this.controlPoints.add(new Vector3d(cp)); + setDynamic(true); + } + + public BezierCurve(BezierCurve curve) { + super(); + this.controlPoints = new ArrayList<>(); + for (Vector3d cp : curve.controlPoints) + this.controlPoints.add(new Vector3d(cp)); + } + + @Override + public void generateOutline(Set points, double density) { + if (controlPointsSupplier != null) { + List pts = controlPointsSupplier.get(); + this.controlPoints = new ArrayList<>(); + for (Vector3d cp : pts) + this.controlPoints.add(new Vector3d(cp)); + } + int steps = (int) (estimateLength() / density); + int n = controlPoints.size(); + + Vector3d[] temp = new Vector3d[n]; + for (int i = 0; i < n; i++) + temp[i] = new Vector3d(); + + for (int step = 0; step < steps; step++) { + double t = (double) step / steps; + double nt = 1 - t; + for (int i = 0; i < n; i++) + temp[i].set(controlPoints.get(i)); + for (int level = n - 1; level > 0; level--) { + for (int i = 0; i < level; i++) { + temp[i].mul(nt).add(new Vector3d(temp[i + 1]).mul(t)); + } + } + points.add(new Vector3d(temp[0])); + } + } + + private double estimateLength() { + double dist = 0; + for (int i = 0; i < controlPoints.size() - 1; i++) { + dist += controlPoints.get(i).distance(controlPoints.get(i + 1)); + } + return dist; + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(targetPointCount, 1); + return estimateLength() / count; + } + + @Override + public boolean contains(Vector3d point) { + // Approximate: check distance to nearest sampled point + int samples = Math.max((int) (estimateLength() / 0.1), 10); + int n = controlPoints.size(); + Vector3d[] temp = new Vector3d[n]; + for (int i = 0; i < n; i++) temp[i] = new Vector3d(); + + for (int step = 0; step <= samples; step++) { + double t = (double) step / samples; + double nt = 1 - t; + for (int i = 0; i < n; i++) temp[i].set(controlPoints.get(i)); + for (int level = n - 1; level > 0; level--) { + for (int i = 0; i < level; i++) { + temp[i].mul(nt).add(new Vector3d(temp[i + 1]).mul(t)); + } + } + if (point.distance(temp[0]) <= EPSILON) return true; + } + return false; + } + + public List getControlPoints() { + return controlPoints; + } + + public void setControlPoints(List controlPoints) { + this.controlPoints = new ArrayList<>(); + for (Vector3d cp : controlPoints) + this.controlPoints.add(new Vector3d(cp)); + invalidate(); + } + + public Supplier> getControlPointsSupplier() { + return controlPointsSupplier; + } + + @Override + public Shape clone() { + BezierCurve clone; + if (controlPointsSupplier != null) { + clone = new BezierCurve(controlPointsSupplier); + } else { + clone = new BezierCurve(this); + } + return this.copyTo(clone); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Circle.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Circle.java new file mode 100644 index 0000000..5e7b510 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Circle.java @@ -0,0 +1,159 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Vector3d; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class Circle extends AbstractShape implements RadialShape, LWHShape { + + private double radius; + protected double cutoffAngle; + private double height; + + public Circle(double radius) { + this(radius, 0); + } + + public Circle(double radius, double height) { + super(); + this.radius = Math.max(radius, Shape.EPSILON); + this.height = Math.max(height, 0); + this.cutoffAngle = 2 * Math.PI; + } + + // --- Static calculation methods --- + + public static Set calculateCircle(double radius, double density, double cutoffAngle) { + Set points = new LinkedHashSet<>(); + double stepSize = density / radius; + for (double theta = 0; theta < cutoffAngle; theta += stepSize) { + points.add(new Vector3d(Math.cos(theta) * radius, 0, Math.sin(theta) * radius)); + } + return points; + } + + public static Set calculateDisc(double radius, double density, double cutoffAngle) { + Set points = new LinkedHashSet<>(); + for (double subRadius = density; subRadius < radius; subRadius += density) { + points.addAll(calculateCircle(subRadius, density, cutoffAngle)); + } + points.addAll(calculateCircle(radius, density, cutoffAngle)); + return points; + } + + public static Set calculateCylinder(double radius, double height, double density, double cutoffAngle) { + Set points = calculateDisc(radius, density, cutoffAngle); + // Top disc via direct loop + Set top = new LinkedHashSet<>(); + for (Vector3d v : points) { + top.add(new Vector3d(v.x, height, v.z)); + } + points.addAll(top); + // Wall + Set wall = calculateCircle(radius, density, cutoffAngle); + fillVertically(wall, height, density); + points.addAll(wall); + return points; + } + + // --- Generation methods --- + + @Override + public void generateOutline(Set points, double density) { + Set circle = calculateCircle(radius, density, cutoffAngle); + if (height != 0) { + fillVertically(circle, height, density); + points.addAll(circle); + } else { + points.addAll(circle); + } + } + + @Override + public void generateSurface(Set points, double density) { + if (height != 0) + points.addAll(calculateCylinder(radius, height, density, cutoffAngle)); + else + points.addAll(calculateDisc(radius, density, cutoffAngle)); + } + + @Override + public void generateFilled(Set points, double density) { + Set disc = calculateDisc(radius, density, cutoffAngle); + if (height != 0) { + fillVertically(disc, height, density); + points.addAll(disc); + } else { + points.addAll(disc); + } + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(targetPointCount, 1); + return switch (style) { + case OUTLINE -> { + if (height == 0) yield cutoffAngle * radius / count; + double circumference = cutoffAngle * radius; + double wallArea = circumference * height; + yield Math.sqrt((circumference + wallArea) / count); + } + case SURFACE -> { + double discArea = cutoffAngle * 0.5 * radius * radius; + double wallArea = cutoffAngle * radius * height; + yield Math.sqrt((discArea + wallArea) / count); + } + case FILL -> Math.cbrt(cutoffAngle * 0.5 * radius * radius * height / count); + }; + } + + @Override + public boolean contains(Vector3d point) { + double distSq = point.x * point.x + point.z * point.z; + if (distSq > radius * radius) return false; + if (height > 0) return point.y >= 0 && point.y <= height; + return Math.abs(point.y) < EPSILON; + } + + @Override + public double getRadius() { return radius; } + + @Override + public void setRadius(double radius) { + this.radius = Math.max(radius, Shape.EPSILON); + invalidate(); + } + + @Override + public double getLength() { return 0; } + + @Override + public void setLength(double length) { } + + @Override + public double getWidth() { return 0; } + + @Override + public void setWidth(double width) { } + + @Override + public double getHeight() { return height; } + + @Override + public void setHeight(double height) { + this.height = Math.max(height, 0); + invalidate(); + } + + @Override + public Shape clone() { + return this.copyTo(new Circle(radius, height)); + } + + @Override + public String toString() { + return "Circle{radius=" + radius + ", cutoffAngle=" + cutoffAngle + ", height=" + height + '}'; + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Cuboid.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Cuboid.java new file mode 100644 index 0000000..57b1e0c --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Cuboid.java @@ -0,0 +1,186 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Vector3d; + +import java.util.Set; +import java.util.function.Supplier; + +/** + * A cuboid shape, defined either by dimensions or by two corner vectors. + * For dynamic (entity-following) cuboids, use the plugin-side DynamicCuboid wrapper. + */ +public class Cuboid extends AbstractShape implements LWHShape { + + private double halfLength, halfWidth, halfHeight; + private double lengthStep, widthStep, heightStep; + private Vector3d centerOffset = new Vector3d(0, 0, 0); + private Supplier cornerASupplier; + private Supplier cornerBSupplier; + + public Cuboid(double length, double width, double height) { + super(); + this.halfWidth = Math.max(width / 2, Shape.EPSILON); + this.halfLength = Math.max(length / 2, Shape.EPSILON); + this.halfHeight = Math.max(height / 2, Shape.EPSILON); + } + + public Cuboid(Vector3d cornerA, Vector3d cornerB) { + super(); + if (cornerA.equals(cornerB)) + throw new IllegalArgumentException("Cuboid corners cannot be equal."); + this.halfLength = Math.abs(cornerB.x - cornerA.x) / 2; + this.halfWidth = Math.abs(cornerB.z - cornerA.z) / 2; + this.halfHeight = Math.abs(cornerB.y - cornerA.y) / 2; + centerOffset = new Vector3d(cornerB).add(cornerA).mul(0.5); + } + + public Cuboid(Supplier cornerA, Supplier cornerB) { + super(); + this.cornerASupplier = cornerA; + this.cornerBSupplier = cornerB; + Vector3d a = cornerA.get(); + Vector3d b = cornerB.get(); + this.halfLength = Math.max(Math.abs(b.x - a.x) / 2, Shape.EPSILON); + this.halfWidth = Math.max(Math.abs(b.z - a.z) / 2, Shape.EPSILON); + this.halfHeight = Math.max(Math.abs(b.y - a.y) / 2, Shape.EPSILON); + setDynamic(true); + } + + private void calculateSteps(double density) { + widthStep = 2 * halfWidth / Math.round(2 * halfWidth / density); + lengthStep = 2 * halfLength / Math.round(2 * halfLength / density); + heightStep = 2 * halfHeight / Math.round(2 * halfHeight / density); + } + + @Override + public void beforeSampling(double density) { + if (cornerASupplier != null && cornerBSupplier != null) { + Vector3d a = cornerASupplier.get(); + Vector3d b = cornerBSupplier.get(); + this.halfLength = Math.max(Math.abs(b.x - a.x) / 2, Shape.EPSILON); + this.halfWidth = Math.max(Math.abs(b.z - a.z) / 2, Shape.EPSILON); + this.halfHeight = Math.max(Math.abs(b.y - a.y) / 2, Shape.EPSILON); + } + calculateSteps(density); + } + + @Override + public void afterSampling(Set points) { + points.forEach(vector -> vector.add(centerOffset)); + } + + @Override + public void generateOutline(Set points, double density) { + for (double x = -halfLength; x <= halfLength; x += lengthStep) { + points.add(new Vector3d(x, -halfHeight, -halfWidth)); + points.add(new Vector3d(x, -halfHeight, halfWidth)); + points.add(new Vector3d(x, halfHeight, -halfWidth)); + points.add(new Vector3d(x, halfHeight, halfWidth)); + } + for (double y = -halfHeight + heightStep; y < halfHeight; y += heightStep) { + points.add(new Vector3d(-halfLength, y, -halfWidth)); + points.add(new Vector3d(-halfLength, y, halfWidth)); + points.add(new Vector3d(halfLength, y, -halfWidth)); + points.add(new Vector3d(halfLength, y, halfWidth)); + } + for (double z = -halfWidth + widthStep; z < halfWidth; z += widthStep) { + points.add(new Vector3d(-halfLength, -halfHeight, z)); + points.add(new Vector3d(-halfLength, halfHeight, z)); + points.add(new Vector3d(halfLength, -halfHeight, z)); + points.add(new Vector3d(halfLength, halfHeight, z)); + } + } + + @Override + public void generateSurface(Set points, double density) { + for (double x = -halfLength; x <= halfLength; x += lengthStep) { + for (double z = -halfWidth; z <= halfWidth; z += widthStep) { + points.add(new Vector3d(x, -halfHeight, z)); + points.add(new Vector3d(x, halfHeight, z)); + } + } + for (double y = -halfHeight + heightStep; y < halfHeight; y += heightStep) { + for (double z = -halfWidth; z <= halfWidth; z += widthStep) { + points.add(new Vector3d(-halfLength, y, z)); + points.add(new Vector3d(halfLength, y, z)); + } + } + for (double x = -halfLength + lengthStep; x < halfLength; x += lengthStep) { + for (double y = -halfHeight + heightStep; y < halfHeight; y += heightStep) { + points.add(new Vector3d(x, y, -halfWidth)); + points.add(new Vector3d(x, y, halfWidth)); + } + } + } + + @Override + public void generateFilled(Set points, double density) { + for (double x = -halfLength; x <= halfLength; x += lengthStep) { + for (double y = -halfHeight; y <= halfHeight; y += heightStep) { + for (double z = -halfWidth; z <= halfWidth; z += widthStep) { + points.add(new Vector3d(x, y, z)); + } + } + } + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(1, targetPointCount); + return switch (style) { + case OUTLINE -> 8 * (halfLength + halfHeight + halfWidth) / count; + case SURFACE -> Math.sqrt(8 * (halfLength * halfHeight + halfLength * halfWidth + halfHeight * halfWidth) / count); + case FILL -> Math.cbrt(8 * halfLength * halfHeight * halfWidth / count); + }; + } + + @Override + public boolean contains(Vector3d point) { + return Math.abs(point.x) <= halfLength && + Math.abs(point.y) <= halfHeight && + Math.abs(point.z) <= halfWidth; + } + + @Override + public double getLength() { return halfLength * 2; } + + @Override + public void setLength(double length) { + this.halfLength = Math.max(length / 2, Shape.EPSILON); + invalidate(); + } + + @Override + public double getWidth() { return halfWidth * 2; } + + @Override + public void setWidth(double width) { + this.halfWidth = Math.max(width / 2, Shape.EPSILON); + invalidate(); + } + + @Override + public double getHeight() { return halfHeight * 2; } + + @Override + public void setHeight(double height) { + this.halfHeight = Math.max(height / 2, Shape.EPSILON); + invalidate(); + } + + public Supplier getCornerASupplier() { return cornerASupplier; } + public Supplier getCornerBSupplier() { return cornerBSupplier; } + + @Override + public Shape clone() { + Cuboid cuboid; + if (cornerASupplier != null && cornerBSupplier != null) { + cuboid = new Cuboid(cornerASupplier, cornerBSupplier); + } else { + cuboid = new Cuboid(getLength(), getWidth(), getHeight()); + } + cuboid.centerOffset = new Vector3d(this.centerOffset); + return this.copyTo(cuboid); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/CutoffShape.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/CutoffShape.java new file mode 100644 index 0000000..19d3760 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/CutoffShape.java @@ -0,0 +1,9 @@ +package com.sovdee.shapes.shapes; + +/** + * Represents a shape that has a cutoff angle, like an arc. + */ +public interface CutoffShape extends Shape { + double getCutoffAngle(); + void setCutoffAngle(double cutoffAngle); +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Ellipse.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Ellipse.java new file mode 100644 index 0000000..eb99dad --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Ellipse.java @@ -0,0 +1,170 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Vector3d; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class Ellipse extends AbstractShape implements LWHShape { + + private double xRadius; + private double zRadius; + private double height; + protected double cutoffAngle; + + public Ellipse(double xRadius, double zRadius) { + this(xRadius, zRadius, 0); + } + + public Ellipse(double xRadius, double zRadius, double height) { + super(); + this.xRadius = Math.max(xRadius, Shape.EPSILON); + this.zRadius = Math.max(zRadius, Shape.EPSILON); + this.height = Math.max(height, 0); + this.cutoffAngle = 2 * Math.PI; + } + + // --- Static calculation methods --- + + private static double ellipseCircumference(double r1, double r2) { + double a = Math.max(r1, r2); + double b = Math.min(r1, r2); + double h = Math.pow(a - b, 2) / Math.pow(a + b, 2); + return Math.PI * (a + b) * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h))); + } + + public static List calculateEllipse(double r1, double r2, double density, double cutoffAngle) { + List points = new ArrayList<>(); + double circumference = ellipseCircumference(r1, r2); + + int steps = (int) Math.round(circumference / density); + double theta = 0; + double angleStep = 0; + for (int i = 0; i < steps; i++) { + if (theta > cutoffAngle) { + break; + } + points.add(new Vector3d(r1 * Math.cos(theta), 0, r2 * Math.sin(theta))); + double dx = r1 * Math.sin(theta + 0.5 * angleStep); + double dy = r2 * Math.cos(theta + 0.5 * angleStep); + angleStep = density / Math.sqrt(dx * dx + dy * dy); + theta += angleStep; + } + return points; + } + + public static Set calculateEllipticalDisc(double r1, double r2, double density, double cutoffAngle) { + Set points = new LinkedHashSet<>(); + int steps = (int) Math.round(Math.max(r1, r2) / density); + double r; + for (double i = 1; i <= steps; i += 1) { + r = i / steps; + points.addAll(calculateEllipse(r1 * r, r2 * r, density, cutoffAngle)); + } + return points; + } + + public static Set calculateCylinder(double r1, double r2, double height, double density, double cutoffAngle) { + Set points = calculateEllipticalDisc(r1, r2, density, cutoffAngle); + // Top disc via direct loop + Set top = new LinkedHashSet<>(); + for (Vector3d v : points) { + top.add(new Vector3d(v.x, height, v.z)); + } + points.addAll(top); + // Wall + Set wall = new LinkedHashSet<>(calculateEllipse(r1, r2, density, cutoffAngle)); + fillVertically(wall, height, density); + points.addAll(wall); + return points; + } + + // --- Generation methods --- + + @Override + public void generateOutline(Set points, double density) { + Set ellipse = new LinkedHashSet<>(calculateEllipse(xRadius, zRadius, density, cutoffAngle)); + if (height != 0) { + fillVertically(ellipse, height, density); + points.addAll(ellipse); + } else { + points.addAll(ellipse); + } + } + + @Override + public void generateSurface(Set points, double density) { + if (height != 0) + points.addAll(calculateCylinder(xRadius, zRadius, height, density, cutoffAngle)); + else + points.addAll(calculateEllipticalDisc(xRadius, zRadius, density, cutoffAngle)); + } + + @Override + public void generateFilled(Set points, double density) { + Set disc = calculateEllipticalDisc(xRadius, zRadius, density, cutoffAngle); + if (height != 0) { + fillVertically(disc, height, density); + points.addAll(disc); + } else { + points.addAll(disc); + } + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(targetPointCount, 1); + return switch (style) { + case OUTLINE -> { + double h = (xRadius - zRadius) * (xRadius - zRadius) / ((xRadius + zRadius) + (xRadius + zRadius)); + double circumference = Math.PI * (xRadius + zRadius) * (1 + (3 * h / (10 + Math.sqrt(4 - 3 * h)))); + yield circumference / count; + } + case SURFACE, FILL -> Math.sqrt((Math.PI * xRadius * zRadius) / count); + }; + } + + @Override + public boolean contains(Vector3d point) { + double nx = point.x / xRadius; + double nz = point.z / zRadius; + if (nx * nx + nz * nz > 1) return false; + if (height > 0) return point.y >= 0 && point.y <= height; + return Math.abs(point.y) < EPSILON; + } + + @Override + public double getLength() { return xRadius * 2; } + + @Override + public void setLength(double length) { + xRadius = Math.max(length / 2, Shape.EPSILON); + invalidate(); + } + + @Override + public double getWidth() { return zRadius * 2; } + + @Override + public void setWidth(double width) { + zRadius = Math.max(width / 2, Shape.EPSILON); + invalidate(); + } + + @Override + public double getHeight() { return height; } + + @Override + public void setHeight(double height) { + this.height = Math.max(height, 0); + invalidate(); + } + + @Override + public Shape clone() { + return this.copyTo(new Ellipse(xRadius, zRadius, height)); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Ellipsoid.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Ellipsoid.java new file mode 100644 index 0000000..cab4100 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Ellipsoid.java @@ -0,0 +1,138 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.shapes.util.VectorUtil; +import org.joml.Quaterniond; +import org.joml.Vector3d; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class Ellipsoid extends AbstractShape implements LWHShape { + + private static final Quaterniond XY_ROTATION = new Quaterniond().rotateX(Math.PI / 2); + private static final Quaterniond ZY_ROTATION = new Quaterniond().rotateZ(Math.PI / 2); + protected double xRadius; + protected double yRadius; + protected double zRadius; + + public Ellipsoid(double xRadius, double yRadius, double zRadius) { + super(); + this.xRadius = Math.max(xRadius, Shape.EPSILON); + this.yRadius = Math.max(yRadius, Shape.EPSILON); + this.zRadius = Math.max(zRadius, Shape.EPSILON); + } + + @Override + public void generateOutline(Set points, double density) { + points.addAll(Ellipse.calculateEllipse(xRadius, zRadius, density, 2 * Math.PI)); + points.addAll(VectorUtil.transform(XY_ROTATION, Ellipse.calculateEllipse(xRadius, yRadius, density, 2 * Math.PI))); + points.addAll(VectorUtil.transform(ZY_ROTATION, Ellipse.calculateEllipse(yRadius, zRadius, density, 2 * Math.PI))); + } + + @Override + public void generateSurface(Set points, double density) { + List ellipse; + if (xRadius > zRadius) { + ellipse = VectorUtil.transform(XY_ROTATION, Ellipse.calculateEllipse(xRadius, yRadius, density, 2 * Math.PI)); + } else { + ellipse = VectorUtil.transform(ZY_ROTATION, Ellipse.calculateEllipse(yRadius, zRadius, density, 2 * Math.PI)); + } + points.addAll(generateEllipsoid(ellipse, 1, density)); + } + + @Override + public void generateFilled(Set points, double density) { + List ellipse; + double radius = Math.max(xRadius, zRadius); + int steps = (int) Math.round(radius / density); + for (int i = steps; i > 0; i--) { + double r = (i / (double) steps); + if (xRadius > zRadius) { + ellipse = VectorUtil.transform(XY_ROTATION, Ellipse.calculateEllipse(xRadius * r, yRadius * r, density, 2 * Math.PI)); + } else { + ellipse = VectorUtil.transform(ZY_ROTATION, Ellipse.calculateEllipse(yRadius * r, zRadius * r, density, 2 * Math.PI)); + } + points.addAll(generateEllipsoid(ellipse, r, density)); + } + } + + private Set generateEllipsoid(List ellipse, double radius, double density) { + Set points = new LinkedHashSet<>(); + for (int i = 0; i < Math.ceil(ellipse.size() / 4.0); i++) { + double y = ellipse.get(i).y; + double theta = Math.asin(y / (yRadius * radius)); + for (Vector3d v2 : Ellipse.calculateEllipse(radius * xRadius * Math.cos(theta), radius * zRadius * Math.cos(theta), density, 2 * Math.PI)) { + points.add(new Vector3d(v2.x, y, v2.z)); + points.add(new Vector3d(v2.x, -y, v2.z)); + } + } + points.addAll(Ellipse.calculateEllipse(radius * xRadius, radius * zRadius, density, 2 * Math.PI)); + return points; + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(targetPointCount, 1); + return switch (style) { + case OUTLINE -> { + double h = (xRadius - yRadius) * (xRadius - yRadius) / ((xRadius + yRadius) + (xRadius + yRadius)); + double circumferenceXY = Math.PI * (xRadius + yRadius) * (1 + (3 * h / (10 + Math.sqrt(4 - 3 * h)))); + h = (xRadius - zRadius) * (xRadius - zRadius) / ((xRadius + zRadius) + (xRadius + zRadius)); + double circumferenceXZ = Math.PI * (xRadius + zRadius) * (1 + (3 * h / (10 + Math.sqrt(4 - 3 * h)))); + h = (yRadius - zRadius) * (yRadius - zRadius) / ((yRadius + zRadius) + (yRadius + zRadius)); + double circumferenceYZ = Math.PI * (yRadius + zRadius) * (1 + (3 * h / (10 + Math.sqrt(4 - 3 * h)))); + yield (circumferenceXY + circumferenceXZ + circumferenceYZ) / count; + } + case SURFACE -> { + double surfaceArea = 4 * Math.PI * Math.pow((Math.pow(xRadius * yRadius, 1.6) + Math.pow(xRadius * zRadius, 1.6) + Math.pow(zRadius * yRadius, 1.6)) / 3, 1 / 1.6); + yield Math.sqrt(surfaceArea / count); + } + case FILL -> { + double volume = 4 / 3.0 * Math.PI * xRadius * yRadius * zRadius; + yield Math.cbrt(volume / count); + } + }; + } + + @Override + public boolean contains(Vector3d point) { + double nx = point.x / xRadius; + double ny = point.y / yRadius; + double nz = point.z / zRadius; + return nx * nx + ny * ny + nz * nz <= 1; + } + + @Override + public double getLength() { return xRadius * 2; } + + @Override + public void setLength(double length) { + xRadius = Math.max(length / 2, Shape.EPSILON); + invalidate(); + } + + @Override + public double getWidth() { return zRadius * 2; } + + @Override + public void setWidth(double width) { + zRadius = Math.max(width / 2, Shape.EPSILON); + invalidate(); + } + + @Override + public double getHeight() { return yRadius * 2; } + + @Override + public void setHeight(double height) { + yRadius = Math.max(height / 2, Shape.EPSILON); + invalidate(); + } + + @Override + public Shape clone() { + return this.copyTo(new Ellipsoid(xRadius, yRadius, zRadius)); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/EllipticalArc.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/EllipticalArc.java new file mode 100644 index 0000000..faa7541 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/EllipticalArc.java @@ -0,0 +1,46 @@ +package com.sovdee.shapes.shapes; + +import org.joml.Vector3d; + +import java.util.Set; + +public class EllipticalArc extends Ellipse implements CutoffShape { + + public EllipticalArc(double xRadius, double zRadius, double cutoffAngle) { + this(xRadius, zRadius, 0, cutoffAngle); + } + + public EllipticalArc(double xRadius, double zRadius, double height, double cutoffAngle) { + super(xRadius, zRadius, height); + this.cutoffAngle = Math.clamp(cutoffAngle, 0, Math.PI * 2); + } + + @Override + public void generateSurface(Set points, double density) { + generateFilled(points, density); + } + + @Override + public double getCutoffAngle() { + return cutoffAngle; + } + + @Override + public void setCutoffAngle(double cutoffAngle) { + this.cutoffAngle = Math.clamp(cutoffAngle, 0, Math.PI * 2); + invalidate(); + } + + @Override + public boolean contains(Vector3d point) { + if (!super.contains(point)) return false; + double angle = Math.atan2(point.z, point.x); + if (angle < 0) angle += 2 * Math.PI; + return angle <= cutoffAngle; + } + + @Override + public Shape clone() { + return this.copyTo(new EllipticalArc(this.getLength() / 2, this.getWidth() / 2, this.getHeight(), cutoffAngle)); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Heart.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Heart.java new file mode 100644 index 0000000..a82ff16 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Heart.java @@ -0,0 +1,101 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Vector3d; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class Heart extends AbstractShape implements LWHShape { + + private double length; + private double width; + private double eccentricity; + + public Heart(double length, double width, double eccentricity) { + super(); + this.length = Math.max(length, Shape.EPSILON); + this.width = Math.max(width, Shape.EPSILON); + this.eccentricity = Math.max(eccentricity, 1); + } + + private static Set calculateHeart(double length, double width, double eccentricity, double density) { + Set points = new LinkedHashSet<>(); + double angleStep = 4 / 3.0 * density / (width + length); + for (double theta = 0; theta < Math.PI * 2; theta += angleStep) { + double x = width * Math.pow(Math.sin(theta), 3); + double y = length * (Math.cos(theta) - 1 / eccentricity * Math.cos(2 * theta) - 1.0 / 6 * Math.cos(3 * theta) - 1.0 / 16 * Math.cos(4 * theta)); + points.add(new Vector3d(x, 0, y)); + } + return points; + } + + @Override + public void generateOutline(Set points, double density) { + points.addAll(calculateHeart(length / 2, width / 2, eccentricity, density)); + } + + @Override + public void generateSurface(Set points, double density) { + for (double w = width, l = length; w > 0 && l > 0; w -= density * 1.5, l -= density * 1.5) { + points.addAll(calculateHeart(l / 2, w / 2, eccentricity, density)); + } + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + return 0.25; // No good formula available + } + + @Override + public boolean contains(Vector3d point) { + // Approximate: check if point is within parametric heart boundary + if (Math.abs(point.y) > EPSILON) return false; + double x = point.x; + double z = point.z; + double hw = width / 2; + double hl = length / 2; + if (hw < EPSILON || hl < EPSILON) return false; + // Normalize and check parametric distance + double nx = x / hw; + double nz = z / hl; + // Simple bounding check + return nx * nx + nz * nz <= 4; + } + + @Override + public double getHeight() { return 0; } + + @Override + public void setHeight(double height) { } + + @Override + public double getWidth() { return width; } + + @Override + public void setWidth(double width) { + this.width = Math.max(width, Shape.EPSILON); + invalidate(); + } + + @Override + public double getLength() { return length; } + + @Override + public void setLength(double length) { + this.length = Math.max(length, Shape.EPSILON); + invalidate(); + } + + public double getEccentricity() { return eccentricity; } + + public void setEccentricity(double eccentricity) { + this.eccentricity = Math.max(1, eccentricity); + invalidate(); + } + + @Override + public Shape clone() { + return this.copyTo(new Heart(length, width, eccentricity)); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Helix.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Helix.java new file mode 100644 index 0000000..4062b1e --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Helix.java @@ -0,0 +1,134 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Vector3d; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class Helix extends AbstractShape implements RadialShape, LWHShape { + + private double radius; + private double height; + private double slope; + private int direction = 1; + + public Helix(double radius, double height, double slope) { + super(); + this.radius = Math.max(radius, Shape.EPSILON); + this.height = Math.max(height, Shape.EPSILON); + this.slope = Math.max(slope, Shape.EPSILON); + } + + public Helix(double radius, double height, double slope, int direction) { + super(); + this.radius = Math.max(radius, Shape.EPSILON); + this.height = Math.max(height, Shape.EPSILON); + this.slope = Math.max(slope, Shape.EPSILON); + if (direction != 1 && direction != -1) + throw new IllegalArgumentException("Direction must be 1 or -1"); + this.direction = direction; + } + + private static Set calculateHelix(double radius, double height, double slope, int direction, double density) { + Set points = new LinkedHashSet<>(); + if (radius <= 0 || height <= 0) { + return points; + } + double loops = Math.abs(height / slope); + double length = slope * slope + radius * radius; + double stepSize = density / length; + for (double t = 0; t < loops; t += stepSize) { + double x = radius * Math.cos(direction * t); + double z = radius * Math.sin(direction * t); + points.add(new Vector3d(x, t * slope, z)); + } + return points; + } + + @Override + public void generateOutline(Set points, double density) { + points.addAll(calculateHelix(radius, height, slope, direction, density)); + } + + @Override + public void generateSurface(Set points, double density) { + for (double r = radius; r > 0; r -= density) { + points.addAll(calculateHelix(r, height, slope, direction, density)); + } + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(targetPointCount, 1); + return switch (style) { + case OUTLINE -> Math.sqrt(slope * slope + radius * radius) * (height / slope) / count; + case FILL, SURFACE -> Math.sqrt(slope * slope + radius * radius * (height / slope) / count); + }; + } + + @Override + public boolean contains(Vector3d point) { + // Approximate: check distance to nearest helix point + if (point.y < 0 || point.y > height) return false; + double t = point.y / slope; + double helixX = radius * Math.cos(direction * t); + double helixZ = radius * Math.sin(direction * t); + double dist = Math.sqrt((point.x - helixX) * (point.x - helixX) + (point.z - helixZ) * (point.z - helixZ)); + return dist <= EPSILON; + } + + public double getSlope() { return slope; } + + public void setSlope(double slope) { + this.slope = Math.max(slope, Shape.EPSILON); + invalidate(); + } + + public int getDirection() { return direction; } + + public void setDirection(int direction) { + if (direction != 1 && direction != -1) + throw new IllegalArgumentException("Direction must be 1 or -1"); + this.direction = direction; + invalidate(); + } + + @Override + public double getLength() { return height; } + + @Override + public void setLength(double length) { + height = Math.max(length, Shape.EPSILON); + invalidate(); + } + + @Override + public double getWidth() { return 0; } + + @Override + public void setWidth(double width) { } + + @Override + public double getHeight() { return height; } + + @Override + public void setHeight(double height) { + this.height = Math.max(height, Shape.EPSILON); + invalidate(); + } + + @Override + public double getRadius() { return radius; } + + @Override + public void setRadius(double radius) { + this.radius = Math.max(radius, Shape.EPSILON); + invalidate(); + } + + @Override + public Shape clone() { + return this.copyTo(new Helix(radius, height, slope, direction)); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/IrregularPolygon.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/IrregularPolygon.java new file mode 100644 index 0000000..1a6092c --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/IrregularPolygon.java @@ -0,0 +1,120 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Vector3d; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class IrregularPolygon extends AbstractShape implements LWHShape { + + private final List vertices; + private double height; + + public IrregularPolygon(Collection vertices) { + super(); + if (vertices.size() < 3) + throw new IllegalArgumentException("A polygon must have at least 3 vertices."); + setBounds(vertices); + this.vertices = flattenVertices(vertices); + } + + public IrregularPolygon(Collection vertices, double height) { + this(vertices); + this.height = Math.max(height, 0); + } + + private List flattenVertices(Collection vertices) { + List flattened = new ArrayList<>(); + for (Vector3d v : vertices) { + flattened.add(new Vector3d(v.x, 0, v.z)); + } + return flattened; + } + + private void setBounds(Collection vertices) { + double low = Double.MAX_VALUE; + double high = -Double.MAX_VALUE; + for (Vector3d v : vertices) { + if (v.y < low) low = v.y; + if (v.y > high) high = v.y; + } + this.height = high - low; + } + + @Override + public void generateOutline(Set points, double density) { + points.addAll(Line.connectPoints(vertices, density)); + points.addAll(Line.calculateLine(vertices.get(0), vertices.get(vertices.size() - 1), density)); + if (height != 0) { + Set upperPoints = new LinkedHashSet<>(); + for (Vector3d v : points) { + upperPoints.add(new Vector3d(v.x, height, v.z)); + } + points.addAll(upperPoints); + for (Vector3d v : vertices) { + points.addAll(Line.calculateLine(v, new Vector3d(v.x, height, v.z), density)); + } + } + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(targetPointCount, 1); + double perimeter = 0; + for (int i = 0; i < vertices.size() - 1; i++) { + perimeter += vertices.get(i).distance(vertices.get(i + 1)); + } + perimeter += vertices.get(0).distance(vertices.get(vertices.size() - 1)); + perimeter *= 2; + perimeter += vertices.size() * height; + return perimeter / count; + } + + @Override + public boolean contains(Vector3d point) { + if (height > 0 && (point.y < 0 || point.y > height)) return false; + if (height == 0 && Math.abs(point.y) > EPSILON) return false; + // Ray casting algorithm on XZ plane + int n = vertices.size(); + boolean inside = false; + for (int i = 0, j = n - 1; i < n; j = i++) { + double xi = vertices.get(i).x, zi = vertices.get(i).z; + double xj = vertices.get(j).x, zj = vertices.get(j).z; + if ((zi > point.z) != (zj > point.z) && + point.x < (xj - xi) * (point.z - zi) / (zj - zi) + xi) { + inside = !inside; + } + } + return inside; + } + + @Override + public double getLength() { return 0; } + + @Override + public void setLength(double length) { } + + @Override + public double getWidth() { return 0; } + + @Override + public void setWidth(double width) { } + + @Override + public double getHeight() { return height; } + + @Override + public void setHeight(double height) { + this.height = Math.max(height, 0); + invalidate(); + } + + @Override + public Shape clone() { + return this.copyTo(new IrregularPolygon(vertices, height)); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/LWHShape.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/LWHShape.java new file mode 100644 index 0000000..54ce72a --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/LWHShape.java @@ -0,0 +1,13 @@ +package com.sovdee.shapes.shapes; + +/** + * Represents a shape that has a length, width, and/or height. + */ +public interface LWHShape extends Shape { + double getLength(); + void setLength(double length); + double getWidth(); + void setWidth(double width); + double getHeight(); + void setHeight(double height); +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Line.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Line.java new file mode 100644 index 0000000..7c830d4 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Line.java @@ -0,0 +1,173 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Vector3d; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +/** + * A line shape defined by two vector endpoints. + * Supports both static endpoints and dynamic suppliers for entity-following. + */ +public class Line extends AbstractShape implements LWHShape { + + private Supplier startSupplier; + private Supplier endSupplier; + + public Line(Vector3d end) { + this(new Vector3d(0, 0, 0), end); + } + + public Line(Vector3d start, Vector3d end) { + super(); + if (start.equals(end)) + throw new IllegalArgumentException("Start and end locations cannot be the same."); + final Vector3d s = new Vector3d(start); + final Vector3d e = new Vector3d(end); + this.startSupplier = () -> new Vector3d(s); + this.endSupplier = () -> new Vector3d(e); + } + + public Line(Supplier start, Supplier end) { + super(); + this.startSupplier = start; + this.endSupplier = end; + setDynamic(true); + } + + /** + * Calculates points along a line from start to end with the given density. + */ + public static Set calculateLine(Vector3d start, Vector3d end, double density) { + Set points = new LinkedHashSet<>(); + Vector3d direction = new Vector3d(end).sub(start); + double length = direction.length(); + double step = length / Math.round(length / density); + direction.normalize().mul(step); + + Vector3d current = new Vector3d(start); + int count = (int) (length / step); + for (int i = 0; i <= count; i++) { + points.add(new Vector3d(current)); + current.add(direction); + } + return points; + } + + /** + * Connects a list of points with lines, returning all intermediate points. + */ + public static Set connectPoints(List points, double density) { + Set connectedPoints = new LinkedHashSet<>(); + for (int i = 0; i < points.size() - 1; i++) { + connectedPoints.addAll(calculateLine(points.get(i), points.get(i + 1), density)); + } + return connectedPoints; + } + + @Override + public void generateOutline(Set points, double density) { + points.addAll(calculateLine(getStart(), getEnd(), density)); + } + + public Vector3d getStart() { + return startSupplier.get(); + } + + public void setStart(Vector3d start) { + final Vector3d s = new Vector3d(start); + this.startSupplier = () -> new Vector3d(s); + invalidate(); + } + + public Vector3d getEnd() { + return endSupplier.get(); + } + + public void setEnd(Vector3d end) { + final Vector3d e = new Vector3d(end); + this.endSupplier = () -> new Vector3d(e); + invalidate(); + } + + public Supplier getStartSupplier() { + return startSupplier; + } + + public void setStartSupplier(Supplier startSupplier) { + this.startSupplier = startSupplier; + } + + public Supplier getEndSupplier() { + return endSupplier; + } + + public void setEndSupplier(Supplier endSupplier) { + this.endSupplier = endSupplier; + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(targetPointCount, 1); + return new Vector3d(getEnd()).sub(getStart()).length() / count; + } + + @Override + public boolean contains(Vector3d point) { + Vector3d start = getStart(); + Vector3d end = getEnd(); + Vector3d line = new Vector3d(end).sub(start); + double len = line.length(); + if (len < EPSILON) return point.distance(start) < EPSILON; + Vector3d toPoint = new Vector3d(point).sub(start); + double t = toPoint.dot(line) / (len * len); + if (t < 0 || t > 1) return false; + Vector3d closest = new Vector3d(start).add(new Vector3d(line).mul(t)); + return point.distance(closest) <= EPSILON; + } + + @Override + public double getLength() { + return new Vector3d(getStart()).sub(getEnd()).length(); + } + + @Override + public void setLength(double length) { + length = Math.max(length, Shape.EPSILON); + Vector3d start = getStart(); + Vector3d end = getEnd(); + Vector3d direction = new Vector3d(end).sub(start).normalize(); + Vector3d newEnd = new Vector3d(start).add(direction.mul(length)); + setEnd(newEnd); + } + + @Override + public double getWidth() { return 0; } + + @Override + public void setWidth(double width) { } + + @Override + public double getHeight() { return 0; } + + @Override + public void setHeight(double height) { } + + @Override + public Shape clone() { + Line clone; + if (isDynamic()) { + clone = new Line(this.startSupplier, this.endSupplier); + } else { + clone = new Line(getStart(), getEnd()); + } + return this.copyTo(clone); + } + + public String toString() { + return "Line from " + getStart() + " to " + getEnd(); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/PolyShape.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/PolyShape.java new file mode 100644 index 0000000..51a75f9 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/PolyShape.java @@ -0,0 +1,11 @@ +package com.sovdee.shapes.shapes; + +/** + * Represents a shape that has a number of sides and a side length. + */ +public interface PolyShape extends Shape { + int getSides(); + void setSides(int sides); + double getSideLength(); + void setSideLength(double sideLength); +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/RadialShape.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/RadialShape.java new file mode 100644 index 0000000..3ffa825 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/RadialShape.java @@ -0,0 +1,10 @@ +package com.sovdee.shapes.shapes; + +/** + * Represents a shape that has a radius. + * The radius must be greater than 0. + */ +public interface RadialShape extends Shape { + double getRadius(); + void setRadius(double radius); +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Rectangle.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Rectangle.java new file mode 100644 index 0000000..03f8555 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Rectangle.java @@ -0,0 +1,191 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Vector3d; + +import java.util.Set; +import java.util.function.Supplier; + +/** + * A rectangle shape, defined by a plane and dimensions. + * For dynamic (entity-following) rectangles, use the plugin-side DynamicRectangle wrapper. + */ +public class Rectangle extends AbstractShape implements LWHShape { + + private Plane plane; + private double halfLength; + private double halfWidth; + private double lengthStep = 1.0; + private double widthStep = 1.0; + private Vector3d centerOffset = new Vector3d(0, 0, 0); + private Supplier cornerASupplier; + private Supplier cornerBSupplier; + + public Rectangle(double length, double width, Plane plane) { + super(); + this.plane = plane; + this.halfLength = Math.max(length / 2, Shape.EPSILON); + this.halfWidth = Math.max(width / 2, Shape.EPSILON); + } + + public Rectangle(Vector3d cornerA, Vector3d cornerB, Plane plane) { + super(); + if (cornerA.equals(cornerB)) + throw new IllegalArgumentException("Corners cannot be the same."); + this.plane = plane; + setLengthWidth(cornerA, cornerB); + centerOffset = new Vector3d(cornerB).add(cornerA).mul(0.5); + switch (plane) { + case XZ -> centerOffset.y = 0; + case XY -> centerOffset.z = 0; + case YZ -> centerOffset.x = 0; + } + } + + public Rectangle(Supplier cornerA, Supplier cornerB, Plane plane) { + super(); + this.plane = plane; + this.cornerASupplier = cornerA; + this.cornerBSupplier = cornerB; + Vector3d a = cornerA.get(); + Vector3d b = cornerB.get(); + setLengthWidth(a, b); + setDynamic(true); + } + + private void setLengthWidth(Vector3d cornerA, Vector3d cornerB) { + double length = switch (plane) { + case XZ, XY -> Math.abs(cornerA.x - cornerB.x); + case YZ -> Math.abs(cornerA.y - cornerB.y); + }; + double width = switch (plane) { + case XZ, YZ -> Math.abs(cornerA.z - cornerB.z); + case XY -> Math.abs(cornerA.y - cornerB.y); + }; + this.halfWidth = Math.abs(width) / 2; + this.halfLength = Math.abs(length) / 2; + } + + private Vector3d vectorFromLengthWidth(double length, double width) { + return switch (plane) { + case XZ -> new Vector3d(length, 0, width); + case XY -> new Vector3d(length, width, 0); + case YZ -> new Vector3d(0, length, width); + }; + } + + private void calculateSteps(double density) { + lengthStep = 2 * halfWidth / Math.round(2 * halfWidth / density); + widthStep = 2 * halfLength / Math.round(2 * halfLength / density); + } + + @Override + public void beforeSampling(double density) { + if (cornerASupplier != null && cornerBSupplier != null) { + Vector3d a = cornerASupplier.get(); + Vector3d b = cornerBSupplier.get(); + setLengthWidth(a, b); + } + calculateSteps(density); + } + + @Override + public void afterSampling(Set points) { + points.forEach(vector -> vector.add(centerOffset)); + } + + @Override + public void generateOutline(Set points, double density) { + for (double l = -halfLength + widthStep; l < halfLength; l += widthStep) { + points.add(vectorFromLengthWidth(l, -halfWidth)); + points.add(vectorFromLengthWidth(l, halfWidth)); + } + for (double w = -halfWidth; w <= halfWidth; w += lengthStep) { + points.add(vectorFromLengthWidth(-halfLength, w)); + points.add(vectorFromLengthWidth(halfLength, w)); + } + } + + @Override + public void generateSurface(Set points, double density) { + for (double w = -halfWidth; w <= halfWidth; w += lengthStep) { + for (double l = -halfLength; l <= halfLength; l += widthStep) { + points.add(vectorFromLengthWidth(l, w)); + } + } + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(targetPointCount, 1); + return switch (style) { + case FILL, SURFACE -> Math.sqrt(4 * halfWidth * halfLength / count); + case OUTLINE -> 4 * (halfWidth + halfLength) / count; + }; + } + + @Override + public boolean contains(Vector3d point) { + return switch (plane) { + case XZ -> Math.abs(point.x) <= halfLength && Math.abs(point.z) <= halfWidth && Math.abs(point.y) < EPSILON; + case XY -> Math.abs(point.x) <= halfLength && Math.abs(point.y) <= halfWidth && Math.abs(point.z) < EPSILON; + case YZ -> Math.abs(point.y) <= halfLength && Math.abs(point.z) <= halfWidth && Math.abs(point.x) < EPSILON; + }; + } + + @Override + public double getLength() { return halfLength * 2; } + + @Override + public void setLength(double length) { + this.halfLength = Math.max(length / 2, Shape.EPSILON); + invalidate(); + } + + @Override + public double getWidth() { return halfWidth * 2; } + + @Override + public void setWidth(double width) { + this.halfWidth = Math.max(width / 2, Shape.EPSILON); + invalidate(); + } + + @Override + public double getHeight() { return 0; } + + @Override + public void setHeight(double height) { } + + public Plane getPlane() { return plane; } + + public void setPlane(Plane plane) { + this.plane = plane; + invalidate(); + } + + public Supplier getCornerASupplier() { return cornerASupplier; } + public Supplier getCornerBSupplier() { return cornerBSupplier; } + + @Override + public Shape clone() { + Rectangle rectangle; + if (cornerASupplier != null && cornerBSupplier != null) { + rectangle = new Rectangle(cornerASupplier, cornerBSupplier, plane); + } else { + rectangle = new Rectangle(this.getLength(), this.getWidth(), plane); + } + rectangle.centerOffset = new Vector3d(this.centerOffset); + return this.copyTo(rectangle); + } + + @Override + public String toString() { + String axis = this.plane.toString().toLowerCase(); + return axis + " rectangle with length " + this.getLength() + " and width " + this.getWidth(); + } + + public enum Plane { + XZ, XY, YZ + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/RegularPolygon.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/RegularPolygon.java new file mode 100644 index 0000000..87705b3 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/RegularPolygon.java @@ -0,0 +1,196 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.shapes.util.VectorUtil; +import org.joml.Vector3d; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class RegularPolygon extends AbstractShape implements PolyShape, RadialShape, LWHShape { + + private double angle; + private double radius; + private double height; + + public RegularPolygon(int sides, double radius) { + this((Math.PI * 2) / sides, radius, 0); + } + + public RegularPolygon(double angle, double radius) { + this(angle, radius, 0); + } + + public RegularPolygon(int sides, double radius, double height) { + this((Math.PI * 2) / sides, radius, height); + } + + public RegularPolygon(double angle, double radius, double height) { + super(); + this.angle = Math.clamp(angle, Shape.EPSILON, Math.PI * 2 / 3); + this.radius = Math.max(radius, Shape.EPSILON); + this.height = Math.max(height, 0); + } + + // --- Static calculation methods --- + + public static Set calculateRegularPolygon(double radius, double angle, double density, boolean wireframe) { + angle = Math.max(angle, Shape.EPSILON); + + Set points = new LinkedHashSet<>(); + double apothem = radius * Math.cos(angle / 2); + double radiusStep = radius / Math.round(apothem / density); + if (wireframe) { + radiusStep = 2 * radius; + } else { + points.add(new Vector3d(0, 0, 0)); + } + for (double subRadius = radius; subRadius >= 0; subRadius -= radiusStep) { + Vector3d vertex = new Vector3d(subRadius, 0, 0); + for (double i = 0; i < 2 * Math.PI; i += angle) { + points.addAll(Line.calculateLine( + VectorUtil.rotateAroundY(new Vector3d(vertex), i), + VectorUtil.rotateAroundY(new Vector3d(vertex), i + angle), + density)); + } + } + return points; + } + + public static Set calculateRegularPrism(double radius, double angle, double height, double density, boolean wireframe) { + Set points = new LinkedHashSet<>(); + Vector3d vertex = new Vector3d(radius, 0, 0); + for (double i = 0; i < 2 * Math.PI; i += angle) { + Vector3d currentVertex = VectorUtil.rotateAroundY(new Vector3d(vertex), i); + for (Vector3d vector : Line.calculateLine(currentVertex, VectorUtil.rotateAroundY(new Vector3d(vertex), i + angle), density)) { + points.add(vector); + if (wireframe) { + points.add(new Vector3d(vector.x, height, vector.z)); + } else { + points.addAll(Line.calculateLine(vector, new Vector3d(vector.x, height, vector.z), density)); + } + } + if (wireframe) + points.addAll(Line.calculateLine(currentVertex, new Vector3d(currentVertex.x, height, currentVertex.z), density)); + } + return points; + } + + // --- Generation methods --- + + @Override + public void generateOutline(Set points, double density) { + if (height == 0) + points.addAll(calculateRegularPolygon(this.radius, this.angle, density, true)); + else + points.addAll(calculateRegularPrism(this.radius, this.angle, this.height, density, true)); + } + + @Override + public void generateSurface(Set points, double density) { + if (height == 0) + points.addAll(calculateRegularPolygon(this.radius, this.angle, density, false)); + else + points.addAll(calculateRegularPrism(this.radius, this.angle, this.height, density, false)); + } + + @Override + public void generateFilled(Set points, double density) { + if (height == 0) + generateSurface(points, density); + else { + Set polygon = calculateRegularPolygon(this.radius, this.angle, density, false); + fillVertically(polygon, height, density); + points.addAll(polygon); + } + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(targetPointCount, 1); + int sides = getSides(); + return switch (style) { + case OUTLINE -> { + if (height == 0) + yield 2 * sides * radius * Math.sin(angle / 2) / count; + yield (4 * sides * radius * Math.sin(angle / 2) + height * sides) / count; + } + case SURFACE -> { + if (height == 0) + yield Math.sqrt(sides * radius * radius * Math.sin(angle) / 2 / count); + yield (sides * radius * radius * Math.sin(angle) + getSideLength() * sides * height) / count; + } + case FILL -> (sides * radius * radius * Math.sin(angle) * height) / count; + }; + } + + @Override + public boolean contains(Vector3d point) { + if (height > 0 && (point.y < 0 || point.y > height)) return false; + if (height == 0 && Math.abs(point.y) > EPSILON) return false; + // Check if point is within the polygon on XZ plane using inscribed radius + double dist = Math.sqrt(point.x * point.x + point.z * point.z); + double apothem = radius * Math.cos(angle / 2); + return dist <= radius && dist <= apothem / Math.cos(Math.atan2(point.z, point.x) % angle - angle / 2); + } + + @Override + public int getSides() { return (int) (Math.PI * 2 / this.angle); } + + @Override + public void setSides(int sides) { + this.angle = (Math.PI * 2) / Math.max(sides, 3); + invalidate(); + } + + @Override + public double getSideLength() { return this.radius * 2 * Math.sin(this.angle / 2); } + + @Override + public void setSideLength(double sideLength) { + sideLength = Math.max(sideLength, Shape.EPSILON); + this.radius = sideLength / (2 * Math.sin(this.angle / 2)); + this.radius = Math.max(radius, Shape.EPSILON); + invalidate(); + } + + @Override + public double getRadius() { return this.radius; } + + @Override + public void setRadius(double radius) { + this.radius = Math.max(radius, Shape.EPSILON); + invalidate(); + } + + @Override + public double getLength() { return 0; } + + @Override + public void setLength(double length) { } + + @Override + public double getWidth() { return 0; } + + @Override + public void setWidth(double width) { } + + @Override + public double getHeight() { return height; } + + @Override + public void setHeight(double height) { + this.height = Math.max(height, 0); + invalidate(); + } + + @Override + public Shape clone() { + return this.copyTo(new RegularPolygon(angle, radius, height)); + } + + @Override + public String toString() { + return "regular polygon with " + getSides() + " sides and radius " + getRadius(); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/RegularPolyhedron.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/RegularPolyhedron.java new file mode 100644 index 0000000..ec0042b --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/RegularPolyhedron.java @@ -0,0 +1,234 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Quaterniond; +import org.joml.Vector3d; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +public class RegularPolyhedron extends AbstractShape implements RadialShape, PolyShape { + + private static final Quaterniond[] TETRAHEDRON_FACES = { + new Quaterniond(1.0, 0.0, 0.0, 0), + new Quaterniond(-0.5, -0.0, -0.288675134594813, 0.816496580927726), + new Quaterniond(0.5, 0.0, -0.288675134594813, 0.816496580927726), + new Quaterniond(0.0, 0.0, 0.5773502691896258, 0.816496580927726) + }; + private static final Quaterniond[] OCTAHEDRON_FACES = { + new Quaterniond(0.0, 0.0, 0.45970084338098305, 0.8880738339771153), + new Quaterniond(0.3250575836718681, 0.6279630301995544, 0.32505758367186816, 0.6279630301995545), + new Quaterniond(0.45970084338098305, 0.8880738339771153, 0, 0), + new Quaterniond(0.32505758367186816, 0.6279630301995545, -0.3250575836718681, -0.6279630301995544), + new Quaterniond(0.0, 0.0, 0.8880738339771153, -0.45970084338098316), + new Quaterniond(0.6279630301995544, -0.3250575836718682, 0.6279630301995545, -0.3250575836718682), + new Quaterniond(0.8880738339771153, -0.45970084338098316, 0, 0), + new Quaterniond(0.6279630301995545, -0.3250575836718682, -0.6279630301995544, 0.3250575836718682) + }; + private static final Quaterniond[] ICOSAHEDRON_FACES = { + new Quaterniond(1.0, 0.0, 0.0, 0), + new Quaterniond(0.0, 1.0, 0.0, 0), + new Quaterniond(0.3090169943749475, 0.0, 0.17841104488654494, 0.9341723589627158), + new Quaterniond(-0.5, 0.8090169943749475, 0.288675134594813, 0.110264089708268), + new Quaterniond(0, 0.8090169943749475, 0.5773502691896256, -0.110264089708268), + new Quaterniond(0.3090169943749475, 0.0, -0.1784110448865451, -0.9341723589627157), + new Quaterniond(0.5, -0.8090169943749475, 0.288675134594813, 0.110264089708268), + new Quaterniond(0, 0.8090169943749475, -0.5773502691896261, 0.110264089708268), + new Quaterniond(0.0, 0.0, 0.35682208977309, -0.9341723589627157), + new Quaterniond(-0.5, -0.8090169943749475, -0.288675134594813, -0.110264089708268), + new Quaterniond(0.5, 0.8090169943749475, -0.288675134594813, -0.110264089708268), + new Quaterniond(-0.8090169943749475, -0.0, -0.46708617948135794, 0.35682208977309), + new Quaterniond(0.3090169943749475, 0.5, -0.7557613140761709, -0.288675134594813), + new Quaterniond(0.8090169943749475, 0.0, -0.46708617948135794, 0.35682208977309), + new Quaterniond(-0.5, -0.5, -0.6454972243679027, 0.288675134594813), + new Quaterniond(0.0, 0.0, 0.9341723589627157, 0.35682208977309), + new Quaterniond(-0.8090169943749475, 0.5, 0.110264089708268, -0.288675134594813) + }; + private static final Quaterniond[] DODECAHEDRON_FACES = { + new Quaterniond(0.0, 0.3090169943749475, 0.0, 0.9510565162951536), + new Quaterniond(-0.3090169943749475, 0, 0.9510565162951536, 0), + new Quaterniond(0.0, 0.0, 0.8506508083520399, 0.5257311121191337), + new Quaterniond(0.0, 0.0, 0.5257311121191337, -0.8506508083520399), + new Quaterniond(0.5, 0.3090169943749475, 0.6881909602355868, 0.42532540417602), + new Quaterniond(0.3090169943749475, -0.5, 0.42532540417602, -0.6881909602355868), + new Quaterniond(0.8090169943749475, 0.5, 0.2628655560595668, 0.1624598481164532), + new Quaterniond(0.5, -0.8090169943749475, 0.1624598481164532, -0.2628655560595668), + new Quaterniond(0.8090169943749475, 0.5, -0.2628655560595668, -0.1624598481164532), + new Quaterniond(0.5, -0.8090169943749475, -0.1624598481164532, 0.2628655560595668), + new Quaterniond(0.5, 0.3090169943749475, -0.6881909602355868, -0.42532540417602), + new Quaterniond(0.3090169943749475, -0.5, -0.42532540417602, 0.6881909602355868) + }; + private static final double TETRA_R2SL = 0.6123724356957945; + private static final double OCTA_R2SL = 0.7071067811865; + private static final double DODECA_R2SL = 1.401258538; + private static final double ICOSA_R2SL = 0.9510565162951535; + + private static final double TETRA_INSC = 1.0 / 4.89897948556; + private static final double OCTA_INSC = 0.408248290; + private static final double DODECA_INSC = 1.113516364; + private static final double ICOSA_INSC = 0.7557613141; + + private double radius; + private int faces; + + public RegularPolyhedron(double radius, int faces) { + super(); + this.radius = Math.max(radius, Shape.EPSILON); + this.faces = switch (faces) { + case 4, 8, 12, 20 -> faces; + default -> 4; + }; + } + + @Override + public void generateOutline(Set points, double density) { + points.addAll(switch (faces) { + case 4 -> generatePolyhedron(TETRAHEDRON_FACES, radius, density, SamplingStyle.OUTLINE); + case 8 -> generatePolyhedron(OCTAHEDRON_FACES, radius, density, SamplingStyle.OUTLINE); + case 20 -> generatePolyhedron(ICOSAHEDRON_FACES, radius, density, SamplingStyle.OUTLINE); + case 12 -> generatePolyhedron(DODECAHEDRON_FACES, radius, density, SamplingStyle.OUTLINE); + default -> new HashSet<>(); + }); + } + + @Override + public void generateSurface(Set points, double density) { + points.addAll(switch (faces) { + case 4 -> generatePolyhedron(TETRAHEDRON_FACES, radius, density, SamplingStyle.SURFACE); + case 8 -> generatePolyhedron(OCTAHEDRON_FACES, radius, density, SamplingStyle.SURFACE); + case 20 -> generatePolyhedron(ICOSAHEDRON_FACES, radius, density, SamplingStyle.SURFACE); + case 12 -> generatePolyhedron(DODECAHEDRON_FACES, radius, density, SamplingStyle.SURFACE); + default -> new HashSet<>(); + }); + } + + @Override + public void generateFilled(Set points, double density) { + double step = radius / Math.round(radius / density); + Quaterniond[] rotations = switch (faces) { + case 4 -> TETRAHEDRON_FACES; + case 8 -> OCTAHEDRON_FACES; + case 12 -> DODECAHEDRON_FACES; + case 20 -> ICOSAHEDRON_FACES; + default -> new Quaterniond[0]; + }; + for (double i = radius; i > 0; i -= step) { + points.addAll(generatePolyhedron(rotations, i, density, SamplingStyle.SURFACE)); + } + } + + private Set generatePolyhedron(Quaterniond[] rotations, double radius, double density, SamplingStyle style) { + Set points = new LinkedHashSet<>(); + int sides = this.faces == 12 ? 5 : 3; + double sideLength = switch (faces) { + case 4 -> radius / TETRA_R2SL; + case 8 -> radius / OCTA_R2SL; + case 12 -> radius / DODECA_R2SL; + case 20 -> radius / ICOSA_R2SL; + default -> 0.0; + }; + double inscribedRadius = switch (this.faces) { + case 4 -> sideLength * TETRA_INSC; + case 8 -> sideLength * OCTA_INSC; + case 12 -> sideLength * DODECA_INSC; + case 20 -> sideLength * ICOSA_INSC; + default -> 1; + }; + Vector3d offset = new Vector3d(0, inscribedRadius, 0); + double faceRadius = sideLength / (2 * Math.sin(Math.PI / sides)); + for (Quaterniond rotation : rotations) { + Set facePoints = new LinkedHashSet<>(switch (style) { + case OUTLINE -> generateFaceOutline(sides, faceRadius, density); + case FILL, SURFACE -> generateFaceSurface(sides, faceRadius, density); + }); + facePoints.forEach(point -> rotation.transform(point.add(offset))); + points.addAll(facePoints); + } + return points; + } + + private Set generateFaceOutline(int sides, double radius, double density) { + return new LinkedHashSet<>(RegularPolygon.calculateRegularPolygon(radius, 2 * Math.PI / sides, density, true)); + } + + private Set generateFaceSurface(int sides, double radius, double density) { + Set facePoints = new LinkedHashSet<>(); + double apothem = radius * Math.cos(Math.PI / sides); + double radiusStep = radius / Math.round(apothem / density); + for (double subRadius = radius; subRadius > 0; subRadius -= radiusStep) { + facePoints.addAll(RegularPolygon.calculateRegularPolygon(subRadius, 2 * Math.PI / sides, density, false)); + } + facePoints.add(new Vector3d(0, 0, 0)); + return facePoints; + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + return 0.25; // No good formula available + } + + @Override + public boolean contains(Vector3d point) { + // Conservative: check if within inscribed sphere + double sideLength = getSideLength(); + double inscribedRadius = switch (faces) { + case 4 -> sideLength * TETRA_INSC; + case 8 -> sideLength * OCTA_INSC; + case 12 -> sideLength * DODECA_INSC; + case 20 -> sideLength * ICOSA_INSC; + default -> 0; + }; + return point.length() <= inscribedRadius; + } + + @Override + public Shape clone() { + return this.copyTo(new RegularPolyhedron(radius, faces)); + } + + @Override + public int getSides() { return faces; } + + @Override + public void setSides(int sides) { + switch (sides) { + case 4, 8, 12, 20 -> this.faces = sides; + default -> { return; } + } + invalidate(); + } + + @Override + public double getSideLength() { + return switch (faces) { + case 4 -> radius / TETRA_R2SL; + case 8 -> radius / OCTA_R2SL; + case 12 -> radius / DODECA_R2SL; + case 20 -> radius / ICOSA_R2SL; + default -> 0.0; + }; + } + + @Override + public void setSideLength(double sideLength) { + sideLength = Math.max(sideLength, Shape.EPSILON); + switch (faces) { + case 4 -> this.radius = sideLength * TETRA_R2SL; + case 8 -> this.radius = sideLength * OCTA_R2SL; + case 12 -> this.radius = sideLength * DODECA_R2SL; + case 20 -> this.radius = sideLength * ICOSA_R2SL; + default -> { return; } + } + invalidate(); + } + + @Override + public double getRadius() { return radius; } + + @Override + public void setRadius(double radius) { + this.radius = Math.max(radius, Shape.EPSILON); + invalidate(); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Shape.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Shape.java new file mode 100644 index 0000000..194910c --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Shape.java @@ -0,0 +1,78 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.PointSampler; +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Quaterniond; +import org.joml.Vector3d; + +import java.util.Set; + +/** + * Represents a geometric shape. Pure geometry interface — sampling/caching/drawing + * configuration lives in {@link PointSampler}. + */ +public interface Shape extends Cloneable { + + double EPSILON = 0.0001; + + // --- Spatial transform --- + + Quaterniond getOrientation(); + void setOrientation(Quaterniond orientation); + + double getScale(); + void setScale(double scale); + + Vector3d getOffset(); + void setOffset(Vector3d offset); + + // --- Oriented axes --- + + Vector3d getRelativeXAxis(); + Vector3d getRelativeYAxis(); + Vector3d getRelativeZAxis(); + + // --- Geometry query --- + + boolean contains(Vector3d point); + + // --- Change detection --- + + long getVersion(); + + // --- Dynamic support --- + + boolean isDynamic(); + void setDynamic(boolean dynamic); + + // --- Point generation (density as parameter) --- + + void generateOutline(Set points, double density); + void generateSurface(Set points, double density); + void generateFilled(Set points, double density); + + /** + * Called by PointSampler before point generation. Override for supplier refresh, step recalc, etc. + */ + default void beforeSampling(double density) {} + + /** + * Called by PointSampler after point generation. Override for centerOffset adjustment, etc. + */ + default void afterSampling(Set points) {} + + /** + * Computes the density needed to achieve approximately the given number of points. + */ + double computeDensity(SamplingStyle style, int targetPointCount); + + // --- PointSampler --- + + PointSampler getPointSampler(); + void setPointSampler(PointSampler sampler); + + // --- Replication --- + + Shape clone(); + Shape copyTo(Shape target); +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Sphere.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Sphere.java new file mode 100644 index 0000000..b1b9bb6 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Sphere.java @@ -0,0 +1,125 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import org.joml.Vector3d; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class Sphere extends AbstractShape implements RadialShape { + + private static final double PHI = Math.PI * (3.0 - Math.sqrt(5.0)); + private static final double[] SPHERE_THETA_COS = new double[4096]; + private static final double[] SPHERE_THETA_SIN = new double[4096]; + + static { + for (int i = 0; i < SPHERE_THETA_COS.length; i++) { + SPHERE_THETA_COS[i] = Math.cos(PHI * i); + SPHERE_THETA_SIN[i] = Math.sin(PHI * i); + } + } + + private double radius; + protected double cutoffAngle; + protected double cutoffAngleCos; + + public Sphere(double radius) { + super(); + this.radius = Math.max(radius, Shape.EPSILON); + this.cutoffAngle = Math.PI; + this.cutoffAngleCos = -1.0; + this.getPointSampler().setStyle(SamplingStyle.SURFACE); + } + + // --- Static calculation methods --- + + private static Set calculateFibonacciSphere(int pointCount, double radius) { + return calculateFibonacciSphere(pointCount, radius, Math.PI); + } + + private static Set calculateFibonacciSphere(int pointCount, double radius, double angleCutoff) { + Set points = new LinkedHashSet<>(); + double y = 1; + if (angleCutoff > Math.PI) angleCutoff = Math.PI; + double yLimit = Math.cos(angleCutoff); + + double yStep = 2.0 / pointCount; + int preCompPoints = Math.min(pointCount, SPHERE_THETA_COS.length); + for (int i = 0; i < preCompPoints; i++) { + double r = Math.sqrt(1 - y * y) * radius; + points.add(new Vector3d(r * SPHERE_THETA_COS[i], y * radius, r * SPHERE_THETA_SIN[i])); + y -= yStep; + if (y <= yLimit) { + return points; + } + } + if (pointCount > preCompPoints) { + for (int i = preCompPoints; i < pointCount; i++) { + double r = Math.sqrt(1 - y * y) * radius; + double theta = PHI * i; + points.add(new Vector3d(r * Math.cos(theta), y * radius, r * Math.sin(theta))); + y -= yStep; + if (y <= yLimit) { + return points; + } + } + } + return points; + } + + // --- Generation methods --- + + @Override + public void generateOutline(Set points, double density) { + this.generateSurface(points, density); + } + + @Override + public void generateSurface(Set points, double density) { + int pointCount = 4 * (int) (Math.PI * radius * radius / (density * density)); + points.addAll(calculateFibonacciSphere(pointCount, radius, cutoffAngle)); + } + + @Override + public void generateFilled(Set points, double density) { + int subSpheres = (int) (radius / density) - 1; + double radiusStep = radius / subSpheres; + for (int i = 1; i < subSpheres; i++) { + double subRadius = i * radiusStep; + int pointCount = 4 * (int) (Math.PI * subRadius * subRadius / (density * density)); + points.addAll(calculateFibonacciSphere(pointCount, subRadius, cutoffAngle)); + } + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(targetPointCount, 1); + return switch (style) { + case OUTLINE, SURFACE -> Math.sqrt(2 * Math.PI * radius * radius * (1 - cutoffAngleCos) / count); + case FILL -> Math.cbrt(Math.PI / 3 * radius * radius * radius * (2 + cutoffAngleCos) * (1 - cutoffAngleCos) * (1 - cutoffAngleCos) / count); + }; + } + + @Override + public boolean contains(Vector3d point) { + return point.x * point.x + point.y * point.y + point.z * point.z <= radius * radius; + } + + @Override + public double getRadius() { return radius; } + + @Override + public void setRadius(double radius) { + this.radius = Math.max(radius, Shape.EPSILON); + invalidate(); + } + + @Override + public Shape clone() { + return this.copyTo(new Sphere(radius)); + } + + public String toString() { + return this.getPointSampler().getStyle() + " sphere with radius " + this.radius; + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/SphericalCap.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/SphericalCap.java new file mode 100644 index 0000000..fc290a0 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/SphericalCap.java @@ -0,0 +1,37 @@ +package com.sovdee.shapes.shapes; + +import org.joml.Vector3d; + +public class SphericalCap extends Sphere implements CutoffShape { + + public SphericalCap(double radius, double cutoffAngle) { + super(radius); + this.cutoffAngle = Math.clamp(cutoffAngle, 0, Math.PI); + this.cutoffAngleCos = Math.cos(this.cutoffAngle); + } + + @Override + public double getCutoffAngle() { + return cutoffAngle; + } + + @Override + public void setCutoffAngle(double cutoffAngle) { + this.cutoffAngle = Math.clamp(cutoffAngle, 0, Math.PI); + this.cutoffAngleCos = Math.cos(this.cutoffAngle); + invalidate(); + } + + @Override + public boolean contains(Vector3d point) { + if (!super.contains(point)) return false; + // Check if within the cap's polar angle + double y = point.y / getRadius(); + return y >= cutoffAngleCos; + } + + @Override + public Shape clone() { + return this.copyTo(new SphericalCap(this.getRadius(), cutoffAngle)); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Star.java b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Star.java new file mode 100644 index 0000000..c0a262d --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/shapes/Star.java @@ -0,0 +1,92 @@ +package com.sovdee.shapes.shapes; + +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.shapes.util.VectorUtil; +import org.joml.Vector3d; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class Star extends AbstractShape { + + private double innerRadius; + private double outerRadius; + private double angle; + + public Star(double innerRadius, double outerRadius, double angle) { + super(); + this.innerRadius = Math.max(innerRadius, Shape.EPSILON); + this.outerRadius = Math.max(outerRadius, Shape.EPSILON); + this.angle = Math.clamp(angle, Shape.EPSILON, Math.PI); + } + + private static Set calculateStar(double innerRadius, double outerRadius, double angle, double density) { + Set points = new LinkedHashSet<>(); + Vector3d outerVertex = new Vector3d(outerRadius, 0, 0); + Vector3d innerVertex = new Vector3d(innerRadius, 0, 0); + for (double theta = 0; theta < 2 * Math.PI; theta += angle) { + Vector3d currentVertex = VectorUtil.rotateAroundY(new Vector3d(outerVertex), theta); + points.addAll(Line.calculateLine(currentVertex, VectorUtil.rotateAroundY(new Vector3d(innerVertex), theta + angle / 2), density)); + points.addAll(Line.calculateLine(currentVertex, VectorUtil.rotateAroundY(new Vector3d(innerVertex), theta - angle / 2), density)); + } + return points; + } + + @Override + public void generateOutline(Set points, double density) { + points.addAll(calculateStar(innerRadius, outerRadius, angle, density)); + } + + @Override + public void generateSurface(Set points, double density) { + double minRadius = Math.min(innerRadius, outerRadius); + for (double r = 0; r < minRadius; r += density) { + points.addAll(calculateStar(innerRadius - r, outerRadius - r, angle, density)); + } + } + + @Override + public double computeDensity(SamplingStyle style, int targetPointCount) { + int count = Math.max(targetPointCount, 1); + double sideLength = Math.sqrt(Math.pow(innerRadius, 2) + Math.pow(outerRadius, 2) - 2 * innerRadius * outerRadius * Math.cos(angle)); + double perimeter = sideLength * getStarPoints() * 2; + return perimeter / count; + } + + @Override + public boolean contains(Vector3d point) { + if (Math.abs(point.y) > EPSILON) return false; + // Check if within outer radius bounding circle + double dist = Math.sqrt(point.x * point.x + point.z * point.z); + return dist <= Math.max(innerRadius, outerRadius); + } + + public double getInnerRadius() { return innerRadius; } + + public void setInnerRadius(double innerRadius) { + this.innerRadius = Math.max(innerRadius, Shape.EPSILON); + invalidate(); + } + + public double getOuterRadius() { return outerRadius; } + + public void setOuterRadius(double outerRadius) { + this.outerRadius = Math.max(outerRadius, Shape.EPSILON); + invalidate(); + } + + public int getStarPoints() { + return (int) (Math.PI * 2 / angle); + } + + public void setStarPoints(int starPoints) { + starPoints = Math.max(starPoints, 2); + this.angle = Math.PI * 2 / starPoints; + invalidate(); + } + + @Override + public Shape clone() { + return this.copyTo(new Star(innerRadius, outerRadius, angle)); + } +} diff --git a/shapes-lib/src/main/java/com/sovdee/shapes/util/VectorUtil.java b/shapes-lib/src/main/java/com/sovdee/shapes/util/VectorUtil.java new file mode 100644 index 0000000..8519212 --- /dev/null +++ b/shapes-lib/src/main/java/com/sovdee/shapes/util/VectorUtil.java @@ -0,0 +1,51 @@ +package com.sovdee.shapes.util; + +import org.joml.Quaterniond; +import org.joml.Vector3d; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Helper methods for JOML Vector3d operations that mirror Bukkit Vector convenience methods. + */ +public class VectorUtil { + + /** + * Rotates a vector around the Y axis by the given angle in radians. + * Modifies and returns the same vector. + * + * @param v the vector to rotate + * @param angle the angle in radians + * @return the rotated vector (same instance) + */ + public static Vector3d rotateAroundY(Vector3d v, double angle) { + double cos = Math.cos(angle); + double sin = Math.sin(angle); + double x = v.x * cos + v.z * sin; + double z = -v.x * sin + v.z * cos; + v.x = x; + v.z = z; + return v; + } + + /** + * Transforms a list of vectors using a quaternion, modifying them in place. + */ + public static List transform(Quaterniond quaternion, List vectors) { + vectors.replaceAll(quaternion::transform); + return vectors; + } + + /** + * Transforms a set of vectors using a quaternion, returning a new set. + */ + public static Set transform(Quaterniond quaternion, Set vectors) { + Set newVectors = new HashSet<>(); + for (Vector3d vector : vectors) { + newVectors.add(quaternion.transform(new Vector3d(vector))); + } + return newVectors; + } +} diff --git a/build.gradle b/skript-particle/build.gradle similarity index 66% rename from build.gradle rename to skript-particle/build.gradle index 5852bf1..4c7c539 100644 --- a/build.gradle +++ b/skript-particle/build.gradle @@ -2,10 +2,10 @@ import org.apache.tools.ant.filters.ReplaceTokens plugins { id 'java' + id 'com.gradleup.shadow' version '8.3.5' } group 'com.sovdee' -project.ext.jomlVersion = "1.10.5" configurations.configureEach { resolutionStrategy.cacheChangingModulesFor 1, 'minutes' @@ -22,17 +22,14 @@ repositories { } dependencies { + implementation project(':shapes-lib') compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT") compileOnly("com.github.SkriptLang:Skript:2.14.0-pre1") - implementation "org.joml:joml:${jomlVersion}" + compileOnly "org.joml:joml:${jomlVersion}" } processResources { filter ReplaceTokens, tokens: ["version": project.property("version")] - from("lang/") { - include '*.lang' - into 'lang/' - } } javadoc { @@ -45,8 +42,23 @@ java { toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } +shadowJar { + archiveClassifier.set('') + configurations = [project.configurations.runtimeClasspath] + exclude 'org/joml/**' + exclude 'META-INF/maven/org.joml/**' +} + +tasks.named('jar') { + enabled = false +} + +tasks.named('build') { + dependsOn shadowJar +} + tasks.register('copyJar', Copy) { - dependsOn jar - from "build/libs/skript-particle.jar" + dependsOn shadowJar + from shadowJar.archiveFile into "e:/PaperServer/plugins" } diff --git a/src/main/java/com/sovdee/skriptparticles/Metrics.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/Metrics.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/Metrics.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/Metrics.java diff --git a/src/main/java/com/sovdee/skriptparticles/SkriptParticle.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/SkriptParticle.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/SkriptParticle.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/SkriptParticle.java diff --git a/src/main/java/com/sovdee/skriptparticles/elements/effects/EffRotateShape.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/effects/EffRotateShape.java similarity index 91% rename from src/main/java/com/sovdee/skriptparticles/elements/effects/EffRotateShape.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/effects/EffRotateShape.java index 3614ace..031b798 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/effects/EffRotateShape.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/effects/EffRotateShape.java @@ -10,10 +10,11 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import com.sovdee.skriptparticles.elements.sections.DrawShapeEffectSection.DrawEvent; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; import org.bukkit.event.Event; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; +import org.joml.Quaterniond; import org.joml.Quaternionf; @Name("Rotate Shape") @@ -81,7 +82,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override protected void execute(Event event) { - Quaternionf rotation; + Quaterniond rotation; if (isAxisAngle) { Number angle = this.angle.getSingle(event); if (angle == null) return; @@ -105,10 +106,11 @@ protected void execute(Event event) { axis = vectorAxis.getSingle(event); if (axis == null) return; } - rotation = new Quaternionf().rotationAxis((float) angle.doubleValue(), (float) axis.getX(), (float) axis.getY(), (float) axis.getZ()); + rotation = new Quaterniond().rotationAxis(angle.doubleValue(), axis.getX(), axis.getY(), axis.getZ()); } else { - rotation = this.rotation.getSingle(event); - if (rotation == null) return; + Quaternionf rotationF = this.rotation.getSingle(event); + if (rotationF == null) return; + rotation = new Quaterniond(rotationF.x, rotationF.y, rotationF.z, rotationF.w); } Shape[] shapes; @@ -119,7 +121,7 @@ protected void execute(Event event) { } for (Shape shape : shapes) { - Quaternionf orientation = shape.getOrientation(); + Quaterniond orientation = shape.getOrientation(); if (relative) { shape.setOrientation(orientation.mul(rotation)); } else { diff --git a/src/main/java/com/sovdee/skriptparticles/elements/effects/EffSetOrdering.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/effects/EffSetOrdering.java similarity index 82% rename from src/main/java/com/sovdee/skriptparticles/elements/effects/EffSetOrdering.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/effects/EffSetOrdering.java index ada4414..c22004b 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/effects/EffSetOrdering.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/effects/EffSetOrdering.java @@ -9,10 +9,10 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; import org.bukkit.event.Event; -import org.bukkit.util.Vector; import org.checkerframework.checker.nullness.qual.Nullable; +import org.joml.Vector3d; import java.util.Comparator; @@ -44,21 +44,21 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is @Override protected void execute(Event event) { - @Nullable Comparator order = switch (this.order) { + @Nullable Comparator order = switch (this.order) { case 1 -> (o1, o2) -> { - double value1 = o1.getX() + o1.getY() + o1.getZ(); - double value2 = o2.getX() + o2.getY() + o2.getZ(); + double value1 = o1.x + o1.y + o1.z; + double value2 = o2.x + o2.y + o2.z; return Double.compare(value1, value2); }; case 2 -> (o1, o2) -> { - double value1 = o1.getX() + o1.getY() + o1.getZ(); - double value2 = o2.getX() + o2.getY() + o2.getZ(); + double value1 = o1.x + o1.y + o1.z; + double value2 = o2.x + o2.y + o2.z; return -1 * Double.compare(value1, value2); }; default -> null; }; for (Shape shape : shapes.getArray(event)) { - shape.setOrdering(order); + shape.getPointSampler().setOrdering(order); } } diff --git a/src/main/java/com/sovdee/skriptparticles/elements/effects/EffToggleAxes.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/effects/EffToggleAxes.java similarity index 90% rename from src/main/java/com/sovdee/skriptparticles/elements/effects/EffToggleAxes.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/effects/EffToggleAxes.java index a7af564..b8026f5 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/effects/EffToggleAxes.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/effects/EffToggleAxes.java @@ -9,7 +9,8 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.skriptparticles.shapes.DrawData; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -50,10 +51,11 @@ public boolean init(Expression[] expressions, int i, Kleenean kleenean, Skrip protected void execute(Event event) { Shape[] shapes = shape.getArray(event); for (Shape shape : shapes) { + DrawData dd = DrawData.of(shape); if (globalFlag) - shape.showGlobalAxes(showFlag); + dd.showGlobalAxes(showFlag); if (localFlag) - shape.showLocalAxes(showFlag); + dd.showLocalAxes(showFlag); } } diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprDrawnShapes.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprDrawnShapes.java similarity index 95% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprDrawnShapes.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprDrawnShapes.java index 30de270..87cb82a 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprDrawnShapes.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprDrawnShapes.java @@ -7,7 +7,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.ExpressionType; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprLastCreatedParticle.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprLastCreatedParticle.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprLastCreatedParticle.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprLastCreatedParticle.java diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprRotation.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprRotation.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprRotation.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprRotation.java diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprShapeCopy.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprShapeCopy.java similarity index 97% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprShapeCopy.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprShapeCopy.java index 9861c9e..a85e9a1 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprShapeCopy.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/ExprShapeCopy.java @@ -10,7 +10,7 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; import org.bukkit.event.Event; import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprArc.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprArc.java similarity index 85% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprArc.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprArc.java index 5413e63..b137d0c 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprArc.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprArc.java @@ -11,8 +11,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Arc; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Arc; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -30,10 +32,10 @@ "set {_shape} to a cylindrical sector of radius 1, height 0.5, and angle 45" }) @Since("1.0.0") -public class ExprArc extends SimpleExpression { +public class ExprArc extends SimpleExpression { static { - Skript.registerExpression(ExprArc.class, Arc.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprArc.class, Shape.class, ExpressionType.COMBINED, "[a[n]] [circular] (arc|:sector) (with|of) radius %number% and [cutoff] angle [of] %number% [degrees|:radians]", "[a[n]] [cylindrical] (arc|:sector) (with|of) radius %number%(,| and) height %-number%[,] and [cutoff] angle [of] %number% [degrees|:radians]"); } @@ -79,7 +81,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - protected Arc[] get(Event event) { + protected Shape[] get(Event event) { Number radius = this.radius.getSingle(event); Number angle = this.angle.getSingle(event); Number height = (this.height != null) ? this.height.getSingle(event) : 0; @@ -93,11 +95,12 @@ protected Arc[] get(Event event) { height = Math.max(height.doubleValue(), 0); angle = MathUtil.clamp(angle.doubleValue(), 0, 2 * Math.PI); - Arc arc = new Arc(radius.doubleValue(), height.doubleValue(), angle.doubleValue()); + Arc shape = new Arc(radius.doubleValue(), height.doubleValue(), angle.doubleValue()); if (isSector) - arc.setStyle(Shape.Style.FILL); + shape.getPointSampler().setStyle(SamplingStyle.FILL); + shape.getPointSampler().setDrawContext(new DrawData()); - return new Arc[]{arc}; + return new Shape[]{shape}; } @Override @@ -106,8 +109,8 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return Arc.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprBezierCurve.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprBezierCurve.java similarity index 56% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprBezierCurve.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprBezierCurve.java index 2129dcd..5816811 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprBezierCurve.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprBezierCurve.java @@ -10,11 +10,16 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.BezierCurve; +import com.sovdee.shapes.shapes.BezierCurve; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.Point; +import com.sovdee.skriptparticles.util.VectorConversion; +import org.bukkit.Location; import org.bukkit.event.Event; import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.Nullable; +import org.joml.Vector3d; import java.util.ArrayList; import java.util.List; @@ -28,10 +33,10 @@ "set {_shape} to a curve from player to player's target with control point (location 3 above player)" }) @Since("1.3.0") -public class ExprBezierCurve extends SimpleExpression { +public class ExprBezierCurve extends SimpleExpression { static { - Skript.registerExpression(ExprBezierCurve.class, BezierCurve.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprBezierCurve.class, Shape.class, ExpressionType.COMBINED, "[a] [bezier] curve from [start] %vector/entity/location% to [end] %vector/entity/location% (with|using) control point[s] %vectors/entities/locations%"); } @@ -48,17 +53,33 @@ public boolean init(Expression[] exprs, int matchedPattern, @NonNull Kleenean } @Override - protected BezierCurve @Nullable [] get(@NonNull Event event) { - @Nullable Point start = Point.of(this.start.getSingle(event)); - @Nullable Point end = Point.of(this.end.getSingle(event)); - if (start == null || end == null) + protected Shape @Nullable [] get(@NonNull Event event) { + @Nullable Point startPt = Point.of(this.start.getSingle(event)); + @Nullable Point endPt = Point.of(this.end.getSingle(event)); + if (startPt == null || endPt == null) return null; - List> controlPoints = new ArrayList<>(); + List> controlPts = new ArrayList<>(); for (Object value : this.controlPoints.getArray(event)) { - controlPoints.add(Point.of(value)); + controlPts.add(Point.of(value)); } - return new BezierCurve[]{new BezierCurve(start, end, controlPoints)}; + // Use Supplier-based BezierCurve for dynamic control points + BezierCurve curve = getBezierCurve(startPt, controlPts, endPt); + return new Shape[]{curve}; + } + + private static @NonNull BezierCurve getBezierCurve(@NonNull Point startPt, List> controlPts, @NonNull Point endPt) { + BezierCurve curve = new BezierCurve(() -> { + Location origin = startPt.getLocation(); + List result = new ArrayList<>(); + result.add(VectorConversion.toJOML(startPt.getVector(origin))); + for (Point cp : controlPts) + result.add(VectorConversion.toJOML(cp.getVector(origin))); + result.add(VectorConversion.toJOML(endPt.getVector(origin))); + return result; + }); + curve.getPointSampler().setDrawContext(new DrawData()); + return curve; } @Override @@ -68,8 +89,8 @@ public boolean isSingle() { @Override @NonNull - public Class getReturnType() { - return BezierCurve.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCircle.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCircle.java similarity index 77% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCircle.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCircle.java index 5d19fd3..a71f273 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCircle.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCircle.java @@ -11,8 +11,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Circle; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Circle; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.checkerframework.checker.nullness.qual.NonNull; @@ -28,17 +30,17 @@ "set {_shape} to a solid cylinder with radius 3 and height 5" }) @Since("1.0.0") -public class ExprCircle extends SimpleExpression { +public class ExprCircle extends SimpleExpression { static { - Skript.registerExpression(ExprCircle.class, Circle.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprCircle.class, Shape.class, ExpressionType.COMBINED, "[a] (circle|:disc) (with|of) radius %number%", "[a] [hollow|2:solid] (cylinder|1:tube) (with|of) radius %number% and height %number%"); } private Expression radius; private Expression height; - private Shape.Style style; + private SamplingStyle style; private boolean isCylinder; @Override @@ -61,19 +63,19 @@ public boolean init(Expression[] exprs, int matchedPattern, @NonNull Kleenean } style = switch (parseResult.mark) { - case 0 -> Shape.Style.SURFACE; - case 1 -> Shape.Style.OUTLINE; - default -> Shape.Style.FILL; + case 0 -> SamplingStyle.SURFACE; + case 1 -> SamplingStyle.OUTLINE; + default -> SamplingStyle.FILL; }; } else { - style = parseResult.hasTag("disc") ? Shape.Style.SURFACE : Shape.Style.OUTLINE; + style = parseResult.hasTag("disc") ? SamplingStyle.SURFACE : SamplingStyle.OUTLINE; } return true; } @Override @Nullable - protected Circle[] get(@NonNull Event event) { + protected Shape[] get(@NonNull Event event) { Number radius = this.radius.getSingle(event); Number height = this.height != null ? this.height.getSingle(event) : 0; if (radius == null || height == null) @@ -82,9 +84,10 @@ protected Circle[] get(@NonNull Event event) { radius = Math.max(radius.doubleValue(), MathUtil.EPSILON); height = Math.max(height.doubleValue(), 0); - Circle circle = new Circle(radius.doubleValue(), height.doubleValue()); - circle.setStyle(style); - return new Circle[]{circle}; + Circle shape = new Circle(radius.doubleValue(), height.doubleValue()); + shape.getPointSampler().setStyle(style); + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -94,8 +97,8 @@ public boolean isSingle() { @Override @NonNull - public Class getReturnType() { - return Circle.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCuboid.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCuboid.java similarity index 75% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCuboid.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCuboid.java index 120e56d..e0f0d21 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCuboid.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprCuboid.java @@ -10,10 +10,13 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Cuboid; -import com.sovdee.skriptparticles.shapes.Shape.Style; +import com.sovdee.shapes.shapes.Cuboid; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.DynamicLocation; import com.sovdee.skriptparticles.util.MathUtil; +import com.sovdee.skriptparticles.util.VectorConversion; import org.bukkit.event.Event; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; @@ -32,10 +35,10 @@ "draw the shape of a cuboid from player to player's target" }) @Since("1.0.0") -public class ExprCuboid extends SimpleExpression { +public class ExprCuboid extends SimpleExpression { static { - Skript.registerExpression(ExprCuboid.class, Cuboid.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprCuboid.class, Shape.class, ExpressionType.COMBINED, "[a] [:hollow|:solid] cuboid (with|of) length %number%(,| and) width %number%[,] and height %number%", "[a] [:hollow|:solid] cuboid (from|between) %location/entity/vector% (to|and) %location/entity/vector%"); @@ -48,7 +51,7 @@ public class ExprCuboid extends SimpleExpression { private Expression corner2; private int matchedPattern = 0; - private Style style; + private SamplingStyle style; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { @@ -65,19 +68,19 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } this.matchedPattern = matchedPattern; if (parseResult.hasTag("hollow")) { - style = Style.SURFACE; + style = SamplingStyle.SURFACE; } else if (parseResult.hasTag("solid")) { - style = Style.FILL; + style = SamplingStyle.FILL; } else { - style = Style.OUTLINE; + style = SamplingStyle.OUTLINE; } return true; } @Override @Nullable - protected Cuboid[] get(Event event) { - Cuboid cuboid; + protected Shape[] get(Event event) { + Shape shape; // from width, length, height if (matchedPattern == 0) { if (width == null || length == null || height == null) return null; @@ -88,7 +91,7 @@ protected Cuboid[] get(Event event) { width = Math.max(width.doubleValue(), MathUtil.EPSILON); length = Math.max(length.doubleValue(), MathUtil.EPSILON); height = Math.max(height.doubleValue(), MathUtil.EPSILON); - cuboid = new Cuboid(width.doubleValue(), length.doubleValue(), height.doubleValue()); + shape = new Cuboid(length.doubleValue(), width.doubleValue(), height.doubleValue()); // from location/entity/vector to location/entity/vector } else { if (corner1 == null || corner2 == null) return null; @@ -98,22 +101,24 @@ protected Cuboid[] get(Event event) { // vector check if (corner1 instanceof Vector && corner2 instanceof Vector) { - // if both are vectors, create a static cuboid - cuboid = new Cuboid((Vector) corner1, (Vector) corner2); + shape = new Cuboid(VectorConversion.toJOML((Vector) corner1), VectorConversion.toJOML((Vector) corner2)); } else if (corner1 instanceof Vector || corner2 instanceof Vector) { - // if one is a vector, return empty array return null; } else { - // if neither are vectors, create a dynamic cuboid - corner1 = DynamicLocation.fromLocationEntity(corner1); - corner2 = DynamicLocation.fromLocationEntity(corner2); - if (corner1 == null || corner2 == null) + DynamicLocation dl1 = DynamicLocation.fromLocationEntity(corner1); + DynamicLocation dl2 = DynamicLocation.fromLocationEntity(corner2); + if (dl1 == null || dl2 == null) return null; - cuboid = new Cuboid((DynamicLocation) corner1, (DynamicLocation) corner2); + // Use Supplier-based Cuboid for dynamic corners + shape = new Cuboid( + () -> VectorConversion.toJOML(dl1.getLocation().toVector()), + () -> VectorConversion.toJOML(dl2.getLocation().toVector()) + ); } } - cuboid.setStyle(style); - return new Cuboid[]{cuboid}; + shape.getPointSampler().setStyle(style); + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -122,8 +127,8 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return Cuboid.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipse.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipse.java similarity index 81% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipse.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipse.java index 620734e..40f2a55 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipse.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipse.java @@ -11,9 +11,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Ellipse; -import com.sovdee.skriptparticles.shapes.Shape; -import com.sovdee.skriptparticles.shapes.Shape.Style; +import com.sovdee.shapes.shapes.Ellipse; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -31,10 +32,10 @@ "set {_shape} to a hollow elliptical cylinder with radii 3 and 6 and height 5" }) @Since("1.0.0") -public class ExprEllipse extends SimpleExpression { +public class ExprEllipse extends SimpleExpression { static { - Skript.registerExpression(ExprEllipse.class, Ellipse.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprEllipse.class, Shape.class, ExpressionType.COMBINED, "[a[n]] [surface:(solid|filled)] (ellipse|oval) (with|of) radi(i|us) %number% and %number%", "[a[n]] [hollow|2:solid] elliptical (cylinder|1:tube) (with|of) radi(i|us) %number%(,| and) %number%[,] and height %number%"); } @@ -42,22 +43,22 @@ public class ExprEllipse extends SimpleExpression { private Expression xRadius; private Expression zRadius; private Expression height; - private Style style; + private SamplingStyle style; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { xRadius = (Expression) exprs[0]; zRadius = (Expression) exprs[1]; - style = Style.OUTLINE; + style = SamplingStyle.OUTLINE; if (parseResult.hasTag("surface")) { - style = Style.SURFACE; + style = SamplingStyle.SURFACE; } if (matchedPattern == 1) { height = (Expression) exprs[2]; style = switch (parseResult.mark) { - case 0 -> Shape.Style.SURFACE; - case 1 -> Shape.Style.OUTLINE; - default -> Shape.Style.FILL; + case 0 -> SamplingStyle.SURFACE; + case 1 -> SamplingStyle.OUTLINE; + default -> SamplingStyle.FILL; }; } @@ -84,7 +85,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - protected Ellipse[] get(Event event) { + protected Shape[] get(Event event) { Number xRadius = this.xRadius.getSingle(event); Number zRadius = this.zRadius.getSingle(event); Number height = this.height == null ? 0 : this.height.getSingle(event); @@ -95,9 +96,10 @@ protected Ellipse[] get(Event event) { zRadius = Math.max(zRadius.doubleValue(), MathUtil.EPSILON); height = Math.max(height.doubleValue(), 0); - Ellipse ellipse = new Ellipse(xRadius.doubleValue(), zRadius.doubleValue(), height.doubleValue()); - ellipse.setStyle(style); - return new Ellipse[]{ellipse}; + Ellipse shape = new Ellipse(xRadius.doubleValue(), zRadius.doubleValue(), height.doubleValue()); + shape.getPointSampler().setStyle(style); + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -106,8 +108,8 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return Ellipse.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipsoid.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipsoid.java similarity index 81% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipsoid.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipsoid.java index 599ffdc..7a5c822 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipsoid.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipsoid.java @@ -11,8 +11,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Ellipsoid; -import com.sovdee.skriptparticles.shapes.Shape.Style; +import com.sovdee.shapes.shapes.Ellipsoid; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -32,17 +34,17 @@ "set {_shape} to a hollow ellipsoid with radii 3, 6 and 5" }) @Since("1.0.0") -public class ExprEllipsoid extends SimpleExpression { +public class ExprEllipsoid extends SimpleExpression { static { - Skript.registerExpression(ExprEllipsoid.class, Ellipsoid.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprEllipsoid.class, Shape.class, ExpressionType.COMBINED, "[a[n]] [(outlined|wireframe|:hollow|fill:(solid|filled))] ellipsoid (with|of) radi(i|us) %number%(,| and) %number%[,] and %number%"); } private Expression xRadius; private Expression yRadius; private Expression zRadius; - private Style style; + private SamplingStyle style; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { @@ -68,13 +70,13 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return false; } - style = (parseResult.hasTag("hollow") ? Style.SURFACE : (parseResult.hasTag("fill") ? Style.FILL : Style.OUTLINE)); + style = (parseResult.hasTag("hollow") ? SamplingStyle.SURFACE : (parseResult.hasTag("fill") ? SamplingStyle.FILL : SamplingStyle.OUTLINE)); return true; } @Override @Nullable - protected Ellipsoid[] get(Event event) { + protected Shape[] get(Event event) { Number xRadius = this.xRadius.getSingle(event); Number yRadius = this.yRadius.getSingle(event); Number zRadius = this.zRadius.getSingle(event); @@ -85,9 +87,10 @@ protected Ellipsoid[] get(Event event) { yRadius = Math.max(yRadius.doubleValue(), MathUtil.EPSILON); zRadius = Math.max(zRadius.doubleValue(), MathUtil.EPSILON); - Ellipsoid ellipsoid = new Ellipsoid(xRadius.doubleValue(), yRadius.doubleValue(), zRadius.doubleValue()); - ellipsoid.setStyle(style); - return new Ellipsoid[]{ellipsoid}; + Ellipsoid shape = new Ellipsoid(xRadius.doubleValue(), yRadius.doubleValue(), zRadius.doubleValue()); + shape.getPointSampler().setStyle(style); + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -96,8 +99,8 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return Ellipsoid.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipticalArc.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipticalArc.java similarity index 84% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipticalArc.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipticalArc.java index a3de25f..29ef979 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipticalArc.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprEllipticalArc.java @@ -11,8 +11,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.EllipticalArc; -import com.sovdee.skriptparticles.shapes.Shape.Style; +import com.sovdee.shapes.shapes.EllipticalArc; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -30,10 +32,10 @@ "set {_shape} to a elliptical cylinder with radii 3 and 5, height 10, and cutoff angle of 3.1415 radians" }) @Since("1.0.0") -public class ExprEllipticalArc extends SimpleExpression { +public class ExprEllipticalArc extends SimpleExpression { static { - Skript.registerExpression(ExprEllipticalArc.class, EllipticalArc.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprEllipticalArc.class, Shape.class, ExpressionType.COMBINED, "[an] elliptical (arc|:sector) (with|of) radi(i|us) %number%(,| and) %number%[,] and [cutoff] angle [of] %number% [degrees|:radians]", "[an] elliptical [cylindrical] (arc|:sector) (with|of) radi(i|us) %number%(,| and) %number%(,| and) height %-number%[,] and [cutoff] angle [of] %number% [degrees|:radians]"); } @@ -42,7 +44,7 @@ public class ExprEllipticalArc extends SimpleExpression { private Expression zRadius; private Expression height; private Expression angle; - private Style style; + private SamplingStyle style; private boolean isRadians; @Override @@ -84,14 +86,14 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } } - style = parseResult.hasTag("sector") ? Style.FILL : Style.OUTLINE; + style = parseResult.hasTag("sector") ? SamplingStyle.FILL : SamplingStyle.OUTLINE; isRadians = parseResult.hasTag("radians"); return true; } @Override @Nullable - protected EllipticalArc[] get(Event event) { + protected Shape[] get(Event event) { Number xRadius = this.xRadius.getSingle(event); Number zRadius = this.zRadius.getSingle(event); Number height = this.height == null ? 0 : this.height.getSingle(event); @@ -106,9 +108,10 @@ protected EllipticalArc[] get(Event event) { angle = Math.toRadians(angle.doubleValue()); angle = MathUtil.clamp(angle.doubleValue(), 0, Math.PI * 2); - EllipticalArc arc = new EllipticalArc(xRadius.doubleValue(), zRadius.doubleValue(), height.doubleValue(), angle.doubleValue()); - arc.setStyle(style); - return new EllipticalArc[]{arc}; + EllipticalArc shape = new EllipticalArc(xRadius.doubleValue(), zRadius.doubleValue(), height.doubleValue(), angle.doubleValue()); + shape.getPointSampler().setStyle(style); + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -117,13 +120,13 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return EllipticalArc.class; + public Class getReturnType() { + return Shape.class; } @Override public String toString(@Nullable Event event, boolean debug) { - return (this.style == Style.FILL ? "sector" : "arc") + + return (this.style == SamplingStyle.FILL ? "sector" : "arc") + " with radius " + xRadius.toString(event, debug) + " and " + zRadius.toString(event, debug) + (height == null ? "" : " and height " + height.toString(event, debug)) + " and angle " + angle.toString(event, debug) + diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHeart.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHeart.java similarity index 85% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHeart.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHeart.java index e387a5c..c807907 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHeart.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHeart.java @@ -11,8 +11,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Heart; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Heart; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -30,10 +32,10 @@ "draw the shape of a heart of width 2 and length 2 at player" }) @Since("1.0.1") -public class ExprHeart extends SimpleExpression { +public class ExprHeart extends SimpleExpression { static { - Skript.registerExpression(ExprHeart.class, Heart.class, ExpressionType.COMBINED, "[a] [:solid] heart [shape] (with|of) width %number%[,] [and] length %number%[[,] [and] eccentricity %-number%]"); + Skript.registerExpression(ExprHeart.class, Shape.class, ExpressionType.COMBINED, "[a] [:solid] heart [shape] (with|of) width %number%[,] [and] length %number%[[,] [and] eccentricity %-number%]"); } private Expression width; @@ -71,7 +73,7 @@ public boolean init(Expression[] expressions, int i, Kleenean kleenean, Parse @Override @Nullable - protected Heart[] get(Event event) { + protected Shape[] get(Event event) { Number width = this.width.getSingle(event); Number length = this.length.getSingle(event); Number eccentricity = this.eccentricity == null ? 3 : this.eccentricity.getSingle(event); @@ -82,11 +84,12 @@ protected Heart[] get(Event event) { length = Math.max(length.doubleValue(), MathUtil.EPSILON); eccentricity = Math.max(eccentricity.doubleValue(), 1); - Heart heart = new Heart(width.doubleValue(), length.doubleValue(), eccentricity.doubleValue()); + Heart shape = new Heart(width.doubleValue(), length.doubleValue(), eccentricity.doubleValue()); if (isSolid) { - heart.setStyle(Shape.Style.SURFACE); + shape.getPointSampler().setStyle(SamplingStyle.SURFACE); } - return new Heart[]{heart}; + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -95,8 +98,8 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return Heart.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHelix.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHelix.java similarity index 83% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHelix.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHelix.java index 87ecbef..83c8cae 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHelix.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprHelix.java @@ -11,8 +11,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Helix; -import com.sovdee.skriptparticles.shapes.Shape.Style; +import com.sovdee.shapes.shapes.Helix; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.checkerframework.checker.nullness.qual.Nullable; @@ -29,10 +31,10 @@ "set {_shape} to a solid anti-clockwise helix with radius 3, height 5, winding rate 1" }) @Since("1.0.0") -public class ExprHelix extends SimpleExpression { +public class ExprHelix extends SimpleExpression { static { - Skript.registerExpression(ExprHelix.class, Helix.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprHelix.class, Shape.class, ExpressionType.COMBINED, "[a[n]] [:solid] [[1:(counter|anti)[-]]clockwise] (helix|spiral) (with|of) radius %number%[,] [and] height %number%[[,] [and] winding rate [of] %-number% [loops per (meter|block)]]"); } @@ -41,7 +43,7 @@ public class ExprHelix extends SimpleExpression { @Nullable private Expression windingRate; private boolean isClockwise = true; - private Style style; + private SamplingStyle style; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { @@ -50,7 +52,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye if (exprs.length > 2) windingRate = (Expression) exprs[2]; isClockwise = parseResult.mark == 0; - style = parseResult.hasTag("solid") ? Style.SURFACE : Style.OUTLINE; + style = parseResult.hasTag("solid") ? SamplingStyle.SURFACE : SamplingStyle.OUTLINE; if (radius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { Skript.error("The radius of a helix must be greater than 0. (radius: " + @@ -75,20 +77,21 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - protected Helix[] get(Event event) { + protected Shape[] get(Event event) { @Nullable Number radius = this.radius.getSingle(event); @Nullable Number height = this.height.getSingle(event); @Nullable Number windingRate = this.windingRate == null ? 1 : this.windingRate.getSingle(event); if (radius == null || height == null || windingRate == null) - return new Helix[0]; + return new Shape[0]; radius = Math.max(radius.doubleValue(), MathUtil.EPSILON); height = Math.max(height.doubleValue(), MathUtil.EPSILON); double slope = 1.0 / Math.max(windingRate.doubleValue(), MathUtil.EPSILON); int direction = isClockwise ? 1 : -1; - Helix helix = new Helix(radius.doubleValue(), height.doubleValue(), slope / (2 * Math.PI), direction); - helix.setStyle(style); - return new Helix[]{helix}; + Helix shape = new Helix(radius.doubleValue(), height.doubleValue(), slope / (2 * Math.PI), direction); + shape.getPointSampler().setStyle(style); + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -97,8 +100,8 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return Helix.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprIrregularPolygon.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprIrregularPolygon.java similarity index 73% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprIrregularPolygon.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprIrregularPolygon.java index c120a73..864fdc0 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprIrregularPolygon.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprIrregularPolygon.java @@ -11,12 +11,16 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.IrregularPolygon; +import com.sovdee.shapes.shapes.IrregularPolygon; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.DynamicLocation; +import com.sovdee.skriptparticles.util.VectorConversion; import org.bukkit.Location; import org.bukkit.event.Event; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; +import org.joml.Vector3d; import java.util.ArrayList; import java.util.List; @@ -33,10 +37,10 @@ "set {_shape} to a 2d polygon from points vector(0,0,1), vector(1,0,1), vector(0,0,-1) and height 0.5" }) @Since("1.0.0") -public class ExprIrregularPolygon extends SimpleExpression { +public class ExprIrregularPolygon extends SimpleExpression { static { - Skript.registerExpression(ExprIrregularPolygon.class, IrregularPolygon.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprIrregularPolygon.class, Shape.class, ExpressionType.COMBINED, "[a] [2d] polygon (from|with) [vertices|points] %vectors/locations% [and height %-number%]"); } @@ -61,31 +65,33 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - protected IrregularPolygon[] get(Event event) { + protected Shape[] get(Event event) { Object[] points = this.points.getArray(event); - List vertices = new ArrayList<>(points.length); + List vertices = new ArrayList<>(points.length); Vector locationOffset = null; for (Object point : points) { if (point instanceof Vector) { - vertices.add((Vector) point); + vertices.add(VectorConversion.toJOML((Vector) point)); } else if (point instanceof Location) { if (locationOffset == null) { locationOffset = ((Location) point).toVector(); } - vertices.add(((Location) point).toVector().subtract(locationOffset)); + Vector relative = ((Location) point).toVector().subtract(locationOffset); + vertices.add(VectorConversion.toJOML(relative)); } } Number height = this.height != null ? this.height.getSingle(event) : null; - IrregularPolygon polygon; + IrregularPolygon shape; if (height != null) { - polygon = new IrregularPolygon(vertices, height.doubleValue()); + shape = new IrregularPolygon(vertices, height.doubleValue()); } else { - polygon = new IrregularPolygon(vertices); + shape = new IrregularPolygon(vertices); } + shape.getPointSampler().setDrawContext(new DrawData()); if (locationOffset != null) { - polygon.setLocation(new DynamicLocation((Location) points[0])); + DrawData.of(shape).setLocation(new DynamicLocation((Location) points[0])); } - return new IrregularPolygon[]{polygon}; + return new Shape[]{shape}; } @Override @@ -94,8 +100,8 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return IrregularPolygon.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprLine.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprLine.java similarity index 78% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprLine.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprLine.java index 5c97264..0b9ba2d 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprLine.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprLine.java @@ -11,9 +11,12 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Line; +import com.sovdee.shapes.shapes.Line; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.DynamicLocation; import com.sovdee.skriptparticles.util.MathUtil; +import com.sovdee.skriptparticles.util.VectorConversion; import org.bukkit.event.Event; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; @@ -42,10 +45,10 @@ "draw the shape of a line connecting {_locations::*}" }) @Since("1.0.0") -public class ExprLine extends SimpleExpression { +public class ExprLine extends SimpleExpression { static { - Skript.registerExpression(ExprLine.class, Line.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprLine.class, Shape.class, ExpressionType.COMBINED, "[a] line[s] (from|between) %locations/entities/vectors% (to|and) %locations/entities/vectors%", "[a] line (in [the]|from) direction %vector% [(and|[and] with) length %number%]", "[a] line (between|connecting) %locations/entities/vectors%"); @@ -85,30 +88,37 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return true; } + private Shape attachDrawData(Shape shape) { + shape.getPointSampler().setDrawContext(new DrawData()); + return shape; + } + @Override @Nullable - protected Line[] get(Event event) { - List lines = new ArrayList<>(); + protected Shape[] get(Event event) { + List lines = new ArrayList<>(); switch (matchedPattern) { case 0 -> { Object[] start = this.start.getArray(event); Object[] end = this.end.getArray(event); - // if both are vectors, create a line from them for (Object startObject : start) { for (Object endObject : end) { if (startObject instanceof Vector startVector && endObject instanceof Vector endVector) { - lines.add(new Line(startVector, endVector)); + lines.add(attachDrawData(new Line(VectorConversion.toJOML(startVector), VectorConversion.toJOML(endVector)))); continue; } else if (startObject instanceof Vector || endObject instanceof Vector) { continue; } - // if neither are vectors, create a dynamic line DynamicLocation startPoint = DynamicLocation.fromLocationEntity(startObject); DynamicLocation endPoint = DynamicLocation.fromLocationEntity(endObject); if (endPoint == null || startPoint == null) { continue; } - lines.add(new Line(startPoint, endPoint)); + // Use Supplier-based Line for dynamic endpoints + lines.add(attachDrawData(new Line( + () -> VectorConversion.toJOML(startPoint.getLocation().toVector()), + () -> VectorConversion.toJOML(endPoint.getLocation().toVector()) + ))); } } } @@ -121,13 +131,13 @@ protected Line[] get(Event event) { if (length != null) v.multiply(Math.max(length.doubleValue(), MathUtil.EPSILON)); - lines.add(new Line(v)); + lines.add(attachDrawData(new Line(VectorConversion.toJOML(v)))); } case 2 -> { Object[] points = this.points.getArray(event); if (points instanceof Vector[] vectors) { for (int i = 0; i < vectors.length - 1; i++) { - lines.add(new Line(vectors[i], vectors[i + 1])); + lines.add(attachDrawData(new Line(VectorConversion.toJOML(vectors[i]), VectorConversion.toJOML(vectors[i + 1])))); } } else { List locations = new ArrayList<>(); @@ -140,7 +150,12 @@ protected Line[] get(Event event) { locations.add(location); } for (int i = 0; i < locations.size() - 1; i++) { - lines.add(new Line(locations.get(i), locations.get(i + 1))); + DynamicLocation startLoc = locations.get(i); + DynamicLocation endLoc = locations.get(i + 1); + lines.add(attachDrawData(new Line( + () -> VectorConversion.toJOML(startLoc.getLocation().toVector()), + () -> VectorConversion.toJOML(endLoc.getLocation().toVector()) + ))); } } } @@ -148,7 +163,7 @@ protected Line[] get(Event event) { return null; } } - return lines.toArray(new Line[0]); + return lines.toArray(new Shape[0]); } @Override @@ -161,8 +176,8 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return Line.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRectangle.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRectangle.java similarity index 71% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRectangle.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRectangle.java index 025741a..6bebf24 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRectangle.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRectangle.java @@ -10,10 +10,14 @@ import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Rectangle; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Rectangle; +import com.sovdee.shapes.shapes.Rectangle.Plane; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.DynamicLocation; import com.sovdee.skriptparticles.util.MathUtil; +import com.sovdee.skriptparticles.util.VectorConversion; import org.bukkit.event.Event; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; @@ -36,10 +40,10 @@ "draw the shape of a rectangle from player to player's target" }) @Since("1.0.0") -public class ExprRectangle extends SimpleExpression { +public class ExprRectangle extends SimpleExpression { static { - Skript.registerExpression(ExprRectangle.class, Rectangle.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprRectangle.class, Shape.class, ExpressionType.COMBINED, "[a[n]] [solid:(solid|filled)] [:xz|:xy|:yz] rectangle (with|of) length %number% and width %number%", "[a[n]] [solid:(solid|filled)] [:xz|:xy|:yz] rectangle (from|with corners [at]) %location/entity/vector% (to|and) %location/entity/vector%" ); @@ -47,15 +51,15 @@ public class ExprRectangle extends SimpleExpression { private Expression lengthExpr; private Expression widthExpr; - private Shape.Style style; + private SamplingStyle style; private Expression corner1Expr; private Expression corner2Expr; private int matchedPattern; - private Rectangle.Plane plane; + private Plane plane; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { - style = parseResult.hasTag("solid") ? Shape.Style.SURFACE : Shape.Style.OUTLINE; + style = parseResult.hasTag("solid") ? SamplingStyle.SURFACE : SamplingStyle.OUTLINE; this.matchedPattern = matchedPattern; if (matchedPattern == 0) { lengthExpr = (Expression) exprs[0]; @@ -64,15 +68,15 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye corner1Expr = exprs[0]; corner2Expr = exprs[1]; } - plane = Rectangle.Plane.XZ; - if (parseResult.hasTag("xy")) plane = Rectangle.Plane.XY; - else if (parseResult.hasTag("yz")) plane = Rectangle.Plane.YZ; + plane = Plane.XZ; + if (parseResult.hasTag("xy")) plane = Plane.XY; + else if (parseResult.hasTag("yz")) plane = Plane.YZ; return true; } @Override - protected @Nullable Rectangle[] get(Event event) { - Rectangle rectangle; + protected @Nullable Shape[] get(Event event) { + Shape shape; if (matchedPattern == 0) { if (lengthExpr == null || widthExpr == null) return null; Number length = lengthExpr.getSingle(event); @@ -80,31 +84,33 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye if (length == null || width == null) return null; length = Math.max(length.doubleValue(), MathUtil.EPSILON); width = Math.max(width.doubleValue(), MathUtil.EPSILON); - rectangle = new Rectangle(length.doubleValue(), width.doubleValue(), plane); + shape = new Rectangle(length.doubleValue(), width.doubleValue(), plane); } else { if (corner1Expr == null || corner2Expr == null) return null; Object corner1 = corner1Expr.getSingle(event); Object corner2 = corner2Expr.getSingle(event); if (corner1 == null || corner2 == null) return null; - // vector check if (corner1 instanceof Vector && corner2 instanceof Vector) { - // if both are vectors, create a static rectangle - rectangle = new Rectangle((Vector) corner1, (Vector) corner2, plane); + shape = new Rectangle(VectorConversion.toJOML((Vector) corner1), VectorConversion.toJOML((Vector) corner2), plane); } else if (corner1 instanceof Vector || corner2 instanceof Vector) { - // if only one is a vector, return empty array return null; } else { - // if neither are vectors, create a dynamic rectangle - corner1 = DynamicLocation.fromLocationEntity(corner1); - corner2 = DynamicLocation.fromLocationEntity(corner2); - if (corner1 == null || corner2 == null) + DynamicLocation dl1 = DynamicLocation.fromLocationEntity(corner1); + DynamicLocation dl2 = DynamicLocation.fromLocationEntity(corner2); + if (dl1 == null || dl2 == null) return null; - rectangle = new Rectangle((DynamicLocation) corner1, (DynamicLocation) corner2, plane); + // Use Supplier-based Rectangle for dynamic corners + shape = new Rectangle( + () -> VectorConversion.toJOML(dl1.getLocation().toVector()), + () -> VectorConversion.toJOML(dl2.getLocation().toVector()), + plane + ); } } - rectangle.setStyle(style); - return new Rectangle[]{rectangle}; + shape.getPointSampler().setStyle(style); + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -113,13 +119,13 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return Rectangle.class; + public Class getReturnType() { + return Shape.class; } @Override public String toString(@Nullable Event event, boolean debug) { - return (style == Shape.Style.SURFACE ? "solid " : "") + + return (style == SamplingStyle.SURFACE ? "solid " : "") + switch (plane) { case XZ -> " xz "; case XY -> " xy "; diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolygon.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolygon.java similarity index 84% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolygon.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolygon.java index 720a709..5254a76 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolygon.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolygon.java @@ -11,8 +11,10 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.RegularPolygon; -import com.sovdee.skriptparticles.shapes.Shape.Style; +import com.sovdee.shapes.shapes.RegularPolygon; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -27,12 +29,12 @@ "set {_shape} to a solid regular polygon with 6 sides and side length 3", "draw the shape of a triangle with side length 5 at player" }) -public class ExprRegularPolygon extends SimpleExpression { +public class ExprRegularPolygon extends SimpleExpression { // TODO: add ExprRegularPrism static { - Skript.registerExpression(ExprRegularPolygon.class, RegularPolygon.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprRegularPolygon.class, Shape.class, ExpressionType.COMBINED, "[a] [solid:(solid|filled)] regular polygon with %number% sides and radius %number%", "[a] [solid:(solid|filled)] regular polygon with %number% sides and side length %number%", "[a[n]] [solid:(solid|filled)] (3:[equilateral ]triangle|4:square|5:pentagon|6:hexagon|7:heptagon|8:octagon) with radius %number%", @@ -43,12 +45,12 @@ public class ExprRegularPolygon extends SimpleExpression { private Expression radius; private Expression sideLength; private Expression sides; - private Style style; + private SamplingStyle style; private int matchedPattern; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - style = parseResult.hasTag("solid") ? Style.SURFACE : Style.OUTLINE; + style = parseResult.hasTag("solid") ? SamplingStyle.SURFACE : SamplingStyle.OUTLINE; this.matchedPattern = matchedPattern; switch (matchedPattern) { case 0 -> { @@ -89,7 +91,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - protected RegularPolygon[] get(Event event) { + protected Shape[] get(Event event) { Number sides = this.sides.getSingle(event); if (sides == null) return null; @@ -109,9 +111,10 @@ protected RegularPolygon[] get(Event event) { sides = Math.max(sides.intValue(), 3); radius = Math.max(radius.doubleValue(), MathUtil.EPSILON); - RegularPolygon polygon = new RegularPolygon(sides.intValue(), radius.doubleValue()); - polygon.setStyle(style); - return new RegularPolygon[]{polygon}; + RegularPolygon shape = new RegularPolygon(sides.intValue(), radius.doubleValue()); + shape.getPointSampler().setStyle(style); + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -120,13 +123,13 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return RegularPolygon.class; + public Class getReturnType() { + return Shape.class; } @Override public String toString(@Nullable Event event, boolean debug) { - return (style == Style.SURFACE ? "filled" : "outlined") + + return (style == SamplingStyle.SURFACE ? "filled" : "outlined") + switch (matchedPattern) { case 0, 2 -> " regular polygon with " + sides.toString(event, debug) + " sides and radius " + radius.toString(event, debug); diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolyhedron.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolyhedron.java similarity index 67% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolyhedron.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolyhedron.java index 59b57af..a00c78f 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolyhedron.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprRegularPolyhedron.java @@ -10,9 +10,10 @@ import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.RegularPolyhedron; -import com.sovdee.skriptparticles.shapes.Shape; -import com.sovdee.skriptparticles.shapes.Shape.Style; +import com.sovdee.shapes.shapes.RegularPolyhedron; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.skriptparticles.shapes.DrawData; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -28,21 +29,21 @@ "set {_shape} to a solid icosahedron with radius 2", "draw the shape of a tetrahedron with radius 5 at player" }) -public class ExprRegularPolyhedron extends SimpleExpression { +public class ExprRegularPolyhedron extends SimpleExpression { static { - Skript.registerExpression(ExprRegularPolyhedron.class, RegularPolyhedron.class, ExpressionType.COMBINED, "[a[n]] [outlined|:hollow|:solid] (:tetra|:octa|:icosa|:dodeca)hedron (with|of) radius %number%"); + Skript.registerExpression(ExprRegularPolyhedron.class, Shape.class, ExpressionType.COMBINED, "[a[n]] [outlined|:hollow|:solid] (:tetra|:octa|:icosa|:dodeca)hedron (with|of) radius %number%"); } private Expression radius; private int faces; - private Style style; + private SamplingStyle style; @Override public boolean init(Expression[] expressions, int i, Kleenean kleenean, SkriptParser.ParseResult parseResult) { radius = (Expression) expressions[0]; faces = parseResult.hasTag("tetra") ? 4 : parseResult.hasTag("octa") ? 8 : parseResult.hasTag("dodeca") ? 12 : 20; - style = parseResult.hasTag("hollow") ? Shape.Style.SURFACE : parseResult.hasTag("solid") ? Shape.Style.FILL : Shape.Style.OUTLINE; + style = parseResult.hasTag("hollow") ? SamplingStyle.SURFACE : parseResult.hasTag("solid") ? SamplingStyle.FILL : SamplingStyle.OUTLINE; if (radius instanceof Literal literal && literal.getSingle().doubleValue() <= 0) { Skript.error("The radius of the polyhedron must be greater than 0. (radius: " + @@ -54,12 +55,13 @@ public boolean init(Expression[] expressions, int i, Kleenean kleenean, Skrip } @Override - protected @Nullable RegularPolyhedron[] get(Event event) { + protected @Nullable Shape[] get(Event event) { if (radius.getSingle(event) == null) - return new RegularPolyhedron[0]; - RegularPolyhedron regularPolyhedron = new RegularPolyhedron(radius.getSingle(event).doubleValue(), faces); - regularPolyhedron.setStyle(style); - return new RegularPolyhedron[]{regularPolyhedron}; + return new Shape[0]; + RegularPolyhedron shape = new RegularPolyhedron(radius.getSingle(event).doubleValue(), faces); + shape.getPointSampler().setStyle(style); + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -68,8 +70,8 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return RegularPolyhedron.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphere.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphere.java similarity index 74% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphere.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphere.java index c6d601b..7de571a 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphere.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphere.java @@ -11,8 +11,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Shape; -import com.sovdee.skriptparticles.shapes.Sphere; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.shapes.shapes.Sphere; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.jetbrains.annotations.NotNull; @@ -28,10 +30,10 @@ "set {_shape} to solid sphere with radius 10" }) @Since("1.0.0") -public class ExprSphere extends SimpleExpression { +public class ExprSphere extends SimpleExpression { static { - Skript.registerExpression(ExprSphere.class, Sphere.class, ExpressionType.COMBINED, "[a] [:solid] sphere (with|of) radius %number%"); + Skript.registerExpression(ExprSphere.class, Shape.class, ExpressionType.COMBINED, "[a] [:solid] sphere (with|of) radius %number%"); } private Expression radius; @@ -49,16 +51,17 @@ public boolean init(Expression[] exprs, int matchedPattern, @NotNull Kleenean } @Override - protected Sphere[] get(@NotNull Event event) { + protected Shape[] get(@NotNull Event event) { Number radius = this.radius.getSingle(event); if (radius == null) return null; radius = Math.max(radius.doubleValue(), MathUtil.EPSILON); - Sphere sphere = new Sphere(radius.doubleValue()); - if (isSolid) sphere.setStyle(Shape.Style.FILL); - return new Sphere[]{sphere}; + Sphere shape = new Sphere(radius.doubleValue()); + if (isSolid) shape.getPointSampler().setStyle(SamplingStyle.FILL); + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -68,8 +71,8 @@ public boolean isSingle() { @Override @NotNull - public Class getReturnType() { - return Sphere.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphericalCap.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphericalCap.java similarity index 81% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphericalCap.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphericalCap.java index 5e616cc..81f38a9 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphericalCap.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprSphericalCap.java @@ -11,8 +11,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Shape; -import com.sovdee.skriptparticles.shapes.SphericalCap; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.shapes.shapes.SphericalCap; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -28,10 +30,10 @@ "set {_shape} to a spherical sector of radius 3 and angle 90 degrees" }) @Since("1.0.0") -public class ExprSphericalCap extends SimpleExpression { +public class ExprSphericalCap extends SimpleExpression { static { - Skript.registerExpression(ExprSphericalCap.class, SphericalCap.class, ExpressionType.COMBINED, + Skript.registerExpression(ExprSphericalCap.class, Shape.class, ExpressionType.COMBINED, "[a] spherical (cap|:sector) (with|of) radius %number% and [cutoff] angle [of] %number% [degrees|:radians]"); } @@ -65,7 +67,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - protected SphericalCap[] get(Event event) { + protected Shape[] get(Event event) { Number radius = this.radius.getSingle(event); Number angle = this.angle.getSingle(event); if (radius == null || angle == null) @@ -75,11 +77,12 @@ protected SphericalCap[] get(Event event) { if (!isRadians) angle = Math.toRadians(angle.doubleValue()); - SphericalCap cap = new SphericalCap(radius.doubleValue(), angle.doubleValue()); + SphericalCap shape = new SphericalCap(radius.doubleValue(), angle.doubleValue()); if (isSector) - cap.setStyle(Shape.Style.FILL); + shape.getPointSampler().setStyle(SamplingStyle.FILL); + shape.getPointSampler().setDrawContext(new DrawData()); - return new SphericalCap[]{cap}; + return new Shape[]{shape}; } @Override @@ -88,8 +91,8 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return SphericalCap.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprStar.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprStar.java similarity index 80% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprStar.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprStar.java index 213d3b8..4772343 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprStar.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/constructors/ExprStar.java @@ -10,8 +10,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Shape; -import com.sovdee.skriptparticles.shapes.Star; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.shapes.shapes.Star; +import com.sovdee.skriptparticles.shapes.DrawData; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -25,10 +27,10 @@ "set {_shape} to star with 5 points, inner radius 1, and outer radius 2", "draw the shape of a star with 4 points, inner radius 2, and outer radius 4 at player" }) -public class ExprStar extends SimpleExpression { +public class ExprStar extends SimpleExpression { static { - Skript.registerExpression(ExprStar.class, Star.class, ExpressionType.COMBINED, "[a] [:solid] star with %number% points(,| and) inner radius %number%[,] and outer radius %number%"); + Skript.registerExpression(ExprStar.class, Shape.class, ExpressionType.COMBINED, "[a] [:solid] star with %number% points(,| and) inner radius %number%[,] and outer radius %number%"); } private Expression points; @@ -64,7 +66,7 @@ public boolean init(Expression[] expressions, int i, Kleenean kleenean, Parse @Override @Nullable - protected Star[] get(Event event) { + protected Shape[] get(Event event) { Number points = this.points.getSingle(event); Number innerRadius = this.innerRadius.getSingle(event); Number outerRadius = this.outerRadius.getSingle(event); @@ -75,11 +77,11 @@ protected Star[] get(Event event) { innerRadius = Math.max(innerRadius.doubleValue(), MathUtil.EPSILON); outerRadius = Math.max(outerRadius.doubleValue(), MathUtil.EPSILON); - Star star = new Star(innerRadius.doubleValue(), outerRadius.doubleValue(), angle); + Star shape = new Star(innerRadius.doubleValue(), outerRadius.doubleValue(), angle); if (isSolid) - star.setStyle(Shape.Style.SURFACE); - - return new Star[]{star}; + shape.getPointSampler().setStyle(SamplingStyle.SURFACE); + shape.getPointSampler().setDrawContext(new DrawData()); + return new Shape[]{shape}; } @Override @@ -88,8 +90,8 @@ public boolean isSingle() { } @Override - public Class getReturnType() { - return Star.class; + public Class getReturnType() { + return Shape.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprHelixWindingRate.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprHelixWindingRate.java similarity index 96% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprHelixWindingRate.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprHelixWindingRate.java index 0ddb67f..d1a501c 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprHelixWindingRate.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprHelixWindingRate.java @@ -6,8 +6,8 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import com.sovdee.skriptparticles.shapes.Helix; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Helix; +import com.sovdee.shapes.shapes.Shape; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeCutoffAngle.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeCutoffAngle.java similarity index 83% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeCutoffAngle.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeCutoffAngle.java index bde824c..30b1373 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeCutoffAngle.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeCutoffAngle.java @@ -6,7 +6,7 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import com.sovdee.skriptparticles.shapes.CutoffShape; +import com.sovdee.shapes.shapes.CutoffShape; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -29,8 +29,8 @@ public class ExprShapeCutoffAngle extends SimplePropertyExpression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - public Number convert(LWHShape lwhShape) { + public Number convert(LWHShape shape) { return switch (lwh) { - case 0 -> lwhShape.getLength(); - case 1 -> lwhShape.getWidth(); - case 2 -> lwhShape.getHeight(); + case 0 -> shape.getLength(); + case 1 -> shape.getWidth(); + case 2 -> shape.getHeight(); default -> null; }; } diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeLocations.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeLocations.java similarity index 91% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeLocations.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeLocations.java index 62e3636..72b56f2 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeLocations.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeLocations.java @@ -11,7 +11,8 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.Direction; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.skriptparticles.util.VectorConversion; import org.bukkit.Location; import org.bukkit.event.Event; import org.checkerframework.checker.nullness.qual.NonNull; @@ -61,7 +62,7 @@ protected Location[] get(Event event) { ArrayList locations = new ArrayList<>(); for (Shape shape : shapes) { - locations.addAll(shape.getPoints().stream().map(point -> center.clone().add(point)).toList()); + locations.addAll(VectorConversion.toBukkit(shape.getPointSampler().getPoints(shape)).stream().map(point -> center.clone().add(point)).toList()); } return locations.toArray(new Location[0]); } diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeNormal.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeNormal.java similarity index 85% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeNormal.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeNormal.java index c74ad03..96f4b97 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeNormal.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeNormal.java @@ -7,8 +7,10 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; import com.sovdee.skriptparticles.util.Quaternion; +import com.sovdee.skriptparticles.util.VectorConversion; +import org.joml.Quaterniond; import org.bukkit.event.Event; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; @@ -33,7 +35,7 @@ public class ExprShapeNormal extends SimplePropertyExpression { @Override public Vector convert(Shape shape) { - return shape.getRelativeYAxis(false); + return VectorConversion.toBukkit(shape.getRelativeYAxis()); } @Override @@ -50,14 +52,15 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { switch (mode) { case SET: if (delta == null || delta.length == 0) return; + Quaternion q = new Quaternion().rotationTo((Vector) delta[0]); for (Shape shape : getExpr().getArray(event)) { - shape.setOrientation(new Quaternion().rotationTo((Vector) delta[0])); + shape.setOrientation(new Quaterniond(q.x, q.y, q.z, q.w)); } break; case RESET: case DELETE: for (Shape shape : getExpr().getArray(event)) { - shape.setOrientation(Quaternion.IDENTITY); + shape.setOrientation(new Quaterniond()); } break; case ADD: diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOffset.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOffset.java similarity index 87% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOffset.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOffset.java index 5a4ec56..d92597c 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOffset.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOffset.java @@ -6,9 +6,11 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.skriptparticles.util.VectorConversion; import org.bukkit.event.Event; import org.bukkit.util.Vector; +import org.joml.Vector3d; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,7 +32,7 @@ public class ExprShapeOffset extends SimplePropertyExpression { @Override public @Nullable Vector convert(Shape shape) { - return shape.getOffset(); + return VectorConversion.toBukkit(shape.getOffset()); } @Override @@ -49,13 +51,13 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { if (delta == null || delta.length == 0) return; for (Shape shape : getExpr().getArray(event)) { - shape.setOffset(((Vector) delta[0])); + shape.setOffset(VectorConversion.toJOML((Vector) delta[0])); } break; case RESET: case DELETE: for (Shape shape : getExpr().getArray(event)) { - shape.setOffset(new Vector(0, 0, 0)); + shape.setOffset(new Vector3d(0, 0, 0)); } break; case ADD: diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOrientation.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOrientation.java similarity index 87% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOrientation.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOrientation.java index efe5ed6..4eaeda5 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOrientation.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeOrientation.java @@ -7,8 +7,9 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; import com.sovdee.skriptparticles.util.Quaternion; +import org.joml.Quaterniond; import org.bukkit.event.Event; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -35,7 +36,8 @@ public class ExprShapeOrientation extends SimplePropertyExpression @Override @Nullable public Particle convert(Shape shape) { - return shape.getParticle(); + return DrawData.of(shape).getParticle(); } @Override @@ -59,12 +59,12 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { case SET: if (delta == null || delta.length == 0) return; for (Shape shape : getExpr().getArray(event)) - shape.setParticle((Particle) delta[0]); + DrawData.of(shape).setParticle((Particle) delta[0]); break; case RESET: case DELETE: for (Shape shape : getExpr().getArray(event)) - shape.setParticle(ParticleUtil.getDefaultParticle()); + DrawData.of(shape).setParticle(ParticleUtil.getDefaultParticle()); break; case ADD: case REMOVE: diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeParticleDensity.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeParticleDensity.java similarity index 85% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeParticleDensity.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeParticleDensity.java index c5bb4f2..59661cc 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeParticleDensity.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeParticleDensity.java @@ -10,7 +10,8 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.sampling.PointSampler; +import com.sovdee.shapes.shapes.Shape; import com.sovdee.skriptparticles.util.MathUtil; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -54,10 +55,13 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override public @Nullable Number convert(Shape shape) { + PointSampler sampler = shape.getPointSampler(); if (isDensity) - return 1 / shape.getParticleDensity(); - else - return shape.getParticleCount(); + return 1 / sampler.getDensity(); + else { + // Return approximate point count based on current density + return (int) Math.round(1 / sampler.getDensity()); + } } @Override @@ -80,12 +84,13 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { change = -change; case ADD: for (Shape shape : shapes) { + PointSampler sampler = shape.getPointSampler(); if (isDensity) { // clamp to 0.001 and 1000, enough to kill the client but not enough to cause an actual error - shape.setParticleDensity(MathUtil.clamp(1 / (1 / shape.getParticleDensity() + change), 0.001, 1000)); + sampler.setDensity(MathUtil.clamp(1 / (1 / sampler.getDensity() + change), 0.001, 1000)); } else // clamp to 1, the minimum amount of particles - shape.setParticleCount(Math.max(1, shape.getParticleCount() + (int) change)); + sampler.setParticleCount(shape, Math.max(1, (int) (1 / sampler.getDensity()) + (int) change)); } break; case DELETE: @@ -93,10 +98,11 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { case SET: change = isDensity ? MathUtil.clamp(1 / change, 0.001, 1000) : Math.max(1, (int) change); for (Shape shape : shapes) { + PointSampler sampler = shape.getPointSampler(); if (isDensity) { - shape.setParticleDensity(change); + sampler.setDensity(change); } else { - shape.setParticleCount((int) change); + sampler.setParticleCount(shape, (int) change); } } break; diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapePoints.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapePoints.java similarity index 91% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapePoints.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapePoints.java index c91e3ea..dff9e4a 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapePoints.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapePoints.java @@ -8,7 +8,8 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.skriptparticles.util.VectorConversion; import org.bukkit.event.Event; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; @@ -44,7 +45,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye protected Vector[] get(Event event, Shape[] source) { List points = new ArrayList<>(); for (Shape shape : source) { - points.addAll(shape.getPoints()); + points.addAll(VectorConversion.toBukkit(shape.getPointSampler().getPoints(shape))); } return points.toArray(new Vector[0]); } diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRadius.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRadius.java similarity index 90% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRadius.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRadius.java index f3cda5e..53e633e 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRadius.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRadius.java @@ -7,7 +7,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import com.sovdee.skriptparticles.shapes.RadialShape; +import com.sovdee.shapes.shapes.RadialShape; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -46,8 +46,8 @@ public Class[] acceptChange(ChangeMode mode) { @Override public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { - RadialShape[] radialShapes = getExpr().getArray(event); - if (radialShapes.length == 0) + RadialShape[] shapes = getExpr().getArray(event); + if (shapes.length == 0) return; double deltaValue = (delta != null) ? ((Number) delta[0]).doubleValue() : 1; @@ -55,7 +55,7 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { case REMOVE: deltaValue = -deltaValue; case ADD: - for (RadialShape shape : radialShapes) { + for (RadialShape shape : shapes) { shape.setRadius(Math.max(0.001, shape.getRadius() + deltaValue)); } break; @@ -63,7 +63,7 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { case DELETE: case SET: deltaValue = Math.max(0.001, deltaValue); - for (RadialShape shape : radialShapes) { + for (RadialShape shape : shapes) { shape.setRadius(deltaValue); } break; diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRelativeAxis.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRelativeAxis.java similarity index 86% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRelativeAxis.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRelativeAxis.java index c856f67..cf3c0ff 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRelativeAxis.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeRelativeAxis.java @@ -9,7 +9,8 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.skriptparticles.util.VectorConversion; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; @@ -44,9 +45,9 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override public @Nullable Vector convert(Shape shape) { return switch (axis) { - case 0 -> shape.getRelativeXAxis(false); - case 1 -> shape.getRelativeYAxis(false); - case 2 -> shape.getRelativeZAxis(false); + case 0 -> VectorConversion.toBukkit(shape.getRelativeXAxis()); + case 1 -> VectorConversion.toBukkit(shape.getRelativeYAxis()); + case 2 -> VectorConversion.toBukkit(shape.getRelativeZAxis()); default -> null; }; } diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeScale.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeScale.java similarity index 98% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeScale.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeScale.java index 5ccc8e5..89beadb 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeScale.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeScale.java @@ -7,7 +7,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeSideLength.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeSideLength.java similarity index 84% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeSideLength.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeSideLength.java index 5222127..7f7c56e 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeSideLength.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeSideLength.java @@ -6,7 +6,7 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import com.sovdee.skriptparticles.shapes.PolyShape; +import com.sovdee.shapes.shapes.PolyShape; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -31,8 +31,8 @@ public class ExprShapeSideLength extends SimplePropertyExpression @Override @Nullable - public Integer convert(PolyShape polyShape) { - return polyShape.getSides(); + public Integer convert(PolyShape shape) { + return shape.getSides(); } @Override @@ -52,16 +52,16 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { case REMOVE: change = -change; case ADD: - for (PolyShape polyShape : getExpr().getArray(event)) { - polyShape.setSides(Math.max(3, polyShape.getSides() + change)); + for (PolyShape shape : getExpr().getArray(event)) { + shape.setSides(Math.max(3, shape.getSides() + change)); } break; case RESET: case DELETE: case SET: change = Math.max(3, change); - for (PolyShape polyShape : getExpr().getArray(event)) { - polyShape.setSides(change); + for (PolyShape shape : getExpr().getArray(event)) { + shape.setSides(change); } break; case REMOVE_ALL: diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeStyle.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeStyle.java similarity index 72% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeStyle.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeStyle.java index e4f27ec..f90c45a 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeStyle.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprShapeStyle.java @@ -7,7 +7,8 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.shapes.shapes.Shape; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -22,23 +23,23 @@ "set style of {_shape} to hollow" }) @Since("1.0.0") -public class ExprShapeStyle extends SimplePropertyExpression { +public class ExprShapeStyle extends SimplePropertyExpression { static { - PropertyExpression.register(ExprShapeStyle.class, Shape.Style.class, "style", "shapes"); + PropertyExpression.register(ExprShapeStyle.class, SamplingStyle.class, "style", "shapes"); } @Override @Nullable - public Shape.Style convert(Shape shape) { - return shape.getStyle(); + public SamplingStyle convert(Shape shape) { + return shape.getPointSampler().getStyle(); } @Override @Nullable public Class[] acceptChange(ChangeMode mode) { return switch (mode) { - case SET -> new Class[]{Shape.Style.class}; + case SET -> new Class[]{SamplingStyle.class}; case ADD, REMOVE, REMOVE_ALL, DELETE, RESET -> new Class[0]; }; } @@ -46,15 +47,15 @@ public Class[] acceptChange(ChangeMode mode) { @Override public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { if (delta == null || delta.length != 1) return; - Shape.Style style = (Shape.Style) delta[0]; + SamplingStyle style = (SamplingStyle) delta[0]; for (Shape shape : getExpr().getArray(event)) { - shape.setStyle(style); + shape.getPointSampler().setStyle(style); } } @Override - public Class getReturnType() { - return Shape.Style.class; + public Class getReturnType() { + return SamplingStyle.class; } @Override diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarPoints.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarPoints.java similarity index 93% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarPoints.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarPoints.java index 9e493e3..94f30eb 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarPoints.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarPoints.java @@ -6,8 +6,8 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import com.sovdee.skriptparticles.shapes.Shape; -import com.sovdee.skriptparticles.shapes.Star; +import com.sovdee.shapes.shapes.Star; +import com.sovdee.shapes.shapes.Shape; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -30,8 +30,8 @@ public class ExprStarPoints extends SimplePropertyExpression { @Override public @Nullable Number convert(Shape shape) { - if (shape instanceof Star) - return ((Star) shape).getStarPoints(); + if (shape instanceof Star star) + return star.getStarPoints(); return null; } diff --git a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarRadii.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarRadii.java similarity index 91% rename from src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarRadii.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarRadii.java index 1bb94a9..5108987 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarRadii.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/expressions/properties/ExprStarRadii.java @@ -9,9 +9,8 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -import com.sovdee.skriptparticles.shapes.Shape; -import com.sovdee.skriptparticles.shapes.Star; -import com.sovdee.skriptparticles.util.MathUtil; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.shapes.shapes.Star; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -71,15 +70,15 @@ public void change(Event event, Object @Nullable [] delta, Changer.ChangeMode mo for (Shape shape : shapes) { if (shape instanceof Star star) { if (isInner) { - star.setInnerRadius(Math.max(star.getInnerRadius() + deltaValue, MathUtil.EPSILON)); + star.setInnerRadius(Math.max(star.getInnerRadius() + deltaValue, Shape.EPSILON)); } else { - star.setOuterRadius(Math.max(star.getOuterRadius() + deltaValue, MathUtil.EPSILON)); + star.setOuterRadius(Math.max(star.getOuterRadius() + deltaValue, Shape.EPSILON)); } } } break; case SET: - deltaValue = Math.max(deltaValue, MathUtil.EPSILON); + deltaValue = Math.max(deltaValue, Shape.EPSILON); for (Shape shape : shapes) { if (shape instanceof Star star) { if (isInner) { diff --git a/src/main/java/com/sovdee/skriptparticles/elements/package-info.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/package-info.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/elements/package-info.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/package-info.java diff --git a/src/main/java/com/sovdee/skriptparticles/elements/sections/DrawShapeEffectSection.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/sections/DrawShapeEffectSection.java similarity index 68% rename from src/main/java/com/sovdee/skriptparticles/elements/sections/DrawShapeEffectSection.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/sections/DrawShapeEffectSection.java index 75fd27d..370bb28 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/sections/DrawShapeEffectSection.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/sections/DrawShapeEffectSection.java @@ -8,15 +8,15 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Trigger; import ch.njol.skript.lang.TriggerItem; -import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.registrations.EventValues; import ch.njol.skript.util.Direction; import ch.njol.skript.util.Timespan; import ch.njol.skript.util.Timespan.TimePeriod; import ch.njol.skript.variables.Variables; import ch.njol.util.Kleenean; +import com.sovdee.shapes.shapes.Shape; import com.sovdee.skriptparticles.SkriptParticle; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.skriptparticles.shapes.DrawManager; import com.sovdee.skriptparticles.util.DynamicLocation; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -55,23 +55,8 @@ public abstract class DrawShapeEffectSection extends EffectSection { protected boolean useShapeLocation; protected boolean sync; - /** - * Called just after the constructor. Handles checking for delays in the section body and setting the sync tag, then - * passes execution to {@link #init(Expression[], int, Kleenean, ParseResult, boolean)}. - * - * @param expressions all %expr%s included in the matching pattern in the order they appear in the pattern. If an optional value was left out it will still be included in this list - * holding the default value of the desired type which usually depends on the event. - * @param matchedPattern The index of the pattern which matched - * @param isDelayed Whether this expression is used after a delay or not (i.e. if the event has already passed when this expression will be called) - * @param parseResult Additional information about the match. - * @param sectionNode The section node that represents this section. - * @param list A list of {@link TriggerItem}s that belong to this section. This list is modifiable. - * @return Whether this expression was initialised successfully. An error should be printed prior to returning false to specify the cause. - * @see ParserInstance#isCurrentEvent(Class...) - */ @Override public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, @Nullable SectionNode sectionNode, @Nullable List list) { - // handle delays in the section body if (hasSection()) { AtomicBoolean delayed = new AtomicBoolean(false); Runnable afterLoading = () -> delayed.set(!getParser().getHasDelayBefore().isFalse()); @@ -85,22 +70,6 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is return init(expressions, matchedPattern, isDelayed, parseResult, hasSection()); } - /** - * Called just after the constructor. By default, this method does sets the following fields: - * - {@link #shapes} to the first expression - * - {@link #directions} to the second expression - * - {@link #locations} to the third expression - * - {@link #players} to the fourth expression - * - {@link #sync} to whether the sync tag was present - * - * @param expressions all %expr%s included in the matching pattern in the order they appear in the pattern. If an optional value was left out it will still be included in this list - * holding the default value of the desired type which usually depends on the event. - * @param matchedPattern The index of the pattern which matched - * @param isDelayed Whether this expression is used after a delay or not (i.e. if the event has already passed when this expression will be called) - * @param parseResult Additional information about the match. - * @param hasSection Whether this section had a valid section body. - * @return Whether this expression was initialised successfully. An error should be printed prior to returning false to specify the cause - */ public boolean init(@Nullable Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, boolean hasSection) { shapes = (Expression) expressions[0]; @@ -123,7 +92,7 @@ public boolean init(@Nullable Expression[] expressions, int matchedPattern, K protected TriggerItem walk(Event event) { debug(event, true); - Delay.addDelayedEvent(event); // Mark this event as delayed + Delay.addDelayedEvent(event); Collection recipients = new ArrayList<>(); if (players != null) { @@ -144,7 +113,6 @@ protected TriggerItem walk(Event event) { consumer = null; } - // Figure out what locations to draw at, or what entities to follow List locations = new ArrayList<>(); @Nullable Direction direction = null; if (!useShapeLocation) { @@ -159,15 +127,12 @@ protected TriggerItem walk(Event event) { } } } else { - // blank value means use shape location locations.add(new DynamicLocation()); } if (sync) { executeSync(event, locations, consumer, recipients); } else { - // Clone shapes and run Consumer before going async - // We can't guarantee that the consumer will be thread-safe, so we need do this before going async List preppedShapes = new ArrayList<>(); Shape preppedShape; for (Shape shape : shapes.getArray(event)) { @@ -184,13 +149,6 @@ protected TriggerItem walk(Event event) { return getNext(); } - /** - * Sets up the async task to draw the shapes at the given locations for the given recipients. - * Should be called from {@link #walk(Event)} if {@link #sync} is false, and will return the next trigger item to run. - * @param locations the locations to draw the shapes at - * @param shapes the shapes to draw - * @param recipients the players to draw the shapes for - */ protected void setupAsync(Event event, Collection locations, Collection shapes, Collection recipients) { BukkitRunnable runnable = new BukkitRunnable() { @Override @@ -202,14 +160,14 @@ public void run() { } protected void executeSync(Event event, Collection locations, @Nullable Consumer consumer, Collection recipients) { - Shape shapeCopy; try { for (DynamicLocation dynamicLocation : locations) { for (Shape shape : shapes.getArray(event)) { + Shape clone = shape.clone(); if (consumer != null) { - shape.clone().draw(dynamicLocation, consumer, recipients); + DrawManager.drawWithConsumer(clone, dynamicLocation, consumer, recipients); } else { - shape.clone().draw(dynamicLocation, recipients); + DrawManager.draw(clone, dynamicLocation, recipients); } } } @@ -226,7 +184,7 @@ protected void executeAsync(Collection locations, Collection locations, Co Timespan duration = this.duration.getOptionalSingle(event).orElse(new Timespan(TimePeriod.TICK, 0)); long milliseconds = duration.getAs(TimePeriod.MILLISECOND); for (Shape shape : shapes) { - shape.setAnimationDuration(milliseconds); + DrawData.of(shape).setAnimationDuration(milliseconds); } super.setupAsync(event, locations, shapes, recipients); } diff --git a/src/main/java/com/sovdee/skriptparticles/elements/sections/SecParticle.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/sections/SecParticle.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/elements/sections/SecParticle.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/sections/SecParticle.java diff --git a/src/main/java/com/sovdee/skriptparticles/elements/types/ParticleTypes.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/types/ParticleTypes.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/elements/types/ParticleTypes.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/types/ParticleTypes.java diff --git a/src/main/java/com/sovdee/skriptparticles/elements/types/RotationTypes.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/types/RotationTypes.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/elements/types/RotationTypes.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/types/RotationTypes.java diff --git a/src/main/java/com/sovdee/skriptparticles/elements/types/ShapeTypes.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/types/ShapeTypes.java similarity index 72% rename from src/main/java/com/sovdee/skriptparticles/elements/types/ShapeTypes.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/elements/types/ShapeTypes.java index 474790f..57fecf5 100644 --- a/src/main/java/com/sovdee/skriptparticles/elements/types/ShapeTypes.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/elements/types/ShapeTypes.java @@ -4,16 +4,17 @@ import ch.njol.skript.classes.Parser; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.registrations.Classes; -import com.sovdee.skriptparticles.shapes.CutoffShape; -import com.sovdee.skriptparticles.shapes.LWHShape; -import com.sovdee.skriptparticles.shapes.PolyShape; -import com.sovdee.skriptparticles.shapes.RadialShape; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.sampling.SamplingStyle; +import com.sovdee.shapes.shapes.CutoffShape; +import com.sovdee.shapes.shapes.LWHShape; +import com.sovdee.shapes.shapes.PolyShape; +import com.sovdee.shapes.shapes.RadialShape; +import com.sovdee.shapes.shapes.Shape; import org.jetbrains.annotations.Nullable; public class ShapeTypes { static { - // Shape + // Shape — register the library shape directly Classes.registerClass(new ClassInfo<>(Shape.class, "shape") .user("shapes?") .name("Shape") @@ -37,7 +38,7 @@ public String toString(Shape o, int flags) { @Override public String toVariableNameString(Shape shape) { - return "shape:" + shape.getUUID(); + return "shape:" + shape.getPointSampler().getUUID(); } }) .cloner(Shape::clone) @@ -49,26 +50,14 @@ public String toVariableNameString(Shape shape) { .name("Radial Shape") .description("Represents an abstract particle shape that has a radius. E.g. circle, sphere, etc.") .parser(new Parser<>() { - @Override - public RadialShape parse(String input, ParseContext context) { - return null; - } - + public RadialShape parse(String input, ParseContext context) { return null; } @Override - public boolean canParse(ParseContext context) { - return false; - } - + public boolean canParse(ParseContext context) { return false; } @Override - public String toString(RadialShape o, int flags) { - return o.toString(); - } - + public String toString(RadialShape o, int flags) { return o.toString(); } @Override - public String toVariableNameString(RadialShape shape) { - return "shape:" + shape.getUUID(); - } + public String toVariableNameString(RadialShape shape) { return "shape:" + shape.getPointSampler().getUUID(); } }) ); @@ -78,28 +67,15 @@ public String toVariableNameString(RadialShape shape) { .name("Length/Width/Height Shape") .description("Represents an abstract particle shape that has a length, width, and/or height. E.g. cube, cylinder, ellipse, etc.") .parser(new Parser<>() { - @Override - public LWHShape parse(String input, ParseContext context) { - return null; - } - + public LWHShape parse(String input, ParseContext context) { return null; } @Override - public boolean canParse(ParseContext context) { - return false; - } - + public boolean canParse(ParseContext context) { return false; } @Override - public String toString(LWHShape o, int flags) { - return o.toString(); - } - + public String toString(LWHShape o, int flags) { return o.toString(); } @Override - public String toVariableNameString(LWHShape shape) { - return "shape:" + shape.getUUID(); - } + public String toVariableNameString(LWHShape shape) { return "shape:" + shape.getPointSampler().getUUID(); } }) - ); // CutoffShape @@ -108,30 +84,18 @@ public String toVariableNameString(LWHShape shape) { .name("Cutoff Shape") .description("Represents an abstract particle shape that has a cutoff angle. E.g. arc, spherical cap, etc.") .parser(new Parser<>() { - @Override - public CutoffShape parse(String input, ParseContext context) { - return null; - } - + public CutoffShape parse(String input, ParseContext context) { return null; } @Override - public boolean canParse(ParseContext context) { - return false; - } - + public boolean canParse(ParseContext context) { return false; } @Override - public String toString(CutoffShape o, int flags) { - return o.toString(); - } - + public String toString(CutoffShape o, int flags) { return o.toString(); } @Override - public String toVariableNameString(CutoffShape shape) { - return "shape:" + shape.getUUID(); - } + public String toVariableNameString(CutoffShape shape) { return "shape:" + shape.getPointSampler().getUUID(); } }) ); - // Polygonal Shape + // PolyShape Classes.registerClass(new ClassInfo<>(PolyShape.class, "polyshape") .user("poly ?shapes?") .name("Polygonal/Polyhedral Shape") @@ -140,44 +104,32 @@ public String toVariableNameString(CutoffShape shape) { "Irregular shapes are included in this category, but do not support changing either side count or side length." ) .parser(new Parser<>() { - @Override - public PolyShape parse(String input, ParseContext context) { - return null; - } - + public PolyShape parse(String input, ParseContext context) { return null; } @Override - public boolean canParse(ParseContext context) { - return false; - } - + public boolean canParse(ParseContext context) { return false; } @Override - public String toString(PolyShape o, int flags) { - return o.toString(); - } - + public String toString(PolyShape o, int flags) { return o.toString(); } @Override - public String toVariableNameString(PolyShape shape) { - return "shape:" + shape.getUUID(); - } + public String toVariableNameString(PolyShape shape) { return "shape:" + shape.getPointSampler().getUUID(); } }) ); - // Style - Classes.registerClass(new ClassInfo<>(Shape.Style.class, "shapestyle") + // Style — use standalone SamplingStyle enum + Classes.registerClass(new ClassInfo<>(SamplingStyle.class, "shapestyle") .user("shape ?styles?") .name("Shape Style") .description("Represents the way the shape is drawn. Outlined is a wireframe representation, Surface is filling in all the surfaces of the shape, and Filled is filling in the entire shape.") .parser(new Parser<>() { @Override - public @Nullable Shape.Style parse(String s, ParseContext context) { + public @Nullable SamplingStyle parse(String s, ParseContext context) { s = s.toUpperCase(); if (s.matches("OUTLINE(D)?") || s.matches("WIREFRAME")) { - return Shape.Style.OUTLINE; + return SamplingStyle.OUTLINE; } else if (s.matches("SURFACE") || s.matches("HOLLOW")) { - return Shape.Style.SURFACE; + return SamplingStyle.SURFACE; } else if (s.matches("FILL(ED)?") || s.matches("SOLID")) { - return Shape.Style.FILL; + return SamplingStyle.FILL; } return null; } @@ -188,12 +140,12 @@ public boolean canParse(ParseContext context) { } @Override - public String toString(Shape.Style style, int i) { + public String toString(SamplingStyle style, int i) { return style.toString(); } @Override - public String toVariableNameString(Shape.Style style) { + public String toVariableNameString(SamplingStyle style) { return "shapestyle:" + style; } })); diff --git a/src/main/java/com/sovdee/skriptparticles/particles/Particle.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/particles/Particle.java similarity index 93% rename from src/main/java/com/sovdee/skriptparticles/particles/Particle.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/particles/Particle.java index dfe6dd3..3d16431 100644 --- a/src/main/java/com/sovdee/skriptparticles/particles/Particle.java +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/particles/Particle.java @@ -1,6 +1,7 @@ package com.sovdee.skriptparticles.particles; -import com.sovdee.skriptparticles.shapes.Shape; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.skriptparticles.shapes.DrawData; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; @@ -47,17 +48,19 @@ public Particle(org.bukkit.Particle particle, ParticleMotion motion) { } public void spawn(Vector delta) { - if (parent == null || parent.getLastLocation() == null) return; + if (parent == null) return; + DrawData dd = DrawData.of(parent); + if (dd.getLastLocation() == null) return; if (motion != null) { - Vector motionVector = motion.getMotionVector(parent.getRelativeYAxis(true), delta); + Vector yAxis = dd.getLastOrientation().transform(new Vector(0, 1, 0)); + Vector motionVector = motion.getMotionVector(yAxis, delta); this.offset(motionVector.getX(), motionVector.getY(), motionVector.getZ()); this.count(0); } if (gradient != null) { color(gradient.calculateColour(delta)); } - location(parent.getLastLocation().getLocation().add(delta)); - // note that the values we change here do persist, so we may need to reset them after spawning if it causes issues + location(dd.getLastLocation().getLocation().add(delta)); super.spawn(); } diff --git a/src/main/java/com/sovdee/skriptparticles/particles/ParticleGradient.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/particles/ParticleGradient.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/particles/ParticleGradient.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/particles/ParticleGradient.java diff --git a/src/main/java/com/sovdee/skriptparticles/particles/ParticleMotion.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/particles/ParticleMotion.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/particles/ParticleMotion.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/particles/ParticleMotion.java diff --git a/src/main/java/com/sovdee/skriptparticles/particles/package-info.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/particles/package-info.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/particles/package-info.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/particles/package-info.java diff --git a/skript-particle/src/main/java/com/sovdee/skriptparticles/shapes/DrawData.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/shapes/DrawData.java new file mode 100644 index 0000000..7362f9e --- /dev/null +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/shapes/DrawData.java @@ -0,0 +1,101 @@ +package com.sovdee.skriptparticles.shapes; + +import com.sovdee.shapes.sampling.DrawContext; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.skriptparticles.particles.Particle; +import com.sovdee.skriptparticles.util.DynamicLocation; +import com.sovdee.skriptparticles.util.Quaternion; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Plugin-side rendering metadata attached to library shapes via {@link DrawContext}. + * Holds particle, location, animation, and debug axis state. + */ +public class DrawData implements DrawContext { + + private Particle particle; + private @Nullable DynamicLocation location; + private @Nullable DynamicLocation lastLocation; + private final Quaternion lastOrientation; + private long animationDuration = 0; + private boolean drawLocalAxes = false; + private boolean drawGlobalAxes = false; + + public DrawData() { + this.particle = (Particle) new Particle(org.bukkit.Particle.FLAME).extra(0); + this.lastOrientation = Quaternion.IDENTITY.clone(); + } + + /** + * Gets the DrawData attached to a shape's PointSampler, creating and attaching one if missing. + */ + public static DrawData of(Shape shape) { + DrawContext ctx = shape.getPointSampler().getDrawContext(); + if (ctx instanceof DrawData dd) return dd; + DrawData dd = new DrawData(); + shape.getPointSampler().setDrawContext(dd); + return dd; + } + + // ---- Particle ---- + + public Particle getParticle() { return particle.clone(); } + + public Particle getParticleRaw() { return particle; } + + public void setParticle(Particle particle) { this.particle = particle; } + + // ---- Location ---- + + @Nullable + public DynamicLocation getLocation() { + if (location == null) return null; + return location.clone(); + } + + public void setLocation(DynamicLocation location) { this.location = location; } + + @Nullable + public DynamicLocation getLastLocation() { return lastLocation; } + + public void setLastLocation(@Nullable DynamicLocation lastLocation) { this.lastLocation = lastLocation; } + + // ---- Orientation ---- + + public Quaternion getLastOrientation() { return lastOrientation; } + + public void setLastOrientation(Quaternion orientation) { this.lastOrientation.set(orientation); } + + // ---- Animation ---- + + public long getAnimationDuration() { return animationDuration; } + + public void setAnimationDuration(long animationDuration) { this.animationDuration = animationDuration; } + + // ---- Axes ---- + + public boolean showLocalAxes() { return drawLocalAxes; } + + public void showLocalAxes(boolean show) { this.drawLocalAxes = show; } + + public boolean showGlobalAxes() { return drawGlobalAxes; } + + public void showGlobalAxes(boolean show) { this.drawGlobalAxes = show; } + + // ---- DrawContext ---- + + @Override + public DrawData copy() { + DrawData copy = new DrawData(); + copy.particle = this.particle.clone(); + if (this.location != null) + copy.location = this.location.clone(); + if (this.lastLocation != null) + copy.lastLocation = this.lastLocation.clone(); + copy.lastOrientation.set(this.lastOrientation); + copy.animationDuration = this.animationDuration; + copy.drawLocalAxes = this.drawLocalAxes; + copy.drawGlobalAxes = this.drawGlobalAxes; + return copy; + } +} diff --git a/skript-particle/src/main/java/com/sovdee/skriptparticles/shapes/DrawManager.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/shapes/DrawManager.java new file mode 100644 index 0000000..7e349e4 --- /dev/null +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/shapes/DrawManager.java @@ -0,0 +1,120 @@ +package com.sovdee.skriptparticles.shapes; + +import ch.njol.skript.Skript; +import com.sovdee.shapes.shapes.Shape; +import com.sovdee.skriptparticles.particles.Particle; +import com.sovdee.skriptparticles.particles.ParticleGradient; +import com.sovdee.skriptparticles.util.DynamicLocation; +import com.sovdee.skriptparticles.util.MathUtil; +import com.sovdee.skriptparticles.util.ParticleUtil; +import com.sovdee.skriptparticles.util.Quaternion; +import com.sovdee.skriptparticles.util.VectorConversion; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.joml.Quaterniond; +import org.joml.Vector3d; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +/** + * Static draw methods for rendering library shapes with plugin DrawData. + */ +public class DrawManager { + + public static void draw(Shape shape, Collection recipients) { + DrawData dd = DrawData.of(shape); + DynamicLocation location = dd.getLocation(); + if (location == null) return; + draw(shape, location, Quaternion.IDENTITY, dd.getParticleRaw(), recipients); + } + + public static void draw(Shape shape, DynamicLocation location, Collection recipients) { + draw(shape, location, Quaternion.IDENTITY, DrawData.of(shape).getParticleRaw(), recipients); + } + + public static void drawWithConsumer(Shape shape, DynamicLocation location, Consumer consumer, Collection recipients) { + consumer.accept(shape); + DrawData dd = DrawData.of(shape); + Quaterniond shapeOrientation = shape.getOrientation(); + Quaternion shapeOrientationQ = new Quaternion((float) shapeOrientation.x, (float) shapeOrientation.y, (float) shapeOrientation.z, (float) shapeOrientation.w); + draw(shape, location, shapeOrientationQ, dd.getParticleRaw(), recipients); + } + + public static void draw(Shape shape, DynamicLocation location, Quaternion baseOrientation, Particle particle, Collection recipients) { + DrawData dd = DrawData.of(shape); + + if (location.isNull()) { + DynamicLocation shapeLocation = dd.getLocation(); + if (shapeLocation == null) return; + location = shapeLocation.clone(); + } + + dd.setLastLocation(location.clone()); + Quaterniond shapeOrientation = shape.getOrientation(); + Quaternion shapeOrientationQ = new Quaternion((float) shapeOrientation.x, (float) shapeOrientation.y, (float) shapeOrientation.z, (float) shapeOrientation.w); + dd.getLastOrientation().set(baseOrientation.clone().mul(shapeOrientationQ)); + + if (!particle.override()) { + dd.getParticleRaw().parent(shape); + particle = dd.getParticleRaw(); + @Nullable ParticleGradient gradient = particle.gradient(); + if (gradient != null && gradient.isLocal()) + gradient.setOrientation(dd.getLastOrientation()); + } + + particle.receivers(recipients); + + // Get points from library shape using the last orientation + Quaterniond lastOrientationD = new Quaterniond(dd.getLastOrientation().x, dd.getLastOrientation().y, dd.getLastOrientation().z, dd.getLastOrientation().w); + Set jomlPoints = shape.getPointSampler().getPoints(shape, lastOrientationD); + Collection toDraw = VectorConversion.toBukkit(jomlPoints); + + long animationDuration = dd.getAnimationDuration(); + if (animationDuration > 0) { + int particleCount = toDraw.size(); + double millisecondsPerPoint = animationDuration / (double) particleCount; + Iterator> batchIterator = MathUtil.batch(toDraw, millisecondsPerPoint).iterator(); + Particle finalParticle = particle; + BukkitRunnable runnable = new BukkitRunnable() { + @Override + public void run() { + if (!batchIterator.hasNext()) { + this.cancel(); + return; + } + List batch = batchIterator.next(); + try { + for (Vector point : batch) { + finalParticle.spawn(point); + } + } catch (IllegalArgumentException e) { + Skript.error("Failed to spawn particle! Error: " + e.getMessage()); + } + } + }; + runnable.runTaskTimerAsynchronously(Skript.getInstance(), 0, 1); + } else { + for (Vector point : toDraw) { + try { + particle.spawn(point); + } catch (IllegalArgumentException e) { + Skript.error("Failed to spawn particle! Error: " + e.getMessage()); + return; + } + } + } + + if (dd.showLocalAxes()) { + ParticleUtil.drawAxes(location.getLocation().add(VectorConversion.toBukkit(shape.getOffset())), dd.getLastOrientation(), recipients); + } + if (dd.showGlobalAxes()) { + ParticleUtil.drawAxes(location.getLocation().add(VectorConversion.toBukkit(shape.getOffset())), Quaternion.IDENTITY, recipients); + } + } +} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/package-info.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/shapes/package-info.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/shapes/package-info.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/shapes/package-info.java diff --git a/src/main/java/com/sovdee/skriptparticles/util/DynamicLocation.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/util/DynamicLocation.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/util/DynamicLocation.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/util/DynamicLocation.java diff --git a/skript-particle/src/main/java/com/sovdee/skriptparticles/util/MathUtil.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/util/MathUtil.java new file mode 100644 index 0000000..c92e297 --- /dev/null +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/util/MathUtil.java @@ -0,0 +1,47 @@ +package com.sovdee.skriptparticles.util; + +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class MathUtil { + public static final double EPSILON = 0.0001; + + public static double clamp(double value, double min, double max) { + return Math.clamp(value, min, max); + } + + public static Set calculateLine(Vector start, Vector end, double particleDensity) { + Set points = new LinkedHashSet<>(); + Vector direction = end.clone().subtract(start); + double length = direction.length(); + double step = length / Math.round(length / particleDensity); + direction.normalize().multiply(step); + + for (double i = 0; i <= (length / step); i++) { + points.add(start.clone().add(direction.clone().multiply(i))); + } + return points; + } + + public static List> batch(Collection toDraw, double millisecondsPerPoint) { + List> batches = new ArrayList<>(); + double totalDuration = 0; + Iterator pointsIterator = toDraw.iterator(); + while (pointsIterator.hasNext()) { + List batch = new ArrayList<>(); + while (totalDuration < 50 && pointsIterator.hasNext()) { + totalDuration += millisecondsPerPoint; + batch.add(pointsIterator.next()); + } + totalDuration -= 50; + batches.add(batch); + } + return batches; + } +} diff --git a/src/main/java/com/sovdee/skriptparticles/util/ParticleUtil.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/util/ParticleUtil.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/util/ParticleUtil.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/util/ParticleUtil.java diff --git a/src/main/java/com/sovdee/skriptparticles/util/Point.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/util/Point.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/util/Point.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/util/Point.java diff --git a/src/main/java/com/sovdee/skriptparticles/util/Quaternion.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/util/Quaternion.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/util/Quaternion.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/util/Quaternion.java diff --git a/src/main/java/com/sovdee/skriptparticles/util/ReflectionUtils.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/util/ReflectionUtils.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/util/ReflectionUtils.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/util/ReflectionUtils.java diff --git a/skript-particle/src/main/java/com/sovdee/skriptparticles/util/VectorConversion.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/util/VectorConversion.java new file mode 100644 index 0000000..4780668 --- /dev/null +++ b/skript-particle/src/main/java/com/sovdee/skriptparticles/util/VectorConversion.java @@ -0,0 +1,37 @@ +package com.sovdee.skriptparticles.util; + +import org.bukkit.util.Vector; +import org.joml.Vector3d; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Utility class for converting between Bukkit Vectors and JOML Vector3d. + */ +public class VectorConversion { + + public static Vector toBukkit(Vector3d v) { + return new Vector(v.x, v.y, v.z); + } + + public static Vector3d toJOML(Vector v) { + return new Vector3d(v.getX(), v.getY(), v.getZ()); + } + + public static Set toBukkit(Set points) { + Set result = new LinkedHashSet<>(); + for (Vector3d v : points) { + result.add(toBukkit(v)); + } + return result; + } + + public static Set toJOML(Set points) { + Set result = new LinkedHashSet<>(); + for (Vector v : points) { + result.add(toJOML(v)); + } + return result; + } +} diff --git a/src/main/java/com/sovdee/skriptparticles/util/package-info.java b/skript-particle/src/main/java/com/sovdee/skriptparticles/util/package-info.java similarity index 100% rename from src/main/java/com/sovdee/skriptparticles/util/package-info.java rename to skript-particle/src/main/java/com/sovdee/skriptparticles/util/package-info.java diff --git a/src/main/resources/lang/english.lang b/skript-particle/src/main/resources/lang/english.lang similarity index 100% rename from src/main/resources/lang/english.lang rename to skript-particle/src/main/resources/lang/english.lang diff --git a/src/main/resources/plugin.yml b/skript-particle/src/main/resources/plugin.yml similarity index 100% rename from src/main/resources/plugin.yml rename to skript-particle/src/main/resources/plugin.yml diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/AbstractShape.java b/src/main/java/com/sovdee/skriptparticles/shapes/AbstractShape.java deleted file mode 100644 index cea25e9..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/AbstractShape.java +++ /dev/null @@ -1,395 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import ch.njol.skript.Skript; -import com.sovdee.skriptparticles.particles.Particle; -import com.sovdee.skriptparticles.particles.ParticleGradient; -import com.sovdee.skriptparticles.util.DynamicLocation; -import com.sovdee.skriptparticles.util.MathUtil; -import com.sovdee.skriptparticles.util.ParticleUtil; -import com.sovdee.skriptparticles.util.Quaternion; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.util.Vector; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.jetbrains.annotations.Contract; -import org.joml.Quaternionf; - -import java.util.Collection; -import java.util.Comparator; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; -import java.util.function.Consumer; - -public abstract class AbstractShape implements Shape { - - private final UUID uuid; - private Set points; - - private Style style; - private final Quaternion orientation; - private final Quaternion lastOrientation; - private double scale; - private Vector offset; - private @Nullable DynamicLocation location; - private Particle particle; - private @Nullable Comparator ordering; - private double particleDensity = 0.25; // todo: make this configurable - private long animationDuration = 0; - - private State lastState; - private @Nullable DynamicLocation lastLocation; - private boolean needsUpdate = false; - - private boolean drawLocalAxes = false; - private boolean drawGlobalAxes = false; - - public AbstractShape() { - this.style = Style.OUTLINE; - this.points = new LinkedHashSet<>(); - - this.orientation = Quaternion.IDENTITY.clone(); - this.lastOrientation = Quaternion.IDENTITY.clone(); - this.scale = 1; - this.offset = new Vector(0, 0, 0); - - this.particle = (Particle) new Particle(org.bukkit.Particle.FLAME).parent(this).extra(0); - - this.uuid = UUID.randomUUID(); - - this.lastState = getState(); - } - - @Override - public Set getPoints() { - return getPoints(this.orientation); - } - - @Override - public Set getPoints(Quaternion orientation) { - State state = getState(orientation); - if (needsUpdate || !lastState.equals(state) || points.isEmpty()) { - if (ordering != null) - points = new TreeSet<>(ordering); - else - points = new LinkedHashSet<>(); - generatePoints(points); - for (Vector point : points) { - orientation.transform(point); - point.multiply(scale); - point.add(offset); - } - lastState = state; - needsUpdate = false; - } - return points; - } - - @Override - public void setPoints(Set points) { - this.points = points; - } - - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - generateOutline(points); - } - - @Override - @Contract(pure = true) - public void generateFilled(Set points) { - generateSurface(points); - } - - @Override - public void draw(Collection recipients) { - assert location != null; - draw(location, Quaternion.IDENTITY, particle, recipients); - } - - @Override - public void draw(DynamicLocation location, Collection recipients) { - draw(location, Quaternion.IDENTITY, particle, recipients); - } - - @Override - public void draw(DynamicLocation location, Quaternion baseOrientation, Particle particle, Collection recipients) { - // cache the last location and orientation used to draw the shape - if (location.isNull()) { - if (this.location == null) - return; - location = this.location.clone(); - } - - lastLocation = location.clone(); - lastOrientation.set(baseOrientation.clone().mul(orientation)); - - // If the particle doesn't override the shape's particle, use the shape's particle - if (!particle.override()) { - this.particle.parent(this); - particle = this.particle; - // update the gradient if needed - @Nullable ParticleGradient gradient = particle.gradient(); - if (gradient != null && gradient.isLocal()) - gradient.setOrientation(lastOrientation); - } - - particle.receivers(recipients); - Collection toDraw = getPoints(lastOrientation); - if (animationDuration > 0) { - int particleCount = toDraw.size(); - double millisecondsPerPoint = animationDuration / (double) particleCount; - Iterator> batchIterator = MathUtil.batch(toDraw, millisecondsPerPoint).iterator(); - Particle finalParticle = particle; - BukkitRunnable runnable = new BukkitRunnable() { - @Override - public void run() { - if (!batchIterator.hasNext()) { - this.cancel(); - return; - } - List batch = batchIterator.next(); - try { - for (Vector point : batch) { - finalParticle.spawn(point); - } - } catch (IllegalArgumentException e) { - Skript.error("Failed to spawn particle! Error: " + e.getMessage()); - } - } - }; - runnable.runTaskTimerAsynchronously(Skript.getInstance(), 0, 1); - } else { - // no animation needed, draw all particles at once - for (Vector point :toDraw) { - try { - particle.spawn(point); - } catch (IllegalArgumentException e) { - Skript.error("Failed to spawn particle! Error: " + e.getMessage()); - return; - } - } - } - - if (drawLocalAxes) { - ParticleUtil.drawAxes(location.getLocation().add(offset), lastOrientation, recipients); - } - if (drawGlobalAxes) { - ParticleUtil.drawAxes(location.getLocation().add(offset), Quaternion.IDENTITY, recipients); - } - } - - @Override - public void draw(DynamicLocation location, Consumer consumer, Collection recipients) { - consumer.accept(this); - draw(location, this.orientation, this.particle, recipients); - } - - @Override - public Vector getRelativeXAxis(boolean useLastOrientation) { - return (useLastOrientation ? lastOrientation : orientation).transform(new Vector(1, 0, 0)); - } - - @Override - public Vector getRelativeYAxis(boolean useLastOrientation) { - return (useLastOrientation ? lastOrientation : orientation).transform(new Vector(0, 1, 0)); - } - - @Override - public Vector getRelativeZAxis(boolean useLastOrientation) { - return (useLastOrientation ? lastOrientation : orientation).transform(new Vector(0, 0, 1)); - } - - @Override - public void showLocalAxes(boolean show) { - drawLocalAxes = show; - } - - @Override - public boolean showLocalAxes() { - return drawLocalAxes; - } - - @Override - public void showGlobalAxes(boolean show) { - drawGlobalAxes = show; - } - - @Override - public boolean showGlobalAxes() { - return drawGlobalAxes; - } - - @Override - @Nullable - public DynamicLocation getLastLocation() { - return lastLocation; - } - - @Override - public Style getStyle() { - return style; - } - - @Override - public void setStyle(Style style) { - this.style = style; - this.setNeedsUpdate(true); - } - - @Override - public Quaternion getOrientation() { - return new Quaternion(orientation); - } - - @Override - public void setOrientation(Quaternionf orientation) { - this.orientation.set(orientation); - this.setNeedsUpdate(true); - } - - @Override - public double getScale() { - return scale; - } - - @Override - public void setScale(double scale) { - this.scale = scale; - this.setNeedsUpdate(true); - } - - @Override - public Vector getOffset() { - return offset.clone(); - } - - @Override - public void setOffset(Vector offset) { - this.offset = offset; - this.setNeedsUpdate(true); - } - - @Override - @Nullable - public DynamicLocation getLocation() { - if (location == null) - return null; - return location.clone(); - } - - @Override - public void setLocation(DynamicLocation location) { - this.location = location; - } - - @Override - public UUID getUUID() { - return uuid; - } - - @Override - public Particle getParticle() { - return particle.clone(); - } - - @Override - public void setParticle(Particle particle) { - this.particle = particle; - } - - @Override - public @Nullable Comparator getOrdering() { - return ordering; - } - - @Override - public void setOrdering(Comparator comparator) { - ordering = comparator; - this.setNeedsUpdate(true); - } - - @Override - public double getParticleDensity() { - return particleDensity; - } - - - @Override - public void setParticleDensity(double particleDensity) { - this.particleDensity = Math.max(particleDensity, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public int getParticleCount() { - return getPoints().size(); - } - - @Override - public boolean needsUpdate() { - return needsUpdate; - } - - @Override - public void setNeedsUpdate(boolean needsUpdate) { - this.needsUpdate = needsUpdate; - } - - @Override - public long getAnimationDuration() { - return animationDuration; - } - - @Override - public void setAnimationDuration(long animationDuration) { - this.animationDuration = animationDuration; - } - - @Contract("-> new") - public abstract Shape clone(); - - @Override - @Contract("_ -> param1") - public Shape copyTo(Shape shape) { - shape.setOrientation(this.orientation); - shape.setScale(this.scale); - shape.setOffset(this.offset.clone()); - shape.setParticle(this.particle.clone()); - if (this.location != null) - shape.setLocation(this.location.clone()); - shape.setParticleDensity(this.particleDensity); - shape.setStyle(this.style); - shape.showLocalAxes(this.drawLocalAxes); - shape.showGlobalAxes(this.drawGlobalAxes); - shape.setAnimationDuration(this.animationDuration); - shape.setOrdering(this.ordering); - // ensure that the shape's points are updated, so we don't have to recalculate them unless we change the copy. - shape.setPoints(this.getPoints()); - shape.setNeedsUpdate(this.needsUpdate); - shape.setLastState(this.lastState); - return shape; - } - - @Override - @Contract("-> new") - public State getState() { - return new State(style, orientation.hashCode(), scale, offset.hashCode(), particleDensity); - } - - @Override - @Contract("_ -> new") - public State getState(Quaternion orientation) { - return new State(style, orientation.hashCode(), scale, offset.hashCode(), particleDensity); - } - - @Override - public void setLastState(State state) { - this.lastState = state; - } - -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Arc.java b/src/main/java/com/sovdee/skriptparticles/shapes/Arc.java deleted file mode 100644 index d63df2e..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Arc.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.Set; - -/** - * A circle with a cutoff angle. This creates an arc or a sector. - * The cutoff angle is the angle between the start and end of the arc. - * It will always be between 0 and 2 * PI. - */ -public class Arc extends Circle implements CutoffShape { - - /** - * @param radius The radius of the arc. Must be greater than 0. - * @param cutoffAngle The cutoff angle of the arc, in radians. Will be clamped to a value between 0 and 2 * PI. - */ - public Arc(double radius, double cutoffAngle) { - super(radius); - this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI * 2); - } - - /** - * @param radius The radius of the arc. Must be greater than 0. - * @param height The height of the arc. Must be non-negative. - * @param cutoffAngle The cutoff angle of the arc, in radians. Will be clamped to a value between 0 and 2 * PI. - */ - public Arc(double radius, double height, double cutoffAngle) { - super(radius, height); - this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI * 2); - } - - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - generateFilled(points); - } - - @Override - public double getCutoffAngle() { - return this.cutoffAngle; - } - - @Override - public void setCutoffAngle(double cutoffAngle) { - this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI * 2); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new Arc(this.getRadius(), this.getHeight(), cutoffAngle)); - } - - @Override - public String toString() { - return "Arc{" + - "radius=" + this.getRadius() + - ", cutoffAngle=" + cutoffAngle + - ", height=" + this.getHeight() + - '}'; - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/BezierCurve.java b/src/main/java/com/sovdee/skriptparticles/shapes/BezierCurve.java deleted file mode 100644 index f67150a..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/BezierCurve.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.DynamicLocation; -import com.sovdee.skriptparticles.util.Point; -import com.sovdee.skriptparticles.util.Quaternion; -import org.bukkit.Location; -import org.bukkit.util.Vector; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.jetbrains.annotations.Contract; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -public class BezierCurve extends AbstractShape { - - private Point start; - private Point end; - private List> controlPoints; - - private boolean isDynamic; - - /** - * Creates a new line shape with the start point at the origin and the end point at the given vector. - * The vector cannot be the origin. - * @param end the end point of the line - * @throws IllegalArgumentException if the end vector is the origin - */ - - public BezierCurve(Point start, Point end, List> controlPoints) { - this.start = start; - if (start.getType() != Vector.class) - this.setLocation(start.getDynamicLocation()); - this.end = end; - this.controlPoints = new ArrayList<>(controlPoints); - isDynamic = true; - } - - public BezierCurve(BezierCurve curve) { - this.start = curve.getStart(); - this.end = curve.getEnd(); - this.controlPoints = new ArrayList<>(curve.getControlPoints()); - isDynamic = curve.isDynamic; - } - - @Override - @Contract(pure = true) - public Set getPoints(Quaternion orientation) { - Set points = super.getPoints(orientation); - if (isDynamic) - // Ensure that the points are always needing to be updated if the start or end location is dynamic - this.setNeedsUpdate(true); - return points; - } - - private List evaluateControlPoints() { - List controlPoints = new ArrayList<>(); - @Nullable Location origin = start.getLocation(); - controlPoints.add(start.getVector(origin)); - for (Point controlPoint : this.controlPoints) - controlPoints.add(controlPoint.getVector(origin)); - controlPoints.add(end.getVector(origin)); - return controlPoints; - } - - @SuppressWarnings("ConstantConditions") - @Override - public void generateOutline(Set points) { - List controlPoints = evaluateControlPoints(); - - int steps = (int) (estimateLength(controlPoints) / getParticleDensity()); - - for (double step = 0; step < steps; step++) { - double t = step / steps; - double nt = 1 - t; - List tempCP = new ArrayList<>(controlPoints); - while (tempCP.size() > 1) { - for (int i = 0; i < tempCP.size() - 1; i++) - tempCP.set(i, tempCP.get(i).clone().multiply(nt).add(tempCP.get(i + 1).clone().multiply(t))); - tempCP.remove(tempCP.size()-1); - } - points.add(tempCP.get(0)); - } - } - - private double estimateLength() { - return estimateLength(evaluateControlPoints()); - } - private double estimateLength(List controlPoints) { - double dist = 0; - for (int i = 0; i < controlPoints.size()-1; i++) { - dist += controlPoints.get(i).distance(controlPoints.get(i+1)); - } - return dist; - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(particleCount, 1); - this.setParticleDensity(estimateLength() / particleCount); - this.setNeedsUpdate(true); - } - - public Point getStart() { - return start; - } - - public Point getEnd() { - return end; - } - - public List> getControlPoints() { - return controlPoints; - } - - @Override - public Shape clone() { - return this.copyTo(new BezierCurve(this)); - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Circle.java b/src/main/java/com/sovdee/skriptparticles/shapes/Circle.java deleted file mode 100644 index 8de8df8..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Circle.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.Set; - -/** - * A circle with a radius and optionally a height. - * Circle does not implement {@link LWHShape#setWidth(double)} or {@link LWHShape#setLength(double)}. - */ -public class Circle extends AbstractShape implements RadialShape, LWHShape { - - private double radius; - protected double cutoffAngle; - private double height; - - /** - * Creates a circle with the given radius and a height of 0. - * - * @param radius the radius of the circle. Must be greater than 0. - */ - public Circle(double radius) { - this(radius, 0); - } - - /** - * Creates a circle with the given radius and height. - * - * @param radius the radius of the circle. Must be greater than 0. - * @param height the height of the circle. Must be non-negative. - */ - public Circle(double radius, double height) { - super(); - this.radius = Math.max(radius, MathUtil.EPSILON); - this.height = Math.max(height, 0); - - this.cutoffAngle = 2 * Math.PI; - } - - @Override - @Contract(pure = true) - @SuppressWarnings("ConstantConditions") - public void generateOutline(Set points) { - Set circle = MathUtil.calculateCircle(radius, this.getParticleDensity(), cutoffAngle); - if (height != 0) - points.addAll(MathUtil.fillVertically(circle, height, this.getParticleDensity())); - else - points.addAll(circle); - } - - - @Override - @Contract(pure = true) - @SuppressWarnings("ConstantConditions") - public void generateSurface(Set points) { - if (height != 0) - points.addAll(MathUtil.calculateCylinder(radius, height, this.getParticleDensity(), cutoffAngle)); - else - points.addAll(MathUtil.calculateDisc(radius, this.getParticleDensity(), cutoffAngle)); - } - - @Override - @Contract(pure = true) - @SuppressWarnings("ConstantConditions") - public void generateFilled(Set points) { - Set disc = MathUtil.calculateDisc(radius, this.getParticleDensity(), cutoffAngle); - if (height != 0) - points.addAll(MathUtil.fillVertically(disc, height, this.getParticleDensity())); - else - points.addAll(disc); - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(particleCount, 1); - - if (this.getStyle() == Style.OUTLINE && height == 0) { - this.setParticleDensity(cutoffAngle * radius / particleCount); - } else if (this.getStyle() == Style.SURFACE || height == 0) { - double discArea = cutoffAngle * 0.5 * radius * radius; - double wallArea = cutoffAngle * radius * height; - this.setParticleDensity(Math.sqrt((discArea + wallArea) / particleCount)); - } else { - this.setParticleDensity(Math.cbrt(cutoffAngle * 0.5 * radius * radius * height / particleCount)); - } - this.setNeedsUpdate(true); - } - - @Override - public double getRadius() { - return radius; - } - - @Override - public void setRadius(double radius) { - this.radius = Math.max(radius, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getLength() { - return 0; - } - - @Override - public void setLength(double length) { - // intentionally left blank - } - - @Override - public double getWidth() { - return 0; - } - - @Override - public void setWidth(double width) { - // intentionally left blank - } - - @Override - public double getHeight() { - return height; - } - - @Override - public void setHeight(double height) { - this.height = Math.max(height, 0); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new Circle(radius, height)); - } - - @Override - public String toString() { - return "Circle{" + - "radius=" + radius + - ", cutoffAngle=" + cutoffAngle + - ", height=" + height + - '}'; - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Cuboid.java b/src/main/java/com/sovdee/skriptparticles/shapes/Cuboid.java deleted file mode 100644 index 5f672de..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Cuboid.java +++ /dev/null @@ -1,254 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.DynamicLocation; -import com.sovdee.skriptparticles.util.MathUtil; -import com.sovdee.skriptparticles.util.Quaternion; -import org.bukkit.Location; -import org.bukkit.util.Vector; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.Set; - -/* - * A cuboid shape, defined either by two corners or by length, width, and height. - * Cuboids utilising the {@link DynamicLocation} constructor do not require a location to be drawn at and will - * automatically update their positions when drawn. - */ -public class Cuboid extends AbstractShape implements LWHShape { - - private double halfLength, halfWidth, halfHeight; - private double lengthStep, widthStep, heightStep; - private Vector centerOffset = new Vector(0, 0, 0); - private @Nullable DynamicLocation negativeCorner, positiveCorner; - private boolean isDynamic = false; - - /** - * Creates a cuboid with the given length, width, and height. - * @param length The length of the cuboid. Must be greater than 0. - * @param width The width of the cuboid. Must be greater than 0. - * @param height The height of the cuboid. Must be greater than 0. - */ - public Cuboid(double length, double width, double height) { - super(); - - this.halfWidth = Math.max(width / 2, MathUtil.EPSILON); - this.halfLength = Math.max(length / 2, MathUtil.EPSILON); - this.halfHeight = Math.max(height / 2, MathUtil.EPSILON); - calculateSteps(); - } - - /** - * Creates a cuboid from the given corner vectors. Using asymmetric vectors will result in a cuboid that is offset from the origin of the shape. - *

- * For example, using (0, 0, 0) and (1, 1, 1) and drawing the cuboid at (2, 0, 2) will result - * in the negative corner being drawn at (2, 0, 2) and the positive corner being at (3, 1, 3). - * - * @param cornerA A vector from the origin of the shape to the first corner. - * @param cornerB A vector from the origin of the shape to the second corner. - * @throws IllegalArgumentException If the given vectors are equal. - */ - public Cuboid(Vector cornerA, Vector cornerB) { - super(); - if (cornerA.equals(cornerB)) - throw new IllegalArgumentException("Cuboid corners cannot be equal."); - this.halfLength = Math.abs(cornerB.getX() - cornerA.getX()) / 2; - this.halfWidth = Math.abs(cornerB.getZ() - cornerA.getZ()) / 2; - this.halfHeight = Math.abs(cornerB.getY() - cornerA.getY()) / 2; - centerOffset = cornerB.clone().add(cornerA).multiply(0.5); - calculateSteps(); - } - - /** - * Creates a cuboid from the given corner locations. Unlike the vector constructor, this constructor will not offset the cuboid from the origin of the shape. - * When drawn with the default location, the visual corner locations will be at the given corners. If the cuboid is drawn at a different location, the - * shape's length, width, and height will be dependent on the given corners, but the shape will be drawn at the given location instead. - * - * @param cornerA The first corner of the cuboid. - * @param cornerB The second corner of the cuboid. - * @throws IllegalArgumentException If the given locations are equal. - */ - public Cuboid(DynamicLocation cornerA, DynamicLocation cornerB) { - super(); - if (cornerA.equals(cornerB)) - throw new IllegalArgumentException("Cuboid corners cannot be equal."); - Location cornerALocation = cornerA.getLocation(); - Location cornerBLocation = cornerB.getLocation(); - if (cornerALocation.equals(cornerBLocation)) - throw new IllegalArgumentException("Cuboid corners cannot be equal."); - - if (cornerA.isDynamic() || cornerB.isDynamic()) { - this.negativeCorner = cornerA.clone(); - this.positiveCorner = cornerB.clone(); - isDynamic = true; - } else { - this.halfLength = Math.abs(cornerBLocation.getX() - cornerALocation.getX()) / 2; - this.halfWidth = Math.abs(cornerBLocation.getZ() - cornerALocation.getZ()) / 2; - this.halfHeight = Math.abs(cornerBLocation.getY() - cornerALocation.getY()) / 2; - } - this.setLocation(new DynamicLocation(cornerALocation.clone().add(cornerBLocation.subtract(cornerALocation).toVector().multiply(0.5)))); - calculateSteps(); - } - - /** - * Calculates the step size for each axis based on the given particle density. - * todo: Use config option to toggle between adaptive and fixed step size. - */ - private void calculateSteps() { - widthStep = 2 * halfWidth / Math.round(2 * halfWidth / this.getParticleDensity()); - lengthStep = 2 * halfLength / Math.round(2 * halfLength / this.getParticleDensity()); - heightStep = 2 * halfHeight / Math.round(2 * halfHeight / this.getParticleDensity()); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - for (double x = -halfLength; x <= halfLength; x += lengthStep) { - points.add(new Vector(x, -halfHeight, -halfWidth)); - points.add(new Vector(x, -halfHeight, halfWidth)); - points.add(new Vector(x, halfHeight, -halfWidth)); - points.add(new Vector(x, halfHeight, halfWidth)); - } - for (double y = -halfHeight + heightStep; y < halfHeight; y += heightStep) { - points.add(new Vector(-halfLength, y, -halfWidth)); - points.add(new Vector(-halfLength, y, halfWidth)); - points.add(new Vector(halfLength, y, -halfWidth)); - points.add(new Vector(halfLength, y, halfWidth)); - } - for (double z = -halfWidth + widthStep; z < halfWidth; z += widthStep) { - points.add(new Vector(-halfLength, -halfHeight, z)); - points.add(new Vector(-halfLength, halfHeight, z)); - points.add(new Vector(halfLength, -halfHeight, z)); - points.add(new Vector(halfLength, halfHeight, z)); - } - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - for (double x = -halfLength; x <= halfLength; x += lengthStep) { - for (double z = -halfWidth; z <= halfWidth; z += widthStep) { - points.add(new Vector(x, -halfHeight, z)); - points.add(new Vector(x, halfHeight, z)); - } - } - for (double y = -halfHeight + heightStep; y < halfHeight; y += heightStep) { - for (double z = -halfWidth; z <= halfWidth; z += widthStep) { - points.add(new Vector(-halfLength, y, z)); - points.add(new Vector(halfLength, y, z)); - } - } - for (double x = -halfLength + lengthStep; x < halfLength; x += lengthStep) { - for (double y = -halfHeight + heightStep; y < halfHeight; y += heightStep) { - points.add(new Vector(x, y, -halfWidth)); - points.add(new Vector(x, y, halfWidth)); - } - } - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateFilled(Set points) { - for (double x = -halfLength; x <= halfLength; x += lengthStep) { - for (double y = -halfHeight; y <= halfHeight; y += heightStep) { - for (double z = -halfWidth; z <= halfWidth; z += widthStep) { - points.add(new Vector(x, y, z)); - } - } - } - } - - @Override - @Contract(pure = true) - public void generatePoints(Set points) { - if (isDynamic) { - assert negativeCorner != null; - assert positiveCorner != null; - Location negative = negativeCorner.getLocation(); - Location positive = positiveCorner.getLocation(); - this.halfLength = Math.abs(positive.getX() - negative.getX()) / 2; - this.halfWidth = Math.abs(positive.getZ() - negative.getZ()) / 2; - this.halfHeight = Math.abs(positive.getY() - negative.getY()) / 2; - this.setLocation(new DynamicLocation(negative.clone().add(positive.subtract(negative).toVector().multiply(0.5)))); - } - calculateSteps(); - super.generatePoints(points); - points.forEach(vector -> vector.add(centerOffset)); - } - - // Ensure that the points are always needing to be updated if the start or end location is dynamic - @Override - public Set getPoints(@NonNull @NotNull Quaternion orientation) { - Set points = super.getPoints(orientation); - if (isDynamic) - this.setNeedsUpdate(true); - return points; - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(1, particleCount); - this.setParticleDensity(switch (this.getStyle()) { - case OUTLINE -> 8 * (halfLength + halfHeight + halfWidth) / particleCount; - case SURFACE -> - Math.sqrt(8 * (halfLength * halfHeight + halfLength * halfWidth + halfHeight * halfWidth) / particleCount); - case FILL -> Math.cbrt(8 * halfLength * halfHeight * halfWidth / particleCount); - }); - calculateSteps(); - this.setNeedsUpdate(true); - } - - @Override - public double getLength() { - return halfLength * 2; - } - - @Override - public void setLength(double length) { - this.halfLength = Math.max(length / 2, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getWidth() { - return halfWidth * 2; - } - - @Override - public void setWidth(double width) { - this.halfWidth = Math.max(width / 2, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getHeight() { - return halfHeight * 2; - } - - @Override - public void setHeight(double height) { - this.halfHeight = Math.max(height / 2, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - Cuboid cuboid; - if (isDynamic) { - assert negativeCorner != null; - assert positiveCorner != null; - cuboid = (new Cuboid(negativeCorner, positiveCorner)); - } else { - cuboid = (new Cuboid(getLength(), getWidth(), getHeight())); - } - cuboid.isDynamic = isDynamic; - return this.copyTo(cuboid); - } - -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/CutoffShape.java b/src/main/java/com/sovdee/skriptparticles/shapes/CutoffShape.java deleted file mode 100644 index 9291658..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/CutoffShape.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -/** - * Represents a shape that has a cutoff angle, like an arc. - */ -public interface CutoffShape extends Shape { - - /** - * Gets the cutoff angle of the shape, or the angle at which the shape will stop generating particles. - * - * @return The cutoff angle of the shape in radians, between 0 and 2 * PI. - */ - double getCutoffAngle(); - - /** - * Sets the cutoff angle of the shape, or the angle at which the shape will stop generating particles. - * - * @param cutoffAngle The cutoff angle of the shape, in radians. Will be converted to a value between 0 and 2 * PI. - */ - void setCutoffAngle(double cutoffAngle); - -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Ellipse.java b/src/main/java/com/sovdee/skriptparticles/shapes/Ellipse.java deleted file mode 100644 index 69f50d4..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Ellipse.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * Represents an ellipse shape with an x radius, z radius, and optional height. - * The radii must be greater than 0, and the height must be non-negative. - */ -public class Ellipse extends AbstractShape implements LWHShape { - - private double xRadius; - private double zRadius; - private double height; - protected double cutoffAngle; - - /** - * Creates an ellipse with the given x radius and z radius. - * - * @param xRadius the x radius. Must be greater than 0. - * @param zRadius the z radius. Must be greater than 0. - */ - public Ellipse(double xRadius, double zRadius) { - this(xRadius, zRadius, 0); - } - - /** - * Creates an ellipse with the given x radius, z radius, and height. - * - * @param xRadius the x radius. Must be greater than 0. - * @param zRadius the z radius. Must be greater than 0. - * @param height the height. Must be non-negative. - */ - public Ellipse(double xRadius, double zRadius, double height) { - super(); - this.xRadius = Math.max(xRadius, MathUtil.EPSILON); - this.zRadius = Math.max(zRadius, MathUtil.EPSILON); - this.height = Math.max(height, 0); - this.cutoffAngle = 2 * Math.PI; - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - Set ellipse = new LinkedHashSet<>(MathUtil.calculateEllipse(xRadius, zRadius, this.getParticleDensity(), cutoffAngle)); - if (height != 0) - points.addAll(MathUtil.fillVertically(ellipse, height, this.getParticleDensity())); - else - points.addAll(ellipse); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - // if height is not 0, make it a cylinder - if (height != 0) - points.addAll(MathUtil.calculateCylinder(xRadius, zRadius, height, this.getParticleDensity(), cutoffAngle)); - else - points.addAll(MathUtil.calculateEllipticalDisc(xRadius, zRadius, this.getParticleDensity(), cutoffAngle)); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateFilled(Set points) { - Set disc = MathUtil.calculateEllipticalDisc(xRadius, zRadius, this.getParticleDensity(), cutoffAngle); - if (height != 0) - points.addAll(MathUtil.fillVertically(disc, height, this.getParticleDensity())); - else - points.addAll(disc); - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(particleCount, 1); - switch (this.getStyle()) { - case OUTLINE -> { - // this is so fucking cringe - double h = (xRadius - zRadius) * (xRadius - zRadius) / ((xRadius + zRadius) + (xRadius + zRadius)); - double circumferenceXY = Math.PI * (xRadius + zRadius) * (1 + (3 * h / (10 + Math.sqrt(4 - 3 * h)))); - this.setParticleDensity(circumferenceXY / particleCount); - } - case SURFACE, FILL -> this.setParticleDensity(Math.sqrt((Math.PI * xRadius * zRadius) / particleCount)); - } - } - - @Override - public double getLength() { - return xRadius * 2; - } - - @Override - public void setLength(double length) { - xRadius = Math.max(length / 2, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getWidth() { - return zRadius * 2; - } - - @Override - public void setWidth(double width) { - zRadius = Math.max(width / 2, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getHeight() { - return height; - } - - @Override - public void setHeight(double height) { - this.height = Math.max(height, 0); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new Ellipse(xRadius, zRadius, height)); - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Ellipsoid.java b/src/main/java/com/sovdee/skriptparticles/shapes/Ellipsoid.java deleted file mode 100644 index dd6d44e..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Ellipsoid.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import com.sovdee.skriptparticles.util.Quaternion; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * An ellipsoid shape, with an x radius, y radius, and z radius. - * All radii must be greater than 0. - */ -public class Ellipsoid extends AbstractShape implements LWHShape { - - private static final Quaternion XY_ROTATION = new Quaternion(new Vector(1, 0, 0), (float) (Math.PI / 2)); - private static final Quaternion ZY_ROTATION = new Quaternion(new Vector(0, 0, 1), (float) (Math.PI / 2)); - protected double xRadius; - protected double yRadius; - protected double zRadius; - - /** - * Creates an ellipsoid with the given x radius, y radius, and z radius. - * All radii must be greater than 0. - * @param xRadius the x radius. Must be greater than 0. - * @param yRadius the y radius. Must be greater than 0. - * @param zRadius the z radius. Must be greater than 0. - */ - public Ellipsoid(double xRadius, double yRadius, double zRadius) { - super(); - this.xRadius = Math.max(xRadius, MathUtil.EPSILON); - this.yRadius = Math.max(yRadius, MathUtil.EPSILON); - this.zRadius = Math.max(zRadius, MathUtil.EPSILON); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - double particleDensity = this.getParticleDensity(); - points.addAll(MathUtil.calculateEllipse(xRadius, zRadius, particleDensity, 2 * Math.PI)); - points.addAll(XY_ROTATION.transform(MathUtil.calculateEllipse(xRadius, yRadius, particleDensity, 2 * Math.PI))); - points.addAll(ZY_ROTATION.transform(MathUtil.calculateEllipse(yRadius, zRadius, particleDensity, 2 * Math.PI))); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - List ellipse; - double particleDensity = this.getParticleDensity(); - if (xRadius > zRadius) { - ellipse = XY_ROTATION.transform(MathUtil.calculateEllipse(xRadius, yRadius, particleDensity, 2 * Math.PI)); - } else { - ellipse = ZY_ROTATION.transform(MathUtil.calculateEllipse(yRadius, zRadius, particleDensity, 2 * Math.PI)); - } - points.addAll(generateEllipsoid(ellipse, 1)); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateFilled(Set points) { - List ellipse; - double radius = Math.max(xRadius, zRadius); - double particleDensity = this.getParticleDensity(); - int steps = (int) Math.round(radius / particleDensity); - for (int i = steps; i > 0; i--) { - double r = (i / (double) steps); - if (xRadius > zRadius) { - ellipse = XY_ROTATION.transform(MathUtil.calculateEllipse(xRadius * r, yRadius * r, particleDensity, 2 * Math.PI)); - } else { - ellipse = ZY_ROTATION.transform(MathUtil.calculateEllipse(yRadius * r, zRadius * r, particleDensity, 2 * Math.PI)); - } - points.addAll(generateEllipsoid(ellipse, r)); - } - } - - /** - * Generates the point on an ellipsoid with the given elliptical cross-section and third radius. - * - * @param ellipse the elliptical cross-section. - * @param radius the third radius. Must be greater than 0. - * @return the points on the ellipsoid. - */ - private Set generateEllipsoid(List ellipse, double radius) { - Set points = new LinkedHashSet<>(); - for (int i = 0; i < Math.ceil(ellipse.size() / 4.0); i++) { - double y = ellipse.get(i).getY(); - double theta = Math.asin(y / (yRadius * radius)); - for (Vector v2 : MathUtil.calculateEllipse(radius * xRadius * Math.cos(theta), radius * zRadius * Math.cos(theta), this.getParticleDensity(), 2 * Math.PI)) { - points.add(new Vector(v2.getX(), y, v2.getZ())); - points.add(new Vector(v2.getX(), -y, v2.getZ())); - } - } - points.addAll(MathUtil.calculateEllipse(radius * xRadius, radius * zRadius, this.getParticleDensity(), 2 * Math.PI)); - return points; - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(particleCount, 1); - switch (this.getStyle()) { - case OUTLINE -> { - // this is so fucking cringe - double h = (xRadius - yRadius) * (xRadius - yRadius) / ((xRadius + yRadius) + (xRadius + yRadius)); - double circumferenceXY = Math.PI * (xRadius + yRadius) * (1 + (3 * h / (10 + Math.sqrt(4 - 3 * h)))); - h = (xRadius - zRadius) * (xRadius - zRadius) / ((xRadius + zRadius) + (xRadius + zRadius)); - double circumferenceXZ = Math.PI * (xRadius + zRadius) * (1 + (3 * h / (10 + Math.sqrt(4 - 3 * h)))); - h = (yRadius - zRadius) * (yRadius - zRadius) / ((yRadius + zRadius) + (yRadius + zRadius)); - double circumferenceYZ = Math.PI * (yRadius + zRadius) * (1 + (3 * h / (10 + Math.sqrt(4 - 3 * h)))); - this.setParticleDensity((circumferenceXY + circumferenceXZ + circumferenceYZ) / particleCount); - } - case SURFACE -> { - double surfaceArea = 4 * Math.PI * Math.pow((Math.pow(xRadius * yRadius, 1.6) + Math.pow(xRadius * zRadius, 1.6) + Math.pow(zRadius * yRadius, 1.6)) / 3, 1 / 1.6); - this.setParticleDensity(Math.sqrt(surfaceArea / particleCount)); - } - case FILL -> { - double volume = 4 / 3.0 * Math.PI * xRadius * yRadius * zRadius; - this.setParticleDensity(Math.cbrt(volume / particleCount)); - } - } - } - - @Override - public double getLength() { - return xRadius * 2; - } - - @Override - public void setLength(double length) { - xRadius = Math.max(length / 2, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getWidth() { - return zRadius * 2; - } - - @Override - public void setWidth(double width) { - zRadius = Math.max(width / 2, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getHeight() { - return yRadius * 2; - } - - @Override - public void setHeight(double height) { - yRadius = Math.max(height / 2, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new Ellipsoid(xRadius, yRadius, zRadius)); - } - -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/EllipticalArc.java b/src/main/java/com/sovdee/skriptparticles/shapes/EllipticalArc.java deleted file mode 100644 index 8c5b2ed..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/EllipticalArc.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.Set; - -/** - * A shape that is a section of an ellipse. An arc, but an ellipse instead of a circle. - * The radii must be greater than 0, and the height must be non-negative. - * The cutoff angle will always be between 0 and 2π. - */ -public class EllipticalArc extends Ellipse implements CutoffShape { - - /** - * Creates an elliptical arc with the given x radius, z radius, and cutoff angle. - * The height will be 0. - * - * @param xRadius the x radius. Must be greater than 0. - * @param zRadius the z radius. Must be greater than 0. - * @param cutoffAngle the cutoff angle, in radians. will be clamped to between 0 and 2π. - */ - public EllipticalArc(double xRadius, double zRadius, double cutoffAngle) { - this(xRadius, zRadius, 0, cutoffAngle); - } - - /** - * Creates an elliptical arc with the given x radius, z radius, height, and cutoff angle. - * - * @param xRadius the x radius. Must be greater than 0. - * @param zRadius the z radius. Must be greater than 0. - * @param height the height. Must be non-negative. - * @param cutoffAngle the cutoff angle, in radians. will be clamped to between 0 and 2π. - */ - public EllipticalArc(double xRadius, double zRadius, double height, double cutoffAngle) { - super(xRadius, zRadius, height); - this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI * 2); - } - - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - generateFilled(points); - } - - @Override - public double getCutoffAngle() { - return cutoffAngle; - } - - @Override - public void setCutoffAngle(double cutoffAngle) { - this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI * 2); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new EllipticalArc(this.getLength(), this.getWidth(), this.getHeight(), cutoffAngle)); - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Heart.java b/src/main/java/com/sovdee/skriptparticles/shapes/Heart.java deleted file mode 100644 index 425aaf6..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Heart.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * Represents a heart shape with a length, width, and eccentricity. - * The length and width must be greater than 0, and the eccentricity must be greater than 1. Values of 3 or so are recommended. - * The eccentricity determines how much the heart is "pointed" at the bottom. - */ -public class Heart extends AbstractShape implements LWHShape { - - private double length; - private double width; - private double eccentricity; - - /** - * Creates a heart with the given length, width, and eccentricity. - * - * @param length the length. Must be greater than 0. - * @param width the width. Must be greater than 0. - * @param eccentricity the eccentricity. Must be greater than 1. - */ - public Heart(double length, double width, double eccentricity) { - super(); - this.length = Math.max(length, MathUtil.EPSILON); - this.width = Math.max(width, MathUtil.EPSILON); - this.eccentricity = Math.max(eccentricity, 1); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - points.addAll(MathUtil.calculateHeart(length / 2, width / 2, eccentricity, this.getParticleDensity())); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - double particleDensity = this.getParticleDensity(); - for (double w = width, l = length; w > 0 && l > 0; w -= particleDensity * 1.5, l -= particleDensity * 1.5) { - points.addAll(MathUtil.calculateHeart(l / 2, w / 2, eccentricity, particleDensity)); - } - } - - @Override - public void setParticleCount(int particleCount) { - // intentionally empty - } - - @Override - public double getHeight() { - return 0; - } - - @Override - public void setHeight(double height) { - // intentionally empty - } - - @Override - public double getWidth() { - return width; - } - - @Override - public void setWidth(double width) { - this.width = Math.max(width, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getLength() { - return length; - } - - @Override - public void setLength(double length) { - this.length = Math.max(length, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - /** - * Gets the eccentricity of this heart. - * The eccentricity determines how much the heart is "pointed" at the bottom. - * - * @return the eccentricity - */ - public double getEccentricity() { - return eccentricity; - } - - /** - * Sets the eccentricity of this heart. - * The eccentricity determines how much the heart is "pointed" at the bottom. - * - * @param eccentricity the eccentricity. Must be greater than 1. - */ - public void setEccentricity(double eccentricity) { - this.eccentricity = Math.max(1, eccentricity); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new Heart(length, width, eccentricity)); - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Helix.java b/src/main/java/com/sovdee/skriptparticles/shapes/Helix.java deleted file mode 100644 index f960591..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Helix.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * A helix is a spiral that is generated by rotating a line around a central axis. - * The slope of the helix is the rise of the helix per radian of rotation. - * The radius of the helix is the distance from the central point to the line. - * The height of the helix is the distance from the start of the line to the end of the line. - * The direction of the helix is the direction of rotation. - */ -public class Helix extends AbstractShape implements RadialShape, LWHShape { - - private double radius; - private double height; - private double slope; - private int direction = 1; - - /** - * Creates a helix with the given radius, height, and slope. Defaults to a clockwise rotation. - * @param radius the radius of the helix. Must be greater than 0. - * @param height the height of the helix. Must be greater than 0. - * @param slope the slope of the helix. Must be greater than 0. - */ - public Helix(double radius, double height, double slope) { - super(); - this.radius = Math.max(radius, MathUtil.EPSILON); - this.height = Math.max(height, MathUtil.EPSILON); - this.slope = Math.max(slope, MathUtil.EPSILON); - } - - /** - * Creates a helix with the given radius, height, slope, and direction. - * @param radius the radius of the helix. Must be greater than 0. - * @param height the height of the helix. Must be greater than 0. - * @param slope the slope of the helix. Must be greater than 0. - * @param direction the direction of the helix. Must be 1 or -1. - * @throws IllegalArgumentException if the direction is not 1 or -1. - */ - public Helix(double radius, double height, double slope, int direction) { - super(); - this.radius = Math.max(radius, MathUtil.EPSILON); - this.height = Math.max(height, MathUtil.EPSILON); - this.slope = Math.max(slope, MathUtil.EPSILON); - if (direction != 1 && direction != -1) - throw new IllegalArgumentException("Direction must be 1 or -1"); - this.direction = direction; - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - points.addAll(MathUtil.calculateHelix(radius, height, slope, direction, this.getParticleDensity())); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - double particleDensity = this.getParticleDensity(); - for (double r = radius; r > 0; r -= particleDensity) { - points.addAll(MathUtil.calculateHelix(r, height, slope, direction, particleDensity)); - } - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(particleCount, 1); - this.setParticleDensity(switch (this.getStyle()) { - case OUTLINE -> (Math.sqrt(slope * slope + radius * radius) * (height / slope) / particleCount); - case FILL, SURFACE -> Math.sqrt(slope * slope + radius * radius * (height / slope) / particleCount); - }); - } - - /** - * The slope of the helix is the rise of the helix per radian of rotation. - * @return the slope of the helix. Always greater than 0. - */ - public double getSlope() { - return slope; - } - - /** - * The slope of the helix is the rise of the helix per radian of rotation. - * @param slope the slope of the helix. Must be greater than 0. - */ - public void setSlope(double slope) { - this.slope = Math.max(slope, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - /** - * The direction of the helix is the direction of rotation. - * @return the direction of the helix. 1 for clockwise, -1 for counterclockwise. - */ - public int getDirection() { - return direction; - } - - /** - * The direction of the helix is the direction of rotation. - * @param direction the direction of the helix. 1 for clockwise, -1 for counterclockwise. - * @throws IllegalArgumentException if the direction is not 1 or -1. - */ - public void setDirection(int direction) { - if (direction != 1 && direction != -1) - throw new IllegalArgumentException("Direction must be 1 or -1"); - this.direction = direction; - this.setNeedsUpdate(true); - } - - @Override - public double getLength() { - return height; - } - - @Override - public void setLength(double length) { - height = Math.max(length, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getWidth() { - return 0; - } - - @Override - public void setWidth(double width) { - // intentionally empty - } - - @Override - public double getHeight() { - return height; - } - - @Override - public void setHeight(double height) { - this.height = Math.max(height, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getRadius() { - return radius; - } - - @Override - public void setRadius(double radius) { - this.radius = Math.max(radius, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new Helix(radius, height, slope, direction)); - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/IrregularPolygon.java b/src/main/java/com/sovdee/skriptparticles/shapes/IrregularPolygon.java deleted file mode 100644 index a9224e0..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/IrregularPolygon.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * A polygon with an arbitrary number of vertices. The polygon is assumed to be parallel to the xz plane. - * The height of the polygon is the distance from the lowest vertex to the highest vertex, or the given height from the lowest vertex. - */ -public class IrregularPolygon extends AbstractShape implements LWHShape { - - private final List vertices; - private double height; - - /** - * Creates a polygon with the given vertices. The polygon is assumed to be parallel to the xz plane. - * The height of the polygon is the distance from the lowest vertex to the highest vertex. - * @param vertices the vertices of the polygon. Must be greater than 2. - * @throws IllegalArgumentException if the vertices are less than 3. - */ - public IrregularPolygon(Collection vertices) { - super(); - if (vertices.size() < 3) - throw new IllegalArgumentException("A polygon must have at least 3 vertices."); - setBounds(vertices); - this.vertices = flattenVertices(vertices); - } - - /** - * Creates a polygon with the given vertices and height. The polygon is assumed to be parallel to the xz plane. - * @param vertices the vertices of the polygon. Must be greater than 2. - * @param height the height of the polygon. Must be non-negative. - * @throws IllegalArgumentException if the vertices are less than 3. - */ - public IrregularPolygon(Collection vertices, double height) { - this(vertices); - this.height = Math.max(height, 0); - } - - /** - * Flattens the vertices to the xz plane. - * @param vertices the vertices to flatten. Does not modify the original vertices. - * @return the flattened vertices. - */ - @Contract(pure = true, value = "_ -> new") - private List flattenVertices(Collection vertices) { - List flattened = new ArrayList<>(); - for (Vector v : vertices) { - flattened.add(v.clone().setY(0)); - } - return flattened; - } - - /** - * Sets the height of the polygon to the distance from the lowest vertex to the highest vertex. - * @param vertices the vertices of the polygon. - */ - private void setBounds(Collection vertices) { - double low = 9999999; - double high = -9999999; - for (Vector v : vertices) { - if (v.getY() < low) low = v.getY(); - if (v.getY() > high) high = v.getY(); - } - this.height = high - low; - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - double particleDensity = this.getParticleDensity(); - points.addAll(MathUtil.connectPoints(vertices, particleDensity)); - points.addAll(MathUtil.calculateLine(vertices.get(0), vertices.get(vertices.size() - 1), particleDensity)); - if (height != 0) { - Set upperPoints = new LinkedHashSet<>(); - for (Vector v : points) { - upperPoints.add(new Vector(v.getX(), height, v.getZ())); - } - points.addAll(upperPoints); - for (Vector v : vertices) { - points.addAll(MathUtil.calculateLine(v, new Vector(v.getX(), height, v.getZ()), particleDensity)); - } - } - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(particleCount, 1); - double perimeter = 0; - for (int i = 0; i < vertices.size() - 1; i++) { - perimeter += vertices.get(i).distance(vertices.get(i + 1)); - } - perimeter += vertices.get(0).distance(vertices.get(vertices.size() - 1)); - perimeter *= 2; - perimeter += vertices.size() * height; - this.setParticleDensity(perimeter / particleCount); - this.setNeedsUpdate(true); - } - - @Override - public double getLength() { - return 0; - } - - @Override - public void setLength(double length) { - // intentionally left blank - } - - @Override - public double getWidth() { - return 0; - } - - @Override - public void setWidth(double width) { - // intentionally left blank - } - - @Override - public double getHeight() { - return height; - } - - @Override - public void setHeight(double height) { - this.height = Math.max(height, 0); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new IrregularPolygon(vertices, height)); - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/LWHShape.java b/src/main/java/com/sovdee/skriptparticles/shapes/LWHShape.java deleted file mode 100644 index ce4b957..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/LWHShape.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -/** - * Represents a shape that has a length, width, and/or height. - * Neither the length, width, nor height may be negative. - */ -public interface LWHShape extends Shape { - - /** - * @return The length of the shape. - */ - double getLength(); - - /** - * Sets the length of the shape. - * @param length The length of the shape. Must be non-negative. - */ - void setLength(double length); - - /** - * @return The width of the shape. - */ - double getWidth(); - - /** - * Sets the width of the shape. - * @param width The width of the shape. Must be non-negative. - */ - void setWidth(double width); - - /** - * @return The height of the shape. - */ - double getHeight(); - - /** - * Sets the height of the shape. - * @param height The height of the shape. Must be non-negative. - */ - void setHeight(double height); -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Line.java b/src/main/java/com/sovdee/skriptparticles/shapes/Line.java deleted file mode 100644 index 7d4e327..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Line.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.DynamicLocation; -import com.sovdee.skriptparticles.util.MathUtil; -import com.sovdee.skriptparticles.util.Quaternion; -import org.bukkit.util.Vector; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.jetbrains.annotations.Contract; - -import java.util.Set; - -/** - * A line shape. This shape is defined by two points, a start and an end. - * These points can be relative ({@link Vector}) or absolute ({@link DynamicLocation}). - */ -public class Line extends AbstractShape implements LWHShape { - - private Vector start; - private Vector end; - - private @Nullable DynamicLocation startLocation; - private @Nullable DynamicLocation endLocation; - private boolean isDynamic = false; - - /** - * Creates a new line shape with the start point at the origin and the end point at the given vector. - * The vector cannot be the origin. - * @param end the end point of the line - * @throws IllegalArgumentException if the end vector is the origin - */ - public Line(Vector end) { - this(new Vector(0, 0, 0), end); - } - - /** - * Creates a new line shape with the start and end points at the given vectors. - * The vectors cannot be the same. - * @param start the start point of the line - * @param end the end point of the line - * @throws IllegalArgumentException if the start and end vectors are the same - */ - public Line(Vector start, Vector end) { - super(); - if (start.equals(end)) - throw new IllegalArgumentException("Start and end locations cannot be the same."); - this.start = start; - this.end = end; - } - - /** - * Creates a new line shape with the start and end points at the given locations. - * The locations cannot be the same. - * @param start the start point of the line - * @param end the end point of the line - */ - public Line(DynamicLocation start, DynamicLocation end) { - super(); - if (start.equals(end)) - throw new IllegalArgumentException("Start and end locations cannot be the same."); - if (start.isDynamic() || end.isDynamic()) { - this.startLocation = start.clone(); - this.endLocation = end.clone(); - this.isDynamic = true; - } - this.start = new Vector(0, 0, 0); - this.end = end.getLocation().toVector().subtract(start.getLocation().toVector()); - if (this.end.equals(this.start)) - throw new IllegalArgumentException("Start and end locations cannot be the same."); - - this.setLocation(start.clone()); - } - - - @Override - @Contract(pure = true) - public Set getPoints(Quaternion orientation) { - Set points = super.getPoints(orientation); - if (isDynamic) - // Ensure that the points are always needing to be updated if the start or end location is dynamic - this.setNeedsUpdate(true); - return points; - } - - @Override - @Contract(pure = true) - public void generatePoints(Set points) { - if (isDynamic) { - assert startLocation != null; - assert endLocation != null; - this.start = new Vector(0, 0, 0); - this.end = endLocation.getLocation().toVector().subtract(startLocation.getLocation().toVector()); - } - super.generatePoints(points); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - points.addAll(MathUtil.calculateLine(start, end, this.getParticleDensity())); - } - - /** - * Gets the start point of the line as a vector. May change between calls if the start point is dynamic. - * @return the start point of the line - */ - public Vector getStart() { - if (isDynamic) { - assert startLocation != null; - return startLocation.getLocation().toVector(); - } - return start.clone(); - } - - /** - * Sets the start point of the line as a vector. - * @param start the start point of the line. Must not be identical to the end point. - * @throws IllegalArgumentException if the start and end points are identical. - */ - public void setStart(Vector start) { - if (start.equals(end)) - throw new IllegalArgumentException("Start and end points must not be identical"); - this.start = start.clone(); - } - - /** - * Gets the end point of the line as a vector. May change between calls if the end point is dynamic. - * @return the end point of the line - */ - public Vector getEnd() { - if (isDynamic) { - assert endLocation != null; - return endLocation.getLocation().toVector(); - } - return end.clone(); - } - - /** - * Sets the end point of the line as a vector. - * @param end the end point of the line. Must not be identical to the start point. - * @throws IllegalArgumentException if the start and end points are identical. - */ - public void setEnd(Vector end) { - if (end.equals(start)) - throw new IllegalArgumentException("Start and end points must not be identical"); - this.end = end.clone(); - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(particleCount, 1); - this.setParticleDensity(end.clone().subtract(start).length() / particleCount); - this.setNeedsUpdate(true); - } - - @Override - public double getLength() { - return start.clone().subtract(end).length(); - } - - @Override - public void setLength(double length) { - length = Math.max(length, MathUtil.EPSILON); - Vector direction = end.clone().subtract(start).normalize(); - end = start.clone().add(direction.multiply(length)); - } - - @Override - public double getWidth() { - return 0; - } - - @Override - public void setWidth(double width) { - // intentionally left blank - } - - @Override - public double getHeight() { - return 0; - } - - @Override - public void setHeight(double height) { - // intentionally left blank - } - - @Override - @Contract("-> new") - public Shape clone() { - Line line; - if (isDynamic) { - assert this.startLocation != null; - assert this.endLocation != null; - line = (new Line(this.startLocation, this.endLocation)); - } else { - line = (new Line(this.start, this.end)); - } - line.isDynamic = this.isDynamic; - return this.copyTo(line); - } - - public String toString() { - return "Line from " + this.start + " to " + this.end; - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/PolyShape.java b/src/main/java/com/sovdee/skriptparticles/shapes/PolyShape.java deleted file mode 100644 index 0b9df43..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/PolyShape.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -/** - * Represents a shape that has a number of sides and a side length. - * The number of sides must be greater than 2. - * The side length must be greater than 0. - */ -public interface PolyShape extends Shape { - - /** - * @return The number of sides of the shape. - */ - int getSides(); - - /** - * Sets the number of sides of the shape. - * @param sides The number of sides of the shape. Must be greater than 2. - */ - void setSides(int sides); - - /** - * @return The side length of the shape. - */ - double getSideLength(); - - /** - * Sets the side length of the shape. - * @param sideLength The side length of the shape. Must be greater than 0. - */ - void setSideLength(double sideLength); -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/RadialShape.java b/src/main/java/com/sovdee/skriptparticles/shapes/RadialShape.java deleted file mode 100644 index 06bc449..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/RadialShape.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -/** - * Represents a shape that has a radius. - * The radius must be greater than 0. - */ -public interface RadialShape extends Shape { - - /** - * Gets the radius of the shape. - * @return The radius of the shape. - */ - double getRadius(); - - /** - * Sets the radius of the shape. - * @param radius The radius of the shape. Must be greater than 0. - */ - void setRadius(double radius); - -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Rectangle.java b/src/main/java/com/sovdee/skriptparticles/shapes/Rectangle.java deleted file mode 100644 index 53403e5..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Rectangle.java +++ /dev/null @@ -1,302 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.DynamicLocation; -import com.sovdee.skriptparticles.util.MathUtil; -import com.sovdee.skriptparticles.util.Quaternion; -import org.bukkit.Location; -import org.bukkit.util.Vector; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.jetbrains.annotations.Contract; - -import java.util.LinkedHashSet; -import java.util.Set; - - -/** - * A rectangle shape, defined by a plane and a length and width. - * Can be defined by two corners, which can be relative ({@link Vector}) or absolute ({@link DynamicLocation}). - */ -public class Rectangle extends AbstractShape implements LWHShape { - - private Plane plane; - private double halfLength; - private double halfWidth; - private double lengthStep = 1.0; - private double widthStep = 1.0; - private Vector centerOffset = new Vector(0, 0, 0); - private @Nullable DynamicLocation negativeCorner; - private @Nullable DynamicLocation positiveCorner; - private boolean isDynamic = false; - - /** - * Creates a new rectangle shape with the given plane and length and width. - * @param length the length of the rectangle. Must be greater than 0. - * @param width the width of the rectangle. Must be greater than 0. - * @param plane the plane of the rectangle. - */ - public Rectangle(double length, double width, Plane plane) { - super(); - this.plane = plane; - this.halfLength = Math.max(length / 2, MathUtil.EPSILON); - this.halfWidth = Math.max(width / 2, MathUtil.EPSILON); - calculateSteps(); - } - - /** - * Creates a new rectangle shape from the given corners and plane. - * The corners may not be the same. - * @param cornerA the first corner of the rectangle. - * @param cornerB the second corner of the rectangle. - * @param plane the plane of the rectangle. - * @throws IllegalArgumentException if the corners are the same - */ - public Rectangle(Vector cornerA, Vector cornerB, Plane plane) { - super(); - if (cornerA.equals(cornerB)) - throw new IllegalArgumentException("Corners cannot be the same."); - this.plane = plane; - setLengthWidth(cornerA, cornerB); - centerOffset = cornerB.clone().add(cornerA).multiply(0.5); - switch (plane) { - case XZ -> centerOffset.setY(0); - case XY -> centerOffset.setZ(0); - case YZ -> centerOffset.setX(0); - } - calculateSteps(); - } - - /** - * Creates a new rectangle shape from the given corners and plane. - * The corners may not be the same. - * @param cornerA the first corner of the rectangle. - * @param cornerB the second corner of the rectangle. - * @param plane the plane of the rectangle. - * @throws IllegalArgumentException if the corners are the same - */ - public Rectangle(DynamicLocation cornerA, DynamicLocation cornerB, Plane plane) { - super(); - if (cornerA.equals(cornerB)) - throw new IllegalArgumentException("Corners cannot be the same."); - - this.plane = plane; - Location cornerALocation = cornerA.getLocation(); - Location cornerBLocation = cornerB.getLocation(); - if (cornerA.equals(cornerB)) - throw new IllegalArgumentException("Corners cannot be the same."); - - if (cornerA.isDynamic() || cornerB.isDynamic()) { - this.negativeCorner = cornerA.clone(); - this.positiveCorner = cornerB.clone(); - isDynamic = true; - } else { - setLengthWidth(cornerALocation, cornerBLocation); - } - // get center of rectangle - Vector offset = cornerBLocation.toVector().subtract(cornerALocation.toVector()).multiply(0.5); - switch (plane) { - case XZ -> offset.setY(0); - case XY -> offset.setZ(0); - case YZ -> offset.setX(0); - } - this.setLocation(new DynamicLocation(cornerALocation.clone().add(offset))); - calculateSteps(); - } - - /** - * Sets the length and width of the rectangle based on the given corners. - * @param cornerA the first corner - * @param cornerB the second corner - */ - private void setLengthWidth(Location cornerA, Location cornerB) { - setLengthWidth(cornerA.toVector(), cornerB.toVector()); - } - - /** - * Sets the length and width of the rectangle based on the given corners. - * @param cornerA the first corner - * @param cornerB the second corner - */ - private void setLengthWidth(Vector cornerA, Vector cornerB) { - double length = switch (plane) { - case XZ, XY -> Math.abs(cornerA.getX() - cornerB.getX()); - case YZ -> Math.abs(cornerA.getY() - cornerB.getY()); - }; - double width = switch (plane) { - case XZ, YZ -> Math.abs(cornerA.getZ() - cornerB.getZ()); - case XY -> Math.abs(cornerA.getY() - cornerB.getY()); - }; - this.halfWidth = Math.abs(width) / 2; - this.halfLength = Math.abs(length) / 2; - } - - /** - * Creates a {@link Vector} in the plane of the rectangle from the given length and width. - * @param length X or Y coordinate, depending on the plane. - * @param width Z or Y coordinate, depending on the plane. - * @return a vector in the plane of the rectangle. - */ - @Contract(pure = true, value = "_, _ -> new") - private Vector vectorFromLengthWidth(double length, double width) { - return switch (plane) { - case XZ -> new Vector(length, 0, width); - case XY -> new Vector(length, width, 0); - case YZ -> new Vector(0, length, width); - }; - } - - /** - * Calculates the nearest factor to particleDensity as step size for the x and z plane. - * Used to ensure the shape has a uniform density of particles. - */ - private void calculateSteps() { - double particleDensity = this.getParticleDensity(); - lengthStep = 2 * halfWidth / Math.round(2 * halfWidth / particleDensity); - widthStep = 2 * halfLength / Math.round(2 * halfLength / particleDensity); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - for (double l = -halfLength + widthStep; l < halfLength; l += widthStep) { - points.add(vectorFromLengthWidth(l, -halfWidth)); - points.add(vectorFromLengthWidth(l, halfWidth)); - } - for (double w = -halfWidth; w <= halfWidth; w += lengthStep) { - points.add(vectorFromLengthWidth(-halfLength, w)); - points.add(vectorFromLengthWidth(halfLength, w)); - } - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - for (double w = -halfWidth; w <= halfWidth; w += lengthStep) { - for (double l = -halfLength; l <= halfLength; l += widthStep) { - points.add(vectorFromLengthWidth(l, w)); - } - } - } - - @Override - @Contract(pure = true) - public void generatePoints(Set points) { - if (isDynamic) { - assert positiveCorner != null; - assert negativeCorner != null; - Location pos = positiveCorner.getLocation(); - Location neg = negativeCorner.getLocation(); - setLengthWidth(neg, pos); - // get center of rectangle - Vector offset = pos.toVector().subtract(neg.toVector()).multiply(0.5); - switch (plane) { - case XZ -> offset.setY(0); - case XY -> offset.setZ(0); - case YZ -> offset.setX(0); - } - this.setLocation(new DynamicLocation(neg.clone().add(offset))); - } - calculateSteps(); - super.generatePoints(points); - points.forEach(vector -> vector.add(centerOffset)); - } - - @Override - public Set getPoints(Quaternion orientation) { - Set points = super.getPoints(orientation); - if (isDynamic) - // Ensure that the points are always needing to be updated if the start or end location is dynamic - this.setNeedsUpdate(true); - return points; - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(particleCount, 1); - switch (this.getStyle()) { - case FILL, SURFACE -> this.setParticleDensity(Math.sqrt(4 * halfWidth * halfLength / particleCount)); - case OUTLINE -> this.setParticleDensity(4 * (halfWidth + halfLength) / particleCount); - } - this.setNeedsUpdate(true); - } - - @Override - public double getLength() { - return halfLength * 2; - } - - @Override - public void setLength(double length) { - this.halfLength = Math.max(length / 2, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getWidth() { - return halfWidth * 2; - } - - @Override - public void setWidth(double width) { - this.halfWidth = Math.max(width / 2, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getHeight() { - return 0; - } - - @Override - public void setHeight(double height) { - // intentionally left blank - } - - /** - * Gets the plane of the rectangle. - * @return the plane of the rectangle. - */ - public Plane getPlane() { - return plane; - } - - /** - * Sets the plane of the rectangle. Ensures the shape will be updated on the next call to {@link Shape#generatePoints(Set)}. - * @param plane the plane of the rectangle. - */ - public void setPlane(Plane plane) { - this.plane = plane; - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - Rectangle rectangle; - if (isDynamic) { - assert negativeCorner != null; - assert positiveCorner != null; - rectangle = (new Rectangle(negativeCorner, positiveCorner, plane)); - } else { - rectangle = (new Rectangle(this.getLength(), this.getWidth(), plane)); - } - rectangle.isDynamic = this.isDynamic; - return this.copyTo(rectangle); - } - - @Override - public String toString() { - String axis = this.plane.toString().toLowerCase(); - if (isDynamic) - return axis + " rectangle from " + negativeCorner + " to " + positiveCorner; - return axis + " rectangle with length " + this.getLength() + " and width " + this.getWidth(); - } - - /** - * Represents the plane of a rectangle. - */ - public enum Plane { - XZ, XY, YZ - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/RegularPolygon.java b/src/main/java/com/sovdee/skriptparticles/shapes/RegularPolygon.java deleted file mode 100644 index 1b062d2..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/RegularPolygon.java +++ /dev/null @@ -1,189 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.Set; - -/** - * A regular polygon shape. This shape is defined by a radius and either a side count or an angle between adjacent vertices. - * The polygon can be a prism if a height is given. - */ -public class RegularPolygon extends AbstractShape implements PolyShape, RadialShape, LWHShape { - - private double angle; - private double radius; - private double height; - - /** - * Creates a new regular polygon shape with the given side count and radius. - * @param sides the number of sides of the polygon. Must be greater than 2. - * @param radius the radius of the polygon. Must be greater than 0. - */ - public RegularPolygon(int sides, double radius) { - this((Math.PI * 2) / sides, radius, 0); - } - - /** - * Creates a new regular polygon shape with the given angle and radius. - * @param angle the angle between adjacent vertices in radians. Must evenly divide 2pi and be between 0 (exclusive) and 2pi/3 (inclusive). - * @param radius the radius of the polygon. Must be greater than 0. - */ - public RegularPolygon(double angle, double radius) { - this(angle, radius, 0); - } - - /** - * Creates a new regular polygon shape with the given side count, radius, and height. - * @param sides the number of sides of the polygon. Must be greater than 2. - * @param radius the radius of the polygon. Must be greater than 0. - * @param height the height of the polygon. Must be non-negative. - */ - public RegularPolygon(int sides, double radius, double height) { - this((Math.PI * 2) / sides, radius, height); - } - - /** - * Creates a new regular polygon shape with the given angle, radius, and height. - * @param angle the angle between adjacent vertices in radians. Should evenly divide 2pi and must be between 0 (exclusive) and 2pi/3 (inclusive). - * @param radius the radius of the polygon. Must be greater than 0. - * @param height the height of the polygon. Must be non-negative. - */ - public RegularPolygon(double angle, double radius, double height) { - super(); - this.angle = MathUtil.clamp(angle, MathUtil.EPSILON, Math.PI * 2 / 3); - this.radius = Math.max(radius, MathUtil.EPSILON); - this.height = Math.max(height, 0); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - if (height == 0) - points.addAll(MathUtil.calculateRegularPolygon(this.radius, this.angle, this.getParticleDensity(), true)); - else - points.addAll(MathUtil.calculateRegularPrism(this.radius, this.angle, this.height, this.getParticleDensity(), true)); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - if (height == 0) - points.addAll(MathUtil.calculateRegularPolygon(this.radius, this.angle, this.getParticleDensity(), false)); - else - points.addAll(MathUtil.calculateRegularPrism(this.radius, this.angle, this.height, this.getParticleDensity(), false)); - } - - @SuppressWarnings("ConstantConditions") - @Override - public void generateFilled(Set points) { - if (height == 0) - generateSurface(points); - else { - double particleDensity = this.getParticleDensity(); - Set polygon = MathUtil.calculateRegularPolygon(this.radius, this.angle, particleDensity, false); - points.addAll(MathUtil.fillVertically(polygon, height, particleDensity)); - } - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(particleCount, 1); - int sides = getSides(); - this.setParticleDensity(switch (this.getStyle()) { - case OUTLINE -> { - if (height == 0) - yield 2 * sides * radius * Math.sin(angle / 2) / particleCount; - yield (4 * sides * radius * Math.sin(angle / 2) + height * sides) / particleCount; - } - case SURFACE -> { - if (height == 0) - yield Math.sqrt(sides * radius * radius * Math.sin(angle) / 2 / particleCount); - yield (sides * radius * radius * Math.sin(angle) + getSideLength() * sides * height) / particleCount; - } - case FILL -> (sides * radius * radius * Math.sin(angle) * height) / particleCount; - }); - this.setNeedsUpdate(true); - } - - @Override - public int getSides() { - return (int) (Math.PI * 2 / this.angle); - } - - @Override - public void setSides(int sides) { - this.angle = (Math.PI * 2) / Math.max(sides, 3); - this.setNeedsUpdate(true); - } - - @Override - public double getSideLength() { - return this.radius * 2 * Math.sin(this.angle / 2); - } - - @Override - public void setSideLength(double sideLength) { - sideLength = Math.max(sideLength, MathUtil.EPSILON); - this.radius = sideLength / (2 * Math.sin(this.angle / 2)); - this.radius = Math.max(radius, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getRadius() { - return this.radius; - } - - @Override - public void setRadius(double radius) { - this.radius = Math.max(radius, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - public double getLength() { - return 0; - } - - @Override - public void setLength(double length) { - // intentionally empty - } - - @Override - public double getWidth() { - return 0; - } - - @Override - public void setWidth(double width) { - // intentionally empty - } - - @Override - public double getHeight() { - return height; - } - - @Override - public void setHeight(double height) { - this.height = Math.max(height, 0); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new RegularPolygon(angle, radius, height)); - } - - @Override - public String toString() { - return "regular polygon with " + getSides() + " sides and radius " + getRadius(); - } - -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/RegularPolyhedron.java b/src/main/java/com/sovdee/skriptparticles/shapes/RegularPolyhedron.java deleted file mode 100644 index 9f7f0bd..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/RegularPolyhedron.java +++ /dev/null @@ -1,261 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import com.sovdee.skriptparticles.util.Quaternion; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * A regular polyhedron shape. This shape is defined by a center point and a radius, as well as the number of faces. - * The four regular polyhedra are stored as static constant arrays of rotations, each representing a face of the polyhedron. - */ -public class RegularPolyhedron extends AbstractShape implements RadialShape, PolyShape { - - private static final Quaternion[] TETRAHEDRON_FACES = { - new Quaternion(1.0, 0.0, 0.0, 0), - new Quaternion(-0.5, -0.0, -0.288675134594813, 0.816496580927726), - new Quaternion(0.5, 0.0, -0.288675134594813, 0.816496580927726), - new Quaternion(0.0, 0.0, 0.5773502691896258, 0.816496580927726) - }; - private static final Quaternion[] OCTAHEDRON_FACES = { - new Quaternion(0.0, 0.0, 0.45970084338098305, 0.8880738339771153), - new Quaternion(0.3250575836718681, 0.6279630301995544, 0.32505758367186816, 0.6279630301995545), - new Quaternion(0.45970084338098305, 0.8880738339771153, 0, 0), - new Quaternion(0.32505758367186816, 0.6279630301995545, -0.3250575836718681, -0.6279630301995544), - new Quaternion(0.0, 0.0, 0.8880738339771153, -0.45970084338098316), - new Quaternion(0.6279630301995544, -0.3250575836718682, 0.6279630301995545, -0.3250575836718682), - new Quaternion(0.8880738339771153, -0.45970084338098316, 0, 0), - new Quaternion(0.6279630301995545, -0.3250575836718682, -0.6279630301995544, 0.3250575836718682) - }; - private static final Quaternion[] ICOSAHEDRON_FACES = { - new Quaternion(1.0, 0.0, 0.0, 0), - new Quaternion(0.0, 1.0, 0.0, 0), - new Quaternion(0.3090169943749475, 0.0, 0.17841104488654494, 0.9341723589627158), - new Quaternion(-0.5, 0.8090169943749475, 0.288675134594813, 0.110264089708268), - new Quaternion(0, 0.8090169943749475, 0.5773502691896256, -0.110264089708268), - new Quaternion(0.3090169943749475, 0.0, -0.1784110448865451, -0.9341723589627157), - new Quaternion(0.5, -0.8090169943749475, 0.288675134594813, 0.110264089708268), - new Quaternion(0, 0.8090169943749475, -0.5773502691896261, 0.110264089708268), - new Quaternion(0.0, 0.0, 0.35682208977309, -0.9341723589627157), - new Quaternion(-0.5, -0.8090169943749475, -0.288675134594813, -0.110264089708268), - new Quaternion(0.5, 0.8090169943749475, -0.288675134594813, -0.110264089708268), - new Quaternion(-0.8090169943749475, -0.0, -0.46708617948135794, 0.35682208977309), - new Quaternion(0.3090169943749475, 0.5, -0.7557613140761709, -0.288675134594813), - new Quaternion(0.8090169943749475, 0.0, -0.46708617948135794, 0.35682208977309), - new Quaternion(-0.5, -0.5, -0.6454972243679027, 0.288675134594813), - new Quaternion(0.0, 0.0, 0.9341723589627157, 0.35682208977309), - new Quaternion(-0.8090169943749475, 0.5, 0.110264089708268, -0.288675134594813) - }; - private static final Quaternion[] DODECAHEDRON_FACES = { - new Quaternion(0.0, 0.3090169943749475, 0.0, 0.9510565162951536), - new Quaternion(-0.3090169943749475, 0, 0.9510565162951536, 0), - new Quaternion(0.0, 0.0, 0.8506508083520399, 0.5257311121191337), - new Quaternion(0.0, 0.0, 0.5257311121191337, -0.8506508083520399), - new Quaternion(0.5, 0.3090169943749475, 0.6881909602355868, 0.42532540417602), - new Quaternion(0.3090169943749475, -0.5, 0.42532540417602, -0.6881909602355868), - new Quaternion(0.8090169943749475, 0.5, 0.2628655560595668, 0.1624598481164532), - new Quaternion(0.5, -0.8090169943749475, 0.1624598481164532, -0.2628655560595668), - new Quaternion(0.8090169943749475, 0.5, -0.2628655560595668, -0.1624598481164532), - new Quaternion(0.5, -0.8090169943749475, -0.1624598481164532, 0.2628655560595668), - new Quaternion(0.5, 0.3090169943749475, -0.6881909602355868, -0.42532540417602), - new Quaternion(0.3090169943749475, -0.5, -0.42532540417602, 0.6881909602355868) - }; - private double radius; - private int faces; - - /** - * Generates a regular polyhedron with the given radius and number of faces. - * - * @param radius the radius of the polyhedron. Must be greater than 0. - * @param faces the number of faces of the polyhedron. Must be 4, 8, 12, or 20. - */ - public RegularPolyhedron(double radius, int faces) { - super(); - this.radius = Math.max(radius, MathUtil.EPSILON); - this.faces = switch (faces) { - case 4, 8, 12, 20 -> faces; - default -> 4; - }; - } - - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - points.addAll(switch (faces) { - case 4 -> generatePolyhedron(TETRAHEDRON_FACES, radius); - case 8 -> generatePolyhedron(OCTAHEDRON_FACES, radius); - case 20 -> generatePolyhedron(ICOSAHEDRON_FACES, radius); - case 12 -> generatePolyhedron(DODECAHEDRON_FACES, radius); - default -> new HashSet<>(); - }); - } - - @SuppressWarnings("ConstantConditions") - @Override - public void generateFilled(Set points) { - double step = radius / Math.round(radius / this.getParticleDensity()); - switch (faces) { - case 4: - for (double i = radius; i > 0; i -= step) { - points.addAll(generatePolyhedron(TETRAHEDRON_FACES, i)); - } - break; - case 8: - for (double i = radius; i > 0; i -= step) { - points.addAll(generatePolyhedron(OCTAHEDRON_FACES, i)); - } - break; - case 12: - for (double i = radius; i > 0; i -= step) { - points.addAll(generatePolyhedron(DODECAHEDRON_FACES, i)); - } - break; - case 20: - for (double i = radius; i > 0; i -= step) { - points.addAll(generatePolyhedron(ICOSAHEDRON_FACES, i)); - } - break; - } - } - - /** - * Generates a polyhedron from the given set of face rotations and a radius. - * - * @param rotations the rotations of the faces of the polyhedron - * @param radius the radius of the polyhedron - * @return a set of vectors representing the polyhedron - */ - @Contract(pure = true, value = "_, _ -> new") - private Set generatePolyhedron(Quaternion[] rotations, double radius) { - Set points = new LinkedHashSet<>(); - int sides = this.faces == 12 ? 5 : 3; - // todo: get rid of magic numbers - double sideLength = switch (faces) { - case 4 -> radius / 0.6123724356957945; - case 8 -> radius / 0.7071067811865; - case 12 -> radius / 1.401258538; - case 20 -> radius / 0.9510565162951535; - default -> 0.0; - }; - double inscribedRadius = switch (this.faces) { - case 4 -> sideLength / 4.89897948556; - case 8 -> sideLength * 0.408248290; - case 12 -> sideLength * 1.113516364; - case 20 -> sideLength * 0.7557613141; - default -> 1; - }; - Vector offset = new Vector(0, inscribedRadius, 0); - double faceRadius = sideLength / (2 * Math.sin(Math.PI / sides)); - Style style = this.getStyle(); - for (Quaternion rotation : rotations) { - Set facePoints = new LinkedHashSet<>(switch (style) { - case OUTLINE -> generateFaceOutline(sides, faceRadius); - case FILL, SURFACE -> generateFaceSurface(sides, faceRadius); - }); - facePoints.forEach(point -> rotation.transform(point.add(offset))); - points.addAll(facePoints); - } - return points; - } - - /** - * Generates the outline of a face of the polyhedron. The face is a regular polygon with the given number of sides. - * @param sides the number of sides of the face - * @param radius the radius of the face - * @return a set of vectors representing the outline of the face - */ - @Contract(pure = true, value = "_, _ -> new") - private Set generateFaceOutline(int sides, double radius) { - return new LinkedHashSet<>(MathUtil.calculateRegularPolygon(radius, 2 * Math.PI / sides, this.getParticleDensity(), true)); - } - - /** - * Generates the surface of a face of the polyhedron. The face is a regular polygon with the given number of sides. - * @param sides the number of sides of the face - * @param radius the radius of the face - * @return a set of vectors representing the surface of the face - */ - @Contract(pure = true, value = "_, _ -> new") - private Set generateFaceSurface(int sides, double radius) { - HashSet facePoints = new LinkedHashSet<>(); - double particleDensity = this.getParticleDensity(); - double apothem = radius * Math.cos(Math.PI / sides); - double radiusStep = radius / Math.round(apothem / particleDensity); - for (double subRadius = radius; subRadius > 0; subRadius -= radiusStep) { - facePoints.addAll(MathUtil.calculateRegularPolygon(subRadius, 2 * Math.PI / sides, particleDensity, false)); - } - facePoints.add(new Vector(0, 0, 0)); - return facePoints; - } - - @Override - public void setParticleCount(int particleCount) { - // intentionally left blank - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new RegularPolyhedron(radius, faces)); - } - - @Override - public int getSides() { - return faces; - } - - @Override - public void setSides(int sides) { - switch (sides) { - case 4, 8, 12, 20 -> this.faces = sides; - default -> { - // intentionally does not throw an exception - return; - } - } - this.setNeedsUpdate(true); - } - - @Override - public double getSideLength() { - return switch (faces) { - case 4 -> radius / 0.6123724356957945; - case 8 -> radius / 0.7071067811865; - case 12 -> radius / 1.401258538; - case 20 -> radius / 0.9510565162951535; - default -> 0.0; - }; - } - - @Override - public void setSideLength(double sideLength) { - sideLength = Math.max(sideLength, MathUtil.EPSILON); - switch (faces) { - case 4 -> this.radius = sideLength * 0.6123724356957945; - case 8 -> this.radius = sideLength * 0.7071067811865; - case 12 -> this.radius = sideLength * 1.401258538; - case 20 -> this.radius = sideLength * 0.9510565162951535; - default -> { - return; - } - } - this.setNeedsUpdate(true); - } - - @Override - public double getRadius() { - return radius; - } - - @Override - public void setRadius(double radius) { - this.radius = Math.max(radius, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Shape.java b/src/main/java/com/sovdee/skriptparticles/shapes/Shape.java deleted file mode 100644 index 68d426b..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Shape.java +++ /dev/null @@ -1,402 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.particles.Particle; -import com.sovdee.skriptparticles.util.DynamicLocation; -import com.sovdee.skriptparticles.util.Quaternion; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; -import org.jetbrains.annotations.Contract; -import org.joml.Quaternionf; - -import java.util.Collection; -import java.util.Comparator; -import java.util.Set; -import java.util.UUID; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -/** - * Represents a shape that can be drawn with particles. - * Shapes should have a style, which determines how they are drawn. - * They should also have an orientation, a scale, and an offset which are applied when drawing. - */ -public interface Shape extends Cloneable { - /** - * Gets the points for the shape. - * Attempts to use cached points if possible, by checking if the shape has been modified. Updates the cached points if necessary. - * Uses the shape's orientation to rotate the shape. - * - * @return A set of points that make up the shape. - */ - Set getPoints(); - - /** - * Sets the points for the shape. - * - * @param points The points to use. - */ - void setPoints(Set points); - - /** - * Gets the points for the shape. - * Attempts to use cached points if possible, by checking if the shape has been modified. Updates the cached points if necessary. - * Uses the given orientation to rotate the shape. - * - * @param orientation The orientation of the shape. - * @return A set of points that make up the shape. - */ - Set getPoints(Quaternion orientation); - - /** - * Generates the points for the shape. Depends on the set style. - * No orientation, offset, or scale is applied. - */ - @Contract(pure = true) - default void generatePoints(Set points) { - getStyle().generatePoints(this, points); - } - - /** - * Generates the points for the shape, if the style is set to OUTLINE. - * - * @param points A set that will be filled with the points that make up the shape. - */ - @Contract(pure = true) - void generateOutline(Set points); - - /** - * Generates the points for the shape, if the style is set to SURFACE. - * Default implementation is to return the same as generateOutline(). - * - * @param points A set that will be filled with the points that make up the shape. - */ - @Contract(pure = true) - void generateSurface(Set points); - - /** - * Generates the points for the shape, if the style is set to FILL. - * Default implementation is to return the same as generateSurface(). - * - * @param points A set that will be filled with the points that make up the shape. - */ - @Contract(pure = true) - void generateFilled(Set points); - - - /** - * Draws a shape using the default location, orientation, and particle. - * Caches the last orientation used to draw the shape, so that it can be updated if the orientation changes. - * - * @param recipients The players to draw the shape for. - */ - @RequiresNonNull("location") - void draw(Collection recipients); - - /** - * Draws a shape at a location, using the default orientation and particle. - * Caches the last orientation used to draw the shape, so that it can be updated if the orientation changes. - * - * @param location The location to draw the shape at. - * @param recipients The players to draw the shape for. - */ - void draw(DynamicLocation location, Collection recipients); - - /** - * Draws a shape at a location, given a starting orientation and a particle to use. - * Caches the last orientation used to draw the shape, so that it can be updated if the orientation changes. - * - * @param location The location to draw the shape at. - * @param baseOrientation The base orientation to draw the shape with. This is applied before the shape's own orientation. - * @param particle The particle to draw the shape with. - * @param recipients The players to draw the shape for. - */ - void draw(DynamicLocation location, Quaternion baseOrientation, Particle particle, Collection recipients); - - /** - * Draws a shape at a location, using the default orientation and particle. - * The provided consumer is run before the shape is drawn, allowing for the shape to be modified before drawing. - * This method should not be called by a complex shape, but only by the user via EffSecDrawShape. - * - * @param location The location to draw the shape at. - * @param consumer A consumer that is run before the shape is drawn. - * @param recipients The players to draw the shape for. - */ - void draw(DynamicLocation location, Consumer consumer, Collection recipients); - - /** - * @return the relative X axis of the shape, either using its default orientation or the last orientation used to draw the shape. - */ - Vector getRelativeXAxis(boolean useLastOrientation); - - /** - * @return the relative Y axis of the shape, either using its default orientation or the last orientation used to draw the shape. - */ - Vector getRelativeYAxis(boolean useLastOrientation); - - /** - * @return the relative Z axis of the shape, either using its default orientation or the last orientation used to draw the shape. - */ - Vector getRelativeZAxis(boolean useLastOrientation); - - /** - * Sets whether the shape will draw its local axes. - * - * @param show Whether the shape should draw its local axes. - */ - void showLocalAxes(boolean show); - - /** - * @return whether the shape will draw its local axes. - */ - boolean showLocalAxes(); - - /** - * Sets whether the shape will draw its global axes. - * - * @param show Whether the shape should draw its global axes. - */ - void showGlobalAxes(boolean show); - - /** - * @return whether the shape will draw its global axes. - */ - boolean showGlobalAxes(); - - /** - * @return the last location the shape was drawn at. - */ - @Nullable - DynamicLocation getLastLocation(); - - /** - * @return the style of the shape. - */ - Style getStyle(); - - /** - * Sets the style of the shape. Ensures that the shape will be updated upon the next getPoints() call. - * - * @param style The style to use. - */ - void setStyle(Style style); - - /** - * @return a clone of the orientation of the shape. - */ - Quaternion getOrientation(); - - /** - * Sets the orientation of the shape. Ensures that the shape will be updated upon the next getPoints() call. - * - * @param orientation The orientation to use. - */ - void setOrientation(Quaternionf orientation); - - /** - * @return the scale of the shape. - */ - double getScale(); - - /** - * Sets the scale of the shape. Ensures that the shape will be updated upon the next getPoints() call. - * - * @param scale The scale to use. - */ - void setScale(double scale); - - /** - * @return the offset of the shape. - */ - Vector getOffset(); - - /** - * Sets the offset of the shape. Ensures that the shape will be updated upon the next getPoints() call. - * - * @param offset The offset vector to use. - */ - void setOffset(Vector offset); - - /** - * @return the default location of the shape. - */ - @Nullable - DynamicLocation getLocation(); - - /** - * Sets the default location of the shape. This is used as a fallback if the shape is drawn without a given location. - * - * @param location The location to use. - */ - void setLocation(DynamicLocation location); - - /** - * @return the UUID of the shape. Used for uniqueness during serialization. - */ - UUID getUUID(); - - /** - * @return a clone of the particle of the shape. - */ - Particle getParticle(); - - /** - * Sets the particle of the shape. - * - * @param particle The particle to use. - */ - void setParticle(Particle particle); - - /** - * @return The comparator used to order the particles for drawing - */ - @Nullable Comparator getOrdering(); - - /** - * Sets the comparator to be used to order the particles for drawing. - * Using a null value means the particles will be drawn in the order they are calculated. - * - * @param comparator the Comparator to use. - */ - void setOrdering(@Nullable Comparator comparator); - - - /** - * @return the particle density of the shape. - */ - double getParticleDensity(); - - /** - * Sets the particle density of the shape. Ensures that the shape will be updated upon the next getPoints() call. - * - * @param particleDensity The particle density to use, in meters per particle. Must be greater than 0. - */ - void setParticleDensity(double particleDensity); - - /** - * @return the number of points that the shape has. - */ - int getParticleCount(); - - /** - * Sets the number of points that the shape should have. Will not always be accurate, but should be close. - * Ensures that the shape will be updated upon the next getPoints() call. - * - * @param particleCount The number of points to use, ideally. Must be greater than 0. - */ - void setParticleCount(int particleCount); - - /** - * @return whether the shape needs an update. - */ - boolean needsUpdate(); - - /** - * Marks the shape as needing an update or not. - * A value of true ensures that the shape will be updated upon the next getPoints() call. - * - * @param needsUpdate Whether the shape needs an update. - */ - void setNeedsUpdate(boolean needsUpdate); - - /** - * @return the duration of time it takes to draw the whole shape, in milliseconds. Default is 0. - */ - long getAnimationDuration(); - - /** - * Sets the duration of time it takes to draw the whole shape, in milliseconds. - * A value of 0 causes all points to be drawn at once. - * - * @param animationDuration how long it takes to draw the whole shape, in nanoseconds. Points will be drawn in 10ms intervals, so values below 10ms will be treated as 10ms. - */ - void setAnimationDuration(long animationDuration); - - /** - * @return a deep copy of the shape. - */ - @Contract("-> new") - Shape clone(); - - /** - * Used for deeply copying the shape's properties to a new shape. Intended to be used in the clone() method. - * - * @param shape The shape to copy the properties to. - * @return the updated new shape. - */ - @Contract("_ -> param1") - Shape copyTo(Shape shape); - - /** - * Gets the physical state of a shape, represented by its style, orientation, scale, offset, and particle density. - * Used for checking if a shape has changed since the last time it was drawn. - * @return A state object that represents the shape's physical state. - */ - @Contract("-> new") - State getState(); - - /** - * Gets the physical state of a shape, but with a custom orientation. - * - * @param orientation The orientation to use for the state. - * @return A state object that represents the shape's physical state. - */ - @Contract("_ -> new") - State getState(Quaternion orientation); - - /** - * Sets the last state of the shape. Used for checking if a shape has changed since the last time it was drawn. - * - * @param state The state to set. - */ - void setLastState(State state); - - - /** - * The style of a shape, which determines how it is drawn. - * OUTLINE: Draws the shape as a wireframe. - * SURFACE: Draws the shape as a surface. - * FILL: Draws the shape as a solid. - */ - enum Style { - OUTLINE((shape, points) -> shape.generateOutline(points)), - SURFACE((shape, points) -> shape.generateSurface(points)), - FILL((shape, points) -> shape.generateFilled(points)); - - private final BiConsumer> generatePoints; - - Style(BiConsumer> generatePoints) { - this.generatePoints = generatePoints; - } - - /** - * Puts the points of the shape, generated using the correct style, into the points parameter. - */ - public void generatePoints(Shape shape, Set points) { - generatePoints.accept(shape, points); - } - - public String toString() { - return name().toLowerCase(); - } - } - - /** - * A state object that represents the physical state of a shape. - * Used for checking if a shape has changed since the last time it was drawn. - */ - record State(Style style, int orientationHash, double scale, int offsetHash, double particleDensity) { - - /** - * @return whether the state is equal to another state. - */ - public boolean equals(State state) { - return state.style() == style && - state.orientationHash() == orientationHash && - state.scale() == scale && - state.offsetHash() == offsetHash && - state.particleDensity() == particleDensity; - } - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Sphere.java b/src/main/java/com/sovdee/skriptparticles/shapes/Sphere.java deleted file mode 100644 index d6d9f3d..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Sphere.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.Set; - -/** - * A sphere is a 3D shape that is generated by rotating a circle around a central axis. - * The radius of the sphere is the distance from the central point to the circle. - * The generated points are calculated using the golden ratio. - */ -public class Sphere extends AbstractShape implements RadialShape { - - private double radius; - protected double cutoffAngle; - protected double cutoffAngleCos; - - /** - * Creates a sphere with the given radius. - * @param radius the radius of the sphere. Must be greater than 0. - */ - public Sphere(double radius) { - super(); - this.radius = Math.max(radius, MathUtil.EPSILON); - this.cutoffAngle = Math.PI; - this.cutoffAngleCos = -1.0; - this.setStyle(Style.SURFACE); - } - - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - this.generateSurface(points); - } - - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - double particleDensity = this.getParticleDensity(); - int pointCount = 4 * (int) (Math.PI * radius * radius / (particleDensity * particleDensity)); - points.addAll(MathUtil.calculateFibonacciSphere(pointCount, radius, cutoffAngle)); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateFilled(Set points) { - double particleDensity = this.getParticleDensity(); - int subSpheres = (int) (radius / particleDensity) - 1; - double radiusStep = radius / subSpheres; - for (int i = 1; i < subSpheres; i++) { - double subRadius = i * radiusStep; - int pointCount = 4 * (int) (Math.PI * subRadius * subRadius / (particleDensity * particleDensity)); - points.addAll(MathUtil.calculateFibonacciSphere(pointCount, subRadius, cutoffAngle)); - } - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(particleCount, 1); - this.setParticleDensity(switch (this.getStyle()) { - case OUTLINE, SURFACE -> Math.sqrt(2 * Math.PI * radius * radius * (1 - cutoffAngleCos) / particleCount); - case FILL -> - Math.cbrt(Math.PI / 3 * radius * radius * radius * (2 + cutoffAngleCos) * (1 - cutoffAngleCos) * (1 - cutoffAngleCos) / particleCount); - }); - this.setNeedsUpdate(true); - } - - @Override - public double getRadius() { - return radius; - } - - @Override - public void setRadius(double radius) { - this.radius = Math.max(radius, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new Sphere(radius)); - } - - public String toString() { - return this.getStyle() + " sphere with radius " + this.radius; - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/SphericalCap.java b/src/main/java/com/sovdee/skriptparticles/shapes/SphericalCap.java deleted file mode 100644 index 47987fe..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/SphericalCap.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import org.jetbrains.annotations.Contract; - -/** - * A spherical cap is a sphere with a portion of it cut off. - * The cutoff angle is the angle between the center of the sphere and the cutoff plane. - */ -public class SphericalCap extends Sphere implements CutoffShape { - - /** - * Generates a spherical cap with the given radius and cutoff angle. - * @param radius the radius of the sphere. Must be greater than 0. - * @param cutoffAngle the angle in radians between the center of the sphere and the cutoff plane. Will be clamped to be between 0 and pi. - */ - public SphericalCap(double radius, double cutoffAngle) { - super(radius); - this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI); - this.cutoffAngleCos = Math.cos(this.cutoffAngle); - } - - @Override - public double getCutoffAngle() { - return cutoffAngle; - } - - @Override - public void setCutoffAngle(double cutoffAngle) { - this.cutoffAngle = MathUtil.clamp(cutoffAngle, 0, Math.PI); - this.cutoffAngleCos = Math.cos(this.cutoffAngle); - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new SphericalCap(this.getRadius(), cutoffAngle)); - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/shapes/Star.java b/src/main/java/com/sovdee/skriptparticles/shapes/Star.java deleted file mode 100644 index 9d5694c..0000000 --- a/src/main/java/com/sovdee/skriptparticles/shapes/Star.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.sovdee.skriptparticles.shapes; - -import com.sovdee.skriptparticles.util.MathUtil; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Contract; - -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * A star shape. This shape is defined by an inner radius, an outer radius, and an angle. - * The angle is the angle between the points of the star. - */ -public class Star extends AbstractShape { - - private double innerRadius; - private double outerRadius; - private double angle; - - /** - * Creates a new star shape with the given inner radius, outer radius, and angle. - * @param innerRadius the inner radius of the star. Must be greater than 0. - * @param outerRadius the outer radius of the star. Must be greater than 0. - * @param angle the angle between the points of the star in radians. Must be between 0 (exclusive) and pi (inclusive), and should evenly divide 2*pi. - */ - public Star(double innerRadius, double outerRadius, double angle) { - super(); - this.innerRadius = Math.max(innerRadius, MathUtil.EPSILON); - this.outerRadius = Math.max(outerRadius, MathUtil.EPSILON); - this.angle = MathUtil.clamp(angle, MathUtil.EPSILON, Math.PI); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateOutline(Set points) { - points.addAll(MathUtil.calculateStar(innerRadius, outerRadius, angle, this.getParticleDensity())); - } - - @SuppressWarnings("ConstantConditions") - @Override - @Contract(pure = true) - public void generateSurface(Set points) { - double minRadius = Math.min(innerRadius, outerRadius); - double particleDensity = this.getParticleDensity(); - for (double r = 0; r < minRadius; r += particleDensity) { - points.addAll(MathUtil.calculateStar(innerRadius - r, outerRadius - r, angle, particleDensity)); - } - } - - @Override - public void setParticleCount(int particleCount) { - particleCount = Math.max(particleCount, 1); - double sideLength = Math.sqrt(Math.pow(innerRadius, 2) + Math.pow(outerRadius, 2) - 2 * innerRadius * outerRadius * Math.cos(angle)); - double perimeter = sideLength * getStarPoints() * 2; - this.setParticleDensity(perimeter / particleCount); - } - - /** - * Gets the inner radius of the star. - * @return the inner radius of the star. - */ - public double getInnerRadius() { - return innerRadius; - } - - /** - * Sets the inner radius of the star. - * @param innerRadius the new inner radius of the star. Must be greater than 0. - */ - public void setInnerRadius(double innerRadius) { - this.innerRadius = Math.max(innerRadius, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - /** - * Gets the outer radius of the star. - * @return the outer radius of the star. - */ - public double getOuterRadius() { - return outerRadius; - } - - /** - * Sets the outer radius of the star. - * @param outerRadius the new outer radius of the star. Must be greater than 0. - */ - public void setOuterRadius(double outerRadius) { - this.outerRadius = Math.max(outerRadius, MathUtil.EPSILON); - this.setNeedsUpdate(true); - } - - /** - * Gets the number of points on the star. - * - * @return the number of points on the star. - */ - public int getStarPoints() { - return (int) (Math.PI * 2 / angle); - } - - /** - * Sets the number of points on the star. - * - * @param starPoints the new number of points on the star. Must be at least 2. - */ - public void setStarPoints(int starPoints) { - starPoints = Math.max(starPoints, 2); - this.angle = Math.PI * 2 / starPoints; - this.setNeedsUpdate(true); - } - - @Override - @Contract("-> new") - public Shape clone() { - return this.copyTo(new Star(innerRadius, outerRadius, angle)); - } -} diff --git a/src/main/java/com/sovdee/skriptparticles/util/MathUtil.java b/src/main/java/com/sovdee/skriptparticles/util/MathUtil.java deleted file mode 100644 index 8b7cbb7..0000000 --- a/src/main/java/com/sovdee/skriptparticles/util/MathUtil.java +++ /dev/null @@ -1,267 +0,0 @@ -package com.sovdee.skriptparticles.util; - -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -public class MathUtil { - public static final double PHI = Math.PI * (3.0 - Math.sqrt(5.0)); - public static final double PHI_RECIPROCAL = 1.0 / PHI; - public static final double PHI_SQUARED = PHI * PHI; - public static final double[] SPHERE_THETA_COS = new double[4096]; - public static final double[] SPHERE_THETA_SIN = new double[4096]; - public static final double EPSILON = 0.0001; - - static { - for (int i = 0; i < SPHERE_THETA_COS.length; i++) { - SPHERE_THETA_COS[i] = Math.cos(MathUtil.PHI * i); - SPHERE_THETA_SIN[i] = Math.sin(MathUtil.PHI * i); - } - } - - public static double clamp(double value, double min, double max) { - return Math.max(min, Math.min(max, value)); - } - - public static Set calculateFibonacciSphere(int pointCount, double radius) { - return calculateFibonacciSphere(pointCount, radius, Math.PI); - } - - public static Set calculateFibonacciSphere(int pointCount, double radius, double angleCutoff) { - Set points = new LinkedHashSet<>(); - double y = 1; - if (angleCutoff > Math.PI) angleCutoff = Math.PI; - double yLimit = Math.cos(angleCutoff); - - double yStep = 2.0 / pointCount; - int preCompPoints = Math.min(pointCount, MathUtil.SPHERE_THETA_COS.length); - // Use precomputed points if possible - for (int i = 0; i < preCompPoints; i++) { - double r = Math.sqrt(1 - y * y) * radius; - points.add(new Vector(r * MathUtil.SPHERE_THETA_COS[i], y * radius, r * MathUtil.SPHERE_THETA_SIN[i])); - y -= yStep; - if (y <= yLimit) { - return points; - } - } - // If we have more points than we precomputed, we need to calculate the rest - if (pointCount > preCompPoints) { - for (int i = preCompPoints; i < pointCount; i++) { - double r = Math.sqrt(1 - y * y) * radius; - double theta = MathUtil.PHI * i; - points.add(new Vector(r * Math.cos(theta), y * radius, r * Math.sin(theta))); - y -= yStep; - if (y <= yLimit) { - return points; - } - } - } - return points; - } - - public static Set calculateCircle(double radius, double particleDensity, double cutoffAngle) { - Set points = new LinkedHashSet<>(); - double stepSize = particleDensity / radius; - for (double theta = 0; theta < cutoffAngle; theta += stepSize) { - points.add(new Vector(Math.cos(theta) * radius, 0, Math.sin(theta) * radius)); - } - return points; - } - - public static Set calculateDisc(double radius, double particleDensity, double cutoffAngle) { - Set points = new LinkedHashSet<>(); - for (double subRadius = particleDensity; subRadius < radius; subRadius += particleDensity) { - points.addAll(calculateCircle(subRadius, particleDensity, cutoffAngle)); - } - points.addAll(calculateCircle(radius, particleDensity, cutoffAngle)); - return points; - } - - public static Set calculateHelix(double radius, double height, double slope, int direction, double particleDensity) { - Set points = new LinkedHashSet<>(); - if (radius <= 0 || height <= 0) { - return points; - } - double loops = Math.abs(height / slope); - double length = slope * slope + radius * radius; - double stepSize = particleDensity / length; - for (double t = 0; t < loops; t += stepSize) { - double x = radius * Math.cos(direction * t); - double z = radius * Math.sin(direction * t); - points.add(new Vector(x, t * slope, z)); - } - return points; - } - - public static Set calculateLine(Vector start, Vector end, double particleDensity) { - Set points = new LinkedHashSet<>(); - Vector direction = end.clone().subtract(start); - double length = direction.length(); - double step = length / Math.round(length / particleDensity); - direction.normalize().multiply(step); - - for (double i = 0; i <= (length / step); i++) { - points.add(start.clone().add(direction.clone().multiply(i))); - } - return points; - } - - public static Set calculateRegularPolygon(double radius, double angle, double particleDensity, boolean wireframe) { - angle = Math.max(angle, MathUtil.EPSILON); - - Set points = new LinkedHashSet<>(); - double apothem = radius * Math.cos(angle / 2); - double radiusStep = radius / Math.round(apothem / particleDensity); - if (wireframe) { - radiusStep = 2 * radius; - } else { - points.add(new Vector(0, 0, 0)); - } - for (double subRadius = radius; subRadius >= 0; subRadius -= radiusStep) { - Vector vertex = new Vector(subRadius, 0, 0); - for (double i = 0; i < 2 * Math.PI; i += angle) { - points.addAll(calculateLine(vertex.clone().rotateAroundY(i), vertex.clone().rotateAroundY(i + angle), particleDensity)); - } - } - return points; - } - - public static Set calculateRegularPrism(double radius, double angle, double height, double particleDensity, boolean wireframe) { - Set points = new LinkedHashSet<>(); - Vector vertex = new Vector(radius, 0, 0); - for (double i = 0; i < 2 * Math.PI; i += angle) { - Vector currentVertex = vertex.clone().rotateAroundY(i); - for (Vector vector : calculateLine(currentVertex, vertex.clone().rotateAroundY(i + angle), particleDensity)) { - points.add(vector); - if (wireframe) { - points.add(vector.clone().setY(height)); - } else { - points.addAll(calculateLine(vector, vector.clone().setY(height), particleDensity)); - } - } - if (wireframe) - points.addAll(calculateLine(currentVertex, currentVertex.clone().setY(height), particleDensity)); - } - return points; - } - - public static Set connectPoints(List points, double particleDensity) { - Set connectedPoints = new LinkedHashSet<>(); - for (int i = 0; i < points.size() - 1; i++) { - connectedPoints.addAll(calculateLine(points.get(i), points.get(i + 1), particleDensity)); - } - return connectedPoints; - } - - private static double ellipseCircumference(double r1, double r2) { - double a = Math.max(r1, r2); - double b = Math.min(r1, r2); - double h = Math.pow(a - b, 2) / Math.pow(a + b, 2); - return Math.PI * (a + b) * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h))); - } - - public static List calculateEllipse(double r1, double r2, double particleDensity, double cutoffAngle) { - List points = new ArrayList<>(); - double circumference = ellipseCircumference(r1, r2); - - int steps = (int) Math.round(circumference / particleDensity); - double theta = 0; - double angleStep = 0; - for (int i = 0; i < steps; i++) { - if (theta > cutoffAngle) { - break; - } - points.add(new Vector(r1 * Math.cos(theta), 0, r2 * Math.sin(theta))); - double dx = r1 * Math.sin(theta + 0.5 * angleStep); - double dy = r2 * Math.cos(theta + 0.5 * angleStep); - angleStep = particleDensity / Math.sqrt(dx * dx + dy * dy); - theta += angleStep; - } - return points; - } - - public static Set calculateEllipticalDisc(double r1, double r2, double particleDensity, double cutoffAngle) { - Set points = new LinkedHashSet<>(); - int steps = (int) Math.round(Math.max(r1, r2) / particleDensity); - double r; - for (double i = 1; i <= steps; i += 1) { - r = i / steps; - points.addAll(calculateEllipse(r1 * r, r2 * r, particleDensity, cutoffAngle)); - } - return points; - } - - public static Set calculateCylinder(double r1, double height, double particleDensity, double cutoffAngle) { - Set points = calculateDisc(r1, particleDensity, cutoffAngle); - points.addAll(points.stream().map(v -> v.clone().setY(height)).collect(Collectors.toSet())); - // wall - Set wall = calculateCircle(r1, particleDensity, cutoffAngle); - points.addAll(fillVertically(wall, height, particleDensity)); - return points; - } - - public static Set calculateCylinder(double r1, double r2, double height, double particleDensity, double cutoffAngle) { - Set points = calculateEllipticalDisc(r1, r2, particleDensity, cutoffAngle); - points.addAll(points.stream().map(v -> v.clone().setY(height)).collect(Collectors.toSet())); - // wall - Set wall = new LinkedHashSet<>(calculateEllipse(r1, r2, particleDensity, cutoffAngle)); - points.addAll(fillVertically(wall, height, particleDensity)); - return points; - } - - public static Set fillVertically(Set vectors, double height, double particleDensity) { - Set points = new LinkedHashSet<>(vectors); - double heightStep = height / Math.round(height / particleDensity); - for (double i = 0; i < height; i += heightStep) { - for (Vector vector : vectors) { - points.add(vector.clone().setY(i)); - } - } - return points; - } - - public static Set calculateHeart(double length, double width, double eccentricity, double particleDensity) { - Set points = new LinkedHashSet<>(); - double angleStep = 4 / 3.0 * particleDensity / (width + length); - for (double theta = 0; theta < Math.PI * 2; theta += angleStep) { - double x = width * Math.pow(Math.sin(theta), 3); - double y = length * (Math.cos(theta) - 1 / eccentricity * Math.cos(2 * theta) - 1.0 / 6 * Math.cos(3 * theta) - 1.0 / 16 * Math.cos(4 * theta)); - points.add(new Vector(x, 0, y)); - } - return points; - } - - public static Set calculateStar(double innerRadius, double outerRadius, double angle, double particleDensity) { - Set points = new LinkedHashSet<>(); - Vector outerVertex = new Vector(outerRadius, 0, 0); - Vector innerVertex = new Vector(innerRadius, 0, 0); - for (double theta = 0; theta < 2 * Math.PI; theta += angle) { - Vector currentVertex = outerVertex.clone().rotateAroundY(theta); - points.addAll(calculateLine(currentVertex, innerVertex.clone().rotateAroundY(theta + angle / 2), particleDensity)); - points.addAll(calculateLine(currentVertex, innerVertex.clone().rotateAroundY(theta - angle / 2), particleDensity)); - } - return points; - } - - public static List> batch(Collection toDraw, double millisecondsPerPoint) { - List> batches = new ArrayList<>(); - double totalDuration = 0; - Iterator pointsIterator = toDraw.iterator(); - while (pointsIterator.hasNext()) { - List batch = new ArrayList<>(); - while (totalDuration < 50 && pointsIterator.hasNext()) { - totalDuration += millisecondsPerPoint; - batch.add(pointsIterator.next()); - } - totalDuration -= 50; - batches.add(batch); - } - return batches; - } -}