/*- * Copyright (c) 2003 Qube Software, Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of the source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Any redistribution solely in binary form must conspicuously * reproduce the following disclaimer in documentation provided with the * binary redistribution. * * THIS SOFTWARE IS PROVIDED ``AS IS'', WITHOUT ANY WARRANTIES, EXPRESS * OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. LICENSOR SHALL * NOT BE LIABLE FOR ANY LOSS OR DAMAGES RESULTING FROM THE USE OF THIS * SOFTWARE, EITHER ALONE OR IN COMBINATION WITH ANY OTHER SOFTWARE. * * $Qube: Q/doc/tutorial/programmers/ptut81/maniccube.cpp,v 1.3.4.1 2003/12/03 13:48:11 yannick Exp $ */ /** * @file maniccube.cpp */ #include "maniccube.h" #include ////////////////////////////////////////////////////////////////////////////// // // The ManicCube object is the "game object" for our shape that is animated. Q::Animation ManicCube::anim_; Q::Mesh ManicCube::mesh_; /** * The sample category ID for the logger */ static int logger = Utils::Log::theLog()->getCategory("sample"); ManicCube::ManicCube() { looping_ = true; // make the animations play in a loop by default if (!anim_.valid()) { // cache the animation so that we don't delay in playing it. // it is the same object for all instances of the ManicCube. anim_ = Q::Animation::find("ManicCube"); if (!anim_.valid()) { EPF(logger, "Failed to find Animation"); } } if (!mesh_.valid()) { // all ManicCube objects will instantiate the same authored // mesh. mesh_ = Q::Mesh::find("ManicCube"); if (!mesh_.valid()) { EPF(logger, "Failed to find Mesh"); } } } bool ManicCube::embody(Q::EntityInstance* body) { // this method is called when the Entity comes into Scope. // In this case, the Entity is a "moving" entity, i.e. it can potentially // move through portals, so it is brought into scope by a dummy Instance // that is created automatically. EntityBase::embody(body); Q::Group g = group(); // we want to show some geometry for this Entity, so // create an instance of the authored mesh. if (!g.valid() || !mesh_.valid()) return false; if (!inst_.valid()) { inst_ = Q::Instance::create(0, mesh_, g); // we want to play animations on this object, here we do it "manually" // by choosing which animation we wish to play and sequencing it // ourselves. The Playback object will be used for all animation that // we want to play on this Group. playback_ = Q::Playback::create(); g.setPlayback(playback_, 0); // listen for end of animation events so that we can (optionally) loop // the animation. playback_.addListener(*(Q::PlaybackListener*)this); } return true; } void ManicCube::disembody(bool destroyed) { // This method is called when the Entity goes out of scope or is being // cleaned up at the end. We destroy the lightweight Instance (and // Playback) so that this Zone can be flushed out of memory properly. if (inst_.valid()) { inst_.destroy(); inst_ = Q::Instance(); } playback_ = Q::Playback(); EntityBase::disembody(destroyed); } void ManicCube::spin() { DPF(logger, 0, "started spin"); if (!anim_.valid()) return; playAnim("spin"); } void ManicCube::bounce() { DPF(logger, 0, "started bounce"); if (!anim_.valid()) return; playAnim("bounce"); } void ManicCube::idle() { DPF(logger, 0, "started idle"); if (!playback_.valid()) { EPF(logger, "No playback"); return; } segName_ = ""; } void ManicCube::toggleLooping() { looping_ = !looping_; DPF(logger, 0, "Looping set to '%u'", (unsigned int)looping_); } void ManicCube::playAnim(const char* segName) { // the Animation is divided up into named segments (this is done in the // user-defined properties of the MAX object). // // Here we find the start and end times of the animation segment that // we want and initiate the playback with those times. float startTime, endTime; if (!Utils::succeeded(anim_.getSegment(segName, startTime, endTime))) { EPF(logger, "Couldn't find animation segment '%s'", segName); return; } if (!playback_.valid() || !anim_.valid()) { EPF(logger, "No playback"); return; } // store the segment name so that looping can easily restart it. segName_ = segName; // queue this animation. This makes the animation smoother as the timeline // has been authored with each segment starting/ending in the same // "idle" position. If we changed animation immediately the change was // requested the animation would "jump" from the current // position/orientation to that authored at the start of the animation. playback_.playAnimation(Q::Playback::queue, anim_, 1.0f, 0.0f, startTime, endTime); } void ManicCube::onEnd(Q::Playback& pb, Q::Animation& anim, float start, float end) { // this PlaybackListener-interface method is called when playback of the // current animation finishes. We use it here to restart the animation if // we are looping. if (looping_) { if (segName_.size()) { playAnim(segName_); } } else { segName_ = ""; } } ////////////////////////////////////////////////////////////////////////////// // // MyFactory is responsible for creating EntityInstances (in this case of type // "ManicCube"). struct MyFactory: public Q::EntityFactoryBase { MyFactory() { // here we look up the concept that corresponds to the Schema type // that is in the QGS file. You must have the Q file containing the // schema attached before this is called. Q::SchemaFactory::theFactory()->nameToConcept("ManicCube", manicCubeCon_); } virtual Utils::Result get(const Q::Concept& con, Q::Entity** res) { // create a new ManicCube object if the concept (the type of the // Entity that is required) is correct. // You would typically use a single factory for a module that creates // different types of object depending on the concept for the type // passed in. Having an individual factory for each type is not // optimal as the creation has to go through all of them to find the // appropriate factory. if (con == manicCubeCon_) { *res = new ManicCube(); return Utils::Success; } return Utils::Failure; } virtual Utils::Result recycle(Q::Entity*) { // the default behaviour is sufficient here. return Utils::Success; } private: Q::Concept manicCubeCon_; }; //////////////////////////////////////////////////// // loadManicCube() // // called on startup of the application // to register the factory. // // this should be done AFTER the database // containing the schema has been opened // so that the Entity type concept is available // but BEFORE any EntityInstances of this type // are created, e.g. by loading a placement file. Utils::Result loadManicCube() { MyFactory::PTR factory(new MyFactory(), false); return Q::EntityManager::theManager()->addFactory(factory); }