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