/*
 *  Copyright (c) 2000-2022 Inria
 *  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 ALICE 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 HOLDER 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.
 *
 *  Contact: Bruno Levy
 *
 *     https://www.inria.fr/fr/bruno-levy
 *
 *     Inria,
 *     Domaine de Voluceau,
 *     78150 Le Chesnay - Rocquencourt
 *     FRANCE
 *
 */

#ifndef RAYTRACING_H
#define RAYTRACING_H



#include <geogram/basic/geometry.h>
#include <geogram/mesh/mesh.h>
#include <geogram/mesh/mesh_io.h>
#include <geogram/mesh/mesh_geometry.h>
#include <geogram/mesh/mesh_AABB.h>

#ifdef RAYTRACE_GUI
#include <geogram_gfx/third_party/imgui/imgui.h>
#include <geogram_gfx/imgui_ext/imgui_ext.h>
#endif

// We have everything in the .h (a bit ugly but it is used
// by two demos, so it is simpler like that), this makes
// Clang complain about vtbls generation (make it ignore
// the warning).
#ifdef __clang__
#pragma GCC diagnostic ignored "-Wweak-vtables"
#endif

/**
 * \file raytracing.h
 * \brief Implementation of a simple raytracer, to demonstrate
 *   AABB usage. See MeshObject below.
 */

namespace GEO {

    /*******************************************************************/

    /**
     * \brief Normalizes the coordinates of a mesh
     *  in the unit box.
     * \details A uniform scaling is applied.
     * \param[in,out] M the mesh to be normalized.
     */
    inline void normalize_mesh(Mesh& M) {
        double mesh_xyz_min[3];
        double mesh_xyz_max[3];
        get_bbox(M, mesh_xyz_min, mesh_xyz_max);
        double dim[3];
        dim[0] = (mesh_xyz_max[0] - mesh_xyz_min[0]);
        dim[1] = (mesh_xyz_max[1] - mesh_xyz_min[1]);
        dim[2] = (mesh_xyz_max[2] - mesh_xyz_min[2]);
        double max_dim = std::max(dim[0], std::max(dim[1], dim[2]));
        double s = 1.0 / max_dim;
        double T[3];
        T[0] = 0.5 * (max_dim - dim[0]);
        T[1] = 0.5 * (max_dim - dim[1]);
        T[2] = 0.5 * (max_dim - dim[2]);
        for(index_t v=0; v<M.vertices.nb(); ++v) {
            double* p = M.vertices.point_ptr(v);
            double x = s * (T[0] + p[0] - mesh_xyz_min[0]);
            double y = s * (T[1] + p[1] - mesh_xyz_min[1]);
            double z = s * (T[2] + p[2] - mesh_xyz_min[2]);
            p[0] = x;
            p[1] = z;
            p[2] = 1.0-y;
        }
    }

    /*******************************************************************/

    class Object;

    /**
     * \brief The small constant to shift a little bit ray intersections
     *  in order to avoid false positives when detecting shadows.
     */
    const double epsilon_t = 1e-6;

    /**
     * \brief Multiplies two vec3 componentwise.
     * \param[in] U , V the two vec3 to be multiplied
     * \return the component-wise product
     */
    inline vec3 mul(const vec3& U, const vec3& V) {
        return vec3(
            U.x*V.x,
            U.y*V.y,
            U.z*V.z
        );
    }

    /*******************************************************************/

    /**
     * \brief The Camere.
     * \details Launches rays and stores the resulting image.
     */
    class Camera {
    public:

        /**
         * \brief Camera constructor.
         * \param[in] position the position of the camera.
         * \param[in] target a point that is looked at.
         * \param[in] image_width , image_height dimension of the image.
         * \param[in] zoom viewing angle of the camera, in degrees.
         */
        Camera(
            const vec3& position,
            const vec3& target,
            index_t image_width,
            index_t image_height,
            double zoom = 40.0
        ) :
            image_width_(image_width),
            image_height_(image_height),
            image_(image_width*image_height*3),
            bpp_(3)
            {
                update(position, target, zoom);
            }

        /**
         * \brief Camera constructor.
         * \details Viewing parameters are not initialized.
         * \param[in] image_width , image_height dimension of the image.
         * \param[in] bpp bytes per pixel, 3 or 4
         */
        Camera(
            index_t image_width, index_t image_height, index_t bpp
        ) : image_width_(image_width),
            image_height_(image_height),
            image_(image_width*image_height*bpp),
            bpp_(bpp)
            {
                geo_assert(bpp == 3 || bpp == 4);
            }

