@airisu-dev/react-native-live2d-skia 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/Live2dSkia.podspec +159 -0
- package/README.md +64 -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 +146 -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 +194 -0
- package/src/loader/modelContext.ts +80 -0
- package/src/loader/modelUrl.ts +112 -0
- package/src/native.ts +4 -0
- package/src/rendering/drawer.ts +256 -0
- package/src/rendering/skia.ts +114 -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,75 @@
|
|
|
1
|
+
#if LIVE2D_SKIA
|
|
2
|
+
|
|
3
|
+
#include "MeshCache.hpp"
|
|
4
|
+
#include <chrono>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
|
|
7
|
+
namespace margelo::nitro::live2dskia {
|
|
8
|
+
|
|
9
|
+
using namespace Live2D::Cubism::Core;
|
|
10
|
+
|
|
11
|
+
namespace {
|
|
12
|
+
|
|
13
|
+
double millisecondsSince(std::chrono::steady_clock::time_point start) {
|
|
14
|
+
return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
} // namespace
|
|
18
|
+
|
|
19
|
+
void MeshCache::setTextureSizes(std::vector<SkISize> sizes) {
|
|
20
|
+
_textureSizes = std::move(sizes);
|
|
21
|
+
_meshes.clear();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
void MeshCache::resize(size_t drawableCount) {
|
|
25
|
+
if (_meshes.size() != drawableCount) _meshes.resize(drawableCount);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
void MeshCache::resetStats() {
|
|
29
|
+
_meshesBuilt = 0;
|
|
30
|
+
_meshMs = 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const SkVertices* MeshCache::meshOf(const csmModel* model, int i, const CanvasInfo& info) {
|
|
34
|
+
const int vertexCount = csmGetDrawableVertexCounts(model)[i];
|
|
35
|
+
const int indexCount = csmGetDrawableIndexCounts(model)[i];
|
|
36
|
+
const int texture = csmGetDrawableTextureIndices(model)[i];
|
|
37
|
+
if (vertexCount == 0 || indexCount == 0 || texture < 0 ||
|
|
38
|
+
texture >= static_cast<int>(_textureSizes.size())) {
|
|
39
|
+
return nullptr;
|
|
40
|
+
}
|
|
41
|
+
Mesh& mesh = _meshes[i];
|
|
42
|
+
const csmVector2* positions = csmGetDrawableVertexPositions(model)[i];
|
|
43
|
+
if (mesh.vertices && mesh.positions.size() == static_cast<size_t>(vertexCount) &&
|
|
44
|
+
std::memcmp(mesh.positions.data(), positions, vertexCount * sizeof(csmVector2)) == 0) {
|
|
45
|
+
return mesh.vertices.get();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const auto start = std::chrono::steady_clock::now();
|
|
49
|
+
if (mesh.texCoords.size() != static_cast<size_t>(vertexCount)) {
|
|
50
|
+
const csmVector2* uvs = csmGetDrawableVertexUvs(model)[i];
|
|
51
|
+
const SkISize size = _textureSizes[texture];
|
|
52
|
+
mesh.texCoords.resize(vertexCount);
|
|
53
|
+
for (int v = 0; v < vertexCount; ++v) {
|
|
54
|
+
// Cubism UVs are v-up.
|
|
55
|
+
mesh.texCoords[v] = {uvs[v].X * size.width(), (1 - uvs[v].Y) * size.height()};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
_points.resize(vertexCount);
|
|
59
|
+
for (int v = 0; v < vertexCount; ++v) {
|
|
60
|
+
// Cubism is y-up in model units; Skia is y-down in pixels.
|
|
61
|
+
_points[v] = {info.originX + positions[v].X * info.pixelsPerUnit,
|
|
62
|
+
info.originY - positions[v].Y * info.pixelsPerUnit};
|
|
63
|
+
}
|
|
64
|
+
mesh.vertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, vertexCount, _points.data(),
|
|
65
|
+
mesh.texCoords.data(), nullptr, indexCount,
|
|
66
|
+
csmGetDrawableIndices(model)[i]);
|
|
67
|
+
mesh.positions.assign(positions, positions + vertexCount);
|
|
68
|
+
_meshesBuilt++;
|
|
69
|
+
_meshMs += millisecondsSince(start);
|
|
70
|
+
return mesh.vertices.get();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
} // namespace margelo::nitro::live2dskia
|
|
74
|
+
|
|
75
|
+
#endif // LIVE2D_SKIA
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#if LIVE2D_SKIA
|
|
3
|
+
|
|
4
|
+
#include <vector>
|
|
5
|
+
#include "SkiaDrawer.hpp"
|
|
6
|
+
#include "include/core/SkPoint.h"
|
|
7
|
+
#include "include/core/SkRefCnt.h"
|
|
8
|
+
#include "include/core/SkSize.h"
|
|
9
|
+
#include "include/core/SkVertices.h"
|
|
10
|
+
|
|
11
|
+
namespace margelo::nitro::live2dskia {
|
|
12
|
+
|
|
13
|
+
// One Skia mesh per drawable, rebuilt only when the drawable's vertices moved.
|
|
14
|
+
class MeshCache {
|
|
15
|
+
public:
|
|
16
|
+
// Texture coordinates depend on the texture sizes, so this drops every mesh.
|
|
17
|
+
void setTextureSizes(std::vector<SkISize> sizes);
|
|
18
|
+
void resize(size_t drawableCount);
|
|
19
|
+
void resetStats();
|
|
20
|
+
|
|
21
|
+
// Returns the drawable's mesh, rebuilt if its vertices moved; null if it has none.
|
|
22
|
+
const SkVertices* meshOf(const Live2D::Cubism::Core::csmModel* model, int i, const CanvasInfo& info);
|
|
23
|
+
|
|
24
|
+
int meshesBuilt() const { return _meshesBuilt; }
|
|
25
|
+
double meshMs() const { return _meshMs; }
|
|
26
|
+
|
|
27
|
+
private:
|
|
28
|
+
struct Mesh {
|
|
29
|
+
sk_sp<SkVertices> vertices;
|
|
30
|
+
// Model-space positions the mesh was built from, to detect changes.
|
|
31
|
+
std::vector<Live2D::Cubism::Core::csmVector2> positions;
|
|
32
|
+
// Texture coordinates in pixels, built once.
|
|
33
|
+
std::vector<SkPoint> texCoords;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
std::vector<SkISize> _textureSizes;
|
|
37
|
+
std::vector<Mesh> _meshes;
|
|
38
|
+
std::vector<SkPoint> _points; // scratch
|
|
39
|
+
int _meshesBuilt = 0;
|
|
40
|
+
double _meshMs = 0;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
} // namespace margelo::nitro::live2dskia
|
|
44
|
+
|
|
45
|
+
#endif // LIVE2D_SKIA
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#if LIVE2D_SKIA
|
|
2
|
+
|
|
3
|
+
#include "SkiaDrawer.hpp"
|
|
4
|
+
#include "CubismCompat.hpp"
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <numeric>
|
|
7
|
+
#include "JsiSkCanvas.h"
|
|
8
|
+
#include "JsiSkShader.h"
|
|
9
|
+
#include "JsiUnwrap.hpp"
|
|
10
|
+
#include "MeshCache.hpp"
|
|
11
|
+
#include "include/core/SkCanvas.h"
|
|
12
|
+
#include "include/core/SkPaint.h"
|
|
13
|
+
#include "include/core/SkShader.h"
|
|
14
|
+
#include "include/core/SkVertices.h"
|
|
15
|
+
|
|
16
|
+
namespace margelo::nitro::live2dskia {
|
|
17
|
+
|
|
18
|
+
using namespace Live2D::Cubism::Core;
|
|
19
|
+
|
|
20
|
+
namespace {
|
|
21
|
+
|
|
22
|
+
SkBlendMode blendModeOf(csmFlags constantFlags) {
|
|
23
|
+
if (constantFlags & csmBlendAdditive) return SkBlendMode::kPlus;
|
|
24
|
+
if (constantFlags & csmBlendMultiplicative) return SkBlendMode::kMultiply;
|
|
25
|
+
return SkBlendMode::kSrcOver;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
} // namespace
|
|
29
|
+
|
|
30
|
+
struct SkiaDrawer::Impl {
|
|
31
|
+
std::vector<sk_sp<SkShader>> shaders;
|
|
32
|
+
MeshCache meshes;
|
|
33
|
+
std::vector<int> order; // scratch
|
|
34
|
+
SkPaint paint;
|
|
35
|
+
SkPaint layerPaint;
|
|
36
|
+
|
|
37
|
+
void drawMesh(SkCanvas* canvas, const SkVertices* vertices, int texture, SkPaint& p) {
|
|
38
|
+
p.setShader(shaders[texture]);
|
|
39
|
+
// No per-vertex colors, so the vertex blend mode is unused; the shader alone colors the mesh.
|
|
40
|
+
canvas->drawVertices(vertices, SkBlendMode::kDst, p);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
SkiaDrawer::SkiaDrawer() : _impl(std::make_unique<Impl>()) {
|
|
45
|
+
_impl->paint.setAntiAlias(true);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
SkiaDrawer::~SkiaDrawer() = default;
|
|
49
|
+
|
|
50
|
+
void SkiaDrawer::setTextures(jsi::Runtime& runtime, const jsi::Value& shaders, const jsi::Value& sizes) {
|
|
51
|
+
jsi::Array shaderArray = shaders.asObject(runtime).asArray(runtime);
|
|
52
|
+
jsi::Array sizeArray = sizes.asObject(runtime).asArray(runtime);
|
|
53
|
+
const size_t count = shaderArray.size(runtime);
|
|
54
|
+
if (sizeArray.size(runtime) != count * 2) {
|
|
55
|
+
throw jsi::JSError(runtime, "Live2D: expected a width and height per texture");
|
|
56
|
+
}
|
|
57
|
+
_impl->shaders.clear();
|
|
58
|
+
std::vector<SkISize> textureSizes;
|
|
59
|
+
for (size_t i = 0; i < count; ++i) {
|
|
60
|
+
auto shader = unwrap<RNSkia::JsiSkShader>(runtime, shaderArray.getValueAtIndex(runtime, i), "an SkShader");
|
|
61
|
+
_impl->shaders.push_back(shader->getObject());
|
|
62
|
+
textureSizes.push_back(SkISize::Make(
|
|
63
|
+
static_cast<int>(sizeArray.getValueAtIndex(runtime, i * 2).asNumber()),
|
|
64
|
+
static_cast<int>(sizeArray.getValueAtIndex(runtime, i * 2 + 1).asNumber())));
|
|
65
|
+
}
|
|
66
|
+
_impl->meshes.setTextureSizes(std::move(textureSizes));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
jsi::Value SkiaDrawer::draw(jsi::Runtime& runtime, const jsi::Value& canvasValue, bool wantStats,
|
|
70
|
+
const csmModel* model, const CanvasInfo& info) {
|
|
71
|
+
if (_impl->shaders.empty()) throw jsi::JSError(runtime, "Live2D: call setTextures before draw");
|
|
72
|
+
SkCanvas* canvas = unwrap<RNSkia::JsiSkCanvas>(runtime, canvasValue, "an SkCanvas")->getCanvas();
|
|
73
|
+
Impl& d = *_impl;
|
|
74
|
+
|
|
75
|
+
const int count = csmGetDrawableCount(model);
|
|
76
|
+
d.meshes.resize(count);
|
|
77
|
+
d.meshes.resetStats();
|
|
78
|
+
int layers = 0;
|
|
79
|
+
|
|
80
|
+
const int* renderOrders = cubism::renderOrders(model);
|
|
81
|
+
const csmFlags* dynamicFlags = csmGetDrawableDynamicFlags(model);
|
|
82
|
+
const csmFlags* constantFlags = csmGetDrawableConstantFlags(model);
|
|
83
|
+
const int* textureIndices = csmGetDrawableTextureIndices(model);
|
|
84
|
+
const float* opacities = csmGetDrawableOpacities(model);
|
|
85
|
+
const int* maskCounts = csmGetDrawableMaskCounts(model);
|
|
86
|
+
const int** masks = csmGetDrawableMasks(model);
|
|
87
|
+
|
|
88
|
+
// Draw order: drawable indices sorted by render order.
|
|
89
|
+
std::vector<int>& order = d.order;
|
|
90
|
+
order.resize(count);
|
|
91
|
+
std::iota(order.begin(), order.end(), 0);
|
|
92
|
+
std::sort(order.begin(), order.end(), [&](int a, int b) { return renderOrders[a] < renderOrders[b]; });
|
|
93
|
+
|
|
94
|
+
for (int i : order) {
|
|
95
|
+
if (!(dynamicFlags[i] & csmIsVisible) || opacities[i] <= 0) continue;
|
|
96
|
+
const SkVertices* vertices = d.meshes.meshOf(model, i, info);
|
|
97
|
+
if (!vertices) continue;
|
|
98
|
+
|
|
99
|
+
if (maskCounts[i] == 0) {
|
|
100
|
+
d.paint.setAlphaf(opacities[i]);
|
|
101
|
+
d.paint.setBlendMode(blendModeOf(constantFlags[i]));
|
|
102
|
+
d.drawMesh(canvas, vertices, textureIndices[i], d.paint);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Draw into a layer, cut it with the union of the masks, then composite
|
|
107
|
+
// the layer with the drawable's blend mode.
|
|
108
|
+
const SkRect& bounds = vertices->bounds();
|
|
109
|
+
d.layerPaint.setBlendMode(blendModeOf(constantFlags[i]));
|
|
110
|
+
canvas->saveLayer(&bounds, &d.layerPaint);
|
|
111
|
+
d.paint.setAlphaf(opacities[i]);
|
|
112
|
+
d.paint.setBlendMode(SkBlendMode::kSrcOver);
|
|
113
|
+
d.drawMesh(canvas, vertices, textureIndices[i], d.paint);
|
|
114
|
+
|
|
115
|
+
d.layerPaint.setBlendMode((constantFlags[i] & csmIsInvertedMask) ? SkBlendMode::kDstOut : SkBlendMode::kDstIn);
|
|
116
|
+
canvas->saveLayer(&bounds, &d.layerPaint);
|
|
117
|
+
d.paint.setAlphaf(1);
|
|
118
|
+
for (int m = 0; m < maskCounts[i]; ++m) {
|
|
119
|
+
const int maskIndex = masks[i][m];
|
|
120
|
+
const SkVertices* maskVertices = d.meshes.meshOf(model, maskIndex, info);
|
|
121
|
+
if (maskVertices) d.drawMesh(canvas, maskVertices, textureIndices[maskIndex], d.paint);
|
|
122
|
+
}
|
|
123
|
+
canvas->restore();
|
|
124
|
+
canvas->restore();
|
|
125
|
+
layers += 2;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!wantStats) return jsi::Value::undefined();
|
|
129
|
+
jsi::Object stats(runtime);
|
|
130
|
+
stats.setProperty(runtime, "meshesBuilt", d.meshes.meshesBuilt());
|
|
131
|
+
stats.setProperty(runtime, "layers", layers);
|
|
132
|
+
stats.setProperty(runtime, "meshMs", d.meshes.meshMs());
|
|
133
|
+
return stats;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
} // namespace margelo::nitro::live2dskia
|
|
137
|
+
|
|
138
|
+
#endif // LIVE2D_SKIA
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#if LIVE2D_SKIA
|
|
3
|
+
|
|
4
|
+
#include <jsi/jsi.h>
|
|
5
|
+
#include <memory>
|
|
6
|
+
#include <vector>
|
|
7
|
+
#include "Live2DCubismCore.hpp"
|
|
8
|
+
|
|
9
|
+
namespace margelo::nitro::live2dskia {
|
|
10
|
+
|
|
11
|
+
namespace jsi = facebook::jsi;
|
|
12
|
+
|
|
13
|
+
struct CanvasInfo {
|
|
14
|
+
float originX;
|
|
15
|
+
float originY;
|
|
16
|
+
float pixelsPerUnit;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Draws a model's drawables onto a react-native-skia canvas in C++, the same
|
|
20
|
+
// way src/rendering/drawer.ts does in JS, without building meshes through JSI.
|
|
21
|
+
// Not thread-safe: HybridLive2DModel serializes calls.
|
|
22
|
+
class SkiaDrawer {
|
|
23
|
+
public:
|
|
24
|
+
SkiaDrawer();
|
|
25
|
+
~SkiaDrawer();
|
|
26
|
+
|
|
27
|
+
// shaders: SkShader[] (one per texture); sizes: [width0, height0, width1, ...].
|
|
28
|
+
void setTextures(jsi::Runtime& runtime, const jsi::Value& shaders, const jsi::Value& sizes);
|
|
29
|
+
|
|
30
|
+
// Draws the model's current state onto a JS SkCanvas. Returns
|
|
31
|
+
// { meshesBuilt, layers, meshMs } when wantStats, else undefined.
|
|
32
|
+
jsi::Value draw(jsi::Runtime& runtime, const jsi::Value& canvas, bool wantStats,
|
|
33
|
+
const Live2D::Cubism::Core::csmModel* model, const CanvasInfo& info);
|
|
34
|
+
|
|
35
|
+
private:
|
|
36
|
+
struct Impl;
|
|
37
|
+
std::unique_ptr<Impl> _impl;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
} // namespace margelo::nitro::live2dskia
|
|
41
|
+
|
|
42
|
+
#endif // LIVE2D_SKIA
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#include "HybridLive2DModel.hpp"
|
|
2
|
+
#include <algorithm>
|
|
3
|
+
#include <numeric>
|
|
4
|
+
#include "CubismSetup.hpp"
|
|
5
|
+
#include "CubismCompat.hpp"
|
|
6
|
+
|
|
7
|
+
namespace margelo::nitro::live2dskia {
|
|
8
|
+
|
|
9
|
+
using namespace Live2D::Cubism::Core;
|
|
10
|
+
|
|
11
|
+
namespace {
|
|
12
|
+
|
|
13
|
+
template <typename T>
|
|
14
|
+
std::shared_ptr<ArrayBuffer> copyToArrayBuffer(const T* data, size_t count) {
|
|
15
|
+
return ArrayBuffer::copy(reinterpret_cast<const uint8_t*>(data), count * sizeof(T));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Live2DUserModel::Priority toPriority(std::optional<MotionPriority> priority) {
|
|
19
|
+
switch (priority.value_or(MotionPriority::NORMAL)) {
|
|
20
|
+
case MotionPriority::IDLE: return Live2DUserModel::PriorityIdle;
|
|
21
|
+
case MotionPriority::FORCE: return Live2DUserModel::PriorityForce;
|
|
22
|
+
default: return Live2DUserModel::PriorityNormal;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
} // namespace
|
|
27
|
+
|
|
28
|
+
HybridLive2DModel::HybridLive2DModel(const uint8_t* mocData, size_t mocSize, const std::string& settingsJson)
|
|
29
|
+
: HybridObject(TAG), HybridLive2DModelSpec() {
|
|
30
|
+
// The Framework must be started before any of its objects are created.
|
|
31
|
+
ensureCubismFramework();
|
|
32
|
+
_userModel = std::make_unique<Live2DUserModel>(mocData, mocSize, settingsJson);
|
|
33
|
+
|
|
34
|
+
csmVector2 size{}, origin{};
|
|
35
|
+
csmReadCanvasInfo(_userModel->model()->GetModel(), &size, &origin, &_pixelsPerUnit);
|
|
36
|
+
_canvasWidth = size.X;
|
|
37
|
+
_canvasHeight = size.Y;
|
|
38
|
+
_originX = origin.X;
|
|
39
|
+
_originY = origin.Y;
|
|
40
|
+
|
|
41
|
+
// Compute vertex positions, opacities and render orders for the default parameters.
|
|
42
|
+
_userModel->update(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
double HybridLive2DModel::getDrawableCount() {
|
|
46
|
+
std::lock_guard lock(_mutex);
|
|
47
|
+
return csmGetDrawableCount(_userModel->model()->GetModel());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
std::vector<DrawableData> HybridLive2DModel::getDrawables() {
|
|
51
|
+
std::lock_guard lock(_mutex);
|
|
52
|
+
const csmModel* model = _userModel->model()->GetModel();
|
|
53
|
+
const int count = csmGetDrawableCount(model);
|
|
54
|
+
const int* renderOrders = cubism::renderOrders(model);
|
|
55
|
+
const csmFlags* dynamicFlags = csmGetDrawableDynamicFlags(model);
|
|
56
|
+
const csmFlags* constantFlags = csmGetDrawableConstantFlags(model);
|
|
57
|
+
const int* textureIndices = csmGetDrawableTextureIndices(model);
|
|
58
|
+
const float* opacities = csmGetDrawableOpacities(model);
|
|
59
|
+
const int* vertexCounts = csmGetDrawableVertexCounts(model);
|
|
60
|
+
const csmVector2** positions = csmGetDrawableVertexPositions(model);
|
|
61
|
+
const csmVector2** uvs = csmGetDrawableVertexUvs(model);
|
|
62
|
+
const int* indexCounts = csmGetDrawableIndexCounts(model);
|
|
63
|
+
const unsigned short** indices = csmGetDrawableIndices(model);
|
|
64
|
+
const int* maskCounts = csmGetDrawableMaskCounts(model);
|
|
65
|
+
const int** masks = csmGetDrawableMasks(model);
|
|
66
|
+
|
|
67
|
+
std::vector<int> order(count);
|
|
68
|
+
std::iota(order.begin(), order.end(), 0);
|
|
69
|
+
std::sort(order.begin(), order.end(),
|
|
70
|
+
[&](int a, int b) { return renderOrders[a] < renderOrders[b]; });
|
|
71
|
+
|
|
72
|
+
std::vector<DrawableData> result;
|
|
73
|
+
result.reserve(count);
|
|
74
|
+
for (int i : order) {
|
|
75
|
+
result.emplace_back(i,
|
|
76
|
+
(dynamicFlags[i] & csmIsVisible) != 0,
|
|
77
|
+
textureIndices[i],
|
|
78
|
+
opacities[i],
|
|
79
|
+
constantFlags[i],
|
|
80
|
+
std::vector<double>(masks[i], masks[i] + maskCounts[i]),
|
|
81
|
+
copyToArrayBuffer(positions[i], vertexCounts[i]),
|
|
82
|
+
copyToArrayBuffer(uvs[i], vertexCounts[i]),
|
|
83
|
+
copyToArrayBuffer(indices[i], indexCounts[i]));
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
void HybridLive2DModel::loadPhysics(const std::string& json) {
|
|
89
|
+
std::lock_guard lock(_mutex);
|
|
90
|
+
_userModel->loadPhysics(json);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
void HybridLive2DModel::loadPose(const std::string& json) {
|
|
94
|
+
std::lock_guard lock(_mutex);
|
|
95
|
+
_userModel->loadPose(json);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
void HybridLive2DModel::loadMotion(const std::string& group, double index, const std::string& json) {
|
|
99
|
+
std::lock_guard lock(_mutex);
|
|
100
|
+
_userModel->loadMotion(group, static_cast<int>(index), json);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
bool HybridLive2DModel::startMotion(const std::string& group, std::optional<double> index, std::optional<MotionPriority> priority) {
|
|
104
|
+
std::lock_guard lock(_mutex);
|
|
105
|
+
if (!index) return _userModel->startRandomMotion(group, toPriority(priority));
|
|
106
|
+
return _userModel->startMotion(group, static_cast<int>(*index), toPriority(priority));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
void HybridLive2DModel::loadExpression(const std::string& name, const std::string& json) {
|
|
110
|
+
std::lock_guard lock(_mutex);
|
|
111
|
+
_userModel->loadExpression(name, json);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
bool HybridLive2DModel::setExpression(const std::optional<std::string>& name) {
|
|
115
|
+
std::lock_guard lock(_mutex);
|
|
116
|
+
return name ? _userModel->setExpression(*name) : _userModel->setRandomExpression();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
void HybridLive2DModel::clearExpression() {
|
|
120
|
+
std::lock_guard lock(_mutex);
|
|
121
|
+
_userModel->clearExpression();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
void HybridLive2DModel::setParameter(const std::string& id, double value) {
|
|
125
|
+
std::lock_guard lock(_mutex);
|
|
126
|
+
_userModel->setParameter(id, static_cast<float>(value));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
void HybridLive2DModel::clearParameter(const std::string& id) {
|
|
130
|
+
std::lock_guard lock(_mutex);
|
|
131
|
+
_userModel->clearParameter(id);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
void HybridLive2DModel::focus(double x, double y) {
|
|
135
|
+
std::lock_guard lock(_mutex);
|
|
136
|
+
_userModel->focus(static_cast<float>(std::clamp(x, -1.0, 1.0)), static_cast<float>(std::clamp(y, -1.0, 1.0)));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
std::vector<std::string> HybridLive2DModel::hitTest(double x, double y) {
|
|
140
|
+
std::lock_guard lock(_mutex);
|
|
141
|
+
return _userModel->hitTest(static_cast<float>(x), static_cast<float>(y));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
void HybridLive2DModel::update(double deltaSeconds) {
|
|
145
|
+
std::lock_guard lock(_mutex);
|
|
146
|
+
_userModel->update(static_cast<float>(deltaSeconds));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
void HybridLive2DModel::loadHybridMethods() {
|
|
150
|
+
HybridLive2DModelSpec::loadHybridMethods();
|
|
151
|
+
#if LIVE2D_SKIA
|
|
152
|
+
registerHybrids(this, [](Prototype& prototype) {
|
|
153
|
+
prototype.registerRawHybridMethod("setTextures", 2, &HybridLive2DModel::setTexturesRaw);
|
|
154
|
+
prototype.registerRawHybridMethod("drawTo", 2, &HybridLive2DModel::drawToRaw);
|
|
155
|
+
});
|
|
156
|
+
#endif
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
#if LIVE2D_SKIA
|
|
160
|
+
jsi::Value HybridLive2DModel::setTexturesRaw(jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, size_t) {
|
|
161
|
+
std::lock_guard lock(_mutex);
|
|
162
|
+
_drawer.setTextures(runtime, args[0], args[1]);
|
|
163
|
+
return jsi::Value::undefined();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
jsi::Value HybridLive2DModel::drawToRaw(jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, size_t) {
|
|
167
|
+
std::lock_guard lock(_mutex);
|
|
168
|
+
const CanvasInfo info{_originX, _originY, _pixelsPerUnit};
|
|
169
|
+
return _drawer.draw(runtime, args[0], args[1].isBool() && args[1].getBool(),
|
|
170
|
+
_userModel->model()->GetModel(), info);
|
|
171
|
+
}
|
|
172
|
+
#endif
|
|
173
|
+
|
|
174
|
+
} // namespace margelo::nitro::live2dskia
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <memory>
|
|
3
|
+
#include <mutex>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
6
|
+
#include "HybridLive2DModelSpec.hpp"
|
|
7
|
+
#include "Live2DUserModel.hpp"
|
|
8
|
+
#include "SkiaDrawer.hpp"
|
|
9
|
+
|
|
10
|
+
namespace margelo::nitro::live2dskia {
|
|
11
|
+
|
|
12
|
+
class HybridLive2DModel : public HybridLive2DModelSpec {
|
|
13
|
+
public:
|
|
14
|
+
// Takes a .moc3 file's bytes and its model3.json. Throws std::runtime_error if they can't be loaded.
|
|
15
|
+
HybridLive2DModel(const uint8_t* mocData, size_t mocSize, const std::string& settingsJson);
|
|
16
|
+
|
|
17
|
+
double getCanvasWidth() override { return _canvasWidth; }
|
|
18
|
+
double getCanvasHeight() override { return _canvasHeight; }
|
|
19
|
+
double getOriginX() override { return _originX; }
|
|
20
|
+
double getOriginY() override { return _originY; }
|
|
21
|
+
double getPixelsPerUnit() override { return _pixelsPerUnit; }
|
|
22
|
+
double getDrawableCount() override;
|
|
23
|
+
|
|
24
|
+
std::vector<DrawableData> getDrawables() override;
|
|
25
|
+
void loadPhysics(const std::string& json) override;
|
|
26
|
+
void loadPose(const std::string& json) override;
|
|
27
|
+
void loadMotion(const std::string& group, double index, const std::string& json) override;
|
|
28
|
+
bool startMotion(const std::string& group, std::optional<double> index, std::optional<MotionPriority> priority) override;
|
|
29
|
+
void loadExpression(const std::string& name, const std::string& json) override;
|
|
30
|
+
bool setExpression(const std::optional<std::string>& name) override;
|
|
31
|
+
void clearExpression() override;
|
|
32
|
+
void setParameter(const std::string& id, double value) override;
|
|
33
|
+
void clearParameter(const std::string& id) override;
|
|
34
|
+
void focus(double x, double y) override;
|
|
35
|
+
std::vector<std::string> hitTest(double x, double y) override;
|
|
36
|
+
void update(double deltaSeconds) override;
|
|
37
|
+
|
|
38
|
+
protected:
|
|
39
|
+
// Adds the raw JSI methods that take react-native-skia objects, when built with Skia.
|
|
40
|
+
void loadHybridMethods() override;
|
|
41
|
+
|
|
42
|
+
#if LIVE2D_SKIA
|
|
43
|
+
private:
|
|
44
|
+
// setTextures(shaders: SkShader[], sizes: number[]): void
|
|
45
|
+
jsi::Value setTexturesRaw(jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count);
|
|
46
|
+
// drawTo(canvas: SkCanvas, wantStats: boolean): DrawStats | undefined
|
|
47
|
+
jsi::Value drawToRaw(jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count);
|
|
48
|
+
#endif
|
|
49
|
+
|
|
50
|
+
private:
|
|
51
|
+
// The model is updated on the animation thread and controlled from the JS thread.
|
|
52
|
+
std::mutex _mutex;
|
|
53
|
+
std::unique_ptr<Live2DUserModel> _userModel;
|
|
54
|
+
#if LIVE2D_SKIA
|
|
55
|
+
SkiaDrawer _drawer;
|
|
56
|
+
#endif
|
|
57
|
+
float _canvasWidth = 0;
|
|
58
|
+
float _canvasHeight = 0;
|
|
59
|
+
float _originX = 0;
|
|
60
|
+
float _originY = 0;
|
|
61
|
+
float _pixelsPerUnit = 0;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
} // namespace margelo::nitro::live2dskia
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#include "HybridLive2dSkia.hpp"
|
|
2
|
+
#include "HybridLive2DModel.hpp"
|
|
3
|
+
|
|
4
|
+
namespace margelo::nitro::live2dskia {
|
|
5
|
+
|
|
6
|
+
std::shared_ptr<HybridLive2DModelSpec> HybridLive2dSkia::loadModel(const std::shared_ptr<ArrayBuffer>& moc, const std::string& settingsJson) {
|
|
7
|
+
return std::make_shared<HybridLive2DModel>(moc->data(), moc->size(), settingsJson);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
} // namespace margelo::nitro::live2dskia
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "HybridLive2dSkiaSpec.hpp"
|
|
3
|
+
|
|
4
|
+
namespace margelo::nitro::live2dskia {
|
|
5
|
+
class HybridLive2dSkia : public HybridLive2dSkiaSpec {
|
|
6
|
+
public:
|
|
7
|
+
HybridLive2dSkia() : HybridObject(TAG), HybridLive2dSkiaSpec() {}
|
|
8
|
+
|
|
9
|
+
std::shared_ptr<HybridLive2DModelSpec> loadModel(const std::shared_ptr<ArrayBuffer>& moc, const std::string& settingsJson) override;
|
|
10
|
+
};
|
|
11
|
+
} // namespace margelo::nitro::live2dskia
|
package/ios/Bridge.h
ADDED
package/nitro.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://nitro.margelo.com/nitro.schema.json",
|
|
3
|
+
"cxxNamespace": ["live2dskia"],
|
|
4
|
+
"ios": {
|
|
5
|
+
"iosModuleName": "Live2dSkia"
|
|
6
|
+
},
|
|
7
|
+
"android": {
|
|
8
|
+
"androidNamespace": ["live2dskia"],
|
|
9
|
+
"androidCxxLibName": "Live2dSkia"
|
|
10
|
+
},
|
|
11
|
+
"autolinking": {
|
|
12
|
+
"Live2dSkia": {
|
|
13
|
+
"all": {
|
|
14
|
+
"language": "c++",
|
|
15
|
+
"implementationClassName": "HybridLive2dSkia"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"ignorePaths": ["**/node_modules"]
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
** linguist-generated=true
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Live2dSkia+autolinking.cmake
|
|
3
|
+
# This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
# https://github.com/mrousavy/nitro
|
|
5
|
+
# Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
# This is a CMake file that adds all files generated by Nitrogen
|
|
9
|
+
# to the current CMake project.
|
|
10
|
+
#
|
|
11
|
+
# To use it, add this to your CMakeLists.txt:
|
|
12
|
+
# ```cmake
|
|
13
|
+
# include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/Live2dSkia+autolinking.cmake)
|
|
14
|
+
# ```
|
|
15
|
+
|
|
16
|
+
# Define a flag to check if we are building properly
|
|
17
|
+
add_definitions(-DBUILDING_LIVE2DSKIA_WITH_GENERATED_CMAKE_PROJECT)
|
|
18
|
+
|
|
19
|
+
# Enable Raw Props parsing in react-native (for Nitro Views)
|
|
20
|
+
add_definitions(-DRN_SERIALIZABLE_STATE)
|
|
21
|
+
|
|
22
|
+
# Add all headers that were generated by Nitrogen
|
|
23
|
+
include_directories(
|
|
24
|
+
"../nitrogen/generated/shared/c++"
|
|
25
|
+
"../nitrogen/generated/android/c++"
|
|
26
|
+
"../nitrogen/generated/android/"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Add all .cpp sources that were generated by Nitrogen
|
|
30
|
+
target_sources(
|
|
31
|
+
# CMake project name (Android C++ library name)
|
|
32
|
+
Live2dSkia PRIVATE
|
|
33
|
+
# Autolinking Setup
|
|
34
|
+
../nitrogen/generated/android/Live2dSkiaOnLoad.cpp
|
|
35
|
+
# Shared Nitrogen C++ sources
|
|
36
|
+
../nitrogen/generated/shared/c++/HybridLive2DModelSpec.cpp
|
|
37
|
+
../nitrogen/generated/shared/c++/HybridLive2dSkiaSpec.cpp
|
|
38
|
+
# Android-specific Nitrogen C++ sources
|
|
39
|
+
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake
|
|
43
|
+
# Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake
|
|
44
|
+
target_compile_definitions(
|
|
45
|
+
Live2dSkia PRIVATE
|
|
46
|
+
-DFOLLY_NO_CONFIG=1
|
|
47
|
+
-DFOLLY_HAVE_CLOCK_GETTIME=1
|
|
48
|
+
-DFOLLY_USE_LIBCPP=1
|
|
49
|
+
-DFOLLY_CFG_NO_COROUTINES=1
|
|
50
|
+
-DFOLLY_MOBILE=1
|
|
51
|
+
-DFOLLY_HAVE_RECVMMSG=1
|
|
52
|
+
-DFOLLY_HAVE_PTHREAD=1
|
|
53
|
+
# Once we target android-23 above, we can comment
|
|
54
|
+
# the following line. NDK uses GNU style stderror_r() after API 23.
|
|
55
|
+
-DFOLLY_HAVE_XSI_STRERROR_R=1
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Add all libraries required by the generated specs
|
|
59
|
+
find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++
|
|
60
|
+
find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule)
|
|
61
|
+
find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library
|
|
62
|
+
|
|
63
|
+
# Link all libraries together
|
|
64
|
+
target_link_libraries(
|
|
65
|
+
Live2dSkia
|
|
66
|
+
fbjni::fbjni # <-- Facebook C++ JNI helpers
|
|
67
|
+
ReactAndroid::jsi # <-- RN: JSI
|
|
68
|
+
react-native-nitro-modules::NitroModules # <-- NitroModules Core :)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Link react-native (different prefab between RN 0.75 and RN 0.76)
|
|
72
|
+
if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
|
|
73
|
+
target_link_libraries(
|
|
74
|
+
Live2dSkia
|
|
75
|
+
ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab
|
|
76
|
+
)
|
|
77
|
+
else()
|
|
78
|
+
target_link_libraries(
|
|
79
|
+
Live2dSkia
|
|
80
|
+
ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core
|
|
81
|
+
)
|
|
82
|
+
endif()
|