blob: c13ff2f11dfbfe08b43e8619e643923c5f76b98b [file] [log] [blame]
Ruiqi Maof5101492018-06-29 14:32:21 -04001/*
2* Copyright 2018 Google Inc.
3*
4* Use of this source code is governed by a BSD-style license that can be
5* found in the LICENSE file.
6*/
7
8#include "NIMASlide.h"
9
10#include "SkAnimTimer.h"
11#include "SkOSPath.h"
12#include "Resources.h"
13#include "imgui.h"
14
15#include <algorithm>
16#include <cmath>
17
18using namespace sk_app;
19using namespace nima;
20
21// NIMA stores its matrices as 6 floats to represent translation and scale. This function takes
22// that format and converts it into a 3x3 matrix representation.
23static void nima_to_skmatrix(const float* nimaData, SkMatrix& matrix) {
24 matrix[0] = nimaData[0];
25 matrix[1] = nimaData[2];
26 matrix[2] = nimaData[4];
27 matrix[3] = nimaData[1];
28 matrix[4] = nimaData[3];
29 matrix[5] = nimaData[5];
30 matrix[6] = 0.0f;
31 matrix[7] = 0.0f;
32 matrix[8] = 1.0f;
33}
34
35// ImGui expects an array of const char* when displaying a ListBox. This function is for an
36// overload of ImGui::ListBox that takes a getter so that ListBox works with
37// std::vector<std::string>.
38static bool vector_getter(void* v, int index, const char** out) {
39 auto vector = reinterpret_cast<std::vector<std::string>*>(v);
40 *out = vector->at(index).c_str();
41 return true;
42}
43
44// A wrapper class that handles rendering of ActorImages (renderable components NIMA Actors).
45class NIMAActorImage {
46public:
47 NIMAActorImage(ActorImage* actorImage, SkImage* texture, SkPaint* paint)
48 : fActorImage(actorImage)
49 , fTexture(texture)
50 , fPaint(paint)
51 , fSkinned(false)
52 , fPositions()
53 , fTexs()
54 , fBoneIdx()
55 , fBoneWgt()
56 , fIndices()
57 , fBones()
58 , fVertices(nullptr)
59 , fRenderMode(kBackend_RenderMode) {
60 // Update the vertices and bones.
61 this->updateVertices();
62 this->updateBones();
63
64 // Update the vertices object.
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +000065 this->updateVerticesObject(false);
Ruiqi Maof5101492018-06-29 14:32:21 -040066 }
67
68 void renderBackend(SkCanvas* canvas) {
69 // Reset vertices if the render mode has changed.
70 if (fRenderMode != kBackend_RenderMode) {
71 fRenderMode = kBackend_RenderMode;
72 this->updateVertices();
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +000073 this->updateVerticesObject(false);
Ruiqi Maof5101492018-06-29 14:32:21 -040074 }
75
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +000076 canvas->save();
77
Ruiqi Maof5101492018-06-29 14:32:21 -040078 // Update the vertex data.
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +000079 if (fActorImage->doesAnimationVertexDeform() && fActorImage->isVertexDeformDirty()) {
Ruiqi Maof5101492018-06-29 14:32:21 -040080 this->updateVertices();
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +000081 this->updateVerticesObject(false);
82 fActorImage->isVertexDeformDirty(false);
Ruiqi Maof5101492018-06-29 14:32:21 -040083 }
84
85 // Update the bones.
86 this->updateBones();
87
88 // Draw the vertices object.
89 this->drawVerticesObject(canvas, true);
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +000090
91 canvas->restore();
Ruiqi Maof5101492018-06-29 14:32:21 -040092 }
93
94 void renderImmediate(SkCanvas* canvas) {
95 // Reset vertices if the render mode has changed.
96 if (fRenderMode != kImmediate_RenderMode) {
97 fRenderMode = kImmediate_RenderMode;
98 this->updateVertices();
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +000099 this->updateVerticesObject(true);
Ruiqi Maof5101492018-06-29 14:32:21 -0400100 }
101
102 // Update the vertex data.
103 if (fActorImage->doesAnimationVertexDeform() && fActorImage->isVertexDeformDirty()) {
104 this->updateVertices();
105 fActorImage->isVertexDeformDirty(false);
106 }
107
108 // Update the vertices object.
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +0000109 this->updateVerticesObject(true);
Ruiqi Maof5101492018-06-29 14:32:21 -0400110
111 // Draw the vertices object.
112 this->drawVerticesObject(canvas, false);
113 }
114
115 int drawOrder() const { return fActorImage->drawOrder(); }
116
117private:
118 void updateVertices() {
119 // Update whether the image is skinned.
120 fSkinned = fActorImage->connectedBoneCount() > 0;
121
122 // Retrieve data from the image.
123 uint32_t vertexCount = fActorImage->vertexCount();
124 uint32_t vertexStride = fActorImage->vertexStride();
125 float* vertexData = fActorImage->vertices();
126 uint32_t indexCount = fActorImage->triangleCount() * 3;
127 uint16_t* indexData = fActorImage->triangles();
128
129 // Don't render if not visible.
130 if (!vertexCount || fActorImage->textureIndex() < 0) {
131 fPositions.clear();
132 fTexs.clear();
133 fBoneIdx.clear();
134 fBoneWgt.clear();
135 fIndices.clear();
136 return;
137 }
138
139 // Split the vertex data.
140 fPositions.resize(vertexCount);
141 fTexs.resize(vertexCount);
142 fIndices.resize(indexCount);
143 if (fSkinned) {
144 fBoneIdx.resize(vertexCount * 4);
145 fBoneWgt.resize(vertexCount * 4);
146 }
147 for (uint32_t i = 0; i < vertexCount; i ++) {
148 uint32_t j = i * vertexStride;
149
150 // Get the attributes.
151 float* attrPosition = vertexData + j;
152 float* attrTex = vertexData + j + 2;
153 float* attrBoneIdx = vertexData + j + 4;
154 float* attrBoneWgt = vertexData + j + 8;
155
156 // Get deformed positions if necessary.
157 if (fActorImage->doesAnimationVertexDeform()) {
158 attrPosition = fActorImage->animationDeformedVertices() + i * 2;
159 }
160
161 // Set the data.
162 fPositions[i].set(attrPosition[0], attrPosition[1]);
163 fTexs[i].set(attrTex[0] * fTexture->width(), attrTex[1] * fTexture->height());
164 if (fSkinned) {
165 for (uint32_t k = 0; k < 4; k ++) {
166 fBoneIdx[i].indices[k] = static_cast<uint32_t>(attrBoneIdx[k]);
167 fBoneWgt[i].weights[k] = attrBoneWgt[k];
168 }
169 }
170 }
171 memcpy(fIndices.data(), indexData, indexCount * sizeof(uint16_t));
172 }
173
174 void updateBones() {
175 // NIMA matrices are a collection of 6 floats.
176 constexpr int kNIMAMatrixSize = 6;
177
178 // Set up the matrices for the first time.
179 if (fBones.size() == 0) {
180 int numMatrices = 1;
181 if (fSkinned) {
182 numMatrices = fActorImage->boneInfluenceMatricesLength() / kNIMAMatrixSize;
183 }
184 fBones.assign(numMatrices, SkMatrix());
185 }
186
187 if (fSkinned) {
188 // Update the matrices.
189 float* matrixData = fActorImage->boneInfluenceMatrices();
190 for (uint32_t i = 1; i < fBones.size(); i ++) {
191 SkMatrix& matrix = fBones[i];
192 float* data = matrixData + i * kNIMAMatrixSize;
193 nima_to_skmatrix(data, matrix);
194 }
195 }
196
197 // Set the zero matrix to be the world transform.
198 nima_to_skmatrix(fActorImage->worldTransform().values(), fBones[0]);
199 }
200
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +0000201 void updateVerticesObject(bool applyDeforms) {
Ruiqi Maof5101492018-06-29 14:32:21 -0400202 std::vector<SkPoint>* positions = &fPositions;
203
204 // Apply deforms if requested.
205 uint32_t vertexCount = fPositions.size();
206 std::vector<SkPoint> deformedPositions;
207 if (applyDeforms) {
208 positions = &deformedPositions;
209 deformedPositions.reserve(vertexCount);
210 for (uint32_t i = 0; i < vertexCount; i ++) {
211 Vec2D nimaPoint(fPositions[i].x(), fPositions[i].y());
212 uint32_t* boneIdx = nullptr;
213 float* boneWgt = nullptr;
214 if (fSkinned) {
215 boneIdx = fBoneIdx[i].indices;
216 boneWgt = fBoneWgt[i].weights;
217 }
218 nimaPoint = this->deform(nimaPoint, boneIdx, boneWgt);
219 deformedPositions.push_back(SkPoint::Make(nimaPoint[0], nimaPoint[1]));
220 }
221 }
222
223 // Update the vertices object.
224 fVertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
225 vertexCount,
226 positions->data(),
227 fTexs.data(),
228 nullptr,
229 fBoneIdx.data(),
230 fBoneWgt.data(),
231 fIndices.size(),
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +0000232 fIndices.data());
Ruiqi Maof5101492018-06-29 14:32:21 -0400233 }
234
235 void drawVerticesObject(SkCanvas* canvas, bool useBones) const {
236 // Determine the blend mode.
237 SkBlendMode blendMode;
238 switch (fActorImage->blendMode()) {
239 case BlendMode::Off: {
240 blendMode = SkBlendMode::kSrc;
241 break;
242 }
243 case BlendMode::Normal: {
244 blendMode = SkBlendMode::kSrcOver;
245 break;
246 }
247 case BlendMode::Additive: {
248 blendMode = SkBlendMode::kPlus;
249 break;
250 }
251 case BlendMode::Multiply: {
252 blendMode = SkBlendMode::kMultiply;
253 break;
254 }
255 case BlendMode::Screen: {
256 blendMode = SkBlendMode::kScreen;
257 break;
258 }
259 }
260
261 // Set the opacity.
262 fPaint->setAlpha(static_cast<U8CPU>(fActorImage->renderOpacity() * 255));
263
264 // Draw the vertices.
265 if (useBones) {
266 canvas->drawVertices(fVertices, fBones.data(), fBones.size(), blendMode, *fPaint);
267 } else {
268 canvas->drawVertices(fVertices, blendMode, *fPaint);
269 }
270
271 // Reset the opacity.
272 fPaint->setAlpha(255);
273 }
274
275 Vec2D deform(const Vec2D& position, uint32_t* boneIdx, float* boneWgt) const {
276 float px = position[0], py = position[1];
277 float px2 = px, py2 = py;
278 float influence[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
279
280 // Apply the world transform.
281 Mat2D worldTransform = fActorImage->worldTransform();
282 px2 = worldTransform[0] * px + worldTransform[2] * py + worldTransform[4];
283 py2 = worldTransform[1] * px + worldTransform[3] * py + worldTransform[5];
284
285 // Apply deformations based on bone offsets.
286 if (boneIdx && boneWgt) {
287 float* matrices = fActorImage->boneInfluenceMatrices();
288
289 for (uint32_t i = 0; i < 4; i ++) {
290 uint32_t index = boneIdx[i];
291 float weight = boneWgt[i];
292 for (int j = 0; j < 6; j ++) {
293 influence[j] += matrices[index * 6 + j] * weight;
294 }
295 }
296
297 px = influence[0] * px2 + influence[2] * py2 + influence[4];
298 py = influence[1] * px2 + influence[3] * py2 + influence[5];
299 } else {
300 px = px2;
301 py = py2;
302 }
303
304 // Return the transformed position.
305 return Vec2D(px, py);
306 }
307
308private:
309 ActorImage* fActorImage;
310 SkImage* fTexture;
311 SkPaint* fPaint;
312
313 bool fSkinned;
314 std::vector<SkPoint> fPositions;
315 std::vector<SkPoint> fTexs;
316 std::vector<SkVertices::BoneIndices> fBoneIdx;
317 std::vector<SkVertices::BoneWeights> fBoneWgt;
318 std::vector<uint16_t> fIndices;
319
320 std::vector<SkMatrix> fBones;
321 sk_sp<SkVertices> fVertices;
322
323 RenderMode fRenderMode;
324};
325
326//////////////////////////////////////////////////////////////////////////////////////////////////
327
328// Represents an Actor, or an animated character, in NIMA.
329class NIMAActor : public Actor {
330public:
331 NIMAActor(const std::string& basePath)
332 : fTexture(nullptr)
333 , fActorImages()
334 , fPaint()
335 , fAnimations() {
336 // Load the NIMA data.
337 std::string nimaPath((basePath + ".nima").c_str());
338 INHERITED::load(nimaPath);
339
340 // Load the image asset.
341 sk_sp<SkData> imageData = SkData::MakeFromFileName((basePath + ".png").c_str());
342 fTexture = SkImage::MakeFromEncoded(imageData);
343
344 // Create the paint.
345 fPaint.setShader(fTexture->makeShader(nullptr));
346 fPaint.setFilterQuality(SkFilterQuality::kLow_SkFilterQuality);
347
348 // Load the image nodes.
349 fActorImages.reserve(m_ImageNodeCount);
350 for (uint32_t i = 0; i < m_ImageNodeCount; i ++) {
351 fActorImages.emplace_back(m_ImageNodes[i], fTexture.get(), &fPaint);
352 }
353
354 // Sort the image nodes.
355 std::sort(fActorImages.begin(), fActorImages.end(), [](auto a, auto b) {
356 return a.drawOrder() < b.drawOrder();
357 });
358
359 // Get the list of animations.
360 fAnimations.reserve(m_AnimationsCount);
361 for (uint32_t i = 0; i < m_AnimationsCount; i ++) {
362 fAnimations.push_back(m_Animations[i].name());
363 }
364 }
365
366 void render(SkCanvas* canvas, RenderMode renderMode) {
367 // Render the image nodes.
368 for (auto& image : fActorImages) {
369 switch (renderMode) {
370 case kBackend_RenderMode: {
371 // Render with Skia backend.
372 image.renderBackend(canvas);
373 break;
374 }
375 case kImmediate_RenderMode: {
376 // Render with immediate backend.
377 image.renderImmediate(canvas);
378 break;
379 }
380 }
381 }
382 }
383
384 const std::vector<std::string>& getAnimations() const {
385 return fAnimations;
386 }
387
388private:
389 sk_sp<SkImage> fTexture;
390 std::vector<NIMAActorImage> fActorImages;
391 SkPaint fPaint;
392 std::vector<std::string> fAnimations;
393
394 typedef Actor INHERITED;
395};
396
397//////////////////////////////////////////////////////////////////////////////////////////////////
398
399NIMASlide::NIMASlide(const SkString& name, const SkString& path)
400 : fBasePath()
401 , fActor(nullptr)
402 , fPlaying(true)
403 , fTime(0.0f)
404 , fRenderMode(kBackend_RenderMode)
405 , fAnimation(nullptr)
406 , fAnimationIndex(0) {
407 fName = name;
408
409 // Get the path components.
410 SkString baseName = SkOSPath::Basename(path.c_str());
411 baseName.resize(baseName.size() - 5);
412 SkString dirName = SkOSPath::Dirname(path.c_str());
413 SkString basePath = SkOSPath::Join(dirName.c_str(), baseName.c_str());
414
415 // Save the base path.
416 fBasePath = std::string(basePath.c_str());
417}
418
419NIMASlide::~NIMASlide() {}
420
421void NIMASlide::draw(SkCanvas* canvas) {
422 canvas->save();
423
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +0000424 canvas->translate(500, 500);
425 canvas->scale(1, -1);
Ruiqi Maof5101492018-06-29 14:32:21 -0400426
Ruiqi Maoc05aa7d2018-07-03 21:18:07 +0000427 // Render the actor.
428 fActor->render(canvas, fRenderMode);
Ruiqi Maof5101492018-06-29 14:32:21 -0400429
430 canvas->restore();
431
432 // Render the GUI.
433 this->renderGUI();
434}
435
436void NIMASlide::load(SkScalar winWidth, SkScalar winHeight) {
437 this->resetActor();
438}
439
440void NIMASlide::unload() {
441 // Discard resources.
442 fAnimation = nullptr;
443 fActor.reset(nullptr);
444}
445
446bool NIMASlide::animate(const SkAnimTimer& timer) {
447 // Apply the animation.
448 if (fAnimation) {
449 if (fPlaying) {
450 fTime = std::fmod(timer.secs(), fAnimation->max());
451 }
452 fAnimation->time(fTime);
453 fAnimation->apply(1.0f);
454 }
455 return true;
456}
457
458bool NIMASlide::onChar(SkUnichar c) {
459 return false;
460}
461
462bool NIMASlide::onMouse(SkScalar x, SkScalar y, Window::InputState state, uint32_t modifiers) {
463 return false;
464}
465
466void NIMASlide::resetActor() {
467 // Create the actor.
468 fActor = std::make_unique<NIMAActor>(fBasePath);
469
470 // Get the animation.
471 fAnimation = fActor->animationInstance(fActor->getAnimations()[fAnimationIndex]);
472}
473
474void NIMASlide::renderGUI() {
475 ImGui::SetNextWindowSize(ImVec2(300, 220));
476 ImGui::Begin("NIMA");
477
478 // List of animations.
479 auto animations = const_cast<std::vector<std::string>&>(fActor->getAnimations());
480 ImGui::PushItemWidth(-1);
481 if (ImGui::ListBox("Animations",
482 &fAnimationIndex,
483 vector_getter,
484 reinterpret_cast<void*>(&animations),
485 animations.size(),
486 5)) {
487 resetActor();
488 }
489
490 // Playback control.
491 ImGui::Spacing();
492 if (ImGui::Button("Play")) {
493 fPlaying = true;
494 }
495 ImGui::SameLine();
496 if (ImGui::Button("Pause")) {
497 fPlaying = false;
498 }
499
500 // Time slider.
501 ImGui::PushItemWidth(-1);
502 ImGui::SliderFloat("Time", &fTime, 0.0f, fAnimation->max(), "Time: %.3f");
503
504 // Backend control.
505 int renderMode = fRenderMode;
506 ImGui::Spacing();
507 ImGui::RadioButton("Skia Backend", &renderMode, 0);
508 ImGui::RadioButton("Immediate Backend", &renderMode, 1);
509 if (renderMode == 0) {
510 fRenderMode = kBackend_RenderMode;
511 } else {
512 fRenderMode = kImmediate_RenderMode;
513 }
514
515 ImGui::End();
516}