        /**
         * \brief Resizes the image.
         * \param[in] new_width , new_height new image size, in pixels.
         */
        void resize(index_t new_width, index_t new_height) {
            image_width_ = new_width;
            image_height_ = new_height;
            image_.resize(image_width_*image_height_*bpp_);
        }

        /**
         * \brief Updates the camera parameters.
         * \param[in] position the position of the camera.
         * \param[in] target a point that is looked at.
         * \param[in] zoom viewing angle of the camera, in degrees.
         */
        void update(
            const vec3& position,
            const vec3& target,
            double zoom = 20.0
        ) {
            position_ = position;
            target_ = target;

            // Viewing vector
            Z_ = normalize(target_ - position_);

            // Horizontal direction
            // We construct it as a vector both orthogonal
            // to Z_ and to the vertical direction (0 0 1).
            X_ = cross(Z_, vec3(0.0, 0.0, 1.0));

            // Vertical direction
            Y_ = cross(Z_,X_);

            // Coordinate of the viewing plane along viewing vector
            double zp =
                (double(image_height_) / 2.0) / tan(zoom * M_PI / 180.0);

            // Center of the viewing plane
            center_ = position_+zp*Z_;
        }

        /**
         * \brief gets the image width.
         * \return the image width, in pixels.
         */
        index_t image_width() const {
            return image_width_;
        }

        /**
         * \brief gets the image height.
         * \return the image height, in pixels.
         */
        index_t image_height() const {
            return image_height_;
        }

        /**
         * \brief gets the image data.
         * \return a raw pointer to the image data.
         */
        const Memory::byte* image_data() const {
            return image_.data();
        }

        /**
         * \brief Launches a primary ray.
         * \param[in] X , Y the pixel coordinates of the ray.
         * \return the launched ray.
         */
        Ray launch_ray(index_t X, index_t Y) const {
            vec3 pixel3d = center_ +
                (double(X) - double(image_width_/2 ))*X_ +
                (double(Y) - double(image_height_/2))*Y_ ;

            return Ray(
                position_,
                pixel3d - position_
            );
        }

        /**
         * \brief Sets a pixel of the image.
         * \param[in] X , Y the pixel coordinates of the ray.
         * \param[in] color the RGB color of the pixel, with components
         *  in [0.0,1.0]. Components larger than 1.0 are clamped to 1.0.
         */
        void set_pixel(index_t X, index_t Y, const vec3& color) {
            geo_debug_assert(X < image_width_);
            geo_debug_assert(Y < image_height_);
            Memory::byte* pixel_base = &image_[(Y*image_width_+X)*bpp_];
            pixel_base[0] = Memory::byte(std::min(color.x, 1.0)*255.0);
            pixel_base[1] = Memory::byte(std::min(color.y, 1.0)*255.0);
            pixel_base[2] = Memory::byte(std::min(color.z, 1.0)*255.0);
            if(bpp_ == 4) {
                pixel_base[3] = 255;
            }
        }

        /**
         * \brief Saves the image in PPM file format.
         * \param[in] filename the name of the file where to save the image.
         */
        void save_image(const std::string& filename) const {
            geo_assert(bpp_ == 3);
            FILE* f = fopen(filename.c_str(),"wb");
            if(f == nullptr) {
                std::cerr << "Could not create file: " << filename << std::endl;
                return;
            }
            fprintf(
                f,"P6 %d %d %d ", int(image_width_), int(image_height_), 255
            );
            fwrite(image_.data(), 1, image_.size(), f);
            fclose(f);
        }

    private:
        vec3 position_;
        vec3 target_;
        vec3 center_;
        vec3 X_;
        vec3 Y_;
        vec3 Z_;
        index_t image_width_;
        index_t image_height_;
        vector<Memory::byte> image_;
        index_t bpp_;
    };

    /*******************************************************************/

