+
+/*******************************************************************************
+
+ Copyright (c) 2009, Charles McGarvey
+ 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.
+
+*******************************************************************************/
+
+#include <map>
+#include <vector>
+
+#include <Moof/Aabb.hh>
+#include <Moof/Camera.hh>
+#include <Moof/Entity.hh>
+#include <Moof/Log.hh>
+#include <Moof/Math.hh>
+#include <Moof/Mippleton.hh>
+#include <Moof/Octree.hh>
+#include <Moof/Script.hh>
+#include <Moof/Settings.hh>
+#include <Moof/Tilemap.hh>
+
+#include "Scene.hh"
+
+
+struct Scene::Impl : public Mf::Mippleton<Impl>
+{
+ class Quad : public Mf::Entity, public Mf::OctreeInsertable
+ {
+ Mf::Scalar vertices_[12];
+ Mf::Scalar texCoords_[8];
+
+ Mf::Tilemap tilemap_;
+
+ bool blending_;
+ bool fog_;
+
+ Mf::Aabb aabb_;
+ Mf::Sphere sphere_;
+
+ public:
+
+ enum SURFACE_TYPE
+ {
+ LEFT = 1,
+ RIGHT = 2,
+ TOP = 3
+ };
+
+ Quad(const Mf::Vector3 vertices[4], const std::string& texture,
+ Mf::Tilemap::Index tileIndex) :
+ tilemap_(texture),
+ blending_(false),
+ fog_(false)
+ {
+ for (int i = 0, num = 0; i < 4; ++i)
+ {
+ for (int j = 0; j < 3; ++j, ++num)
+ {
+ vertices_[num] = vertices[i][j];
+ }
+ }
+
+ if (!tilemap_.getTileCoords(tileIndex, texCoords_))
+ {
+ Mf::logWarning("no index %d in texture %s", tileIndex,
+ texture.c_str());
+
+ texCoords_[0] = texCoords_[1] =
+ texCoords_[3] = texCoords_[6] = 0.0;
+ texCoords_[2] = texCoords_[4] =
+ texCoords_[5] = texCoords_[7] = 1.0;
+ }
+
+ aabb_.encloseVertices(vertices, 4);
+ sphere_.point = aabb_.getCenter();
+ sphere_.radius = (aabb_.min - sphere_.point).length();
+ }
+
+ void setBlending(bool blending)
+ {
+ blending_ = blending;
+ }
+
+ void setFog(bool fog)
+ {
+ fog_ = fog;
+ }
+
+ void draw(Mf::Scalar alpha = 0.0) const
+ {
+ if (blending_)
+ {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+
+ if (fog_)
+ {
+ glEnable(GL_FOG);
+ glFogi(GL_FOG_MODE, GL_LINEAR);
+ }
+
+ //glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+ tilemap_.bind();
+
+ glVertexPointer(3, GL_SCALAR, 0, vertices_);
+ glTexCoordPointer(2, GL_SCALAR, 0, texCoords_);
+
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+ glDisable(GL_BLEND);
+ glDisable(GL_FOG);
+ }
+
+ bool isVisible(const Mf::Frustum& frustum) const
+ {
+ return sphere_.isVisible(frustum);
+ }
+
+
+ bool isInsideAabb(const Mf::Aabb& aabb) const
+ {
+ // make sure the entity is fully inside the volume
+ if (!(aabb_.max[0] < aabb.max[0] &&
+ aabb_.min[0] > aabb.min[0] &&
+ aabb_.max[1] < aabb.max[1] &&
+ aabb_.min[1] > aabb.min[1] &&
+ aabb_.max[2] < aabb.max[2] &&
+ aabb_.min[2] > aabb.min[2]))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ int getOctant(const Mf::Aabb& aabb) const
+ {
+ int octantNum = -1;
+
+ Mf::Plane::Halfspace halfspace;
+
+ Mf::Plane xy = aabb.getPlaneXY();
+ halfspace = xy.intersects(sphere_);
+ if (halfspace == Mf::Plane::INTERSECT)
+ {
+ halfspace = xy.intersects(aabb_);
+ }
+
+ if (halfspace == Mf::Plane::POSITIVE)
+ {
+ Mf::Plane xz = aabb.getPlaneXZ();
+ halfspace = xz.intersects(sphere_);
+ if (halfspace == Mf::Plane::INTERSECT)
+ {
+ halfspace = xz.intersects(aabb_);
+ }
+
+ if (halfspace == Mf::Plane::POSITIVE)
+ {
+ Mf::Plane yz = aabb.getPlaneYZ();
+ halfspace = yz.intersects(sphere_);
+ if (halfspace == Mf::Plane::INTERSECT)
+ {
+ halfspace = yz.intersects(aabb_);
+ }
+
+ if (halfspace == Mf::Plane::POSITIVE)
+ {
+ octantNum = 2;
+ }
+ else if (halfspace == Mf::Plane::NEGATIVE)
+ {
+ octantNum = 3;
+ }
+ }
+ else if (halfspace == Mf::Plane::NEGATIVE)
+ {
+ Mf::Plane yz = aabb.getPlaneYZ();
+ halfspace = yz.intersects(sphere_);
+ if (halfspace == Mf::Plane::INTERSECT)
+ {
+ halfspace = yz.intersects(aabb_);
+ }
+
+ if (halfspace == Mf::Plane::POSITIVE)
+ {
+ octantNum = 1;
+ }
+ else if (halfspace == Mf::Plane::NEGATIVE)
+ {
+ octantNum = 0;
+ }
+ }
+ }
+ else if (halfspace == Mf::Plane::NEGATIVE)
+ {
+ Mf::Plane xz = aabb.getPlaneXZ();
+ halfspace = xz.intersects(sphere_);
+ if (halfspace == Mf::Plane::INTERSECT)
+ {
+ halfspace = xz.intersects(aabb_);
+ }
+
+ if (halfspace == Mf::Plane::POSITIVE)
+ {
+ Mf::Plane yz = aabb.getPlaneYZ();
+ halfspace = yz.intersects(sphere_);
+ if (halfspace == Mf::Plane::INTERSECT)
+ {
+ halfspace = yz.intersects(aabb_);
+ }
+
+ if (halfspace == Mf::Plane::POSITIVE)
+ {
+ octantNum = 6;
+ }
+ else if (halfspace == Mf::Plane::NEGATIVE)
+ {
+ octantNum = 7;
+ }
+ }
+ else if (halfspace == Mf::Plane::NEGATIVE)
+ {
+ Mf::Plane yz = aabb.getPlaneYZ();
+ halfspace = yz.intersects(sphere_);
+ if (halfspace == Mf::Plane::INTERSECT)
+ {
+ halfspace = yz.intersects(aabb_);
+ }
+
+ if (halfspace == Mf::Plane::POSITIVE)
+ {
+ octantNum = 5;
+ }
+ else if (halfspace == Mf::Plane::NEGATIVE)
+ {
+ octantNum = 4;
+ }
+ }
+ }
+
+ return octantNum;
+ }
+ };
+
+
+
+ Mf::Script script;
+
+ Mf::Matrix4 transform;
+ std::string texture;
+
+ Mf::Octree<Quad>::Ptr octree;
+
+
+ enum AXIS
+ {
+ X = 0,
+ Y = 1,
+ Z = 2
+ };
+
+
+
+ explicit Impl(const std::string& name) :
+ Mf::Mippleton<Impl>(name)
+ {
+ script.importStandardLibraries();
+ importLogScript(script);
+
+ importSceneBindings(script);
+ loadFromFile();
+ }
+
+ void importSceneBindings(Mf::Script& script)
+ {
+ script.importFunction("SetPlayfieldBounds",
+ boost::bind(&Impl::setPlayfieldBounds, this, _1));
+ script.importFunction("SetMaximumBounds",
+ boost::bind(&Impl::setMaximumBounds, this, _1));
+ script.importFunction("ResetTransform",
+ boost::bind(&Impl::resetTransform, this, _1));
+ script.importFunction("Translate",
+ boost::bind(&Impl::translate, this, _1));
+ script.importFunction("Scale",
+ boost::bind(&Impl::scale, this, _1));
+ script.importFunction("Rotate",
+ boost::bind(&Impl::rotate, this, _1));
+ script.importFunction("SetTexture",
+ boost::bind(&Impl::setTexture, this, _1));
+ script.importFunction("MakeTilemap",
+ boost::bind(&Impl::makeTilemap, this, _1));
+ script.importFunction("MakeBillboard",
+ boost::bind(&Impl::makeBillboard, this, _1));
+
+ long detail = 3;
+ Mf::Settings::getInstance().get("detail", detail);
+ script.push(detail); script.set("detail");
+
+ script.push(Quad::LEFT); script.set("LEFT");
+ script.push(Quad::RIGHT); script.set("RIGHT");
+ script.push(Quad::TOP); script.set("TOP");
+
+ script.push(X); script.set("X");
+ script.push(Y); script.set("Y");
+ script.push(Z); script.set("Z");
+ }
+
+
+ void loadFromFile()
+ {
+ std::string filePath = Scene::getPath(getName());
+
+ if (script.doFile(filePath) != Mf::Script::SUCCESS)
+ {
+ std::string str;
+ script[-1].get(str);
+ Mf::logScript("%s", str.c_str());
+ }
+ }
+
+
+ static int loadBox(Mf::Script& script, Mf::Aabb& aabb)
+ {
+ Mf::Script::Value table[] =
+ {
+ script[1].requireTable(),
+ script[2].requireTable()
+ };
+
+ if (!table[0].isTable() || !table[1].isTable())
+ {
+ Mf::logWarning("wrong arguments to setPlayfieldBounds; ignoring...");
+ return 0;
+ }
+
+ for (int i = 0; i <= 1; ++i)
+ {
+ for (int j = 1; j <= 3; ++j)
+ {
+ script.push((long)j);
+ table[i].pushField();
+ }
+ }
+
+ script[3].get(aabb.min[0]);
+ script[4].get(aabb.min[1]);
+ script[5].get(aabb.min[2]);
+ script[6].get(aabb.max[0]);
+ script[7].get(aabb.max[1]);
+ script[8].get(aabb.max[2]);
+
+ return 0;
+ }
+
+ int setPlayfieldBounds(Mf::Script& script)
+ {
+ Mf::Aabb bounds;
+ return loadBox(script, bounds);
+ }
+
+ int setMaximumBounds(Mf::Script& script)
+ {
+ Mf::Aabb bounds;
+ int ret = loadBox(script, bounds);
+ octree = Mf::Octree<Quad>::alloc(bounds);
+ return ret;
+ }
+
+ int resetTransform(Mf::Script& script)
+ {
+ transform.identity();
+ return 0;
+ }
+
+ int translate(Mf::Script& script)
+ {
+ Mf::Script::Value x = script[1].requireNumber();
+ Mf::Script::Value y = script[2].requireNumber();
+ Mf::Script::Value z = script[3].requireNumber();
+
+ Mf::Vector3 vec;
+ x.get(vec[0]);
+ y.get(vec[1]);
+ z.get(vec[2]);
+
+ Mf::Matrix4 translation;
+ cml::matrix_translation(translation, vec);
+ transform = translation * transform;
+
+ return 0;
+ }
+
+ int scale(Mf::Script& script)
+ {
+ if (script.getSize() == 3)
+ {
+ Mf::Vector3 vec;
+ script[1].requireNumber().get(vec[0]);
+ script[2].requireNumber().get(vec[1]);
+ script[3].requireNumber().get(vec[2]);
+
+ Mf::Matrix4 scaling;
+ cml::matrix_scale(scaling, vec);
+ transform = scaling * transform;
+ }
+ else if (script.getSize() == 1)
+ {
+ Mf::Scalar value = 1.0;
+ script[1].requireNumber().get(value);
+
+ Mf::Matrix4 scaling;
+ cml::matrix_uniform_scale(scaling, value);
+ transform = scaling * transform;
+ }
+ else
+ {
+ script.getTop().throwError("wrong number of arguments");
+ }
+
+ return 0;
+ }
+
+ int rotate(Mf::Script& script)
+ {
+ Mf::Script::Value axis = script[1].requireString();
+ Mf::Script::Value angle = script[2].requireNumber();
+
+ size_t index = 0;
+ axis.get(index);
+
+ Mf::Scalar value;
+ angle.get(value);
+
+ cml::matrix_rotate_about_world_axis(transform, index, cml::rad(value));
+
+ return 0;
+ }
+
+ int setTexture(Mf::Script& script)
+ {
+ Mf::Script::Value name = script[1].requireString();
+
+ name.get(texture);
+
+ return 0;
+ }
+
+ int makeTilemap(Mf::Script& script)
+ {
+ Mf::Script::Value table = script[1].requireTable();
+ Mf::Script::Value top = script[-1];
+
+ long width = 1;
+ long height = 1;
+
+ table.pushField("width");
+ top.get(width);
+
+ long nTiles = 0;
+
+ table.pushField("tiles");
+ Mf::Script::Value tiles = script.getTop();
+ nTiles = tiles.getLength();
+
+ if (nTiles % width != 0) table.throwError("invalid number of tiles");
+
+ std::vector< std::vector<Mf::Tilemap::Index> > indices;
+
+ int i, w, h;
+
+ height = nTiles / width;
+ indices.resize(height);
+
+ // the indices are stored upside-down in the scene file so that they
+ // are easier to edit as text, so we'll need to load them last row
+ // first
+
+ i = 1;
+ for (h = height - 1; h >= 0; --h)
+ {
+ std::vector<Mf::Tilemap::Index> row;
+
+ for (w = 0; w < width; ++w, ++i)
+ {
+ script.checkStack(2);
+ script.push(long(i));
+ tiles.pushField();
+
+ long index;
+ top.get(index);
+
+ row.push_back(Mf::Tilemap::Index(index));
+ }
+
+ indices[h] = row;
+ }
+
+ Mf::Vector4 vertices[height+1][width+1];
+
+ Mf::Matrix4 transposedTransform = transform;
+ transposedTransform.transpose();
+
+ for (int h = 0; h <= height; ++h)
+ {
+ for (int w = 0; w <= width; ++w)
+ {
+ vertices[h][w] = Mf::Vector4(Mf::Scalar(w), Mf::Scalar(h), 0.0, 1.0) *
+ transposedTransform;
+ }
+ }
+
+ for (int h = 0; h < height; ++h)
+ {
+ for (int w = 0; w < width; ++w)
+ {
+ if (indices[h][w] == Mf::Tilemap::NO_TILE) continue;
+
+ Mf::Vector3 quadVertices[4];
+
+ quadVertices[0] = Mf::demote(vertices[h][w]);
+ quadVertices[1] = Mf::demote(vertices[h][w+1]);
+ quadVertices[2] = Mf::demote(vertices[h+1][w+1]);
+ quadVertices[3] = Mf::demote(vertices[h+1][w]);
+
+ Quad* quad = new Quad(quadVertices, texture, indices[h][w]);
+ boost::shared_ptr<Quad> quadPtr(quad);
+
+ octree->insert(quadPtr);
+ }
+ }
+
+ return 0;
+ }
+
+ int makeBillboard(Mf::Script& script)
+ {
+ Mf::Script::Value table = script[1];
+ Mf::Script::Value top = script[-1];
+
+ long index = 0;
+ long width = 1;
+ bool blending = false;
+ bool fog = false;
+
+ if (table.isTable())
+ {
+ table.pushField("tile");
+ top.get(index);
+
+ table.pushField("u_scale");
+ top.get(width);
+
+ table.pushField("blend");
+ top.get(blending);
+
+ table.pushField("fog");
+ top.get(fog);
+ }
+
+ Mf::Vector4 vertices[2][width+1];
+
+ Mf::Matrix4 transposedTransform = transform;
+ transposedTransform.transpose();
+
+ Mf::Scalar xf;
+ Mf::Scalar increment = 1.0 / Mf::Scalar(width);
+
+ for (int h = 0; h <= 1; ++h)
+ {
+ xf = 0.0;
+ for (int w = 0; w <= width; ++w, xf += increment)
+ {
+ vertices[h][w] = Mf::Vector4(xf, Mf::Scalar(h), 0.0, 1.0) *
+ transposedTransform;
+ }
+ }
+
+ for (int w = 0; w < width; ++w)
+ {
+ Mf::Vector3 quadVertices[4];
+
+ quadVertices[0] = Mf::demote(vertices[0][w]);
+ quadVertices[1] = Mf::demote(vertices[0][w+1]);
+ quadVertices[2] = Mf::demote(vertices[1][w+1]);
+ quadVertices[3] = Mf::demote(vertices[1][w]);
+
+ Quad* quad = new Quad(quadVertices, texture, Mf::Tilemap::Index(index));
+ quad->setBlending(blending);
+ quad->setFog(fog);
+
+ boost::shared_ptr<Quad> quadPtr(quad);
+
+ octree->insert(quadPtr);
+ }
+
+ return 0;
+ }
+};
+
+
+Scene::Scene(const std::string& name) :
+ // pass through
+ impl_(Scene::Impl::getInstance(name)) {}
+
+
+void Scene::draw(Mf::Scalar alpha) const
+{
+ impl_->octree->draw(alpha);
+}
+
+void Scene::drawIfVisible(Mf::Scalar alpha, const Mf::Frustum& frustum) const
+{
+ impl_->octree->drawIfVisible(alpha, frustum);
+}
+
+
+std::string Scene::getPath(const std::string& name)
+{
+ return Mf::Resource::getPath("scenes/" + name + ".lua");
+}
+
+
+/** vim: set ts=4 sw=4 tw=80: *************************************************/
+