blob: 25d023f67b78caebbbd2606c8ff27ebfd9b71047 [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 Mao9a6e42f2018-07-09 14:16:56 -040065 this->updateVerticesObject(false, 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 Mao9a6e42f2018-07-09 14:16:56 -040073 this->updateVerticesObject(false, false);
Ruiqi Maof5101492018-06-29 14:32:21 -040074 }
75
Ruiqi Maof5101492018-06-29 14:32:21 -040076 // Update the vertex data.
Ruiqi Mao9a6e42f2018-07-09 14:16:56 -040077 if (fActorImage->doesAnimationVertexDeform()) {
Ruiqi Maof5101492018-06-29 14:32:21 -040078 this->updateVertices();
Ruiqi Mao9a6e42f2018-07-09 14:16:56 -040079 this->updateVerticesObject(false, true);
Ruiqi Maof5101492018-06-29 14:32:21 -040080 }
81
82 // Update the bones.
83 this->updateBones();
84
85 // Draw the vertices object.
86 this->drawVerticesObject(canvas, true);
Ruiqi Maof5101492018-06-29 14:32:21 -040087 }
88
89 void renderImmediate(SkCanvas* canvas) {
90 // Reset vertices if the render mode has changed.
91 if (fRenderMode != kImmediate_RenderMode) {
92 fRenderMode = kImmediate_RenderMode;
93 this->updateVertices();
Ruiqi Mao9a6e42f2018-07-09 14:16:56 -040094 this->updateVerticesObject(true, true);
Ruiqi Maof5101492018-06-29 14:32:21 -040095 }
96
97 // Update the vertex data.
98 if (fActorImage->doesAnimationVertexDeform() && fActorImage->isVertexDeformDirty()) {
99 this->updateVertices();
100 fActorImage->isVertexDeformDirty(false);
101 }
102
103 // Update the vertices object.
Ruiqi Mao9a6e42f2018-07-09 14:16:56 -0400104 this->updateVerticesObject(true, true);
Ruiqi Maof5101492018-06-29 14:32:21 -0400105
106 // Draw the vertices object.
107 this->drawVerticesObject(canvas, false);
108 }
109
110 int drawOrder() const { return fActorImage->drawOrder(); }
111
112private:
113 void updateVertices() {
114 // Update whether the image is skinned.
115 fSkinned = fActorImage->connectedBoneCount() > 0;
116
117 // Retrieve data from the image.
118 uint32_t vertexCount = fActorImage->vertexCount();
119 uint32_t vertexStride = fActorImage->vertexStride();
120 float* vertexData = fActorImage->vertices();
121 uint32_t indexCount = fActorImage->triangleCount() * 3;
122 uint16_t* indexData = fActorImage->triangles();
123
124 // Don't render if not visible.
125 if (!vertexCount || fActorImage->textureIndex() < 0) {
126 fPositions.clear();
127 fTexs.clear();
128 fBoneIdx.clear();
129 fBoneWgt.clear();
130 fIndices.clear();
131 return;
132 }
133
134 // Split the vertex data.
135 fPositions.resize(vertexCount);
136 fTexs.resize(vertexCount);
137 fIndices.resize(indexCount);
138 if (fSkinned) {
139 fBoneIdx.resize(vertexCount * 4);
140 fBoneWgt.resize(vertexCount * 4);
141 }
142 for (uint32_t i = 0; i < vertexCount; i ++) {
143 uint32_t j = i * vertexStride;
144
145 // Get the attributes.
146 float* attrPosition = vertexData + j;
147 float* attrTex = vertexData + j + 2;
148 float* attrBoneIdx = vertexData + j + 4;
149 float* attrBoneWgt = vertexData + j + 8;
150
151 // Get deformed positions if necessary.
152 if (fActorImage->doesAnimationVertexDeform()) {
153 attrPosition = fActorImage->animationDeformedVertices() + i * 2;
154 }
155
156 // Set the data.
157 fPositions[i].set(attrPosition[0], attrPosition[1]);
158 fTexs[i].set(attrTex[0] * fTexture->width(), attrTex[1] * fTexture->height());
159 if (fSkinned) {
160 for (uint32_t k = 0; k < 4; k ++) {
161 fBoneIdx[i].indices[k] = static_cast<uint32_t>(attrBoneIdx[k]);
162 fBoneWgt[i].weights[k] = attrBoneWgt[k];
163 }
164 }
165 }
166 memcpy(fIndices.data(), indexData, indexCount * sizeof(uint16_t));
167 }
168
169 void updateBones() {
170 // NIMA matrices are a collection of 6 floats.
171 constexpr int kNIMAMatrixSize = 6;
172
173 // Set up the matrices for the first time.
174 if (fBones.size() == 0) {
175 int numMatrices = 1;
176 if (fSkinned) {
177 numMatrices = fActorImage->boneInfluenceMatricesLength() / kNIMAMatrixSize;
178 }
179 fBones.assign(numMatrices, SkMatrix());
180 }
181
182 if (fSkinned) {
183 // Update the matrices.
184 float* matrixData = fActorImage->boneInfluenceMatrices();
185 for (uint32_t i = 1; i < fBones.size(); i ++) {
186 SkMatrix& matrix = fBones[i];
187 float* data = matrixData + i * kNIMAMatrixSize;
188 nima_to_skmatrix(data, matrix);
189 }
190 }
191
192 // Set the zero matrix to be the world transform.
193 nima_to_skmatrix(fActorImage->worldTransform().values(), fBones[0]);
194 }
195
Ruiqi Mao9a6e42f2018-07-09 14:16:56 -0400196 void updateVerticesObject(bool applyDeforms, bool isVolatile) {
Ruiqi Maof5101492018-06-29 14:32:21 -0400197 std::vector<SkPoint>* positions = &fPositions;
198
199 // Apply deforms if requested.
200 uint32_t vertexCount = fPositions.size();
201 std::vector<SkPoint> deformedPositions;
202 if (applyDeforms) {
203 positions = &deformedPositions;
204 deformedPositions.reserve(vertexCount);
205 for (uint32_t i = 0; i < vertexCount; i ++) {
206 Vec2D nimaPoint(fPositions[i].x(), fPositions[i].y());
207 uint32_t* boneIdx = nullptr;
208 float* boneWgt = nullptr;
209 if (fSkinned) {
210 boneIdx = fBoneIdx[i].indices;
211 boneWgt = fBoneWgt[i].weights;
212 }
213 nimaPoint = this->deform(nimaPoint, boneIdx, boneWgt);
214 deformedPositions.push_back(SkPoint::Make(nimaPoint[0], nimaPoint[1]));
215 }
216 }
217
218 // Update the vertices object.
219 fVertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
220 vertexCount,
221 positions->data(),
222 fTexs.data(),
223 nullptr,
224 fBoneIdx.data(),
225 fBoneWgt.data(),
226 fIndices.size(),
Ruiqi Mao9a6e42f2018-07-09 14:16:56 -0400227 fIndices.data(),
228 isVolatile);
Ruiqi Maof5101492018-06-29 14:32:21 -0400229 }
230
231 void drawVerticesObject(SkCanvas* canvas, bool useBones) const {
232 // Determine the blend mode.
233 SkBlendMode blendMode;
234 switch (fActorImage->blendMode()) {
235 case BlendMode::Off: {
236 blendMode = SkBlendMode::kSrc;
237 break;
238 }
239 case BlendMode::Normal: {
240 blendMode = SkBlendMode::kSrcOver;
241 break;
242 }
243 case BlendMode::Additive: {
244 blendMode = SkBlendMode::kPlus;
245 break;
246 }
247 case BlendMode::Multiply: {
248 blendMode = SkBlendMode::kMultiply;
249 break;
250 }
251 case BlendMode::Screen: {
252 blendMode = SkBlendMode::kScreen;
253 break;
254 }
255 }
256
257 // Set the opacity.
258 fPaint->setAlpha(static_cast<U8CPU>(fActorImage->renderOpacity() * 255));
259
260 // Draw the vertices.
261 if (useBones) {
262 canvas->drawVertices(fVertices, fBones.data(), fBones.size(), blendMode, *fPaint);
263 } else {
264 canvas->drawVertices(fVertices, blendMode, *fPaint);
265 }
266
267 // Reset the opacity.
268 fPaint->setAlpha(255);
269 }
270
271 Vec2D deform(const Vec2D& position, uint32_t* boneIdx, float* boneWgt) const {
272 float px = position[0], py = position[1];
273 float px2 = px, py2 = py;
274 float influence[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
275
276 // Apply the world transform.
277 Mat2D worldTransform = fActorImage->worldTransform();
278 px2 = worldTransform[0] * px + worldTransform[2] * py + worldTransform[4];
279 py2 = worldTransform[1] * px + worldTransform[3] * py + worldTransform[5];
280
281 // Apply deformations based on bone offsets.
282 if (boneIdx && boneWgt) {
283 float* matrices = fActorImage->boneInfluenceMatrices();
284
285 for (uint32_t i = 0; i < 4; i ++) {
286 uint32_t index = boneIdx[i];
287 float weight = boneWgt[i];
288 for (int j = 0; j < 6; j ++) {
289 influence[j] += matrices[index * 6 + j] * weight;
290 }
291 }
292
293 px = influence[0] * px2 + influence[2] * py2 + influence[4];
294 py = influence[1] * px2 + influence[3] * py2 + influence[5];
295 } else {
296 px = px2;
297 py = py2;
298 }
299
300 // Return the transformed position.
301 return Vec2D(px, py);
302 }
303
304private:
305 ActorImage* fActorImage;
306 SkImage* fTexture;
307 SkPaint* fPaint;
308
309 bool fSkinned;
310 std::vector<SkPoint> fPositions;
311 std::vector<SkPoint> fTexs;
312 std::vector<SkVertices::BoneIndices> fBoneIdx;
313 std::vector<SkVertices::BoneWeights> fBoneWgt;
314 std::vector<uint16_t> fIndices;
315
316 std::vector<SkMatrix> fBones;
317 sk_sp<SkVertices> fVertices;
318
319 RenderMode fRenderMode;
320};
321
322//////////////////////////////////////////////////////////////////////////////////////////////////
323
324// Represents an Actor, or an animated character, in NIMA.
325class NIMAActor : public Actor {
326public:
327 NIMAActor(const std::string& basePath)
328 : fTexture(nullptr)
329 , fActorImages()
330 , fPaint()
331 , fAnimations() {
332 // Load the NIMA data.
333 std::string nimaPath((basePath + ".nima").c_str());
334 INHERITED::load(nimaPath);
335
336 // Load the image asset.
337 sk_sp<SkData> imageData = SkData::MakeFromFileName((basePath + ".png").c_str());
338 fTexture = SkImage::MakeFromEncoded(imageData);
339
340 // Create the paint.
341 fPaint.setShader(fTexture->makeShader(nullptr));
342 fPaint.setFilterQuality(SkFilterQuality::kLow_SkFilterQuality);
343
344 // Load the image nodes.
345 fActorImages.reserve(m_ImageNodeCount);
346 for (uint32_t i = 0; i < m_ImageNodeCount; i ++) {
347 fActorImages.emplace_back(m_ImageNodes[i], fTexture.get(), &fPaint);
348 }
349
350 // Sort the image nodes.
351 std::sort(fActorImages.begin(), fActorImages.end(), [](auto a, auto b) {
352 return a.drawOrder() < b.drawOrder();
353 });
354
355 // Get the list of animations.
356 fAnimations.reserve(m_AnimationsCount);
357 for (uint32_t i = 0; i < m_AnimationsCount; i ++) {
358 fAnimations.push_back(m_Animations[i].name());
359 }
360 }
361
362 void render(SkCanvas* canvas, RenderMode renderMode) {
363 // Render the image nodes.
364 for (auto& image : fActorImages) {
365 switch (renderMode) {
366 case kBackend_RenderMode: {
367 // Render with Skia backend.
368 image.renderBackend(canvas);
369 break;
370 }
371 case kImmediate_RenderMode: {
372 // Render with immediate backend.
373 image.renderImmediate(canvas);
374 break;
375 }
376 }
377 }
378 }
379
380 const std::vector<std::string>& getAnimations() const {
381 return fAnimations;
382 }
383
384private:
385 sk_sp<SkImage> fTexture;
386 std::vector<NIMAActorImage> fActorImages;
387 SkPaint fPaint;
388 std::vector<std::string> fAnimations;
389
390 typedef Actor INHERITED;
391};
392
393//////////////////////////////////////////////////////////////////////////////////////////////////
394
395NIMASlide::NIMASlide(const SkString& name, const SkString& path)
396 : fBasePath()
397 , fActor(nullptr)
398 , fPlaying(true)
399 , fTime(0.0f)
400 , fRenderMode(kBackend_RenderMode)
401 , fAnimation(nullptr)
402 , fAnimationIndex(0) {
403 fName = name;
404
405 // Get the path components.
406 SkString baseName = SkOSPath::Basename(path.c_str());
407 baseName.resize(baseName.size() - 5);
408 SkString dirName = SkOSPath::Dirname(path.c_str());
409 SkString basePath = SkOSPath::Join(dirName.c_str(), baseName.c_str());
410
411 // Save the base path.
412 fBasePath = std::string(basePath.c_str());
413}
414
415NIMASlide::~NIMASlide() {}
416
417void NIMASlide::draw(SkCanvas* canvas) {
418 canvas->save();
419
Ruiqi Mao9a6e42f2018-07-09 14:16:56 -0400420 for (int i = 0; i < 10; i ++) {
421 for (int j = 0; j < 10; j ++) {
422 canvas->save();
Ruiqi Maof5101492018-06-29 14:32:21 -0400423
Ruiqi Mao9a6e42f2018-07-09 14:16:56 -0400424 canvas->translate(1250 - 250 * i, 1250 - 250 * j);
425 canvas->scale(0.5, -0.5);
426
427 // Render the actor.
428 fActor->render(canvas, fRenderMode);
429
430 canvas->restore();
431 }
432 }
Ruiqi Maof5101492018-06-29 14:32:21 -0400433
434 canvas->restore();
435
436 // Render the GUI.
437 this->renderGUI();
438}
439
440void NIMASlide::load(SkScalar winWidth, SkScalar winHeight) {
441 this->resetActor();
442}
443
444void NIMASlide::unload() {
445 // Discard resources.
446 fAnimation = nullptr;
447 fActor.reset(nullptr);
448}
449
450bool NIMASlide::animate(const SkAnimTimer& timer) {
451 // Apply the animation.
452 if (fAnimation) {
453 if (fPlaying) {
454 fTime = std::fmod(timer.secs(), fAnimation->max());
455 }
456 fAnimation->time(fTime);
457 fAnimation->apply(1.0f);
458 }
459 return true;
460}
461
462bool NIMASlide::onChar(SkUnichar c) {
463 return false;
464}
465
466bool NIMASlide::onMouse(SkScalar x, SkScalar y, Window::InputState state, uint32_t modifiers) {
467 return false;
468}
469
470void NIMASlide::resetActor() {
471 // Create the actor.
472 fActor = std::make_unique<NIMAActor>(fBasePath);
473
474 // Get the animation.
475 fAnimation = fActor->animationInstance(fActor->getAnimations()[fAnimationIndex]);
476}
477
478void NIMASlide::renderGUI() {
479 ImGui::SetNextWindowSize(ImVec2(300, 220));
480 ImGui::Begin("NIMA");
481
482 // List of animations.
483 auto animations = const_cast<std::vector<std::string>&>(fActor->getAnimations());
484 ImGui::PushItemWidth(-1);
485 if (ImGui::ListBox("Animations",
486 &fAnimationIndex,
487 vector_getter,
488 reinterpret_cast<void*>(&animations),
489 animations.size(),
490 5)) {
491 resetActor();
492 }
493
494 // Playback control.
495 ImGui::Spacing();
496 if (ImGui::Button("Play")) {
497 fPlaying = true;
498 }
499 ImGui::SameLine();
500 if (ImGui::Button("Pause")) {
501 fPlaying = false;
502 }
503
504 // Time slider.
505 ImGui::PushItemWidth(-1);
506 ImGui::SliderFloat("Time", &fTime, 0.0f, fAnimation->max(), "Time: %.3f");
507
508 // Backend control.
509 int renderMode = fRenderMode;
510 ImGui::Spacing();
511 ImGui::RadioButton("Skia Backend", &renderMode, 0);
512 ImGui::RadioButton("Immediate Backend", &renderMode, 1);
513 if (renderMode == 0) {
514 fRenderMode = kBackend_RenderMode;
515 } else {
516 fRenderMode = kImmediate_RenderMode;
517 }
518
519 ImGui::End();
520}