    /**
     * \brief A material.
     */
    struct Material {
        vec3 Kd; /**< Diffuse    */
        vec3 Kr; /**< Reflection */
        vec3 Ke; /**< Emmission  */
        Material():
            Kd(0.7, 0.7, 0.7),
            Kr(0.0, 0.0, 0.0),
            Ke(0.0, 0.0, 0.0) {
        }
        /**
         * \brief Tests whether this material is reflective.
         * \retval true if this material has non-zero Kr, false otherwise.
         */
        bool reflective() const {
            return (Kr.x != 0.0 || Kr.y != 0.0 || Kr.z != 0.0);
        }
        /**
         * \brief Tests whether this material is emissive.
         * \retval true if this material has non-zero Ke, false otherwise.
         */
        bool emissive() const {
            return (Ke.x != 0.0 || Ke.y != 0.0 || Ke.z != 0.0);
        }
    };

    /*******************************************************************/

    /**
     * \brief A Ray-Object intersection.
     */
    struct Intersection {
        /**
         * \brief Intersection default constructor.
         */
        Intersection() :
            t(Numeric::max_float64()),
            object(nullptr),
            K(0.1, 0.1, 0.1) {
        }
        vec3 position; /**< position of the intersection. */
        vec3 normal; /**< normal to the object. */
        double t; /**< ray parameter of the intersection. */
        const Object* object; /**< intersected object. */
        vec3 K; /**< current computed ray color. */
        Material material; /**< current material. */
    };

    /*******************************************************************/

    /**
     * \brief An object that can be raytraced.
     */
    class Object {
    public:

        /**
         * \brief Object destructor.
         */
        virtual ~Object() {}

        /**
         * \brief Computes the intersection with a ray.
         * \details If there is an intersection and if it is nearer
         *  than the previous one, then replace it.
         * \param[in] R the ray
         * \param[in,out] I the nearest intersection along the ray.
         */
        virtual void get_nearest_intersection(
            const Ray& R, Intersection& I
        ) const = 0;

        /**
         * \brief Tests whether this object shadows a ray.
         * \details This object shadows the ray R if there is an
         *  intersection between R.origin and R.origin + R.direction.
         *  Intersections further away than R.origin + R.direction
         *  are ignored.
         * \param[in] R the ray. R.origin corresponds to a point
         *  queried for shadow. R.origin + R.direction corresponds to
         *  the light-source.
         */
        virtual bool in_shadow(const Ray& R) const = 0;

        /**
         * \brief Sets the diffuse coefficient.
         * \param[in] K the diffuse coefficient.
         * \return a pointer to the Object, to allow chaining operations.
         */
        Object* set_diffuse_coefficient(const vec3& K) {
            material_.Kd = K;
            return this;
        }

        /**
         * \brief Sets the reflection coefficient.
         * \param[in] K the reflection coefficient.
         * \return a pointer to the Object, to allow chaining operations.
         */
        Object* set_reflection_coefficient(const vec3& K) {
            material_.Kr = K;
            return this;
        }

        /**
         * \brief Sets the emission coefficient.
         * \param[in] K the emission coefficient.
         * \return a pointer to the Object, to allow chaining operations.
         */
        Object* set_emission_coefficient(const vec3& K) {
            material_.Ke = K;
            return this;
        }

        /**
         * \brief Gets the Material.
         * \return a const reference to the Material.
         */
        const Material& material() const {
            return material_;
        }

        /**
         * \brief Gets the Material.
         * \return a modifiable reference to the Material.
         */
        Material& material() {
            return material_;
        }

        Object* rename(const std::string& name) {
            name_ = name;
            return this;
        }

        const std::string& name() const {
            return name_;
        }

#ifdef RAYTRACE_GUI

        bool edit_color(const std::string& name, vec3& K) {
            float Kf[3];
            Kf[0] = float(K.x);
            Kf[1] = float(K.y);
            Kf[2] = float(K.z);
            bool result = ImGui::ColorEdit3WithPalette(name.c_str(), Kf);
            if(result) {
                K.x = double(Kf[0]);
                K.y = double(Kf[1]);
                K.z = double(Kf[2]);
            }
            return result;
        }

        bool edit_vector(const std::string& name, vec3& V) {
            float Vf[3];
            Vf[0] = float(V.x);
            Vf[1] = float(V.y);
            Vf[2] = float(V.z);
            ImGui::SetNextItemWidth(-ImGui::CalcTextSize(name.c_str()).x);
            bool result = ImGui::DragFloat3(
                name.c_str(), Vf, 0.1f, 0.0f, 0.0f, "%.3f"
            );
            if(result) {
                V.x = double(Vf[0]);
                V.y = double(Vf[1]);
                V.z = double(Vf[2]);
            }
            return result;
        }

