DREAMFIRE Docs ← Back to site
Loading...
Searching...
No Matches
DreamParticles.java
Go to the documentation of this file.
1/*
2 * MIT License
3 *
4 * Copyright (c) 2025 Dreamfire Studio
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24package com.dreamfirestudios.dreamcore.DreamParticles;
25
26import org.bukkit.Location;
27import org.bukkit.Particle;
28import org.bukkit.World;
29import org.bukkit.util.Vector;
30
31import java.util.ArrayList;
32import java.util.Collection;
33import java.util.List;
34import java.util.Objects;
35
49public final class DreamParticles {
50
51 private DreamParticles() {}
52
53 /* ======================================================================
54 * Basic single-point emission
55 * ====================================================================== */
56
68 public static void spawnSingle(World world, Particle particle, Location location) {
69 if (world == null || particle == null || location == null || location.getWorld() == null) return;
70 world.spawnParticle(particle, location, 1, 0, 0, 0, 0);
71 }
72
79 public static void spawnSingle(World world, Particle particle, Vector point) {
80 if (world == null || particle == null || point == null) return;
81 world.spawnParticle(particle, point.getX(), point.getY(), point.getZ(), 1, 0, 0, 0, 0);
82 }
83
102 public static void spawn(
103 World world, Particle particle, Location location,
104 int count, double offsetX, double offsetY, double offsetZ, double speed,
105 Object data, boolean force
106 ) {
107 if (world == null || particle == null || location == null || location.getWorld() == null) return;
108 if (count <= 0) return;
109 world.spawnParticle(particle, location, count, offsetX, offsetY, offsetZ, speed, data, force);
110 }
111
112 /* ======================================================================
113 * Legacy-style simple wrappers (kept for convenience)
114 * ====================================================================== */
115
127 public static void spawn(World world, Particle particle, Location location,
128 int count, double offsetX, double offsetY, double offsetZ, double speed) {
129 spawn(world, particle, location, count, offsetX, offsetY, offsetZ, speed, null, false);
130 }
131
132 /* ======================================================================
133 * Generic Shape System
134 * ====================================================================== */
135
143 @FunctionalInterface
144 public interface ParticleShape {
149 Collection<Vector> sample();
150 }
151
172 public static void emitShape(
173 World world, Particle particle, Location origin,
174 ParticleShape shape,
175 int count, double offsetX, double offsetY, double offsetZ, double speed,
176 Object data, boolean force
177 ) {
178 if (world == null || particle == null || origin == null || origin.getWorld() == null) return;
179 if (shape == null) return;
180 if (count <= 0) return;
181
182 for (Vector rel : shape.sample()) {
183 Location at = origin.clone().add(rel);
184 world.spawnParticle(particle, at, count, offsetX, offsetY, offsetZ, speed, data, force);
185 }
186 }
187
198 public static void emitShape(
199 World world, Particle particle, Location origin, ParticleShape shape,
200 int count, double offsetXYZ, double speed
201 ) {
202 emitShape(world, particle, origin, shape, count, offsetXYZ, offsetXYZ, offsetXYZ, speed, null, false);
203 }
204
205 /* ======================================================================
206 * Ready-made shapes
207 * ====================================================================== */
208
215 private static int positive(int n, int fallback) {
216 return (n > 0) ? n : fallback;
217 }
218
232 public static ParticleShape sphere(double radius, int rings, int segments) {
233 final double R = Math.max(0.0, radius);
234 final int RINGS = positive(rings, 12);
235 final int SEGS = positive(segments, 24);
236
237 return () -> {
238 List<Vector> pts = new ArrayList<>(RINGS * SEGS);
239 for (int i = 0; i <= RINGS; i++) {
240 double theta = Math.PI * i / RINGS; // 0..π
241 double sin = Math.sin(theta);
242 double cos = Math.cos(theta);
243 double ringR = R * sin;
244 double y = R * cos;
245 for (int j = 0; j < SEGS; j++) {
246 double phi = (2 * Math.PI) * j / SEGS; // 0..2π
247 double x = Math.cos(phi) * ringR;
248 double z = Math.sin(phi) * ringR;
249 pts.add(new Vector(x, y, z));
250 }
251 }
252 return pts;
253 };
254 }
255
266 public static ParticleShape ring(double radius, int segments) {
267 final double R = Math.max(0.0, radius);
268 final int SEGS = positive(segments, 64);
269 return () -> {
270 List<Vector> pts = new ArrayList<>(SEGS);
271 for (int j = 0; j < SEGS; j++) {
272 double a = (2 * Math.PI) * j / SEGS;
273 pts.add(new Vector(Math.cos(a) * R, 0, Math.sin(a) * R));
274 }
275 return pts;
276 };
277 }
278
285 public static ParticleShape cube(double sideLength, int stepsPerEdge) {
286 final double L = Math.max(0.0, sideLength);
287 final int STEPS = positive(stepsPerEdge, 8);
288 final double half = L / 2.0;
289 final double step = (STEPS <= 1 ? L : L / (STEPS - 1));
290
291 return () -> {
292 List<Vector> pts = new ArrayList<>(STEPS * STEPS * 6);
293 // Six faces: +/-X, +/-Y, +/-Z
294 for (int i = 0; i < STEPS; i++) {
295 double t = -half + i * step;
296 for (int j = 0; j < STEPS; j++) {
297 double u = -half + j * step;
298
299 // +X, -X
300 pts.add(new Vector( half, t, u));
301 pts.add(new Vector(-half, t, u));
302 // +Y, -Y
303 pts.add(new Vector(t, half, u));
304 pts.add(new Vector(t, -half, u));
305 // +Z, -Z
306 pts.add(new Vector(t, u, half));
307 pts.add(new Vector(t, u, -half));
308 }
309 }
310 return pts;
311 };
312 }
313
325 public static ParticleShape cone(double radius, double height, int rings, int segments) {
326 final double R = Math.max(0.0, radius);
327 final double H = Math.max(0.0, height);
328 final int RINGS = positive(rings, 12);
329 final int SEGS = Math.max(3, segments);
330
331 return () -> {
332 List<Vector> pts = new ArrayList<>(RINGS * SEGS);
333 for (int i = 0; i <= RINGS; i++) {
334 double y = (H * i) / RINGS;
335 double ringR = (H == 0.0) ? 0.0 : R * (1.0 - (y / H)); // linear taper
336 for (int j = 0; j < SEGS; j++) {
337 double a = (2 * Math.PI) * j / SEGS;
338 double x = Math.cos(a) * ringR;
339 double z = Math.sin(a) * ringR;
340 pts.add(new Vector(x, y, z));
341 }
342 }
343 return pts;
344 };
345 }
346
355 public static ParticleShape spiral(double radius, double height, double turns, int points) {
356 final double R = Math.max(0.0, radius);
357 final double H = Math.max(0.0, height);
358 final double TURNS = Math.max(0.0, turns);
359 final int PTS = positive(points, 256);
360
361 return () -> {
362 List<Vector> out = new ArrayList<>(PTS);
363 double maxAngle = TURNS * 2.0 * Math.PI;
364 for (int i = 0; i < PTS; i++) {
365 double t = (PTS == 1) ? 0.0 : (double) i / (PTS - 1);
366 double angle = maxAngle * t;
367 double y = H * t;
368 double x = Math.cos(angle) * R;
369 double z = Math.sin(angle) * R;
370 out.add(new Vector(x, y, z));
371 }
372 return out;
373 };
374 }
375
376 /* ======================================================================
377 * Orientation helpers
378 * ====================================================================== */
379
387 public static Vector rotateAroundAxis(Vector v, Vector axisNormalized, double angle) {
388 Objects.requireNonNull(v, "v");
389 Objects.requireNonNull(axisNormalized, "axisNormalized");
390 Vector k = axisNormalized.clone().normalize();
391 double cos = Math.cos(angle);
392 double sin = Math.sin(angle);
393 // v_rot = v*cos + (k x v)*sin + k*(k·v)*(1-cos)
394 Vector cross = k.clone().crossProduct(v).multiply(sin);
395 Vector term1 = v.clone().multiply(cos);
396 Vector term2 = cross;
397 Vector term3 = k.clone().multiply(k.dot(v) * (1.0 - cos));
398 return term1.add(term2).add(term3);
399 }
400
408 public static List<Vector> rotatePoints(Collection<Vector> points, Vector axis, double angle) {
409 Objects.requireNonNull(points, "points");
410 Objects.requireNonNull(axis, "axis");
411 List<Vector> out = new ArrayList<>(points.size());
412 Vector n = axis.clone().normalize();
413 for (Vector p : points) {
414 out.add(rotateAroundAxis(p, n, angle));
415 }
416 return out;
417 }
418
425 public static List<Vector> orientPoints(Collection<Vector> points, Vector axis) {
426 Objects.requireNonNull(points, "points");
427 Objects.requireNonNull(axis, "axis");
428 Vector y = new Vector(0, 1, 0);
429 Vector n = axis.clone().normalize();
430
431 double dot = y.dot(n);
432 // Already aligned:
433 if (Math.abs(dot - 1.0) < 1e-6) return new ArrayList<>(points); // same direction
434 if (Math.abs(dot + 1.0) < 1e-6) { // opposite: rotate 180° around any perpendicular
435 Vector perp = new Vector(1, 0, 0);
436 if (Math.abs(y.dot(perp)) > 0.99) perp = new Vector(0, 0, 1);
437 return rotatePoints(points, perp, Math.PI);
438 }
439
440 // Rotation axis is cross product; angle is arccos(dot)
441 Vector axisRot = y.clone().crossProduct(n).normalize();
442 double angle = Math.acos(dot);
443 return rotatePoints(points, axisRot, angle);
444 }
445
446 /* ======================================================================
447 * Deprecated legacy API (kept to ease migration)
448 * ====================================================================== */
449
457 @Deprecated
458 public static void spawnParticle(World world, Particle particle, Location location) {
459 spawnSingle(world, particle, location);
460 }
461
469 @Deprecated
470 public static void spawnParticle(World world, Particle particle, Vector point) {
471 spawnSingle(world, particle, point);
472 }
473
486 @Deprecated
487 public static void spawnParticle(World world, Particle particle, Location location,
488 int count, double offsetX, double offsetY, double offsetZ, double speed) {
489 spawn(world, particle, location, count, offsetX, offsetY, offsetZ, speed);
490 }
491}
Particle utilities with a generic, reusable shape system.
static void spawn(World world, Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double speed)
Convenience wrapper for simple emission (no data, not forced).
static ParticleShape cone(double radius, double height, int rings, int segments)
Cone shell aligned along +Y (origin at base center).
static void spawnParticle(World world, Particle particle, Location location)
Legacy alias of spawnSingle(World, Particle, Location).
static Vector rotateAroundAxis(Vector v, Vector axisNormalized, double angle)
Rotates a vector around a normalized axis by an angle (radians) using Rodrigues’ formula.
static ParticleShape cube(double sideLength, int stepsPerEdge)
Cube shell (grid points per face).
static List< Vector > orientPoints(Collection< Vector > points, Vector axis)
Orients points so that local +Y aligns with the specified axis (best-effort).
static ParticleShape spiral(double radius, double height, double turns, int points)
Vertical spiral around +Y axis from y=0 to height .
static void spawnSingle(World world, Particle particle, Location location)
Spawns a single particle at an absolute location (no data, not forced).
static ParticleShape ring(double radius, int segments)
Flat ring in the XZ plane centered at origin.
static void emitShape(World world, Particle particle, Location origin, ParticleShape shape, int count, double offsetX, double offsetY, double offsetZ, double speed, Object data, boolean force)
Emits a sampled ParticleShape at a given origin with full control.
static void spawn(World world, Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double speed, Object data, boolean force)
Spawns particles with full control of count, offsets, speed, data, and force.
static void emitShape(World world, Particle particle, Location origin, ParticleShape shape, int count, double offsetXYZ, double speed)
Emits a shape with simple parameters (no data, not forced).
static void spawnSingle(World world, Particle particle, Vector point)
Spawns a single particle at an absolute point (no data, not forced).
static ParticleShape sphere(double radius, int rings, int segments)
Sphere shell distribution (rings × segments).
static void spawnParticle(World world, Particle particle, Vector point)
Legacy alias of spawnSingle(World, Particle, Vector).
static void spawnParticle(World world, Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double speed)
Legacy alias of spawn(World, Particle, Location, int, double, double, double, double).
static List< Vector > rotatePoints(Collection< Vector > points, Vector axis, double angle)
Rotates each point in a collection around an axis by an angle (radians).
Represents a collection of relative emission points (vectors) around an origin.
Collection< Vector > sample()
Samples the shape and returns relative points to the origin.