@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,223 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath "com.android.tools.build:gradle:8.8.0"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
def reactNativeArchitectures() {
|
|
13
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
14
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def isNewArchitectureEnabled() {
|
|
18
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
apply plugin: "com.android.library"
|
|
22
|
+
apply plugin: 'org.jetbrains.kotlin.android'
|
|
23
|
+
apply from: '../nitrogen/generated/android/Live2dSkia+autolinking.gradle'
|
|
24
|
+
apply from: "./fix-prefab.gradle"
|
|
25
|
+
|
|
26
|
+
if (isNewArchitectureEnabled()) {
|
|
27
|
+
apply plugin: "com.facebook.react"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Cubism SDK for Native: proprietary, so each app downloads it from Live2D and
|
|
31
|
+
// sets its folder (app.plugin.js writes live2dSkia.cubismSdkPath, relative to android/).
|
|
32
|
+
def findCubismSdk() {
|
|
33
|
+
def configured = System.getenv("LIVE2D_SKIA_CUBISM_SDK") ?: rootProject.findProperty("live2dSkia.cubismSdkPath")
|
|
34
|
+
if (!configured) {
|
|
35
|
+
throw new GradleException("[react-native-live2d-skia] Cubism SDK for Native isn't configured. Add the plugin to app.json: " +
|
|
36
|
+
"[\"@airisu-dev/react-native-live2d-skia\", { \"cubismSdkPath\": \"./vendor/CubismSdkForNative\" }], download the SDK with `@airisu-dev/react-native-live2d-skia fetch-sdk --accept-license` (through npx, bunx, yarn or pnpm), then prebuild.")
|
|
37
|
+
}
|
|
38
|
+
def dir = rootProject.file(configured)
|
|
39
|
+
def info = new File(dir, "cubism-info.yml")
|
|
40
|
+
if (!info.exists()) {
|
|
41
|
+
throw new GradleException("[react-native-live2d-skia] Cubism SDK for Native not found at ${dir} (no cubism-info.yml). " +
|
|
42
|
+
"Download it with `@airisu-dev/react-native-live2d-skia fetch-sdk --accept-license` (through npx, bunx, yarn or pnpm).")
|
|
43
|
+
}
|
|
44
|
+
def matcher = info.text =~ /(?m)^version:\s*(\S+)/
|
|
45
|
+
def version = matcher.find() ? matcher.group(1) : "unknown"
|
|
46
|
+
checkCubismSdkVersion(version, dir)
|
|
47
|
+
return [dir: dir.absolutePath.replace("\\", "/"), version: version]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// A Cubism SDK version as a number: major * 10000 + release * 100 + patch, so
|
|
51
|
+
// 5-r.4.1 is 50401 (a -beta suffix is ignored). Null if it isn't one.
|
|
52
|
+
def cubismSdkVersionNumber(String version) {
|
|
53
|
+
def m = version =~ /^(\d+)-r\.(\d+)(?:\.(\d+))?/
|
|
54
|
+
return m.find() ? m.group(1).toInteger() * 10000 + m.group(2).toInteger() * 100 + (m.group(3) ?: "0").toInteger() : null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Listed versions (cubismSdk.versions in package.json) are supported. A newer
|
|
58
|
+
// release of a listed major only warns; anything else fails.
|
|
59
|
+
def checkCubismSdkVersion(String version, File dir) {
|
|
60
|
+
def listed = new groovy.json.JsonSlurper().parse(file("../package.json")).cubismSdk.versions.keySet()
|
|
61
|
+
if (listed.contains(version)) return
|
|
62
|
+
def number = cubismSdkVersionNumber(version)
|
|
63
|
+
def newestOfMajor = number == null ? null : listed.collect { cubismSdkVersionNumber(it) }
|
|
64
|
+
.findAll { it.intdiv(10000) == number.intdiv(10000) }.max()
|
|
65
|
+
if (newestOfMajor == null || number < newestOfMajor) {
|
|
66
|
+
throw new GradleException("[react-native-live2d-skia] Cubism SDK for Native ${version} at ${dir} isn't supported: use ${listed.join(', ')}.")
|
|
67
|
+
}
|
|
68
|
+
logger.warn("[react-native-live2d-skia] Cubism SDK for Native ${version} found, tested with ${listed.join(', ')}.")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The folder of a node package, as `from` resolves it. Null if it isn't installed.
|
|
72
|
+
def resolveNodePackage(String name, File from) {
|
|
73
|
+
def process = ["node", "-p",
|
|
74
|
+
"require('path').dirname(require.resolve(process.argv[1] + '/package.json', { paths: [process.argv[2]] }))",
|
|
75
|
+
name, from.absolutePath].execute()
|
|
76
|
+
def out = process.text.trim()
|
|
77
|
+
return process.waitFor() == 0 && out ? new File(out) : null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Drawing in C++ onto Skia canvases (cpp/drawing/SkiaDrawer.cpp) needs the headers,
|
|
81
|
+
// defines and prebuilt libskia.a of the Skia the app uses: react-native-skia
|
|
82
|
+
// (v3, Graphite) or @shopify/react-native-skia (v2, Ganesh), as app.plugin.js
|
|
83
|
+
// found in the app's package.json (live2dSkia.skiaPackage). Resolved from the
|
|
84
|
+
// app, since a monorepo can have both installed. No arguments: drawn in JS.
|
|
85
|
+
def skiaCmakeArguments() {
|
|
86
|
+
def name = rootProject.findProperty("live2dSkia.skiaPackage")
|
|
87
|
+
if (!name) return []
|
|
88
|
+
def appDir = rootProject.projectDir.parentFile
|
|
89
|
+
def skiaDir = resolveNodePackage(name, appDir)
|
|
90
|
+
def graphite = name == "react-native-skia"
|
|
91
|
+
def libsDir = skiaDir ? resolveNodePackage(graphite ? "react-native-skia-graphite-android" : "react-native-skia-android", skiaDir) : null
|
|
92
|
+
def reactNativeDir = resolveNodePackage("react-native", appDir)
|
|
93
|
+
if (!skiaDir || !libsDir || !reactNativeDir) {
|
|
94
|
+
logger.warn("[react-native-live2d-skia] ${name} or its Android binaries not found from ${appDir}: drawing in JS.")
|
|
95
|
+
return []
|
|
96
|
+
}
|
|
97
|
+
return ["-DRN_SKIA_DIR=${skiaDir.absolutePath.replace("\\", "/")}",
|
|
98
|
+
"-DRN_SKIA_LIBS=${new File(libsDir, "libs").absolutePath.replace("\\", "/")}",
|
|
99
|
+
"-DRN_SKIA_GRAPHITE=${graphite ? "ON" : "OFF"}",
|
|
100
|
+
"-DREACT_NATIVE_DIR=${reactNativeDir.absolutePath.replace("\\", "/")}"]
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
def getExtOrDefault(name) {
|
|
104
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["Live2dSkia_" + name]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
def getExtOrIntegerDefault(name) {
|
|
108
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Live2dSkia_" + name]).toInteger()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
android {
|
|
112
|
+
namespace "com.live2dskia"
|
|
113
|
+
|
|
114
|
+
ndkVersion getExtOrDefault("ndkVersion")
|
|
115
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
116
|
+
|
|
117
|
+
defaultConfig {
|
|
118
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
119
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
120
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
121
|
+
|
|
122
|
+
externalNativeBuild {
|
|
123
|
+
cmake {
|
|
124
|
+
cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
|
|
125
|
+
def cubismSdk = findCubismSdk()
|
|
126
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON",
|
|
127
|
+
"-DCUBISM_SDK_DIR=${cubismSdk.dir}",
|
|
128
|
+
"-DCUBISM_SDK_VERSION_NUMBER=${cubismSdkVersionNumber(cubismSdk.version)}", *skiaCmakeArguments()
|
|
129
|
+
abiFilters (*reactNativeArchitectures())
|
|
130
|
+
|
|
131
|
+
buildTypes {
|
|
132
|
+
debug {
|
|
133
|
+
cppFlags "-O1 -g"
|
|
134
|
+
}
|
|
135
|
+
release {
|
|
136
|
+
cppFlags "-O2"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
externalNativeBuild {
|
|
144
|
+
cmake {
|
|
145
|
+
path "CMakeLists.txt"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
packagingOptions {
|
|
150
|
+
excludes = [
|
|
151
|
+
"META-INF",
|
|
152
|
+
"META-INF/**",
|
|
153
|
+
"**/libc++_shared.so",
|
|
154
|
+
"**/libfbjni.so",
|
|
155
|
+
"**/libjsi.so",
|
|
156
|
+
"**/libfolly_json.so",
|
|
157
|
+
"**/libfolly_runtime.so",
|
|
158
|
+
"**/libglog.so",
|
|
159
|
+
"**/libhermes.so",
|
|
160
|
+
"**/libhermes-executor-debug.so",
|
|
161
|
+
"**/libhermes_executor.so",
|
|
162
|
+
"**/libreactnative.so",
|
|
163
|
+
"**/libreactnativejni.so",
|
|
164
|
+
"**/libturbomodulejsijni.so",
|
|
165
|
+
"**/libreact_nativemodule_core.so",
|
|
166
|
+
"**/libjscexecutor.so"
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
buildFeatures {
|
|
171
|
+
buildConfig true
|
|
172
|
+
prefab true
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
buildTypes {
|
|
176
|
+
release {
|
|
177
|
+
minifyEnabled false
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
lintOptions {
|
|
182
|
+
disable "GradleCompatible"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
compileOptions {
|
|
186
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
187
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
sourceSets {
|
|
191
|
+
main {
|
|
192
|
+
if (isNewArchitectureEnabled()) {
|
|
193
|
+
java.srcDirs += [
|
|
194
|
+
// React Codegen files
|
|
195
|
+
"${project.buildDir}/generated/source/codegen/java"
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
repositories {
|
|
203
|
+
mavenCentral()
|
|
204
|
+
google()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
dependencies {
|
|
209
|
+
// For < 0.71, this will be from the local maven repo
|
|
210
|
+
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
|
211
|
+
//noinspection GradleDynamicVersion
|
|
212
|
+
implementation "com.facebook.react:react-native:+"
|
|
213
|
+
|
|
214
|
+
implementation project(":react-native-nitro-modules")
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (isNewArchitectureEnabled()) {
|
|
218
|
+
react {
|
|
219
|
+
jsRootDir = file("../src/")
|
|
220
|
+
libraryName = "Live2dSkia"
|
|
221
|
+
codegenJavaPackageName = "com.live2dskia"
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
tasks.configureEach { task ->
|
|
2
|
+
// Make sure that we generate our prefab publication file only after having built the native library
|
|
3
|
+
// so that not a header publication file, but a full configuration publication will be generated, which
|
|
4
|
+
// will include the .so file
|
|
5
|
+
|
|
6
|
+
def prefabConfigurePattern = ~/^prefab(.+)ConfigurePackage$/
|
|
7
|
+
def matcher = task.name =~ prefabConfigurePattern
|
|
8
|
+
if (matcher.matches()) {
|
|
9
|
+
def variantName = matcher[0][1]
|
|
10
|
+
task.outputs.upToDateWhen { false }
|
|
11
|
+
task.dependsOn("externalNativeBuild${variantName}")
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
afterEvaluate {
|
|
16
|
+
def abis = reactNativeArchitectures()
|
|
17
|
+
rootProject.allprojects.each { proj ->
|
|
18
|
+
if (proj === rootProject) return
|
|
19
|
+
|
|
20
|
+
def dependsOnThisLib = proj.configurations.findAll { it.canBeResolved }.any { config ->
|
|
21
|
+
config.dependencies.any { dep ->
|
|
22
|
+
dep.group == project.group && dep.name == project.name
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!dependsOnThisLib && proj != project) return
|
|
26
|
+
|
|
27
|
+
if (!proj.plugins.hasPlugin('com.android.application') && !proj.plugins.hasPlugin('com.android.library')) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def variants = proj.android.hasProperty('applicationVariants') ? proj.android.applicationVariants : proj.android.libraryVariants
|
|
32
|
+
// Touch the prefab_config.json files to ensure that in ExternalNativeJsonGenerator.kt we will re-trigger the prefab CLI to
|
|
33
|
+
// generate a libnameConfig.cmake file that will contain our native library (.so).
|
|
34
|
+
// See this condition: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/ExternalNativeJsonGenerator.kt;l=207-219?q=createPrefabBuildSystemGlue
|
|
35
|
+
variants.all { variant ->
|
|
36
|
+
def variantName = variant.name
|
|
37
|
+
abis.each { abi ->
|
|
38
|
+
def searchDir = new File(proj.projectDir, ".cxx/${variantName}")
|
|
39
|
+
if (!searchDir.exists()) return
|
|
40
|
+
def matches = []
|
|
41
|
+
searchDir.eachDir { randomDir ->
|
|
42
|
+
def prefabFile = new File(randomDir, "${abi}/prefab_config.json")
|
|
43
|
+
if (prefabFile.exists()) matches << prefabFile
|
|
44
|
+
}
|
|
45
|
+
matches.each { prefabConfig ->
|
|
46
|
+
prefabConfig.setLastModified(System.currentTimeMillis())
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.live2dskia;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.NativeModule;
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
5
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider;
|
|
6
|
+
import com.facebook.react.BaseReactPackage;
|
|
7
|
+
import com.margelo.nitro.live2dskia.Live2dSkiaOnLoad;
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
public class Live2dSkiaPackage : BaseReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
|
|
12
|
+
|
|
13
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { emptyMap() }
|
|
14
|
+
|
|
15
|
+
companion object {
|
|
16
|
+
init {
|
|
17
|
+
Live2dSkiaOnLoad.initializeNative();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.sha256 = void 0;
|
|
8
|
+
exports.parseArgs = parseArgs;
|
|
9
|
+
// @airisu-dev/react-native-live2d-skia fetch-sdk --accept-license [--sdk-version <version>] [--out <dir>] [--keep-zip]
|
|
10
|
+
//
|
|
11
|
+
// Downloads Cubism SDK for Native into <dir> (default ./vendor/CubismSdkForNative), for the
|
|
12
|
+
// config plugin's `cubismSdkPath`. The Core is proprietary and not shipped: downloading means accepting Live2D's license.
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
// The SDK versions the package supports. Two levels up from both cli/src and cli/build.
|
|
18
|
+
const { cubismSdk } = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '..', '..', 'package.json'), 'utf8'));
|
|
19
|
+
const VERSIONS = Object.keys(cubismSdk.versions);
|
|
20
|
+
const sdkUrl = (version) => `https://cubism.live2d.com/sdk-native/bin/CubismSdkForNative-${version}.zip`;
|
|
21
|
+
const DEFAULT_OUT = 'vendor/CubismSdkForNative';
|
|
22
|
+
const LICENSES = [
|
|
23
|
+
'Live2D Proprietary Software License (Cubism Core): https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html',
|
|
24
|
+
'Live2D Open Software License (Cubism Native Framework): https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html',
|
|
25
|
+
'Cubism SDK Release License (required to publish apps, for businesses above 10M JPY a year): https://www.live2d.com/en/sdk/license/',
|
|
26
|
+
];
|
|
27
|
+
function usage() {
|
|
28
|
+
console.log(`Usage: @airisu-dev/react-native-live2d-skia fetch-sdk --accept-license [--sdk-version <version>] [--out <dir>] [--keep-zip]
|
|
29
|
+
|
|
30
|
+
Downloads Cubism SDK for Native from Live2D into <dir>
|
|
31
|
+
(default: ./${DEFAULT_OUT}). Then set it in app.json:
|
|
32
|
+
|
|
33
|
+
"plugins": [["@airisu-dev/react-native-live2d-skia", { "cubismSdkPath": "./${DEFAULT_OUT}" }]]
|
|
34
|
+
|
|
35
|
+
--accept-license Required: you accept Live2D's license terms:
|
|
36
|
+
${LICENSES.map((l) => ` - ${l}`).join('\n')}
|
|
37
|
+
--sdk-version Which version: ${VERSIONS.join(', ')} (default: ${cubismSdk.latest}).
|
|
38
|
+
--out <dir> Where to extract the SDK.
|
|
39
|
+
--keep-zip Keep the downloaded zip next to <dir> (e.g. to cache it on CI).`);
|
|
40
|
+
}
|
|
41
|
+
function fail(message) {
|
|
42
|
+
console.error(`error: ${message}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
function parseArgs(argv) {
|
|
46
|
+
const args = {
|
|
47
|
+
command: argv[0] === '--help' || argv[0] === '-h' ? 'help' : argv[0],
|
|
48
|
+
acceptLicense: false,
|
|
49
|
+
version: cubismSdk.latest,
|
|
50
|
+
out: DEFAULT_OUT,
|
|
51
|
+
keepZip: false,
|
|
52
|
+
};
|
|
53
|
+
for (let i = 1; i < argv.length; i++) {
|
|
54
|
+
const arg = argv[i];
|
|
55
|
+
const next = argv[i + 1];
|
|
56
|
+
if (arg === '--accept-license')
|
|
57
|
+
args.acceptLicense = true;
|
|
58
|
+
else if (arg === '--keep-zip')
|
|
59
|
+
args.keepZip = true;
|
|
60
|
+
else if (arg === '--sdk-version' && next) {
|
|
61
|
+
args.version = next;
|
|
62
|
+
i++;
|
|
63
|
+
}
|
|
64
|
+
else if (arg === '--out' && next) {
|
|
65
|
+
args.out = next;
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
else if (arg === '--help' || arg === '-h')
|
|
69
|
+
args.command = 'help';
|
|
70
|
+
else
|
|
71
|
+
throw new Error(`unknown option ${arg}`);
|
|
72
|
+
}
|
|
73
|
+
return args;
|
|
74
|
+
}
|
|
75
|
+
const sha256 = (file) => crypto_1.default.createHash('sha256').update(fs_1.default.readFileSync(file)).digest('hex');
|
|
76
|
+
exports.sha256 = sha256;
|
|
77
|
+
async function download(url, file) {
|
|
78
|
+
console.log(`Downloading ${url}`);
|
|
79
|
+
const response = await fetch(url);
|
|
80
|
+
if (!response.ok)
|
|
81
|
+
fail(`download failed: HTTP ${response.status}`);
|
|
82
|
+
fs_1.default.writeFileSync(file, Buffer.from(await response.arrayBuffer()));
|
|
83
|
+
}
|
|
84
|
+
// unzip where it exists (macOS, most Linux), else tar, which reads zips on
|
|
85
|
+
// Windows 10+ (bsdtar) and macOS.
|
|
86
|
+
function extract(zip, dir) {
|
|
87
|
+
const hasUnzip = !(0, child_process_1.spawnSync)('unzip', ['-v'], { stdio: 'ignore' }).error;
|
|
88
|
+
const [cmd, args] = hasUnzip
|
|
89
|
+
? ['unzip', ['-q', zip, '-d', dir]]
|
|
90
|
+
: ['tar', ['-xf', zip, '-C', dir]];
|
|
91
|
+
const result = (0, child_process_1.spawnSync)(cmd, args, { stdio: 'inherit' });
|
|
92
|
+
if (result.error || result.status !== 0) {
|
|
93
|
+
fail(`couldn't extract ${zip} with ${cmd}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function fetchSdk({ acceptLicense, version, out, keepZip }) {
|
|
97
|
+
if (!acceptLicense) {
|
|
98
|
+
console.error("Cubism SDK for Native is Live2D's software. Downloading it means accepting:\n" +
|
|
99
|
+
LICENSES.map((l) => ` - ${l}`).join('\n') +
|
|
100
|
+
'\nRe-run with --accept-license to accept them and download.');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
const expected = cubismSdk.versions[version]?.sha256;
|
|
104
|
+
if (!expected)
|
|
105
|
+
fail(`Cubism SDK for Native ${version} isn't supported: use ${VERSIONS.join(', ')}`);
|
|
106
|
+
const sdkDir = path_1.default.resolve(out);
|
|
107
|
+
const versionFile = path_1.default.join(sdkDir, '.version');
|
|
108
|
+
if (fs_1.default.existsSync(versionFile) && fs_1.default.readFileSync(versionFile, 'utf8').trim() === version) {
|
|
109
|
+
console.log(`Cubism SDK for Native ${version} already in ${sdkDir}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const zip = `${sdkDir}-${version}.zip`;
|
|
113
|
+
fs_1.default.mkdirSync(path_1.default.dirname(sdkDir), { recursive: true });
|
|
114
|
+
if (!fs_1.default.existsSync(zip) || (0, exports.sha256)(zip) !== expected) {
|
|
115
|
+
await download(sdkUrl(version), zip);
|
|
116
|
+
}
|
|
117
|
+
const actual = (0, exports.sha256)(zip);
|
|
118
|
+
if (actual !== expected) {
|
|
119
|
+
fs_1.default.rmSync(zip, { force: true });
|
|
120
|
+
fail(`checksum mismatch for ${zip} (expected ${expected}, got ${actual})`);
|
|
121
|
+
}
|
|
122
|
+
// Extract next to the destination, so the move is a rename on the same drive.
|
|
123
|
+
const tmp = `${sdkDir}.extracting`;
|
|
124
|
+
fs_1.default.rmSync(tmp, { recursive: true, force: true });
|
|
125
|
+
fs_1.default.mkdirSync(tmp);
|
|
126
|
+
try {
|
|
127
|
+
extract(zip, tmp);
|
|
128
|
+
fs_1.default.rmSync(sdkDir, { recursive: true, force: true });
|
|
129
|
+
fs_1.default.renameSync(path_1.default.join(tmp, `CubismSdkForNative-${version}`), sdkDir);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
fs_1.default.rmSync(tmp, { recursive: true, force: true });
|
|
133
|
+
}
|
|
134
|
+
fs_1.default.writeFileSync(versionFile, `${version}\n`);
|
|
135
|
+
if (!keepZip)
|
|
136
|
+
fs_1.default.rmSync(zip, { force: true });
|
|
137
|
+
const relative = path_1.default.relative(process.cwd(), sdkDir).split(path_1.default.sep).join('/');
|
|
138
|
+
console.log(`Extracted Cubism SDK for Native ${version} to ${sdkDir}
|
|
139
|
+
|
|
140
|
+
Next:
|
|
141
|
+
- Keep it out of public repos: add "/${relative}/" to .gitignore.
|
|
142
|
+
- Set it in app.json: "plugins": [["@airisu-dev/react-native-live2d-skia", { "cubismSdkPath": "./${relative}" }]]
|
|
143
|
+
- On EAS Build, run this command in an "eas-build-post-install" script.`);
|
|
144
|
+
}
|
|
145
|
+
async function main() {
|
|
146
|
+
const args = parseArgs(process.argv.slice(2));
|
|
147
|
+
if (args.command === 'fetch-sdk')
|
|
148
|
+
await fetchSdk(args);
|
|
149
|
+
else {
|
|
150
|
+
usage();
|
|
151
|
+
if (args.command && args.command !== 'help')
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (require.main === module) {
|
|
156
|
+
main().catch((e) => fail(e instanceof Error ? e.message : String(e)));
|
|
157
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// Differences between the Cubism SDK for Native versions react-native-live2d-skia builds
|
|
4
|
+
// against (cubismSdk.versions in package.json). build.gradle and the podspec
|
|
5
|
+
// pass the SDK's version as LIVE2D_SKIA_CUBISM_SDK: major * 10000 + release *
|
|
6
|
+
// 100 + patch, so 5-r.4.1 is 50401. Version-specific calls go here, behind
|
|
7
|
+
// #if on it (older SDK versions).
|
|
8
|
+
|
|
9
|
+
#include "Live2DCubismCore.hpp"
|
|
10
|
+
|
|
11
|
+
#ifndef LIVE2D_SKIA_CUBISM_SDK
|
|
12
|
+
#error "[react-native-live2d-skia] LIVE2D_SKIA_CUBISM_SDK isn't set: build through Gradle or CocoaPods."
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
#if LIVE2D_SKIA_CUBISM_SDK < 50500
|
|
16
|
+
#error "[react-native-live2d-skia] Cubism SDK for Native before 5-r.5 isn't supported."
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
namespace margelo::nitro::live2dskia::cubism {
|
|
20
|
+
|
|
21
|
+
// Drawable render orders. Named csmGetDrawableRenderOrders before 5-r.5.
|
|
22
|
+
inline const int* renderOrders(const Live2D::Cubism::Core::csmModel* model) {
|
|
23
|
+
return Live2D::Cubism::Core::csmGetRenderOrders(model);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
} // namespace margelo::nitro::live2dskia::cubism
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <stdexcept>
|
|
3
|
+
#include <string>
|
|
4
|
+
#include "CubismFramework.hpp"
|
|
5
|
+
#include "Id/CubismIdManager.hpp"
|
|
6
|
+
#include "Utils/CubismJson.hpp"
|
|
7
|
+
|
|
8
|
+
namespace margelo::nitro::live2dskia {
|
|
9
|
+
|
|
10
|
+
inline const Live2D::Cubism::Framework::csmByte* bytesOf(const std::string& s) {
|
|
11
|
+
return reinterpret_cast<const Live2D::Cubism::Framework::csmByte*>(s.data());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// The Framework's loaders don't check for parse errors and crash on invalid
|
|
15
|
+
// JSON, so parse it once up front and throw instead.
|
|
16
|
+
inline void requireValidJson(const std::string& json, const std::string& what) {
|
|
17
|
+
using namespace Live2D::Cubism::Framework;
|
|
18
|
+
Utils::CubismJson* parsed = Utils::CubismJson::Create(bytesOf(json), static_cast<csmSizeInt>(json.size()));
|
|
19
|
+
if (!parsed) throw std::runtime_error("Live2D: invalid " + what);
|
|
20
|
+
Utils::CubismJson::Delete(parsed);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
inline Live2D::Cubism::Framework::CubismIdHandle idOf(const Live2D::Cubism::Framework::csmChar* name) {
|
|
24
|
+
return Live2D::Cubism::Framework::CubismFramework::GetIdManager()->GetId(name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
} // namespace margelo::nitro::live2dskia
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The Cubism Framework references a few renderer entry points that each
|
|
2
|
+
// Rendering/<backend> normally defines. We draw with Skia instead, so no
|
|
3
|
+
// backend is compiled and these are stubbed out.
|
|
4
|
+
#include "Rendering/CubismRenderer.hpp"
|
|
5
|
+
|
|
6
|
+
namespace Live2D::Cubism::Framework::Rendering {
|
|
7
|
+
|
|
8
|
+
CubismRenderer* CubismRenderer::Create(csmUint32, csmUint32) {
|
|
9
|
+
return nullptr;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
void CubismRenderer::StaticRelease() {}
|
|
13
|
+
|
|
14
|
+
} // namespace Live2D::Cubism::Framework::Rendering
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#include "CubismSetup.hpp"
|
|
2
|
+
#include <cstdio>
|
|
3
|
+
#include <cstdlib>
|
|
4
|
+
#include <mutex>
|
|
5
|
+
#include "CubismFramework.hpp"
|
|
6
|
+
#include "ICubismAllocator.hpp"
|
|
7
|
+
|
|
8
|
+
#ifdef __ANDROID__
|
|
9
|
+
#include <android/log.h>
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
namespace margelo::nitro::live2dskia {
|
|
13
|
+
|
|
14
|
+
namespace {
|
|
15
|
+
|
|
16
|
+
using namespace Live2D::Cubism::Framework;
|
|
17
|
+
|
|
18
|
+
class Allocator : public ICubismAllocator {
|
|
19
|
+
public:
|
|
20
|
+
void* Allocate(const csmSizeType size) override { return std::malloc(size); }
|
|
21
|
+
void Deallocate(void* memory) override { std::free(memory); }
|
|
22
|
+
void* AllocateAligned(const csmSizeType size, const csmUint32 alignment) override {
|
|
23
|
+
void* p = nullptr;
|
|
24
|
+
// posix_memalign needs at least pointer alignment.
|
|
25
|
+
const size_t a = alignment < sizeof(void*) ? sizeof(void*) : alignment;
|
|
26
|
+
return posix_memalign(&p, a, size) == 0 ? p : nullptr;
|
|
27
|
+
}
|
|
28
|
+
void DeallocateAligned(void* alignedMemory) override { std::free(alignedMemory); }
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
void log(const char* message) {
|
|
32
|
+
#ifdef __ANDROID__
|
|
33
|
+
__android_log_print(ANDROID_LOG_INFO, "Live2dSkia", "%s", message);
|
|
34
|
+
#else
|
|
35
|
+
std::printf("[Live2dSkia] %s", message);
|
|
36
|
+
#endif
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
} // namespace
|
|
40
|
+
|
|
41
|
+
void ensureCubismFramework() {
|
|
42
|
+
static std::once_flag once;
|
|
43
|
+
std::call_once(once, [] {
|
|
44
|
+
// Both live for the whole process: the Framework keeps pointers to them.
|
|
45
|
+
static Allocator allocator;
|
|
46
|
+
static CubismFramework::Option option{};
|
|
47
|
+
option.LogFunction = log;
|
|
48
|
+
option.LoggingLevel = CubismFramework::Option::LogLevel_Info;
|
|
49
|
+
CubismFramework::StartUp(&allocator, &option);
|
|
50
|
+
CubismFramework::Initialize();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
} // namespace margelo::nitro::live2dskia
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <map>
|
|
3
|
+
#include "CubismModelSettingJson.hpp"
|
|
4
|
+
#include "Motion/ICubismUpdater.hpp"
|
|
5
|
+
|
|
6
|
+
namespace margelo::nitro::live2dskia {
|
|
7
|
+
|
|
8
|
+
// Applies setParameter values every update, after look and breath (so they
|
|
9
|
+
// can be overridden) and before physics (so hair and clothes follow).
|
|
10
|
+
class HeldParameterUpdater : public Live2D::Cubism::Framework::ICubismUpdater {
|
|
11
|
+
public:
|
|
12
|
+
explicit HeldParameterUpdater(const std::map<Live2D::Cubism::Framework::CubismIdHandle, float>& values)
|
|
13
|
+
: ICubismUpdater(Live2D::Cubism::Framework::CubismUpdateOrder_Breath + 50), _values(values) {}
|
|
14
|
+
|
|
15
|
+
void OnLateUpdate(Live2D::Cubism::Framework::CubismModel* model, Live2D::Cubism::Framework::csmFloat32) override {
|
|
16
|
+
for (const auto& [id, value] : _values) {
|
|
17
|
+
model->SetParameterValue(id, value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private:
|
|
22
|
+
const std::map<Live2D::Cubism::Framework::CubismIdHandle, float>& _values;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
} // namespace margelo::nitro::live2dskia
|