        bool edit_scalar(const std::string& name, double& V) {
            double zero = 0.0;
            ImGui::SetNextItemWidth(-ImGui::CalcTextSize(name.c_str()).x);
            return ImGui::DragScalar(
                name.c_str(),
                ImGuiDataType_Double,
                &V,
                0.005f,
                &zero,
                nullptr,
                "%.3f"
            );
        }

        /**
         * \brief Draws and handle the GUI.
         * \retval true if an element was changed.
         * \retval false otherwise.
         */
        virtual bool draw_gui() {
            ImGui::PushID(this);
            bool result = false;
            ImGui::Separator();
            if(ImGui::Button("X")) {
                to_delete_ = this;
            }
            ImGui::SameLine();
            ImGui::Text("%s", name().c_str());
            if(edit_color("Diffuse", material().Kd)) {
                result = true;
            }
            if(edit_color("Reflect", material().Kr)) {
                result = true;
            }
            ImGui::PopID();
            return result;
        }
#endif

    protected:
        std::string name_;
        Material material_;
        static Object* to_delete_;
    };

    Object* Object::to_delete_ = nullptr;

    /*******************************************************************/

    /**
     * \brief A sphere object.
     */
    class Sphere : public Object {
    public:

        /**
         * \brief Sphere constructor.
         * \param[in] center the center of the sphere.
         * \param[in] radius the radius of the sphere.
         */
        Sphere(const vec3& center, double radius) :
            center_(center), radius_(radius) {
        }

        /**
         * \brief Gets the center.
         * \return the center of the sphere.
         */
        const vec3& center() const {
            return center_;
        }

        /**
         * \brief Gets the radius.
         * \return the radius of the sphere.
         */
        double radius() const {
            return radius_;
        }

        /**
         * \copydoc Object::get_nearest_intersection()
         */
        void get_nearest_intersection(
            const Ray& R, Intersection& I
        ) const override {
            double t = get_intersection_t(R);
            if(t > epsilon_t && t < I.t) {
                I.t = t;
                I.object = this;
                I.material = material_;
                I.position = R.origin + t * R.direction;
                I.normal = normalize(I.position - center_);
            }
        }

        /**
         * \copydoc Object::in_shadow()
         */
        bool in_shadow(const Ray& R) const override {
            double t = get_intersection_t(R);
            return (t > 0.0 && t < 1.0);
        }

#ifdef RAYTRACE_GUI
        /**
         * \copydoc Object::draw_gui()
         */
        bool draw_gui() override {
            ImGui::PushID(this);
            bool result = Object::draw_gui();
            if(edit_vector("C", center_)) {
                result = true;
            }
            if(edit_scalar("R", radius_)) {
                result = true;
            }
            ImGui::PopID();
            return result;
        }
#endif


    protected:

        /**
         * \brief Gets the coordinate of the intersection between
         *   this sphere and a ray.
         * \return the coordinate of the intersection along \p R
         *   or a negative number if there is no intersection.
         */
        double get_intersection_t(const Ray& R) const {
            // Detail of the computation:
            // M = O + tD                        (1) (parametric ray eqn)
            // (M-C)^2 = M^2 - 2M.C + C^2 = R^2  (2) (implicit sphere eqn)
            //(O + tD)^2 - 2(O+tD).C + C^2 = R^2     (inject (1) into (2)
            // O^2 + 2tO.D + t^2D^2 -2O.C -2tD.C + C^2 = R^2
            // t^2 (D^2) + 2t D.(O-C) + O^2 - 2 O.C + C^2 - R^2 = 0
            // t^2 (D^2) + 2t D.(O-C) + (O-C)^2 - R^2 = 0
            // This is a quadratic equation in t (a t^2 + b t + c = 0),
            // let us solve it for t now !

            double t = -1.0;

            vec3 CO = R.origin - center_;
            double a = length2(R.direction);
            double b = 2.0*dot(R.direction,CO);
            double c = length2(CO) - radius_*radius_;
            double delta = b*b - 4.0 * a * c;

            if(delta < 0.0) {
                return -1.0;
            }
            double sqrt_delta = sqrt(delta);
            t = (-b-sqrt_delta) / (2.0 * a);
            if(t > 0) {
                return t;
            }
            t = (-b+sqrt_delta) / (2.0 * a);
            return t;
        }


