<commit_before>#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
#define __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__

// vector tile
#include "vector_tile.pb.h"
#include <mapnik/vertex.hpp>

namespace mapnik { namespace vector {

        template <typename T>
        unsigned encode_geometry(T & path,
                                 tile_GeomType type,
                                 tile_feature & current_feature,
                                 int32_t & x_,
                                 int32_t & y_,
                                 unsigned tolerance,
                                 unsigned path_multiplier)
        {
            unsigned count = 0;
            path.rewind(0);
            current_feature.set_type(type);

            vertex2d vtx(vertex2d::no_init);
            int cmd = -1;
            int prev_cmd = -1;
            int cmd_idx = -1;
            const int cmd_bits = 3;
            unsigned length = 0;
            bool skipped_last = false;
            int32_t skipped_index = -1;
            int32_t cur_x = 0;
            int32_t cur_y = 0;

            // See vector_tile.proto for a description of how vertex command
            // encoding works.
            while ((vtx.cmd = path.vertex(&vtx.x, &vtx.y)) != SEG_END)
            {
                if (static_cast<int>(vtx.cmd) != cmd)
                {
                    if (cmd_idx >= 0)
                    {
                        // Encode the previous length/command value.
                        current_feature.set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1)));
                    }
                    cmd = static_cast<int>(vtx.cmd);
                    length = 0;
                    cmd_idx = current_feature.geometry_size();
                    current_feature.add_geometry(0); // placeholder added in first pass
                }

                if (cmd == SEG_MOVETO || cmd == SEG_LINETO)
                {
                    // Compute delta to the previous coordinate.
                    cur_x = static_cast<int32_t>(std::floor((vtx.x * path_multiplier) + 0.5));
                    cur_y = static_cast<int32_t>(std::floor((vtx.y * path_multiplier) + 0.5));
                    int32_t dx = cur_x - x_;
                    int32_t dy = cur_y - y_;
                    // Keep all move_to commands, but omit other movements that are
                    // not >= the tolerance threshold and should be considered no-ops.
                    // NOTE: length == 0 indicates the command has changed and will
                    // preserve any non duplicate move_to or line_to
                    if ( length == 0 ||
                         (static_cast<unsigned>(std::abs(dx)) >= tolerance) ||
                         (static_cast<unsigned>(std::abs(dy)) >= tolerance)
                        )
                    {
                        // Manual zigzag encoding.
                        current_feature.add_geometry((dx << 1) ^ (dx >> 31));
                        current_feature.add_geometry((dy << 1) ^ (dy >> 31));
                        x_ = cur_x;
                        y_ = cur_y;
                        skipped_last = false;
                        ++length;
                    }
                    else
                    {
                        skipped_last = true;
                        skipped_index = current_feature.geometry_size();
                    }
                }
                else if (cmd == SEG_CLOSE)
                {
                    if (prev_cmd != SEG_CLOSE) ++length;
                }
                else
                {
                    std::stringstream msg;
                    msg << "Unknown command type (backend_pbf): "
                        << cmd;
                    throw std::runtime_error(msg.str());
                }

                ++count;
                prev_cmd = cmd;
            }

            if (skipped_last && skipped_index > 1) // at least one vertex + cmd/length
            {
                // if we skipped previous vertex we just update it to the last one here.
                uint32_t last_x = current_feature.geometry(skipped_index - 2);
                uint32_t last_y = current_feature.geometry(skipped_index - 1);
                int32_t last_dx = ((last_x >> 1) ^ (-(last_x & 1)));
                int32_t last_dy = ((last_y >> 1) ^ (-(last_y & 1)));
                int32_t dx = cur_x - x_ + last_dx;
                int32_t dy = cur_y - y_ + last_dy;
                x_ = cur_x;
                y_ = cur_y;
                // FIXME : add tolerance check here to discard short segments
                current_feature.set_geometry(skipped_index - 2, ((dx << 1) ^ (dx >> 31)));
                current_feature.set_geometry(skipped_index - 1, ((dy << 1) ^ (dy >> 31)));
            }

            // Update the last length/command value.
            if (cmd_idx >= 0)
            {
                current_feature.set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1)));
            }
            return count;
        }

}} // end ns

#endif // __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
<commit_msg>preserve vertices that are forming horizontal or vertical segments<commit_after>#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
#define __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__

// vector tile
#include "vector_tile.pb.h"
#include <mapnik/vertex.hpp>

namespace mapnik { namespace vector {

inline void handle_skipped_last(tile_feature & current_feature,
                                int32_t skipped_index,
                                int32_t cur_x,
                                int32_t cur_y,
                                int32_t & x_,
                                int32_t & y_)
{
    // if we skipped previous vertex we just update it to the last one here.
    uint32_t last_x = current_feature.geometry(skipped_index - 2);
    uint32_t last_y = current_feature.geometry(skipped_index - 1);
    int32_t last_dx = ((last_x >> 1) ^ (-(last_x & 1)));
    int32_t last_dy = ((last_y >> 1) ^ (-(last_y & 1)));
    int32_t dx = cur_x - x_ + last_dx;
    int32_t dy = cur_y - y_ + last_dy;
    x_ = cur_x;
    y_ = cur_y;
    // FIXME : add tolerance check here to discard short segments
    current_feature.set_geometry(skipped_index - 2, ((dx << 1) ^ (dx >> 31)));
    current_feature.set_geometry(skipped_index - 1, ((dy << 1) ^ (dy >> 31)));
}

template <typename T>
unsigned encode_geometry(T & path,
                         tile_GeomType type,
                         tile_feature & current_feature,
                         int32_t & x_,
                         int32_t & y_,
                         unsigned tolerance,
                         unsigned path_multiplier)
{
    unsigned count = 0;
    path.rewind(0);
    current_feature.set_type(type);

    vertex2d vtx(vertex2d::no_init);
    int cmd = -1;
    int prev_cmd = -1;
    int cmd_idx = -1;
    const int cmd_bits = 3;
    unsigned length = 0;
    bool skipped_last = false;
    int32_t skipped_index = -1;
    int32_t cur_x = 0;
    int32_t cur_y = 0;

    // See vector_tile.proto for a description of how vertex command
    // encoding works.

    std::vector<vertex2d> output;
    const std::size_t buffer_size = 8;
    output.reserve(buffer_size);
    bool done = false;
    bool cache = true;
    while (true)
    {
        if (cache)
        {
            // read
            vertex2d v(vertex2d::no_init);
            while ((v.cmd = path.vertex(&v.x, &v.y)) != SEG_END)
            {
                output.push_back(std::move(v));
                if (output.size() == buffer_size) break;
            }
            cache = false;
            if (v.cmd == SEG_END)
            {
                done = true;
            }
        }
        else
        {
            if (done && output.empty()) break;
            // process
            vtx = output.front();
            {
                if (static_cast<int>(vtx.cmd) != cmd)
                {
                    if (cmd_idx >= 0)
                    {
                        // Encode the previous length/command value.
                        current_feature.set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1)));
                    }
                    cmd = static_cast<int>(vtx.cmd);
                    length = 0;
                    cmd_idx = current_feature.geometry_size();
                    current_feature.add_geometry(0); // placeholder added in first pass
                }

                switch (vtx.cmd)
                {
                case SEG_MOVETO:
                case SEG_LINETO:
                {
                    if (cmd == SEG_MOVETO && skipped_last && skipped_index > 1) // at least one vertex + cmd/length
                    {
                        // if we skipped previous vertex we just update it to the last one here.
                        handle_skipped_last(current_feature, skipped_index, cur_x, cur_y,  x_, y_);
                    }

                    // Compute delta to the previous coordinate.
                    cur_x = static_cast<int32_t>(std::floor((vtx.x * path_multiplier) + 0.5));
                    cur_y = static_cast<int32_t>(std::floor((vtx.y * path_multiplier) + 0.5));
                    int32_t dx = cur_x - x_;
                    int32_t dy = cur_y - y_;
                    bool sharp_turn_ahead = false;
                    if (output.size() > 1)
                    {
                        auto const& next_vtx = output[1];
                        if (next_vtx.cmd == SEG_LINETO)
                        {
                            uint32_t next_dx = std::abs(cur_x - static_cast<int32_t>(std::floor((next_vtx.x * path_multiplier) + 0.5)));
                            uint32_t next_dy = std::abs(cur_y - static_cast<int32_t>(std::floor((next_vtx.y * path_multiplier) + 0.5)));
                            if ((next_dx == 0 && next_dy > 0) || (next_dy == 0 && next_dx > 0))
                            {
                                sharp_turn_ahead = true;
                            }
                        }
                    }
                    // Keep all move_to commands, but omit other movements that are
                    // not >= the tolerance threshold and should be considered no-ops.
                    // NOTE: length == 0 indicates the command has changed and will
                    // preserve any non duplicate move_to or line_to
                    if ( length == 0 || sharp_turn_ahead ||
                         (static_cast<unsigned>(std::abs(dx)) >= tolerance) ||
                         (static_cast<unsigned>(std::abs(dy)) >= tolerance)
                        )
                    {
                        // Manual zigzag encoding.
                        current_feature.add_geometry((dx << 1) ^ (dx >> 31));
                        current_feature.add_geometry((dy << 1) ^ (dy >> 31));
                        x_ = cur_x;
                        y_ = cur_y;
                        skipped_last = false;
                        ++length;
                    }
                    else
                    {
                        skipped_last = true;
                        skipped_index = current_feature.geometry_size();
                    }
                    break;
                }

                case SEG_CLOSE:
                {
                    if (prev_cmd != SEG_CLOSE) ++length;
                    break;
                }
                default:
                    std::stringstream msg;
                    msg << "Unknown command type (backend_pbf): "
                        << cmd;
                    throw std::runtime_error(msg.str());
                    break;
                }
            }
            ++count;
            prev_cmd = cmd;
            output.erase(output.begin());
            if (output.size() < 2) cache = true;
        }
    }
    //
    if (skipped_last && skipped_index > 1) // at least one vertex + cmd/length
    {
        handle_skipped_last(current_feature, skipped_index, cur_x, cur_y, x_, y_);
    }
    // Update the last length/command value.
    if (cmd_idx >= 0)
    {
        current_feature.set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1)));
    }
    return count;
}

}} // end ns

#endif // __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
<|endoftext|>


<commit_before>#include "trace.h"
#include "lib/output.h"
#include "lib/sampling.h"
#include "lib/image.h"
#include "lib/lambertian.h"
#include "lib/range.h"
#include "lib/runtime.h"
#include "lib/triangle.h"
#include "lib/stats.h"
#include "lib/xorshift.h"

