@flighthq/particles-formats 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/detect.d.ts +18 -0
  2. package/dist/detect.d.ts.map +1 -0
  3. package/dist/detect.js +75 -0
  4. package/dist/detect.js.map +1 -0
  5. package/dist/formatRegistry.d.ts +60 -0
  6. package/dist/formatRegistry.d.ts.map +1 -0
  7. package/dist/formatRegistry.js +75 -0
  8. package/dist/formatRegistry.js.map +1 -0
  9. package/dist/index.d.ts +21 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +21 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/libgdxParse.d.ts +27 -0
  14. package/dist/libgdxParse.d.ts.map +1 -0
  15. package/dist/libgdxParse.js +316 -0
  16. package/dist/libgdxParse.js.map +1 -0
  17. package/dist/libgdxSchema.d.ts +68 -0
  18. package/dist/libgdxSchema.d.ts.map +1 -0
  19. package/dist/libgdxSchema.js +12 -0
  20. package/dist/libgdxSchema.js.map +1 -0
  21. package/dist/libgdxSerialize.d.ts +17 -0
  22. package/dist/libgdxSerialize.d.ts.map +1 -0
  23. package/dist/libgdxSerialize.js +295 -0
  24. package/dist/libgdxSerialize.js.map +1 -0
  25. package/dist/parseParticleConfig.d.ts +34 -0
  26. package/dist/parseParticleConfig.d.ts.map +1 -0
  27. package/dist/parseParticleConfig.js +96 -0
  28. package/dist/parseParticleConfig.js.map +1 -0
  29. package/dist/particleDesignerParse.d.ts +23 -0
  30. package/dist/particleDesignerParse.d.ts.map +1 -0
  31. package/dist/particleDesignerParse.js +214 -0
  32. package/dist/particleDesignerParse.js.map +1 -0
  33. package/dist/particleDesignerSchema.d.ts +51 -0
  34. package/dist/particleDesignerSchema.d.ts.map +1 -0
  35. package/dist/particleDesignerSchema.js +5 -0
  36. package/dist/particleDesignerSchema.js.map +1 -0
  37. package/dist/particleDesignerSerialize.d.ts +22 -0
  38. package/dist/particleDesignerSerialize.d.ts.map +1 -0
  39. package/dist/particleDesignerSerialize.js +209 -0
  40. package/dist/particleDesignerSerialize.js.map +1 -0
  41. package/dist/pixiParse.d.ts +18 -0
  42. package/dist/pixiParse.d.ts.map +1 -0
  43. package/dist/pixiParse.js +173 -0
  44. package/dist/pixiParse.js.map +1 -0
  45. package/dist/serializeResult.d.ts +15 -0
  46. package/dist/serializeResult.d.ts.map +1 -0
  47. package/dist/serializeResult.js +2 -0
  48. package/dist/serializeResult.js.map +1 -0
  49. package/dist/spineParse.d.ts +18 -0
  50. package/dist/spineParse.d.ts.map +1 -0
  51. package/dist/spineParse.js +263 -0
  52. package/dist/spineParse.js.map +1 -0
  53. package/dist/spineSchema.d.ts +41 -0
  54. package/dist/spineSchema.d.ts.map +1 -0
  55. package/dist/spineSchema.js +8 -0
  56. package/dist/spineSchema.js.map +1 -0
  57. package/dist/spineSerialize.d.ts +16 -0
  58. package/dist/spineSerialize.d.ts.map +1 -0
  59. package/dist/spineSerialize.js +108 -0
  60. package/dist/spineSerialize.js.map +1 -0
  61. package/dist/starlingPexParse.d.ts +29 -0
  62. package/dist/starlingPexParse.d.ts.map +1 -0
  63. package/dist/starlingPexParse.js +254 -0
  64. package/dist/starlingPexParse.js.map +1 -0
  65. package/dist/starlingPexSchema.d.ts +49 -0
  66. package/dist/starlingPexSchema.d.ts.map +1 -0
  67. package/dist/starlingPexSchema.js +15 -0
  68. package/dist/starlingPexSchema.js.map +1 -0
  69. package/dist/starlingPexSerialize.d.ts +20 -0
  70. package/dist/starlingPexSerialize.d.ts.map +1 -0
  71. package/dist/starlingPexSerialize.js +184 -0
  72. package/dist/starlingPexSerialize.js.map +1 -0
  73. package/dist/unityParse.d.ts +23 -0
  74. package/dist/unityParse.d.ts.map +1 -0
  75. package/dist/unityParse.js +347 -0
  76. package/dist/unityParse.js.map +1 -0
  77. package/dist/unitySchema.d.ts +109 -0
  78. package/dist/unitySchema.d.ts.map +1 -0
  79. package/dist/unitySchema.js +9 -0
  80. package/dist/unitySchema.js.map +1 -0
  81. package/dist/unitySerialize.d.ts +20 -0
  82. package/dist/unitySerialize.d.ts.map +1 -0
  83. package/dist/unitySerialize.js +148 -0
  84. package/dist/unitySerialize.js.map +1 -0
  85. package/package.json +38 -0
  86. package/src/detect.test.ts +99 -0
  87. package/src/formatRegistry.test.ts +118 -0
  88. package/src/libgdxParse.test.ts +309 -0
  89. package/src/libgdxSerialize.test.ts +279 -0
  90. package/src/parseParticleConfig.test.ts +447 -0
  91. package/src/particleDesignerParse.test.ts +279 -0
  92. package/src/particleDesignerSerialize.test.ts +113 -0
  93. package/src/pixiParse.test.ts +131 -0
  94. package/src/spineParse.test.ts +289 -0
  95. package/src/spineSerialize.test.ts +81 -0
  96. package/src/starlingPexParse.test.ts +154 -0
  97. package/src/starlingPexSerialize.test.ts +115 -0
  98. package/src/unityParse.test.ts +328 -0
  99. package/src/unitySerialize.test.ts +77 -0