    protected:
        vec3 center_;
        double radius_;
    };

    /*******************************************************************/

    /**
     * \brief Light object.
     * \details A Light appears as a colored sphere. The radius does not play
     *  a role in the lighting, this is just a point light.
     */
    class Light : public Sphere {
    public:
        /**
         * \brief Light constructor.
         * \param[in] center the position of the light.
         * \param[in] R the radius.
         * \param[in] K the color of the light.
         */
        Light(const vec3& center, double R, const vec3& K) : Sphere(center, R) {
            material_.Kd = vec3(0.0, 0.0, 0.0);
            material_.Kr = vec3(0.0, 0.0, 0.0);
            material_.Ke = K;
            on_ = true;
        }

        bool on() const {
            return on_;
        }


        /**
         * \copydoc Object::get_nearest_intersection()
         */
        void get_nearest_intersection(
            const Ray& R, Intersection& I
        ) const override {
            Sphere::get_nearest_intersection(R,I);
            if(!on()) {
                I.material.Ke = vec3(0.0, 0.0, 0.0);
            }
        }

#ifdef RAYTRACE_GUI
        /**
         * \copydoc Object::draw_gui()
         */
        bool draw_gui() override {
            ImGui::PushID(this);
            bool result = false;
            ImGui::Separator();
            if(ImGui::Button("X")) {
                to_delete_ = this;
            }
            ImGui::SameLine();
            ImGui::Text("%s", name().c_str());
            if(ImGui::Checkbox("##On", &on_)) {
                result = true;
            }
            ImGui::SameLine();
            if(edit_color("Emit.", material().Ke)) {
                result = true;
            }
            if(edit_vector("C", center_)) {
                result = true;
            }
            if(edit_scalar("R", radius_)) {
                result = true;
            }
            ImGui::PopID();
            return result;
        }
#endif

    private:
        bool on_;

    };

    /*******************************************************************/

    /**
     * \brief Mesh object.
     * \details Optimized ray-mesh intersections computed using an axis-aligned
     *  bounding box tree (geogram's MeshAABB).
     */
    class MeshObject : public Object {
    public:
        /**
         * \brief MeshObject constructor.
         * \param[in] filename the name of the file that contains the mesh.
         * \param[in] normalize if set, the mesh is normalized in
         *  the unit box after loading.
         */
        MeshObject(const std::string& filename, bool normalize=true) {
            mesh_load(filename, mesh_);
            if(normalize) {
                normalize_mesh(mesh_);
            }
            AABB_.initialize(mesh_);
        }

        /**
         * \copydoc Object::get_nearest_intersection()
         */
        void get_nearest_intersection(
            const Ray& R, Intersection& I
        ) const override {
            MeshFacetsAABB::Intersection cur_I;
            if(AABB_.ray_nearest_intersection(R, cur_I)) {
                if(cur_I.t > epsilon_t && cur_I.t < I.t) {
                    I.t = cur_I.t;
                    I.object = this;
                    I.material = material_;
                    I.position = cur_I.p;
                    I.normal = normalize(cur_I.N);
                }
            }
        }

        /**
         * \copydoc Object::in_shadow()
         */
        bool in_shadow(const Ray& R) const override {
            vec3 p2 = R.origin + R.direction;
            return AABB_.segment_intersection(R.origin, p2);
        }

    private:
        Mesh mesh_;
        MeshFacetsAABB AABB_;
    };

    /*******************************************************************/

    /**
     * \brief The traditional checkerboard.
     * \details Cannot avoid to have this in a raytracer
     *   (this is the tradition).
     */
    class HorizontalCheckerboardPlane : public Object {
    public:

        /**
         * \brief HorizontalCheckerboardPlane constructor;
         * \param[in] z altitude of the plane.
         */
        HorizontalCheckerboardPlane(double z) : Z_(z) {
        }