#include <assimp/Importer.hpp>      // C++ importer interface
#include <assimp/scene.h>           // Output data structure
#include <assimp/postprocess.h>     // Post processing flags
#include <docopt.h>
#include <ThreadPool.h>
#include <Eigen/Core>
#include <Eigen/Dense>

#include <math.h>
#include <vector>
#include <map>
#include <iostream>
#include <unordered_set>
#include <chrono>

Color trace(
    const Vec& origin, const Vec& dir, const Tree& tree,
    const std::vector<Color>& radiosity, const Configuration& conf)
{
    // intersection
    float dist_to_triangle, s, t;
    auto id = tree.intersect(aiRay{origin, dir}, dist_to_triangle, s, t);
    if (!id) {
        return conf.bg_color;
    }

    Stats::instance().num_rays += 1;
    return radiosity[id] * 10.f;
}

//
// The form factor between faces `from` (i) and `to` (j) is defined as
//
// F_ij = 1/A_i ∫_x ∫_y G'(x, y) dA_y dA_x
//      ≈ 1/A_j A_j/L ∑_{l = 1...L} (A_i/K ∑_{k = 1...K} G'(x^l, y^k))
//      = A_j/N ∑_{n = 1...N} G'(x^n, y^n)
//
// Here, x and y are points on face i and j, A_i is the area of i, and A_i, and
// A_j are infinitesimal areas around x and y. Further,
//
// G'(x, y) = V(x, y) * cos+(θ_i) * cos+(θ_j) / (π * ||x-y||^2), where
//
// V(x, y) - visibility indicator between x and y (1 if visible, 0 else)
// θ_i - angle between normal of x and vector to y (ω)
// θ_j - angle between normal of y and vector to x (-ω)
//
// The above integrals are approximated by Monte-Carlo.
//
float form_factor(
    const Tree& tree,
    const Tree::TriangleId from_id,
    const Tree::TriangleId to_id,
    const size_t num_samples)
{
    const auto& from = tree[from_id];
    const auto& to = tree[to_id];

    float sum = 0;
    for (size_t i = 0; i != num_samples; ++i) {
        auto p1 = sampling::triangle(from);
        auto p2 = sampling::triangle(to);

        auto v = p2 - p1;
        float unused;
        auto id = tree.intersect(Ray{p1, v}, unused, unused, unused);
        auto visibility = id && (id == from_id || id == to_id);
        if (!visibility) {
            continue;
        }

        auto square_length = v.SquareLength();

        v.Normalize();
        auto cos_theta1 = std::fmax(0, v * from.normal);
        auto cos_theta2 = std::fmax(0, (-v) * to.normal);
        auto G = cos_theta1 * cos_theta2 / square_length;
        sum += G;
    }

    return M_1_PI * sum * to.area() / num_samples;
}

auto compute_radiosity(const Tree& tree) {
    size_t num_triangles = tree.num_triangles();

    // compute matrices
    Eigen::MatrixXf F(num_triangles, num_triangles);
    Eigen::VectorXf rho_r(num_triangles);
    Eigen::VectorXf rho_g(num_triangles);
    Eigen::VectorXf rho_b(num_triangles);
    Eigen::VectorXf E_r(num_triangles);
    Eigen::VectorXf E_g(num_triangles);
    Eigen::VectorXf E_b(num_triangles);

    for (size_t i = 0; i < num_triangles; ++i) {
        const auto& from = tree[i];

        // construct form factor matrix (F_ij)
        for (size_t j = i; j < num_triangles; ++j) {
            if (i == j) {
                F(i, i) = 0.f;
            } else {
                F(i, j) = form_factor(tree, i, j, 128);
                F(j, i) = F(i, j);
            }
        }

        // construct material diagonal matrix (ρ_i)
        rho_r(i) = from.diffuse.r;
        rho_g(i) = from.diffuse.g;
        rho_b(i) = from.diffuse.b;

        // construct vector of emittors
        E_r(i) = from.emissive.r;
        E_g(i) = from.emissive.g;
        E_b(i) = from.emissive.b;
    }

    // solve radiosity equation
    auto I = Eigen::MatrixXf::Identity(num_triangles, num_triangles);

    Eigen::VectorXf B_r = (I - rho_r.asDiagonal() * F)
        .colPivHouseholderQr().solve(E_r);
    Eigen::VectorXf B_g = (I - rho_g.asDiagonal() * F)
        .colPivHouseholderQr().solve(E_g);
    Eigen::VectorXf B_b = (I - rho_b.asDiagonal() * F)
        .colPivHouseholderQr().solve(E_b);

    // combine results in a vector
    std::vector<Color> B;
    for (size_t id = 0; id != num_triangles; ++id) {
        B.emplace_back(B_r(id), B_g(id), B_b(id), 1.f);
    }
    return B;
}

Triangles triangles_from_scene(const aiScene* scene) {
    Triangles triangles;
    for (auto node : make_range(
            scene->mRootNode->mChildren, scene->mRootNode->mNumChildren))
    {
        if (node->mNumMeshes == 0) {
            continue;
        }

        const auto& T = node->mTransformation;
        const aiMatrix3x3 Tp(T);  // trafo without translation

        for (auto mesh_index : make_range(node->mMeshes, node->mNumMeshes)) {
            const auto& mesh = *scene->mMeshes[mesh_index];
            const auto& material = scene->mMaterials[mesh.mMaterialIndex];

            aiColor4D ambient, diffuse, reflective, emissive;
            material->Get(AI_MATKEY_COLOR_AMBIENT, ambient);
            material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse);
            material->Get(AI_MATKEY_COLOR_REFLECTIVE, reflective);
            material->Get(AI_MATKEY_COLOR_EMISSIVE, emissive);

            float reflectivity = 0.f;
            material->Get(AI_MATKEY_REFLECTIVITY, reflectivity);

            for (aiFace face : make_range(mesh.mFaces, mesh.mNumFaces)) {
                assert(face.mNumIndices == 3);
                triangles.push_back(Triangle{
                    // vertices
                    {{
                        T * mesh.mVertices[face.mIndices[0]],
                        T * mesh.mVertices[face.mIndices[1]],
                        T * mesh.mVertices[face.mIndices[2]]
                    }},
                    // normals
                    {{
                        Tp * mesh.mNormals[face.mIndices[0]],
                        Tp * mesh.mNormals[face.mIndices[1]],
                        Tp * mesh.mNormals[face.mIndices[2]]
                    }},
                    ambient,
                    diffuse,
                    emissive,
                    reflective,
                    reflectivity
                });
            }
        }
    }
    return triangles;
}

static const char USAGE[] =
R"(Usage: raytracer <filename> [options]

Options:
  -w --width=<px>                   Width of the image [default: 640].
  -a --aspect=<num>                 Aspect ratio of the image. If the model has
                                    specified the aspect ratio, it will be
                                    used. Otherwise default value is 1.
  --background=<3x float>           Background color [default: 0 0 0].
  -t --threads=<int>                Number of threads [default: 1].
  --inverse-gamma=<float>           Inverse of gamma for gamma correction
                                    [default: 0.454545].
  --no-gamma-correction             Disables gamma correction.
)";

int main(int argc, char const *argv[])
{
    // parameters
    std::map<std::string, docopt::value> args =
        docopt::docopt(USAGE, {argv + 1, argv + argc}, true, "raytracer 0.2");

    const Configuration conf
        { 1, 0, 1, 0  // unused configuration arguments
        , args["--threads"].asLong()
        , args["--background"].asString()
        , std::stof(args["--inverse-gamma"].asString())
        , args["--no-gamma-correction"].asBool()
        };

    // import scene
    Assimp::Importer importer;
    const aiScene* scene = importer.ReadFile(
        args["<filename>"].asString().c_str(),
        aiProcess_CalcTangentSpace       |
        aiProcess_Triangulate            |
        aiProcess_JoinIdenticalVertices  |
        aiProcess_GenNormals             |
        aiProcess_SortByPType);

    if (!scene) {
        std::cout << importer.GetErrorString() << std::endl;
        return 1;
    }

    // setup camera
    assert(scene->mNumCameras == 1);  // we can deal only with a single camera
    auto& sceneCam = *scene->mCameras[0];
    if (args["--aspect"]) {
        sceneCam.mAspect = std::stof(args["--aspect"].asString());
        assert(sceneCam.mAspect > 0);
    } else if (sceneCam.mAspect == 0) {
        sceneCam.mAspect = 1.f;
    }
    auto* camNode = scene->mRootNode->FindNode(sceneCam.mName);
    assert(camNode != nullptr);
    const Camera cam(camNode->mTransformation, sceneCam);

    // load triangles from the scene into a kd-tree
    auto triangles = triangles_from_scene(scene);
    Stats::instance().num_triangles = triangles.size();
    Tree tree(std::move(triangles));

    // compute radiosity
    auto radiosity = compute_radiosity(tree);

    //
    // Raycaster
    //

    int width = args["--width"].asLong();
    assert(width > 0);
    int height = width / cam.mAspect;

    Image image(width, height);
    {
        Runtime rt(Stats::instance().runtime_ms);

        std::cerr << "Rendering          ";

        ThreadPool pool(conf.num_threads);
        std::vector<std::future<void>> tasks;

        for (int y = 0; y < height; ++y) {
            tasks.emplace_back(pool.enqueue([
                &image, &cam, &tree, &radiosity, width, height, y, &conf]()
            {
                for (int x = 0; x < width; ++x) {
                    for (int i = 0; i < conf.num_pixel_samples; ++i) {
                        auto cam_dir = cam.raster2cam(
                            aiVector2D(x, y), width, height);

                        Stats::instance().num_prim_rays += 1;
                        image(x, y) += trace(
                            cam.mPosition, cam_dir, tree, radiosity, conf);
                    }

                    // gamma correction
                    if (conf.gamma_correction_enabled) {
                        image(x, y).r = powf(image(x, y).r, conf.inverse_gamma);
                        image(x, y).g = powf(image(x, y).g, conf.inverse_gamma);
                        image(x, y).b = powf(image(x, y).b, conf.inverse_gamma);
                    }
                }
            }));
        }

        long completed = 0;

        for (auto& task: tasks) {
            task.get();
            completed += 1;
            float progress = static_cast<float>(completed) / tasks.size();
            int bar_width = progress * 20;
            std::cerr
                << "\rRendering          "
                << "[" << std::string(bar_width, '-')
                << std::string(20 - bar_width, ' ') << "] "
                << std::setfill(' ') << std::setw(6)
                << std::fixed << std::setprecision(2) << (progress * 100.0) << '%';
            std::cerr.flush();
        }
        std::cerr << std::endl;

        // Render feature lines after
        // "Ray Tracing NPR-Style Feature Lines" by Choudhury and Parker.
        std::cerr << "Drawing mesh lines ";
        std::vector<std::future<void>> mesh_tasks;
        constexpr float offset = 1.f;
        constexpr std::array<Vec2, 8> offsets =
            { Vec2{0.f, 0.f}
            , Vec2{offset, 0.f}
            , Vec2{offset, offset}
            , Vec2{0.f, offset}
            , Vec2{0.f, offset / 2}
            , Vec2{offset / 2, offset}
            , Vec2{offset, offset / 2}
            , Vec2{offset / 2, 0.f}
            };
        for (int y = 0; y < height; ++y) {
            mesh_tasks.emplace_back(pool.enqueue([
                &image, &offsets, &cam, &tree, width, height, y, &conf]()
            {
                for (int x = 0; x < width; ++x) {
                    float dist_to_triangle, s, t;
                    std::unordered_set<Tree::OptionalId> triangle_ids;

                    // Shoot center ray.
                    auto cam_dir = cam.raster2cam(
                        aiVector2D(x + 0.5f, y + 0.5f), width, height);
                    auto center_id = tree.intersect(
                            Ray(cam.mPosition, cam_dir), dist_to_triangle, s, t);
                    triangle_ids.insert(center_id);

                    // Sample disc rays around center.
                    // TODO: Sample disc with Poisson or similar.
                    for (auto offset : offsets) {
                        cam_dir = cam.raster2cam(
                            aiVector2D(x + offset[0], y + offset[1]), width, height);
                        auto id = tree.intersect(
                            Ray(cam.mPosition, cam_dir), dist_to_triangle, s, t);
                        triangle_ids.insert(id);
                    }

                    constexpr float M_2 = 0.5f * offsets.size();
                    // All hit primitives except the one hit by center.
                    const float m = triangle_ids.size() - 1.f;
                    float e = std::pow(std::abs(m - M_2) / M_2, 10);
                    image(x, y) = image(x, y) * e;
                }
            }));
        }

        completed = 0;
        for (auto& task: mesh_tasks) {
            task.get();
            completed += 1;
            float progress = static_cast<float>(completed) / mesh_tasks.size();
            int bar_width = progress * 20;
            std::cerr
                << "\rDrawing mesh lines "
                << "[" << std::string(bar_width, '-')
                << std::string(20 - bar_width, ' ') << "] "
                << std::setfill(' ') << std::setw(6)
                << std::fixed << std::setprecision(2) << (progress * 100.0) << '%';
            std::cerr.flush();
        }
        std::cerr << std::endl;
    }

    // output stats
    std::cerr << Stats::instance() << std::endl;

    // output image
    std::cout << image << std::endl;
    return 0;
}
<commit_msg>Compute F_ij correctly (it is not symmetric!)<commit_after>#include "trace.h"
#include "lib/output.h"
#include "lib/sampling.h"
#include "lib/image.h"
#include "lib/lambertian.h"
#include "lib/range.h"
#include "lib/runtime.h"
#include "lib/triangle.h"
#include "lib/stats.h"
#include "lib/xorshift.h"

