9a6e42ff18
SkVertices can now be "volatile", meaning they should not be cached. SkVertices is volatile by default if the argument is not given. Pulled from reverted CL: https://skia-review.googlesource.com/c/skia/+/138596 Docs-Preview: https://skia.org/?cl=139545 Bug: skia: Change-Id: I92cf832efe1c0aaa8f432eedde2678582dd2454e Reviewed-on: https://skia-review.googlesource.com/139545 Reviewed-by: Brian Osman <brianosman@google.com> Reviewed-by: Robert Phillips <robertphillips@google.com> Commit-Queue: Ruiqi Mao <ruiqimao@google.com>
521 lines
16 KiB
C++
521 lines
16 KiB
C++
/*
|
|
* Copyright 2018 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "NIMASlide.h"
|
|
|
|
#include "SkAnimTimer.h"
|
|
#include "SkOSPath.h"
|
|
#include "Resources.h"
|
|
#include "imgui.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
using namespace sk_app;
|
|
using namespace nima;
|
|
|
|
// NIMA stores its matrices as 6 floats to represent translation and scale. This function takes
|
|
// that format and converts it into a 3x3 matrix representation.
|
|
static void nima_to_skmatrix(const float* nimaData, SkMatrix& matrix) {
|
|
matrix[0] = nimaData[0];
|
|
matrix[1] = nimaData[2];
|
|
matrix[2] = nimaData[4];
|
|
matrix[3] = nimaData[1];
|
|
matrix[4] = nimaData[3];
|
|
matrix[5] = nimaData[5];
|
|
matrix[6] = 0.0f;
|
|
matrix[7] = 0.0f;
|
|
matrix[8] = 1.0f;
|
|
}
|
|
|
|
// ImGui expects an array of const char* when displaying a ListBox. This function is for an
|
|
// overload of ImGui::ListBox that takes a getter so that ListBox works with
|
|
// std::vector<std::string>.
|
|
static bool vector_getter(void* v, int index, const char** out) {
|
|
auto vector = reinterpret_cast<std::vector<std::string>*>(v);
|
|
*out = vector->at(index).c_str();
|
|
return true;
|
|
}
|
|
|
|
// A wrapper class that handles rendering of ActorImages (renderable components NIMA Actors).
|
|
class NIMAActorImage {
|
|
public:
|
|
NIMAActorImage(ActorImage* actorImage, SkImage* texture, SkPaint* paint)
|
|
: fActorImage(actorImage)
|
|
, fTexture(texture)
|
|
, fPaint(paint)
|
|
, fSkinned(false)
|
|
, fPositions()
|
|
, fTexs()
|
|
, fBoneIdx()
|
|
, fBoneWgt()
|
|
, fIndices()
|
|
, fBones()
|
|
, fVertices(nullptr)
|
|
, fRenderMode(kBackend_RenderMode) {
|
|
// Update the vertices and bones.
|
|
this->updateVertices();
|
|
this->updateBones();
|
|
|
|
// Update the vertices object.
|
|
this->updateVerticesObject(false, false);
|
|
}
|
|
|
|
void renderBackend(SkCanvas* canvas) {
|
|
// Reset vertices if the render mode has changed.
|
|
if (fRenderMode != kBackend_RenderMode) {
|
|
fRenderMode = kBackend_RenderMode;
|
|
this->updateVertices();
|
|
this->updateVerticesObject(false, false);
|
|
}
|
|
|
|
// Update the vertex data.
|
|
if (fActorImage->doesAnimationVertexDeform()) {
|
|
this->updateVertices();
|
|
this->updateVerticesObject(false, true);
|
|
}
|
|
|
|
// Update the bones.
|
|
this->updateBones();
|
|
|
|
// Draw the vertices object.
|
|
this->drawVerticesObject(canvas, true);
|
|
}
|
|
|
|
void renderImmediate(SkCanvas* canvas) {
|
|
// Reset vertices if the render mode has changed.
|
|
if (fRenderMode != kImmediate_RenderMode) {
|
|
fRenderMode = kImmediate_RenderMode;
|
|
this->updateVertices();
|
|
this->updateVerticesObject(true, true);
|
|
}
|
|
|
|
// Update the vertex data.
|
|
if (fActorImage->doesAnimationVertexDeform() && fActorImage->isVertexDeformDirty()) {
|
|
this->updateVertices();
|
|
fActorImage->isVertexDeformDirty(false);
|
|
}
|
|
|
|
// Update the vertices object.
|
|
this->updateVerticesObject(true, true);
|
|
|
|
// Draw the vertices object.
|
|
this->drawVerticesObject(canvas, false);
|
|
}
|
|
|
|
int drawOrder() const { return fActorImage->drawOrder(); }
|
|
|
|
private:
|
|
void updateVertices() {
|
|
// Update whether the image is skinned.
|
|
fSkinned = fActorImage->connectedBoneCount() > 0;
|
|
|
|
// Retrieve data from the image.
|
|
uint32_t vertexCount = fActorImage->vertexCount();
|
|
uint32_t vertexStride = fActorImage->vertexStride();
|
|
float* vertexData = fActorImage->vertices();
|
|
uint32_t indexCount = fActorImage->triangleCount() * 3;
|
|
uint16_t* indexData = fActorImage->triangles();
|
|
|
|
// Don't render if not visible.
|
|
if (!vertexCount || fActorImage->textureIndex() < 0) {
|
|
fPositions.clear();
|
|
fTexs.clear();
|
|
fBoneIdx.clear();
|
|
fBoneWgt.clear();
|
|
fIndices.clear();
|
|
return;
|
|
}
|
|
|
|
// Split the vertex data.
|
|
fPositions.resize(vertexCount);
|
|
fTexs.resize(vertexCount);
|
|
fIndices.resize(indexCount);
|
|
if (fSkinned) {
|
|
fBoneIdx.resize(vertexCount * 4);
|
|
fBoneWgt.resize(vertexCount * 4);
|
|
}
|
|
for (uint32_t i = 0; i < vertexCount; i ++) {
|
|
uint32_t j = i * vertexStride;
|
|
|
|
// Get the attributes.
|
|
float* attrPosition = vertexData + j;
|
|
float* attrTex = vertexData + j + 2;
|
|
float* attrBoneIdx = vertexData + j + 4;
|
|
float* attrBoneWgt = vertexData + j + 8;
|
|
|
|
// Get deformed positions if necessary.
|
|
if (fActorImage->doesAnimationVertexDeform()) {
|
|
attrPosition = fActorImage->animationDeformedVertices() + i * 2;
|
|
}
|
|
|
|
// Set the data.
|
|
fPositions[i].set(attrPosition[0], attrPosition[1]);
|
|
fTexs[i].set(attrTex[0] * fTexture->width(), attrTex[1] * fTexture->height());
|
|
if (fSkinned) {
|
|
for (uint32_t k = 0; k < 4; k ++) {
|
|
fBoneIdx[i].indices[k] = static_cast<uint32_t>(attrBoneIdx[k]);
|
|
fBoneWgt[i].weights[k] = attrBoneWgt[k];
|
|
}
|
|
}
|
|
}
|
|
memcpy(fIndices.data(), indexData, indexCount * sizeof(uint16_t));
|
|
}
|
|
|
|
void updateBones() {
|
|
// NIMA matrices are a collection of 6 floats.
|
|
constexpr int kNIMAMatrixSize = 6;
|
|
|
|
// Set up the matrices for the first time.
|
|
if (fBones.size() == 0) {
|
|
int numMatrices = 1;
|
|
if (fSkinned) {
|
|
numMatrices = fActorImage->boneInfluenceMatricesLength() / kNIMAMatrixSize;
|
|
}
|
|
fBones.assign(numMatrices, SkMatrix());
|
|
}
|
|
|
|
if (fSkinned) {
|
|
// Update the matrices.
|
|
float* matrixData = fActorImage->boneInfluenceMatrices();
|
|
for (uint32_t i = 1; i < fBones.size(); i ++) {
|
|
SkMatrix& matrix = fBones[i];
|
|
float* data = matrixData + i * kNIMAMatrixSize;
|
|
nima_to_skmatrix(data, matrix);
|
|
}
|
|
}
|
|
|
|
// Set the zero matrix to be the world transform.
|
|
nima_to_skmatrix(fActorImage->worldTransform().values(), fBones[0]);
|
|
}
|
|
|
|
void updateVerticesObject(bool applyDeforms, bool isVolatile) {
|
|
std::vector<SkPoint>* positions = &fPositions;
|
|
|
|
// Apply deforms if requested.
|
|
uint32_t vertexCount = fPositions.size();
|
|
std::vector<SkPoint> deformedPositions;
|
|
if (applyDeforms) {
|
|
positions = &deformedPositions;
|
|
deformedPositions.reserve(vertexCount);
|
|
for (uint32_t i = 0; i < vertexCount; i ++) {
|
|
Vec2D nimaPoint(fPositions[i].x(), fPositions[i].y());
|
|
uint32_t* boneIdx = nullptr;
|
|
float* boneWgt = nullptr;
|
|
if (fSkinned) {
|
|
boneIdx = fBoneIdx[i].indices;
|
|
boneWgt = fBoneWgt[i].weights;
|
|
}
|
|
nimaPoint = this->deform(nimaPoint, boneIdx, boneWgt);
|
|
deformedPositions.push_back(SkPoint::Make(nimaPoint[0], nimaPoint[1]));
|
|
}
|
|
}
|
|
|
|
// Update the vertices object.
|
|
fVertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
|
|
vertexCount,
|
|
positions->data(),
|
|
fTexs.data(),
|
|
nullptr,
|
|
fBoneIdx.data(),
|
|
fBoneWgt.data(),
|
|
fIndices.size(),
|
|
fIndices.data(),
|
|
isVolatile);
|
|
}
|
|
|
|
void drawVerticesObject(SkCanvas* canvas, bool useBones) const {
|
|
// Determine the blend mode.
|
|
SkBlendMode blendMode;
|
|
switch (fActorImage->blendMode()) {
|
|
case BlendMode::Off: {
|
|
blendMode = SkBlendMode::kSrc;
|
|
break;
|
|
}
|
|
case BlendMode::Normal: {
|
|
blendMode = SkBlendMode::kSrcOver;
|
|
break;
|
|
}
|
|
case BlendMode::Additive: {
|
|
blendMode = SkBlendMode::kPlus;
|
|
break;
|
|
}
|
|
case BlendMode::Multiply: {
|
|
blendMode = SkBlendMode::kMultiply;
|
|
break;
|
|
}
|
|
case BlendMode::Screen: {
|
|
blendMode = SkBlendMode::kScreen;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Set the opacity.
|
|
fPaint->setAlpha(static_cast<U8CPU>(fActorImage->renderOpacity() * 255));
|
|
|
|
// Draw the vertices.
|
|
if (useBones) {
|
|
canvas->drawVertices(fVertices, fBones.data(), fBones.size(), blendMode, *fPaint);
|
|
} else {
|
|
canvas->drawVertices(fVertices, blendMode, *fPaint);
|
|
}
|
|
|
|
// Reset the opacity.
|
|
fPaint->setAlpha(255);
|
|
}
|
|
|
|
Vec2D deform(const Vec2D& position, uint32_t* boneIdx, float* boneWgt) const {
|
|
float px = position[0], py = position[1];
|
|
float px2 = px, py2 = py;
|
|
float influence[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
|
|
|
|
// Apply the world transform.
|
|
Mat2D worldTransform = fActorImage->worldTransform();
|
|
px2 = worldTransform[0] * px + worldTransform[2] * py + worldTransform[4];
|
|
py2 = worldTransform[1] * px + worldTransform[3] * py + worldTransform[5];
|
|
|
|
// Apply deformations based on bone offsets.
|
|
if (boneIdx && boneWgt) {
|
|
float* matrices = fActorImage->boneInfluenceMatrices();
|
|
|
|
for (uint32_t i = 0; i < 4; i ++) {
|
|
uint32_t index = boneIdx[i];
|
|
float weight = boneWgt[i];
|
|
for (int j = 0; j < 6; j ++) {
|
|
influence[j] += matrices[index * 6 + j] * weight;
|
|
}
|
|
}
|
|
|
|
px = influence[0] * px2 + influence[2] * py2 + influence[4];
|
|
py = influence[1] * px2 + influence[3] * py2 + influence[5];
|
|
} else {
|
|
px = px2;
|
|
py = py2;
|
|
}
|
|
|
|
// Return the transformed position.
|
|
return Vec2D(px, py);
|
|
}
|
|
|
|
private:
|
|
ActorImage* fActorImage;
|
|
SkImage* fTexture;
|
|
SkPaint* fPaint;
|
|
|
|
bool fSkinned;
|
|
std::vector<SkPoint> fPositions;
|
|
std::vector<SkPoint> fTexs;
|
|
std::vector<SkVertices::BoneIndices> fBoneIdx;
|
|
std::vector<SkVertices::BoneWeights> fBoneWgt;
|
|
std::vector<uint16_t> fIndices;
|
|
|
|
std::vector<SkMatrix> fBones;
|
|
sk_sp<SkVertices> fVertices;
|
|
|
|
RenderMode fRenderMode;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Represents an Actor, or an animated character, in NIMA.
|
|
class NIMAActor : public Actor {
|
|
public:
|
|
NIMAActor(const std::string& basePath)
|
|
: fTexture(nullptr)
|
|
, fActorImages()
|
|
, fPaint()
|
|
, fAnimations() {
|
|
// Load the NIMA data.
|
|
std::string nimaPath((basePath + ".nima").c_str());
|
|
INHERITED::load(nimaPath);
|
|
|
|
// Load the image asset.
|
|
sk_sp<SkData> imageData = SkData::MakeFromFileName((basePath + ".png").c_str());
|
|
fTexture = SkImage::MakeFromEncoded(imageData);
|
|
|
|
// Create the paint.
|
|
fPaint.setShader(fTexture->makeShader(nullptr));
|
|
fPaint.setFilterQuality(SkFilterQuality::kLow_SkFilterQuality);
|
|
|
|
// Load the image nodes.
|
|
fActorImages.reserve(m_ImageNodeCount);
|
|
for (uint32_t i = 0; i < m_ImageNodeCount; i ++) {
|
|
fActorImages.emplace_back(m_ImageNodes[i], fTexture.get(), &fPaint);
|
|
}
|
|
|
|
// Sort the image nodes.
|
|
std::sort(fActorImages.begin(), fActorImages.end(), [](auto a, auto b) {
|
|
return a.drawOrder() < b.drawOrder();
|
|
});
|
|
|
|
// Get the list of animations.
|
|
fAnimations.reserve(m_AnimationsCount);
|
|
for (uint32_t i = 0; i < m_AnimationsCount; i ++) {
|
|
fAnimations.push_back(m_Animations[i].name());
|
|
}
|
|
}
|
|
|
|
void render(SkCanvas* canvas, RenderMode renderMode) {
|
|
// Render the image nodes.
|
|
for (auto& image : fActorImages) {
|
|
switch (renderMode) {
|
|
case kBackend_RenderMode: {
|
|
// Render with Skia backend.
|
|
image.renderBackend(canvas);
|
|
break;
|
|
}
|
|
case kImmediate_RenderMode: {
|
|
// Render with immediate backend.
|
|
image.renderImmediate(canvas);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const std::vector<std::string>& getAnimations() const {
|
|
return fAnimations;
|
|
}
|
|
|
|
private:
|
|
sk_sp<SkImage> fTexture;
|
|
std::vector<NIMAActorImage> fActorImages;
|
|
SkPaint fPaint;
|
|
std::vector<std::string> fAnimations;
|
|
|
|
typedef Actor INHERITED;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
NIMASlide::NIMASlide(const SkString& name, const SkString& path)
|
|
: fBasePath()
|
|
, fActor(nullptr)
|
|
, fPlaying(true)
|
|
, fTime(0.0f)
|
|
, fRenderMode(kBackend_RenderMode)
|
|
, fAnimation(nullptr)
|
|
, fAnimationIndex(0) {
|
|
fName = name;
|
|
|
|
// Get the path components.
|
|
SkString baseName = SkOSPath::Basename(path.c_str());
|
|
baseName.resize(baseName.size() - 5);
|
|
SkString dirName = SkOSPath::Dirname(path.c_str());
|
|
SkString basePath = SkOSPath::Join(dirName.c_str(), baseName.c_str());
|
|
|
|
// Save the base path.
|
|
fBasePath = std::string(basePath.c_str());
|
|
}
|
|
|
|
NIMASlide::~NIMASlide() {}
|
|
|
|
void NIMASlide::draw(SkCanvas* canvas) {
|
|
canvas->save();
|
|
|
|
for (int i = 0; i < 10; i ++) {
|
|
for (int j = 0; j < 10; j ++) {
|
|
canvas->save();
|
|
|
|
canvas->translate(1250 - 250 * i, 1250 - 250 * j);
|
|
canvas->scale(0.5, -0.5);
|
|
|
|
// Render the actor.
|
|
fActor->render(canvas, fRenderMode);
|
|
|
|
canvas->restore();
|
|
}
|
|
}
|
|
|
|
canvas->restore();
|
|
|
|
// Render the GUI.
|
|
this->renderGUI();
|
|
}
|
|
|
|
void NIMASlide::load(SkScalar winWidth, SkScalar winHeight) {
|
|
this->resetActor();
|
|
}
|
|
|
|
void NIMASlide::unload() {
|
|
// Discard resources.
|
|
fAnimation = nullptr;
|
|
fActor.reset(nullptr);
|
|
}
|
|
|
|
bool NIMASlide::animate(const SkAnimTimer& timer) {
|
|
// Apply the animation.
|
|
if (fAnimation) {
|
|
if (fPlaying) {
|
|
fTime = std::fmod(timer.secs(), fAnimation->max());
|
|
}
|
|
fAnimation->time(fTime);
|
|
fAnimation->apply(1.0f);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool NIMASlide::onChar(SkUnichar c) {
|
|
return false;
|
|
}
|
|
|
|
bool NIMASlide::onMouse(SkScalar x, SkScalar y, Window::InputState state, uint32_t modifiers) {
|
|
return false;
|
|
}
|
|
|
|
void NIMASlide::resetActor() {
|
|
// Create the actor.
|
|
fActor = std::make_unique<NIMAActor>(fBasePath);
|
|
|
|
// Get the animation.
|
|
fAnimation = fActor->animationInstance(fActor->getAnimations()[fAnimationIndex]);
|
|
}
|
|
|
|
void NIMASlide::renderGUI() {
|
|
ImGui::SetNextWindowSize(ImVec2(300, 220));
|
|
ImGui::Begin("NIMA");
|
|
|
|
// List of animations.
|
|
auto animations = const_cast<std::vector<std::string>&>(fActor->getAnimations());
|
|
ImGui::PushItemWidth(-1);
|
|
if (ImGui::ListBox("Animations",
|
|
&fAnimationIndex,
|
|
vector_getter,
|
|
reinterpret_cast<void*>(&animations),
|
|
animations.size(),
|
|
5)) {
|
|
resetActor();
|
|
}
|
|
|
|
// Playback control.
|
|
ImGui::Spacing();
|
|
if (ImGui::Button("Play")) {
|
|
fPlaying = true;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Pause")) {
|
|
fPlaying = false;
|
|
}
|
|
|
|
// Time slider.
|
|
ImGui::PushItemWidth(-1);
|
|
ImGui::SliderFloat("Time", &fTime, 0.0f, fAnimation->max(), "Time: %.3f");
|
|
|
|
// Backend control.
|
|
int renderMode = fRenderMode;
|
|
ImGui::Spacing();
|
|
ImGui::RadioButton("Skia Backend", &renderMode, 0);
|
|
ImGui::RadioButton("Immediate Backend", &renderMode, 1);
|
|
if (renderMode == 0) {
|
|
fRenderMode = kBackend_RenderMode;
|
|
} else {
|
|
fRenderMode = kImmediate_RenderMode;
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|