        /**
         * \copydoc Object::get_nearest_intersection()
         */
        void get_nearest_intersection(
            const Ray& R, Intersection& I
        ) const override {
            if(R.direction.z != 0.0) {
                double t = (Z_ - R.origin.z) / R.direction.z;
                if(t > epsilon_t && t < I.t) {
                    I.t = t;
                    I.position = R.origin + t * R.direction;
                    int X = int((I.position.x + 1000.0)* 2.0);
                    int Y = int((I.position.y + 1000.0)* 2.0);
                    double color = (((X&1) ^ (Y&1)) == 0) ? 0.0 : 1.0;
                    I.object = this;
                    I.material = material_;
                    I.material.Kd.x *= color;
                    I.material.Kd.y *= color;
                    I.material.Kd.z *= color;
                    I.normal = vec3(0.0, 0.0, 1.0);
                }
            }
        }

        /**
         * \copydoc Object::in_shadow()
         */
        bool in_shadow(const Ray& R) const override {
            if(R.direction.z == 0.0) {
                return false;
            }
            double t = (Z_ - R.origin.z) / R.direction.z;
            return (t >= 0.0 && t <= 1.0);
        }

    private:
        double Z_;
    };

    /*******************************************************************/

    /**
     * \brief A scene to be ray-traced.
     */
    class Scene : public Object {
    public:

        /**
         * \brief Scene destructor.
         */
        ~Scene() override {
            for(index_t i=0; i<objects_.size(); ++i) {
                delete objects_[i];
            }
        }

        /**
         * \brief Adds an object to the scene.
         * \param[in] O a pointer to the object to be added.
         *  Pointer ownership is transfered to this Scene.
         */
        Object* add_object(Object* O) {
            objects_.push_back(O);
            Light* L = dynamic_cast<Light*>(O);
            if(L == nullptr) {
                real_objects_.push_back(O);
            } else {
                lights_.push_back(L);
            }
            if(O->name() == "") {
                O->rename("object " + String::to_string(objects_.size()));
            }
            return O;
        }


        /**
         * \brief Removes an object from the scene.
         * \param[in] o the object to be removed.
         * \details this does not deallocates the object.
         */
        void remove_object(Object* o) {
            for(index_t i=0; i<objects_.size(); ++i) {
                if(objects_[i] == o) {
                    objects_.erase(objects_.begin() + std::ptrdiff_t(i));
                    return;
                }
            }
            geo_assert_not_reached;
        }

        /**
         * \brief Gets the number of objects in the scene.
         * \return the number of objects, comprising lights.
         */
        index_t nb_objects() const {
            return objects_.size();
        }

        /**
         * \brief Gets an object by index.
         * \param[in] i the index, in [0..nb_objects()-1]
         * \return a pointer to the ith object.
         */
        Object* ith_object(index_t i) {
            return objects_[i];
        }

        /**
         * \copydoc Object::get_nearest_intersection()
         */
        void get_nearest_intersection(
            const Ray& R, Intersection& I
        ) const override {
            for(index_t i=0; i<objects_.size(); ++i) {
                objects_[i]->get_nearest_intersection(R,I);
            }
        }

        /**
         * \copydoc Object::in_shadow()
         */
        bool in_shadow(const Ray& R) const override {
            for(index_t i=0; i<real_objects_.size(); ++i) {
                if(real_objects_[i]->in_shadow(R)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * \brief Computes the lighting at an intersection.
         * \details Launches shadow rays to the light sources.
         * \param[in,out] I a reference to the intersection.
         */
        void compute_lighting(Intersection& I) const {
            if(I.material.emissive()) {
                I.K = I.material.Ke;
            } else {
                for(index_t i=0; i<lights_.size(); ++i) {
                    if(!lights_[i]->on()) {
                        continue;
                    }
                    vec3 L = lights_[i]->center() - I.position;
                    if(!in_shadow(Ray(I.position + epsilon_t*L, L))) {
                        double Lambert = dot(I.normal, L);
                        if(Lambert > 0.0) {
                            Lambert /= length(L);
                            I.K += Lambert*mul(
                                I.material.Kd,lights_[i]->material().Ke
                            );
                        }
                    }
                }
            }
        }

        /**
         * \brief Launches a ray and computes the color.
         * \details Reflected rays are recursively computed.
         * \param[in] R the ray to be launched.
         * \return the computed color.
         */
        vec3 raytrace(const Ray& R, index_t level=0) const {
            Intersection I;
            get_nearest_intersection(R,I);
            if(I.object != nullptr) {
                compute_lighting(I);
                if(I.material.reflective() && level < 3) {
                    vec3 D = R.direction;
                    Ray Reflected(
                        I.position,
                        D - 2.0*dot(D, I.normal)*I.normal
                    );
                    vec3 Kreflect = raytrace(Reflected,level+1);
                    I.K += mul(I.material.Kr, Kreflect);
                }
            }
            return I.K;
        }

#ifdef RAYTRACE_GUI
        /**
         * \copydoc Object::draw_gui()
         */
        virtual bool draw_gui() override {
            bool result = false;

            for(index_t i=0; i<objects_.size(); ++i) {
                if(to_delete_ == objects_[i]) {
                    delete objects_[i];
                    objects_.erase(objects_.begin() + std::ptrdiff_t(i));
                    result = true;
                    break;
                }
            }

            for(index_t i=0; i<real_objects_.size(); ++i) {
                if(to_delete_ == real_objects_[i]) {
                    real_objects_.erase(
                        real_objects_.begin() + std::ptrdiff_t(i)
                    );
                    break;
                }
            }

            for(index_t i=0; i<lights_.size(); ++i) {
                if(to_delete_ == lights_[i]) {
                    lights_.erase(lights_.begin() + std::ptrdiff_t(i));
                    break;
                }
            }

            to_delete_ = nullptr;

            for(index_t i=0; i<objects_.size(); ++i) {
                if(objects_[i]->draw_gui()) {
                    result = true;
                }
            }
            return result;
        }
#endif

    private:
        vector<Object*> objects_; /**< all the objects. */
        vector<Object*> real_objects_; /**< all the objects but the lights. */
        vector<Light*> lights_; /**< the lights are here. */
    };

    /*******************************************************************/
    /** Utilities                                                      */
    /*******************************************************************/

    /**
     * \brief Sets a 4x4 homogeneous transform matrix from a translation
     *  and a quaternion.
     * \param[out] M the matrix.
     * \param[in] Tx , Ty , Tz the translation.
     * \param[in] Qx , Qy , Qz , Qw the quaternion.
     */
    inline void set_mat4_from_translation_and_quaternion(
        mat4& M,
        double Tx, double Ty, double Tz,
        double Qx, double Qy, double Qz, double Qw
    ) {
        // for unit q, just set s = 2 or set xs = Qx + Qx, etc.
        double s = 2.0 / (Qx*Qx + Qy*Qy + Qz*Qz + Qw*Qw);

        double xs = Qx * s;
        double ys = Qy * s;
        double zs = Qz * s;

        double wx = Qw * xs;
        double wy = Qw * ys;
        double wz = Qw * zs;

        double xx = Qx * xs;
        double xy = Qx * ys;
        double xz = Qx * zs;

        double yy = Qy * ys;
        double yz = Qy * zs;
        double zz = Qz * zs;

        M(0,0) = 1.0 - (yy + zz);
        M(0,1) = xy - wz;
        M(0,2) = xz + wy;
        M(0,3) = 0.0;

        M(1,0) = xy + wz;
        M(1,1) = 1 - (xx + zz);
        M(1,2) = yz - wx;
        M(1,3) = 0.0;

        M(2,0) = xz - wy;
        M(2,1) = yz + wx;
        M(2,2) = 1 - (xx + yy);
        M(2,3) = 0.0;

        M(3,0) = Tx;
        M(3,1) = Ty;
        M(3,2) = Tz;
        M(3,3) = 1.0;
    }

    /**
     * \brief Transform a 3d point by an affine transform
     *  stored in a homogeneous 4x4 matrix.
     * \param[in] v the 3d point to be transformed.
     * \param[in] m the 4x4 matrix.
     * \return the transformed point.
     */
    inline vec3 transform_point(
        const vec3& v,
        const mat4& m
    ){
        index_t i,j ;
        double result[4] ;

        for(i=0; i<4; i++) {
            result[i] = 0 ;
        }
        for(i=0; i<4; i++) {
            for(j=0; j<3; j++) {
                result[i] += v[j] * m(j,i) ;
            }
            result[i] += m(3,i);
        }

        return vec3(
            result[0] / result[3],
            result[1] / result[3],
            result[2] / result[3]
        ) ;
    }

    inline vec3 random_color() {
        vec3 result;
        while(length2(result) < 0.1) {
            result = vec3(
                Numeric::random_float64(),
                Numeric::random_float64(),
                Numeric::random_float64()
            );
        }
        return result;
    }
}


#endif
