@doki-land/live2d 0.0.11 → 0.0.13
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.
- package/README.md +1 -1
- package/dist/index.d.ts +118 -23
- package/dist/index.js +938 -339
- package/package.json +4 -4
- package/src/create-live2d.ts +23 -575
- package/src/index.ts +15 -2
- package/src/stage/actor-model-slot.ts +429 -0
- package/src/stage/actor.ts +188 -0
- package/src/stage/index.ts +18 -0
- package/src/stage/single-facade.ts +221 -0
- package/src/stage/stage.ts +388 -0
- package/src/stage/transform.ts +161 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { EventEmitter as EventEmitter2 } from "@doki-land/live2d-core";
|
|
2
|
+
import { DEFAULT_ACTOR_TRANSFORM as DEFAULT_ACTOR_TRANSFORM2, EventEmitter as EventEmitter2 } from "@doki-land/live2d-core";
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_NPM_CDN,
|
|
5
5
|
resolveModelSourceUrl as resolveModelSourceUrl2,
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
} from "@doki-land/live2d-loader";
|
|
8
8
|
import {
|
|
9
9
|
createCanvas2DRenderer,
|
|
10
|
-
createMoc2Backend as
|
|
11
|
-
createMoc3Backend as
|
|
10
|
+
createMoc2Backend as createMoc2Backend3,
|
|
11
|
+
createMoc3Backend as createMoc3Backend3,
|
|
12
12
|
createQuadProgram,
|
|
13
|
-
createRenderer as
|
|
13
|
+
createRenderer as createRenderer3,
|
|
14
14
|
createWebGl2Renderer,
|
|
15
15
|
createWebGpuRenderer,
|
|
16
16
|
decodeMoc3,
|
|
@@ -21,19 +21,51 @@ import {
|
|
|
21
21
|
} from "@doki-land/live2d-renderer";
|
|
22
22
|
|
|
23
23
|
// src/create-live2d.ts
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
createMoc2Backend as createMoc2Backend2,
|
|
26
|
+
createMoc3Backend as createMoc3Backend2,
|
|
27
|
+
createRenderer as createRenderer2
|
|
28
|
+
} from "@doki-land/live2d-renderer";
|
|
29
|
+
|
|
30
|
+
// src/focus.ts
|
|
31
|
+
function focusParameterUpdates(parameters, dragX, dragY) {
|
|
32
|
+
const byId = new Map(parameters.map((p) => [p.id, p]));
|
|
33
|
+
const x = clampUnit(dragX);
|
|
34
|
+
const y = clampUnit(dragY);
|
|
35
|
+
const out = [];
|
|
36
|
+
const set = (id, normalized) => {
|
|
37
|
+
const binding = byId.get(id);
|
|
38
|
+
if (!binding) return;
|
|
39
|
+
out.push({ id, value: valueFromNormalized(binding, normalized) });
|
|
40
|
+
};
|
|
41
|
+
set("PARAM_ANGLE_X", x);
|
|
42
|
+
set("PARAM_ANGLE_Y", y);
|
|
43
|
+
set("PARAM_ANGLE_Z", clampUnit(x * y * -1));
|
|
44
|
+
set("PARAM_BODY_ANGLE_X", x);
|
|
45
|
+
set("PARAM_BODY_ANGLE_Y", y);
|
|
46
|
+
set("PARAM_EYE_BALL_X", x);
|
|
47
|
+
set("PARAM_EYE_BALL_Y", y);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
function clampUnit(n) {
|
|
51
|
+
if (n > 1) return 1;
|
|
52
|
+
if (n < -1) return -1;
|
|
53
|
+
return n;
|
|
54
|
+
}
|
|
55
|
+
function valueFromNormalized(binding, normalized) {
|
|
56
|
+
const n = clampUnit(normalized);
|
|
57
|
+
return n >= 0 ? binding.defaultValue + (binding.max - binding.defaultValue) * n : binding.defaultValue + (binding.defaultValue - binding.min) * n;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/stage/actor-model-slot.ts
|
|
61
|
+
import { modelSourceUrl } from "@doki-land/live2d-core";
|
|
25
62
|
import {
|
|
26
63
|
createUrlAssetResolver,
|
|
27
64
|
fetchModelJson,
|
|
28
65
|
normalizeModelSettings,
|
|
29
66
|
resolveModelSourceUrl
|
|
30
67
|
} from "@doki-land/live2d-loader";
|
|
31
|
-
import {
|
|
32
|
-
createMoc2Backend,
|
|
33
|
-
createMoc3Backend,
|
|
34
|
-
createRenderer,
|
|
35
|
-
selectModelBackend
|
|
36
|
-
} from "@doki-land/live2d-renderer";
|
|
68
|
+
import { selectModelBackend } from "@doki-land/live2d-renderer";
|
|
37
69
|
|
|
38
70
|
// src/load-textures.ts
|
|
39
71
|
function guessMime(path) {
|
|
@@ -578,116 +610,577 @@ function optionalNum(v) {
|
|
|
578
610
|
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
579
611
|
}
|
|
580
612
|
|
|
581
|
-
// src/
|
|
613
|
+
// src/stage/actor-model-slot.ts
|
|
582
614
|
function lerp(a, b, t) {
|
|
583
615
|
return a + (b - a) * Math.min(1, Math.max(0, t));
|
|
584
616
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
if (!
|
|
617
|
+
var ActorModelSlot = class {
|
|
618
|
+
#backends;
|
|
619
|
+
#renderer;
|
|
620
|
+
#onProgress;
|
|
621
|
+
#motionPlayer;
|
|
622
|
+
#motionCache = /* @__PURE__ */ new Map();
|
|
623
|
+
#drawPass = null;
|
|
624
|
+
#model = null;
|
|
625
|
+
#backend = null;
|
|
626
|
+
#textures = [];
|
|
627
|
+
#resolver = null;
|
|
628
|
+
#loadGeneration = 0;
|
|
629
|
+
constructor(options) {
|
|
630
|
+
this.#backends = options.backends;
|
|
631
|
+
this.#renderer = options.renderer;
|
|
632
|
+
this.#onProgress = options.onProgress;
|
|
633
|
+
this.#motionPlayer = new MotionPlayer({
|
|
634
|
+
onStart: options.onMotionStart,
|
|
635
|
+
onFinish: options.onMotionFinish
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
get model() {
|
|
639
|
+
return this.#model;
|
|
640
|
+
}
|
|
641
|
+
get drawPass() {
|
|
642
|
+
return this.#drawPass;
|
|
643
|
+
}
|
|
644
|
+
ensureDrawPass() {
|
|
645
|
+
if (!this.#drawPass) {
|
|
646
|
+
this.#drawPass = this.#renderer.createModelDrawPass();
|
|
647
|
+
}
|
|
648
|
+
return this.#drawPass;
|
|
649
|
+
}
|
|
650
|
+
#report(payload) {
|
|
651
|
+
this.#onProgress?.(payload);
|
|
652
|
+
}
|
|
653
|
+
#clearTextures() {
|
|
654
|
+
if (this.#textures.length > 0) {
|
|
655
|
+
releaseTextureData(this.#textures);
|
|
656
|
+
this.#textures = [];
|
|
657
|
+
}
|
|
658
|
+
this.#drawPass?.setTextures([]);
|
|
659
|
+
}
|
|
660
|
+
#applyMotionSamples(samples) {
|
|
661
|
+
if (!this.#model || !this.#backend) return;
|
|
614
662
|
for (const s of samples) {
|
|
615
663
|
if (s.weight <= 0) continue;
|
|
616
664
|
if (s.target === "PartOpacity") {
|
|
617
|
-
if (!
|
|
665
|
+
if (!this.#backend.setPartOpacity) continue;
|
|
618
666
|
if (s.weight >= 1) {
|
|
619
|
-
|
|
667
|
+
this.#backend.setPartOpacity(this.#model, s.id, s.value);
|
|
620
668
|
} else {
|
|
621
669
|
const cur2 = 1;
|
|
622
|
-
|
|
623
|
-
model,
|
|
670
|
+
this.#backend.setPartOpacity(
|
|
671
|
+
this.#model,
|
|
624
672
|
s.id,
|
|
625
673
|
cur2 + (s.value - cur2) * s.weight
|
|
626
674
|
);
|
|
627
675
|
}
|
|
628
676
|
continue;
|
|
629
677
|
}
|
|
630
|
-
if (s.target !== "Parameter" || !
|
|
678
|
+
if (s.target !== "Parameter" || !this.#backend.setParameter)
|
|
631
679
|
continue;
|
|
632
680
|
if (s.weight >= 1) {
|
|
633
|
-
|
|
681
|
+
this.#backend.setParameter(this.#model, s.id, s.value);
|
|
634
682
|
continue;
|
|
635
683
|
}
|
|
636
|
-
const cur =
|
|
637
|
-
|
|
638
|
-
model,
|
|
684
|
+
const cur = this.#backend.listParameters?.(this.#model).find((p) => p.id === s.id)?.value ?? s.value;
|
|
685
|
+
this.#backend.setParameter(
|
|
686
|
+
this.#model,
|
|
639
687
|
s.id,
|
|
640
688
|
cur + (s.value - cur) * s.weight
|
|
641
689
|
);
|
|
642
690
|
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
691
|
+
}
|
|
692
|
+
async load(source, resolver) {
|
|
693
|
+
const gen = ++this.#loadGeneration;
|
|
694
|
+
this.#report({
|
|
695
|
+
stage: "mounting",
|
|
696
|
+
progress: 0.01,
|
|
697
|
+
detail: "prepare draw pass"
|
|
698
|
+
});
|
|
699
|
+
const drawPass = this.ensureDrawPass();
|
|
700
|
+
this.#report({
|
|
701
|
+
stage: "resolve",
|
|
702
|
+
progress: 0.02,
|
|
703
|
+
detail: "resolve source"
|
|
704
|
+
});
|
|
705
|
+
let json;
|
|
706
|
+
let baseUrl;
|
|
707
|
+
let settingsUrl;
|
|
708
|
+
if (typeof source === "object" && source.kind === "json") {
|
|
709
|
+
json = source.json;
|
|
710
|
+
baseUrl = source.baseUrl;
|
|
711
|
+
settingsUrl = source.baseUrl;
|
|
712
|
+
this.#report({
|
|
713
|
+
stage: "settings",
|
|
714
|
+
progress: 0.2,
|
|
715
|
+
detail: "inline settings"
|
|
716
|
+
});
|
|
717
|
+
} else {
|
|
718
|
+
const raw = typeof source === "string" ? source : source.kind === "npm" ? modelSourceUrl(source) : source.url;
|
|
719
|
+
const cdnBase = typeof source === "object" && source.kind === "npm" ? source.cdnBase : void 0;
|
|
720
|
+
const fetchUrl = resolveModelSourceUrl(raw, {
|
|
721
|
+
npmCdnBase: cdnBase
|
|
722
|
+
});
|
|
723
|
+
this.#report({
|
|
724
|
+
stage: "settings",
|
|
725
|
+
progress: 0.05,
|
|
726
|
+
detail: fetchUrl
|
|
727
|
+
});
|
|
728
|
+
json = await fetchModelJson(fetchUrl, (u) => {
|
|
729
|
+
const ratio = u.bytesTotal && u.bytesTotal > 0 ? u.bytesLoaded / u.bytesTotal : 0;
|
|
730
|
+
this.#report({
|
|
731
|
+
stage: "settings",
|
|
732
|
+
progress: lerp(0.05, 0.22, ratio),
|
|
733
|
+
detail: fetchUrl,
|
|
734
|
+
bytesLoaded: u.bytesLoaded,
|
|
735
|
+
bytesTotal: u.bytesTotal
|
|
736
|
+
});
|
|
737
|
+
});
|
|
738
|
+
baseUrl = fetchUrl;
|
|
739
|
+
settingsUrl = fetchUrl;
|
|
740
|
+
}
|
|
741
|
+
if (gen !== this.#loadGeneration) {
|
|
742
|
+
throw new Error("@doki-land/live2d: load cancelled");
|
|
648
743
|
}
|
|
649
|
-
|
|
744
|
+
const settings = normalizeModelSettings(json, settingsUrl);
|
|
745
|
+
this.#report({
|
|
746
|
+
stage: "moc",
|
|
747
|
+
progress: 0.25,
|
|
748
|
+
detail: settings.moc
|
|
749
|
+
});
|
|
750
|
+
const assetResolver = resolver ?? createUrlAssetResolver(baseUrl, {
|
|
751
|
+
onBytesProgress: (key, u) => {
|
|
752
|
+
const isMoc = key === settings.moc;
|
|
753
|
+
const ratio = u.bytesTotal && u.bytesTotal > 0 ? u.bytesLoaded / u.bytesTotal : 0;
|
|
754
|
+
if (isMoc) {
|
|
755
|
+
this.#report({
|
|
756
|
+
stage: "moc",
|
|
757
|
+
progress: lerp(0.25, 0.8, ratio),
|
|
758
|
+
detail: key,
|
|
759
|
+
bytesLoaded: u.bytesLoaded,
|
|
760
|
+
bytesTotal: u.bytesTotal
|
|
761
|
+
});
|
|
762
|
+
} else {
|
|
763
|
+
this.#report({
|
|
764
|
+
stage: "textures",
|
|
765
|
+
progress: lerp(0.8, 0.9, ratio),
|
|
766
|
+
detail: key,
|
|
767
|
+
bytesLoaded: u.bytesLoaded,
|
|
768
|
+
bytesTotal: u.bytesTotal
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
this.#resolver = assetResolver;
|
|
774
|
+
this.#motionPlayer.clear();
|
|
775
|
+
this.#motionCache.clear();
|
|
776
|
+
const backend = selectModelBackend([...this.#backends], json);
|
|
777
|
+
this.#report({
|
|
778
|
+
stage: "decode",
|
|
779
|
+
progress: 0.85,
|
|
780
|
+
detail: `decode ${settings.format}`
|
|
781
|
+
});
|
|
782
|
+
const next = await backend.createModel(settings, {
|
|
783
|
+
renderer: this.#renderer,
|
|
784
|
+
resolver: assetResolver
|
|
785
|
+
});
|
|
786
|
+
if (gen !== this.#loadGeneration) {
|
|
787
|
+
backend.destroyModel(next);
|
|
788
|
+
throw new Error("@doki-land/live2d: load cancelled");
|
|
789
|
+
}
|
|
790
|
+
this.#clearTextures();
|
|
791
|
+
if (settings.textures.length > 0) {
|
|
792
|
+
this.#report({
|
|
793
|
+
stage: "textures",
|
|
794
|
+
progress: 0.88,
|
|
795
|
+
detail: `${settings.textures.length} textures`
|
|
796
|
+
});
|
|
797
|
+
const textures = await loadTextureData(
|
|
798
|
+
assetResolver,
|
|
799
|
+
settings.textures,
|
|
800
|
+
{
|
|
801
|
+
onProgress: (u) => {
|
|
802
|
+
const ratio = u.total > 0 ? (u.index + 1) / u.total : 1;
|
|
803
|
+
this.#report({
|
|
804
|
+
stage: "textures",
|
|
805
|
+
progress: lerp(0.88, 0.96, ratio),
|
|
806
|
+
detail: u.key,
|
|
807
|
+
bytesLoaded: u.bytesLoaded,
|
|
808
|
+
bytesTotal: u.bytesTotal
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
if (gen !== this.#loadGeneration) {
|
|
814
|
+
releaseTextureData(textures);
|
|
815
|
+
backend.destroyModel(next);
|
|
816
|
+
throw new Error("@doki-land/live2d: load cancelled");
|
|
817
|
+
}
|
|
818
|
+
this.#textures = textures;
|
|
819
|
+
drawPass.setTextures(textures);
|
|
820
|
+
}
|
|
821
|
+
if (this.#model && this.#backend) {
|
|
822
|
+
this.#backend.destroyModel(this.#model);
|
|
823
|
+
}
|
|
824
|
+
this.#model = next;
|
|
825
|
+
this.#backend = backend;
|
|
826
|
+
this.#report({
|
|
827
|
+
stage: "ready",
|
|
828
|
+
progress: 1,
|
|
829
|
+
detail: next.id
|
|
830
|
+
});
|
|
831
|
+
return next;
|
|
832
|
+
}
|
|
833
|
+
setParameter(id, value) {
|
|
834
|
+
if (!this.#model || !this.#backend?.setParameter) return;
|
|
835
|
+
this.#backend.setParameter(this.#model, id, value);
|
|
836
|
+
}
|
|
837
|
+
listParameters() {
|
|
838
|
+
if (!this.#model || !this.#backend?.listParameters) return [];
|
|
839
|
+
return this.#backend.listParameters(this.#model);
|
|
840
|
+
}
|
|
841
|
+
listMotionGroups() {
|
|
842
|
+
return this.#model?.settings.motionGroups ?? {};
|
|
843
|
+
}
|
|
844
|
+
async playMotion(group, index = 0, options = {}) {
|
|
845
|
+
if (!this.#model || !this.#resolver) return false;
|
|
846
|
+
const list = this.#model.settings.motionGroups[group];
|
|
847
|
+
const def = list?.[index];
|
|
848
|
+
if (!def) return false;
|
|
849
|
+
let clip = this.#motionCache.get(def.file);
|
|
850
|
+
if (!clip) {
|
|
851
|
+
const json = await this.#resolver.fetchJson(def.file);
|
|
852
|
+
clip = parseMotion3(json);
|
|
853
|
+
this.#motionCache.set(def.file, clip);
|
|
854
|
+
}
|
|
855
|
+
const fadeInTime = options.fadeInTime ?? def.fadeInTime ?? clip.fadeInTime;
|
|
856
|
+
const fadeOutTime = options.fadeOutTime ?? def.fadeOutTime ?? clip.fadeOutTime;
|
|
857
|
+
return this.#motionPlayer.start(group, index, clip, {
|
|
858
|
+
priority: options.priority ?? MotionPriority.normal,
|
|
859
|
+
slot: options.slot,
|
|
860
|
+
queue: options.queue,
|
|
861
|
+
loop: options.loop,
|
|
862
|
+
fadeInTime,
|
|
863
|
+
fadeOutTime
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
stopMotion(opts) {
|
|
867
|
+
this.#motionPlayer.stop(opts?.fade !== false, opts?.slot);
|
|
868
|
+
}
|
|
869
|
+
listPlayingMotions() {
|
|
870
|
+
return this.#motionPlayer.listPlaying();
|
|
871
|
+
}
|
|
872
|
+
update(deltaTimeSeconds) {
|
|
873
|
+
if (!this.#model || !this.#backend || !this.#drawPass) return null;
|
|
874
|
+
this.#applyMotionSamples(this.#motionPlayer.update(deltaTimeSeconds));
|
|
875
|
+
this.#backend.updateModel(this.#model, deltaTimeSeconds);
|
|
876
|
+
return this.#backend.getDrawables(this.#model);
|
|
877
|
+
}
|
|
878
|
+
hitTestModelCoords(modelX, modelY) {
|
|
879
|
+
if (!this.#model || !this.#backend) return null;
|
|
880
|
+
const drawables = this.#backend.getDrawables(this.#model);
|
|
881
|
+
for (let n = drawables.length - 1; n >= 0; n -= 1) {
|
|
882
|
+
const d = drawables[n];
|
|
883
|
+
if (!d.visible || d.opacity <= 0) continue;
|
|
884
|
+
const p = d.vertexPositions;
|
|
885
|
+
const idx = d.indices;
|
|
886
|
+
for (let i = 0; i + 2 < idx.length; i += 3) {
|
|
887
|
+
const a = idx[i] * 2, b = idx[i + 1] * 2, c = idx[i + 2] * 2;
|
|
888
|
+
const ax = p[a], ay = p[a + 1];
|
|
889
|
+
const bx = p[b], by = p[b + 1];
|
|
890
|
+
const cx = p[c], cy = p[c + 1];
|
|
891
|
+
const s = (ax - cx) * (modelY - cy) - (ay - cy) * (modelX - cx);
|
|
892
|
+
const s1 = (bx - ax) * (modelY - ay) - (by - ay) * (modelX - ax);
|
|
893
|
+
const s2 = (cx - bx) * (modelY - by) - (cy - by) * (modelX - bx);
|
|
894
|
+
if (s >= 0 && s1 >= 0 && s2 >= 0 || s <= 0 && s1 <= 0 && s2 <= 0) {
|
|
895
|
+
const hitArea = this.#model.settings.hitAreas.find(
|
|
896
|
+
(h) => h.id === `D_${d.index}` || h.id === `${d.index}`
|
|
897
|
+
);
|
|
898
|
+
return hitArea?.name ?? `drawable:${d.index}`;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
destroy() {
|
|
905
|
+
this.#loadGeneration += 1;
|
|
906
|
+
this.#motionPlayer.clear();
|
|
907
|
+
this.#motionCache.clear();
|
|
908
|
+
this.#resolver = null;
|
|
909
|
+
if (this.#model && this.#backend) {
|
|
910
|
+
this.#backend.destroyModel(this.#model);
|
|
911
|
+
}
|
|
912
|
+
this.#model = null;
|
|
913
|
+
this.#backend = null;
|
|
914
|
+
this.#clearTextures();
|
|
915
|
+
this.#drawPass?.destroy();
|
|
916
|
+
this.#drawPass = null;
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// src/stage/transform.ts
|
|
921
|
+
import { DEFAULT_ACTOR_TRANSFORM } from "@doki-land/live2d-core";
|
|
922
|
+
function resolveActorTransform(patch) {
|
|
923
|
+
const base = DEFAULT_ACTOR_TRANSFORM;
|
|
924
|
+
const scale = patch?.scale ?? base.scale ?? 1;
|
|
925
|
+
return {
|
|
926
|
+
x: patch?.x ?? base.x,
|
|
927
|
+
y: patch?.y ?? base.y,
|
|
928
|
+
scale,
|
|
929
|
+
scaleX: patch?.scaleX ?? patch?.scale ?? scale,
|
|
930
|
+
scaleY: patch?.scaleY ?? patch?.scale ?? scale,
|
|
931
|
+
rotation: patch?.rotation ?? base.rotation ?? 0,
|
|
932
|
+
anchorX: patch?.anchorX ?? base.anchorX ?? 0.5,
|
|
933
|
+
anchorY: patch?.anchorY ?? base.anchorY ?? 1
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
function modelNdcToStage(modelX, modelY, transform) {
|
|
937
|
+
const scaleX = transform.scaleX ?? transform.scale ?? 1;
|
|
938
|
+
const scaleY = transform.scaleY ?? transform.scale ?? 1;
|
|
939
|
+
const anchorX = transform.anchorX ?? 0.5;
|
|
940
|
+
const anchorY = transform.anchorY ?? 1;
|
|
941
|
+
const rotation = transform.rotation ?? 0;
|
|
942
|
+
const anchorModelX = -1 + anchorX * 2;
|
|
943
|
+
const anchorModelY = 1 - anchorY * 2;
|
|
944
|
+
let localX = (modelX - anchorModelX) * scaleX * 0.5;
|
|
945
|
+
let localY = (anchorModelY - modelY) * scaleY * 0.5;
|
|
946
|
+
if (rotation !== 0) {
|
|
947
|
+
const c = Math.cos(rotation);
|
|
948
|
+
const s = Math.sin(rotation);
|
|
949
|
+
const rx = localX * c - localY * s;
|
|
950
|
+
const ry = localX * s + localY * c;
|
|
951
|
+
localX = rx;
|
|
952
|
+
localY = ry;
|
|
953
|
+
}
|
|
954
|
+
return {
|
|
955
|
+
stageX: transform.x + localX,
|
|
956
|
+
stageY: transform.y + localY
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
function stageToModelNdc(stageX, stageY, transform) {
|
|
960
|
+
const scaleX = transform.scaleX ?? transform.scale ?? 1;
|
|
961
|
+
const scaleY = transform.scaleY ?? transform.scale ?? 1;
|
|
962
|
+
const anchorX = transform.anchorX ?? 0.5;
|
|
963
|
+
const anchorY = transform.anchorY ?? 1;
|
|
964
|
+
const anchorModelX = -1 + anchorX * 2;
|
|
965
|
+
const anchorModelY = 1 - anchorY * 2;
|
|
966
|
+
const localX = (stageX - transform.x) / (scaleX * 0.5);
|
|
967
|
+
const localY = (stageY - transform.y) / (scaleY * 0.5);
|
|
968
|
+
return {
|
|
969
|
+
modelX: localX + anchorModelX,
|
|
970
|
+
modelY: anchorModelY - localY
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function clientToStage(clientX, clientY, canvas) {
|
|
974
|
+
const rect = canvas.getBoundingClientRect();
|
|
975
|
+
const x = rect.width > 0 ? (clientX - rect.left) / rect.width : 0;
|
|
976
|
+
const y = rect.height > 0 ? (clientY - rect.top) / rect.height : 0;
|
|
977
|
+
return {
|
|
978
|
+
stageX: Math.min(1, Math.max(0, x)),
|
|
979
|
+
stageY: Math.min(1, Math.max(0, y))
|
|
650
980
|
};
|
|
981
|
+
}
|
|
982
|
+
function stageFocusDrag(stageX, stageY, transform) {
|
|
983
|
+
const dx = stageX - transform.x;
|
|
984
|
+
const dy = transform.y - stageY;
|
|
985
|
+
const dragX = Math.min(1, Math.max(-1, dx * 2));
|
|
986
|
+
const dragY = Math.min(1, Math.max(-1, dy * 2));
|
|
987
|
+
return { dragX, dragY };
|
|
988
|
+
}
|
|
989
|
+
function layerOrderIndex(layer, definedLayers) {
|
|
990
|
+
const idx = definedLayers.indexOf(layer);
|
|
991
|
+
return idx >= 0 ? idx : definedLayers.length;
|
|
992
|
+
}
|
|
993
|
+
function compareActorsForDraw(a, b, definedLayers) {
|
|
994
|
+
const la = layerOrderIndex(a.layer, definedLayers);
|
|
995
|
+
const lb = layerOrderIndex(b.layer, definedLayers);
|
|
996
|
+
return la - lb || a.order - b.order || a.creationIndex - b.creationIndex;
|
|
997
|
+
}
|
|
998
|
+
function compareActorsForHit(a, b, definedLayers) {
|
|
999
|
+
return compareActorsForDraw(b, a, definedLayers);
|
|
1000
|
+
}
|
|
1001
|
+
function transformDrawablesForStage(drawables, transform, opacity) {
|
|
1002
|
+
const alpha = Math.min(1, Math.max(0, opacity));
|
|
1003
|
+
return drawables.map((d) => {
|
|
1004
|
+
if (!d.visible || alpha <= 0) return { ...d, visible: false };
|
|
1005
|
+
const pos = new Float32Array(d.vertexPositions.length);
|
|
1006
|
+
for (let i = 0; i < pos.length; i += 2) {
|
|
1007
|
+
const { stageX, stageY } = modelNdcToStage(
|
|
1008
|
+
d.vertexPositions[i],
|
|
1009
|
+
d.vertexPositions[i + 1],
|
|
1010
|
+
transform
|
|
1011
|
+
);
|
|
1012
|
+
pos[i] = stageX * 2 - 1;
|
|
1013
|
+
pos[i + 1] = 1 - stageY * 2;
|
|
1014
|
+
}
|
|
1015
|
+
return {
|
|
1016
|
+
...d,
|
|
1017
|
+
vertexPositions: pos,
|
|
1018
|
+
opacity: d.opacity * alpha
|
|
1019
|
+
};
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/stage/actor.ts
|
|
1024
|
+
var nextActorId = 0;
|
|
1025
|
+
var Live2dActorImpl = class {
|
|
1026
|
+
id;
|
|
1027
|
+
creationIndex;
|
|
1028
|
+
#transform;
|
|
1029
|
+
#visible = true;
|
|
1030
|
+
#opacity = 1;
|
|
1031
|
+
#layer = "characters";
|
|
1032
|
+
#order = 0;
|
|
1033
|
+
#destroyed = false;
|
|
1034
|
+
#lastDrawables = null;
|
|
1035
|
+
#slot;
|
|
1036
|
+
constructor(options, shared) {
|
|
1037
|
+
this.id = options?.id ?? shared.id;
|
|
1038
|
+
this.creationIndex = shared.creationIndex;
|
|
1039
|
+
this.#transform = resolveActorTransform(options?.transform);
|
|
1040
|
+
this.#visible = options?.visible ?? true;
|
|
1041
|
+
this.#opacity = options?.opacity ?? 1;
|
|
1042
|
+
this.#layer = options?.layer ?? "characters";
|
|
1043
|
+
this.#order = options?.order ?? 0;
|
|
1044
|
+
this.#slot = new ActorModelSlot({
|
|
1045
|
+
backends: shared.backends,
|
|
1046
|
+
renderer: shared.renderer
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
get model() {
|
|
1050
|
+
return this.#slot.model;
|
|
1051
|
+
}
|
|
1052
|
+
get visible() {
|
|
1053
|
+
return this.#visible;
|
|
1054
|
+
}
|
|
1055
|
+
set visible(value) {
|
|
1056
|
+
this.#visible = value;
|
|
1057
|
+
}
|
|
1058
|
+
get opacity() {
|
|
1059
|
+
return this.#opacity;
|
|
1060
|
+
}
|
|
1061
|
+
set opacity(value) {
|
|
1062
|
+
this.#opacity = Math.min(1, Math.max(0, value));
|
|
1063
|
+
}
|
|
1064
|
+
get layer() {
|
|
1065
|
+
return this.#layer;
|
|
1066
|
+
}
|
|
1067
|
+
set layer(value) {
|
|
1068
|
+
this.#layer = value;
|
|
1069
|
+
}
|
|
1070
|
+
get order() {
|
|
1071
|
+
return this.#order;
|
|
1072
|
+
}
|
|
1073
|
+
set order(value) {
|
|
1074
|
+
this.#order = value;
|
|
1075
|
+
}
|
|
1076
|
+
getTransform() {
|
|
1077
|
+
return { ...this.#transform };
|
|
1078
|
+
}
|
|
1079
|
+
setTransform(patch) {
|
|
1080
|
+
this.#transform = resolveActorTransform({
|
|
1081
|
+
...this.#transform,
|
|
1082
|
+
...patch
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
async load(source, resolver) {
|
|
1086
|
+
if (this.#destroyed) {
|
|
1087
|
+
throw new Error("@doki-land/live2d: actor destroyed");
|
|
1088
|
+
}
|
|
1089
|
+
return await this.#slot.load(source, resolver);
|
|
1090
|
+
}
|
|
1091
|
+
setParameter(id, value) {
|
|
1092
|
+
this.#slot.setParameter(id, value);
|
|
1093
|
+
}
|
|
1094
|
+
lookAt(stageX, stageY) {
|
|
1095
|
+
const { dragX, dragY } = stageFocusDrag(
|
|
1096
|
+
stageX,
|
|
1097
|
+
stageY,
|
|
1098
|
+
this.#transform
|
|
1099
|
+
);
|
|
1100
|
+
for (const u of focusParameterUpdates(
|
|
1101
|
+
this.#slot.listParameters(),
|
|
1102
|
+
dragX,
|
|
1103
|
+
dragY
|
|
1104
|
+
)) {
|
|
1105
|
+
this.#slot.setParameter(u.id, u.value);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
/** Internal: evaluate motion/physics and cache drawables for render. */
|
|
1109
|
+
update(deltaTimeSeconds) {
|
|
1110
|
+
if (!this.#visible || this.#opacity <= 0) {
|
|
1111
|
+
this.#lastDrawables = null;
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
this.#lastDrawables = this.#slot.update(deltaTimeSeconds);
|
|
1115
|
+
return this.#lastDrawables;
|
|
1116
|
+
}
|
|
1117
|
+
get lastDrawables() {
|
|
1118
|
+
return this.#lastDrawables;
|
|
1119
|
+
}
|
|
1120
|
+
get slot() {
|
|
1121
|
+
return this.#slot;
|
|
1122
|
+
}
|
|
1123
|
+
hitTestStage(stageX, stageY) {
|
|
1124
|
+
if (!this.#visible || this.#opacity <= 0 || !this.#slot.model)
|
|
1125
|
+
return null;
|
|
1126
|
+
const { modelX, modelY } = stageToModelNdc(
|
|
1127
|
+
stageX,
|
|
1128
|
+
stageY,
|
|
1129
|
+
this.#transform
|
|
1130
|
+
);
|
|
1131
|
+
const area = this.#slot.hitTestModelCoords(modelX, modelY);
|
|
1132
|
+
if (!area) return null;
|
|
1133
|
+
const drawableMatch = /^drawable:(\d+)$/.exec(area);
|
|
1134
|
+
return {
|
|
1135
|
+
actorId: this.id,
|
|
1136
|
+
area,
|
|
1137
|
+
drawableIndex: drawableMatch ? Number(drawableMatch[1]) : -1,
|
|
1138
|
+
stageX,
|
|
1139
|
+
stageY,
|
|
1140
|
+
localX: modelX,
|
|
1141
|
+
localY: modelY
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
destroy() {
|
|
1145
|
+
if (this.#destroyed) return;
|
|
1146
|
+
this.#destroyed = true;
|
|
1147
|
+
this.#slot.destroy();
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
function allocateActorId(prefix = "actor") {
|
|
1151
|
+
nextActorId += 1;
|
|
1152
|
+
return `${prefix}-${nextActorId}`;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// src/stage/single-facade.ts
|
|
1156
|
+
import { EventEmitter } from "@doki-land/live2d-core";
|
|
1157
|
+
function nowMs() {
|
|
1158
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
1159
|
+
}
|
|
1160
|
+
function createSingleActorFacade(stage, actor, backends) {
|
|
1161
|
+
const events = new EventEmitter();
|
|
1162
|
+
let canvas = null;
|
|
1163
|
+
let phase = "idle";
|
|
1164
|
+
let lastError = null;
|
|
1165
|
+
let generation = 0;
|
|
1166
|
+
let fpsSmooth = 0;
|
|
651
1167
|
const setPhase = (next) => {
|
|
652
1168
|
phase = next;
|
|
653
1169
|
events.emit("phase", { phase, generation });
|
|
654
1170
|
};
|
|
655
|
-
const report = (payload) => {
|
|
656
|
-
events.emit("progress", payload);
|
|
657
|
-
};
|
|
658
1171
|
const state = () => ({
|
|
659
1172
|
phase,
|
|
660
1173
|
lastError,
|
|
661
1174
|
generation
|
|
662
1175
|
});
|
|
663
|
-
const ensureInitialized = async () => {
|
|
664
|
-
if (!canvas) {
|
|
665
|
-
throw new Error(
|
|
666
|
-
"@doki-land/live2d: call mount(canvas) before loadModel"
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
if (!initPromise) {
|
|
670
|
-
setPhase("mounting");
|
|
671
|
-
const gen = generation;
|
|
672
|
-
initPromise = renderer.initialize(canvas).then(() => {
|
|
673
|
-
if (gen !== generation) return;
|
|
674
|
-
drawPass = renderer.createModelDrawPass();
|
|
675
|
-
setPhase("ready");
|
|
676
|
-
}).catch((err) => {
|
|
677
|
-
lastError = err;
|
|
678
|
-
setPhase("error");
|
|
679
|
-
events.emit("error", { error: err });
|
|
680
|
-
throw err;
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
await initPromise;
|
|
684
|
-
};
|
|
685
1176
|
const runtime = {
|
|
686
1177
|
events,
|
|
687
1178
|
backends,
|
|
688
|
-
renderer,
|
|
1179
|
+
renderer: stage.renderer,
|
|
1180
|
+
stage,
|
|
1181
|
+
actor,
|
|
689
1182
|
get model() {
|
|
690
|
-
return model;
|
|
1183
|
+
return actor.model;
|
|
691
1184
|
},
|
|
692
1185
|
get state() {
|
|
693
1186
|
return state();
|
|
@@ -695,159 +1188,22 @@ function createLive2D(options = {}) {
|
|
|
695
1188
|
mount(target) {
|
|
696
1189
|
generation += 1;
|
|
697
1190
|
canvas = target;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1191
|
+
setPhase("mounting");
|
|
1192
|
+
void stage.mount(target).then(
|
|
1193
|
+
() => setPhase(actor.model ? "live" : "ready"),
|
|
1194
|
+
(err) => {
|
|
1195
|
+
lastError = err;
|
|
1196
|
+
setPhase("error");
|
|
1197
|
+
events.emit("error", { error: err });
|
|
1198
|
+
}
|
|
1199
|
+
);
|
|
704
1200
|
},
|
|
705
1201
|
async loadModel(source, resolver) {
|
|
706
|
-
const gen = ++loadGeneration;
|
|
707
1202
|
setPhase("loading");
|
|
708
|
-
report({
|
|
709
|
-
stage: "mounting",
|
|
710
|
-
progress: 0.01,
|
|
711
|
-
detail: "initialize renderer"
|
|
712
|
-
});
|
|
713
|
-
await ensureInitialized();
|
|
714
|
-
if (gen !== loadGeneration) {
|
|
715
|
-
throw new Error("@doki-land/live2d: load cancelled");
|
|
716
|
-
}
|
|
717
|
-
report({
|
|
718
|
-
stage: "resolve",
|
|
719
|
-
progress: 0.02,
|
|
720
|
-
detail: "resolve source"
|
|
721
|
-
});
|
|
722
1203
|
try {
|
|
723
|
-
|
|
724
|
-
let baseUrl;
|
|
725
|
-
let settingsUrl;
|
|
726
|
-
if (typeof source === "object" && source.kind === "json") {
|
|
727
|
-
json = source.json;
|
|
728
|
-
baseUrl = source.baseUrl;
|
|
729
|
-
settingsUrl = source.baseUrl;
|
|
730
|
-
report({
|
|
731
|
-
stage: "settings",
|
|
732
|
-
progress: 0.2,
|
|
733
|
-
detail: "inline settings"
|
|
734
|
-
});
|
|
735
|
-
} else {
|
|
736
|
-
const raw = typeof source === "string" ? source : source.kind === "npm" ? modelSourceUrl(source) : source.url;
|
|
737
|
-
const cdnBase = typeof source === "object" && source.kind === "npm" ? source.cdnBase : void 0;
|
|
738
|
-
const fetchUrl = resolveModelSourceUrl(raw, {
|
|
739
|
-
npmCdnBase: cdnBase
|
|
740
|
-
});
|
|
741
|
-
report({
|
|
742
|
-
stage: "settings",
|
|
743
|
-
progress: 0.05,
|
|
744
|
-
detail: fetchUrl
|
|
745
|
-
});
|
|
746
|
-
json = await fetchModelJson(fetchUrl, (u) => {
|
|
747
|
-
const ratio = u.bytesTotal && u.bytesTotal > 0 ? u.bytesLoaded / u.bytesTotal : 0;
|
|
748
|
-
report({
|
|
749
|
-
stage: "settings",
|
|
750
|
-
progress: lerp(0.05, 0.22, ratio),
|
|
751
|
-
detail: fetchUrl,
|
|
752
|
-
bytesLoaded: u.bytesLoaded,
|
|
753
|
-
bytesTotal: u.bytesTotal
|
|
754
|
-
});
|
|
755
|
-
});
|
|
756
|
-
baseUrl = fetchUrl;
|
|
757
|
-
settingsUrl = fetchUrl;
|
|
758
|
-
}
|
|
759
|
-
if (gen !== loadGeneration) {
|
|
760
|
-
throw new Error("@doki-land/live2d: load cancelled");
|
|
761
|
-
}
|
|
762
|
-
const settings = normalizeModelSettings(json, settingsUrl);
|
|
763
|
-
report({
|
|
764
|
-
stage: "moc",
|
|
765
|
-
progress: 0.25,
|
|
766
|
-
detail: settings.moc
|
|
767
|
-
});
|
|
768
|
-
const assetResolver = resolver ?? createUrlAssetResolver(baseUrl, {
|
|
769
|
-
onBytesProgress: (key, u) => {
|
|
770
|
-
const isMoc = key === settings.moc;
|
|
771
|
-
const ratio = u.bytesTotal && u.bytesTotal > 0 ? u.bytesLoaded / u.bytesTotal : 0;
|
|
772
|
-
if (isMoc) {
|
|
773
|
-
report({
|
|
774
|
-
stage: "moc",
|
|
775
|
-
progress: lerp(0.25, 0.8, ratio),
|
|
776
|
-
detail: key,
|
|
777
|
-
bytesLoaded: u.bytesLoaded,
|
|
778
|
-
bytesTotal: u.bytesTotal
|
|
779
|
-
});
|
|
780
|
-
} else {
|
|
781
|
-
report({
|
|
782
|
-
stage: "textures",
|
|
783
|
-
progress: lerp(0.8, 0.9, ratio),
|
|
784
|
-
detail: key,
|
|
785
|
-
bytesLoaded: u.bytesLoaded,
|
|
786
|
-
bytesTotal: u.bytesTotal
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
});
|
|
791
|
-
activeResolver = assetResolver;
|
|
792
|
-
motionPlayer.clear();
|
|
793
|
-
motionCache.clear();
|
|
794
|
-
const backend = selectModelBackend(backends, json);
|
|
795
|
-
report({
|
|
796
|
-
stage: "decode",
|
|
797
|
-
progress: 0.85,
|
|
798
|
-
detail: `decode ${settings.format}`
|
|
799
|
-
});
|
|
800
|
-
const next = await backend.createModel(settings, {
|
|
801
|
-
renderer,
|
|
802
|
-
resolver: assetResolver
|
|
803
|
-
});
|
|
804
|
-
if (gen !== loadGeneration) {
|
|
805
|
-
backend.destroyModel(next);
|
|
806
|
-
throw new Error("@doki-land/live2d: load cancelled");
|
|
807
|
-
}
|
|
808
|
-
clearTextures();
|
|
809
|
-
if (settings.textures.length > 0 && drawPass) {
|
|
810
|
-
report({
|
|
811
|
-
stage: "textures",
|
|
812
|
-
progress: 0.88,
|
|
813
|
-
detail: `${settings.textures.length} textures`
|
|
814
|
-
});
|
|
815
|
-
const textures = await loadTextureData(
|
|
816
|
-
assetResolver,
|
|
817
|
-
settings.textures,
|
|
818
|
-
{
|
|
819
|
-
onProgress: (u) => {
|
|
820
|
-
const ratio = u.total > 0 ? (u.index + 1) / u.total : 1;
|
|
821
|
-
report({
|
|
822
|
-
stage: "textures",
|
|
823
|
-
progress: lerp(0.88, 0.96, ratio),
|
|
824
|
-
detail: u.key,
|
|
825
|
-
bytesLoaded: u.bytesLoaded,
|
|
826
|
-
bytesTotal: u.bytesTotal
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
);
|
|
831
|
-
if (gen !== loadGeneration) {
|
|
832
|
-
releaseTextureData(textures);
|
|
833
|
-
backend.destroyModel(next);
|
|
834
|
-
throw new Error("@doki-land/live2d: load cancelled");
|
|
835
|
-
}
|
|
836
|
-
loadedTextures = textures;
|
|
837
|
-
drawPass.setTextures(textures);
|
|
838
|
-
}
|
|
839
|
-
if (model && activeBackend) {
|
|
840
|
-
activeBackend.destroyModel(model);
|
|
841
|
-
}
|
|
842
|
-
model = next;
|
|
843
|
-
activeBackend = backend;
|
|
1204
|
+
const model = await actor.load(source, resolver);
|
|
844
1205
|
lastError = null;
|
|
845
1206
|
setPhase("live");
|
|
846
|
-
report({
|
|
847
|
-
stage: "ready",
|
|
848
|
-
progress: 1,
|
|
849
|
-
detail: model.id
|
|
850
|
-
});
|
|
851
1207
|
events.emit("ready", { modelId: model.id });
|
|
852
1208
|
return model;
|
|
853
1209
|
} catch (err) {
|
|
@@ -858,70 +1214,32 @@ function createLive2D(options = {}) {
|
|
|
858
1214
|
}
|
|
859
1215
|
},
|
|
860
1216
|
captureFrame() {
|
|
861
|
-
|
|
862
|
-
return activeBackend.captureFrame(model);
|
|
1217
|
+
return null;
|
|
863
1218
|
},
|
|
864
1219
|
setParameter(id, value) {
|
|
865
|
-
|
|
866
|
-
activeBackend.setParameter(model, id, value);
|
|
1220
|
+
actor.setParameter(id, value);
|
|
867
1221
|
},
|
|
868
1222
|
hitTest(x, y) {
|
|
869
|
-
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const p = d.vertexPositions;
|
|
875
|
-
const idx = d.indices;
|
|
876
|
-
for (let i = 0; i + 2 < idx.length; i += 3) {
|
|
877
|
-
const a = idx[i] * 2, b = idx[i + 1] * 2, c = idx[i + 2] * 2;
|
|
878
|
-
const ax = p[a], ay = p[a + 1];
|
|
879
|
-
const bx = p[b], by = p[b + 1];
|
|
880
|
-
const cx = p[c], cy = p[c + 1];
|
|
881
|
-
const s = (ax - cx) * (y - cy) - (ay - cy) * (x - cx);
|
|
882
|
-
const s1 = (bx - ax) * (y - ay) - (by - ay) * (x - ax);
|
|
883
|
-
const s2 = (cx - bx) * (y - by) - (cy - by) * (x - bx);
|
|
884
|
-
if (s >= 0 && s1 >= 0 && s2 >= 0 || s <= 0 && s1 <= 0 && s2 <= 0) {
|
|
885
|
-
return `drawable:${d.index}`;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
return null;
|
|
1223
|
+
const stageX = (x + 1) / 2;
|
|
1224
|
+
const stageY = (1 - y) / 2;
|
|
1225
|
+
const hit = stage.hitTest(stageX, stageY);
|
|
1226
|
+
if (!hit || hit.actorId !== actor.id) return null;
|
|
1227
|
+
return hit.area;
|
|
890
1228
|
},
|
|
891
1229
|
listParameters() {
|
|
892
|
-
|
|
893
|
-
return activeBackend.listParameters(model);
|
|
1230
|
+
return actor.slot.listParameters();
|
|
894
1231
|
},
|
|
895
1232
|
listMotionGroups() {
|
|
896
|
-
return
|
|
1233
|
+
return actor.slot.listMotionGroups();
|
|
897
1234
|
},
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
const list = model.settings.motionGroups[group];
|
|
901
|
-
const def = list?.[index];
|
|
902
|
-
if (!def) return false;
|
|
903
|
-
let clip = motionCache.get(def.file);
|
|
904
|
-
if (!clip) {
|
|
905
|
-
const json = await activeResolver.fetchJson(def.file);
|
|
906
|
-
clip = parseMotion3(json);
|
|
907
|
-
motionCache.set(def.file, clip);
|
|
908
|
-
}
|
|
909
|
-
const fadeInTime = options2.fadeInTime ?? def.fadeInTime ?? clip.fadeInTime;
|
|
910
|
-
const fadeOutTime = options2.fadeOutTime ?? def.fadeOutTime ?? clip.fadeOutTime;
|
|
911
|
-
return motionPlayer.start(group, index, clip, {
|
|
912
|
-
priority: options2.priority ?? MotionPriority.normal,
|
|
913
|
-
slot: options2.slot,
|
|
914
|
-
queue: options2.queue,
|
|
915
|
-
loop: options2.loop,
|
|
916
|
-
fadeInTime,
|
|
917
|
-
fadeOutTime
|
|
918
|
-
});
|
|
1235
|
+
playMotion(group, index, options) {
|
|
1236
|
+
return actor.slot.playMotion(group, index, options);
|
|
919
1237
|
},
|
|
920
1238
|
stopMotion(opts) {
|
|
921
|
-
|
|
1239
|
+
actor.slot.stopMotion(opts);
|
|
922
1240
|
},
|
|
923
1241
|
listPlayingMotions() {
|
|
924
|
-
return
|
|
1242
|
+
return actor.slot.listPlayingMotions();
|
|
925
1243
|
},
|
|
926
1244
|
async capturePng(opts = {}) {
|
|
927
1245
|
if (!canvas) {
|
|
@@ -929,7 +1247,7 @@ function createLive2D(options = {}) {
|
|
|
929
1247
|
"@doki-land/live2d: mount(canvas) before capturePng"
|
|
930
1248
|
);
|
|
931
1249
|
}
|
|
932
|
-
if (phase === "live"
|
|
1250
|
+
if (phase === "live") {
|
|
933
1251
|
runtime.update(0);
|
|
934
1252
|
}
|
|
935
1253
|
const mime = opts.mimeType ?? "image/png";
|
|
@@ -950,55 +1268,35 @@ function createLive2D(options = {}) {
|
|
|
950
1268
|
});
|
|
951
1269
|
},
|
|
952
1270
|
update(deltaTimeSeconds) {
|
|
953
|
-
if (
|
|
954
|
-
if (phase !== "live") return;
|
|
1271
|
+
if (phase !== "live" && phase !== "ready") return;
|
|
955
1272
|
const t0 = nowMs();
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const drawables = activeBackend.getDrawables(model);
|
|
1273
|
+
stage.update(deltaTimeSeconds);
|
|
1274
|
+
stage.render();
|
|
959
1275
|
const t1 = nowMs();
|
|
960
|
-
|
|
961
|
-
drawPass.draw(drawables, new Float32Array(16));
|
|
962
|
-
renderer.endFrame();
|
|
963
|
-
const t2 = nowMs();
|
|
1276
|
+
const drawables = actor.lastDrawables ?? [];
|
|
964
1277
|
let vertexCount = 0;
|
|
965
1278
|
let indexCount = 0;
|
|
966
1279
|
for (const d of drawables) {
|
|
967
1280
|
vertexCount += d.vertexPositions.length / 2;
|
|
968
1281
|
indexCount += d.indices.length;
|
|
969
1282
|
}
|
|
970
|
-
const frameMs =
|
|
971
|
-
const evaluateMs = t1 - t0;
|
|
972
|
-
const drawMs = t2 - t1;
|
|
1283
|
+
const frameMs = t1 - t0;
|
|
973
1284
|
const fps = deltaTimeSeconds > 0 ? 1 / deltaTimeSeconds : 0;
|
|
974
1285
|
fpsSmooth = fpsSmooth <= 0 ? fps : fpsSmooth * 0.85 + fps * 0.15;
|
|
975
1286
|
events.emit("profile", {
|
|
976
1287
|
fps,
|
|
977
1288
|
fpsSmooth,
|
|
978
1289
|
frameMs,
|
|
979
|
-
evaluateMs,
|
|
980
|
-
drawMs,
|
|
1290
|
+
evaluateMs: frameMs,
|
|
1291
|
+
drawMs: 0,
|
|
981
1292
|
drawableCount: drawables.length,
|
|
982
1293
|
vertexCount,
|
|
983
1294
|
indexCount
|
|
984
1295
|
});
|
|
985
1296
|
},
|
|
986
1297
|
destroy() {
|
|
987
|
-
loadGeneration += 1;
|
|
988
1298
|
generation += 1;
|
|
989
|
-
|
|
990
|
-
motionCache.clear();
|
|
991
|
-
activeResolver = null;
|
|
992
|
-
if (model && activeBackend) {
|
|
993
|
-
activeBackend.destroyModel(model);
|
|
994
|
-
}
|
|
995
|
-
model = null;
|
|
996
|
-
activeBackend = null;
|
|
997
|
-
clearTextures();
|
|
998
|
-
drawPass?.destroy();
|
|
999
|
-
drawPass = null;
|
|
1000
|
-
initPromise = null;
|
|
1001
|
-
renderer.destroy();
|
|
1299
|
+
stage.destroy();
|
|
1002
1300
|
canvas = null;
|
|
1003
1301
|
setPhase("destroyed");
|
|
1004
1302
|
events.clear();
|
|
@@ -1007,39 +1305,339 @@ function createLive2D(options = {}) {
|
|
|
1007
1305
|
return runtime;
|
|
1008
1306
|
}
|
|
1009
1307
|
|
|
1010
|
-
// src/
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1308
|
+
// src/stage/stage.ts
|
|
1309
|
+
import {
|
|
1310
|
+
createMoc2Backend,
|
|
1311
|
+
createMoc3Backend,
|
|
1312
|
+
createRenderer
|
|
1313
|
+
} from "@doki-land/live2d-renderer";
|
|
1314
|
+
var Live2dStageImpl = class {
|
|
1315
|
+
#backends;
|
|
1316
|
+
#renderer;
|
|
1317
|
+
#updateMode;
|
|
1318
|
+
#actors = /* @__PURE__ */ new Map();
|
|
1319
|
+
#definedLayers = [
|
|
1320
|
+
"background",
|
|
1321
|
+
"characters-back",
|
|
1322
|
+
"characters",
|
|
1323
|
+
"characters-front",
|
|
1324
|
+
"effects"
|
|
1325
|
+
];
|
|
1326
|
+
#pointerListeners = /* @__PURE__ */ new Map([
|
|
1327
|
+
["pointerdown", /* @__PURE__ */ new Set()],
|
|
1328
|
+
["pointermove", /* @__PURE__ */ new Set()],
|
|
1329
|
+
["pointerup", /* @__PURE__ */ new Set()]
|
|
1330
|
+
]);
|
|
1331
|
+
#canvas = null;
|
|
1332
|
+
#initPromise = null;
|
|
1333
|
+
#rafId = null;
|
|
1334
|
+
#running = false;
|
|
1335
|
+
#paused = false;
|
|
1336
|
+
#lastFrameMs = 0;
|
|
1337
|
+
#creationCounter = 0;
|
|
1338
|
+
#focusedActorId = null;
|
|
1339
|
+
#lastPointer = null;
|
|
1340
|
+
#pointerTracking = { mode: "focused" };
|
|
1341
|
+
#boundPointerDown;
|
|
1342
|
+
#boundPointerMove;
|
|
1343
|
+
#boundPointerUp;
|
|
1344
|
+
#destroyed = false;
|
|
1345
|
+
constructor(options = {}) {
|
|
1346
|
+
this.#backends = options.backends ?? [
|
|
1347
|
+
createMoc2Backend(),
|
|
1348
|
+
createMoc3Backend()
|
|
1349
|
+
];
|
|
1350
|
+
this.#renderer = options.renderer ?? createRenderer({ prefer: options.prefer });
|
|
1351
|
+
this.#updateMode = options.updateMode ?? "auto";
|
|
1352
|
+
}
|
|
1353
|
+
get actors() {
|
|
1354
|
+
return [...this.#actors.values()];
|
|
1355
|
+
}
|
|
1356
|
+
get pointerTracking() {
|
|
1357
|
+
return this.#pointerTracking;
|
|
1358
|
+
}
|
|
1359
|
+
set pointerTracking(policy) {
|
|
1360
|
+
this.#pointerTracking = policy;
|
|
1361
|
+
}
|
|
1362
|
+
get renderer() {
|
|
1363
|
+
return this.#renderer;
|
|
1364
|
+
}
|
|
1365
|
+
async mount(canvas) {
|
|
1366
|
+
if (this.#destroyed) {
|
|
1367
|
+
throw new Error("@doki-land/live2d: stage destroyed");
|
|
1368
|
+
}
|
|
1369
|
+
this.#canvas = canvas;
|
|
1370
|
+
this.#initPromise = this.#renderer.initialize(canvas);
|
|
1371
|
+
await this.#initPromise;
|
|
1372
|
+
this.#attachPointerListeners(canvas);
|
|
1373
|
+
}
|
|
1374
|
+
createActor(options) {
|
|
1375
|
+
if (this.#destroyed) {
|
|
1376
|
+
throw new Error("@doki-land/live2d: stage destroyed");
|
|
1377
|
+
}
|
|
1378
|
+
const id = options?.id ?? allocateActorId();
|
|
1379
|
+
if (this.#actors.has(id)) {
|
|
1380
|
+
throw new Error(`@doki-land/live2d: duplicate actor id "${id}"`);
|
|
1381
|
+
}
|
|
1382
|
+
const creationIndex = this.#creationCounter++;
|
|
1383
|
+
const actor = new Live2dActorImpl(options, {
|
|
1384
|
+
id,
|
|
1385
|
+
creationIndex,
|
|
1386
|
+
backends: this.#backends,
|
|
1387
|
+
renderer: this.#renderer
|
|
1388
|
+
});
|
|
1389
|
+
this.#actors.set(id, actor);
|
|
1390
|
+
if (!this.#focusedActorId) {
|
|
1391
|
+
this.#focusedActorId = id;
|
|
1392
|
+
}
|
|
1393
|
+
return actor;
|
|
1394
|
+
}
|
|
1395
|
+
removeActor(actorOrId) {
|
|
1396
|
+
const id = typeof actorOrId === "string" ? actorOrId : actorOrId.id;
|
|
1397
|
+
const actor = this.#actors.get(id);
|
|
1398
|
+
if (!actor) return;
|
|
1399
|
+
actor.destroy();
|
|
1400
|
+
this.#actors.delete(id);
|
|
1401
|
+
if (this.#focusedActorId === id) {
|
|
1402
|
+
this.#focusedActorId = this.#actors.keys().next().value ?? null;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
defineLayers(layers) {
|
|
1406
|
+
this.#definedLayers.length = 0;
|
|
1407
|
+
this.#definedLayers.push(...layers);
|
|
1408
|
+
}
|
|
1409
|
+
update(deltaTimeSeconds) {
|
|
1410
|
+
if (this.#destroyed) return;
|
|
1411
|
+
for (const actor of this.#actors.values()) {
|
|
1412
|
+
actor.update(deltaTimeSeconds);
|
|
1413
|
+
}
|
|
1414
|
+
this.#applyPointerTracking();
|
|
1415
|
+
}
|
|
1416
|
+
render() {
|
|
1417
|
+
if (this.#destroyed || !this.#canvas) return;
|
|
1418
|
+
const sorted = [...this.#actors.values()].sort(
|
|
1419
|
+
(a, b) => compareActorsForDraw(a, b, this.#definedLayers)
|
|
1420
|
+
);
|
|
1421
|
+
this.#renderer.beginFrame();
|
|
1422
|
+
for (const actor of sorted) {
|
|
1423
|
+
if (!actor.visible || actor.opacity <= 0) continue;
|
|
1424
|
+
const drawables = actor.lastDrawables;
|
|
1425
|
+
const pass = actor.slot.drawPass;
|
|
1426
|
+
if (!drawables || !pass) continue;
|
|
1427
|
+
const placed = transformDrawablesForStage(
|
|
1428
|
+
drawables,
|
|
1429
|
+
actor.getTransform(),
|
|
1430
|
+
actor.opacity
|
|
1431
|
+
);
|
|
1432
|
+
pass.draw(placed, new Float32Array(16));
|
|
1433
|
+
}
|
|
1434
|
+
this.#renderer.endFrame();
|
|
1435
|
+
}
|
|
1436
|
+
start() {
|
|
1437
|
+
if (this.#updateMode === "manual") {
|
|
1438
|
+
throw new Error(
|
|
1439
|
+
"@doki-land/live2d: start() is not available when updateMode is manual"
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
if (this.#running) return;
|
|
1443
|
+
this.#running = true;
|
|
1444
|
+
this.#paused = false;
|
|
1445
|
+
this.#lastFrameMs = nowMs2();
|
|
1446
|
+
const tick = () => {
|
|
1447
|
+
if (!this.#running) return;
|
|
1448
|
+
if (!this.#paused) {
|
|
1449
|
+
const t = nowMs2();
|
|
1450
|
+
const dt = Math.min(0.1, (t - this.#lastFrameMs) / 1e3);
|
|
1451
|
+
this.#lastFrameMs = t;
|
|
1452
|
+
this.update(dt);
|
|
1453
|
+
this.render();
|
|
1454
|
+
}
|
|
1455
|
+
this.#rafId = requestAnimationFrame(tick);
|
|
1456
|
+
};
|
|
1457
|
+
this.#rafId = requestAnimationFrame(tick);
|
|
1458
|
+
}
|
|
1459
|
+
pause() {
|
|
1460
|
+
this.#paused = true;
|
|
1461
|
+
}
|
|
1462
|
+
resume() {
|
|
1463
|
+
this.#paused = false;
|
|
1464
|
+
this.#lastFrameMs = nowMs2();
|
|
1465
|
+
}
|
|
1466
|
+
stop() {
|
|
1467
|
+
this.#running = false;
|
|
1468
|
+
this.#paused = false;
|
|
1469
|
+
if (this.#rafId !== null) {
|
|
1470
|
+
cancelAnimationFrame(this.#rafId);
|
|
1471
|
+
this.#rafId = null;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
hitTest(stageX, stageY) {
|
|
1475
|
+
const hits = this.hitTestAll(stageX, stageY);
|
|
1476
|
+
return hits[0] ?? null;
|
|
1477
|
+
}
|
|
1478
|
+
hitTestAll(stageX, stageY) {
|
|
1479
|
+
const sorted = [...this.#actors.values()].sort(
|
|
1480
|
+
(a, b) => compareActorsForHit(a, b, this.#definedLayers)
|
|
1481
|
+
);
|
|
1482
|
+
const hits = [];
|
|
1483
|
+
for (const actor of sorted) {
|
|
1484
|
+
const partial = actor.hitTestStage(stageX, stageY);
|
|
1485
|
+
if (!partial) continue;
|
|
1486
|
+
hits.push({ ...partial, actor });
|
|
1487
|
+
}
|
|
1488
|
+
return hits;
|
|
1489
|
+
}
|
|
1490
|
+
addEventListener(type, listener) {
|
|
1491
|
+
this.#pointerListeners.get(type)?.add(listener);
|
|
1492
|
+
}
|
|
1493
|
+
removeEventListener(type, listener) {
|
|
1494
|
+
this.#pointerListeners.get(type)?.delete(listener);
|
|
1495
|
+
}
|
|
1496
|
+
destroy() {
|
|
1497
|
+
if (this.#destroyed) return;
|
|
1498
|
+
this.#destroyed = true;
|
|
1499
|
+
this.stop();
|
|
1500
|
+
this.#detachPointerListeners();
|
|
1501
|
+
for (const actor of this.#actors.values()) {
|
|
1502
|
+
actor.destroy();
|
|
1503
|
+
}
|
|
1504
|
+
this.#actors.clear();
|
|
1505
|
+
this.#renderer.destroy();
|
|
1506
|
+
this.#canvas = null;
|
|
1507
|
+
this.#initPromise = null;
|
|
1508
|
+
for (const set of this.#pointerListeners.values()) {
|
|
1509
|
+
set.clear();
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
#attachPointerListeners(canvas) {
|
|
1513
|
+
this.#detachPointerListeners();
|
|
1514
|
+
this.#boundPointerDown = (e) => this.#onPointer("pointerdown", e);
|
|
1515
|
+
this.#boundPointerMove = (e) => this.#onPointer("pointermove", e);
|
|
1516
|
+
this.#boundPointerUp = (e) => this.#onPointer("pointerup", e);
|
|
1517
|
+
canvas.addEventListener("pointerdown", this.#boundPointerDown);
|
|
1518
|
+
canvas.addEventListener("pointermove", this.#boundPointerMove);
|
|
1519
|
+
canvas.addEventListener("pointerup", this.#boundPointerUp);
|
|
1520
|
+
}
|
|
1521
|
+
#detachPointerListeners() {
|
|
1522
|
+
if (!this.#canvas) return;
|
|
1523
|
+
if (this.#boundPointerDown) {
|
|
1524
|
+
this.#canvas.removeEventListener(
|
|
1525
|
+
"pointerdown",
|
|
1526
|
+
this.#boundPointerDown
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
if (this.#boundPointerMove) {
|
|
1530
|
+
this.#canvas.removeEventListener(
|
|
1531
|
+
"pointermove",
|
|
1532
|
+
this.#boundPointerMove
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
if (this.#boundPointerUp) {
|
|
1536
|
+
this.#canvas.removeEventListener("pointerup", this.#boundPointerUp);
|
|
1537
|
+
}
|
|
1538
|
+
this.#boundPointerDown = void 0;
|
|
1539
|
+
this.#boundPointerMove = void 0;
|
|
1540
|
+
this.#boundPointerUp = void 0;
|
|
1541
|
+
}
|
|
1542
|
+
#onPointer(type, event) {
|
|
1543
|
+
if (!this.#canvas) return;
|
|
1544
|
+
const { stageX, stageY } = clientToStage(
|
|
1545
|
+
event.clientX,
|
|
1546
|
+
event.clientY,
|
|
1547
|
+
this.#canvas
|
|
1548
|
+
);
|
|
1549
|
+
this.#lastPointer = { stageX, stageY };
|
|
1550
|
+
const hit = this.hitTest(stageX, stageY);
|
|
1551
|
+
if (type === "pointerdown" && hit) {
|
|
1552
|
+
this.#focusedActorId = hit.actorId;
|
|
1553
|
+
}
|
|
1554
|
+
const payload = {
|
|
1555
|
+
actor: hit?.actor ?? null,
|
|
1556
|
+
actorId: hit?.actorId ?? null,
|
|
1557
|
+
area: hit?.area ?? null,
|
|
1558
|
+
stageX,
|
|
1559
|
+
stageY,
|
|
1560
|
+
clientX: event.clientX,
|
|
1561
|
+
clientY: event.clientY,
|
|
1562
|
+
hit
|
|
1563
|
+
};
|
|
1564
|
+
for (const listener of this.#pointerListeners.get(type) ?? []) {
|
|
1565
|
+
listener(payload);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
#applyPointerTracking() {
|
|
1569
|
+
if (!this.#canvas || !this.#lastPointer) return;
|
|
1570
|
+
const { stageX, stageY } = this.#lastPointer;
|
|
1571
|
+
const mode = this.#pointerTracking.mode;
|
|
1572
|
+
if (mode === "none") return;
|
|
1573
|
+
if (mode === "all") {
|
|
1574
|
+
for (const actor of this.#actors.values()) {
|
|
1575
|
+
actor.lookAt(stageX, stageY);
|
|
1576
|
+
}
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
if (mode === "custom" && this.#pointerTracking.targetActorId) {
|
|
1580
|
+
const actor = this.#actors.get(this.#pointerTracking.targetActorId);
|
|
1581
|
+
actor?.lookAt(stageX, stageY);
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
if (mode === "hovered") {
|
|
1585
|
+
for (const actor of this.#actors.values()) {
|
|
1586
|
+
if (actor.hitTestStage(stageX, stageY)) {
|
|
1587
|
+
actor.lookAt(stageX, stageY);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
if (mode === "nearest") {
|
|
1593
|
+
let best = null;
|
|
1594
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
1595
|
+
for (const actor of this.#actors.values()) {
|
|
1596
|
+
const t = actor.getTransform();
|
|
1597
|
+
const dx = t.x - stageX;
|
|
1598
|
+
const dy = t.y - stageY;
|
|
1599
|
+
const dist = dx * dx + dy * dy;
|
|
1600
|
+
if (dist < bestDist) {
|
|
1601
|
+
bestDist = dist;
|
|
1602
|
+
best = actor;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
best?.lookAt(stageX, stageY);
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
if (mode === "focused" && this.#focusedActorId) {
|
|
1609
|
+
this.#actors.get(this.#focusedActorId)?.lookAt(stageX, stageY);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
function nowMs2() {
|
|
1614
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
1029
1615
|
}
|
|
1030
|
-
function
|
|
1031
|
-
|
|
1032
|
-
if (n < -1) return -1;
|
|
1033
|
-
return n;
|
|
1616
|
+
function createLive2dStage(options) {
|
|
1617
|
+
return new Live2dStageImpl(options);
|
|
1034
1618
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1619
|
+
|
|
1620
|
+
// src/create-live2d.ts
|
|
1621
|
+
function createLive2D(options = {}) {
|
|
1622
|
+
const backends = options.backends ?? [
|
|
1623
|
+
createMoc2Backend2(),
|
|
1624
|
+
createMoc3Backend2()
|
|
1625
|
+
];
|
|
1626
|
+
const stage = createLive2dStage({
|
|
1627
|
+
backends,
|
|
1628
|
+
renderer: options.renderer ?? createRenderer2({ prefer: options.prefer }),
|
|
1629
|
+
updateMode: options.updateMode ?? "manual"
|
|
1630
|
+
});
|
|
1631
|
+
const actor = stage.createActor({
|
|
1632
|
+
id: allocateActorId("default")
|
|
1633
|
+
});
|
|
1634
|
+
return createSingleActorFacade(stage, actor, backends);
|
|
1038
1635
|
}
|
|
1039
1636
|
|
|
1040
1637
|
// src/index.ts
|
|
1041
1638
|
var LIVE2D_VERSION = "0.0.0";
|
|
1042
1639
|
export {
|
|
1640
|
+
DEFAULT_ACTOR_TRANSFORM2 as DEFAULT_ACTOR_TRANSFORM,
|
|
1043
1641
|
DEFAULT_NPM_CDN,
|
|
1044
1642
|
EventEmitter2 as EventEmitter,
|
|
1045
1643
|
LIVE2D_VERSION,
|
|
@@ -1048,10 +1646,11 @@ export {
|
|
|
1048
1646
|
blendMotionLayers,
|
|
1049
1647
|
createCanvas2DRenderer,
|
|
1050
1648
|
createLive2D,
|
|
1051
|
-
|
|
1052
|
-
|
|
1649
|
+
createLive2dStage,
|
|
1650
|
+
createMoc2Backend3 as createMoc2Backend,
|
|
1651
|
+
createMoc3Backend3 as createMoc3Backend,
|
|
1053
1652
|
createQuadProgram,
|
|
1054
|
-
|
|
1653
|
+
createRenderer3 as createRenderer,
|
|
1055
1654
|
createWebGl2Renderer,
|
|
1056
1655
|
createWebGpuRenderer,
|
|
1057
1656
|
decodeMoc3,
|