| /* |
| * 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); |
| } |