khanat-opennel-code/code/nel/tools/3d/shapes_exporter/shapes_exporter.cpp

732 lines
17 KiB
C++
Raw Normal View History

2010-05-06 00:08:41 +00:00
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// 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 <http://www.gnu.org/licenses/>.
#include <nel/misc/path.h>
#include <nel/misc/file.h>
#include <nel/misc/config_file.h>
#include <nel/3d/u_light.h>
#include <nel/3d/u_camera.h>
#include <nel/3d/u_instance.h>
#include <nel/3d/u_skeleton.h>
#include <nel/3d/u_particle_system_instance.h>
#include <nel/3d/particle_system.h>
#include <nel/3d/particle_system_model.h>
#include <nel/3d/ps_util.h>
#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<UParticleSystemInstance*>(&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<string> &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<UInstance> 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<UParticleSystemInstance*>(&entity);
CTransformShape *e = entity.getObjectPtr();
CParticleSystemModel *psm = dynamic_cast<CParticleSystemModel*>(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;
}