C++程序  |  324行  |  9.92 KB

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