// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.

#include "BoxVolume.h"
#include "Engine/Core/Math/Matrix.h"
#include "Engine/Serialization/Serialization.h"

BoxVolume::BoxVolume(const SpawnParams& params)
    : Actor(params)
    , _size(1000.0f)
{
}

void BoxVolume::SetSize(const Vector3& value)
{
    if (!Vector3::NearEqual(value, _size))
    {
        const auto prevBounds = _box;
        _size = value;
        OrientedBoundingBox::CreateCentered(Vector3::Zero, _size, _bounds);
        _bounds.Transform(_transform);
        _bounds.GetBoundingBox(_box);
        BoundingSphere::FromBox(_box, _sphere);
        OnBoundsChanged(prevBounds);
    }
}

#if USE_EDITOR

#include "Engine/Debug/DebugDraw.h"
#include "Engine/Core/Math/Color.h"

Color BoxVolume::GetWiresColor()
{
    return Color::White;
}

void BoxVolume::OnDebugDraw()
{
    const Color color = GetWiresColor();
    DEBUG_DRAW_WIRE_BOX(_bounds, color, 0, true);

    // Base
    Actor::OnDebugDraw();
}

namespace
{
    OrientedBoundingBox GetWriteBox(const Vector3& min, const Vector3& max, const float margin)
    {
        OrientedBoundingBox box;
        const Vector3 vec = max - min;
        const Vector3 dir = Vector3::Normalize(vec);
        Quaternion orientation;
        if (Vector3::Dot(dir, Vector3::Up) >= 0.999f)
            Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation);
        else
            Quaternion::LookRotation(dir, Vector3::Cross(Vector3::Cross(dir, Vector3::Up), dir), orientation);
        const Vector3 up = orientation * Vector3::Up;
        Matrix world;
        Matrix::CreateWorld(min + vec * 0.5f, dir, up, world);
        world.Decompose(box.Transformation);
        Matrix invWorld;
        Matrix::Invert(world, invWorld);
        Vector3 vecLocal;
        Vector3::TransformNormal(vec * 0.5f, invWorld, vecLocal);
        box.Extents.X = margin;
        box.Extents.Y = margin;
        box.Extents.Z = vecLocal.Z;
        return box;
    }
}

void BoxVolume::OnDebugDrawSelected()
{
    const Color color = GetWiresColor();
    DEBUG_DRAW_WIRE_BOX(_bounds, color * 0.3f, 0, false);

    Vector3 sideLinks[] =
    {
        Vector3(0.5f, 0, 0),
        Vector3(-0.5f, 0, 0),
        Vector3(0, 0.5f, 0),
        Vector3(0, -0.5f, 0),
        Vector3(0, 0, 0.5f),
        Vector3(0, 0, -0.5f),
    };
    Vector3 sideLinksFinal[6];
    for (int32 i = 0; i < 6; i++)
    {
        sideLinksFinal[i] = sideLinks[i] * _size;
        _transform.LocalToWorld(sideLinksFinal[i], sideLinksFinal[i]);
    }
    for (int32 i = 0; i < 6; i++)
    {
        DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(sideLinksFinal[i], 10.0f), Color::YellowGreen, 0, true);
    }

    Vector3 corners[8];
    _bounds.GetCorners(corners);
    const float margin = 2.0f;
    const Color wiresColor = color.AlphaMultiplied(0.8f);
    DEBUG_DRAW_BOX(GetWriteBox(corners[0], corners[1], margin), wiresColor, 0, true);
    DEBUG_DRAW_BOX(GetWriteBox(corners[0], corners[3], margin), wiresColor, 0, true);
    DEBUG_DRAW_BOX(GetWriteBox(corners[0], corners[4], margin), wiresColor, 0, true);
    DEBUG_DRAW_BOX(GetWriteBox(corners[1], corners[2], margin), wiresColor, 0, true);
    DEBUG_DRAW_BOX(GetWriteBox(corners[1], corners[5], margin), wiresColor, 0, true);
    DEBUG_DRAW_BOX(GetWriteBox(corners[2], corners[3], margin), wiresColor, 0, true);
    DEBUG_DRAW_BOX(GetWriteBox(corners[2], corners[6], margin), wiresColor, 0, true);
    DEBUG_DRAW_BOX(GetWriteBox(corners[3], corners[7], margin), wiresColor, 0, true);
    DEBUG_DRAW_BOX(GetWriteBox(corners[4], corners[5], margin), wiresColor, 0, true);
    DEBUG_DRAW_BOX(GetWriteBox(corners[4], corners[7], margin), wiresColor, 0, true);
    DEBUG_DRAW_BOX(GetWriteBox(corners[5], corners[6], margin), wiresColor, 0, true);
    DEBUG_DRAW_BOX(GetWriteBox(corners[6], corners[7], margin), wiresColor, 0, true);

    // Base
    Actor::OnDebugDrawSelected();
}

#endif

void BoxVolume::Serialize(SerializeStream& stream, const void* otherObj)
{
    // Base
    Actor::Serialize(stream, otherObj);

    SERIALIZE_GET_OTHER_OBJ(BoxVolume);

    SERIALIZE_MEMBER(Size, _size);
}

void BoxVolume::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
    // Base
    Actor::Deserialize(stream, modifier);

    DESERIALIZE_MEMBER(Size, _size);
}

void BoxVolume::OnTransformChanged()
{
    // Base
    Actor::OnTransformChanged();

    const auto prevBounds = _box;
    OrientedBoundingBox::CreateCentered(Vector3::Zero, _size, _bounds);
    _bounds.Transform(_transform);
    _bounds.GetBoundingBox(_box);
    BoundingSphere::FromBox(_box, _sphere);
    OnBoundsChanged(prevBounds);
}
