// -*-c++-*-

#include "ReaderWriterVRML2.h"

#include <complex>

#if defined(_MSC_VER)
#   pragma warning(disable: 4250)
#   pragma warning(disable: 4290)
#   pragma warning(disable: 4800)
#endif

#include <openvrml/node.h>

#include <osg/CullFace>

osg::ref_ptr<osg::Geometry> ReaderWriterVRML2::convertVRML97Box(openvrml::node* vrml_box) const
{
    std::auto_ptr<openvrml::field_value> fv = vrml_box->field("size");
    const openvrml::vec3f &size = static_cast<const openvrml::sfvec3f *> (fv.get())->value();

    osg::Vec3 halfSize(size[0] * 0.5f, size[1] * 0.5f, size[2] * 0.5f);

    BoxLibrary::const_iterator it = m_boxLibrary.find(halfSize);
    if (it != m_boxLibrary.end())
    {
        return (*it).second.get();
    }

    osg::ref_ptr<osg::Geometry> osg_geom = new osg::Geometry();
    osg::ref_ptr<osg::Vec3Array> osg_vertices = new osg::Vec3Array();
    osg::ref_ptr<osg::Vec2Array> osg_texcoords = new osg::Vec2Array();
    osg::ref_ptr<osg::Vec3Array> osg_normals = new osg::Vec3Array();

    osg::ref_ptr<osg::DrawArrays> box = new osg::DrawArrays(osg::PrimitiveSet::QUADS);

    osg_vertices->push_back(osg::Vec3(-halfSize[0], halfSize[1], halfSize[2]));
    osg_vertices->push_back(osg::Vec3(-halfSize[0], -halfSize[1], halfSize[2]));
    osg_vertices->push_back(osg::Vec3(halfSize[0], -halfSize[1], halfSize[2]));
    osg_vertices->push_back(osg::Vec3(halfSize[0], halfSize[1], halfSize[2]));

    osg_vertices->push_back(osg::Vec3(halfSize[0], halfSize[1], -halfSize[2]));
    osg_vertices->push_back(osg::Vec3(halfSize[0], -halfSize[1], -halfSize[2]));
    osg_vertices->push_back(osg::Vec3(-halfSize[0], -halfSize[1], -halfSize[2]));
    osg_vertices->push_back(osg::Vec3(-halfSize[0], halfSize[1], -halfSize[2]));

    osg_vertices->push_back(osg::Vec3(halfSize[0], halfSize[1], halfSize[2]));
    osg_vertices->push_back(osg::Vec3(halfSize[0], -halfSize[1], halfSize[2]));
    osg_vertices->push_back(osg::Vec3(halfSize[0], -halfSize[1], -halfSize[2]));
    osg_vertices->push_back(osg::Vec3(halfSize[0], halfSize[1], -halfSize[2]));

    osg_vertices->push_back(osg::Vec3(-halfSize[0], halfSize[1], -halfSize[2]));
    osg_vertices->push_back(osg::Vec3(-halfSize[0], -halfSize[1], -halfSize[2]));
    osg_vertices->push_back(osg::Vec3(-halfSize[0], -halfSize[1], halfSize[2]));
    osg_vertices->push_back(osg::Vec3(-halfSize[0], halfSize[1], halfSize[2]));

    osg_vertices->push_back(osg::Vec3(-halfSize[0], halfSize[1], -halfSize[2]));
    osg_vertices->push_back(osg::Vec3(-halfSize[0], halfSize[1], halfSize[2]));
    osg_vertices->push_back(osg::Vec3(halfSize[0], halfSize[1], halfSize[2]));
    osg_vertices->push_back(osg::Vec3(halfSize[0], halfSize[1], -halfSize[2]));

    osg_vertices->push_back(osg::Vec3(-halfSize[0], -halfSize[1], halfSize[2]));
    osg_vertices->push_back(osg::Vec3(-halfSize[0], -halfSize[1], -halfSize[2]));
    osg_vertices->push_back(osg::Vec3(halfSize[0], -halfSize[1], -halfSize[2]));
    osg_vertices->push_back(osg::Vec3(halfSize[0], -halfSize[1], halfSize[2]));

    for (int i = 0; i != 6; ++i)
    {
        osg_texcoords->push_back(osg::Vec2(0.0f, 1.0f));
        osg_texcoords->push_back(osg::Vec2(0.0f, 0.0f));
        osg_texcoords->push_back(osg::Vec2(1.0f, 0.0f));
        osg_texcoords->push_back(osg::Vec2(1.0f, 1.0f));
    }

    osg_normals->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));
    osg_normals->push_back(osg::Vec3(0.0f, 0.0f, -1.0f));
    osg_normals->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));
    osg_normals->push_back(osg::Vec3(-1.0f, 0.0f, 0.0f));
    osg_normals->push_back(osg::Vec3(0.0f, 1.0f, 0.0f));
    osg_normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));

    box->setCount(osg_vertices->size());

    osg_geom->addPrimitiveSet(box.get());

    osg_geom->setVertexArray(osg_vertices.get());
    osg_geom->setTexCoordArray(0, osg_texcoords.get());
    osg_geom->setNormalArray(osg_normals.get());
    osg_geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE);

    osg_geom->getOrCreateStateSet()->setAttributeAndModes(new osg::CullFace(osg::CullFace::BACK));

    m_boxLibrary[halfSize] = osg_geom;

    return osg_geom.get();
}

