Skip to content

Commit addb53b

Browse files
committed
新的command
1 parent 914dc2e commit addb53b

12 files changed

Lines changed: 903 additions & 2 deletions

common/src/main/kotlin/cn/coostack/cooparticlesapi/network/particle/emitters/ClassParticleEmitters.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ abstract class ClassParticleEmitters(
4444
override var playing: Boolean = false
4545
var airDensity = 0.0
4646
var gravity: Double = 0.0
47+
private var lastTickPos: Vec3 = pos
48+
var emitterVelocity: Vec3 = Vec3.ZERO
49+
private set
4750
val handlerList = ConcurrentHashMap<String, SortedMap<ParticleEventHandler, Boolean>>()
4851

4952
/**
@@ -179,6 +182,8 @@ abstract class ClassParticleEmitters(
179182
override fun start() {
180183
if (playing) return
181184
playing = true
185+
lastTickPos = pos
186+
emitterVelocity = Vec3.ZERO
182187
if (world?.isClientSide == false) {
183188
ParticleEmittersManager.updateEmitters(this)
184189
}
@@ -200,7 +205,10 @@ abstract class ClassParticleEmitters(
200205
}
201206

202207
world ?: return
208+
val previousPos = lastTickPos
203209
doTick()
210+
emitterVelocity = pos.subtract(previousPos)
211+
lastTickPos = pos
204212
if (!world!!.isClientSide) {
205213
increaseTick()
206214
return
@@ -524,4 +532,4 @@ abstract class ClassParticleEmitters(
524532
ParticleEmittersHelper.updateEmitter(this, emitters)
525533
}
526534

527-
}
535+
}

common/src/main/kotlin/cn/coostack/cooparticlesapi/network/particle/emitters/command/ParticleGravityCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ class ParticleGravityCommand(val emitter: ClassParticleEmitters) : ParticleComma
1515
data: ControlableParticleData,
1616
particle: ControlableParticle
1717
) {
18-
data.velocity.add(0.0, -emitter.gravity, 0.0)
18+
data.velocity = data.velocity.add(0.0, -emitter.gravity, 0.0)
1919
}
2020
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package cn.coostack.cooparticlesapi.network.particle.emitters.command
2+
3+
enum class ParticleInheritMode {
4+
INITIAL,
5+
CURRENT
6+
}
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
package cn.coostack.cooparticlesapi.network.particle.emitters.command
2+
3+
import cn.coostack.cooparticlesapi.network.particle.emitters.ClassParticleEmitters
4+
import cn.coostack.cooparticlesapi.network.particle.emitters.ControlableParticleData
5+
import cn.coostack.cooparticlesapi.network.particle.emitters.command.curve.ConstantFloatCurve
6+
import cn.coostack.cooparticlesapi.network.particle.emitters.command.curve.FloatCurve
7+
import cn.coostack.cooparticlesapi.particles.ControlableParticle
8+
import cn.coostack.cooparticlesapi.utils.GraphMathHelper
9+
import net.minecraft.world.phys.Vec3
10+
import org.joml.Quaternionf
11+
import org.joml.Vector3f
12+
import java.util.function.Supplier
13+
import kotlin.random.Random
14+
15+
/**
16+
* 继承速度命令(Inherit Velocity)。
17+
*
18+
* 目标:
19+
* - 让粒子继承“发射源速度”或任意自定义速度源
20+
* - 支持一次性继承(INITIAL)和持续跟随(CURRENT)
21+
* - 支持按轴屏蔽、生命周期权重、空间转换、阻尼跟随
22+
*
23+
* 常见场景:
24+
* - 武器挥动拖尾:CURRENT + 适度 damping
25+
* - 喷射火花:INITIAL + 较高 multiplier
26+
*/
27+
class ParticleInheritVelocityCommand() : ParticleCommand {
28+
/**
29+
* 速度来源。
30+
*
31+
* 默认返回 `Vec3.ZERO`,通常你会传入:
32+
* - `Supplier { emitter.emitterVelocity }`
33+
* - 或任意自定义动态速度源
34+
*/
35+
var source: Supplier<Vec3> = Supplier { Vec3.ZERO }
36+
37+
/**
38+
* 继承模式。
39+
*
40+
* - INITIAL:只在首次执行时继承,后续保持(或按 damping 衰减)
41+
* - CURRENT:每 tick 跟随最新来源速度
42+
*/
43+
var mode: ParticleInheritMode = ParticleInheritMode.INITIAL
44+
45+
/**
46+
* 继承倍率。
47+
*
48+
* - 1.0:等于来源速度
49+
* - <1.0:弱继承
50+
* - >1.0:强化继承
51+
*/
52+
var multiplier: Double = 1.0
53+
54+
/**
55+
* 轴向掩码(逐轴相乘)。
56+
*
57+
* 示例:
58+
* - (1,0,1):忽略 Y 轴继承
59+
* - (0,1,0):只继承竖直方向
60+
*/
61+
var axisMask: Vec3 = Vec3(1.0, 1.0, 1.0)
62+
63+
/**
64+
* 生命周期权重曲线。
65+
*
66+
* 采样值会乘到继承速度上:
67+
* - 1 -> 全量继承
68+
* - 0 -> 不继承
69+
*/
70+
var overLifetime: FloatCurve = ConstantFloatCurve(1.0)
71+
72+
/**
73+
* 阻尼强度(用于平滑/衰减继承速度)。
74+
*
75+
* - 0:不平滑,立即跟随
76+
* - >0:指数平滑,值越大变化越柔和
77+
*/
78+
var damping: Double = 0.0
79+
80+
/**
81+
* 继承速度长度上限。
82+
*
83+
* - <=0:不限制
84+
* - >0:超过则截断
85+
*/
86+
var maxContributionSpeed: Double = 0.0
87+
88+
/**
89+
* 继承向量所在空间。
90+
*
91+
* - WORLD:按世界轴解释
92+
* - LOCAL:按粒子当前旋转解释后再转到世界空间
93+
*/
94+
var space: ParticleMotionSpace = ParticleMotionSpace.WORLD
95+
96+
/**
97+
* 是否启用按粒子固定随机缩放。
98+
*/
99+
var randomizePerParticle: Boolean = false
100+
101+
/**
102+
* 随机缩放最小值(按轴独立采样)。
103+
*/
104+
var randomScaleMin: Double = 1.0
105+
106+
/**
107+
* 随机缩放最大值(按轴独立采样)。
108+
*/
109+
var randomScaleMax: Double = 1.0
110+
111+
/**
112+
* 随机种子偏移。
113+
*/
114+
var randomSeedOffset: Int = 0
115+
116+
private val stateKey = "inherit_velocity.state.${System.identityHashCode(this)}"
117+
private val randomScaleKey = "inherit_velocity.random_scale.${System.identityHashCode(this)}"
118+
119+
private data class InheritState(
120+
var initialized: Boolean = false,
121+
var applied: Vec3 = Vec3.ZERO
122+
)
123+
124+
private data class AxisRandomScale(
125+
val x: Double,
126+
val y: Double,
127+
val z: Double
128+
)
129+
130+
/**
131+
* @param source 速度来源
132+
* @param mode 继承模式(INITIAL/CURRENT)
133+
* @param multiplier 继承倍率
134+
* @param axisMask 轴向掩码
135+
* @param overLifetime 生命周期权重曲线
136+
* @param damping 平滑阻尼
137+
* @param maxContributionSpeed 继承速度上限
138+
* @param space 继承空间(WORLD/LOCAL)
139+
*/
140+
constructor(
141+
source: Supplier<Vec3>,
142+
mode: ParticleInheritMode = ParticleInheritMode.INITIAL,
143+
multiplier: Double = 1.0,
144+
axisMask: Vec3 = Vec3(1.0, 1.0, 1.0),
145+
overLifetime: FloatCurve = ConstantFloatCurve(1.0),
146+
damping: Double = 0.0,
147+
maxContributionSpeed: Double = 0.0,
148+
space: ParticleMotionSpace = ParticleMotionSpace.WORLD
149+
) : this() {
150+
this.source = source
151+
this.mode = mode
152+
this.multiplier = multiplier
153+
this.axisMask = axisMask
154+
this.overLifetime = overLifetime
155+
this.damping = damping
156+
this.maxContributionSpeed = maxContributionSpeed
157+
this.space = space
158+
}
159+
160+
/**
161+
* 便捷构造:直接继承指定发射器的每 tick 位移速度。
162+
*/
163+
constructor(emitter: ClassParticleEmitters) : this(
164+
source = Supplier { emitter.emitterVelocity }
165+
)
166+
167+
/**
168+
* 设置速度来源。
169+
*/
170+
fun source(v: Supplier<Vec3>) = apply { source = v }
171+
172+
/**
173+
* 设置继承模式。
174+
*
175+
* - INITIAL:一次继承
176+
* - CURRENT:持续跟随
177+
*/
178+
fun mode(v: ParticleInheritMode) = apply { mode = v }
179+
180+
/**
181+
* 设置继承倍率。
182+
*/
183+
fun multiplier(v: Double) = apply { multiplier = v }
184+
185+
/**
186+
* 设置轴向掩码。
187+
*/
188+
fun axisMask(v: Vec3) = apply { axisMask = v }
189+
190+
/**
191+
* 设置生命周期权重曲线。
192+
*/
193+
fun overLifetime(v: FloatCurve) = apply { overLifetime = v }
194+
195+
/**
196+
* 设置阻尼强度。
197+
*/
198+
fun damping(v: Double) = apply { damping = v }
199+
200+
/**
201+
* 设置继承速度上限。
202+
*/
203+
fun maxContributionSpeed(v: Double) = apply { maxContributionSpeed = v }
204+
205+
/**
206+
* 设置继承空间(WORLD/LOCAL)。
207+
*/
208+
fun space(v: ParticleMotionSpace) = apply { space = v }
209+
210+
/**
211+
* 设置是否启用按粒子固定随机缩放。
212+
*/
213+
fun randomizePerParticle(v: Boolean) = apply { randomizePerParticle = v }
214+
215+
/**
216+
* 设置随机缩放区间。
217+
*/
218+
fun randomScale(min: Double, max: Double) = apply {
219+
randomScaleMin = min
220+
randomScaleMax = max
221+
}
222+
223+
/**
224+
* 设置随机种子偏移。
225+
*/
226+
fun randomSeedOffset(v: Int) = apply { randomSeedOffset = v }
227+
228+
/**
229+
* 每 tick 应用继承速度。
230+
*/
231+
override fun execute(data: ControlableParticleData, particle: ControlableParticle) {
232+
val state = particle.controler.bufferedData.getOrPut(stateKey) { InheritState() } as InheritState
233+
234+
val t = normalizedLifetime(particle)
235+
val lifeMultiplier = overLifetime.sample(t)
236+
val randomScale = resolveRandomScale(particle)
237+
238+
var target = source.get()
239+
target = applyMask(target)
240+
target = scaleAxis(target, randomScale)
241+
target = target.scale(multiplier * lifeMultiplier)
242+
target = transformBySpace(target, space, particle)
243+
target = clampLength(target, maxContributionSpeed)
244+
245+
target = when (mode) {
246+
ParticleInheritMode.INITIAL -> {
247+
if (!state.initialized) {
248+
state.initialized = true
249+
target
250+
} else if (damping > 0.0) {
251+
state.applied.scale(GraphMathHelper.expDampFactor(damping, 1.0))
252+
} else {
253+
state.applied
254+
}
255+
}
256+
257+
ParticleInheritMode.CURRENT -> {
258+
state.initialized = true
259+
if (damping > 0.0) {
260+
val follow = 1.0 - GraphMathHelper.expDampFactor(damping, 1.0)
261+
state.applied.add(target.subtract(state.applied).scale(follow.coerceIn(0.0, 1.0)))
262+
} else {
263+
target
264+
}
265+
}
266+
}
267+
268+
val delta = target.subtract(state.applied)
269+
if (delta.lengthSqr() > 1e-12) {
270+
data.velocity = data.velocity.add(delta)
271+
}
272+
state.applied = target
273+
}
274+
275+
private fun applyMask(vec: Vec3): Vec3 {
276+
return Vec3(
277+
vec.x * axisMask.x,
278+
vec.y * axisMask.y,
279+
vec.z * axisMask.z
280+
)
281+
}
282+
283+
private fun scaleAxis(vec: Vec3, scale: AxisRandomScale): Vec3 {
284+
return Vec3(vec.x * scale.x, vec.y * scale.y, vec.z * scale.z)
285+
}
286+
287+
private fun normalizedLifetime(particle: ControlableParticle): Double {
288+
if (particle.lifetime <= 0) return 0.0
289+
return (particle.currentAge.toDouble() / particle.lifetime.toDouble()).coerceIn(0.0, 1.0)
290+
}
291+
292+
private fun transformBySpace(
293+
vec: Vec3,
294+
space: ParticleMotionSpace,
295+
particle: ControlableParticle
296+
): Vec3 {
297+
if (space == ParticleMotionSpace.WORLD) return vec
298+
299+
val q = Quaternionf().rotateXYZ(
300+
particle.currentPitch,
301+
particle.currentYaw,
302+
particle.currentRoll
303+
)
304+
val v = Vector3f(vec.x.toFloat(), vec.y.toFloat(), vec.z.toFloat())
305+
v.rotate(q)
306+
return Vec3(v.x.toDouble(), v.y.toDouble(), v.z.toDouble())
307+
}
308+
309+
private fun resolveRandomScale(particle: ControlableParticle): AxisRandomScale {
310+
if (!randomizePerParticle) {
311+
return AxisRandomScale(1.0, 1.0, 1.0)
312+
}
313+
val cached = particle.controler.bufferedData[randomScaleKey]
314+
if (cached is AxisRandomScale) {
315+
return cached
316+
}
317+
318+
val minScale = minOf(randomScaleMin, randomScaleMax)
319+
val maxScale = maxOf(randomScaleMin, randomScaleMax)
320+
val random = Random(particle.controlUUID.hashCode() + randomSeedOffset)
321+
val fixed = if (maxScale - minScale <= 1e-9) {
322+
AxisRandomScale(minScale, minScale, minScale)
323+
} else {
324+
AxisRandomScale(
325+
random.nextDouble(minScale, maxScale),
326+
random.nextDouble(minScale, maxScale),
327+
random.nextDouble(minScale, maxScale)
328+
)
329+
}
330+
particle.controler.bufferedData[randomScaleKey] = fixed
331+
return fixed
332+
}
333+
334+
private fun clampLength(vec: Vec3, maxLen: Double): Vec3 {
335+
if (maxLen <= 0.0) return vec
336+
val len = vec.length()
337+
if (len <= 1e-9 || len <= maxLen) return vec
338+
return vec.scale(maxLen / len)
339+
}
340+
}

0 commit comments

Comments
 (0)