#include <assimp/Importer.hpp>      // C++ importer interface
#include <assimp/scene.h>           // Output data structure
#include <assimp/postprocess.h>     // Post processing flags
#include <docopt.h>
#include <ThreadPool.h>
#include <Eigen/Core>
#include <Eigen/Dense>

#include <math.h>
#include <vector>
#include <map>
#include <iostream>
#include <unordered_set>
#include <chrono>

Color trace(
    const Vec& origin, const Vec& dir, const Tree& tree,
    const std::vector<Color>& radiosity, const Configuration& conf)
{
    // intersection
    float dist_to_triangle, s, t;
    auto id = tree.intersect(aiRay{origin, dir}, dist_to_triangle, s, t);
    if (!id) {
        return conf.bg_color;
    }

    // (!) dirty workaround
    Color c = radiosity[id];
    if (c.r < 0) {
        c.r = 0;
    }
    if (c.g < 0) {
        c.g = 0;
    }
    if (c.b < 0) {
        c.b = 0;
    }

    Stats::instance().num_rays += 1;
    // (!) workaround, let's give it more light
    return c * 10.f;
}

//
// The form factor between faces `from` (i) and `to` (j) is defined as
//
// F_ij = 1/A_i ∫_x ∫_y G'(x, y) dA_y dA_x
//      ≈ 1/A_j A_j/L ∑_{l = 1...L} (A_i/K ∑_{k = 1...K} G'(x^l, y^k))
//      = A_j/N ∑_{n = 1...N} G'(x^n, y^n)
//
// Here, x and y are points on face i and j, A_i is the area of i, and A_i, and
// A_j are infinitesimal areas around x and y. Further,
//
// G'(x, y) = V(x, y) * cos+(θ_i) * cos+(θ_j) / (π * ||x-y||^2), where
//
// V(x, y) - visibility indicator between x and y (1 if visible, 0 else)
// θ_i - angle between normal of x and vector to y (ω)
// θ_j - angle between normal of y and vector to x (-ω)
//
// The above integrals are approximated by Monte-Carlo.
//
float form_factor(
    const Tree& tree,
    const Tree::TriangleId from_id,
    const Tree::TriangleId to_id,
    const size_t num_samples = 128)
{
    assert(from_id != to_id);

    const auto& from = tree[from_id];
    const auto& to = tree[to_id];

    float sum = 0;
    for (size_t i = 0; i != num_samples; ++i) {
        auto p1 = sampling::triangle(from);
        auto p2 = sampling::triangle(to);

        auto v = p2 - p1;
        float unused;
        auto id = tree.intersect(Ray{p1, v}, unused, unused, unused);
        auto visibility = id && (id == from_id || id == to_id);
        if (!visibility) {
            continue;
        }

        auto square_length = v.SquareLength();

        v.Normalize();
        auto cos_theta1 = std::fmax(0, v * from.normal);
        auto cos_theta2 = std::fmax(0, (-v) * to.normal);
        auto G = cos_theta1 * cos_theta2 / square_length;
        sum += G;
    }

    return M_1_PI * sum * to.area() / num_samples;
}

auto compute_radiosity(const Tree& tree) {
    size_t num_triangles = tree.num_triangles();

    // compute matrices
    Eigen::MatrixXf F(num_triangles, num_triangles);
    Eigen::VectorXf rho_r(num_triangles);
    Eigen::VectorXf rho_g(num_triangles);
    Eigen::VectorXf rho_b(num_triangles);
    Eigen::VectorXf E_r(num_triangles);
    Eigen::VectorXf E_g(num_triangles);
    Eigen::VectorXf E_b(num_triangles);

    for (size_t i = 0; i < num_triangles; ++i) {

        // construct form factor matrix (F_ij)
        for (size_t j = 0; j < num_triangles; ++j) {
            if (i == j) {
                F(i, i) = 0.f;
            } else {
                F(i, j) = form_factor(tree, i, j);
            }
        }

        const auto& triangle = tree[i];

        // construct material diagonal matrix (ρ_i)
        rho_r(i) = triangle.diffuse.r;
        rho_g(i) = triangle.diffuse.g;
        rho_b(i) = triangle.diffuse.b;

        // construct vector of emittors
        E_r(i) = triangle.emissive.r;
        E_g(i) = triangle.emissive.g;
        E_b(i) = triangle.emissive.b;
    }

    // solve radiosity equation
    auto I = Eigen::MatrixXf::Identity(num_triangles, num_triangles);

    Eigen::VectorXf B_r = (I - rho_r.asDiagonal() * F)
        .colPivHouseholderQr().solve(E_r);
    Eigen::VectorXf B_g = (I - rho_g.asDiagonal() * F)
        .colPivHouseholderQr().solve(E_g);
    Eigen::VectorXf B_b = (I - rho_b.asDiagonal() * F)
        .colPivHouseholderQr().solve(E_b);

    // combine results in a vector
    std::vector<Color> B;
    for (size_t id = 0; id != num_triangles; ++id) {
        B.emplace_back(B_r(id), B_g(id), B_b(id), 1.f);
    }
    return B;
}

Triangles triangles_from_scene(const aiScene* scene) {
    Triangles triangles;
    for (auto node : make_range(
            scene->mRootNode->mChildren, scene->mRootNode->mNumChildren))
    {
        if (node->mNumMeshes == 0) {
            continue;
        }

        const auto& T = node->mTransformation;
        const aiMatrix3x3 Tp(T);  // trafo without translation

        for (auto mesh_index : make_range(node->mMeshes, node->mNumMeshes)) {
            const auto& mesh = *scene->mMeshes[mesh_index];
            const auto& material = scene->mMaterials[mesh.mMaterialIndex];

            aiColor4D ambient, diffuse, reflective, emissive;
            material->Get(AI_MATKEY_COLOR_AMBIENT, ambient);
            material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse);
            material->Get(AI_MATKEY_COLOR_REFLECTIVE, reflective);
            material->Get(AI_MATKEY_COLOR_EMISSIVE, emissive);

            float reflectivity = 0.f;
            material->Get(AI_MATKEY_REFLECTIVITY, reflectivity);

            for (aiFace face : make_range(mesh.mFaces, mesh.mNumFaces)) {
                assert(face.mNumIndices == 3);
                triangles.push_back(Triangle{
                    // vertices
                    {{
                        T * mesh.mVertices[face.mIndices[0]],
                        T * mesh.mVertices[face.mIndices[1]],
                        T * mesh.mVertices[face.mIndices[2]]
                    }},
                    // normals
                    {{
                        Tp * mesh.mNormals[face.mIndices[0]],
                        Tp * mesh.mNormals[face.mIndices[1]],
                        Tp * mesh.mNormals[face.mIndices[2]]
                    }},
                    ambient,
                    diffuse,
                    emissive,
                    reflective,
                    reflectivity
                });
            }
        }
    }
    return triangles;
}

static const char USAGE[] =
R"(Usage: raytracer <filename> [options]

Options:
  -w --width=<px>                   Width of the image [default: 640].
  -a --aspect=<num>                 Aspect ratio of the image. If the model has
                                    specified the aspect ratio, it will be
                                    used. Otherwise default value is 1.
  --background=<3x float>           Background color [default: 0 0 0].
  -t --threads=<int>                Number of threads [default: 1].
  --inverse-gamma=<float>           Inverse of gamma for gamma correction
                                    [default: 0.454545].
  --no-gamma-correction             Disables gamma correction.
)";