@@ -0,0 +1,18 @@
1
+ import type { ParticleFormatKind } from '@flighthq/types';
2
+ /** Sniff the text content of a particle file and return the format kind, or `null`
3
+ * when no supported format is recognisable.
4
+ *
5
+ * Detection is structural, not extension-based:
6
+ * - libGDX `.p`: first non-empty line is `Particle Effect` (exact text).
7
+ * - Starling PEX: XML containing `<particleEmitterConfig`.
8
+ * - Particle Designer plist: XML containing `<plist`.
9
+ * - Unity Shuriken JSON: root object contains `looping` or `startLifetime` with a
10
+ * `mode` field (Unity's MinMaxCurve convention), or a `gravityModifier` key.
11
+ * - Pixi particle JSON: root object contains `alpha` with a `start`/`end` sub-object
12
+ * (pixi-particle-emitter shape) and a `pos` field.
13
+ * - Spine JSON: root object contains `continuous` or `emission` with `{low,high}` range shape
14
+ * (Spine's range-object convention) without Unity-specific keys.
15
+ *
16
+ * Returns `null` for unknown or corrupt input — never throws. */
17
+ export declare function detectParticleFormat(text: string): ParticleFormatKind | null;
18
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAU1D;;;;;;;;;;;;;;kEAckE;AAClE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CA8C5E"}
package/dist/detect.js ADDED
@@ -0,0 +1,75 @@
1
+ import { LibgdxParticleFormatKind, ParticleDesignerFormatKind, PixiParticleFormatKind, SpineParticleFormatKind, StarlingPexFormatKind, UnityParticleFormatKind, } from '@flighthq/types';
2
+ /** Sniff the text content of a particle file and return the format kind, or `null`
3
+ * when no supported format is recognisable.
4
+ *
5
+ * Detection is structural, not extension-based:
6
+ * - libGDX `.p`: first non-empty line is `Particle Effect` (exact text).
7
+ * - Starling PEX: XML containing `<particleEmitterConfig`.
8
+ * - Particle Designer plist: XML containing `<plist`.
9
+ * - Unity Shuriken JSON: root object contains `looping` or `startLifetime` with a
10
+ * `mode` field (Unity's MinMaxCurve convention), or a `gravityModifier` key.
11
+ * - Pixi particle JSON: root object contains `alpha` with a `start`/`end` sub-object
12
+ * (pixi-particle-emitter shape) and a `pos` field.
13
+ * - Spine JSON: root object contains `continuous` or `emission` with `{low,high}` range shape
14
+ * (Spine's range-object convention) without Unity-specific keys.
15
+ *
16
+ * Returns `null` for unknown or corrupt input — never throws. */
17
+ export function detectParticleFormat(text) {
18
+ if (typeof text !== 'string')
19
+ return null;
20
+ const trimmed = text.trimStart();
21
+ // libGDX `.p` — first non-empty line is exactly "Particle Effect"
22
+ const firstLine = trimmed.split('\n')[0]?.trim();
23
+ if (firstLine === 'Particle Effect')
24
+ return LibgdxParticleFormatKind;
25
+ // XML path — Starling PEX or Particle Designer plist
26
+ if (trimmed.startsWith('<') || trimmed.startsWith('<?xml')) {
27
+ if (trimmed.includes('<particleEmitterConfig'))
28
+ return StarlingPexFormatKind;
29
+ if (trimmed.includes('<plist'))
30
+ return ParticleDesignerFormatKind;
31
+ return null;
32
+ }
33
+ // JSON path — disambiguate Spine vs Unity vs Pixi
34
+ let raw;
35
+ try {
36
+ raw = JSON.parse(text);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
42
+ return null;
43
+ const obj = raw;
44
+ // Unity: MinMaxCurve mode field, or gravityModifier, or looping + startLifetime
45
+ if (hasMinMaxCurveMode(obj.startLifetime) ||
46
+ hasMinMaxCurveMode(obj.startSpeed) ||
47
+ hasMinMaxCurveMode(obj.startSize) ||
48
+ typeof obj.gravityModifier === 'number' ||
49
+ (typeof obj.looping === 'boolean' && obj.startLifetime !== undefined)) {
50
+ return UnityParticleFormatKind;
51
+ }
52
+ // Pixi: has 'pos' + alpha.start/end or frequency field
53
+ if (obj.pos !== undefined &&
54
+ obj.alpha !== undefined &&
55
+ typeof obj.alpha === 'object' &&
56
+ obj.alpha !== null &&
57
+ ('start' in obj.alpha || 'end' in obj.alpha)) {
58
+ return PixiParticleFormatKind;
59
+ }
60
+ // Spine: has 'continuous' boolean or emission as {low, high} range object
61
+ if (typeof obj.continuous === 'boolean' || isRangeObject(obj.emission) || isRangeObject(obj.life)) {
62
+ return SpineParticleFormatKind;
63
+ }
64
+ return null;
65
+ }
66
+ function hasMinMaxCurveMode(val) {
67
+ return val !== null && typeof val === 'object' && typeof val.mode === 'string';
68
+ }
69
+ function isRangeObject(val) {
70
+ if (val === null || typeof val !== 'object')
71
+ return false;
72
+ const o = val;
73
+ return typeof o.low === 'number' && typeof o.high === 'number';
74
+ }
75
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import type { ParticleEmitterConfig } from '@flighthq/types';
2
+ import type { ParticleFormatKind } from '@flighthq/types';
3
+ import type { ParticleConfigParseResult } from './parseParticleConfig';
4
+ import type { ParticleSerializeResult } from './serializeResult';
5
+ /** Contract for a particle format codec registered via `registerParticleFormat`.
6
+ *
7
+ * A codec provides the three operations needed to integrate a format into the
8
+ * unified dispatcher: format detection, config parse, and serialize.
9
+ *
10
+ * `detect` should return `true` when the text is confidently recognized as this format —
11
+ * it must not throw. `parseToConfig` and `parseToDocument` may throw on genuinely
12
+ * malformed input; the dispatcher catches and wraps errors as warnings. */
13
+ export interface ParticleFormatCodec {
14
+ /** Return `true` when `text` is recognisable as this format. Must not throw. */
15
+ detect(text: string): boolean;
16
+ /** Parse `text` and return a `ParticleEmitterConfig`. May throw on malformed input. */
17
+ parseToConfig(text: string): ParticleEmitterConfig;
18
+ /** Parse `text` and return `{ config, warnings }`. May throw on malformed input.
19
+ * Return an empty `warnings` array when nothing is lossy. */
20
+ parseToDocument(text: string): {
21
+ config: ParticleEmitterConfig;
22
+ warnings: string[];
23
+ };
24
+ /** Serialize `config` to the format string.
25
+ * May accept optional options via closure capture in the codec implementation. */
26
+ serialize(config: Readonly<ParticleEmitterConfig>): ParticleSerializeResult;
27
+ }
28
+ /** Detect the format of `text` by consulting all registered codecs in registration order.
29
+ *
30
+ * Returns the first `kind` whose codec's `detect` returns `true`, or `null` when no
31
+ * registered codec recognises the input. Codecs are consulted in the order they were
32
+ * registered; last-write-wins replacement does not change detection order (the replaced
33
+ * kind retains its original position).
34
+ *
35
+ * This is the registry-backed counterpart of `detectParticleFormat`. */
36
+ export declare function detectRegisteredParticleFormat(text: string): string | null;
37
+ /** Return the registered codec for `kind`, or `null` when no codec is registered. */
38
+ export declare function getParticleFormatCodec(kind: ParticleFormatKind): ParticleFormatCodec | null;
39
+ /** Return all currently registered format kinds. */
40
+ export declare function getRegisteredParticleFormats(): ReadonlyArray<string>;
41
+ /** Parse `text` using the registered codec for `kind`.
42
+ *
43
+ * Returns a `ParticleConfigParseResult` with the config, format, and any import warnings.
44
+ * When no codec is registered for `kind`, returns a default config with an
45
+ * `'unknown-format'` warning. Codec errors are caught and returned as `'parse-error'` warnings. */
46
+ export declare function parseRegisteredParticleFormat(text: string, kind: string): ParticleConfigParseResult;
47
+ /** Register a particle format codec for the given `kind`.
48
+ *
49
+ * Last-write-wins: registering the same kind again replaces the previous codec.
50
+ * Built-in kinds (bare names, no dot) are registered at module load time by the
51
+ * format packages themselves; custom/vendor kinds use a dotted namespace prefix
52
+ * (e.g. `'acme.Custom'`).
53
+ *
54
+ * This function must be called explicitly — there is no implicit registration at
55
+ * module load time. */
56
+ export declare function registerParticleFormat(kind: ParticleFormatKind, codec: ParticleFormatCodec): void;
57
+ /** Remove a previously registered format codec. Returns `true` if the kind was found
58
+ * and removed, `false` if it was not registered. */
59
+ export declare function unregisterParticleFormat(kind: ParticleFormatKind): boolean;
60
+ //# sourceMappingURL=formatRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatRegistry.d.ts","sourceRoot":"","sources":["../src/formatRegistry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAEjE;;;;;;;4EAO4E;AAC5E,MAAM,WAAW,mBAAmB;IAClC,gFAAgF;IAChF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,uFAAuF;IACvF,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,CAAC;IACnD;kEAC8D;IAC9D,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;QAC7B,MAAM,EAAE,qBAAqB,CAAC;QAC9B,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF;uFACmF;IACnF,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GAAG,uBAAuB,CAAC;CAC7E;AAED;;;;;;;yEAOyE;AACzE,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS1E;AAED,qFAAqF;AACrF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,kBAAkB,GAAG,mBAAmB,GAAG,IAAI,CAE3F;AAED,oDAAoD;AACpD,wBAAgB,4BAA4B,IAAI,aAAa,CAAC,MAAM,CAAC,CAEpE;AAED;;;;oGAIoG;AACpG,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,yBAAyB,CAmBnG;AAED;;;;;;;;wBAQwB;AACxB,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAEjG;AAED;qDACqD;AACrD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAE1E"}
@@ -0,0 +1,75 @@
1
+ import { createParticleEmitterConfig } from '@flighthq/particles';
2
+ /** Detect the format of `text` by consulting all registered codecs in registration order.
3
+ *
4
+ * Returns the first `kind` whose codec's `detect` returns `true`, or `null` when no
5
+ * registered codec recognises the input. Codecs are consulted in the order they were
6
+ * registered; last-write-wins replacement does not change detection order (the replaced
7
+ * kind retains its original position).
8
+ *
9
+ * This is the registry-backed counterpart of `detectParticleFormat`. */
10
+ export function detectRegisteredParticleFormat(text) {
11
+ for (const [kind, codec] of _registry) {
12
+ try {
13
+ if (codec.detect(text))
14
+ return kind;
15
+ }
16
+ catch {
17
+ // A codec detect() must not throw; ignore any that do.
18
+ }
19
+ }
20
+ return null;
21
+ }
22
+ /** Return the registered codec for `kind`, or `null` when no codec is registered. */
23
+ export function getParticleFormatCodec(kind) {
24
+ return _registry.get(kind) ?? null;
25
+ }
26
+ /** Return all currently registered format kinds. */
27
+ export function getRegisteredParticleFormats() {
28
+ return [..._registry.keys()];
29
+ }
30
+ /** Parse `text` using the registered codec for `kind`.
31
+ *
32
+ * Returns a `ParticleConfigParseResult` with the config, format, and any import warnings.
33
+ * When no codec is registered for `kind`, returns a default config with an
34
+ * `'unknown-format'` warning. Codec errors are caught and returned as `'parse-error'` warnings. */
35
+ export function parseRegisteredParticleFormat(text, kind) {
36
+ const codec = _registry.get(kind);
37
+ if (!codec) {
38
+ return {
39
+ config: createParticleEmitterConfig(),
40
+ format: kind,
41
+ warnings: [`unknown-format: format '${kind}' has no registered codec`],
42
+ };
43
+ }
44
+ try {
45
+ const result = codec.parseToDocument(text);
46
+ return { config: result.config, format: kind, warnings: result.warnings };
47
+ }
48
+ catch (err) {
49
+ return {
50
+ config: createParticleEmitterConfig(),
51
+ format: kind,
52
+ warnings: [`parse-error: ${err.message}`],
53
+ };
54
+ }
55
+ }
56
+ /** Register a particle format codec for the given `kind`.
57
+ *
58
+ * Last-write-wins: registering the same kind again replaces the previous codec.
59
+ * Built-in kinds (bare names, no dot) are registered at module load time by the
60
+ * format packages themselves; custom/vendor kinds use a dotted namespace prefix
61
+ * (e.g. `'acme.Custom'`).
62
+ *
63
+ * This function must be called explicitly — there is no implicit registration at
64
+ * module load time. */
65
+ export function registerParticleFormat(kind, codec) {
66
+ _registry.set(kind, codec);
67
+ }
68
+ /** Remove a previously registered format codec. Returns `true` if the kind was found
69
+ * and removed, `false` if it was not registered. */
70
+ export function unregisterParticleFormat(kind) {
71
+ return _registry.delete(kind);
72
+ }
73
+ // The format registry: last-write-wins per kind (matches the SDK's kind-registration pattern).
74
+ const _registry = new Map();
75
+ //# sourceMappingURL=formatRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatRegistry.js","sourceRoot":"","sources":["../src/formatRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AA+BlE;;;;;;;yEAOyE;AACzE,MAAM,UAAU,8BAA8B,CAAC,IAAY;IACzD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,SAAS,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,uDAAuD;QACzD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,sBAAsB,CAAC,IAAwB;IAC7D,OAAO,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AACrC,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,4BAA4B;IAC1C,OAAO,CAAC,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;AAC/B,CAAC;AAED;;;;oGAIoG;AACpG,MAAM,UAAU,6BAA6B,CAAC,IAAY,EAAE,IAAY;IACtE,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,MAAM,EAAE,2BAA2B,EAAE;YACrC,MAAM,EAAE,IAAI;YACZ,QAAQ,EAAE,CAAC,2BAA2B,IAAI,2BAA2B,CAAC;SACvE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC5E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,MAAM,EAAE,2BAA2B,EAAE;YACrC,MAAM,EAAE,IAAI;YACZ,QAAQ,EAAE,CAAC,gBAAiB,GAAa,CAAC,OAAO,EAAE,CAAC;SACrD,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;wBAQwB;AACxB,MAAM,UAAU,sBAAsB,CAAC,IAAwB,EAAE,KAA0B;IACzF,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED;qDACqD;AACrD,MAAM,UAAU,wBAAwB,CAAC,IAAwB;IAC/D,OAAO,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,+FAA+F;AAC/F,MAAM,SAAS,GAAG,IAAI,GAAG,EAA+B,CAAC"}
@@ -0,0 +1,21 @@
1
+ export * from './detect';
2
+ export * from './formatRegistry';
3
+ export * from './libgdxParse';
4
+ export * from './libgdxSchema';
5
+ export * from './libgdxSerialize';
6
+ export * from './parseParticleConfig';
7
+ export * from './particleDesignerParse';
8
+ export * from './particleDesignerSchema';
9
+ export * from './particleDesignerSerialize';
10
+ export * from './pixiParse';
11
+ export * from './serializeResult';
12
+ export * from './spineParse';
13
+ export * from './spineSchema';
14
+ export * from './spineSerialize';
15
+ export * from './starlingPexParse';
16
+ export * from './starlingPexSchema';
17
+ export * from './starlingPexSerialize';
18
+ export * from './unityParse';
19
+ export * from './unitySchema';
20
+ export * from './unitySerialize';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ export * from './detect';
2
+ export * from './formatRegistry';
3
+ export * from './libgdxParse';
4
+ export * from './libgdxSchema';
5
+ export * from './libgdxSerialize';
6
+ export * from './parseParticleConfig';
7
+ export * from './particleDesignerParse';
8
+ export * from './particleDesignerSchema';
9
+ export * from './particleDesignerSerialize';
10
+ export * from './pixiParse';
11
+ export * from './serializeResult';
12
+ export * from './spineParse';
13
+ export * from './spineSchema';
14
+ export * from './spineSerialize';
15
+ export * from './starlingPexParse';
16
+ export * from './starlingPexSchema';
17
+ export * from './starlingPexSerialize';
18
+ export * from './unityParse';
19
+ export * from './unitySchema';
20
+ export * from './unitySerialize';
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { ParticleEmitterConfig } from '@flighthq/types';
2
+ import type { LibgdxParticleDocument } from './libgdxSchema';
3
+ export interface LibgdxParseOptions {
4
+ /** Side length of the particle texture in pixels, used to normalise pixel sizes
5
+ * to dimensionless scale multipliers. Defaults to 1 (no normalisation). */
6
+ textureSize?: number;
7
+ }
8
+ export interface LibgdxParseResult {
9
+ config: ParticleEmitterConfig;
10
+ document: LibgdxParticleDocument;
11
+ /** Features present in the source that the common-subset importer cannot
12
+ * represent and silently dropped — surface these in your asset pipeline. */
13
+ warnings: string[];
14
+ }
15
+ /** @deprecated Use `LibgdxParseResult`. */
16
+ export type LibgdxParsed = LibgdxParseResult;
17
+ /** Parse a libGDX 2D Particle Editor `.p` file string directly to a ParticleEmitterConfig.
18
+ *
19
+ * Single-pass: no intermediate document object is allocated.
20
+ * Use `parseLibgdxParticleDocument` instead when you need round-trip serialisation.
21
+ * Throws a clear, format-tagged error when the input cannot be parsed. */
22
+ export declare function parseLibgdxParticle(text: string, options?: LibgdxParseOptions): ParticleEmitterConfig;
23
+ /** Parse a libGDX 2D Particle Editor `.p` file string and preserve the full document
24
+ * for round-trip serialisation via `serializeLibgdxParticle`.
25
+ * Throws a clear, format-tagged error when the input cannot be parsed. */
26
+ export declare function parseLibgdxParticleDocument(text: string, options?: LibgdxParseOptions): LibgdxParseResult;
27
+ //# sourceMappingURL=libgdxParse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"libgdxParse.d.ts","sourceRoot":"","sources":["../src/libgdxParse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,KAAK,EAAE,sBAAsB,EAAoB,MAAM,gBAAgB,CAAC;AAE/E,MAAM,WAAW,kBAAkB;IACjC;gFAC4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,QAAQ,EAAE,sBAAsB,CAAC;IACjC;iFAC6E;IAC7E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,2CAA2C;AAC3C,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAE7C;;;;2EAI2E;AAC3E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,qBAAqB,CAOrG;AAED;;2EAE2E;AAC3E,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,iBAAiB,CAYzG"}
@@ -0,0 +1,316 @@
1
+ import { createParticleEmitterConfig } from '@flighthq/particles';
2
+ /** Parse a libGDX 2D Particle Editor `.p` file string directly to a ParticleEmitterConfig.
3
+ *
4
+ * Single-pass: no intermediate document object is allocated.
5
+ * Use `parseLibgdxParticleDocument` instead when you need round-trip serialisation.
6
+ * Throws a clear, format-tagged error when the input cannot be parsed. */
7
+ export function parseLibgdxParticle(text, options) {
8
+ if (typeof text !== 'string' || !text.trim()) {
9
+ throw new Error('Invalid libGDX particle: input is empty or not a string');
10
+ }
11
+ const { sections } = parseLibgdxText(text);
12
+ const doc = sectionsToDocument(sections);
13
+ return documentToConfig(doc, options?.textureSize ?? 1);
14
+ }
15
+ /** Parse a libGDX 2D Particle Editor `.p` file string and preserve the full document
16
+ * for round-trip serialisation via `serializeLibgdxParticle`.
17
+ * Throws a clear, format-tagged error when the input cannot be parsed. */
18
+ export function parseLibgdxParticleDocument(text, options) {
19
+ if (typeof text !== 'string' || !text.trim()) {
20
+ throw new Error('Invalid libGDX particle: input is empty or not a string');
21
+ }
22
+ const { sections } = parseLibgdxText(text);
23
+ const doc = sectionsToDocument(sections);
24
+ const textureSize = options?.textureSize ?? 1;
25
+ return {
26
+ config: documentToConfig(doc, textureSize),
27
+ document: doc,
28
+ warnings: collectLibgdxWarnings(doc),
29
+ };
30
+ }
31
+ const DEG2RAD = Math.PI / 180;
32
+ function boolKey(section, key, def = false) {
33
+ const v = section.get(key);
34
+ if (v === undefined)
35
+ return def;
36
+ return v === 'true';
37
+ }
38
+ function collectLibgdxWarnings(doc) {
39
+ const warnings = [];
40
+ if (doc.delay.active)
41
+ warnings.push('libGDX emission delay is not supported and was ignored');
42
+ if (doc.lifeOffset.active)
43
+ warnings.push('libGDX lifeOffset is not supported and was ignored');
44
+ if (doc.xOffset.active || doc.yOffset.active) {
45
+ warnings.push('libGDX emitter x/y position offsets are not supported and were ignored');
46
+ }
47
+ if (doc.premultipliedAlpha) {
48
+ warnings.push('libGDX premultipliedAlpha flag is informational only; blending behavior may differ');
49
+ }
50
+ if (doc.spawnShape.shape === 'line') {
51
+ warnings.push('libGDX line spawn shape has no equivalent and was mapped to point emitter');
52
+ }
53
+ return warnings;
54
+ }
55
+ function documentToConfig(doc, textureSize) {
56
+ const [lifeMin, lifeMax] = rangeToMinMax(doc.life);
57
+ const [velMin, velMax] = doc.velocity.active ? rangeToMinMax(doc.velocity) : [0, 0];
58
+ const [angleMin, angleMax] = doc.angle.active ? rangeToMinMax(doc.angle) : [0, 360];
59
+ const angleMid = (angleMin + angleMax) * 0.5 * DEG2RAD;
60
+ const spread = (angleMax - angleMin) * 0.5 * DEG2RAD;
61
+ const [scaleMinPx, scaleMaxPx] = rangeToMinMax(doc.scale);
62
+ const scaleMin = scaleMinPx / textureSize;
63
+ const scaleMax = scaleMaxPx / textureSize;
64
+ // Scale end: use last scaling value (libGDX stores the scale curve as normalised scalings).
65
+ const lastScaling = doc.scale.scaling.length > 0 ? doc.scale.scaling[doc.scale.scaling.length - 1] : 1;
66
+ const scaleEnd = lastScaling;
67
+ const [windMin] = doc.wind.active ? rangeToMinMax(doc.wind) : [0, 0];
68
+ const [gravMin] = doc.gravity.active ? rangeToMinMax(doc.gravity) : [0, 0];
69
+ // Emitter spawn shape
70
+ const shape = doc.spawnShape.shape;
71
+ const [swMin, swMax] = rangeToMinMax(doc.spawnWidth);
72
+ const [shMin, shMax] = rangeToMinMax(doc.spawnHeight);
73
+ const swMid = (swMin + swMax) * 0.5;
74
+ const shMid = (shMin + shMax) * 0.5;
75
+ let emitterShape = 'point';
76
+ let emitterRadius = 0;
77
+ let emitterWidth = 0;
78
+ let emitterHeight = 0;
79
+ if (shape === 'ellipse') {
80
+ if (swMid > 0 && swMid === shMid) {
81
+ emitterShape = 'circle';
82
+ emitterRadius = swMid * 0.5;
83
+ }
84
+ else if (swMid > 0 || shMid > 0) {
85
+ emitterShape = 'rect';
86
+ emitterWidth = swMid;
87
+ emitterHeight = shMid;
88
+ }
89
+ }
90
+ else if (shape === 'square') {
91
+ emitterShape = 'rect';
92
+ emitterWidth = swMid;
93
+ emitterHeight = shMid;
94
+ }
95
+ // Colors: first and last tint entry
96
+ const firstColor = doc.tint.colors[0] ?? 'ffffff';
97
+ const lastColor = doc.tint.colors[doc.tint.colors.length - 1] ?? 'ffffff';
98
+ const [sr, sg, sb] = hexToRgb(firstColor);
99
+ const [er, eg, eb] = hexToRgb(lastColor);
100
+ // Alpha: first and last transparency scaling value
101
+ const alphaStart = doc.transparency.scaling[0] ?? 1;
102
+ const alphaEnd = doc.transparency.scaling[doc.transparency.scaling.length - 1] ?? 0;
103
+ // Duration: libGDX lowMin/lowMax is the constant range; use midpoint
104
+ const [durMin, durMax] = rangeToMinMax(doc.duration);
105
+ const durMid = (durMin + durMax) * 0.5;
106
+ // Rotation: libGDX stores angular velocity range
107
+ const [rotMin, rotMax] = doc.rotation.active ? rangeToMinMax(doc.rotation) : [0, 0];
108
+ const lifetimeMid = (lifeMin / 1000 + lifeMax / 1000) * 0.5 || 1;
109
+ const rotSpeedMin = (rotMin * DEG2RAD) / lifetimeMid;
110
+ const rotSpeedMax = (rotMax * DEG2RAD) / lifetimeMid;
111
+ const blendMode = doc.additive ? 'add' : 'normal';
112
+ return createParticleEmitterConfig({
113
+ maxParticles: doc.maxParticleCount,
114
+ loop: durMid <= 0,
115
+ duration: durMid > 0 ? durMid / 1000 : 0,
116
+ lifetimeMin: Math.max(0, lifeMin / 1000),
117
+ lifetimeMax: lifeMax / 1000,
118
+ speedMin: velMin,
119
+ speedMax: velMax,
120
+ directionX: Math.cos(angleMid),
121
+ directionY: -Math.sin(angleMid),
122
+ spread,
123
+ gravityX: windMin,
124
+ gravityY: gravMin,
125
+ emitterShape,
126
+ emitterRadius,
127
+ emitterWidth,
128
+ emitterHeight,
129
+ scaleMin,
130
+ scaleMax,
131
+ scaleEnd,
132
+ colorStartR: sr,
133
+ colorStartG: sg,
134
+ colorStartB: sb,
135
+ colorEndR: er,
136
+ colorEndG: eg,
137
+ colorEndB: eb,
138
+ alphaStart,
139
+ alphaEnd,
140
+ rotationSpeedMin: rotSpeedMin,
141
+ rotationSpeedMax: rotSpeedMax,
142
+ blendMode,
143
+ });
144
+ }
145
+ function hexToRgb(hex) {
146
+ const s = hex.replace(/^#/, '').padEnd(6, 'f');
147
+ const channel = (i) => {
148
+ const v = parseInt(s.slice(i, i + 2), 16);
149
+ return Number.isFinite(v) ? v / 255 : 1;
150
+ };
151
+ return [channel(0), channel(2), channel(4)];
152
+ }
153
+ function numKey(section, key, def = 0) {
154
+ const v = section.get(key);
155
+ if (v === undefined)
156
+ return def;
157
+ const n = parseFloat(v);
158
+ return Number.isFinite(n) ? n : def;
159
+ }
160
+ /** Parse the libGDX .p text format into a map of section → key/value pairs.
161
+ *
162
+ * The format is a sequence of:
163
+ * SectionName (bare line, no colon)
164
+ * key: value (key/value pair belonging to the last section)
165
+ *
166
+ * Scalar multi-value groups like `scalingCount: N / scaling0: v ... scalingN: v`
167
+ * are read as individual keys; the caller assembles them into arrays. */
168
+ function parseLibgdxText(text) {
169
+ const lines = text.split(/\r?\n/);
170
+ const sections = new Map();
171
+ let currentSection = '';
172
+ let current = new Map();
173
+ let header = '';
174
+ for (const raw of lines) {
175
+ const line = raw.trim();
176
+ if (line === '' || line.startsWith('#'))
177
+ continue;
178
+ const colonIdx = line.indexOf(':');
179
+ if (colonIdx === -1) {
180
+ // Bare section name. "- Emitter -" or "SectionName" or file header.
181
+ if (currentSection !== '' || current.size > 0) {
182
+ sections.set(currentSection, current);
183
+ }
184
+ currentSection = line;
185
+ current = new Map();
186
+ if (header === '')
187
+ header = line;
188
+ }
189
+ else {
190
+ const key = line.slice(0, colonIdx).trim();
191
+ const value = line.slice(colonIdx + 1).trim();
192
+ current.set(key, value);
193
+ }
194
+ }
195
+ // Flush last section.
196
+ if (currentSection !== '' || current.size > 0) {
197
+ sections.set(currentSection, current);
198
+ }
199
+ return { header, sections };
200
+ }
201
+ function rangeToMinMax(r) {
202
+ // libGDX range: low end is lowMin..lowMax, high end is highMin..highMax.
203
+ // We take the midpoint of each end, then use low as min and high as max.
204
+ const lo = (r.lowMin + r.lowMax) * 0.5;
205
+ const hi = (r.highMin + r.highMax) * 0.5;
206
+ return [Math.min(lo, hi), Math.max(lo, hi)];
207
+ }
208
+ function readRange(section, def = {}) {
209
+ const count = numKey(section, 'scalingCount', 1) | 0;
210
+ const scaling = [];
211
+ const tlCount = numKey(section, 'timelineCount', 1) | 0;
212
+ const timeline = [];
213
+ for (let i = 0; i < count; i++)
214
+ scaling.push(numKey(section, `scaling${i}`, 1));
215
+ for (let i = 0; i < tlCount; i++)
216
+ timeline.push(numKey(section, `timeline${i}`, i === 0 ? 0 : 1));
217
+ return {
218
+ lowMin: numKey(section, 'lowMin', def.lowMin ?? 0),
219
+ lowMax: numKey(section, 'lowMax', def.lowMax ?? 0),
220
+ highMin: numKey(section, 'highMin', def.highMin ?? 0),
221
+ highMax: numKey(section, 'highMax', def.highMax ?? 0),
222
+ relative: boolKey(section, 'relative', false),
223
+ scaling,
224
+ timeline,
225
+ };
226
+ }
227
+ function sectionsToDocument(sections) {
228
+ // Find the emitter name: the "- Emitter -" section contains both the emitter name
229
+ // and the root metadata keys (maxParticleCount, additive, imagePath, etc.).
230
+ const emitterKey = [...sections.keys()].find((k) => k.startsWith('- ') && k.endsWith(' -'));
231
+ const emitterName = emitterKey?.slice(2, -2).trim() ?? '';
232
+ // Root keys (additive, maxParticleCount, imagePath) live in the emitter header section.
233
+ // Fall back to the empty-key section for files that don't use the "- Name -" form.
234
+ const root = (emitterKey !== undefined ? sections.get(emitterKey) : null) ??
235
+ sections.get('Particle Effect') ??
236
+ sections.get('') ??
237
+ new Map();
238
+ const get = (name) => sections.get(name) ?? new Map();
239
+ const delaySection = get('Delay');
240
+ const durationSection = get('Duration');
241
+ const emissionSection = get('Emission');
242
+ const lifeSection = get('Life');
243
+ const lifeOffsetSection = get('Life Offset');
244
+ const xOffSection = get('X Offset');
245
+ const yOffSection = get('Y Offset');
246
+ const spawnShapeSection = get('Spawn Shape');
247
+ const spawnWidthSection = get('Spawn Width');
248
+ const spawnHeightSection = get('Spawn Height');
249
+ const scaleSection = get('Scale');
250
+ const velocitySection = get('Velocity');
251
+ const angleSection = get('Angle');
252
+ const rotationSection = get('Rotation');
253
+ const windSection = get('Wind');
254
+ const gravitySection = get('Gravity');
255
+ const tintSection = get('Tint');
256
+ const transparencySection = get('Transparency');
257
+ // Tint colors are stored as a comma-separated hex string and a timeline.
258
+ const tintColors = [];
259
+ const tintTimeline = [];
260
+ const rawColors = strKey(tintSection, 'colors', '');
261
+ if (rawColors) {
262
+ rawColors.split(',').forEach((c) => {
263
+ const trimmed = c.trim();
264
+ if (trimmed)
265
+ tintColors.push(trimmed);
266
+ });
267
+ }
268
+ if (tintColors.length === 0)
269
+ tintColors.push('ffffff');
270
+ const tintTlCount = numKey(tintSection, 'timelineCount', 1) | 0 || 1;
271
+ for (let i = 0; i < tintTlCount; i++)
272
+ tintTimeline.push(numKey(tintSection, `timeline${i}`, i === 0 ? 0 : 1));
273
+ const shapeStr = strKey(spawnShapeSection, 'shape', 'point');
274
+ const spawnShape = {
275
+ shape: (shapeStr === 'line' || shapeStr === 'square' || shapeStr === 'ellipse' ? shapeStr : 'point'),
276
+ edges: boolKey(spawnShapeSection, 'edges', false),
277
+ side: (strKey(spawnShapeSection, 'side', 'both') || 'both'),
278
+ };
279
+ return {
280
+ name: emitterName,
281
+ minParticleCount: numKey(root, 'minParticleCount', 4),
282
+ maxParticleCount: numKey(root, 'maxParticleCount', 100),
283
+ additive: boolKey(root, 'additive', false),
284
+ premultipliedAlpha: boolKey(root, 'premultipliedAlpha', false),
285
+ delay: { active: boolKey(delaySection, 'active', false), ...readRange(delaySection) },
286
+ duration: readRange(durationSection, { lowMin: 3000, lowMax: 3000 }),
287
+ emission: readRange(emissionSection, { highMin: 32, highMax: 64 }),
288
+ life: readRange(lifeSection, { lowMin: 1000, lowMax: 1000 }),
289
+ lifeOffset: { active: boolKey(lifeOffsetSection, 'active', false), ...readRange(lifeOffsetSection) },
290
+ xOffset: { active: boolKey(xOffSection, 'active', false), ...readRange(xOffSection) },
291
+ yOffset: { active: boolKey(yOffSection, 'active', false), ...readRange(yOffSection) },
292
+ spawnShape,
293
+ spawnWidth: readRange(spawnWidthSection),
294
+ spawnHeight: readRange(spawnHeightSection),
295
+ scale: readRange(scaleSection, { highMin: 32, highMax: 32 }),
296
+ velocity: {
297
+ active: boolKey(velocitySection, 'active', true),
298
+ ...readRange(velocitySection, { highMin: 100, highMax: 100 }),
299
+ },
300
+ angle: {
301
+ active: boolKey(angleSection, 'active', true),
302
+ ...readRange(angleSection, { highMin: 360, highMax: 360 }),
303
+ },
304
+ rotation: { active: boolKey(rotationSection, 'active', false), ...readRange(rotationSection) },
305
+ wind: { active: boolKey(windSection, 'active', false), ...readRange(windSection) },
306
+ gravity: { active: boolKey(gravitySection, 'active', false), ...readRange(gravitySection) },
307
+ tint: { colors: tintColors, timeline: tintTimeline },
308
+ transparency: readRange(transparencySection, { highMin: 1, highMax: 1 }),
309
+ imageCount: numKey(root, 'imageCount', 1),
310
+ imagePath: strKey(root, 'imagePath', ''),
311
+ };
312
+ }
313
+ function strKey(section, key, def = '') {
314
+ return section.get(key) ?? def;
315
+ }
316
+ //# sourceMappingURL=libgdxParse.js.map