d969932474
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>
324 lines
9.9 KiB
C++
324 lines
9.9 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 "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);
|
|
}
|