int main(int argc, char const *argv[])
{
    // parameters
    std::map<std::string, docopt::value> args =
        docopt::docopt(USAGE, {argv + 1, argv + argc}, true, "raytracer 0.2");

    const Configuration conf
        { 1, 0, 1, 0  // unused configuration arguments
        , args["--threads"].asLong()
        , args["--background"].asString()
        , std::stof(args["--inverse-gamma"].asString())
        , args["--no-gamma-correction"].asBool()
        };

    // import scene
    Assimp::Importer importer;
    const aiScene* scene = importer.ReadFile(
        args["<filename>"].asString().c_str(),
        aiProcess_CalcTangentSpace       |
        aiProcess_Triangulate            |
        aiProcess_JoinIdenticalVertices  |
        aiProcess_GenNormals             |
        aiProcess_SortByPType);

    if (!scene) {
        std::cout << importer.GetErrorString() << std::endl;
        return 1;
    }

    // setup camera
    assert(scene->mNumCameras == 1);  // we can deal only with a single camera
    auto& sceneCam = *scene->mCameras[0];
    if (args["--aspect"]) {
        sceneCam.mAspect = std::stof(args["--aspect"].asString());
        assert(sceneCam.mAspect > 0);
    } else if (sceneCam.mAspect == 0) {
        sceneCam.mAspect = 1.f;
    }
    auto* camNode = scene->mRootNode->FindNode(sceneCam.mName);
    assert(camNode != nullptr);
    const Camera cam(camNode->mTransformation, sceneCam);

    // load triangles from the scene into a kd-tree
    auto triangles = triangles_from_scene(scene);
    Stats::instance().num_triangles = triangles.size();
    Tree tree(std::move(triangles));

    // compute radiosity
    auto radiosity = compute_radiosity(tree);

    //
    // Raycaster
    //

    int width = args["--width"].asLong();
    assert(width > 0);
    int height = width / cam.mAspect;

    Image image(width, height);
    {
        Runtime rt(Stats::instance().runtime_ms);

        std::cerr << "Rendering          ";

        ThreadPool pool(conf.num_threads);
        std::vector<std::future<void>> tasks;

        for (int y = 0; y < height; ++y) {
            tasks.emplace_back(pool.enqueue([
                &image, &cam, &tree, &radiosity, width, height, y, &conf]()
            {
                for (int x = 0; x < width; ++x) {
                    for (int i = 0; i < conf.num_pixel_samples; ++i) {
                        auto cam_dir = cam.raster2cam(
                            aiVector2D(x, y), width, height);

                        Stats::instance().num_prim_rays += 1;
                        image(x, y) += trace(
                            cam.mPosition, cam_dir, tree, radiosity, conf);
                    }

                    // gamma correction
                    if (conf.gamma_correction_enabled) {
                        image(x, y).r = powf(image(x, y).r, conf.inverse_gamma);
                        image(x, y).g = powf(image(x, y).g, conf.inverse_gamma);
                        image(x, y).b = powf(image(x, y).b, conf.inverse_gamma);
                    }
                }
            }));
        }

        long completed = 0;

        for (auto& task: tasks) {
            task.get();
            completed += 1;
            float progress = static_cast<float>(completed) / tasks.size();
            int bar_width = progress * 20;
            std::cerr
                << "\rRendering          "
                << "[" << std::string(bar_width, '-')
                << std::string(20 - bar_width, ' ') << "] "
                << std::setfill(' ') << std::setw(6)
                << std::fixed << std::setprecision(2) << (progress * 100.0) << '%';
            std::cerr.flush();
        }
        std::cerr << std::endl;

        // Render feature lines after
        // "Ray Tracing NPR-Style Feature Lines" by Choudhury and Parker.
        std::cerr << "Drawing mesh lines ";
        std::vector<std::future<void>> mesh_tasks;
        constexpr float offset = 1.f;
        constexpr std::array<Vec2, 8> offsets =
            { Vec2{0.f, 0.f}
            , Vec2{offset, 0.f}
            , Vec2{offset, offset}
            , Vec2{0.f, offset}
            , Vec2{0.f, offset / 2}
            , Vec2{offset / 2, offset}
            , Vec2{offset, offset / 2}
            , Vec2{offset / 2, 0.f}
            };
        for (int y = 0; y < height; ++y) {
            mesh_tasks.emplace_back(pool.enqueue([
                &image, &offsets, &cam, &tree, width, height, y, &conf]()
            {
                for (int x = 0; x < width; ++x) {
                    float dist_to_triangle, s, t;
                    std::unordered_set<Tree::OptionalId> triangle_ids;

                    // Shoot center ray.
                    auto cam_dir = cam.raster2cam(
                        aiVector2D(x + 0.5f, y + 0.5f), width, height);
                    auto center_id = tree.intersect(
                            Ray(cam.mPosition, cam_dir), dist_to_triangle, s, t);
                    triangle_ids.insert(center_id);

                    // Sample disc rays around center.
                    // TODO: Sample disc with Poisson or similar.
                    for (auto offset : offsets) {
                        cam_dir = cam.raster2cam(
                            aiVector2D(x + offset[0], y + offset[1]), width, height);
                        auto id = tree.intersect(
                            Ray(cam.mPosition, cam_dir), dist_to_triangle, s, t);
                        triangle_ids.insert(id);
                    }

                    constexpr float M_2 = 0.5f * offsets.size();
                    // All hit primitives except the one hit by center.
                    const float m = triangle_ids.size() - 1.f;
                    float e = std::pow(std::abs(m - M_2) / M_2, 10);
                    image(x, y) = image(x, y) * e;
                }
            }));
        }

        completed = 0;
        for (auto& task: mesh_tasks) {
            task.get();
            completed += 1;
            float progress = static_cast<float>(completed) / mesh_tasks.size();
            int bar_width = progress * 20;
            std::cerr
                << "\rDrawing mesh lines "
                << "[" << std::string(bar_width, '-')
                << std::string(20 - bar_width, ' ') << "] "
                << std::setfill(' ') << std::setw(6)
                << std::fixed << std::setprecision(2) << (progress * 100.0) << '%';
            std::cerr.flush();
        }
        std::cerr << std::endl;
    }

    // output stats
    std::cerr << Stats::instance() << std::endl;

    // output image
    std::cout << image << std::endl;
    return 0;
}
<|endoftext|>


<commit_before>//Sample provided by Thiago Massari Guedes
//August 2015
//http://www.simplycpp.com/

#include "example_main.h"

#include <iostream>
#include <map>

using namespace std;

struct Cash {
	using Date = unsigned int;
	Date date; //yyyymmdd
	double value;
};

template<typename T>
struct CashRange {
	using const_iterator = typename T::const_iterator;

	CashRange(const_iterator begin, const_iterator end)
	{
		_begin = begin;
		_end = end;
	}

	const const_iterator begin() const { return _begin; }
	const const_iterator end() const { return _end; }

private:
	const_iterator _begin;
	const_iterator _end;
};

class CashFlow {
	using CashMap = multimap<Cash::Date, Cash>;
	using const_iterator = multimap<Cash::Date, Cash>::const_iterator;
	multimap<Cash::Date, Cash> _flow;

public:

	CashFlow()
	{
		int date = 20150000;
		for(int m=1; m < 8; m+=2) {
			for(int d=1; d <= 30; d += 6) {
				int dt = date+(m*100)+d;
				Cash c; c.date = dt; (c.value = m + d * 10.0) / (double)(d*10-m);
				_flow.insert( make_pair(dt, c) );
			}
		}

	}

	auto getMonthlyFlow(int year, int month) -> CashRange<CashMap>
	{
		int kb = (year * 10000) + (month * 100) + 00;
		int ke = (year * 10000) + (month * 100) + 31;
		const auto b = _flow.lower_bound(kb);
		if( b == _flow.end() ) {
			return CashRange<CashMap>(_flow.cend(), _flow.cend());
		}
		const auto e = _flow.upper_bound(ke);
		return CashRange<CashMap>(b, e);
	}

};

EXAMPLE_MAIN(range_for)
{

	cout << "Getting data from 2015-03" << endl;
	CashFlow flow;
	for(auto &c : flow.getMonthlyFlow(2015, 03)) {
		cout << c.second.date << ": " << c.second.value << endl;
	}

	return 0;
}
<commit_msg>small fix<commit_after>//Sample provided by Thiago Massari Guedes
//August 2015
//http://www.simplycpp.com/

#include "example_main.h"

#include <iostream>
#include <map>

using namespace std;

struct Cash {
	using Date = unsigned int;
	Date date; //yyyymmdd
	double value;
};

template<typename T>
struct CashRange {
	using const_iterator = typename T::const_iterator;

	CashRange(const_iterator begin, const_iterator end)
	{
		_begin = begin;
		_end = end;
	}

	const const_iterator begin() const { return _begin; }
	const const_iterator end() const { return _end; }

private:
	const_iterator _begin;
	const_iterator _end;
};

class CashFlow {
	using CashMap = multimap<Cash::Date, Cash>;
	using const_iterator = multimap<Cash::Date, Cash>::const_iterator;
	multimap<Cash::Date, Cash> _flow;

public:

	CashFlow()
	{
		int date = 20150000;
		for(int m=1; m < 8; m+=2) {
			for(int d=1; d <= 30; d += 6) {
				int dt = date+(m*100)+d;
				Cash c; c.date = dt; c.value = (m + d * 10.0) / (double)(d*10-m);
				_flow.insert( make_pair(dt, c) );
			}
		}

	}

	auto getMonthlyFlow(int year, int month) -> CashRange<CashMap>
	{
		int kb = (year * 10000) + (month * 100) + 00;
		int ke = (year * 10000) + (month * 100) + 31;
		const auto b = _flow.lower_bound(kb);
		if( b == _flow.end() ) {
			return CashRange<CashMap>(_flow.cend(), _flow.cend());
		}
		const auto e = _flow.upper_bound(ke);
		return CashRange<CashMap>(b, e);
	}

};

EXAMPLE_MAIN(range_for)
{

	cout << "Getting data from 2015-03" << endl;
	CashFlow flow;
	for(auto &c : flow.getMonthlyFlow(2015, 03)) {
		cout << c.second.date << ": " << c.second.value << endl;
	}

	return 0;
}
<|endoftext|>