osg::ref_ptr<osg::Geometry> ReaderWriterVRML2::convertVRML97Sphere(openvrml::node* vrml_sphere) const
{
    std::auto_ptr<openvrml::field_value> fv = vrml_sphere->field("radius");
    const float radius = static_cast<const openvrml::sffloat *> (fv.get())->value();

    SphereLibrary::const_iterator it = m_sphereLibrary.find(radius);
    if (it != m_sphereLibrary.end())
    {
        return (*it).second.get();
    }

    osg::ref_ptr<osg::Geometry> osg_geom = new osg::Geometry();
    osg::ref_ptr<osg::Vec3Array> osg_vertices = new osg::Vec3Array();
    osg::ref_ptr<osg::Vec2Array> osg_texcoords = new osg::Vec2Array();
    osg::ref_ptr<osg::Vec3Array> osg_normals = new osg::Vec3Array();

    unsigned int numSegments = 40;
    unsigned int numRows = 20;

    const float thetaDelta = 2.0f * float(osg::PI) / float(numSegments);
    const float texCoordSDelta = 1.0f / float(numSegments);
    const float phiDelta = float(osg::PI) / float(numRows);
    const float texCoordTDelta = 1.0f / float(numRows);

    float phi = -0.5f * float(osg::PI);
    float texCoordT = 0.0f;

    osg::ref_ptr<osg::DrawArrayLengths> sphere = new osg::DrawArrayLengths(osg::PrimitiveSet::QUAD_STRIP);

    for (unsigned int i = 0; i < numRows; ++i, phi += phiDelta, texCoordT += texCoordTDelta)
    {
        std::complex<float> latBottom = std::polar(1.0f, phi);
        std::complex<float> latTop = std::polar(1.0f, phi + phiDelta);
        std::complex<float> eBottom = latBottom * radius;
        std::complex<float> eTop = latTop * radius;

        float theta = 0.0f;
        float texCoordS = 0.0f;

        for (unsigned int j = 0; j < numSegments; ++j, theta += thetaDelta, texCoordS += texCoordSDelta)
        {
            std::complex<float> n = -std::polar(1.0f, theta);

            osg_normals->push_back(osg::Vec3(latTop.real() * n.imag(), latTop.imag(), latTop.real() * n.real()));
            osg_normals->push_back(osg::Vec3(latBottom.real() * n.imag(), latBottom.imag(), latBottom.real() * n.real()));

            osg_texcoords->push_back(osg::Vec2(texCoordS, texCoordT + texCoordTDelta));
            osg_texcoords->push_back(osg::Vec2(texCoordS, texCoordT));

            osg_vertices->push_back(osg::Vec3(eTop.real() * n.imag(), eTop.imag(), eTop.real() * n.real()));
            osg_vertices->push_back(osg::Vec3(eBottom.real() * n.imag(), eBottom.imag(), eBottom.real() * n.real()));
        }

        osg_normals->push_back(osg::Vec3(0.0f, latTop.imag(), -latTop.real()));
        osg_normals->push_back(osg::Vec3(0.0f, latBottom.imag(), -latBottom.real()));

        osg_texcoords->push_back(osg::Vec2(1.0f, texCoordT + texCoordTDelta));
        osg_texcoords->push_back(osg::Vec2(1.0f, texCoordT));

        osg_vertices->push_back(osg::Vec3(0.0f, eTop.imag(), -eTop.real()));
        osg_vertices->push_back(osg::Vec3(0.0f, eBottom.imag(), -eBottom.real()));

        sphere->push_back(numSegments * 2 + 2);
    }

    osg_geom->addPrimitiveSet(sphere.get());

    osg_geom->setVertexArray(osg_vertices.get());
    osg_geom->setTexCoordArray(0, osg_texcoords.get());
    osg_geom->setNormalArray(osg_normals.get());
    osg_geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);

    osg_geom->getOrCreateStateSet()->setAttributeAndModes(new osg::CullFace(osg::CullFace::BACK));

    m_sphereLibrary[radius] = osg_geom;

    return osg_geom.get();
}

