[x3d-public] X3D meeting minutes 1 February 2018: strategy for mapping glTF 2.0 to X3Dv4 (Brutzman, Donald (Don) (CIV))

Michalis Kamburelis michalis.kambi at gmail.com
Sat Feb 2 11:25:54 PST 2019


 Andreas Plesch <andreasplesch at gmail.com> wrote:
> A few edges cases are:
>
> https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#reference-animation
> says that a name is not required. What name should be used then ?

Hm, that's a curious case. Can such unnamed animation be played by
other glTF APIs/viewers?

Consistently, we could convert unnamed glTF animation to an unnamed
TimeSensor. It would be equally uncomfortable (to access) then, both
in X3D and in glTF :)

>
> The name can contain any character including spaces which are not
> allowed in DEF names. How to sanitize ?

We use a "brutal" solution in CGE right now that replaces everything
except ['a'..'z', 'A'..'Z', '0'..'9'] to an underscore,
https://github.com/castle-engine/castle-engine/blob/master/src/x3d/x3dloadinternalutils.pas#L60.

It's "brutal" in the sense that it knowingly replaces much more than
necessary. I don't have a strong opinion whether this is the right
solution, I could change it to be consistent with other X3D players.

>
> I suppose for blending animations, eg. mixing interpolator output by
> weight, there are additional processing steps. Do you use an actual
> script or just internal computing ?

I'm using an internal mechanism. It allows to send an event and say
that "the effect of this event should be applied only partially".

You can watch the demo movie on
https://castle-engine.io/wp/2018/03/21/animation-blending/ , it shows
how it works for both animating Transform.translation/rotation
(skeletal animation from Spine) and Coordinate.point (animation of
mesh coordinates).

The reasons for doing it without any new X3D nodes:

- I wanted animation blending to work with any existing X3D animation.
So I didn't want to require X3D authors to use a new node, or to
organize existing TimeSensor/interpolators/routes in any new way.

- I wanted it to also allow "fade out" of the animation (when the
animation is applied with less and less impact, but no new animation
takes it's place).

- I wanted it to work for any combination of animations. E.g.
Animation1 affects bones A, B, C, and Animation2 affects bones B, C,
D. ("Bone" here can mean just a "Transform" node for Spine skeletal
animation.) So some bones are controlled by both animations (B, C),
but some not. In all cases, fade out of the old Animation1 and fade-in
of Animation2 should work as expected.

<details>

(Be warned, this is one of the more complicated algorithms in CGE :)
It's not a lot of code lines, but understanding and writing this was
hard.)

I track when we should do cross-fading between animations (it has to
be initilalized by calling TCastleScene.PlayAnimation), and when we
are doing cross-fading, then

1. First the TimeSensor of the old animation sends events in a "fake"
way (it sends events despite being active or not), with "partial"
factor falling down from 1.0 to 0.0 as the scene time passes. The
procedure to "send TimeSensor events in a fake way",
TTimeSensorNode.FakeTime , is documented on
https://github.com/castle-engine/castle-engine/blob/master/src/x3d/x3dnodes_standard_time.inc#L343
.

    The "partial" factor is passed on as one event produces another,
e.g. TimeSensor sends fraction_changed, some interpolator receives it
and then sends value_changed, and the "partial factor" information is
still carried over. Various complicated setups are covered by this
approach, in all cases the "event cascade" carries the information
that "it should be applied only partially".

    The resulting "partial" values (e.g. Transform.translation, or
Coordinate.point) are stored at the field, with current accumulated
"weight". But they are not immediately applied to the field actual
value, they are only stored in an additional record "attached" to the
field. Some notes about it are at TPartialReceived record in
https://github.com/castle-engine/castle-engine/blob/master/src/x3d/castlefields_x3dfield.inc#L87
.

    This entire procedure is done by "PartialSendBegin" method which
is in https://github.com/castle-engine/castle-engine/blob/master/src/x3d/castlescenecore.pas
.

2. Then the TimeSensor of the new animation sends events in a regular
way (without FakeTime), but also with "partial factor" (growing from
0.0 to 1.0).

3. At the end, for all fields that received some "partial" values in
this frame, we calculate the final field value. This is done by
PartialSendEnd method in
https://github.com/castle-engine/castle-engine/blob/master/src/x3d/castlescenecore.pas
that sends TX3DField.InternalAffectedPartial . We do a lerp between
"PartialValue" (the sum of all values received in this frame, weighted
by their "partial" factor) and a "SettledValue" (last known value when
animation blending did not occur).

    This way the mechanism works e.g. when cross-fading two animations
(old animation has partial = 0.7, new one has 0.3, their final weight
is 1.0 and then the "SettledValue" doesn't matter) or when old
animation fades away (so the "partial" value has decreasing weight,
and is mixed with unchanging "SettledValue").

</details>

Regards,
Michalis



More information about the x3d-public mailing list