Skip to content

Commit ec0ca80

Browse files
committed
Bezier Curves, new Point class, slightly better error handling
1 parent b14eef4 commit ec0ca80

File tree

7 files changed

+388
-26
lines changed

7 files changed

+388
-26
lines changed

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ repositories {
2525
}
2626

2727
dependencies {
28-
compileOnly("io.papermc.paper:paper-api:1.20.2-R0.1-SNAPSHOT")
29-
compileOnly("com.github.SkriptLang:Skript:2.7.0")
28+
compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT")
29+
compileOnly("com.github.SkriptLang:Skript:2.8.4")
3030
shadow "org.bstats:bstats-bukkit:3.0.2"
3131
implementation "org.joml:joml:${jomlVersion}"
3232
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.sovdee.skriptparticles.elements.expressions.constructors;
2+
3+
import ch.njol.skript.Skript;
4+
import ch.njol.skript.lang.Expression;
5+
import ch.njol.skript.lang.ExpressionType;
6+
import ch.njol.skript.lang.Literal;
7+
import ch.njol.skript.lang.SkriptParser.ParseResult;
8+
import ch.njol.skript.lang.util.SimpleExpression;
9+
import ch.njol.util.Kleenean;
10+
import com.sovdee.skriptparticles.shapes.BezierCurve;
11+
import com.sovdee.skriptparticles.shapes.Circle;
12+
import com.sovdee.skriptparticles.shapes.Shape;
13+
import com.sovdee.skriptparticles.util.MathUtil;
14+
import com.sovdee.skriptparticles.util.Point;
15+
import org.bukkit.event.Event;
16+
import org.checkerframework.checker.nullness.qual.NonNull;
17+
import org.jetbrains.annotations.Nullable;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
public class ExprBezierCurve extends SimpleExpression<BezierCurve> {
23+
24+
static {
25+
Skript.registerExpression(ExprBezierCurve.class, BezierCurve.class, ExpressionType.COMBINED,
26+
"[a] [bezier] curve from [start] %vector/entity/location% to [end] %vector/entity/location% (with|using) control point[s] %vectors/entities/locations%");
27+
}
28+
29+
private Expression<?> start;
30+
private Expression<?> end;
31+
private Expression<?> controlPoints;
32+
33+
@Override
34+
public boolean init(Expression<?>[] exprs, int matchedPattern, @NonNull Kleenean isDelayed, @NonNull ParseResult parseResult) {
35+
start = exprs[0];
36+
end = exprs[1];
37+
controlPoints = exprs[2];
38+
return true;
39+
}
40+
41+
@Override
42+
protected BezierCurve @Nullable [] get(@NonNull Event event) {
43+
@Nullable Point<?> start = Point.of(this.start.getSingle(event));
44+
@Nullable Point<?> end = Point.of(this.end.getSingle(event));
45+
if (start == null || end == null)
46+
return null;
47+
List<Point<?>> controlPoints = new ArrayList<>();
48+
for (Object value : this.controlPoints.getArray(event)) {
49+
controlPoints.add(Point.of(value));
50+
}
51+
52+
return new BezierCurve[]{new BezierCurve(start, end, controlPoints)};
53+
}
54+
55+
@Override
56+
public boolean isSingle() {
57+
return true;
58+
}
59+
60+
@Override
61+
@NonNull
62+
public Class<? extends BezierCurve> getReturnType() {
63+
return BezierCurve.class;
64+
}
65+
66+
@Override
67+
@NonNull
68+
public String toString(@Nullable Event event, boolean debug) {
69+
return "bezier curve between start " + start.toString(event, debug) + " and end " + end.toString(event, debug) +
70+
" using control points " + controlPoints.toString(event, debug);
71+
}
72+
}

src/main/java/com/sovdee/skriptparticles/elements/sections/DrawShapeEffectSection.java

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import ch.njol.skript.util.Timespan;
1616
import ch.njol.skript.variables.Variables;
1717
import ch.njol.util.Kleenean;
18+
import com.sovdee.skriptparticles.SkriptParticle;
1819
import com.sovdee.skriptparticles.shapes.Shape;
1920
import com.sovdee.skriptparticles.util.DynamicLocation;
2021
import org.bukkit.Bukkit;
@@ -35,7 +36,7 @@
3536

3637
public abstract class DrawShapeEffectSection extends EffectSection {
3738

38-
public static final Timespan ONE_TICK = Timespan.fromTicks_i(1);
39+
public static final Timespan ONE_TICK = Timespan.fromTicks(1);
3940

4041
static {
4142
EventValues.registerEventValue(DrawEvent.class, Shape.class, new Getter<>() {
@@ -105,7 +106,7 @@ public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean is
105106
* @param hasSection Whether this section had a valid section body.
106107
* @return Whether this expression was initialised successfully. An error should be printed prior to returning false to specify the cause
107108
*/
108-
public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, boolean hasSection) {
109+
public boolean init(@Nullable Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, boolean hasSection) {
109110
shapes = (Expression<Shape>) expressions[0];
110111

111112
if (expressions[2] != null) {
@@ -136,9 +137,9 @@ protected TriggerItem walk(Event event) {
136137
recipients.addAll(Bukkit.getOnlinePlayers());
137138
}
138139

139-
Object localVars = Variables.copyLocalVariables(event);
140+
@Nullable Object localVars = Variables.copyLocalVariables(event);
140141

141-
Consumer<Shape> consumer;
142+
@Nullable Consumer<Shape> consumer;
142143
if (trigger != null) {
143144
consumer = shape -> {
144145
DrawEvent drawEvent = new DrawEvent(shape);
@@ -153,10 +154,11 @@ protected TriggerItem walk(Event event) {
153154

154155
// Figure out what locations to draw at, or what entities to follow
155156
List<DynamicLocation> locations = new ArrayList<>();
156-
Direction direction = null;
157+
@Nullable Direction direction = null;
157158
if (!useShapeLocation) {
158159
if (directions != null)
159160
direction = directions.getSingle(event);
161+
assert this.locations != null;
160162
for (Object location : this.locations.getArray(event)) {
161163
if (location instanceof Entity) {
162164
locations.add(new DynamicLocation((Entity) location, direction));
@@ -207,26 +209,42 @@ public void run() {
207209
runnable.runTaskAsynchronously(Skript.getInstance());
208210
}
209211

210-
protected void executeSync(Event event, Collection<DynamicLocation> locations, Consumer<Shape> consumer, Collection<Player> recipients) {
212+
protected void executeSync(Event event, Collection<DynamicLocation> locations, @Nullable Consumer<Shape> consumer, Collection<Player> recipients) {
211213
Shape shapeCopy;
212-
for (DynamicLocation dynamicLocation : locations) {
213-
for (Shape shape : shapes.getArray(event)) {
214-
if (consumer != null) {
215-
// copy the shape so that it can be modified by the consumer without affecting the original
216-
shapeCopy = shape.clone();
217-
shapeCopy.draw(dynamicLocation, consumer, recipients);
218-
} else {
219-
shape.draw(dynamicLocation, recipients);
214+
try {
215+
for (DynamicLocation dynamicLocation : locations) {
216+
for (Shape shape : shapes.getArray(event)) {
217+
if (consumer != null) {
218+
// copy the shape so that it can be modified by the consumer without affecting the original
219+
shapeCopy = shape.clone();
220+
shapeCopy.draw(dynamicLocation, consumer, recipients);
221+
} else {
222+
shape.draw(dynamicLocation, recipients);
223+
}
220224
}
221225
}
226+
} catch (IllegalArgumentException exception) {
227+
SkriptParticle.severe("Unable to draw shape[s]! Please check that your particles are valid!");
228+
SkriptParticle.severe("Exception: " + exception.getMessage());
229+
SkriptParticle.severe("To see the full stack trace, set Skript's verbosity to very high or debug.");
230+
if (Skript.logVeryHigh())
231+
exception.printStackTrace();
222232
}
223233
}
224234

225235
protected void executeAsync(Collection<DynamicLocation> locations, Collection<Shape> shapes, Collection<Player> recipients) {
226-
for (DynamicLocation dynamicLocation : locations) {
227-
for (Shape shape : shapes) {
228-
shape.draw(dynamicLocation, recipients);
236+
try {
237+
for (DynamicLocation dynamicLocation : locations) {
238+
for (Shape shape : shapes) {
239+
shape.draw(dynamicLocation, recipients);
240+
}
229241
}
242+
} catch (IllegalArgumentException exception) {
243+
SkriptParticle.severe("Unable to draw shape[s]! Please check that your particles are valid!");
244+
SkriptParticle.severe("Exception: " + exception.getMessage());
245+
SkriptParticle.severe("To see the full stack trace, set Skript's verbosity to very high or debug.");
246+
if (Skript.logVeryHigh())
247+
exception.printStackTrace();
230248
}
231249
}
232250

src/main/java/com/sovdee/skriptparticles/elements/sections/EffSecDrawShape.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public Shape get(EffSecDrawShape.DrawEvent event) {
7272
private Expression<Timespan> delay;
7373

7474
@Override
75-
public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, boolean hasSection) {
75+
public boolean init(@Nullable Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, boolean hasSection) {
7676
if (parseResult.hasTag("duration")) {
7777
duration = (Expression<Timespan>) expressions[4];
7878
delay = (Expression<Timespan>) expressions[5];
@@ -91,8 +91,8 @@ protected void setupAsync(Event event, Collection<DynamicLocation> locations, Co
9191
@Nullable Timespan duration = this.duration.getSingle(event);
9292
if (delay == null || duration == null) return;
9393

94-
period = Math.max(delay.getTicks_i(), 1);
95-
iterations = Math.max(duration.getTicks_i() / period, 1);
94+
period = Math.max(delay.getTicks(), 1);
95+
iterations = Math.max(duration.getTicks() / period, 1);
9696
}
9797
AtomicLong currentIteration = new AtomicLong(0);
9898
BukkitRunnable runnable = new BukkitRunnable() {

src/main/java/com/sovdee/skriptparticles/elements/sections/EffSecDrawShapeAnimation.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import org.bukkit.entity.Player;
1515
import org.bukkit.event.Event;
1616
import org.checkerframework.checker.nullness.qual.NonNull;
17-
import org.jetbrains.annotations.Nullable;
17+
import org.checkerframework.checker.nullness.qual.Nullable;
1818

1919
import java.util.Collection;
2020
import java.util.function.Consumer;
@@ -36,7 +36,7 @@ public class EffSecDrawShapeAnimation extends DrawShapeEffectSection {
3636
private Expression<Timespan> duration;
3737

3838
@Override
39-
public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, boolean hasSection) {
39+
public boolean init(@Nullable Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, boolean hasSection) {
4040
duration = (Expression<Timespan>) expressions[4];
4141
return super.init(expressions, matchedPattern, isDelayed, parseResult, hasSection);
4242
}
@@ -45,13 +45,13 @@ public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean is
4545
* This method should not be called for this section.
4646
*/
4747
@Override
48-
protected void executeSync(Event event, Collection<DynamicLocation> locations, Consumer<Shape> consumer, Collection<Player> recipients) {
48+
protected void executeSync(Event event, Collection<DynamicLocation> locations, @Nullable Consumer<Shape> consumer, Collection<Player> recipients) {
4949
// intentionally empty
5050
}
5151

5252
@Override
5353
protected void setupAsync(Event event, Collection<DynamicLocation> locations, Collection<Shape> shapes, Collection<Player> recipients) {
54-
Timespan duration = this.duration.getOptionalSingle(event).orElse(Timespan.fromTicks_i(0));
54+
Timespan duration = this.duration.getOptionalSingle(event).orElse(Timespan.fromTicks(0));
5555
long milliseconds = duration.getMilliSeconds();
5656
for (Shape shape : shapes) {
5757
shape.setAnimationDuration(milliseconds);
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.sovdee.skriptparticles.shapes;
2+
3+
import com.sovdee.skriptparticles.util.DynamicLocation;
4+
import com.sovdee.skriptparticles.util.Point;
5+
import com.sovdee.skriptparticles.util.Quaternion;
6+
import org.bukkit.Location;
7+
import org.bukkit.util.Vector;
8+
import org.checkerframework.checker.nullness.qual.Nullable;
9+
import org.jetbrains.annotations.Contract;
10+
11+
import java.util.ArrayList;
12+
import java.util.LinkedHashSet;
13+
import java.util.List;
14+
import java.util.Set;
15+
16+
public class BezierCurve extends AbstractShape {
17+
18+
private Point<?> start;
19+
private Point<?> end;
20+
private List<Point<?>> controlPoints;
21+
22+
private boolean isDynamic;
23+
24+
/**
25+
* Creates a new line shape with the start point at the origin and the end point at the given vector.
26+
* The vector cannot be the origin.
27+
* @param end the end point of the line
28+
* @throws IllegalArgumentException if the end vector is the origin
29+
*/
30+
31+
public BezierCurve(Point<?> start, Point<?> end, List<Point<?>> controlPoints) {
32+
this.start = start;
33+
if (start.getType() != Vector.class)
34+
this.setLocation(start.getDynamicLocation());
35+
this.end = end;
36+
this.controlPoints = new ArrayList<>(controlPoints);
37+
isDynamic = true;
38+
}
39+
40+
public BezierCurve(BezierCurve curve) {
41+
this.start = curve.getStart();
42+
this.end = curve.getEnd();
43+
this.controlPoints = new ArrayList<>(curve.getControlPoints());
44+
isDynamic = curve.isDynamic;
45+
}
46+
47+
@Override
48+
@Contract(pure = true)
49+
public Set<Vector> getPoints(Quaternion orientation) {
50+
Set<Vector> points = super.getPoints(orientation);
51+
if (isDynamic)
52+
// Ensure that the points are always needing to be updated if the start or end location is dynamic
53+
this.setNeedsUpdate(true);
54+
return points;
55+
}
56+
57+
private List<Vector> evaluateControlPoints() {
58+
List<Vector> controlPoints = new ArrayList<>();
59+
@Nullable Location origin = start.getLocation();
60+
controlPoints.add(start.getVector(origin));
61+
for (Point<?> controlPoint : this.controlPoints)
62+
controlPoints.add(controlPoint.getVector(origin));
63+
controlPoints.add(end.getVector(origin));
64+
return controlPoints;
65+
}
66+
67+
@Override
68+
public Set<Vector> generateOutline() {
69+
Set<Vector> points = new LinkedHashSet<>();
70+
List<Vector> controlPoints = evaluateControlPoints();
71+
72+
int steps = (int) (estimateLength(controlPoints) / getParticleDensity());
73+
74+
for (double step = 0; step < steps; step++) {
75+
double t = step / steps;
76+
double nt = 1 - t;
77+
List<Vector> tempCP = new ArrayList<>(controlPoints);
78+
while (tempCP.size() > 1) {
79+
for (int i = 0; i < tempCP.size() - 1; i++)
80+
tempCP.set(i, tempCP.get(i).clone().multiply(nt).add(tempCP.get(i + 1).clone().multiply(t)));
81+
tempCP.remove(tempCP.size()-1);
82+
}
83+
points.add(tempCP.get(0));
84+
}
85+
return points;
86+
}
87+
88+
private double estimateLength() {
89+
return estimateLength(evaluateControlPoints());
90+
}
91+
private double estimateLength(List<Vector> controlPoints) {
92+
double dist = 0;
93+
for (int i = 0; i < controlPoints.size()-1; i++) {
94+
dist += controlPoints.get(i).distance(controlPoints.get(i+1));
95+
}
96+
return dist;
97+
}
98+
99+
@Override
100+
public void setParticleCount(int particleCount) {
101+
particleCount = Math.max(particleCount, 1);
102+
this.setParticleDensity(estimateLength() / particleCount);
103+
this.setNeedsUpdate(true);
104+
}
105+
106+
public Point<?> getStart() {
107+
return start;
108+
}
109+
110+
public Point<?> getEnd() {
111+
return end;
112+
}
113+
114+
public List<Point<?>> getControlPoints() {
115+
return controlPoints;
116+
}
117+
118+
@Override
119+
public Shape clone() {
120+
return this.copyTo(new BezierCurve(this));
121+
}
122+
}

0 commit comments

Comments
 (0)