osg::ref_ptr<osg::Geometry> ReaderWriterVRML2::convertVRML97Cone(openvrml::node* vrml_cone) const
{
    float height = static_cast<const openvrml::sffloat*>(vrml_cone->field("height").get())->value();
    float radius = static_cast<const openvrml::sffloat*>(vrml_cone->field("bottomRadius").get())->value();
    bool bottom = static_cast<const openvrml::sfbool*>(vrml_cone->field("bottom").get())->value();
    bool side = static_cast<const openvrml::sfbool*>(vrml_cone->field("side").get())->value();

    QuadricKey key(height, radius, bottom, side, false);

    ConeLibrary::const_iterator it = m_coneLibrary.find(key);
    if (it != m_coneLibrary.end())
    {
        return (*it).second.get();
    }

    osg::ref_ptr<osg::Geometry> osg_geom = new osg::Geometry();
    osg::ref_ptr<osg::Vec3Array> osg_vertices = new osg::Vec3Array();
    osg::ref_ptr<osg::Vec2Array> osg_texcoords = new osg::Vec2Array();
    osg::ref_ptr<osg::Vec3Array> osg_normals = new osg::Vec3Array();

    unsigned int numSegments = 40;

    const float thetaDelta = 2.0f * float(osg::PI) / float(numSegments);

    float topY = height * 0.5f;
    float bottomY = height * -0.5f;

    if (side)
    {
        osg::ref_ptr<osg::DrawArrays> side = new osg::DrawArrays(osg::PrimitiveSet::QUAD_STRIP);

        const float texCoordDelta = 1.0f / float(numSegments);
        float theta = 0.0f;
        float texCoord = 0.0f;

        for (unsigned int i = 0; i < numSegments; ++i, theta += thetaDelta, texCoord += texCoordDelta)
        {
            std::complex<float> n = -std::polar(1.0f, theta);
            std::complex<float> e = n * radius;

            osg::Vec3 normal(n.imag() * height, radius, n.real() * height);
            normal.normalize();

            osg_normals->push_back(normal);
            osg_normals->push_back(normal);

            osg_texcoords->push_back(osg::Vec2(texCoord, 1.0f));
            osg_texcoords->push_back(osg::Vec2(texCoord, 0.0f));

            osg_vertices->push_back(osg::Vec3(0.0f, topY, 0.0f));
            osg_vertices->push_back(osg::Vec3(e.imag(), bottomY, e.real()));
        }

        // do last point by hand to ensure no round off errors.

        osg::Vec3 normal(0.0f, radius, -height);
        normal.normalize();

        osg_normals->push_back(normal);
        osg_normals->push_back(normal);

        osg_texcoords->push_back(osg::Vec2(1.0f, 1.0f));
        osg_texcoords->push_back(osg::Vec2(1.0f, 0.0f));

        osg_vertices->push_back(osg::Vec3(0.0f, topY, 0.0f));
        osg_vertices->push_back(osg::Vec3(0.0f, bottomY, -radius));

        side->setCount(osg_vertices->size());
        osg_geom->addPrimitiveSet(side.get());
    }

    if (bottom)
    {
        osg::ref_ptr<osg::DrawArrays> bottom = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_FAN);

        size_t first = osg_vertices->size();
        bottom->setFirst(first);

        float theta = 0.0f;

        for (unsigned int i = 0; i < numSegments; ++i, theta += thetaDelta)
        {
            std::complex<float> n = -std::polar(1.0f, theta);
            std::complex<float> e = n * radius;

            osg_normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
            osg_texcoords->push_back(osg::Vec2(0.5f - 0.5f * n.imag(), 0.5f + 0.5f * n.real()));
            osg_vertices->push_back(osg::Vec3(-e.imag(), bottomY, e.real()));
        }

        // do last point by hand to ensure no round off errors.

        osg_normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
        osg_texcoords->push_back(osg::Vec2(0.5f, 0.0f));
        osg_vertices->push_back(osg::Vec3(0.0f, bottomY, -radius));

        bottom->setCount(osg_vertices->size() - first);
        osg_geom->addPrimitiveSet(bottom.get());
    }

    osg_geom->setVertexArray(osg_vertices.get());
    osg_geom->setTexCoordArray(0, osg_texcoords.get());
    osg_geom->setNormalArray(osg_normals.get());
    osg_geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);

    osg_geom->getOrCreateStateSet()->setAttributeAndModes(new osg::CullFace(osg::CullFace::BACK));

    m_coneLibrary[key] = osg_geom;

    return osg_geom.get();
}

