Refactor Nima code

There were two copies of a Nima "player" and this moves them out of
samplecode/ and viewer/ to experimental/ where it is a bit more
accessible (e.g. for WebAssembly).

Bug: skia:
Change-Id: I05419a352f0d13d16b462a374578107513eb1243
Reviewed-on: https://skia-review.googlesource.com/c/166441
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
This commit is contained in:
Kevin Lubick 2018-10-30 15:08:53 -04:00 committed by Skia Commit-Bot
parent 9877e8407b
commit d969932474
9 changed files with 491 additions and 584 deletions

View File

@ -1689,6 +1689,7 @@ if (skia_enable_tools) {
# NIMA does not build on Windows clang
if (!is_win || !is_clang) {
sources += [ "experimental/nima/NimaActor.cpp" ]
deps += [ "//third_party/Nima-Cpp" ]
}
@ -2157,7 +2158,7 @@ if (skia_enable_tools) {
]
libs = []
include_dirs = []
include_dirs = [ "experimental" ]
deps = [
":experimental_svg_model",
":flags",

View File

@ -0,0 +1,323 @@
/*
* 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 "NimaActor.h"
#include "SkData.h"
#include "SkFilterQuality.h"
#include "SkImage.h"
#include "SkPaint.h"
#include "SkString.h"
#include "SkVertices.h"
#include <algorithm>
#include <cmath>
NimaActor::NimaActor(std::string nimaPath, std::string texturePath)
: fTexture(nullptr)
, fActorImages()
, fPaint(nullptr)
, fAnimationNames()
, fAnimationInstance(nullptr) {
// Load the NIMA data.
INHERITED::load(nimaPath);
// Load the image asset.
fTexture = SkImage::MakeFromEncoded(SkData::MakeFromFileName(texturePath.c_str()));
this->init();
}
NimaActor::NimaActor(sk_sp<SkData> nimaBytes, sk_sp<SkData> textureBytes)
: fTexture(nullptr)
, fActorImages()
, fPaint(nullptr)
, fAnimationNames()
, fAnimationInstance(nullptr) {
// Load the NIMA data.
INHERITED::load(const_cast<uint8_t*>(nimaBytes->bytes()), nimaBytes->size());
// Load the image asset.
fTexture = SkImage::MakeFromEncoded(textureBytes);
this->init();
}
void NimaActor::init() {
// Create the paint.
fPaint = std::make_unique<SkPaint>();
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.get());
}
// Sort the image nodes.
std::sort(fActorImages.begin(), fActorImages.end(), [](auto a, auto b) {
return a.drawOrder() < b.drawOrder();
});
// Get the list of animations.
fAnimationNames.reserve(m_AnimationsCount);
for (uint32_t i = 0; i < m_AnimationsCount; i++) {
fAnimationNames.push_back(m_Animations[i].name());
}
this->setAnimation(0);
}
SkScalar NimaActor::duration() const {
if (fAnimationInstance) {
return fAnimationInstance->duration();
}
return 0.0f;
}
void NimaActor::setAnimation(uint8_t index) {
if (index < fAnimationNames.size()) {
fAnimationIndex = index;
fAnimationInstance = this->animationInstance(fAnimationNames[fAnimationIndex]);
}
}
void NimaActor::setAnimation(std::string name) {
for (size_t i = 0; i < fAnimationNames.size(); i++)
{
std::string aName = fAnimationNames[i];
if (aName == name)
{
setAnimation(i);
return;
}
}
}
void NimaActor::render(SkCanvas* canvas, uint32_t renderFlags) {
// Render the image nodes.
for (auto& image : fActorImages) {
image.render(canvas, renderFlags);
}
}
void NimaActor::seek(SkScalar t) {
// Apply the animation.
if (fAnimationInstance) {
t = std::fmod(t, fAnimationInstance->max());
fAnimationInstance->time(t);
fAnimationInstance->apply(1.0f);
}
}
// ===================================================================================
NimaActorImage::NimaActorImage(nima::ActorImage* actorImage, SkImage* texture, SkPaint* paint)
: fActorImage(actorImage)
, fTexture(texture)
, fPaint(paint)
, fSkinned(false)
, fPositions()
, fTexs()
, fBoneIdx()
, fBoneWgt()
, fIndices()
, fBones()
, fVertices(nullptr)
, fRenderFlags(0) {
// Update the vertices and bones.
this->updateVertices(true);
this->updateBones();
}
void NimaActorImage::render(SkCanvas* canvas, uint32_t renderFlags) {
bool dirty = renderFlags != fRenderFlags;
fRenderFlags = renderFlags;
bool useImmediate = renderFlags & kImmediate_RenderFlag;
bool useCache = renderFlags & kCache_RenderFlag;
bool drawBounds = renderFlags & kBounds_RenderFlag;
// Don't use the cache if drawing in immediate mode.
useCache &= !useImmediate;
if (fActorImage->doesAnimationVertexDeform() || dirty) {
// These are vertices that transform beyond just bone transforms, so they must be
// updated every frame.
// If the render flags are dirty, reset the vertices object.
this->updateVertices(!useCache);
}
// Update the bones.
this->updateBones();
// Deform the bones in immediate mode.
sk_sp<SkVertices> vertices = fVertices;
if (useImmediate) {
vertices = fVertices->applyBones(fBones.data(), fBones.size());
}
// Draw the vertices object.
this->drawVerticesObject(vertices.get(), canvas, !useImmediate);
// Draw the bounds.
if (drawBounds && fActorImage->renderOpacity() > 0.0f) {
// Get the bounds.
SkRect bounds = vertices->bounds();
// Approximate bounds if not using immediate transforms.
if (!useImmediate) {
const SkRect originalBounds = fBones[0].mapRect(vertices->bounds());
bounds = originalBounds;
for (size_t i = 1; i < fBones.size(); i++) {
const SkVertices::Bone& matrix = fBones[i];
bounds.join(matrix.mapRect(originalBounds));
}
}
// Draw the bounds.
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setColor(0xFFFF0000);
canvas->drawRect(bounds, paint);
}
}
void NimaActorImage::updateVertices(bool isVolatile) {
// 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][k] = static_cast<uint32_t>(attrBoneIdx[k]);
fBoneWgt[i][k] = attrBoneWgt[k];
}
}
}
memcpy(fIndices.data(), indexData, indexCount * sizeof(uint16_t));
// Update the vertices object.
fVertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
vertexCount,
fPositions.data(),
fTexs.data(),
nullptr,
fBoneIdx.data(),
fBoneWgt.data(),
fIndices.size(),
fIndices.data(),
isVolatile);
}
void NimaActorImage::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;
}
// Initialize all matrices to the identity matrix.
fBones.assign(numMatrices, {{ 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }});
}
if (fSkinned) {
// Update the matrices.
float* matrixData = fActorImage->boneInfluenceMatrices();
memcpy(fBones.data(), matrixData, fBones.size() * kNIMAMatrixSize * sizeof(float));
}
// Set the zero matrix to be the world transform.
memcpy(fBones.data(),
fActorImage->worldTransform().values(),
kNIMAMatrixSize * sizeof(float));
}
void NimaActorImage::drawVerticesObject(SkVertices* vertices, SkCanvas* canvas, bool useBones) const {
// Determine the blend mode.
SkBlendMode blendMode;
switch (fActorImage->blendMode()) {
case nima::BlendMode::Off: {
blendMode = SkBlendMode::kSrc;
break;
}
case nima::BlendMode::Normal: {
blendMode = SkBlendMode::kSrcOver;
break;
}
case nima::BlendMode::Additive: {
blendMode = SkBlendMode::kPlus;
break;
}
case nima::BlendMode::Multiply: {
blendMode = SkBlendMode::kMultiply;
break;
}
case nima::BlendMode::Screen: {
blendMode = SkBlendMode::kScreen;
break;
}
}
// Set the opacity.
fPaint->setAlpha(static_cast<U8CPU>(fActorImage->renderOpacity() * 255));
// Draw the vertices.
if (useBones) {
canvas->drawVertices(vertices, fBones.data(), fBones.size(), blendMode, *fPaint);
} else {
canvas->drawVertices(vertices, blendMode, *fPaint);
}
// Reset the opacity.
fPaint->setAlpha(255);
}

View File

@ -0,0 +1,133 @@
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef NimaActor_DEFINED
#define NimaActor_DEFINED
#include <nima/Actor.hpp>
#include <nima/ActorImage.hpp>
#include <nima/Vec2D.hpp>
#include "SkCanvas.h"
#include "SkData.h"
#include "SkImage.h"
#include <string>
class NimaActor;
class NimaActorImage;
enum RenderFlags {
kImmediate_RenderFlag = 0x1,
kCache_RenderFlag = 0x2,
kBounds_RenderFlag = 0x4,
};
/** \class NimaActor
NimaActor acts as a breidge between Skia and a nima::Actor object.
The Actor object essentially is a set of bones and textures.
NimaActor knows how to draw itself (the Actor) to an SkCanvas
at various time stamps.
NimaActor is also aware of the different animation types the
Actor has and coordinates switching between them. For example,
an animation might have an "idle" and a "jump" animation it can
switch between.
*/
class NimaActor : public nima::Actor {
public:
NimaActor(std::string nimaPath, std::string texturePath);
NimaActor(sk_sp<SkData> nimaBytes, sk_sp<SkData> textureBytes);
~NimaActor() = default;
/**
* Render draws itself to the given canvas, at whatever
* the current time position is (see seek).
*/
void render(SkCanvas* canvas, uint32_t renderFlags = 0);
/**
* Updates the animation state to be at time t.
* This does not re-draw anything, another call to render() is required.
*
* @param t - number of second in (modulo total duration)
*
*/
void seek(SkScalar t);
/**
* Returns the duration of the current Actor's animation in seconds.
*/
SkScalar duration() const;
/**
* Sets the animation type based on the index given. The default
* animation index is 0. If index is invalid, nothing changes.
*/
void setAnimation(uint8_t index);
/**
* Sets the animation type to be one that matches the provided
* name. If the name does not match any of the existing animation
* types, nothing changes.
*/
void setAnimation(std::string name);
/**
* Returns all possible animation names. Use with setAnimation().
*/
const std::vector<std::string>& getAnimationNames() const {
return fAnimationNames;
}
private:
void init();
sk_sp<SkImage> fTexture;
std::vector<NimaActorImage> fActorImages;
std::unique_ptr<SkPaint> fPaint;
std::vector<std::string> fAnimationNames;
nima::ActorAnimationInstance* fAnimationInstance;
uint8_t fAnimationIndex;
typedef nima::Actor INHERITED;
};
// A wrapper class that handles rendering of ActorImages (renderable components NIMA Actors).
class NimaActorImage {
public:
NimaActorImage(nima::ActorImage* actorImage, SkImage* texture, SkPaint* paint);
~NimaActorImage() = default;
void render(SkCanvas* canvas, uint32_t renderFlags);
int drawOrder() const { return fActorImage->drawOrder(); }
private:
nima::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<SkVertices::Bone> fBones;
sk_sp<SkVertices> fVertices;
uint32_t fRenderFlags;
void updateVertices(bool isVolatile);
void updateBones();
void drawVerticesObject(SkVertices* vertices, SkCanvas* canvas, bool useBones) const;
};
#endif

View File

@ -67,8 +67,6 @@ samples_sources = [
"$_samplecode/SampleMeasure.cpp",
"$_samplecode/SampleMegaStroke.cpp",
"$_samplecode/SampleNima.cpp",
"$_samplecode/SampleNimaActor.cpp",
"$_samplecode/SampleNimaActor.h",
"$_samplecode/SamplePatch.cpp",
"$_samplecode/SamplePath.cpp",
"$_samplecode/SamplePathText.cpp",
@ -109,9 +107,5 @@ samples_sources = [
]
if (is_win && is_clang) {
samples_sources -= [
"$_samplecode/SampleNimaActor.cpp",
"$_samplecode/SampleNimaActor.h",
"$_samplecode/SampleNima.cpp",
]
samples_sources -= [ "$_samplecode/SampleNima.cpp" ]
}

View File

@ -6,8 +6,11 @@
*/
#include "Sample.h"
#include "SampleNimaActor.h"
#include "Resources.h"
#include "SkAnimTimer.h"
#include "nima/NimaActor.h"
#include <nima/Animation/ActorAnimationInstance.hpp>
#include <cmath>
@ -16,8 +19,7 @@ using namespace nima;
class NimaView : public Sample {
public:
NimaView()
: fActor(nullptr)
, fAnimation(nullptr) {
: fActor(nullptr) {
}
protected:
@ -31,16 +33,19 @@ protected:
void onOnceBeforeDraw() override {
// Create the actor.
fActor = std::make_unique<SampleActor>("Robot");
std::string nimaPath(GetResourcePath("nima/Robot.nima").c_str());
std::string texturePath(GetResourcePath("nima/Robot.png").c_str());
// Get the animation.
fAnimation = fActor->animationInstance("attack");
fActor = std::make_unique<NimaActor>(nimaPath, texturePath);
// Also available: dance, jump, idle
fActor->setAnimation("attack");
}
void onDrawContent(SkCanvas* canvas) override {
canvas->save();
canvas->translate(500, 500);
canvas->translate(500, 700);
canvas->scale(1, -1);
// Render the actor.
@ -50,18 +55,15 @@ protected:
}
bool onAnimate(const SkAnimTimer& timer) override {
// Apply the animation.
if (fAnimation) {
float time = std::fmod(timer.secs(), fAnimation->max());
fAnimation->time(time);
fAnimation->apply(1.0f);
if (fActor) {
float time = std::fmod(timer.secs(), fActor->duration());
fActor->seek(time);
}
return true;
}
private:
std::unique_ptr<SampleActor> fActor;
ActorAnimationInstance* fAnimation;
std::unique_ptr<NimaActor> fActor;
typedef Sample INHERITED;
};

View File

@ -1,185 +0,0 @@
/*
* 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 "SampleNimaActor.h"
#include "SkString.h"
#include "SkVertices.h"
#include "SkPaint.h"
#include "SkFilterQuality.h"
#include "Resources.h"
#include <algorithm>
using namespace nima;
SampleActor::SampleActor(std::string baseName)
: fTexture(nullptr)
, fActorImages()
, fPaint(nullptr) {
// Load the NIMA data.
SkString nimaSkPath = GetResourcePath(("nima/" + baseName + ".nima").c_str());
std::string nimaPath(nimaSkPath.c_str());
INHERITED::load(nimaPath);
// Load the image asset.
fTexture = GetResourceAsImage(("nima/" + baseName + ".png").c_str());
// Create the paint.
fPaint = std::make_unique<SkPaint>();
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, fPaint.get());
}
// Sort the image nodes.
std::sort(fActorImages.begin(), fActorImages.end(), [](auto a, auto b) {
return a.drawOrder() < b.drawOrder();
});
}
SampleActor::~SampleActor() {
}
void SampleActor::render(SkCanvas* canvas) const {
// Render the image nodes.
for (auto image : fActorImages) {
image.render(this, canvas);
}
}
SampleActorImage::SampleActorImage(ActorImage* actorImage, sk_sp<SkImage> texture, SkPaint* paint)
: fActorImage(actorImage)
, fTexture(texture)
, fPaint(paint) {
}
SampleActorImage::~SampleActorImage() {
}
void SampleActorImage::render(const SampleActor* actor, SkCanvas* canvas) const {
// 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) {
return;
}
// Split the vertex data.
std::vector<SkPoint> positions(vertexCount);
std::vector<SkPoint> texs(vertexCount);
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;
}
// Deform the position.
Vec2D position(attrPosition[0], attrPosition[1]);
if (fActorImage->connectedBoneCount() > 0) {
position = deform(position, attrBoneIdx, attrBoneWgt);
} else {
position = deform(position, nullptr, nullptr);
}
// Set the data.
positions[i].set(position[0], position[1]);
texs[i].set(attrTex[0] * fTexture->width(), attrTex[1] * fTexture->height());
}
// Create vertices.
sk_sp<SkVertices> vertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
vertexCount,
positions.data(),
texs.data(),
nullptr,
indexCount,
indexData);
// 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.
canvas->drawVertices(vertices, blendMode, *fPaint);
// Reset the opacity.
fPaint->setAlpha(255);
}
Vec2D SampleActorImage::deform(const Vec2D& position, float* 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 ++) {
int index = static_cast<int>(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);
}

View File

@ -1,54 +0,0 @@
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SampleNimaActor_DEFINED
#define SampleNimaActor_DEFINED
#include <nima/Actor.hpp>
#include <nima/ActorImage.hpp>
#include <nima/Vec2D.hpp>
#include "SkCanvas.h"
#include "SkImage.h"
class SampleActor;
class SampleActorImage;
class SampleActor : public nima::Actor {
public:
SampleActor(std::string baseName);
~SampleActor();
void render(SkCanvas* canvas) const;
private:
sk_sp<SkImage> fTexture;
std::vector<SampleActorImage> fActorImages;
std::unique_ptr<SkPaint> fPaint;
typedef nima::Actor INHERITED;
};
class SampleActorImage {
public:
SampleActorImage(nima::ActorImage* actorImage, sk_sp<SkImage> texture, SkPaint* paint);
~SampleActorImage();
void render(const SampleActor* actor, SkCanvas* canvas) const;
int drawOrder() const { return fActorImage->drawOrder(); }
private:
nima::Vec2D deform(const nima::Vec2D& position, float* boneIdx, float* boneWgt) const;
private:
nima::ActorImage* fActorImage;
sk_sp<SkImage> fTexture;
SkPaint* fPaint;
};
#endif

View File

@ -7,10 +7,11 @@
#include "NIMASlide.h"
#include "Resources.h"
#include "SkAnimTimer.h"
#include "SkOSPath.h"
#include "Resources.h"
#include "imgui.h"
#include "nima/NimaActor.h"
#include <algorithm>
#include <cmath>
@ -27,306 +28,15 @@ static bool vector_getter(void* v, int index, const char** out) {
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)
, fRenderFlags(0) {
// Update the vertices and bones.
this->updateVertices(true);
this->updateBones();
}
void render(SkCanvas* canvas, uint32_t renderFlags) {
bool dirty = renderFlags != fRenderFlags;
fRenderFlags = renderFlags;
bool useImmediate = renderFlags & kImmediate_RenderFlag;
bool useCache = renderFlags & kCache_RenderFlag;
bool drawBounds = renderFlags & kBounds_RenderFlag;
// Don't use the cache if drawing in immediate mode.
useCache &= !useImmediate;
if (fActorImage->doesAnimationVertexDeform() || dirty) {
// These are vertices that transform beyond just bone transforms, so they must be
// updated every frame.
// If the render flags are dirty, reset the vertices object.
this->updateVertices(!useCache);
}
// Update the bones.
this->updateBones();
// Deform the bones in immediate mode.
sk_sp<SkVertices> vertices = fVertices;
if (useImmediate) {
vertices = fVertices->applyBones(fBones.data(), fBones.size());
}
// Draw the vertices object.
this->drawVerticesObject(vertices.get(), canvas, !useImmediate);
// Draw the bounds.
if (drawBounds && fActorImage->renderOpacity() > 0.0f) {
// Get the bounds.
SkRect bounds = vertices->bounds();
// Approximate bounds if not using immediate transforms.
if (!useImmediate) {
const SkRect originalBounds = fBones[0].mapRect(vertices->bounds());
bounds = originalBounds;
for (size_t i = 1; i < fBones.size(); i++) {
const SkVertices::Bone& matrix = fBones[i];
bounds.join(matrix.mapRect(originalBounds));
}
}
// Draw the bounds.
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setColor(0xFFFF0000);
canvas->drawRect(bounds, paint);
}
}
int drawOrder() const { return fActorImage->drawOrder(); }
private:
void updateVertices(bool isVolatile) {
// 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][k] = static_cast<uint32_t>(attrBoneIdx[k]);
fBoneWgt[i][k] = attrBoneWgt[k];
}
}
}
memcpy(fIndices.data(), indexData, indexCount * sizeof(uint16_t));
// Update the vertices object.
fVertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
vertexCount,
fPositions.data(),
fTexs.data(),
nullptr,
fBoneIdx.data(),
fBoneWgt.data(),
fIndices.size(),
fIndices.data(),
isVolatile);
}
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;
}
// Initialize all matrices to the identity matrix.
fBones.assign(numMatrices, {{ 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }});
}
if (fSkinned) {
// Update the matrices.
float* matrixData = fActorImage->boneInfluenceMatrices();
memcpy(fBones.data(), matrixData, fBones.size() * kNIMAMatrixSize * sizeof(float));
}
// Set the zero matrix to be the world transform.
memcpy(fBones.data(),
fActorImage->worldTransform().values(),
kNIMAMatrixSize * sizeof(float));
}
void drawVerticesObject(SkVertices* vertices, 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(vertices, fBones.data(), fBones.size(), blendMode, *fPaint);
} else {
canvas->drawVertices(vertices, blendMode, *fPaint);
}
// Reset the opacity.
fPaint->setAlpha(255);
}
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<SkVertices::Bone> fBones;
sk_sp<SkVertices> fVertices;
uint32_t fRenderFlags;
};
//////////////////////////////////////////////////////////////////////////////////////////////////
// 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, uint32_t renderFlags) {
// Render the image nodes.
for (auto& image : fActorImages) {
image.render(canvas, renderFlags);
}
}
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)
, fAnimationIndex(0)
, fPlaying(true)
, fTime(0.0f)
, fRenderFlags(0)
, fAnimation(nullptr)
, fAnimationIndex(0) {
, fRenderFlags(0) {
fName = name;
// Get the path components.
@ -356,6 +66,7 @@ void NIMASlide::draw(SkCanvas* canvas) {
canvas->scale(0.5, -0.5);
// Render the actor.
fActor->setAnimation(fAnimationIndex);
fActor->render(canvas, fRenderFlags);
canvas->restore();
@ -374,18 +85,14 @@ void NIMASlide::load(SkScalar winWidth, SkScalar winHeight) {
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);
if (fActor) {
float time = std::fmod(timer.secs(), fActor->duration());
fActor->seek(time);
}
return true;
}
@ -400,10 +107,10 @@ bool NIMASlide::onMouse(SkScalar x, SkScalar y, Window::InputState state, uint32
void NIMASlide::resetActor() {
// Create the actor.
fActor = std::make_unique<NIMAActor>(fBasePath);
std::string nimaPath = fBasePath + ".nima";
std::string texturePath = fBasePath + ".png";
// Get the animation.
fAnimation = fActor->animationInstance(fActor->getAnimations()[fAnimationIndex]);
fActor = std::make_unique<NimaActor>(nimaPath, texturePath);
}
void NIMASlide::renderGUI() {
@ -411,7 +118,7 @@ void NIMASlide::renderGUI() {
ImGui::Begin("NIMA");
// List of animations.
auto animations = const_cast<std::vector<std::string>&>(fActor->getAnimations());
auto animations = const_cast<std::vector<std::string>&>(fActor->getAnimationNames());
ImGui::PushItemWidth(-1);
if (ImGui::ListBox("Animations",
&fAnimationIndex,
@ -434,7 +141,7 @@ void NIMASlide::renderGUI() {
// Time slider.
ImGui::PushItemWidth(-1);
ImGui::SliderFloat("Time", &fTime, 0.0f, fAnimation->max(), "Time: %.3f");
ImGui::SliderFloat("Time", &fTime, 0.0f, fActor->duration(), "Time: %.3f");
// Backend control.
int useImmediate = SkToBool(fRenderFlags & kImmediate_RenderFlag);

View File

@ -12,19 +12,7 @@
#include "SkCanvas.h"
#include "SkVertices.h"
#include <nima/Actor.hpp>
#include <nima/ActorImage.hpp>
#include <nima/Animation/ActorAnimationInstance.hpp>
#include <nima/Vec2D.hpp>
class NIMAActor;
class NIMAActorImage;
enum RenderFlags {
kImmediate_RenderFlag = 0x1,
kCache_RenderFlag = 0x2,
kBounds_RenderFlag = 0x4,
};
#include "nima/NimaActor.h"
class NIMASlide : public Slide {
public:
@ -49,14 +37,12 @@ private:
private:
std::string fBasePath;
std::unique_ptr<NIMAActor> fActor;
std::unique_ptr<NimaActor> fActor;
int fAnimationIndex;
bool fPlaying;
float fTime;
uint32_t fRenderFlags;
nima::ActorAnimationInstance* fAnimation;
int fAnimationIndex;
};
#endif