<commit_before>/** \copyright
 * Copyright (c) 2017, Balazs Racz
 * 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.
 *
 * 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.
 *
 * \file CDIUtils.hxx
 *
 * Utility library for interpreting CDI XML files.
 *
 * @author Balazs Racz
 * @date 31 Aug 2017
 */

#ifndef _OPENLCB_CDIUTILS_HXX_
#define _OPENLCB_CDIUTILS_HXX_

#include "sxmlc.h"

namespace openlcb
{

class CDIUtils
{
public:
    /// Helper class for unique_ptr to delete an XML document correctly.
    struct XMLDocDeleter
    {
        void operator()(XMLDoc *doc)
        {
            if (!doc)
                return;
            cleanup_doc(doc);
            XMLDoc_free(doc);
            delete doc;
        }
    };

    /// Smart pointer class for holding XML documents.
    typedef std::unique_ptr<XMLDoc, XMLDocDeleter> xmldoc_ptr_t;

    /// Helper class for deleting sxml strings.
    struct SXmlStringDeleter
    {
        void operator()(const SXML_CHAR* data) {
            free(const_cast<SXML_CHAR*>(data));
        }
    };
    typedef std::unique_ptr<const SXML_CHAR, SXmlStringDeleter> xmlstring_t;

    /// Searches the list of children for the first child with a specific tag.
    /// @param parent is the node whose children to search.
    /// @param tag is which child to look for.
    /// @return nullptr if not found, else the first occurrence of tag in the
    /// children.
    static XMLNode *find_child_or_null(const XMLNode *parent, const char *tag)
    {
        auto count = XMLNode_get_children_count(parent);
        for (int i = 0; i < count; ++i)
        {
            auto *c = XMLNode_get_child(parent, i);
            if (strcmp(tag, c->tag) == 0)
                return c;
        }
        return nullptr;
    }

    /// Finds the name of a CDI element.
    ///
    /// @param node is a CDI element (segment, group, or config element).
    /// @return finds a child tag <name> and returns its contents. Returns
    /// empty string if no <name> was found.
    static string find_node_name(XMLNode *node, const char *def)
    {
        auto *n = find_child_or_null(node, "name");
        if (n == nullptr || n->text == nullptr)
            return def;
        return n->text;
    }

    /// Find the description of a CDI element.
    ///
    /// @param node is a CDI element (segment, group, or config element).
    /// @return finds a child tag <description> and returns its
    /// contents. Returns empty string if no <description> was found.
    static string find_node_description(XMLNode *node)
    {
        auto *n = find_child_or_null(node, "description");
        if (n == nullptr || n->text == nullptr)
            return "";
        return n->text;
    }

    /// Clears out al luserinfo structure pointers. This is necessary to use
    /// the new_userinfo call below. Call this after the XML has been
    /// successfully parsed.
    /// @param doc document to prepare up.
    static void prepare_doc(XMLDoc *doc)
    {
        auto *node = XMLDoc_root(doc);
        while (node)
        {
            node->user = nullptr;
            node = XMLNode_next(node);
        }
    }

    /// Deletes all userinfo structures allocated in a doc.
    /// @param doc document to clean up.
    static void cleanup_doc(XMLDoc *doc)
    {
        auto *node = XMLDoc_root(doc);
        while (node)
        {
            free(node->user);
            node->user = nullptr;
            node = XMLNode_next(node);
        }
    }

    /// Allocates a new object of type T for the node as userinfo; calls T's
    /// constructor with args..., stores the resulting pointer in the userinfo
    /// pointer of node.
    /// @param info output argument for the userinfo structure pointer.
    /// @param node which XML node the userinfo should point at.
    /// @param args... forwarded as the constructor arguments for T (can be
    /// empty).
    template <class T, typename... Args>
    static void new_userinfo(T **info, XMLNode *node, Args &&... args)
    {
        HASSERT(node->user == nullptr);
        static_assert(std::is_trivially_destructible<T>::value == true,
            "Userdata attached to nodes must be trivially destructible");
        *info = static_cast<T *>(malloc(sizeof(T)));
        new (*info) T(std::forward<Args>(args)...);
        node->user = *info;
    }

    /// Retrieve the userinfo structure from an XML node.
    /// @param info will be set to the userinfo structure using an unchecked
    /// cast to T. This variable must be of the same (or compatible) type as
    /// what the userinfo has been allocated to.
    /// @param node is the XML element node whose userinfo we are trying to
    /// fetch
    template <class T> static void get_userinfo(T **info, const XMLNode *node)
    {
        HASSERT(node);
        *info = static_cast<T *>(node->user);
    }

    /// Allocation data we hold about a Data Element in its userinfo structure.
    struct NodeInfo
    {
        /// Offset of the address of this element from the address of the
        /// parent group element. This is the sum of size values of the
        /// preceding elements within the given group. Inside a repeated group
        /// these offsets are counted from the current repetition start offset.
        int offset_from_parent = 0;
        /// Total number of bytes that this element occupies. This includes all
        /// repetitions for a repeated group.
        int size = 0;
    };

    static int get_numeric_attribute(
        const XMLNode *node, const char *attr_name, int def = 0)
    {
        const SXML_CHAR *attr_value;
        XMLNode_get_attribute_with_default(
            const_cast<XMLNode*>(node), attr_name, &attr_value, nullptr);
        xmlstring_t d(attr_value);
        if ((!attr_value) || (attr_value[0] == 0))
        {
            return def;
        }
        return atoi(attr_value);
    }

    /// Allocates all userinfo structures within a segment and performs the
    /// offset layout algorithm.
    static void layout_segment(XMLNode *segment)
    {
        HASSERT(strcmp(segment->tag, "segment") == 0);
        unsigned current_offset = get_numeric_attribute(segment, "origin");
        NodeInfo *info;
        new_userinfo(&info, segment);
        info->offset_from_parent = current_offset;
        if (XMLNode_get_children_count(segment) == 0)
            return;
        XMLNode *current_parent = segment;
        XMLNode *current_child = XMLNode_get_child(segment, 0);
        NodeInfo *parent_info = info;
        while (true)
        {
            if (strcmp(current_child->tag, "name") == 0 ||
                strcmp(current_child->tag, "description") == 0 ||
                strcmp(current_child->tag, "repname") == 0)
            {
                // Do nothing, not a data element
            }
            else
            {
                new_userinfo(&info, current_child);
                parent_info->size +=
                    get_numeric_attribute(current_child, "offset", 0);
                info->offset_from_parent = parent_info->size;
                if (strcmp(current_child->tag, "eventid") == 0)
                {
                    info->size = 8;
                }
                else if (strcmp(current_child->tag, "string") == 0 ||
                    strcmp(current_child->tag, "int") == 0 ||
                    strcmp(current_child->tag, "float") == 0)
                {
                    info->size =
                        get_numeric_attribute(current_child, "size", 1);
                }
                else if (strcmp(current_child->tag, "group") == 0)
                {
                    if (XMLNode_get_children_count(current_child) > 0)
                    {
                        current_parent = current_child;
                        current_child = XMLNode_get_child(current_parent, 0);
                        get_userinfo(&parent_info, current_parent);
                        continue;
                    }
                    // an empty group has size == 0 and we don't need to do
                    // anything here.
                }
                parent_info->size += info->size;
            }

            // Move to next child.
            while ((current_child = XMLNode_next_sibling(current_child)) ==
                nullptr)
            {
                // End of children; must go up.
                if (current_parent == segment)
                {
                    // nowhere to go up
                    break;
                }
                current_child = current_parent;
                current_parent = current_child->father;
                // handle groups with repetitions
                get_userinfo(&info, current_child);
                get_userinfo(&parent_info, current_parent);
                int repcount =
                    get_numeric_attribute(current_child, "replication", 1);
                info->size *= repcount;
                parent_info->size += info->size;
            }
            if (current_parent == segment && current_child == nullptr)
            {
                // end of iteration
                break;
            }
        }
    }

    /// @return the number of replicas a group has, if it is a repeated group,
    /// or 1 if it is a non-repeated group.
    static int get_replication(XMLNode *group)
    {
        HASSERT(strcmp(group->tag, "group") == 0);
        return get_numeric_attribute(group, "replication", 1);
    }

    struct CDINodeRep
    {
        /// Element in the XML where we are. This is a segment or a group node.
        const XMLNode *node_;
        /// address in the current space of the beginning of the current
        /// node. For segments, this is the origin; for groups this is the
        /// address of the virtual zero-length data element at the beginning of
        /// the group. For repeated groups this is meaningful only with a given
        /// repetition of a group.
        unsigned address_;

        /// Initializes a group rep from a segment root.
        /// @param segment is the XML element representing the segment root.
        CDINodeRep(const XMLNode *segment)
        {
            NodeInfo *info;
            get_userinfo(&info, segment);
            HASSERT(info);
            node_ = segment;
            address_ = info->offset_from_parent;
        }

        /// Initializes a group rep which has no replication.
        /// @param parent is the Node representation of the parent group or
        /// segment.
        /// @param group is the XML element for the current (child) group. it
        /// must have no replication.
        CDINodeRep(const CDINodeRep *parent, XMLNode *group)
        {
            HASSERT(group->father == parent->node_);
            node_ = group;
            HASSERT(get_replication(group) == 1);
            address_ = parent->get_child_address(group);
        }

        /// Initializes a group rep for a given replica.
        /// @param parent is the Node representation of the parent group or
        /// segment.
        /// @param group is the XML element for the current (child) group. it
        /// must have replication.
        /// @param replica is the number of the replication (zero to
        /// replication - 1).
        CDINodeRep(const CDINodeRep *parent, XMLNode *group, unsigned replica)
        {
            HASSERT(group->father == parent->node_);
            node_ = group;
            int replication = get_replication(group);
            HASSERT(replication > 1);
            NodeInfo *info;
            get_userinfo(&info, group);
            HASSERT(info);
            unsigned base_address = parent->get_child_address(group);
            unsigned stride = info->size / replication;
            address_ = base_address + stride * replica;
        }

        /// Resets the current representation. Use any constructor argument
        /// set.
        template <typename... Args> void reset(Args &&... args)
        {
            new (this) CDINodeRep(std::forward<Args>(args)...);
        }

        /// Gets the absolute address of a given child in the current segment.
        unsigned get_child_address(XMLNode *child) const
        {
            HASSERT(child->father == node_);
            NodeInfo *info;
            get_userinfo(&info, child);
            HASSERT(info);
            return address_ + info->offset_from_parent;
        }
    };

private:
    // Static class; never instantiated.
    CDIUtils();
};

} // namespace openlcb

