/**

 Copyright (c) 2010-2013  hkrn

 All rights reserved.

 Redistribution and use in source and binary forms, with or
 without modification, are permitted provided that the following
 conditions are met:

 - Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
 - Redistributions in binary form must reproduce the above
   copyright notice, this list of conditions and the following
   disclaimer in the documentation and/or other materials provided
   with the distribution.
 - Neither the name of the MMDAI project team nor the names of
   its contributors may be used to endorse or promote products
   derived from this software without specific prior written
   permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.

*/

#include "vpvl2/vpvl2.h"
#include "vpvl2/internal/ModelHelper.h"

#include "vpvl2/pmx/Bone.h"
#include "vpvl2/pmx/Vertex.h"

namespace
{

using namespace vpvl2;
using namespace vpvl2::pmx;

#pragma pack(push, 1)

struct VertexUnit {
    float32 position[3];
    float32 normal[3];
    float32 texcoord[2];
};

struct AdditinalUVUnit {
    float32 value[4];
};

struct Bdef2Unit {
    float32 weight;
};

struct Bdef4Unit {
    float32 weight[4];
};

struct SdefUnit {
    float32 weight;
    float32 c[3];
    float32 r0[3];
    float32 r1[3];
};

#pragma pack(pop)

}

namespace vpvl2
{
namespace pmx
{

const int Vertex::kMaxBones = 4;
const int Vertex::kMaxMorphs = 5;

struct Vertex::PrivateContext {
    PrivateContext(IModel *modelRef)
        : modelRef(modelRef),
          materialRef(Factory::sharedNullMaterialRef()),
          origin(kZeroV3),
          morphDelta(kZeroV3),
          normal(kZeroV3),
          texcoord(kZeroV3),
          c(kZeroV3),
          r0(kZeroV3),
          r1(kZeroV3),
          type(kBdef1),
          edgeSize(0),
          index(-1)
    {
        for (int i = 0; i < kMaxBones; i++) {
            boneRefs[i] = Factory::sharedNullBoneRef();
            weight[i] = 0;
            boneIndices[i] = -1;
        }
        for (int i = 0; i < kMaxMorphs; i++) {
            originUVs[i].setZero();
            morphUVs[i].setZero();
        }
    }
    ~PrivateContext() {
        modelRef = 0;
        materialRef = 0;
        origin.setZero();
        morphDelta.setZero();
        normal.setZero();
        texcoord.setZero();
        c.setZero();
        r0.setZero();
        r1.setZero();
        type = kBdef1;
        edgeSize = 0;
        index = -1;
        for (int i = 0; i < kMaxBones; i++) {
            boneRefs[i] = 0;
            weight[i] = 0;
            boneIndices[i] = -1;
        }
        for (int i = 0; i < kMaxMorphs; i++) {
            originUVs[i].setZero();
            morphUVs[i].setZero();
        }
    }
    IModel *modelRef;
    IBone *boneRefs[kMaxBones];
    IMaterial *materialRef;
    Array<PropertyEventListener *> eventRefs;
    Vector4 originUVs[kMaxMorphs];
    Vector4 morphUVs[kMaxMorphs];
    Vector3 origin;
    Vector3 morphDelta;
    Vector3 normal;
    Vector3 texcoord;
    Vector3 c;
    Vector3 r0;
    Vector3 r1;
    IVertex::Type type;
    IVertex::EdgeSizePrecision edgeSize;
    IVertex::WeightPrecision weight[kMaxBones];
    int boneIndices[kMaxBones];
    int index;
};

Vertex::Vertex(IModel *modelRef)
    : m_context(new PrivateContext(modelRef))
{
}

Vertex::~Vertex()
{
    internal::deleteObject(m_context);
}

bool Vertex::preparse(uint8 *&ptr, vsize &rest, Model::DataInfo &info)
{
    int32 nvertices;
    if (!internal::getTyped<int32>(ptr, rest, nvertices)) {
        VPVL2_LOG(WARNING, "Invalid size of PMX vertex detected: size=" << nvertices << " rest=" << rest);
        return false;
    }
    if (!internal::checkBound(info.additionalUVSize, vsize(0), vsize(kMaxMorphs))) {
        VPVL2_LOG(WARNING, "Invalid size of PMX additional UV size detected: size=" << info.additionalUVSize << " rest=" << rest);
        return false;
    }
    info.verticesPtr = ptr;
    vsize baseSize = sizeof(VertexUnit) + sizeof(AdditinalUVUnit) * info.additionalUVSize;
    for (int i = 0; i < nvertices; i++) {
        if (!internal::validateSize(ptr, baseSize, rest)) {
            VPVL2_LOG(WARNING, "Invalid size of PMX base vertex unit detected: index=" << i << " ptr=" << static_cast<const void *>(ptr) << " rest=" << rest);
            return false;
        }
        uint8 type;
        /* bone type */
        if (!internal::getTyped<uint8>(ptr, rest, type)) {
            VPVL2_LOG(WARNING, "Invalid size of PMX vertex type detected: index=" << i << " ptr=" << static_cast<const void *>(ptr) << " rest=" << rest);
            return false;
        }
        if (type == kQdef && info.version < 2.1) {
            VPVL2_LOG(WARNING, "QDEF is not supported: index=" << i << " ptr=" << static_cast<const void *>(ptr) << " rest=" << rest);
            return false;
        }
        vsize boneSize = 0;
        switch (type) {
        case kBdef1: /* BDEF1 */
            boneSize = info.boneIndexSize;
            break;
        case kBdef2: /* BDEF2 */
            boneSize = info.boneIndexSize * 2 + sizeof(Bdef2Unit);
            break;
        case kBdef4: /* BDEF4 */
        case kQdef: /* QDEF */
            boneSize = info.boneIndexSize * 4 + sizeof(Bdef4Unit);
            break;
        case kSdef: /* SDEF */
            boneSize = info.boneIndexSize * 2 + sizeof(SdefUnit);
            break;
        default: /* unexpected value */
            VPVL2_LOG(WARNING, "Unexpected vertex type detected: index=" << i << " type=" << int(type) <<  " rest=" << rest);
            return false;
        }
        boneSize += sizeof(float); /* edge */
        if (!internal::validateSize(ptr, boneSize, rest)) {
            VPVL2_LOG(WARNING, "Invalid size of PMX vertex unit of bone detected: index=" << i << " size=" << boneSize <<  " rest=" << rest);
            return false;
        }
    }
    info.verticesCount = nvertices;
    return rest > 0;
}

bool Vertex::loadVertices(const Array<Vertex *> &vertices, const Array<Bone *> &bones)
{
    const int nvertices = vertices.count();
    const int nbones = bones.count();
    for (int i = 0; i < nvertices; i++) {
        Vertex *vertex = vertices[i];
        vertex->setIndex(i);
        switch (vertex->m_context->type) {
        case kBdef1: {
            int boneIndex = vertex->m_context->boneIndices[0];
            if (boneIndex >= 0) {
                if (boneIndex >= nbones) {
                    VPVL2_LOG(WARNING, "Invalid PMX bone (Bdef1) specified: index=" << i << " bone=" << boneIndex);
                    return false;
                }
                else {
                    vertex->m_context->boneRefs[0] = bones[boneIndex];
                }
            }
            else {
                vertex->m_context->boneRefs[0] = Factory::sharedNullBoneRef();
            }
            break;
        }
        case kBdef2:
        case kSdef:
        {
            for (int j = 0; j < 2; j++) {
                int boneIndex = vertex->m_context->boneIndices[j];
                if (boneIndex >= 0) {
                    if (boneIndex >= nbones) {
                        VPVL2_LOG(WARNING, "Invalid PMX bone (Bdef2|Sdef) specified: index=" << i << " offset=" << j << " bone=" << boneIndex);
                        return false;
                    }
                    else {
                        vertex->m_context->boneRefs[j] = bones[boneIndex];
                    }
                }
                else {
                    vertex->m_context->boneRefs[j] = Factory::sharedNullBoneRef();
                }
            }
            break;
        }
        case kBdef4:
        case kQdef:
        {
            for (int j = 0; j < 4; j++) {
                int boneIndex = vertex->m_context->boneIndices[j];
                if (boneIndex >= 0) {
                    if (boneIndex >= nbones) {
                        VPVL2_LOG(WARNING, "Invalid PMX bone (Bdef4|Qdef) specified: index=" << i << " offset=" << j << " bone=" << boneIndex);
                        return false;
                    }
                    else {
                        vertex->m_context->boneRefs[j] = bones[boneIndex];
                    }
                }
                else {
                    vertex->m_context->boneRefs[j] = Factory::sharedNullBoneRef();
                }
            }
            break;
        }
        default:
            break;
        }
    }
    return true;
}

void Vertex::writeVertices(const Array<Vertex *> &vertices, const Model::DataInfo &info, uint8 *&data)
{
    const int32 nveritces = vertices.count();
    internal::writeBytes(&nveritces, sizeof(nveritces), data);
    for (int32 i = 0; i < nveritces; i++) {
        const Vertex *vertex = vertices[i];
        vertex->write(data, info);
    }
}

vsize Vertex::estimateTotalSize(const Array<Vertex *> &vertices, const Model::DataInfo &info)
{
    const int32 nvertices = vertices.count();
    vsize size = 0;
    size += sizeof(nvertices);
    for (int32 i = 0; i < nvertices; i++) {
        Vertex *vertex = vertices[i];
        size += vertex->estimateSize(info);
    }
    return size;
}

void Vertex::read(const uint8 *data, const Model::DataInfo &info, vsize &size)
{
    uint8 *ptr = const_cast<uint8 *>(data), *start = ptr;
    VertexUnit vertex;
    internal::getData(ptr, vertex);
    internal::setPosition(vertex.position, m_context->origin);
    VPVL2_VLOG(3, "PMXVertex: position=" << m_context->origin.x() << "," << m_context->origin.y() << "," << m_context->origin.z());
    internal::setPosition(vertex.normal, m_context->normal);
    VPVL2_VLOG(3, "PMXVertex: normal=" << m_context->normal.x() << "," << m_context->normal.y() << "," << m_context->normal.z());
    float32 u = vertex.texcoord[0], v = vertex.texcoord[1];
    m_context->texcoord.setValue(u, v, 0);
    VPVL2_VLOG(3, "PMXVertex: texcoord=" << m_context->texcoord.x() << "," << m_context->texcoord.y() << "," << m_context->texcoord.z());
    ptr += sizeof(vertex);
    int additionalUVSize = int(info.additionalUVSize);
    AdditinalUVUnit uv;
    m_context->originUVs[0].setValue(u, v, 0, 0);
    for (int i = 0; i < additionalUVSize; i++) {
        internal::getData(ptr, uv);
        Vector4 &v = m_context->originUVs[i + 1];
        v.setValue(uv.value[0], uv.value[1], uv.value[2], uv.value[3]);
        VPVL2_VLOG(3, "PMXVertex: uv(" << i << ")=" << v.x() << "," << v.y() << "," << v.z() << "," << v.w());
        ptr += sizeof(uv);
    }
    m_context->type = static_cast<Type>(*reinterpret_cast<uint8 *>(ptr));
    ptr += sizeof(uint8);
    switch (m_context->type) {
    case kBdef1: {
        m_context->boneIndices[0] = internal::readSignedIndex(ptr, info.boneIndexSize);
        VPVL2_VLOG(3, "PMXVertex: type=" << m_context->type << " bone=" << m_context->boneIndices[0]);
        break;
    }
    case kBdef2: {
        for (int i = 0; i < 2; i++) {
            m_context->boneIndices[i] = internal::readSignedIndex(ptr, info.boneIndexSize);
        }
        Bdef2Unit unit;
        internal::getData(ptr, unit);
        m_context->weight[0] = btClamped(unit.weight, 0.0f, 1.0f);
        VPVL2_VLOG(3, "PMXVertex: type=" << m_context->type << " bone=" << m_context->boneIndices[0] << "," << m_context->boneIndices[1] << " weight=" << m_context->weight[0]);
        ptr += sizeof(unit);
        break;
    }
    case kBdef4:
    case kQdef: {
        for (int i = 0; i < 4; i++) {
            m_context->boneIndices[i] = internal::readSignedIndex(ptr, info.boneIndexSize);
        }
        Bdef4Unit unit;
        internal::getData(ptr, unit);
        for (int i = 0; i < 4; i++) {
            m_context->weight[i] = btClamped(unit.weight[i], 0.0f, 1.0f);
        }
        VPVL2_VLOG(3, "PMXVertex: type=" << m_context->type << " bone=" << m_context->boneIndices[0] << "," << m_context->boneIndices[1] << "," << m_context->boneIndices[2] << "," << m_context->boneIndices[3] << " weight=" << m_context->weight[0] << "," << m_context->weight[1] << "," << m_context->weight[2] << "," << m_context->weight[3]);
        ptr += sizeof(unit);
        break;
    }
    case kSdef: {
        for (int i = 0; i < 2; i++) {
            m_context->boneIndices[i] = internal::readSignedIndex(ptr, info.boneIndexSize);
        }
        SdefUnit unit;
        internal::getData(ptr, unit);
        m_context->c.setValue(unit.c[0], unit.c[1], unit.c[2]);
        m_context->r0.setValue(unit.r0[0], unit.r0[1], unit.r0[2]);
        m_context->r1.setValue(unit.r1[0], unit.r1[1], unit.r1[2]);
        m_context->weight[0] = btClamped(unit.weight, 0.0f, 1.0f);
        VPVL2_VLOG(3, "PMXVertex: type=" << m_context->type << " bone=" << m_context->boneIndices[0] << "," << m_context->boneIndices[1] << " weight=" << m_context->weight[0]);
        VPVL2_VLOG(3, "PMXVertex: C=" << m_context->c.x() << "," << m_context->c.y() << "," << m_context->c.z());
        VPVL2_VLOG(3, "PMXVertex: R0=" << m_context->r0.x() << "," << m_context->r0.y() << "," << m_context->r0.z());
        VPVL2_VLOG(3, "PMXVertex: R1=" << m_context->r1.x() << "," << m_context->r1.y() << "," << m_context->r1.z());
        ptr += sizeof(unit);
        break;
    }
    default: /* unexpected value */
        return;
    }
    float32 edgeSize;
    internal::getData(ptr, edgeSize);
    ptr += sizeof(edgeSize);
    m_context->edgeSize = edgeSize;
    size = ptr - start;
}

void Vertex::write(uint8 *&data, const Model::DataInfo &info) const
{
    VertexUnit vu;
    internal::getPosition(m_context->origin, vu.position);
    internal::getPosition(m_context->normal, vu.normal);
    vu.texcoord[0] = m_context->texcoord.x();
    vu.texcoord[1] = m_context->texcoord.y();
    internal::writeBytes(&vu, sizeof(vu), data);
    int additionalUVSize = int(info.additionalUVSize);
    AdditinalUVUnit avu;
    for (int i = 0; i < additionalUVSize; i++) {
        const Vector4 &uv = m_context->originUVs[i + 1];
        avu.value[0] = uv.x();
        avu.value[1] = uv.y();
        avu.value[2] = uv.z();
        avu.value[3] = uv.w();
        internal::writeBytes(&avu, sizeof(avu), data);
    }
    internal::writeBytes(&m_context->type, sizeof(uint8), data);
    int boneIndexSize = int(info.boneIndexSize);
    switch (m_context->type) {
    case kBdef1: {
        internal::writeSignedIndex(m_context->boneIndices[0], boneIndexSize, data);
        break;
    }
    case kBdef2: {
        for (int i = 0; i < 2; i++) {
            internal::writeSignedIndex(m_context->boneIndices[i], boneIndexSize, data);
        }
        float32 weight = float32(m_context->weight[0]);
        internal::writeBytes(&weight, sizeof(weight), data);
        break;
    }
    case kBdef4:
    case kQdef:
    {
        for (int i = 0; i < 4; i++) {
            internal::writeSignedIndex(m_context->boneIndices[i], boneIndexSize, data);
        }
        for (int i = 0; i < 4; i++) {
            float32 weight = float32(m_context->weight[i]);
            internal::writeBytes(&weight, sizeof(weight), data);
        }
        break;
    }
    case kSdef: {
        for (int i = 0; i < 2; i++) {
            internal::writeSignedIndex(m_context->boneIndices[i], boneIndexSize, data);
        }
        SdefUnit unit;
        unit.c[0] = m_context->c.x();
        unit.c[1] = m_context->c.y();
        unit.c[2] = m_context->c.z();
        unit.r0[0] = m_context->r0.x();
        unit.r0[1] = m_context->r0.y();
        unit.r0[2] = m_context->r0.z();
        unit.r1[0] = m_context->r1.x();
        unit.r1[1] = m_context->r1.y();
        unit.r1[2] = m_context->r1.z();
        unit.weight = float(m_context->weight[0]);
        internal::writeBytes(&unit, sizeof(unit), data);
        break;
    }
    default: /* unexpected value */
        return;
    }
    float32 edgeSize = float32(m_context->edgeSize);
    internal::writeBytes(&edgeSize, sizeof(edgeSize), data);
}

vsize Vertex::estimateSize(const Model::DataInfo &info) const
{
    vsize size = 0;
    size += sizeof(VertexUnit);
    size += sizeof(AdditinalUVUnit) * info.additionalUVSize;
    size += sizeof(uint8);
    size += sizeof(float32); /* edgeSize */
    switch (m_context->type) {
    case kBdef1:
        size += info.boneIndexSize;
        break;
    case kBdef2:
        size += info.boneIndexSize * 2 + sizeof(Bdef2Unit);
        break;
    case kBdef4:
    case kQdef:
        size += info.boneIndexSize * 4 + sizeof(Bdef4Unit);
        break;
    case kSdef:
        size += info.boneIndexSize * 2 + sizeof(SdefUnit);
        break;
    default: /* unexpected value */
        return 0;
    }
    return size;
}

void Vertex::reset()
{
    m_context->morphDelta.setZero();
    for (int i = 0; i < kMaxMorphs; i++) {
        m_context->morphUVs[i].setZero();
    }
}

void Vertex::mergeMorph(const Morph::UV *morph, const IMorph::WeightPrecision &weight)
{
    int offset = morph->offset;
    if (internal::checkBound(offset, 0, kMaxMorphs)) {
        const Vector4 &m = morph->position, &o = m_context->morphUVs[offset];
        Vector4 v(Scalar(o.x() + m.x() * weight),
                  Scalar(o.y() + m.y() * weight),
                  Scalar(o.z() + m.z() * weight),
                  Scalar(o.w() + m.w() * weight));
        m_context->morphUVs[offset] = v;
    }
}

void Vertex::mergeMorph(const Morph::Vertex *morph, const IMorph::WeightPrecision &weight)
{
    m_context->morphDelta += morph->position * Scalar(weight);
}

void Vertex::performSkinning(Vector3 &position, Vector3 &normal) const
{
    const Vector3 &vertexPosition = m_context->origin + m_context->morphDelta;
    switch (m_context->type) {
    case kBdef1: {
        internal::ModelHelper::transformVertex(m_context->boneRefs[0]->localTransform(), vertexPosition, m_context->normal, position, normal);
        break;
    }
    case kBdef2:
    case kSdef: {
        const WeightPrecision &weight = m_context->weight[0];
        if (btFuzzyZero(Scalar(1 - weight))) {
            const Transform &transform = m_context->boneRefs[0]->localTransform();
            internal::ModelHelper::transformVertex(transform, vertexPosition, m_context->normal, position, normal);
        }
        else if (btFuzzyZero(Scalar(weight))) {
            const Transform &transform = m_context->boneRefs[1]->localTransform();
            internal::ModelHelper::transformVertex(transform, vertexPosition, m_context->normal, position, normal);
        }
        else {
            const Transform &transformA = m_context->boneRefs[0]->localTransform();
            const Transform &transformB = m_context->boneRefs[1]->localTransform();
            internal::ModelHelper::transformVertex(transformA, transformB, vertexPosition, m_context->normal, position, normal, weight);
        }
        break;
    }
    case kBdef4: {
        const Transform &transformA = m_context->boneRefs[0]->localTransform();
        const Transform &transformB = m_context->boneRefs[1]->localTransform();
        const Transform &transformC = m_context->boneRefs[2]->localTransform();
        const Transform &transformD = m_context->boneRefs[3]->localTransform();
        const Vector3 &v1 = transformA * vertexPosition;
        const Vector3 &n1 = transformA.getBasis() * m_context->normal;
        const Vector3 &v2 = transformB * vertexPosition;
        const Vector3 &n2 = transformB.getBasis() * m_context->normal;
        const Vector3 &v3 = transformC * vertexPosition;
        const Vector3 &n3 = transformC.getBasis() * m_context->normal;
        const Vector3 &v4 = transformD * vertexPosition;
        const Vector3 &n4 = transformD.getBasis() * m_context->normal;
        const WeightPrecision &w1 = m_context->weight[0], &w2 = m_context->weight[1], &w3 = m_context->weight[2], &w4 = m_context->weight[3];
        const WeightPrecision &s  = w1 + w2 + w3 + w4, &w1s = w1 / s, &w2s = w2 / s, &w3s = w3 / s, &w4s = w4 / s;
        position = v1 * Scalar(w1s) + v2 * Scalar(w2s) + v3 * Scalar(w3s) + v4 * Scalar(w4s);
        normal   = n1 * Scalar(w1s) + n2 * Scalar(w2s) + n3 * Scalar(w3s) + n4 * Scalar(w4s);
        break;
    }
    case kMaxType:
    default:
        break;
    }
}

void Vertex::addEventListenerRef(PropertyEventListener *value)
{
    if (value) {
        m_context->eventRefs.remove(value);
        m_context->eventRefs.append(value);
    }
}

void Vertex::removeEventListenerRef(PropertyEventListener *value)
{
    if (value) {
        m_context->eventRefs.remove(value);
    }
}

void Vertex::getEventListenerRefs(Array<PropertyEventListener *> &value)
{
    value.copy(m_context->eventRefs);
}

IModel *Vertex::parentModelRef() const
{
    return m_context->modelRef;
}

Vector3 Vertex::origin() const
{
    return m_context->origin;
}

Vector3 Vertex::delta() const
{
    return m_context->morphDelta;
}

Vector3 Vertex::normal() const
{
    return m_context->normal;
}

Vector3 Vertex::textureCoord() const
{
    return m_context->texcoord;
}

IVertex::Type Vertex::type() const
{
    return m_context->type;
}

IVertex::EdgeSizePrecision Vertex::edgeSize() const
{
    return m_context->edgeSize;
}

int Vertex::index() const
{
    return m_context->index;
}

Vector3 Vertex::sdefC() const
{
    return m_context->c;
}

Vector3 Vertex::sdefR0() const
{
    return m_context->r0;
}

Vector3 Vertex::sdefR1() const
{
    return m_context->r1;
}

Vector4 Vertex::uv(int index) const
{
    return internal::checkBound(index, 0, kMaxMorphs) ? m_context->morphUVs[index] : kZeroV4;
}

IVertex::WeightPrecision Vertex::weight(int index) const
{
    return internal::checkBound(index, 0, kMaxBones) ? m_context->weight[index] : 0;
}

IBone *Vertex::boneRef(int index) const
{
    return internal::checkBound(index, 0, kMaxBones) ? m_context->boneRefs[index] : Factory::sharedNullBoneRef();
}

IMaterial *Vertex::materialRef() const
{
    return m_context->materialRef;
}

void Vertex::setOrigin(const Vector3 &value)
{
    if (m_context->origin != value) {
        VPVL2_TRIGGER_PROPERTY_EVENTS(m_context->eventRefs, originWillChange(value, this));
        m_context->origin = value;
    }
}

void Vertex::setNormal(const Vector3 &value)
{
    if (m_context->normal != value) {
        VPVL2_TRIGGER_PROPERTY_EVENTS(m_context->eventRefs, normalWillChange(value, this));
        m_context->normal = value;
    }
}

void Vertex::setTextureCoord(const Vector3 &value)
{
    if (m_context->texcoord != value) {
        VPVL2_TRIGGER_PROPERTY_EVENTS(m_context->eventRefs, textureCoordWillChange(value, this));
        m_context->texcoord = value;
    }
}

void Vertex::setUV(int index, const Vector4 &value)
{
    if (internal::checkBound(index, 0, kMaxBones) && m_context->originUVs[index + 1] != value) {
        VPVL2_TRIGGER_PROPERTY_EVENTS(m_context->eventRefs, UVWillChange(index, value, this));
        m_context->originUVs[index + 1] = value;
    }
}

void Vertex::setType(Type value)
{
    if (m_context->type != value) {
        VPVL2_TRIGGER_PROPERTY_EVENTS(m_context->eventRefs, typeWillChange(value, this));
        m_context->type = value;
    }
}

void Vertex::setEdgeSize(const EdgeSizePrecision &value)
{
    if (m_context->edgeSize != value) {
        VPVL2_TRIGGER_PROPERTY_EVENTS(m_context->eventRefs, edgeSizeWillChange(value, this));
        m_context->edgeSize = value;
    }
}

void Vertex::setWeight(int index, const WeightPrecision &weight)
{
    if (internal::checkBound(index, 0, kMaxBones) && m_context->weight[index] != weight) {
        VPVL2_TRIGGER_PROPERTY_EVENTS(m_context->eventRefs, weightWillChange(index, weight, this));
        m_context->weight[index] = weight;
    }
}

void Vertex::setBoneRef(int index, IBone *value)
{
    if (internal::checkBound(index, 0, kMaxBones) && m_context->boneRefs[index] != value) {
        VPVL2_TRIGGER_PROPERTY_EVENTS(m_context->eventRefs, boneRefWillChange(index, value, this));
        if (value) {
            m_context->boneRefs[index] = value;
            m_context->boneIndices[index] = value->index();
        }
        else {
            m_context->boneRefs[index] = Factory::sharedNullBoneRef();
            m_context->boneIndices[index] = -1;
        }
    }
}

void Vertex::setMaterialRef(IMaterial *value)
{
    if (m_context->materialRef != value) {
        VPVL2_TRIGGER_PROPERTY_EVENTS(m_context->eventRefs, materialRefWillChange(value, this));
        m_context->materialRef = value ? value : Factory::sharedNullMaterialRef();
    }
}

void Vertex::setSdefC(const Vector3 &value)
{
    m_context->c = value;
}

void Vertex::setSdefR0(const Vector3 &value)
{
    m_context->r0 = value;
}

void Vertex::setSdefR1(const Vector3 &value)
{
    m_context->r1 = value;
}

void Vertex::setIndex(int value)
{
    m_context->index = value;
}

} /* namespace pmx */
} /* namespace vpvl2 */
