// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include #include #include #include #include #include #include #include #include #include #include #include "shapes_exporter.h" using namespace NLMISC; using namespace NL3D; using namespace std; Settings::Settings() { preview_width = 0; preview_height = 0; preview_quality = 0; output_steps_z = 0; output_steps_x = 0; output_width = 0; output_height = 0; output_antialiasing = 0; output_quality = 0; output_background = CRGBA::Black; light_ambiant = CRGBA::White; light_diffuse = CRGBA::White; light_specular = CRGBA::White; light_direction = CVector(0.f, 0.f, 0.f); } ShapesExporter::ShapesExporter():Driver(NULL), Scene(NULL) { } ShapesExporter::~ShapesExporter() { // delete the scene Driver->deleteScene(Scene); // release all textures and others elements Driver->release(); // delete the driver delete Driver; } bool ShapesExporter::init() { if (!settings.output_width) return false; // create OpenGL driver Driver = UDriver::createDriver(); if (!Driver) return false; // create a window Driver->setDisplay(UDriver::CMode((uint16)(settings.output_width * settings.output_antialiasing), (uint16)(settings.output_height * settings.output_antialiasing), 32, true)); // set the title Driver->setWindowTitle(ucstring("NeL images exporter")); // Create a scene Scene = Driver->createScene(true); if (!Scene) return false; return true; } bool ShapesExporter::parseConfigFile(const string &filename) { CConfigFile cf; try { // load the config file cf.load(filename); } catch(exception &e) { nlwarning("can't parse config file : %s", filename.c_str()); nlwarning(e.what()); return false; } // output path try { settings.output_path = CPath::standardizePath(cf.getVar("output_path").asString()); } catch (EUnknownVar &) { } // output format try { settings.output_format = cf.getVar("output_format").asString(); } catch (NLMISC::EUnknownVar &) { settings.output_format = "jpg"; } // add search pathes try { CConfigFile::CVar &search_pathes = cf.getVar("search_pathes"); for (uint i=0; i < (uint)search_pathes.size(); ++i) CPath::addSearchPath(CPath::standardizePath(search_pathes.asString(i))); } catch(EUnknownVar &) { } // add recusrive search pathes try { CConfigFile::CVar &recursive_search_pathes = cf.getVar("recursive_search_pathes"); for (uint i=0; i< (uint)recursive_search_pathes.size(); ++i) CPath::addSearchPath(CPath::standardizePath(recursive_search_pathes.asString(i)), true, false); } catch(EUnknownVar &) { } // add extension remapping try { CConfigFile::CVar &extensions_remapping = cf.getVar("extensions_remapping"); if (extensions_remapping.size()%2 != 0) { nlwarning ("extensions_remapping must have a multiple of 2 entries (ex: extensions_remapping={\"dds\",\"tga\"};)"); } else { for (uint i=0; i < (uint)extensions_remapping.size(); i+=2) CPath::remapExtension(extensions_remapping.asString(i), extensions_remapping.asString(i+1), true); } } catch (EUnknownVar &) { } // preview format try { settings.preview_format = cf.getVar("preview_format").asString(); } catch (NLMISC::EUnknownVar &) { settings.preview_format = "jpg"; } // preview image width try { settings.preview_width = cf.getVar("preview_width").asInt(); } catch (NLMISC::EUnknownVar &) { settings.preview_width = 256; } // preview image height try { settings.preview_height = cf.getVar("preview_height").asInt(); } catch (NLMISC::EUnknownVar &) { settings.preview_height = 256; } // preview jpeg image quality try { settings.preview_quality = (uint8)cf.getVar("preview_quality").asInt(); } catch (NLMISC::EUnknownVar &) { settings.preview_quality = 90; } // output background color try { CConfigFile::CVar &var = cf.getVar("output_background"); settings.output_background.R = (uint8)var.asInt(0); settings.output_background.G = (uint8)var.asInt(1); settings.output_background.B = (uint8)var.asInt(2); } catch (EUnknownVar &) { settings.output_background = CRGBA::Black; } // light ambiant color try { CConfigFile::CVar &var = cf.getVar("light_ambiant"); settings.light_ambiant.R = (uint8)var.asInt(0); settings.light_ambiant.G = (uint8)var.asInt(1); settings.light_ambiant.B = (uint8)var.asInt(2); } catch (EUnknownVar &) { settings.light_ambiant = CRGBA::White; } // light diffuse color try { CConfigFile::CVar &var = cf.getVar("light_diffuse"); settings.light_diffuse.R = (uint8)var.asInt(0); settings.light_diffuse.G = (uint8)var.asInt(1); settings.light_diffuse.B = (uint8)var.asInt(2); } catch (EUnknownVar &) { settings.light_diffuse = CRGBA::White; } // light specular color try { CConfigFile::CVar &var = cf.getVar("light_specular"); settings.light_specular.R = (uint8)var.asInt(0); settings.light_specular.G = (uint8)var.asInt(1); settings.light_specular.B = (uint8)var.asInt(2); } catch (EUnknownVar &) { settings.light_specular = CRGBA::White; } // light direction try { CConfigFile::CVar &var = cf.getVar("light_direction"); settings.light_direction = CVector(var.asFloat(0), var.asFloat(1), var.asFloat(2)); } catch (EUnknownVar &) { settings.light_direction = CVector(0.f, 1.f, 0.f); } // output steps z number try { settings.output_steps_z = cf.getVar("output_steps_z").asInt(); } catch (NLMISC::EUnknownVar &) { settings.output_steps_z = 10; } // output steps x number try { settings.output_steps_x = cf.getVar("output_steps_x").asInt(); } catch (NLMISC::EUnknownVar &) { settings.output_steps_x = 10; } // output image width try { settings.output_width = cf.getVar("output_width").asInt(); } catch (NLMISC::EUnknownVar &) { settings.output_width = 256; } // output image height try { settings.output_height = cf.getVar("output_height").asInt(); } catch (NLMISC::EUnknownVar &) { settings.output_height = 256; } // output image antialiasing try { settings.output_antialiasing = (uint8)cf.getVar("output_antialiasing").asInt(); } catch (NLMISC::EUnknownVar &) { settings.output_antialiasing = 2; } // output jpeg image quality try { settings.output_quality = (uint8)cf.getVar("output_quality").asInt(); } catch (NLMISC::EUnknownVar &) { settings.output_quality = 90; } return true; } bool ShapesExporter::setupLight() { // create the light ULight *Light = ULight::createLight(); if (!Light) return false; // set mode of the light Light->setMode(ULight::DirectionalLight); // set position of the light Light->setupDirectional(settings.light_ambiant, settings.light_diffuse, settings.light_specular, settings.light_direction); // set and enable the light Driver->setLight(0, *Light); Driver->enableLight(0); return true; } void ShapesExporter::setCamera(CAABBox &bbox, UInstance &entity, bool high_z) { UCamera Camera = Scene->getCam(); CVector center = bbox.getCenter(); CVector max_radius = bbox.getHalfSize(); entity.setPivot(center); float fov = float(20.0*Pi/180.0); Camera.setPerspective (fov, 1.0f, 0.1f, 1000.0f); float radius = max(max(max_radius.x, max_radius.y), max_radius.z); float left, right, bottom, top, znear, zfar; Camera.getFrustum(left, right, bottom, top, znear, zfar); float dist = radius / (tan(fov/2)); Camera.lookAt(CVector(center.x+dist+max_radius.x, center.y, center.z+(high_z?max_radius.z/1.0f:0.0f)), center); } bool ShapesExporter::exportShape(const string &filename, const string &output_path) { // get scene camera if (Scene->getCam().empty()) { nlwarning("can't get camera from scene"); return false; } //CParticleSystem::forceDisplayBBox(true); // add an entity to the scene UInstance Entity = Scene->createInstance(filename); // if we can't create entity, skip it if (Entity.empty()) { nlwarning("can't create instance from %s", filename.c_str()); return false; } // get AABox of Entity CAABBox bbox; Entity.getShapeAABBox(bbox); setCamera(bbox , Entity); Scene->animate(1.0); Scene->render(); UParticleSystemInstance *psi = static_cast(&Entity); if(psi) { psi->getSystemBBox(bbox); setCamera(bbox, Entity); } if(CFile::getExtension(filename) == "ps") { // first pass to detect bbox & duration CAABBox bbox2; double duration = 0.0; renderPS(Entity, output_path, duration, bbox2); Scene->deleteInstance(Entity); // second pass to actually take screenshots Entity = Scene->createInstance(filename); setCamera(bbox, Entity, true); renderPS(Entity, output_path, duration, bbox2); } else { renderShape(Entity, CVector::I, output_path); } // delete entity Scene->deleteInstance(Entity); return true; } bool ShapesExporter::exportSkeleton(const string &skeleton, const vector &parts, const string &output_path) { // get scene camera UCamera Camera = Scene->getCam(); if (Camera.empty()) { nlwarning("can't get camera from scene"); return false; } // add a skeleton to the scene USkeleton Skeleton = Scene->createSkeleton(skeleton); // if we can't create entity, skip it if (Skeleton.empty()) { nlwarning("can't create skeleton from %s", skeleton.c_str()); return false; } vector Entities(parts.size()); for(size_t i = 0; i < parts.size(); ++i) { Entities[i] = Scene->createInstance(parts[i]); // if we can't create entity, skip it if (Entities[i].empty()) { nlwarning("can't create instance from %s", parts[i].c_str()); return false; } if (!Skeleton.bindSkin(Entities[i])) { nlwarning("can't bind %s to skeleton", parts[i].c_str()); return false; } } // get AABox of Entity CAABBox bbox; Skeleton.computeCurrentBBox(bbox, NULL); CVector center = bbox.getCenter(); CVector max_radius = bbox.getMax(); Skeleton.setPivot(center); float radius = max_radius.x; if (max_radius.y > radius) radius = max_radius.y; if (max_radius.z > radius) radius = max_radius.z; // camera will look at entities // Camera.lookAt(CVector(center.x, center.y - bbox.getRadius() * 1.5f, center.z * 2.f), CVector(center.x, center.y, center.z * 2.f)); Camera.lookAt(CVector(center.x + bbox.getRadius() * 2.0f, center.y, center.z), CVector(center.x, center.y, center.z)); renderShape(Skeleton, CVector::J, output_path); // delete entities for(size_t i = 0; i < Entities.size(); ++i) { Skeleton.detachSkeletonSon(Entities[i]); Scene->deleteInstance(Entities[i]); } // delete skeleton Scene->deleteSkeleton(Skeleton); return true; } bool ShapesExporter::saveOneImage(const string &filename) { CBitmap btm; Driver->getBuffer(btm); // resamble bitmap only if antialiasing is enabled if (settings.output_antialiasing > 1) btm.resample(settings.output_width, settings.output_height); COFile fs; if (fs.open(filename)) { if (settings.output_format == "png") { if (!btm.writePNG(fs, 24)) { nlwarning("can't save image to PNG"); return false; } } else if (settings.output_format == "jpg") { if (!btm.writeJPG(fs, settings.output_quality)) { nlwarning("can't save image to JPG"); return false; } } else if (settings.output_format == "tga") { if (!btm.writeTGA(fs, 24)) { nlwarning("can't save image to TGA"); return false; } } } else { nlwarning("can't create %s", filename.c_str()); return false; } return true; } bool ShapesExporter::renderShape(UTransform &entity, const CVector &axis, const string &output_path) { // main loop for (uint step_z = 0; step_z < settings.output_steps_z; ++step_z) { CQuat z(axis, (float)step_z * ((float)NLMISC::Pi*2.f / (float)settings.output_steps_z)); for (uint step_x = 0; step_x < settings.output_steps_x; ++step_x) { CQuat x(CVector::K, (float)step_x * ((float)NLMISC::Pi*2.f / (float)settings.output_steps_x)); entity.setRotQuat(z * x); string filename = CPath::standardizePath(output_path) + toString("%03d_%03d.%s", step_z, step_x, settings.output_format.c_str()); // the background is black Driver->clearBuffers(settings.output_background); // render the scene Scene->render(); if(!saveOneImage(filename)) return false; } } return true; } bool ShapesExporter::renderPS(UInstance &entity, const string &output_path, double &duration, CAABBox &bbox) { TGlobalAnimationTime time = 0.0, startTime = 0.0f; uint step = 0; static uint NbFrame = 100; double deltaTime = 0.05f; if (duration > 0.0f) deltaTime = duration / NbFrame; // main loop while(true) { uint nbparticle = 0; time += deltaTime; Scene->animate(time); UParticleSystemInstance *psi = static_cast(&entity); CTransformShape *e = entity.getObjectPtr(); CParticleSystemModel *psm = dynamic_cast(e); if(psm->getPS()) { nbparticle = psm->getPS()->getCurrNumParticles(); if(duration == 0.0f && time <= 10.0) { // after 10s we stop computing the bbox CAABBox bbox2; psm->getPS()->forceComputeBBox(bbox2); bbox = CAABBox::computeAABBoxUnion(bbox, bbox2); //bbox = bbox2; } } if(psi) { //psi->getSystemBBox(bbox); setCamera(bbox, entity, true); } // the background is black Driver->clearBuffers(settings.output_background); // render the scene Scene->render(); if(nbparticle > 0) { if(startTime == 0.0f) startTime = time; } if(duration > 0.0f) { string filename = CPath::standardizePath(output_path) + toString("000_%03d.%s", step, settings.output_format.c_str()); step++; if(!saveOneImage(filename)) return false; } if(time >= 30.0 || nbparticle == 0 && startTime > 0.0f || duration != 0.0f && time > duration) break; } if(duration > 0.0f) { if(step > 0) { FILE *fp = fopen(string(CPath::standardizePath(output_path)+"nb_steps.txt").c_str(), "w"); if(fp) { fprintf(fp, "%d", step); fclose(fp); } } nlinfo("PS duration %f after %f with nothing with %d steps, dt %f", duration, startTime, step, deltaTime); } if(time >= 30.0) duration = 10.0f; // for infinite duration animation, only take first 10s else duration = time; return true; } bool ShapesExporter::createThumbnail(const string &filename, const string &path) { string output_path = path; // Select which frame we'll use to do the thumnail uint selectedFrame = 0; if(CFile::getExtension(filename) == "ps") { FILE *fp = fopen(string(CPath::standardizePath(output_path)+"nb_steps.txt").c_str(), "r"); if(fp) { char str[100]; fgets(str, 100, fp); fclose(fp); selectedFrame = atoi(str)/2; } } CIFile in; // create a thumbail from first image if (in.open(output_path + toString("/000_%03d.", selectedFrame) + settings.output_format)) { CBitmap bitmap; bitmap.load(in); bitmap.resample(settings.preview_width, settings.preview_height); output_path += "/thumb"; // create directory for thumbnail if (!CFile::isExists(output_path) && !CFile::createDirectory(output_path)) { nlwarning("can't create %s", output_path.c_str()); return false; } COFile out; if (out.open(output_path + "/" + CFile::getFilenameWithoutExtension(filename) + "." + settings.preview_format)) { if (settings.preview_format == "png") { if (!bitmap.writePNG(out, 24)) { nlwarning("can't save image to PNG"); return false; } } else if (settings.preview_format == "jpg") { if (!bitmap.writeJPG(out, settings.preview_quality)) { nlwarning("can't save image to JPG"); return false; } } else if (settings.preview_format == "tga") { if (!bitmap.writeTGA(out, 24)) { nlwarning("can't save image to TGA"); return false; } } } else { nlwarning("can't create %s", filename.c_str()); return false; } } return true; }