#endif // _OPENLCB_CDIUTILS_HXX_
<commit_msg>Makes XMLNode pointers be const correct. Adds a couple of extra constructors to the CDINodeRep.<commit_after>/** \copyright
 * Copyright (c) 2017, Balazs Racz
 * 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.
 *
 * 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.
 *
 * \file CDIUtils.hxx
 *
 * Utility library for interpreting CDI XML files.
 *
 * @author Balazs Racz
 * @date 31 Aug 2017
 */

#ifndef _OPENLCB_CDIUTILS_HXX_
#define _OPENLCB_CDIUTILS_HXX_

#include "sxmlc.h"

namespace openlcb
{

class CDIUtils
{
public:
    /// Helper class for unique_ptr to delete an XML document correctly.
    struct XMLDocDeleter
    {
        void operator()(XMLDoc *doc)
        {
            if (!doc)
                return;
            cleanup_doc(doc);
            XMLDoc_free(doc);
            delete doc;
        }
    };

    /// Smart pointer class for holding XML documents.
    typedef std::unique_ptr<XMLDoc, XMLDocDeleter> xmldoc_ptr_t;

    /// Helper class for deleting sxml strings.
    struct SXmlStringDeleter
    {
        void operator()(const SXML_CHAR *data)
        {
            free(const_cast<SXML_CHAR *>(data));
        }
    };
    typedef std::unique_ptr<const SXML_CHAR, SXmlStringDeleter> xmlstring_t;

    /// Searches the list of children for the first child with a specific tag.
    /// @param parent is the node whose children to search.
    /// @param tag is which child to look for.
    /// @return nullptr if not found, else the first occurrence of tag in the
    /// children.
    static XMLNode *find_child_or_null(const XMLNode *parent, const char *tag)
    {
        auto count = XMLNode_get_children_count(parent);
        for (int i = 0; i < count; ++i)
        {
            auto *c = XMLNode_get_child(parent, i);
            if (strcmp(tag, c->tag) == 0)
                return c;
        }
        return nullptr;
    }

    /// Finds the name of a CDI element.
    ///
    /// @param node is a CDI element (segment, group, or config element).
    /// @return finds a child tag <name> and returns its contents. Returns
    /// empty string if no <name> was found.
    static string find_node_name(const XMLNode *node, const char *def)
    {
        auto *n = find_child_or_null(node, "name");
        if (n == nullptr || n->text == nullptr)
            return def;
        return n->text;
    }

    /// Find the description of a CDI element.
    ///
    /// @param node is a CDI element (segment, group, or config element).
    /// @return finds a child tag <description> and returns its
    /// contents. Returns empty string if no <description> was found.
    static string find_node_description(const XMLNode *node)
    {
        auto *n = find_child_or_null(node, "description");
        if (n == nullptr || n->text == nullptr)
            return "";
        return n->text;
    }

    /// Clears out al luserinfo structure pointers. This is necessary to use
    /// the new_userinfo call below. Call this after the XML has been
    /// successfully parsed.
    /// @param doc document to prepare up.
    static void prepare_doc(XMLDoc *doc)
    {
        auto *node = XMLDoc_root(doc);
        while (node)
        {
            node->user = nullptr;
            node = XMLNode_next(node);
        }
    }

    /// Deletes all userinfo structures allocated in a doc.
    /// @param doc document to clean up.
    static void cleanup_doc(XMLDoc *doc)
    {
        auto *node = XMLDoc_root(doc);
        while (node)
        {
            free(node->user);
            node->user = nullptr;
            node = XMLNode_next(node);
        }
    }

    /// Allocates a new object of type T for the node as userinfo; calls T's
    /// constructor with args..., stores the resulting pointer in the userinfo
    /// pointer of node.
    /// @param info output argument for the userinfo structure pointer.
    /// @param node which XML node the userinfo should point at.
    /// @param args... forwarded as the constructor arguments for T (can be
    /// empty).
    template <class T, typename... Args>
    static void new_userinfo(T **info, XMLNode *node, Args &&... args)
    {
        HASSERT(node->user == nullptr);
        static_assert(std::is_trivially_destructible<T>::value == true,
            "Userdata attached to nodes must be trivially destructible");
        *info = static_cast<T *>(malloc(sizeof(T)));
        new (*info) T(std::forward<Args>(args)...);
        node->user = *info;
    }

    /// Retrieve the userinfo structure from an XML node.
    /// @param info will be set to the userinfo structure using an unchecked
    /// cast to T. This variable must be of the same (or compatible) type as
    /// what the userinfo has been allocated to.
    /// @param node is the XML element node whose userinfo we are trying to
    /// fetch
    template <class T> static void get_userinfo(T **info, const XMLNode *node)
    {
        HASSERT(node);
        *info = static_cast<T *>(node->user);
    }

    /// Allocation data we hold about a Data Element in its userinfo structure.
    struct NodeInfo
    {
        /// Offset of the address of this element from the address of the
        /// parent group element. This is the sum of size values of the
        /// preceding elements within the given group. Inside a repeated group
        /// these offsets are counted from the current repetition start offset.
        int offset_from_parent = 0;
        /// Total number of bytes that this element occupies. This includes all
        /// repetitions for a repeated group.
        int size = 0;
    };

    static int get_numeric_attribute(
        const XMLNode *node, const char *attr_name, int def = 0)
    {
        const SXML_CHAR *attr_value;
        XMLNode_get_attribute_with_default(
            const_cast<XMLNode *>(node), attr_name, &attr_value, nullptr);
        xmlstring_t d(attr_value);
        if ((!attr_value) || (attr_value[0] == 0))
        {
            return def;
        }
        return atoi(attr_value);
    }

    /// Allocates all userinfo structures within a segment and performs the
    /// offset layout algorithm.
    static void layout_segment(XMLNode *segment)
    {
        HASSERT(strcmp(segment->tag, "segment") == 0);
        unsigned current_offset = get_numeric_attribute(segment, "origin");
        NodeInfo *info;
        new_userinfo(&info, segment);
        info->offset_from_parent = current_offset;
        if (XMLNode_get_children_count(segment) == 0)
            return;
        XMLNode *current_parent = segment;
        XMLNode *current_child = XMLNode_get_child(segment, 0);
        NodeInfo *parent_info = info;
        while (true)
        {
            if (strcmp(current_child->tag, "name") == 0 ||
                strcmp(current_child->tag, "description") == 0 ||
                strcmp(current_child->tag, "repname") == 0)
            {
                // Do nothing, not a data element
            }
            else
            {
                new_userinfo(&info, current_child);
                parent_info->size +=
                    get_numeric_attribute(current_child, "offset", 0);
                info->offset_from_parent = parent_info->size;
                if (strcmp(current_child->tag, "eventid") == 0)
                {
                    info->size = 8;
                }
                else if (strcmp(current_child->tag, "string") == 0 ||
                    strcmp(current_child->tag, "int") == 0 ||
                    strcmp(current_child->tag, "float") == 0)
                {
                    info->size =
                        get_numeric_attribute(current_child, "size", 1);
                }
                else if (strcmp(current_child->tag, "group") == 0)
                {
                    if (XMLNode_get_children_count(current_child) > 0)
                    {
                        current_parent = current_child;
                        current_child = XMLNode_get_child(current_parent, 0);
                        get_userinfo(&parent_info, current_parent);
                        continue;
                    }
                    // an empty group has size == 0 and we don't need to do
                    // anything here.
                }
                parent_info->size += info->size;
            }

            // Move to next child.
            while ((current_child = XMLNode_next_sibling(current_child)) ==
                nullptr)
            {
                // End of children; must go up.
                if (current_parent == segment)
                {
                    // nowhere to go up
                    break;
                }
                current_child = current_parent;
                current_parent = current_child->father;
                // handle groups with repetitions
                get_userinfo(&info, current_child);
                get_userinfo(&parent_info, current_parent);
                int repcount =
                    get_numeric_attribute(current_child, "replication", 1);
                info->size *= repcount;
                parent_info->size += info->size;
            }
            if (current_parent == segment && current_child == nullptr)
            {
                // end of iteration
                break;
            }
        }
    }

    /// @return the number of replicas a group has, if it is a repeated group,
    /// or 1 if it is a non-repeated group.
    static int get_replication(XMLNode *group)
    {
        HASSERT(strcmp(group->tag, "group") == 0);
        return get_numeric_attribute(group, "replication", 1);
    }

    struct CDINodeRep
    {
        /// Element in the XML where we are. This is a segment or a group node.
        const XMLNode *node_;
        /// address in the current space of the beginning of the current
        /// node. For segments, this is the origin; for groups this is the
        /// address of the virtual zero-length data element at the beginning of
        /// the group. For repeated groups this is meaningful only with a given
        /// repetition of a group.
        unsigned address_;

        CDINodeRep()
            : node_(nullptr)
            , address_(0)
        {
        }

        /// Initializes a group rep from an arbitrary XML element (e.g. the cdi
        /// root). Tihs does not allow child computations.
        /// @param node is the XML element.
        CDINodeRep(const XMLNode *node, std::nullptr_t)
            : node_(node)
            , address_(0)
        {
        }

        /// Initializes a group rep from a segment root.
        /// @param segment is the XML element representing the segment root.
        CDINodeRep(const XMLNode *segment)
        {
            NodeInfo *info;
            get_userinfo(&info, segment);
            HASSERT(info);
            node_ = segment;
            address_ = info->offset_from_parent;
        }

        /// Initializes a group rep which has no replication.
        /// @param parent is the Node representation of the parent group or
        /// segment.
        /// @param group is the XML element for the current (child) group. it
        /// must have no replication.
        CDINodeRep(const CDINodeRep *parent, XMLNode *group)
        {
            HASSERT(group->father == parent->node_);
            node_ = group;
            HASSERT(get_replication(group) == 1);
            address_ = parent->get_child_address(group);
        }

        /// Initializes a group rep for a given replica.
        /// @param parent is the Node representation of the parent group or
        /// segment.
        /// @param group is the XML element for the current (child) group. it
        /// must have replication.
        /// @param replica is the number of the replication (zero to
        /// replication - 1).
        CDINodeRep(const CDINodeRep *parent, XMLNode *group, unsigned replica)
        {
            HASSERT(group->father == parent->node_);
            node_ = group;
            int replication = get_replication(group);
            HASSERT(replication > 1);
            NodeInfo *info;
            get_userinfo(&info, group);
            HASSERT(info);
            unsigned base_address = parent->get_child_address(group);
            unsigned stride = info->size / replication;
            address_ = base_address + stride * replica;
        }

        /// Resets the current representation. Use any constructor argument
        /// set.
        template <typename... Args> void reset(Args &&... args)
        {
            new (this) CDINodeRep(std::forward<Args>(args)...);
        }

        /// Gets the absolute address of a given child in the current segment.
        unsigned get_child_address(XMLNode *child) const
        {
            HASSERT(child->father == node_);
            NodeInfo *info;
            get_userinfo(&info, child);
            HASSERT(info);
            return address_ + info->offset_from_parent;
        }
    };

private:
    // Static class; never instantiated.
    CDIUtils();
};

} // namespace openlcb

#endif // _OPENLCB_CDIUTILS_HXX_
<|endoftext|>


<commit_before>/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2013 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#include <xenia/kernel/xboxkrnl_rtl.h>

#include <xenia/kernel/kernel_state.h>
#include <xenia/kernel/xboxkrnl_private.h>
#include <xenia/kernel/objects/xthread.h>
#include <xenia/kernel/objects/xuser_module.h>
#include <xenia/kernel/util/shim_utils.h>
#include <xenia/kernel/util/xex2.h>

using namespace xe;
using namespace xe::kernel;
using namespace xe::kernel::xboxkrnl;

namespace xe {
namespace kernel {

// TODO: clean me up!
SHIM_CALL _vsnprintf_shim(
    PPCContext* ppc_state, KernelState* state) {

  uint32_t buffer_ptr = SHIM_GET_ARG_32(0);
  uint32_t count = SHIM_GET_ARG_32(1);
  uint32_t format_ptr = SHIM_GET_ARG_32(2);
  uint32_t arg_ptr = SHIM_GET_ARG_32(3);

  if (format_ptr == 0) {
    SHIM_SET_RETURN(-1);
    return;
  }

  char *buffer = (char *)SHIM_MEM_ADDR(buffer_ptr);  // TODO: ensure it never writes past the end of the buffer (count)...
  const char *format = (const char *)SHIM_MEM_ADDR(format_ptr);

  int arg_index = 0;

  char *b = buffer;
  for (; *format != '\0'; ++format) {
    const char *start = format;

    if (*format != '%') {
      *b++ = *format;
      continue;
    }

    ++format;
    if (*format == '\0') {
      break;
    }

    if (*format == '%') {
      *b++ = *format;
      continue;
    }

    const char *end;
    end = format;

    // skip flags
    while (*end == '-' ||
           *end == '+' ||
           *end == ' ' ||
           *end == '#' ||
           *end == '0') {
      ++end;
    }

    if (*end == '\0') {
      break;
    }

    int arg_extras = 0;

    // skip width
    if (*end == '*') {
      ++end;
      arg_extras++;
    }
    else {
      while (*end >= '0' && *end <= '9') {
        ++end;
      }
    }

    if (*end == '\0') {
      break;
    }

    // skip precision
    if (*end == '.') {
      ++end;

      if (*end == '*') {
        ++end;
        ++arg_extras;
      }
      else {
        while (*end >= '0' && *end <= '9') {
          ++end;
        }
      }
    }

    if (*end == '\0') {
      break;
    }

    // get length
    int arg_size = 4;

    if (*end == 'h') {
      ++end;
      arg_size = 4;
      if (*end == 'h') {
        ++end;
      }
    }
    else if (*end == 'l') {
      ++end;
      arg_size = 4;
      if (*end == 'l') {
        ++end;
        arg_size = 8;
      }
    }
    else if (*end == 'j') {
      arg_size = 8;
      ++end;
    }
    else if (*end == 'z') {
      arg_size = 4;
      ++end;
    }
    else if (*end == 't') {
      arg_size = 8;
      ++end;
    }
    else if (*end == 'L') {
      arg_size = 8;
      ++end;
    }

    if (*end == '\0') {
      break;
    }

    if (*end == 'd' ||
        *end == 'i' ||
        *end == 'u' ||
        *end == 'o' ||
        *end == 'x' ||
        *end == 'X' ||
        *end == 'f' ||
        *end == 'F' ||
        *end == 'e' ||
        *end == 'E' ||
        *end == 'g' ||
        *end == 'G' ||
        *end == 'a' ||
        *end == 'A' ||
        *end == 'c') {
      char local[512];
      local[0] = '\0';
      strncat(local, start, end + 1 - start);

      XEASSERT(arg_size == 8 || arg_size == 4);
      if (arg_size == 8) {
        if (arg_extras == 0) {
          uint64_t value = SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
          int result = sprintf(b, local, value);
          b += result;
          arg_index++;
        }
        else {
          XEASSERT(false);
        }
      }
      else if (arg_size == 4) {
        if (arg_extras == 0) {
          uint32_t value = (uint32_t)SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
          int result = sprintf(b, local, value);
          b += result;
          arg_index++;
        }
        else {
          XEASSERT(false);
        }
      }
    }
    else if (*end == 's' ||
             *end == 'p' ||
             *end == 'n') {
      char local[512];
      local[0] = '\0';
      strncat(local, start, end + 1 - start);

      XEASSERT(arg_size == 4);
      if (arg_extras == 0) {
        uint32_t value = (uint32_t)SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
        const char *pointer = (const char *)SHIM_MEM_ADDR(value);
        int result = sprintf(b, local, pointer);
        b += result;
        arg_index++;
      }
      else {
        XEASSERT(false);
      }
    }
    else {
      XEASSERT(false);
      break;
    }
    format = end;
  }
  *b = '\0';
  SHIM_SET_RETURN((uint32_t)(b - buffer));
}

// TODO: clean me up!
SHIM_CALL _vswprintf_shim(
    PPCContext* ppc_state, KernelState* state) {

  uint32_t buffer_ptr = SHIM_GET_ARG_32(0);
  uint32_t format_ptr = SHIM_GET_ARG_32(1);
  uint32_t arg_ptr = SHIM_GET_ARG_32(2);

  if (format_ptr == 0) {
    SHIM_SET_RETURN(-1);
    return;
  }

  wchar_t*buffer = (wchar_t*)SHIM_MEM_ADDR(buffer_ptr);  // TODO: ensure it never writes past the end of the buffer (count)...
  const wchar_t* format = (const wchar_t*)SHIM_MEM_ADDR(format_ptr);

  // this will work since a null is the same regardless of endianness
  size_t format_length = wcslen(format);

  // swap the format buffer
  wchar_t *swapped_format = (wchar_t*)xe_malloc((format_length + 1) * sizeof(wchar_t));
  for (size_t i = 0; i < format_length; ++i)
  {
    swapped_format[i] = XESWAP16(format[i]);
  }
  swapped_format[format_length] = '\0';

  // be sneaky
  format = swapped_format;

  int arg_index = 0;

  wchar_t *b = buffer;
  for (; *format != '\0'; ++format) {
    const wchar_t *start = format;

    if (*format != '%') {
      *b++ = *format;
      continue;
    }

    ++format;
    if (*format == '\0') {
      break;
    }

    if (*format == '%') {
      *b++ = *format;
      continue;
    }

    const wchar_t *end;
    end = format;

    // skip flags
    while (*end == '-' ||
           *end == '+' ||
           *end == ' ' ||
           *end == '#' ||
           *end == '0') {
      ++end;
    }

    if (*end == '\0') {
      break;
    }

    int arg_extras = 0;

    // skip width
    if (*end == '*') {
      ++end;
      arg_extras++;
    }
    else {
      while (*end >= '0' && *end <= '9') {
        ++end;
      }
    }

    if (*end == '\0') {
      break;
    }

    // skip precision
    if (*end == '.') {
      ++end;

      if (*end == '*') {
        ++end;
        ++arg_extras;
      }
      else {
        while (*end >= '0' && *end <= '9') {
          ++end;
        }
      }
    }

    if (*end == '\0') {
      break;
    }

    // get length
    int arg_size = 4;

    if (*end == 'h') {
      ++end;
      arg_size = 4;
      if (*end == 'h') {
        ++end;
      }
    }
    else if (*end == 'l') {
      ++end;
      arg_size = 4;
      if (*end == 'l') {
        ++end;
        arg_size = 8;
      }
    }
    else if (*end == 'j') {
      arg_size = 8;
      ++end;
    }
    else if (*end == 'z') {
      arg_size = 4;
      ++end;
    }
    else if (*end == 't') {
      arg_size = 8;
      ++end;
    }
    else if (*end == 'L') {
      arg_size = 8;
      ++end;
    }

    if (*end == '\0') {
      break;
    }

    if (*end == 'd' ||
        *end == 'i' ||
        *end == 'u' ||
        *end == 'o' ||
        *end == 'x' ||
        *end == 'X' ||
        *end == 'f' ||
        *end == 'F' ||
        *end == 'e' ||
        *end == 'E' ||
        *end == 'g' ||
        *end == 'G' ||
        *end == 'a' ||
        *end == 'A' ||
        *end == 'c') {
      wchar_t local[512];
      local[0] = '\0';
      wcsncat(local, start, end + 1 - start);

      XEASSERT(arg_size == 8 || arg_size == 4);
      if (arg_size == 8) {
        if (arg_extras == 0) {
          uint64_t value = SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
          int result = wsprintf(b, local, value);
          b += result;
          arg_index++;
        }
        else {
          XEASSERT(false);
        }
      }
      else if (arg_size == 4) {
        if (arg_extras == 0) {
          uint32_t value = (uint32_t)SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
          int result = wsprintf(b, local, value);
          b += result;
          arg_index++;
        }
        else {
          XEASSERT(false);
        }
      }
    }
    else if (*end == 's' ||
             *end == 'p' ||
             *end == 'n') {
      wchar_t local[512];
      local[0] = '\0';
      wcsncat(local, start, end + 1 - start);

      XEASSERT(arg_size == 4);
      if (arg_extras == 0) {
        uint32_t value = (uint32_t)SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
        const char *pointer = (const char *)SHIM_MEM_ADDR(value);
        int result = wsprintf(b, local, pointer);
        b += result;
        arg_index++;
      }
      else {
        XEASSERT(false);
      }
    }
    else {
      XEASSERT(false);
      break;
    }
    format = end;
  }
  *b = '\0';

  xe_free(swapped_format);

  // swap the result buffer
  for (wchar_t* swap = buffer; swap != b; ++swap)
  {
    *swap = XESWAP16(*swap);
  }

  SHIM_SET_RETURN((uint32_t)((b - buffer) / sizeof(wchar_t)));
}

}  // namespace kernel
}  // namespace xe