osg::ref_ptr<osg::Geometry> ReaderWriterVRML2::convertVRML97Cylinder(openvrml::node* vrml_cylinder) const
{
    float height = static_cast<const openvrml::sffloat*>(vrml_cylinder->field("height").get())->value();
    float radius = static_cast<const openvrml::sffloat*>(vrml_cylinder->field("radius").get())->value();
    bool bottom = static_cast<const openvrml::sfbool*>(vrml_cylinder->field("bottom").get())->value();
    bool side = static_cast<const openvrml::sfbool*>(vrml_cylinder->field("side").get())->value();
    bool top = static_cast<const openvrml::sfbool*>(vrml_cylinder->field("top").get())->value();

    QuadricKey key(height, radius, bottom, side, top);

    CylinderLibrary::const_iterator it = m_cylinderLibrary.find(key);
    if (it != m_cylinderLibrary.end())
    {
        return (*it).second.get();
    }

    osg::ref_ptr<osg::Geometry> osg_geom = new osg::Geometry();
    osg::ref_ptr<osg::Vec3Array> osg_vertices = new osg::Vec3Array();
    osg::ref_ptr<osg::Vec2Array> osg_texcoords = new osg::Vec2Array();
    osg::ref_ptr<osg::Vec3Array> osg_normals = new osg::Vec3Array();

    unsigned int numSegments = 40;

    const float thetaDelta = 2.0f * float(osg::PI) / float(numSegments);

    float topY = height * 0.5f;
    float bottomY = height * -0.5f;

    if (side)
    {
        osg::ref_ptr<osg::DrawArrays> side = new osg::DrawArrays(osg::PrimitiveSet::QUAD_STRIP);

        const float texCoordDelta = 1.0f / float(numSegments);
        float theta = 0.0f;
        float texCoord = 0.0f;

        for (unsigned int i = 0; i < numSegments; ++i, theta += thetaDelta, texCoord += texCoordDelta)
        {
            std::complex<float> n = -std::polar(1.0f, theta);
            std::complex<float> e = n * radius;

            osg::Vec3 normal(n.imag(), 0.0f, n.real());

            osg_normals->push_back(normal);
            osg_normals->push_back(normal);

            osg_texcoords->push_back(osg::Vec2(texCoord, 1.0f));
            osg_texcoords->push_back(osg::Vec2(texCoord, 0.0f));

            osg_vertices->push_back(osg::Vec3(e.imag(), topY, e.real()));
            osg_vertices->push_back(osg::Vec3(e.imag(), bottomY, e.real()));
        }

        // do last point by hand to ensure no round off errors.

        osg::Vec3 normal(0.0f, 0.0f, -1.0f);
        osg_normals->push_back(normal);
        osg_normals->push_back(normal);

        osg_texcoords->push_back(osg::Vec2(1.0f, 1.0f));
        osg_texcoords->push_back(osg::Vec2(1.0f, 0.0f));

        osg_vertices->push_back(osg::Vec3(0.0f, topY, -radius));
        osg_vertices->push_back(osg::Vec3(0.0f, bottomY, -radius));

        side->setCount(osg_vertices->size());
        osg_geom->addPrimitiveSet(side.get());
    }

    if (bottom)
    {
        osg::ref_ptr<osg::DrawArrays> bottom = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_FAN);

        size_t first = osg_vertices->size();
        bottom->setFirst(first);

        float theta = 0.0f;

        for (unsigned int i = 0; i < numSegments; ++i, theta += thetaDelta)
        {
            std::complex<float> n = -std::polar(1.0f, theta);
            std::complex<float> e = n * radius;

            osg_normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
            osg_texcoords->push_back(osg::Vec2(0.5f - 0.5f * n.imag(), 0.5f + 0.5f * n.real()));
            osg_vertices->push_back(osg::Vec3(-e.imag(), bottomY, e.real()));
        }

        // do last point by hand to ensure no round off errors.

        osg_normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
        osg_texcoords->push_back(osg::Vec2(0.5f, 0.0f));
        osg_vertices->push_back(osg::Vec3(0.0f, bottomY, -radius));

        bottom->setCount(osg_vertices->size() - first);
        osg_geom->addPrimitiveSet(bottom.get());
    }

    if (top)
    {
        osg::ref_ptr<osg::DrawArrays> top = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_FAN);

        size_t first = osg_vertices->size();
        top->setFirst(first);

        float theta = 0.0f;

        for (unsigned int i = 0; i < numSegments; ++i, theta += thetaDelta)
        {
            std::complex<float> n = -std::polar(1.0f, theta);
            std::complex<float> e = n * radius;

            osg_normals->push_back(osg::Vec3(0.0f, 1.0f, 0.0f));
            osg_texcoords->push_back(osg::Vec2(0.5f + 0.5f * n.imag(), 0.5f - 0.5f * n.real()));
            osg_vertices->push_back(osg::Vec3(e.imag(), topY, e.real()));
        }

        // do last point by hand to ensure no round off errors.

        osg_normals->push_back(osg::Vec3(0.0f, 1.0f, 0.0f));
        osg_texcoords->push_back(osg::Vec2(0.5f, 1.0f));
        osg_vertices->push_back(osg::Vec3(0.0f, topY, -radius));

        top->setCount(osg_vertices->size() - first);
        osg_geom->addPrimitiveSet(top.get());
    }

    osg_geom->setVertexArray(osg_vertices.get());
    osg_geom->setTexCoordArray(0, osg_texcoords.get());
    osg_geom->setNormalArray(osg_normals.get());
    osg_geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);

    osg_geom->getOrCreateStateSet()->setAttributeAndModes(new osg::CullFace(osg::CullFace::BACK));

    m_cylinderLibrary[key] = osg_geom;

    return osg_geom.get();
}
