@airisu-dev/react-native-live2d-skia 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/Live2dSkia.podspec +159 -0
- package/README.md +258 -2
- package/android/CMakeLists.txt +113 -0
- package/android/build.gradle +223 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +9 -0
- package/android/src/main/java/com/live2dskia/Live2dSkiaPackage.kt +20 -0
- package/app.plugin.js +2 -0
- package/cli/build/index.js +157 -0
- package/cpp/cubism/CubismCompat.hpp +26 -0
- package/cpp/cubism/CubismHelpers.hpp +27 -0
- package/cpp/cubism/CubismRendererStub.cpp +14 -0
- package/cpp/cubism/CubismSetup.cpp +54 -0
- package/cpp/cubism/CubismSetup.hpp +9 -0
- package/cpp/cubism/HeldParameterUpdater.hpp +25 -0
- package/cpp/cubism/Live2DUserModel.cpp +125 -0
- package/cpp/cubism/Live2DUserModel.hpp +57 -0
- package/cpp/cubism/Live2DUserModelAnimation.cpp +107 -0
- package/cpp/drawing/JsiUnwrap.hpp +25 -0
- package/cpp/drawing/MeshCache.cpp +75 -0
- package/cpp/drawing/MeshCache.hpp +45 -0
- package/cpp/drawing/SkiaDrawer.cpp +138 -0
- package/cpp/drawing/SkiaDrawer.hpp +42 -0
- package/cpp/hybrid/HybridLive2DModel.cpp +174 -0
- package/cpp/hybrid/HybridLive2DModel.hpp +64 -0
- package/cpp/hybrid/HybridLive2dSkia.cpp +10 -0
- package/cpp/hybrid/HybridLive2dSkia.hpp +11 -0
- package/ios/Bridge.h +8 -0
- package/nitro.json +20 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/Live2dSkia+autolinking.cmake +82 -0
- package/nitrogen/generated/android/Live2dSkia+autolinking.gradle +27 -0
- package/nitrogen/generated/android/Live2dSkiaOnLoad.cpp +49 -0
- package/nitrogen/generated/android/Live2dSkiaOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/live2dskia/Live2dSkiaOnLoad.kt +35 -0
- package/nitrogen/generated/ios/Live2dSkia+autolinking.rb +62 -0
- package/nitrogen/generated/ios/Live2dSkia-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/Live2dSkia-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/Live2dSkia-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/Live2dSkiaAutolinking.mm +35 -0
- package/nitrogen/generated/ios/Live2dSkiaAutolinking.swift +16 -0
- package/nitrogen/generated/shared/c++/DrawableData.hpp +116 -0
- package/nitrogen/generated/shared/c++/HybridLive2DModelSpec.cpp +39 -0
- package/nitrogen/generated/shared/c++/HybridLive2DModelSpec.hpp +86 -0
- package/nitrogen/generated/shared/c++/HybridLive2dSkiaSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridLive2dSkiaSpec.hpp +66 -0
- package/nitrogen/generated/shared/c++/MotionPriority.hpp +80 -0
- package/package.json +149 -4
- package/plugin/build/index.d.ts +14 -0
- package/plugin/build/index.js +81 -0
- package/src/components/Live2D.tsx +131 -0
- package/src/components/index.ts +2 -0
- package/src/hooks/index.ts +8 -0
- package/src/hooks/useLive2DGesture.ts +140 -0
- package/src/hooks/useLive2DModel.ts +47 -0
- package/src/index.ts +5 -0
- package/src/loader/cubismJson.ts +23 -0
- package/src/loader/index.ts +9 -0
- package/src/loader/loadModel.ts +195 -0
- package/src/loader/modelContext.ts +80 -0
- package/src/loader/modelUrl.ts +112 -0
- package/src/native.ts +4 -0
- package/src/rendering/decodeImage.ts +12 -0
- package/src/rendering/drawer.ts +256 -0
- package/src/rendering/skia.ts +26 -0
- package/src/rendering/skiaTypes.ts +99 -0
- package/src/rendering/ticker/addToTicker.ts +52 -0
- package/src/rendering/ticker/constants.ts +11 -0
- package/src/rendering/ticker/frames.ts +35 -0
- package/src/rendering/ticker/index.ts +5 -0
- package/src/rendering/ticker/loop.ts +115 -0
- package/src/rendering/ticker/state.ts +65 -0
- package/src/rendering/ticker/stats.ts +36 -0
- package/src/rendering/ticker/types.ts +57 -0
- package/src/specs/react-native-live2d-skia.nitro.ts +88 -0
- package/src/utils/clock.ts +10 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// One animation loop for every <Live2D> on the live2d worklet runtime: each
|
|
2
|
+
// frame, every shown model is updated and drawn once, in one pass. Everything
|
|
3
|
+
// here runs on that runtime, and its state lives in the runtime's globalThis.
|
|
4
|
+
export { addToTicker } from './addToTicker'
|
|
5
|
+
export type { Live2DFrameStats, PublishedFrame, TickerModel, TickerView } from './types'
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { scheduleOnRN } from 'react-native-worklets'
|
|
2
|
+
import { now } from '../../utils/clock'
|
|
3
|
+
import type { SkPicture } from '../skia'
|
|
4
|
+
import {
|
|
5
|
+
CAP_TOLERANCE_MS,
|
|
6
|
+
DEFAULT_FRAME_MS,
|
|
7
|
+
MAX_DELTA_SECONDS,
|
|
8
|
+
MAX_FRAME_MS,
|
|
9
|
+
MAX_FRAMES_AHEAD,
|
|
10
|
+
MIN_FRAME_MS,
|
|
11
|
+
STATS_INTERVAL_MS,
|
|
12
|
+
} from './constants'
|
|
13
|
+
import { freeFrames, oldestShown, releaseView, stopEntry } from './frames'
|
|
14
|
+
import { type Entry, reportError, tickerState } from './state'
|
|
15
|
+
import { reportEntry } from './stats'
|
|
16
|
+
|
|
17
|
+
function drawEntry(entry: Entry, frameStart: number, wantStats: boolean) {
|
|
18
|
+
'worklet'
|
|
19
|
+
const dt = entry.last === null ? 0 : Math.min((frameStart - entry.last) / 1000, MAX_DELTA_SECONDS)
|
|
20
|
+
entry.last = frameStart
|
|
21
|
+
// Timed from here: frameStart is when the whole loop frame began.
|
|
22
|
+
const start = wantStats ? now() : 0
|
|
23
|
+
entry.native.update(dt)
|
|
24
|
+
const updated = wantStats ? now() : 0
|
|
25
|
+
const stats = wantStats ? entry.drawStats : undefined
|
|
26
|
+
let copied = updated
|
|
27
|
+
let picture: SkPicture
|
|
28
|
+
if (entry.nativeDraw) {
|
|
29
|
+
picture = entry.nativeDraw(stats)
|
|
30
|
+
} else {
|
|
31
|
+
const drawables = entry.native.getDrawables()
|
|
32
|
+
copied = wantStats ? now() : 0
|
|
33
|
+
picture = entry.jsDraw!(drawables, stats)
|
|
34
|
+
}
|
|
35
|
+
entry.frame++
|
|
36
|
+
for (const { view } of entry.views) {
|
|
37
|
+
view.target.setBlocking({ frame: entry.frame, picture })
|
|
38
|
+
}
|
|
39
|
+
entry.frames.push({ frame: entry.frame, picture })
|
|
40
|
+
if (wantStats) {
|
|
41
|
+
const end = now()
|
|
42
|
+
entry.statsFrames++
|
|
43
|
+
entry.updateMs += updated - start
|
|
44
|
+
entry.drawablesMs += copied - updated
|
|
45
|
+
entry.drawMs += end - copied
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Updates and draws every model once. Returns the delay until the next frame, or null to stop. */
|
|
50
|
+
export function runFrame(): number | null {
|
|
51
|
+
'worklet'
|
|
52
|
+
const ticker = tickerState()
|
|
53
|
+
const frameStart = now()
|
|
54
|
+
let displayMs = 0
|
|
55
|
+
for (const [id, entry] of ticker.entries) {
|
|
56
|
+
const stopped = entry.views.filter((s) => !s.view.running.getDirty())
|
|
57
|
+
if (stopped.length > 0) {
|
|
58
|
+
for (const slot of stopped) releaseView(entry, slot)
|
|
59
|
+
entry.views = entry.views.filter((s) => !stopped.includes(s))
|
|
60
|
+
}
|
|
61
|
+
if (entry.views.length === 0) {
|
|
62
|
+
stopEntry(ticker, id, entry)
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
freeFrames(entry)
|
|
66
|
+
for (const { view } of entry.views) {
|
|
67
|
+
const ms = view.displayMs.getDirty()
|
|
68
|
+
if (ms > 0 && (displayMs === 0 || ms < displayMs)) displayMs = ms
|
|
69
|
+
}
|
|
70
|
+
// The fastest view sets the model's frame rate.
|
|
71
|
+
const capMs = Math.min(...entry.views.map((s) => s.view.capMs))
|
|
72
|
+
if (entry.last !== null && frameStart - entry.last < capMs - CAP_TOLERANCE_MS) {
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
// Wait for a UI thread that's behind, rather than draw frames it won't show.
|
|
76
|
+
if (entry.frame - Math.min(...entry.views.map(oldestShown)) >= MAX_FRAMES_AHEAD) {
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
const wantStats = entry.views.some((s) => s.view.report !== null)
|
|
80
|
+
try {
|
|
81
|
+
drawEntry(entry, frameStart, wantStats)
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// An uncaught error on this runtime aborts the app: drop the model instead.
|
|
84
|
+
for (const slot of entry.views) {
|
|
85
|
+
slot.view.running.setBlocking(false)
|
|
86
|
+
releaseView(entry, slot)
|
|
87
|
+
}
|
|
88
|
+
stopEntry(ticker, id, entry)
|
|
89
|
+
scheduleOnRN(reportError, String((e as Error)?.stack ?? e))
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const end = now()
|
|
94
|
+
ticker.loopWorkMs += end - frameStart
|
|
95
|
+
ticker.loopFrames++
|
|
96
|
+
const interval = Math.min(MAX_FRAME_MS, Math.max(MIN_FRAME_MS, displayMs || DEFAULT_FRAME_MS))
|
|
97
|
+
for (const entry of ticker.entries.values()) {
|
|
98
|
+
const capMs = Math.min(...entry.views.map((s) => s.view.capMs))
|
|
99
|
+
reportEntry(ticker, entry, end, Math.max(interval, capMs))
|
|
100
|
+
}
|
|
101
|
+
if (end - ticker.loopStatsStart >= STATS_INTERVAL_MS) {
|
|
102
|
+
ticker.loopStatsStart = end
|
|
103
|
+
ticker.loopWorkMs = 0
|
|
104
|
+
ticker.loopFrames = 0
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (ticker.entries.size === 0) {
|
|
108
|
+
ticker.running = false
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
ticker.nextFrameAt += interval
|
|
112
|
+
// More than a frame behind (a stall, or the first frame): start over from now.
|
|
113
|
+
if (ticker.nextFrameAt < end - interval) ticker.nextFrameAt = end + interval
|
|
114
|
+
return Math.max(0, ticker.nextFrameAt - end)
|
|
115
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Live2DModel as NativeModel } from '../../specs/react-native-live2d-skia.nitro'
|
|
2
|
+
import type { createDrawer, DrawStats } from '../drawer'
|
|
3
|
+
import type { SkPicture } from '../skia'
|
|
4
|
+
import type { TickerView } from './types'
|
|
5
|
+
|
|
6
|
+
export interface Slot {
|
|
7
|
+
view: TickerView
|
|
8
|
+
/** The entry's newest frame when the view joined: the oldest it can show before acking one. */
|
|
9
|
+
since: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Frame {
|
|
13
|
+
frame: number
|
|
14
|
+
picture: SkPicture
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Entry {
|
|
18
|
+
native: NativeModel
|
|
19
|
+
/** Draws the model's current state: in C++ when built with Skia, else in JS. */
|
|
20
|
+
nativeDraw: ((stats?: DrawStats) => SkPicture) | null
|
|
21
|
+
jsDraw: ReturnType<typeof createDrawer> | null
|
|
22
|
+
views: Slot[]
|
|
23
|
+
/**
|
|
24
|
+
* Frames a view may still show, oldest first. Freed explicitly once no view
|
|
25
|
+
* can show them: the GC doesn't see their native memory.
|
|
26
|
+
*/
|
|
27
|
+
frames: Frame[]
|
|
28
|
+
frame: number
|
|
29
|
+
last: number | null
|
|
30
|
+
statsStart: number
|
|
31
|
+
statsFrames: number
|
|
32
|
+
updateMs: number
|
|
33
|
+
drawablesMs: number
|
|
34
|
+
drawMs: number
|
|
35
|
+
drawStats: DrawStats
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Ticker {
|
|
39
|
+
entries: Map<number, Entry>
|
|
40
|
+
running: boolean
|
|
41
|
+
/** When the next frame is due (absolute, so late timer wake-ups don't add up). */
|
|
42
|
+
nextFrameAt: number
|
|
43
|
+
loopStatsStart: number
|
|
44
|
+
loopWorkMs: number
|
|
45
|
+
loopFrames: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const reportError = (message: string) =>
|
|
49
|
+
console.error('[react-native-live2d-skia] animation stopped:', message)
|
|
50
|
+
|
|
51
|
+
export function tickerState(): Ticker {
|
|
52
|
+
'worklet'
|
|
53
|
+
const global = globalThis as unknown as { __live2dSkiaTicker?: Ticker }
|
|
54
|
+
if (!global.__live2dSkiaTicker) {
|
|
55
|
+
global.__live2dSkiaTicker = {
|
|
56
|
+
entries: new Map(),
|
|
57
|
+
running: false,
|
|
58
|
+
nextFrameAt: 0,
|
|
59
|
+
loopStatsStart: 0,
|
|
60
|
+
loopWorkMs: 0,
|
|
61
|
+
loopFrames: 0,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return global.__live2dSkiaTicker
|
|
65
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { scheduleOnRN } from 'react-native-worklets'
|
|
2
|
+
import { STATS_INTERVAL_MS } from './constants'
|
|
3
|
+
import type { Entry, Ticker } from './state'
|
|
4
|
+
import type { Live2DFrameStats } from './types'
|
|
5
|
+
|
|
6
|
+
export function reportEntry(ticker: Ticker, entry: Entry, at: number, frameMs: number) {
|
|
7
|
+
'worklet'
|
|
8
|
+
const elapsed = at - entry.statsStart
|
|
9
|
+
if (elapsed < STATS_INTERVAL_MS || entry.statsFrames === 0) return
|
|
10
|
+
const n = entry.statsFrames
|
|
11
|
+
const s = entry.drawStats
|
|
12
|
+
const stats: Live2DFrameStats = {
|
|
13
|
+
fps: (n * 1000) / elapsed,
|
|
14
|
+
targetFps: 1000 / frameMs,
|
|
15
|
+
workMs: (entry.updateMs + entry.drawablesMs + entry.drawMs) / n,
|
|
16
|
+
updateMs: entry.updateMs / n,
|
|
17
|
+
drawablesMs: entry.drawablesMs / n,
|
|
18
|
+
drawMs: entry.drawMs / n,
|
|
19
|
+
meshMs: s.meshMs / n,
|
|
20
|
+
meshesBuilt: s.meshesBuilt / n,
|
|
21
|
+
layers: s.layers / n,
|
|
22
|
+
models: ticker.entries.size,
|
|
23
|
+
loopWorkMs: ticker.loopFrames > 0 ? ticker.loopWorkMs / ticker.loopFrames : 0,
|
|
24
|
+
}
|
|
25
|
+
for (const { view } of entry.views) {
|
|
26
|
+
if (view.report) scheduleOnRN(view.report, stats)
|
|
27
|
+
}
|
|
28
|
+
entry.statsStart = at
|
|
29
|
+
entry.statsFrames = 0
|
|
30
|
+
entry.updateMs = 0
|
|
31
|
+
entry.drawablesMs = 0
|
|
32
|
+
entry.drawMs = 0
|
|
33
|
+
s.meshMs = 0
|
|
34
|
+
s.meshesBuilt = 0
|
|
35
|
+
s.layers = 0
|
|
36
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Synchronizable } from 'react-native-worklets'
|
|
2
|
+
import type { Live2DModel as NativeModel } from '../../specs/react-native-live2d-skia.nitro'
|
|
3
|
+
import type { CanvasInfo } from '../drawer'
|
|
4
|
+
import type { SkImage, SkPicture, SkShader } from '../skia'
|
|
5
|
+
|
|
6
|
+
/** Averages per frame over the last second, in milliseconds unless noted. */
|
|
7
|
+
export interface Live2DFrameStats {
|
|
8
|
+
fps: number
|
|
9
|
+
/** The frame rate the loop aims for: the display's refresh rate, or `maxFps`. */
|
|
10
|
+
targetFps: number
|
|
11
|
+
/** All work for this model's frame on the animation thread. */
|
|
12
|
+
workMs: number
|
|
13
|
+
/** Motions, physics and so on in C++ (`update`). */
|
|
14
|
+
updateMs: number
|
|
15
|
+
/** Copying vertex data out of C++ (`getDrawables`). 0 when drawn in C++. */
|
|
16
|
+
drawablesMs: number
|
|
17
|
+
/** Recording the picture, including `meshMs`. */
|
|
18
|
+
drawMs: number
|
|
19
|
+
/** Building Skia vertices for drawables that moved. */
|
|
20
|
+
meshMs: number
|
|
21
|
+
/** Meshes rebuilt per frame (a count). */
|
|
22
|
+
meshesBuilt: number
|
|
23
|
+
/**
|
|
24
|
+
* Mask layers per frame (a count). Their cost is in the UI thread's Skia
|
|
25
|
+
* draw, which these stats don't cover.
|
|
26
|
+
*/
|
|
27
|
+
layers: number
|
|
28
|
+
/** Models animated by the loop, this one included (a count). */
|
|
29
|
+
models: number
|
|
30
|
+
/** All work for a frame of the loop, every model included. */
|
|
31
|
+
loopWorkMs: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A newly drawn frame, for the UI thread to pick up. */
|
|
35
|
+
export type PublishedFrame = { frame: number; picture: SkPicture } | null
|
|
36
|
+
|
|
37
|
+
export interface TickerView {
|
|
38
|
+
target: Synchronizable<PublishedFrame>
|
|
39
|
+
/** The frame the UI thread shows (-1 before the first). Frames are freed once every view is past them. */
|
|
40
|
+
shown: Synchronizable<number>
|
|
41
|
+
/** Set to false to stop showing frames here. */
|
|
42
|
+
running: Synchronizable<boolean>
|
|
43
|
+
/** The display's frame interval, measured on the UI thread; 0 until known. */
|
|
44
|
+
displayMs: Synchronizable<number>
|
|
45
|
+
/** Shortest frame time for this view (`maxFps`); 0 for no cap. */
|
|
46
|
+
capMs: number
|
|
47
|
+
/** Gets frame stats about once a second; null if not wanted. */
|
|
48
|
+
report: ((stats: Live2DFrameStats) => void) | null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TickerModel {
|
|
52
|
+
id: number
|
|
53
|
+
native: NativeModel
|
|
54
|
+
textures: SkImage[]
|
|
55
|
+
shaders: SkShader[]
|
|
56
|
+
canvas: CanvasInfo
|
|
57
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { HybridObject } from 'react-native-nitro-modules'
|
|
2
|
+
|
|
3
|
+
export interface DrawableData {
|
|
4
|
+
/** Index into the model's drawables; `masks` refers to these. */
|
|
5
|
+
index: number
|
|
6
|
+
/** Cubism's visibility flag. Hidden drawables can still be used as masks. */
|
|
7
|
+
visible: boolean
|
|
8
|
+
textureIndex: number
|
|
9
|
+
opacity: number
|
|
10
|
+
/** Cubism constant flags (`csmBlendAdditive`, `csmIsInvertedMask`, ...). */
|
|
11
|
+
constantFlags: number
|
|
12
|
+
/** Indices of the drawables that clip this one. Empty if unmasked. */
|
|
13
|
+
masks: number[]
|
|
14
|
+
/** float32 x/y pairs in model units, y-up. */
|
|
15
|
+
positions: ArrayBuffer
|
|
16
|
+
/** float32 u/v pairs in 0..1, v-up. */
|
|
17
|
+
uvs: ArrayBuffer
|
|
18
|
+
/** uint16 triangle indices. */
|
|
19
|
+
indices: ArrayBuffer
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Like the Cubism SDK samples: a higher priority interrupts a lower one. */
|
|
23
|
+
export type MotionPriority = 'idle' | 'normal' | 'force'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Thread-safe: the model can be updated on one thread while another starts
|
|
27
|
+
* motions or moves the focus.
|
|
28
|
+
*/
|
|
29
|
+
export interface Live2DModel extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
|
|
30
|
+
readonly canvasWidth: number
|
|
31
|
+
readonly canvasHeight: number
|
|
32
|
+
readonly originX: number
|
|
33
|
+
readonly originY: number
|
|
34
|
+
readonly pixelsPerUnit: number
|
|
35
|
+
readonly drawableCount: number
|
|
36
|
+
/** All drawables sorted by render order (back to front), including hidden ones. */
|
|
37
|
+
getDrawables(): DrawableData[]
|
|
38
|
+
loadPhysics(json: string): void
|
|
39
|
+
loadPose(json: string): void
|
|
40
|
+
/**
|
|
41
|
+
* Loads one `motion3.json` as entry `index` of motion `group`.
|
|
42
|
+
* Fade times come from `model3.json` when it defines them.
|
|
43
|
+
*/
|
|
44
|
+
loadMotion(group: string, index: number, json: string): void
|
|
45
|
+
/**
|
|
46
|
+
* Starts a loaded motion, or a random one of `group` without `index`.
|
|
47
|
+
* Returns false if it isn't loaded or a higher priority motion is playing.
|
|
48
|
+
*/
|
|
49
|
+
startMotion(group: string, index?: number, priority?: MotionPriority): boolean
|
|
50
|
+
loadExpression(name: string, json: string): void
|
|
51
|
+
/**
|
|
52
|
+
* Fades to a loaded expression, or a random one without `name`.
|
|
53
|
+
* Returns false if it isn't loaded.
|
|
54
|
+
*/
|
|
55
|
+
setExpression(name?: string): boolean
|
|
56
|
+
clearExpression(): void
|
|
57
|
+
/**
|
|
58
|
+
* Holds a parameter at `value` until `clearParameter`. Applied after motions,
|
|
59
|
+
* expressions, eye blink, look and breath, before physics and pose.
|
|
60
|
+
*/
|
|
61
|
+
setParameter(id: string, value: number): void
|
|
62
|
+
clearParameter(id: string): void
|
|
63
|
+
/**
|
|
64
|
+
* Makes the model look at a point, easing towards it: -1..1 on each axis,
|
|
65
|
+
* y-up, (0, 0) is straight ahead.
|
|
66
|
+
*/
|
|
67
|
+
focus(x: number, y: number): void
|
|
68
|
+
/**
|
|
69
|
+
* Names of the `model3.json` hit areas containing a point in model units
|
|
70
|
+
* (y-up, see `originX`/`pixelsPerUnit`), in the order they're listed.
|
|
71
|
+
*/
|
|
72
|
+
hitTest(x: number, y: number): string[]
|
|
73
|
+
/**
|
|
74
|
+
* Advances the model by `deltaSeconds`: motions (a random `Idle` motion
|
|
75
|
+
* whenever nothing plays), expression, eye blink, look, breath, held
|
|
76
|
+
* parameters, physics and pose.
|
|
77
|
+
* Pass 0 to just apply the current state.
|
|
78
|
+
*/
|
|
79
|
+
update(deltaSeconds: number): void
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface Live2dSkia extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
|
|
83
|
+
/**
|
|
84
|
+
* Loads a `.moc3` file with its `model3.json` (used for eye blink and lip
|
|
85
|
+
* sync parameters and motion fade times). Throws if the moc is invalid.
|
|
86
|
+
*/
|
|
87
|
+
loadModel(moc: ArrayBuffer, settingsJson: string): Live2DModel
|
|
88
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// performance.now() exists on the JS thread and on worklet runtimes, but this
|
|
2
|
+
// package's TypeScript lib (esnext, no DOM) doesn't declare it.
|
|
3
|
+
type WithPerformance = { performance: { now(): number } }
|
|
4
|
+
|
|
5
|
+
export function now(): number {
|
|
6
|
+
'worklet'
|
|
7
|
+
// Read globalThis here, not from a module constant: a worklet copies what it
|
|
8
|
+
// captures to its runtime, and the global object can't be copied (it's cyclic).
|
|
9
|
+
return (globalThis as unknown as WithPerformance).performance.now()
|
|
10
|
+
}
|