void xe::kernel::xboxkrnl::RegisterStringExports(
    ExportResolver* export_resolver, KernelState* state) {
  SHIM_SET_MAPPING("xboxkrnl.exe", _vsnprintf, state);
  SHIM_SET_MAPPING("xboxkrnl.exe", _vswprintf, state);
}
<commit_msg>Fixed some formatting and a cast.<commit_after>/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2013 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#include <xenia/kernel/xboxkrnl_rtl.h>

#include <xenia/kernel/kernel_state.h>
#include <xenia/kernel/xboxkrnl_private.h>
#include <xenia/kernel/objects/xthread.h>
#include <xenia/kernel/objects/xuser_module.h>
#include <xenia/kernel/util/shim_utils.h>
#include <xenia/kernel/util/xex2.h>

using namespace xe;
using namespace xe::kernel;
using namespace xe::kernel::xboxkrnl;

namespace xe {
namespace kernel {

// TODO: clean me up!
SHIM_CALL _vsnprintf_shim(
    PPCContext* ppc_state, KernelState* state) {

  uint32_t buffer_ptr = SHIM_GET_ARG_32(0);
  uint32_t count = SHIM_GET_ARG_32(1);
  uint32_t format_ptr = SHIM_GET_ARG_32(2);
  uint32_t arg_ptr = SHIM_GET_ARG_32(3);

  if (format_ptr == 0) {
    SHIM_SET_RETURN(-1);
    return;
  }

  char* buffer = (char*)SHIM_MEM_ADDR(buffer_ptr);  // TODO: ensure it never writes past the end of the buffer (count)...
  const char* format = (const char*)SHIM_MEM_ADDR(format_ptr);

  int arg_index = 0;

  char* b = buffer;
  for (; *format != '\0'; ++format) {
    const char* start = format;

    if (*format != '%') {
      *b++ = *format;
      continue;
    }

    ++format;
    if (*format == '\0') {
      break;
    }

    if (*format == '%') {
      *b++ = *format;
      continue;
    }

    const char* end;
    end = format;

    // skip flags
    while (*end == '-' ||
           *end == '+' ||
           *end == ' ' ||
           *end == '#' ||
           *end == '0') {
      ++end;
    }

    if (*end == '\0') {
      break;
    }

    int arg_extras = 0;

    // skip width
    if (*end == '*') {
      ++end;
      arg_extras++;
    }
    else {
      while (*end >= '0' && *end <= '9') {
        ++end;
      }
    }

    if (*end == '\0') {
      break;
    }

    // skip precision
    if (*end == '.') {
      ++end;

      if (*end == '*') {
        ++end;
        ++arg_extras;
      }
      else {
        while (*end >= '0' && *end <= '9') {
          ++end;
        }
      }
    }

    if (*end == '\0') {
      break;
    }

    // get length
    int arg_size = 4;

    if (*end == 'h') {
      ++end;
      arg_size = 4;
      if (*end == 'h') {
        ++end;
      }
    }
    else if (*end == 'l') {
      ++end;
      arg_size = 4;
      if (*end == 'l') {
        ++end;
        arg_size = 8;
      }
    }
    else if (*end == 'j') {
      arg_size = 8;
      ++end;
    }
    else if (*end == 'z') {
      arg_size = 4;
      ++end;
    }
    else if (*end == 't') {
      arg_size = 8;
      ++end;
    }
    else if (*end == 'L') {
      arg_size = 8;
      ++end;
    }

    if (*end == '\0') {
      break;
    }

    if (*end == 'd' ||
        *end == 'i' ||
        *end == 'u' ||
        *end == 'o' ||
        *end == 'x' ||
        *end == 'X' ||
        *end == 'f' ||
        *end == 'F' ||
        *end == 'e' ||
        *end == 'E' ||
        *end == 'g' ||
        *end == 'G' ||
        *end == 'a' ||
        *end == 'A' ||
        *end == 'c') {
      char local[512];
      local[0] = '\0';
      strncat(local, start, end + 1 - start);

      XEASSERT(arg_size == 8 || arg_size == 4);
      if (arg_size == 8) {
        if (arg_extras == 0) {
          uint64_t value = SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
          int result = sprintf(b, local, value);
          b += result;
          arg_index++;
        }
        else {
          XEASSERT(false);
        }
      }
      else if (arg_size == 4) {
        if (arg_extras == 0) {
          uint32_t value = (uint32_t)SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
          int result = sprintf(b, local, value);
          b += result;
          arg_index++;
        }
        else {
          XEASSERT(false);
        }
      }
    }
    else if (*end == 's' ||
             *end == 'p' ||
             *end == 'n') {
      char local[512];
      local[0] = '\0';
      strncat(local, start, end + 1 - start);

      XEASSERT(arg_size == 4);
      if (arg_extras == 0) {
        uint32_t value = (uint32_t)SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
        const char* pointer = (const char*)SHIM_MEM_ADDR(value);
        int result = sprintf(b, local, pointer);
        b += result;
        arg_index++;
      }
      else {
        XEASSERT(false);
      }
    }
    else {
      XEASSERT(false);
      break;
    }
    format = end;
  }
  *b = '\0';
  SHIM_SET_RETURN((uint32_t)(b - buffer));
}

// TODO: clean me up!
SHIM_CALL _vswprintf_shim(
    PPCContext* ppc_state, KernelState* state) {

  uint32_t buffer_ptr = SHIM_GET_ARG_32(0);
  uint32_t format_ptr = SHIM_GET_ARG_32(1);
  uint32_t arg_ptr = SHIM_GET_ARG_32(2);

  if (format_ptr == 0) {
    SHIM_SET_RETURN(-1);
    return;
  }

  wchar_t*buffer = (wchar_t*)SHIM_MEM_ADDR(buffer_ptr);  // TODO: ensure it never writes past the end of the buffer (count)...
  const wchar_t* format = (const wchar_t*)SHIM_MEM_ADDR(format_ptr);

  // this will work since a null is the same regardless of endianness
  size_t format_length = wcslen(format);

  // swap the format buffer
  wchar_t* swapped_format = (wchar_t*)xe_malloc((format_length + 1) * sizeof(wchar_t));
  for (size_t i = 0; i < format_length; ++i)
  {
    swapped_format[i] = XESWAP16(format[i]);
  }
  swapped_format[format_length] = '\0';

  // be sneaky
  format = swapped_format;

  int arg_index = 0;

  wchar_t* b = buffer;
  for (; *format != '\0'; ++format) {
    const wchar_t *start = format;

    if (*format != '%') {
      *b++ = *format;
      continue;
    }

    ++format;
    if (*format == '\0') {
      break;
    }

    if (*format == '%') {
      *b++ = *format;
      continue;
    }

    const wchar_t* end;
    end = format;

    // skip flags
    while (*end == '-' ||
           *end == '+' ||
           *end == ' ' ||
           *end == '#' ||
           *end == '0') {
      ++end;
    }

    if (*end == '\0') {
      break;
    }

    int arg_extras = 0;

    // skip width
    if (*end == '*') {
      ++end;
      arg_extras++;
    }
    else {
      while (*end >= '0' && *end <= '9') {
        ++end;
      }
    }

    if (*end == '\0') {
      break;
    }

    // skip precision
    if (*end == '.') {
      ++end;

      if (*end == '*') {
        ++end;
        ++arg_extras;
      }
      else {
        while (*end >= '0' && *end <= '9') {
          ++end;
        }
      }
    }

    if (*end == '\0') {
      break;
    }

    // get length
    int arg_size = 4;

    if (*end == 'h') {
      ++end;
      arg_size = 4;
      if (*end == 'h') {
        ++end;
      }
    }
    else if (*end == 'l') {
      ++end;
      arg_size = 4;
      if (*end == 'l') {
        ++end;
        arg_size = 8;
      }
    }
    else if (*end == 'j') {
      arg_size = 8;
      ++end;
    }
    else if (*end == 'z') {
      arg_size = 4;
      ++end;
    }
    else if (*end == 't') {
      arg_size = 8;
      ++end;
    }
    else if (*end == 'L') {
      arg_size = 8;
      ++end;
    }

    if (*end == '\0') {
      break;
    }

    if (*end == 'd' ||
        *end == 'i' ||
        *end == 'u' ||
        *end == 'o' ||
        *end == 'x' ||
        *end == 'X' ||
        *end == 'f' ||
        *end == 'F' ||
        *end == 'e' ||
        *end == 'E' ||
        *end == 'g' ||
        *end == 'G' ||
        *end == 'a' ||
        *end == 'A' ||
        *end == 'c') {
      wchar_t local[512];
      local[0] = '\0';
      wcsncat(local, start, end + 1 - start);

      XEASSERT(arg_size == 8 || arg_size == 4);
      if (arg_size == 8) {
        if (arg_extras == 0) {
          uint64_t value = SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
          int result = wsprintf(b, local, value);
          b += result;
          arg_index++;
        }
        else {
          XEASSERT(false);
        }
      }
      else if (arg_size == 4) {
        if (arg_extras == 0) {
          uint32_t value = (uint32_t)SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
          int result = wsprintf(b, local, value);
          b += result;
          arg_index++;
        }
        else {
          XEASSERT(false);
        }
      }
    }
    else if (*end == 's' ||
             *end == 'p' ||
             *end == 'n') {
      wchar_t local[512];
      local[0] = '\0';
      wcsncat(local, start, end + 1 - start);

      XEASSERT(arg_size == 4);
      if (arg_extras == 0) {
        uint32_t value = (uint32_t)SHIM_MEM_64(arg_ptr + (arg_index * 8)); // TODO: check if this is correct...
        const wchar_t* pointer = (const wchar_t*)SHIM_MEM_ADDR(value);
        int result = wsprintf(b, local, pointer);
        b += result;
        arg_index++;
      }
      else {
        XEASSERT(false);
      }
    }
    else {
      XEASSERT(false);
      break;
    }
    format = end;
  }
  *b = '\0';

  xe_free(swapped_format);

  // swap the result buffer
  for (wchar_t* swap = buffer; swap != b; ++swap)
  {
    *swap = XESWAP16(*swap);
  }

  SHIM_SET_RETURN((uint32_t)((b - buffer) / sizeof(wchar_t)));
}

}  // namespace kernel
}  // namespace xe

void xe::kernel::xboxkrnl::RegisterStringExports(
    ExportResolver* export_resolver, KernelState* state) {
  SHIM_SET_MAPPING("xboxkrnl.exe", _vsnprintf, state);
  SHIM_SET_MAPPING("xboxkrnl.exe", _vswprintf, state);
}
<|endoftext|>