fix(vfx #56): ParticleHookSink applies CreateParticleHook.PartIndex transform
Adds per-entity part-transform side-table mirroring _rotationByEntity. SpawnFromHook now transforms the hook offset through partTransforms[partIndex] before rotating to world space. Backwards-compatible: entities without registered part transforms fall through to identity (pre-C.1.5b behavior), so the existing C.1.5a rotation-seed test stays green. Adds SetEntityPartTransforms public method. Cleared on StopAllForEntity alongside the rotation entry. 2 new xUnit tests: - SpawnFromHook_AppliesPartTransform_WhenRegistered — part 1 lifted +Z=1, hook offset (1,0,0), PartIndex=1 → world (1,0,1). - SpawnFromHook_FallsBackToIdentity_WhenPartIndexOutOfBounds — PartIndex=99 on a 2-part array → offset applied without crash, pre-C.1.5b behavior. Closes the renderer side of #56. EntityScriptActivator wiring (Task 3) lands next. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f3bc15ed9d
commit
11521f4418
2 changed files with 115 additions and 4 deletions
|
|
@ -92,4 +92,84 @@ public sealed class ParticleHookSinkTests
|
|||
sys.Tick(0.01f);
|
||||
Assert.Equal(0, sys.ActiveEmitterCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpawnFromHook_AppliesPartTransform_WhenRegistered()
|
||||
{
|
||||
// C.1.5b #56: when SetEntityPartTransforms has been called for
|
||||
// entityId, SpawnFromHook must transform the hook offset through
|
||||
// the part-local matrix before applying entity rotation.
|
||||
// Part 1 is lifted +Z=1; hook offset = (1, 0, 0), PartIndex=1.
|
||||
// Expected world position: (1, 0, 1) with identity rotation.
|
||||
var registry = new EmitterDescRegistry();
|
||||
registry.Register(MakeDesc(0x32000030u, attachLocal: false));
|
||||
var sys = new ParticleSystem(registry, new System.Random(42));
|
||||
var sink = new ParticleHookSink(sys);
|
||||
|
||||
var partTransforms = new Matrix4x4[]
|
||||
{
|
||||
Matrix4x4.Identity,
|
||||
Matrix4x4.CreateTranslation(0f, 0f, 1f),
|
||||
};
|
||||
sink.SetEntityRotation(0xCAFEu, Quaternion.Identity);
|
||||
sink.SetEntityPartTransforms(0xCAFEu, partTransforms);
|
||||
|
||||
sink.OnHook(0xCAFEu, Vector3.Zero, new CreateParticleHook
|
||||
{
|
||||
EmitterInfoId = 0x32000030u,
|
||||
EmitterId = 0,
|
||||
PartIndex = 1,
|
||||
Offset = new Frame
|
||||
{
|
||||
Origin = new Vector3(1f, 0f, 0f),
|
||||
Orientation = Quaternion.Identity,
|
||||
},
|
||||
});
|
||||
sys.Tick(0.001f);
|
||||
|
||||
var live = System.Linq.Enumerable.Single(sys.EnumerateLive());
|
||||
var pos = live.Emitter.Particles[live.Index].Position;
|
||||
Assert.InRange(pos.X, 0.99f, 1.01f);
|
||||
Assert.InRange(pos.Y, -0.01f, 0.01f);
|
||||
Assert.InRange(pos.Z, 0.99f, 1.01f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpawnFromHook_FallsBackToIdentity_WhenPartIndexOutOfBounds()
|
||||
{
|
||||
// Out-of-bounds PartIndex must NOT crash and must NOT apply a
|
||||
// wrong matrix; falls back to no part transform (Identity), so
|
||||
// the offset is applied in entity-local space as pre-C.1.5b.
|
||||
var registry = new EmitterDescRegistry();
|
||||
registry.Register(MakeDesc(0x32000031u, attachLocal: false));
|
||||
var sys = new ParticleSystem(registry, new System.Random(42));
|
||||
var sink = new ParticleHookSink(sys);
|
||||
|
||||
var partTransforms = new Matrix4x4[]
|
||||
{
|
||||
Matrix4x4.Identity,
|
||||
Matrix4x4.CreateTranslation(0f, 0f, 1f),
|
||||
};
|
||||
sink.SetEntityRotation(0xCAFEu, Quaternion.Identity);
|
||||
sink.SetEntityPartTransforms(0xCAFEu, partTransforms);
|
||||
|
||||
sink.OnHook(0xCAFEu, Vector3.Zero, new CreateParticleHook
|
||||
{
|
||||
EmitterInfoId = 0x32000031u,
|
||||
EmitterId = 0,
|
||||
PartIndex = 99, // way past the 2-part array
|
||||
Offset = new Frame
|
||||
{
|
||||
Origin = new Vector3(2f, 0f, 0f),
|
||||
Orientation = Quaternion.Identity,
|
||||
},
|
||||
});
|
||||
sys.Tick(0.001f);
|
||||
|
||||
var live = System.Linq.Enumerable.Single(sys.EnumerateLive());
|
||||
var pos = live.Emitter.Particles[live.Index].Position;
|
||||
Assert.InRange(pos.X, 1.99f, 2.01f);
|
||||
Assert.InRange(pos.Y, -0.01f, 0.01f);
|
||||
Assert.InRange(pos.Z, -0.01f, 0.01f);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue