C++程序  |  319行  |  7.62 KB

/*
 * Copyright (C) 2016 Google, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cassert>
#include <cmath>
#include <array>
#include <glm/gtc/matrix_transform.hpp>
#include "Simulation.h"

namespace {

class MeshPicker {
public:
    MeshPicker() :
        pattern_({
                Meshes::MESH_PYRAMID,
                Meshes::MESH_ICOSPHERE,
                Meshes::MESH_TEAPOT,
                Meshes::MESH_PYRAMID,
                Meshes::MESH_ICOSPHERE,
                Meshes::MESH_PYRAMID,
                Meshes::MESH_PYRAMID,
                Meshes::MESH_PYRAMID,
                Meshes::MESH_PYRAMID,
                Meshes::MESH_PYRAMID,
                }), cur_(-1)
    {
    }

    Meshes::Type pick()
    {
        cur_ = (cur_ + 1) % pattern_.size();
        return pattern_[cur_];
    }

    float scale(Meshes::Type type) const
    {
        float base = 0.005f;

        switch (type) {
        case Meshes::MESH_PYRAMID:
        default:
            return base * 1.0f;
        case Meshes::MESH_ICOSPHERE:
            return base * 3.0f;
        case Meshes::MESH_TEAPOT:
            return base * 10.0f;
        }
    }

private:
    const std::array<Meshes::Type, 10> pattern_;
    int cur_;
};

class ColorPicker {
public:
    ColorPicker(unsigned int rng_seed) :
        rng_(rng_seed),
        red_(0.0f, 1.0f),
        green_(0.0f, 1.0f),
        blue_(0.0f, 1.0f)
    {
    }

    glm::vec3 pick()
    {
        return glm::vec3{ red_(rng_),
                          green_(rng_),
                          blue_(rng_) };
    }

private:
    std::mt19937 rng_;
    std::uniform_real_distribution<float> red_;
    std::uniform_real_distribution<float> green_;
    std::uniform_real_distribution<float> blue_;
};

} // namespace

Animation::Animation(unsigned int rng_seed, float scale)
    : rng_(rng_seed), dir_(-1.0f, 1.0f), speed_(0.1f, 1.0f)
{
    float x = dir_(rng_);
    float y = dir_(rng_);
    float z = dir_(rng_);
    if (std::abs(x) + std::abs(y) + std::abs(z) == 0.0f)
        x = 1.0f;

    current_.axis = glm::normalize(glm::vec3(x, y, z));

    current_.speed = speed_(rng_);
    current_.scale = scale;

    current_.matrix = glm::scale(glm::mat4(1.0f), glm::vec3(current_.scale));
}

glm::mat4 Animation::transformation(float t)
{
    current_.matrix = glm::rotate(current_.matrix, current_.speed * t, current_.axis);

    return current_.matrix;
}

class Curve {
public:
    virtual ~Curve() {}
    virtual glm::vec3 evaluate(float t) = 0;
};

namespace {

enum CurveType {
    CURVE_RANDOM,
    CURVE_CIRCLE,
    CURVE_COUNT,
};

class RandomCurve : public Curve {
public:
    RandomCurve(unsigned int rng_seed)
        : rng_(rng_seed), direction_(-0.3f, 0.3f), duration_(1.0f, 5.0f),
          segment_start_(0.0f), segment_direction_(0.0f),
          time_start_(0.0f), time_duration_(0.0f)
    {
    }

    glm::vec3 evaluate(float t)
    {
        if (t >= time_start_ + time_duration_)
            new_segment(t);

        pos_ += unit_dir_ * (t - last_);
        last_ = t;

        return pos_;
    }

private:
    void new_segment(float time_start)
    {
        segment_start_ += segment_direction_;
        segment_direction_ = glm::vec3(direction_(rng_),
                                       direction_(rng_),
                                       direction_(rng_));

        time_start_ = time_start;
        time_duration_ = duration_(rng_);

        unit_dir_ = segment_direction_ / time_duration_;
        pos_ = segment_start_;
        last_ = time_start_;
    }

    std::mt19937 rng_;
    std::uniform_real_distribution<float> direction_;
    std::uniform_real_distribution<float> duration_;

    glm::vec3 segment_start_;
    glm::vec3 segment_direction_;
    float time_start_;
    float time_duration_;

    glm::vec3 unit_dir_;
    glm::vec3 pos_;
    float last_;
};

class CircleCurve : public Curve {
public:
    CircleCurve(float radius, glm::vec3 axis)
        : r_(radius)
    {
        glm::vec3 a;

        if (axis.x != 0.0f) {
            a.x = -axis.z / axis.x;
            a.y = 0.0f;
            a.z = 1.0f;
        } else if (axis.y != 0.0f) {
            a.x = 1.0f;
            a.y = -axis.x / axis.y;
            a.z = 0.0f;
        } else {
            a.x = 1.0f;
            a.y = 0.0f;
            a.z = -axis.x / axis.z;
        }

        a_ = glm::normalize(a);
        b_ = glm::normalize(glm::cross(a_, axis));
    }

    glm::vec3 evaluate(float t)
    {
        return (a_ * (glm::vec3(std::cos(t)) - glm::vec3(1.0f)) + b_ * glm::vec3(std::sin(t))) *
            glm::vec3(r_);
    }

private:
    float r_;
    glm::vec3 a_;
    glm::vec3 b_;
};

} // namespace

Path::Path(unsigned int rng_seed)
    : rng_(rng_seed), type_(0, CURVE_COUNT - 1), duration_(5.0f, 20.0f)
{
    // trigger a subpath generation
    current_.end = -1.0f;
    current_.now = 0.0f;
}

glm::vec3 Path::position(float t)
{
    current_.now += t;

    while (current_.now >= current_.end)
        generate_subpath();

    return current_.origin + current_.curve->evaluate(current_.now - current_.start);
}

void Path::generate_subpath()
{
    float duration = duration_(rng_);
    CurveType type = static_cast<CurveType>(type_(rng_));

    if (current_.curve) {
        current_.origin += current_.curve->evaluate(current_.end - current_.start);
        current_.start = current_.end;
    } else {
        std::uniform_real_distribution<float> origin(0.0f, 2.0f);
        current_.origin = glm::vec3(origin(rng_), origin(rng_), origin(rng_));
        current_.start = current_.now;
    }

    current_.end = current_.start + duration;

    Curve *curve;

    switch (type) {
    case CURVE_RANDOM:
        curve = new RandomCurve(rng_());
        break;
    case CURVE_CIRCLE:
        {
            std::uniform_real_distribution<float> dir(-1.0f, 1.0f);
            glm::vec3 axis(dir(rng_), dir(rng_), dir(rng_));
            if (axis.x == 0.0f && axis.y == 0.0f && axis.z == 0.0f)
                axis.x = 1.0f;

            std::uniform_real_distribution<float> radius_(0.02f, 0.2f);
            curve = new CircleCurve(radius_(rng_), axis);
        }
        break;
    default:
        assert(!"unreachable");
        curve = nullptr;
        break;
    }

    current_.curve.reset(curve);
}

Simulation::Simulation(int object_count)
    : random_dev_()
{
    MeshPicker mesh;
    ColorPicker color(random_dev_());

    objects_.reserve(object_count);
    for (int i = 0; i < object_count; i++) {
        Meshes::Type type = mesh.pick();
        float scale = mesh.scale(type);

        objects_.emplace_back(Object{
            type, glm::vec3(0.5f + 0.5f * (float)i / object_count),
            color.pick(), Animation(random_dev_(), scale), Path(random_dev_()),
        });
    }
}

void Simulation::set_frame_data_size(uint32_t size)
{
    uint32_t offset = 0;
    for (auto &obj : objects_) {
        obj.frame_data_offset = offset;
        offset += size;
    }
}

void Simulation::update(float time, int begin, int end)
{
    for (int i = begin; i < end; i++) {
        auto &obj = objects_[i];

        glm::vec3 pos = obj.path.position(time);
        glm::mat4 trans = obj.animation.transformation(time);
        obj.model = glm::translate(glm::mat4(1.0f), pos) * trans;
    }
}