Example #1
0
OSL::Matrix44 ShadingPoint::OSLObjectTransformInfo::get_inverse_transform(float t) const
{
    const Transformd assembly_xform = m_assembly_instance_transform->evaluate(t);
    const Transformd::MatrixType m(
        m_object_instance_transform->get_parent_to_local() * assembly_xform.get_parent_to_local());

    return Matrix4f(transpose(m));
}
        double get_autofocus_focal_distance(const Intersector& intersector) const
        {
            // The autofocus considers the scene at the middle of the shutter interval.
            const float time = get_shutter_middle_time();
            const Transformd transform = m_transform_sequence.evaluate(time);

            // Create a ray that goes through the center of the lens.
            ShadingRay ray;
            ray.m_org = transform.get_local_to_parent().extract_translation();
            ray.m_dir = normalize(transform.vector_to_parent(-ndc_to_camera(m_autofocus_target)));
            ray.m_tmin = 0.0;
            ray.m_tmax = numeric_limits<double>::max();
            ray.m_time =
                ShadingRay::Time::create_with_normalized_time(
                    0.5f,
                    get_shutter_open_time(),
                    get_shutter_close_time());
            ray.m_flags = VisibilityFlags::ProbeRay;
            ray.m_depth = 0;

            // Trace the ray.
            ShadingPoint shading_point;
            intersector.trace(ray, shading_point);

            if (shading_point.hit())
            {
                // Hit: compute the focal distance.
                const Vector3d v = shading_point.get_point() - ray.m_org;
                const double af_focal_distance = -transform.vector_to_local(v).z;

                RENDERER_LOG_INFO(
                    "camera \"%s\": autofocus sets focal distance to %f (using camera position at time=%.1f).",
                    get_path().c_str(),
                    af_focal_distance,
                    ray.m_time.m_absolute);

                return af_focal_distance;
            }
            else
            {
                // Miss: focus at infinity.
                RENDERER_LOG_INFO(
                    "camera \"%s\": autofocus sets focal distance to infinity (using camera position at time=%.1f).",
                    get_path().c_str(),
                    ray.m_time.m_absolute);

                return 1.0e38;
            }
        }
Example #3
0
        double get_autofocus_focal_distance(const Intersector& intersector) const
        {
            // The autofocus considers the scene at the middle of the shutter interval.
            const double time = get_shutter_middle_time();
            const Transformd transform = m_transform_sequence.evaluate(time);

            // Compute the camera space coordinates of the focus point.
            const Vector3d film_point = ndc_to_camera(m_autofocus_target);

            // Create a ray in world space.
            ShadingRay ray;
            ray.m_org = transform.point_to_parent(Vector3d(0.0));
            ray.m_dir = transform.point_to_parent(film_point) - ray.m_org;
            ray.m_tmin = 0.0;
            ray.m_tmax = numeric_limits<double>::max();
            ray.m_time = time;
            ray.m_type = ShadingRay::ProbeRay;
            ray.m_depth = 0;

            // Trace the ray.
            ShadingPoint shading_point;
            intersector.trace(ray, shading_point);

            if (shading_point.hit())
            {
                // Hit: compute the focal distance.
                const Vector3d v = shading_point.get_point() - ray.m_org;
                const double af_focal_distance = -transform.vector_to_local(v).z;

                RENDERER_LOG_INFO(
                    "camera \"%s\": autofocus sets focal distance to %f (using camera position at time=%.1f).",
                    get_name(),
                    af_focal_distance,
                    ray.m_time);

                return af_focal_distance;
            }
            else
            {
                // Miss: focus at infinity.
                RENDERER_LOG_INFO(
                    "camera \"%s\": autofocus sets focal distance to infinity (using camera position at time=%.1f).",
                    get_name(),
                    ray.m_time);

                return 1.0e38;
            }
        }
Example #4
0
        void compute_radiance(
            InputEvaluator&     input_evaluator,
            const Transformd&   light_transform,
            const Vector3d&     axis,
            const Vector3d&     outgoing,
            Spectrum&           radiance) const
        {
            const Vector3d up = light_transform.vector_to_parent(m_up);
            const Vector3d v = -axis;
            const Vector3d u = normalize(cross(up, v));
            const Vector3d n = cross(v, u);

            const double cos_theta = dot(outgoing, axis);
            assert(cos_theta > m_cos_outer_half_angle);

            const Vector3d d = outgoing / cos_theta - axis;
            const float x = static_cast<float>(dot(d, u) * m_rcp_screen_half_size);
            const float y = static_cast<float>(dot(d, n) * m_rcp_screen_half_size);
            const Vector2f uv(0.5f * (x + 1.0f), 0.5f * (y + 1.0f));

            const InputValues* values = input_evaluator.evaluate<InputValues>(m_inputs, uv);
            radiance = values->m_intensity;
            radiance *= static_cast<float>(values->m_intensity_multiplier);

            if (cos_theta < m_cos_inner_half_angle)
            {
                radiance *=
                    static_cast<float>(
                        smoothstep(m_cos_outer_half_angle, m_cos_inner_half_angle, cos_theta));
            }
        }
Example #5
0
        void sample_disk(
            const Transformd&       light_transform,
            const Vector2d&         s,
            const Vector3d&         disk_center,
            const double            disk_radius,
            Vector3d&               position,
            Vector3d&               outgoing,
            Spectrum&               value,
            double&                 probability) const
        {
            outgoing = -normalize(light_transform.get_parent_z());

            const Basis3d basis(outgoing);
            const Vector2d p = sample_disk_uniform(s);

            position =
                  disk_center
                - m_safe_scene_diameter * basis.get_normal()
                + disk_radius * p[0] * basis.get_tangent_u()
                + disk_radius * p[1] * basis.get_tangent_v();

            probability = 1.0 / (Pi * disk_radius * disk_radius);

            compute_sun_radiance(
                outgoing,
                m_values.m_turbidity,
                m_values.m_radiance_multiplier,
                value);

            value *= SunSolidAngle;
        }
Example #6
0
    void add_cube(
        Assembly&           assembly,
        const size_t        ix,
        const size_t        iy,
        const double        fx,
        const double        fz,
        const Color3b&      color,
        const Transformd&   transform) override
    {
        // Push vertices.
        const size_t base_vertex_index = m_mesh->get_vertex_count();
        for (size_t i = 0; i < m_cube->get_vertex_count(); ++i)
        {
            m_mesh->push_vertex(
                transform.point_to_parent(m_cube->get_vertex(i)));
        }

        // Push normals.
        const size_t base_vertex_normal_index = m_mesh->get_vertex_normal_count();
        for (size_t i = 0; i < m_cube->get_vertex_normal_count(); ++i)
        {
            m_mesh->push_vertex_normal(
                normalize(
                    transform.normal_to_parent(m_cube->get_vertex_normal(i))));
        }

        // Push texture coordinates.
        const size_t tex_coords_index =
            m_mesh->push_tex_coords(
                GVector2(static_cast<float>(fx), static_cast<float>(1.0 - fz)));

        // Push triangles.
        for (size_t i = 0; i < m_cube->get_triangle_count(); ++i)
        {
            Triangle triangle = m_cube->get_triangle(i);
            triangle.m_v0 += static_cast<uint32>(base_vertex_index);
            triangle.m_v1 += static_cast<uint32>(base_vertex_index);
            triangle.m_v2 += static_cast<uint32>(base_vertex_index);
            triangle.m_n0 += static_cast<uint32>(base_vertex_normal_index);
            triangle.m_n1 += static_cast<uint32>(base_vertex_normal_index);
            triangle.m_n2 += static_cast<uint32>(base_vertex_normal_index);
            triangle.m_a0 = triangle.m_a1 = triangle.m_a2 = static_cast<uint32>(tex_coords_index);
            m_mesh->push_triangle(triangle);
        }
    }
bool TransformSequence::swaps_handedness(const Transformd& xform) const
{
    if (!m_can_swap_handedness)
        return false;

    if (m_all_swap_handedness)
        return true;

    return xform.swaps_handedness();
}
        Vector3d compute_ray_direction(
            const Vector2d&     film_point,         // NDC
            const Vector3d&     lens_point,         // world space
            const Transformd&   transform) const
        {
            // Compute film point in camera space.
            const Vector3d film_point_cs = ndc_to_camera(film_point);

            // Compute focal point in world space.
            const Vector3d focal_point = transform.point_to_parent(-m_focal_ratio * film_point_cs);

            // Return ray direction in world space.
            return normalize(focal_point - lens_point);
        }
Example #9
0
void LightSampler::collect_emitting_triangles(
    const Assembly&         assembly,
    const AssemblyInstance& assembly_instance)
{
    // Loop over the object instances of the assembly.
    const size_t object_instance_count = assembly.object_instances().size();
    for (size_t object_instance_index = 0; object_instance_index < object_instance_count; ++object_instance_index)
    {
        // Retrieve the object instance.
        const ObjectInstance* object_instance = assembly.object_instances().get_by_index(object_instance_index);

        // Retrieve the materials of the object instance.
        const MaterialArray& front_materials = object_instance->get_front_materials();
        const MaterialArray& back_materials = object_instance->get_back_materials();

        // Skip object instances without light-emitting materials.
        if (!has_emitting_materials(front_materials) && !has_emitting_materials(back_materials))
            continue;

        // Compute the object space to world space transformation.
        // todo: add support for moving light-emitters.
        const Transformd& object_instance_transform = object_instance->get_transform();
        const Transformd assembly_instance_transform =
            assembly_instance.transform_sequence().empty()
                ? Transformd::identity()
                : assembly_instance.transform_sequence().earliest_transform();
        const Transformd global_transform = assembly_instance_transform * object_instance_transform;

        // Retrieve the object.
        Object& object = object_instance->get_object();

        // Retrieve the region kit of the object.
        Access<RegionKit> region_kit(&object.get_region_kit());

        // Loop over the regions of the object.
        const size_t region_count = region_kit->size();
        for (size_t region_index = 0; region_index < region_count; ++region_index)
        {
            // Retrieve the region.
            const IRegion* region = (*region_kit)[region_index];

            // Retrieve the tessellation of the region.
            Access<StaticTriangleTess> tess(&region->get_static_triangle_tess());

            // Loop over the triangles of the region.
            const size_t triangle_count = tess->m_primitives.size();
            for (size_t triangle_index = 0; triangle_index < triangle_count; ++triangle_index)
            {
                // Fetch the triangle.
                const Triangle& triangle = tess->m_primitives[triangle_index];

                // Skip triangles without a material.
                if (triangle.m_pa == Triangle::None)
                    continue;

                // Fetch the materials assigned to this triangle.
                const size_t pa_index = static_cast<size_t>(triangle.m_pa);
                const Material* front_material =
                    pa_index < front_materials.size() ? front_materials[pa_index] : 0;
                const Material* back_material =
                    pa_index < back_materials.size() ? back_materials[pa_index] : 0;

                // Skip triangles that don't emit light.
                if ((front_material == 0 || front_material->get_uncached_edf() == 0) &&
                    (back_material == 0 || back_material->get_uncached_edf() == 0))
                    continue;

                // Retrieve object instance space vertices of the triangle.
                const GVector3& v0_os = tess->m_vertices[triangle.m_v0];
                const GVector3& v1_os = tess->m_vertices[triangle.m_v1];
                const GVector3& v2_os = tess->m_vertices[triangle.m_v2];

                // Transform triangle vertices to assembly space.
                const GVector3 v0_as = object_instance_transform.point_to_parent(v0_os);
                const GVector3 v1_as = object_instance_transform.point_to_parent(v1_os);
                const GVector3 v2_as = object_instance_transform.point_to_parent(v2_os);

                // Compute the support plane of the hit triangle in assembly space.
                const GTriangleType triangle_geometry(v0_as, v1_as, v2_as);
                TriangleSupportPlaneType triangle_support_plane;
                triangle_support_plane.initialize(TriangleType(triangle_geometry));

                // Transform triangle vertices to world space.
                const Vector3d v0(assembly_instance_transform.point_to_parent(v0_as));
                const Vector3d v1(assembly_instance_transform.point_to_parent(v1_as));
                const Vector3d v2(assembly_instance_transform.point_to_parent(v2_as));

                // Compute the geometric normal to the triangle and the area of the triangle.
                Vector3d geometric_normal = cross(v1 - v0, v2 - v0);
                const double geometric_normal_norm = norm(geometric_normal);
                if (geometric_normal_norm == 0.0)
                    continue;
                const double rcp_geometric_normal_norm = 1.0 / geometric_normal_norm;
                const double rcp_area = 2.0 * rcp_geometric_normal_norm;
                const double area = 0.5 * geometric_normal_norm;
                geometric_normal *= rcp_geometric_normal_norm;
                assert(is_normalized(geometric_normal));

                // Retrieve object instance space vertex normals.
                const GVector3& n0_os = tess->m_vertex_normals[triangle.m_n0];
                const GVector3& n1_os = tess->m_vertex_normals[triangle.m_n1];
                const GVector3& n2_os = tess->m_vertex_normals[triangle.m_n2];

                // Transform vertex normals to world space.
                const Vector3d n0(normalize(global_transform.normal_to_parent(n0_os)));
                const Vector3d n1(normalize(global_transform.normal_to_parent(n1_os)));
                const Vector3d n2(normalize(global_transform.normal_to_parent(n2_os)));

                for (size_t side = 0; side < 2; ++side)
                {
                    const Material* material = side == 0 ? front_material : back_material;
                    const Vector3d side_geometric_normal = side == 0 ? geometric_normal : -geometric_normal;
                    const Vector3d side_n0 = side == 0 ? n0 : -n0;
                    const Vector3d side_n1 = side == 0 ? n1 : -n1;
                    const Vector3d side_n2 = side == 0 ? n2 : -n2;

                    // Skip sides without a material.
                    if (material == 0)
                        continue;

                    const EDF* edf = material->get_uncached_edf();

                    // Skip sides without a light-emitting material.
                    if (edf == 0)
                        continue;

                    // Create a light-emitting triangle.
                    EmittingTriangle emitting_triangle;
                    emitting_triangle.m_assembly_instance = &assembly_instance;
                    emitting_triangle.m_object_instance_index = object_instance_index;
                    emitting_triangle.m_region_index = region_index;
                    emitting_triangle.m_triangle_index = triangle_index;
                    emitting_triangle.m_v0 = v0;
                    emitting_triangle.m_v1 = v1;
                    emitting_triangle.m_v2 = v2;
                    emitting_triangle.m_n0 = side_n0;
                    emitting_triangle.m_n1 = side_n1;
                    emitting_triangle.m_n2 = side_n2;
                    emitting_triangle.m_geometric_normal = side_geometric_normal;
                    emitting_triangle.m_triangle_support_plane = triangle_support_plane;
                    emitting_triangle.m_rcp_area = rcp_area;
                    emitting_triangle.m_edf = edf;

                    // Store the light-emitting triangle.
                    const size_t emitting_triangle_index = m_emitting_triangles.size();
                    m_emitting_triangles.push_back(emitting_triangle);

                    // Insert the light-emitting triangle into the CDFs.
                    m_emitter_cdf.insert(emitting_triangle_index + m_lights.size(), area);
                    m_emitting_triangle_cdf.insert(emitting_triangle_index, area);

                    // Keep track of the total area of the light-emitting triangles.
                    m_total_emissive_area += area;
                }
            }
        }
    }
}
AABB3d TransformSequence::compute_motion_segment_bbox(
    const AABB3d&       bbox,
    const Transformd&   from,
    const Transformd&   to) const
{
    //
    // Reference:
    //
    //   http://gruenschloss.org/motion-blur/motion-blur.pdf page 11.
    //

    // Parameters.
    const double MinLength = Pi / 2.0;
    const double RootEps = 1.0e-6;
    const double GrowEps = 1.0e-4;
    const size_t MaxIterations = 100;

    // Start with the bounding box at 'from'.
    const AABB3d from_bbox = from.to_parent(bbox);
    AABB3d motion_bbox = from_bbox;

    // Setup an interpolator between 'from' and 'to'.
    TransformInterpolatord interpolator;
    if (!interpolator.set_transforms(from, to))
        return motion_bbox;

    // Compute the scalings at 'from' and 'to'.
    const Vector3d s0 = interpolator.get_s0();
    const Vector3d s1 = interpolator.get_s1();

    // Compute the relative rotation between 'from' and 'to'.
    const Quaterniond q =
        interpolator.get_q1() * conjugate(interpolator.get_q0());

    // Transform the relative rotation to the axis-angle representation.
    Vector3d axis;
    double angle;
    q.extract_axis_angle(axis, angle);
    if (axis.z < 0.0)
        angle = -angle;

    // The following code only makes sense if there is a rotation component.
    if (angle == 0.0)
        return motion_bbox;

    // Compute the rotation required to align the rotation axis with the Z axis.
    const Vector3d Z(0.0, 0.0, 1.0);
    const Vector3d perp = cross(Z, axis);
    const double perp_norm = norm(perp);
    Transformd axis_to_z;
    if (perp_norm == 0.0)
        axis_to_z = Transformd::identity();
    else
    {
        const Vector3d v = perp / perp_norm;
        const double sin_a = clamp(perp_norm, -1.0, 1.0);
        const double cos_a = sqrt(1.0 - sin_a * sin_a);
        axis_to_z.set_local_to_parent(Matrix4d::make_rotation(v, cos_a, +sin_a));
        axis_to_z.set_parent_to_local(Matrix4d::make_rotation(v, cos_a, -sin_a));
    }

    // Build the linear scaling functions Sx(theta), Sy(theta) and Sz(theta).
    const LinearFunction sx(1.0, s1.x / s0.x, angle);
    const LinearFunction sy(1.0, s1.y / s0.y, angle);
    const LinearFunction sz(1.0, s1.z / s0.z, angle);

    // Consider each corner of the bounding box. Notice an important trick here:
    // we take advantage of the way AABB::compute_corner() works to only iterate
    // over the four corners at Z=min instead of over all eight corners since we
    // anyway transform the rotation to be aligned with the Z axis.
    for (size_t c = 0; c < 4; ++c)
    {
        // Compute the position of this corner at 'from'.
        const Vector3d corner = axis_to_z.point_to_local(from_bbox.compute_corner(c));
        const Vector2d corner2d(corner.x, corner.y);

        // Build the trajectory functions x(theta) and y(theta).
        const TrajectoryX tx(sx, sy, corner2d);
        const TrajectoryY ty(sx, sy, corner2d);

        // Find all the rotation angles at which this corner is an extremum and update the motion bounding box.
        RootHandler root_handler(tx, ty, sz, axis_to_z, corner, motion_bbox);
        find_multiple_roots_newton(
            Bind<TrajectoryX>(tx, &TrajectoryX::d),
            Bind<TrajectoryX>(tx, &TrajectoryX::dd),
            0.0, angle,
            MinLength,
            RootEps,
            MaxIterations,
            root_handler);
        find_multiple_roots_newton(
            Bind<TrajectoryY>(ty, &TrajectoryY::d),
            Bind<TrajectoryY>(ty, &TrajectoryY::dd),
            0.0, angle,
            MinLength,
            RootEps,
            MaxIterations,
            root_handler);
    }

    motion_bbox.robust_grow(GrowEps);

    return motion_bbox;
}
void LightPathsWidget::set_transform(const Transformd& transform)
{
    m_camera_matrix = transform.get_parent_to_local();
}
Example #12
0
void AOVoxelTree::build(
    const Scene&    scene,
    BuilderType&    builder)
{
    // The voxel tree is built using the scene geometry at the middle of the shutter interval.
    const double time = scene.get_camera()->get_shutter_middle_time();

    // Loop over the assembly instances of the scene.
    for (const_each<AssemblyInstanceContainer> i = scene.assembly_instances(); i; ++i)
    {
        // Retrieve the assembly instance.
        const AssemblyInstance& assembly_instance = *i;

        // Retrieve the assembly.
        const Assembly& assembly = assembly_instance.get_assembly();

        // Loop over the object instances of the assembly.
        for (const_each<ObjectInstanceContainer> j = assembly.object_instances(); j; ++j)
        {
            // Retrieve the object instance.
            const ObjectInstance& object_instance = *j;

            // Compute the object space to world space transformation.
            const Transformd transform =
                  assembly_instance.transform_sequence().evaluate(time)
                * object_instance.get_transform();

            // Retrieve the object.
            Object& object = object_instance.get_object();

            // Retrieve the region kit of the object.
            Access<RegionKit> region_kit(&object.get_region_kit());

            // Loop over the regions of the object.
            const size_t region_count = region_kit->size();
            for (size_t region_index = 0; region_index < region_count; ++region_index)
            {
                // Retrieve the region.
                const IRegion* region = (*region_kit)[region_index];

                // Retrieve the tessellation of the region.
                Access<StaticTriangleTess> tess(&region->get_static_triangle_tess());

                // Push all triangles of the region into the tree.
                const size_t triangle_count = tess->m_primitives.size();
                for (size_t triangle_index = 0; triangle_index < triangle_count; ++triangle_index)
                {
                    // Fetch the triangle.
                    const Triangle& triangle = tess->m_primitives[triangle_index];

                    // Retrieve object instance space vertices of the triangle.
                    const GVector3& v0_os = tess->m_vertices[triangle.m_v0];
                    const GVector3& v1_os = tess->m_vertices[triangle.m_v1];
                    const GVector3& v2_os = tess->m_vertices[triangle.m_v2];

                    // Transform triangle vertices to world space.
                    const GVector3 v0(transform.point_to_parent(v0_os));
                    const GVector3 v1(transform.point_to_parent(v1_os));
                    const GVector3 v2(transform.point_to_parent(v2_os));

                    // Push the triangle into the tree.
                    TriangleIntersector intersector(v0, v1, v2);
                    builder.push(intersector);
                }
            }
        }
    }
}
Example #13
0
void LightSampler::collect_emitting_triangles(
    const Assembly&                     assembly,
    const AssemblyInstance&             assembly_instance,
    const TransformSequence&            transform_sequence)
{
    // Loop over the object instances of the assembly.
    const size_t object_instance_count = assembly.object_instances().size();
    for (size_t object_instance_index = 0; object_instance_index < object_instance_count; ++object_instance_index)
    {
        // Retrieve the object instance.
        const ObjectInstance* object_instance = assembly.object_instances().get_by_index(object_instance_index);

        // Retrieve the materials of the object instance.
        const MaterialArray& front_materials = object_instance->get_front_materials();
        const MaterialArray& back_materials = object_instance->get_back_materials();

        // Skip object instances without light-emitting materials.
        if (!has_emitting_materials(front_materials) && !has_emitting_materials(back_materials))
            continue;

        double object_area = 0.0;

        // Compute the object space to world space transformation.
        // todo: add support for moving light-emitters.
        const Transformd& object_instance_transform = object_instance->get_transform();
        const Transformd& assembly_instance_transform = transform_sequence.get_earliest_transform();
        const Transformd global_transform = assembly_instance_transform * object_instance_transform;

        // Retrieve the object.
        Object& object = object_instance->get_object();

        // Retrieve the region kit of the object.
        Access<RegionKit> region_kit(&object.get_region_kit());

        // Loop over the regions of the object.
        const size_t region_count = region_kit->size();
        for (size_t region_index = 0; region_index < region_count; ++region_index)
        {
            // Retrieve the region.
            const IRegion* region = (*region_kit)[region_index];

            // Retrieve the tessellation of the region.
            Access<StaticTriangleTess> tess(&region->get_static_triangle_tess());

            // Loop over the triangles of the region.
            const size_t triangle_count = tess->m_primitives.size();
            for (size_t triangle_index = 0; triangle_index < triangle_count; ++triangle_index)
            {
                // Fetch the triangle.
                const Triangle& triangle = tess->m_primitives[triangle_index];

                // Skip triangles without a material.
                if (triangle.m_pa == Triangle::None)
                    continue;

                // Fetch the materials assigned to this triangle.
                const size_t pa_index = static_cast<size_t>(triangle.m_pa);
                const Material* front_material =
                    pa_index < front_materials.size() ? front_materials[pa_index] : 0;
                const Material* back_material =
                    pa_index < back_materials.size() ? back_materials[pa_index] : 0;

                // Skip triangles that don't emit light.
                if ((front_material == 0 || front_material->has_emission() == false) &&
                    (back_material == 0 || back_material->has_emission() == false))
                    continue;

                // Retrieve object instance space vertices of the triangle.
                const GVector3& v0_os = tess->m_vertices[triangle.m_v0];
                const GVector3& v1_os = tess->m_vertices[triangle.m_v1];
                const GVector3& v2_os = tess->m_vertices[triangle.m_v2];

                // Transform triangle vertices to assembly space.
                const GVector3 v0_as = object_instance_transform.point_to_parent(v0_os);
                const GVector3 v1_as = object_instance_transform.point_to_parent(v1_os);
                const GVector3 v2_as = object_instance_transform.point_to_parent(v2_os);

                // Compute the support plane of the hit triangle in assembly space.
                const GTriangleType triangle_geometry(v0_as, v1_as, v2_as);
                TriangleSupportPlaneType triangle_support_plane;
                triangle_support_plane.initialize(TriangleType(triangle_geometry));

                // Transform triangle vertices to world space.
                const Vector3d v0(assembly_instance_transform.point_to_parent(v0_as));
                const Vector3d v1(assembly_instance_transform.point_to_parent(v1_as));
                const Vector3d v2(assembly_instance_transform.point_to_parent(v2_as));

                // Compute the geometric normal to the triangle and the area of the triangle.
                Vector3d geometric_normal = cross(v1 - v0, v2 - v0);
                const double geometric_normal_norm = norm(geometric_normal);
                if (geometric_normal_norm == 0.0)
                    continue;
                const double rcp_geometric_normal_norm = 1.0 / geometric_normal_norm;
                const double rcp_area = 2.0 * rcp_geometric_normal_norm;
                const double area = 0.5 * geometric_normal_norm;
                geometric_normal *= rcp_geometric_normal_norm;
                assert(is_normalized(geometric_normal));

                // Retrieve object instance space vertex normals.
                Vector3d n0_os, n1_os, n2_os;

                if (triangle.m_n0 != Triangle::None &&
                    triangle.m_n1 != Triangle::None &&
                    triangle.m_n2 != Triangle::None)
                {
                    n0_os = Vector3d(tess->m_vertex_normals[triangle.m_n0]);
                    n1_os = Vector3d(tess->m_vertex_normals[triangle.m_n1]);
                    n2_os = Vector3d(tess->m_vertex_normals[triangle.m_n2]);
                }
                else
                    n0_os = n1_os = n2_os = geometric_normal;

                // Transform vertex normals to world space.
                const Vector3d n0(normalize(global_transform.normal_to_parent(n0_os)));
                const Vector3d n1(normalize(global_transform.normal_to_parent(n1_os)));
                const Vector3d n2(normalize(global_transform.normal_to_parent(n2_os)));

                for (size_t side = 0; side < 2; ++side)
                {
                    // Retrieve the material; skip sides without a material or without emission.
                    const Material* material = side == 0 ? front_material : back_material;
                    if (material == 0 || material->has_emission() == false)
                        continue;

                    // Retrieve the EDF and get the importance multiplier.
                    double importance_multiplier = 1.0;
                    if (const EDF* edf = material->get_uncached_edf())
                        importance_multiplier = edf->get_uncached_importance_multiplier();

                    // Accumulate the object area for OSL shaders.
                    object_area += area;

                    // Compute the probability density of this triangle.
                    const double triangle_importance = m_params.m_importance_sampling ? area : 1.0;
                    const double triangle_prob = triangle_importance * importance_multiplier;

                    // Create a light-emitting triangle.
                    EmittingTriangle emitting_triangle;
                    emitting_triangle.m_assembly_instance = &assembly_instance;
                    emitting_triangle.m_object_instance_index = object_instance_index;
                    emitting_triangle.m_region_index = region_index;
                    emitting_triangle.m_triangle_index = triangle_index;
                    emitting_triangle.m_v0 = v0;
                    emitting_triangle.m_v1 = v1;
                    emitting_triangle.m_v2 = v2;
                    emitting_triangle.m_n0 = side == 0 ? n0 : -n0;
                    emitting_triangle.m_n1 = side == 0 ? n1 : -n1;
                    emitting_triangle.m_n2 = side == 0 ? n2 : -n2;
                    emitting_triangle.m_geometric_normal = side == 0 ? geometric_normal : -geometric_normal;
                    emitting_triangle.m_triangle_support_plane = triangle_support_plane;
                    emitting_triangle.m_rcp_area = rcp_area;
                    emitting_triangle.m_triangle_prob = 0.0;    // will be initialized once the emitting triangle CDF is built
                    emitting_triangle.m_material = material;

                    // Store the light-emitting triangle.
                    const size_t emitting_triangle_index = m_emitting_triangles.size();
                    m_emitting_triangles.push_back(emitting_triangle);

                    // Insert the light-emitting triangle into the CDF.
                    m_emitting_triangles_cdf.insert(emitting_triangle_index, triangle_prob);
                }
            }
        }

#ifdef APPLESEED_WITH_OSL
        store_object_area_in_shadergroups(
            &assembly_instance,
            object_instance,
            object_area,
            front_materials);

        store_object_area_in_shadergroups(
            &assembly_instance,
            object_instance,
            object_area,
            back_materials);
#endif
    }
}