// Ryzom - 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 "stdpch.h"
// Client
#include "misc.h"
#include "entity_animation_manager.h"
#include "entities.h"
#include "sheet_manager.h"
#include "interface_v3/interface_manager.h"
#include "continent_manager.h"
#include "world_database_manager.h"
#include "client_cfg.h"
#include "user_entity.h"
#include "net_manager.h"
#include "sky_render.h"
// Client Sheets
#include "client_sheets/item_sheet.h"
// 3D
#include "nel/3d/u_instance.h"
#include "nel/3d/u_instance_material.h"
#include "nel/3d/u_scene.h"
#include "nel/3d/u_driver.h"
#include "nel/3d/material.h"
// Misc
#include "nel/misc/file.h"
#include "nel/misc/i_xml.h"
#include "nel/misc/o_xml.h"
#include "nel/misc/fast_floor.h"
#include "nel/misc/noise_value.h"
#include "nel/misc/bitmap.h"
#include "nel/misc/system_utils.h"
// Game Share
#include "game_share/player_visual_properties.h"
#include "game_share/seeds.h"
// Georges
#include "nel/georges/u_form.h"
#include "nel/georges/u_form_elm.h"
#include "nel/georges/u_form_loader.h"
///////////
// USING //
///////////
using namespace NL3D;
using namespace NLMISC;
using namespace std;
using namespace NLGEORGES;
////////////
// EXTERN //
////////////
extern CEntityAnimationManager *EAM;
extern UDriver *Driver;
extern UMaterial GenericMat;
////////////
// GLOBAL //
////////////
const uint RecordVersion = 1;
std::set LodCharactersNotFound;
//////////////
// FUNCTION //
//////////////
//-----------------------------------------------
// keepIn_NegPi_Pi :
//-----------------------------------------------
double keepIn_NegPi_Pi(double angle)
{
if(angle > Pi)
angle = -2*Pi+angle;
else if(angle < -Pi)
angle = 2*Pi+angle;
return angle;
}// keepIn_NegPi_Pi //
//-----------------------------------------------
// angleBetween2Vect :
//-----------------------------------------------
double angleBetween2Vect(const CVectorD &from, const CVectorD &to)
{
// Get the final local head Yaw.
CVector vj = from;
vj.z = 0;
CVector vk(0,0,1);
CVector vi = vj^vk;
CMatrix mat;
mat.setRot(vi,vj,vk,true);
CVector localDir = mat.inverted() * to;
return atan2(-localDir.x, localDir.y);
}// angleBetween2Vect //
//-----------------------------------------------
// makeUp :
// \param face : pointer on the face to make up (must not be null).
// \param idMakeUp : index of the make-up to apply.
// \warning This function does not check if 'face' is valid.
//-----------------------------------------------
void makeUp(NL3D::UInstance face, sint idMakeUp)
{
static const char *tattooStr = "visage_makeup";
// look for tattoo texture
uint numMat = face.getNumMaterials();
std::string texFilename;
for(uint k = 0; k < numMat; ++k)
{
UInstanceMaterial im = face.getMaterial(k);
sint numTex = im.getLastTextureStage();
for(sint l = 0; l <= numTex; ++l)
{
if (im.isTextureFile(l)) // one texture from a file ?
{
// see if it is the texture used for tattoos
texFilename = im.getTextureFileName(l);
// nlinfo("visage tex = %s", texFilename.c_str());
std::string::size_type pos = texFilename.find(tattooStr, 0);
if (pos != std::string::npos)
{
uint charIndex = (uint)(pos + strlen(tattooStr));
if (texFilename.length() >= charIndex + 2)
{
texFilename[charIndex] = '0' + (unsigned char) (idMakeUp / 10);
texFilename[charIndex + 1] = '0' + (unsigned char) (idMakeUp % 10);
im.setTextureFileName(texFilename, l);
}
}
}
}
}
}// makeUp //
//-----------------------------------------------
// qStart is the quaterion at t=0,
// qEnd is the quaterion at t=TAnimEnd,
// time is between 0 and 1. (1=> TAnimEnd)
//-----------------------------------------------
CQuat applyRotationFactor(CQuat in, float rotFactor, CQuat qStart, CQuat qEnd, float time)
{
H_AUTO ( RZ_Client_Apply_Rotation_Factor )
CQuat qRotTotal1, qRotTotal2;
qStart.invert();
qRotTotal1= qEnd*qStart;
// qRotTotal2.makeClosest(CQuat::Identity);
qRotTotal2= CQuat::slerp(CQuat::Identity, qRotTotal1, rotFactor);
// apply animation factor
// qRotTotal1.makeClosest(CQuat::Identity);
// qRotTotal2.makeClosest(CQuat::Identity);
CQuat qRotDelta1= CQuat::slerp(CQuat::Identity, qRotTotal1, time);
CQuat qRotDelta2= CQuat::slerp(CQuat::Identity, qRotTotal2, time);
// remove normal rotation, and apply rotFactor-ed one.
qRotDelta1.invert();
return qRotDelta2 * qRotDelta1 * in;
}// applyRotationFactor //
//-----------------------------------------------
// computeShortestAngle :
// Compute the angle between a source and a destination using the shortest way.
// \param from : angle between -Pi and Pi;
// \param to : angle between -Pi and Pi;
//-----------------------------------------------
double computeShortestAngle(double from, double to)
{
double difAngle = to - from;
if(difAngle < -Pi) // Angle in the wrong direction.
difAngle += 2.0*Pi;
else if(difAngle > Pi) // Angle in the wrong direction.
difAngle -= 2.0*Pi;
return difAngle;
}// computeShortestAngle //
//-----------------------------------------------
// readStringArray :
//-----------------------------------------------
void readStringArray(const std::string &filename, NLGEORGES::UFormLoader *formLoader, NLMISC::CSmartPtr &form, std::map &container)
{
// Read an array of strings
if(formLoader)
{
form = formLoader->loadForm(filename.c_str());
if(form)
{
// Get the root.
const UFormElm& rootElmt = form->getRootNode();
// Get animations.
const UFormElm *elmt = 0;
if(rootElmt.getNodeByName(&elmt, "array") == false)
nlwarning("readStringArray: the node 'array' is not referenced.");
// If the array is not empty (in fact exist).
if(elmt)
{
// Get the array size
uint arraySize;
elmt->getArraySize(arraySize);
// If there is at least 1 animation.
if(arraySize > 0)
{
// Get all animation for the State.
for(uint i = 0; igetArrayNodeName(nodeName, i))
{
if(elmt->getArrayValue(stringName, i))
container.insert(make_pair(nodeName, stringName));
else
nlwarning("readStringArray: no string associated to the node '%d(%s)'.", i, nodeName.c_str());
}
else
nlwarning("readStringArray: node '%d', index valid.", i);
}
}
}
else
nlwarning("readStringArray: array node not allocated.");
}
else
nlwarning("readStringArray: cannot create the form from file '%s'.", filename.c_str());
}
else
nlwarning("readStringArray: the Loader is not allocated.");
}// readStringArray //
//-----------------------------------------------
// mode2Anim :
// Return the animset base name corresponding to the mode.
// \param mode : the mode to convert.
// \param result : this will be filed with the mode animset name.
// \return bool : 'true' if 'result' is filled, 'false' if left untouched.
//-----------------------------------------------
bool mode2Anim(MBEHAV::EMode mode, string &result)
{
static bool init = false;
static string mode2AnimArray[MBEHAV::NUMBER_OF_MODES];
// Is the mode valid
uint index = (uint)mode;
if(index >= MBEHAV::NUMBER_OF_MODES)
return false;
// Initialize
if(!init)
{
// Read mode2animset file
std::map mode2Animset;
const std::string filename = "mode2animset.string_array";
NLGEORGES::UFormLoader *formLoader = UFormLoader::createLoader();
if(formLoader)
{
NLMISC::CSmartPtr form;
readStringArray(filename, formLoader, form, mode2Animset);
}
else
nlwarning("mode2Anim: cannot create de loader");
NLGEORGES::UFormLoader::releaseLoader(formLoader);
// Initialize the static vector.
//-------------------------------
for(uint i=0; i::const_iterator it = mode2Animset.find(modeName);
if(it != mode2Animset.end())
{
mode2AnimArray[i] = (*it).second;
if(mode2AnimArray[i].empty())
nlwarning("mode2Anim: The mode '%d(%s)' has an empty animset associated.", i, modeName.c_str ());
}
// No animset for the mode.
else
{
mode2AnimArray[i] = "";
nlwarning("mode2Anim: no animset associated to the mode %d'%s'.", i, modeName.c_str());
}
}
// Init Done now.
init = true;
}
// Fill the result.
// Check name.
if(mode2AnimArray[index].empty())
result = mode2AnimArray[MBEHAV::NORMAL];
else
result = mode2AnimArray[index];
// Result filled.
return true;
}// mode2Anim //
//-----------------------------------------------
// computeAnimSet :
// Compute the animation set to use according to weapons, mode and race.
// \param animSet : result pointer.
// \param mode : the mode.
// \param animSetBaseName : basic name to construc the complet name of the animSet.
// \param leftHand : animSet name for the left hand.
// \param rightHand : animSet name for the right hand.
// \param lookAtItemsInHands : compute animset according to items in hands or not.
// \return bool : 'true' if the new animation set is the right one. 'false' if the one choosen is not the right one.
//-----------------------------------------------
bool computeAnimSet(const CAnimationSet *&animSet, MBEHAV::EMode mode, const string &animSetBaseName, const CItemSheet *itemLeftHand, const CItemSheet *itemRightHand, bool lookAtItemsInHands)
{
static std::set UnknownAnimSet;
if(EAM == 0)
{
if(!ClientCfg.Light)
{
nlwarning("computeAnimSet: EAM not allocated -> cannot compute the anim set.");
}
return false;
}
string animSetRight, animSetLeft;
if(itemLeftHand)
animSetLeft = itemLeftHand->getAnimSet();
if(itemRightHand)
animSetRight = itemRightHand->getAnimSet();
// Get the animset name from the mode.
string result;
if(!mode2Anim(mode, result))
{
nlwarning("computeAnimSet: unknown mode '%d'.", mode);
result = "default";
}
// Compute the name.
if(lookAtItemsInHands)
result = animSetBaseName + "_" + result + "_" + animSetRight + "_" + animSetLeft;
else
result = animSetBaseName + "_" + result + "__";
// Get the animset.
const CAnimationSet *animSetTmp = EAM->getAnimSet(result);
if(animSetTmp)
{
animSet = animSetTmp;
return true;
}
// Bad one try something else.
else
{
// Up to 100 missing anim set (security).
if(UnknownAnimSet.size() < 100)
{
if(UnknownAnimSet.insert(result).second)
nlwarning("computeAnimSet: unknown Anim Set '%s' ('%u' unkowns).", result.c_str(), UnknownAnimSet.size());
}
// Try to compute the default one
result = animSetBaseName + "_" + "default" + "__";
animSetTmp = EAM->getAnimSet(result);
if(animSetTmp)
animSet = animSetTmp;
else
{
// Up to 100 missing anim set (security).
if(UnknownAnimSet.size() < 100)
{
if(UnknownAnimSet.insert(result).second)
nlwarning("computeAnimSet: unknown Anim Set '%s' ('%u' unkowns).", result.c_str(), UnknownAnimSet.size());
}
}
}
// Not Well done.
return false;
}// computeAnimSet //
//-----------------------------------------------
// dump :
// Create a file with information to debug.
//-----------------------------------------------
void dump(const std::string &name)
{
// Write information to start as the version
COFile fStart;
if(fStart.open(name + "_start.rec", false, false))
{
CVectorD currentPos = UserEntity->pos();
fStart.serialVersion(RecordVersion);
fStart.serial(currentPos);
// Close the File.
fStart.close();
}
else
nlwarning("dump: cannot open/create the file '%s_start.rec'.", name.c_str());
// Write the DB
IngameDbMngr.write(name + "_db.rec");
// Open the file.
COFile f;
if(f.open(name + ".rec", false, false))
{
// Dump entities.
EntitiesMngr.dump(f);
// Dump Client CFG.
ClientCfg.serial(f);
// Close the File.
f.close();
}
else
nlwarning("dump: cannot open/create the file '%s.rec'.", name.c_str());
// Open the file.
if(f.open(name + ".xml", false, true))
{
// Create the XML stream
COXml output;
// Init
if(output.init (&f, "1.0"))
{
// Open the XML Dump.
output.xmlPush("XML");
// Dump Client CFG.
ClientCfg.serial(output);
// Dump entities.
EntitiesMngr.dumpXML(output);
// Close the XML Dump.
output.xmlPop();
// Flush the stream, write all the output file
output.flush();
}
else
nlwarning("dump: cannot initialize '%s.xml'.", name.c_str());
// Close the File.
f.close();
}
else
nlwarning("dump: cannot open/create the file '%s.xml'.", name.c_str());
}// dump //
//-----------------------------------------------
// loadDump :
// Create a file with the current state of the client (good to report a bug).
//-----------------------------------------------
void loadDump(const std::string &name)
{
CVectorD currentPos;
// Load information to start as the version
CIFile fStart;
if(fStart.open(name + "_start.rec", false))
{
fStart.serialVersion(RecordVersion);
fStart.serial(currentPos);
// Close the File.
fStart.close();
}
else
nlwarning("loadDump: cannot open the file '%s_start.rec'.", name.c_str());
// Update the position for the vision.
NetMngr.setReferencePosition(currentPos);
// Select the closest continent from the new position.
class CDummyProgress : public IProgressCallback
{
void progress (float /* value */) {}
};
CDummyProgress dummy;
ContinentMngr.select(currentPos, dummy);
// Load the DB
IngameDbMngr.read(name + "_db.rec");
// Open the file.
CIFile f;
if(f.open(name + ".rec", false))
{
// Dump entities.
EntitiesMngr.dump(f);
// Close the File.
f.close();
}
else
nlwarning("loadDump: cannot open '%s.rec'.", name.c_str());
}// loadDump //
//-----------------------------------------------
// getLodCharacterId
// Get the lod character id from the scene, according to LodCharacterName. Cached.
// -1 if id not found.
//-----------------------------------------------
sint getLodCharacterId(UScene &scene, const string &lodCharacterName)
{
sint lodCharacterId = scene.getCLodShapeIdByName(lodCharacterName);
// display a warning for bad character Id, only if name was setup in the sheet
if(lodCharacterId==-1 && !lodCharacterName.empty() )
{
// Limited to 100 missing Lod to avoid memories problems
if(LodCharactersNotFound.size() < 100)
{
// Insert and display a waring
if(LodCharactersNotFound.insert(lodCharacterName).second)
nlwarning("getLodCharacterId: Not found A Character LodCharacter in the Manager: %s", lodCharacterName.c_str());
}
}
return lodCharacterId;
}// getLodCharacterId //
//-----------------------------------------------
// getItem :
//-----------------------------------------------
CItemSheet *getItem(const CGenderInfo &genderInfo, SLOTTYPE::EVisualSlot slot)
{
CEntitySheet *faceItem = SheetMngr.get(CSheetId(genderInfo.getItemName(slot)));
return (dynamic_cast (faceItem));
}// getItem //
//-----------------------------------------------
// getColorIndex :
//-----------------------------------------------
sint getColorIndex(const CGenderInfo &genderInfo, SLOTTYPE::EVisualSlot slot)
{
CItemSheet *is = getItem(genderInfo, slot);
if(is == 0)
return 0;
return is->Color;
}// getColorIndex //
//-----------------------------------------------
// buildPropVisualA :
//-----------------------------------------------
SPropVisualA buildPropVisualA(const CGenderInfo &genderInfo)
{
SPropVisualA dest;
dest.PropertyA = 0;
// setup items
dest.PropertySubData.JacketModel = SheetMngr.getVSIndex(genderInfo.Items[SLOTTYPE::CHEST_SLOT], SLOTTYPE::CHEST_SLOT);
dest.PropertySubData.TrouserModel = SheetMngr.getVSIndex(genderInfo.Items[SLOTTYPE::LEGS_SLOT], SLOTTYPE::LEGS_SLOT);
dest.PropertySubData.ArmModel = SheetMngr.getVSIndex(genderInfo.Items[SLOTTYPE::ARMS_SLOT], SLOTTYPE::ARMS_SLOT);
dest.PropertySubData.HatModel = SheetMngr.getVSIndex(genderInfo.Items[SLOTTYPE::HEAD_SLOT], SLOTTYPE::HEAD_SLOT);
// setup colors (fixed or user)
sint col = getColorIndex(genderInfo, SLOTTYPE::CHEST_SLOT);
dest.PropertySubData.JacketColor = (col == -1) ? 0 : col;
//
col = getColorIndex(genderInfo, SLOTTYPE::LEGS_SLOT);
dest.PropertySubData.TrouserColor = (col == -1) ? 0 : col;
//
col = getColorIndex(genderInfo, SLOTTYPE::ARMS_SLOT);
dest.PropertySubData.ArmColor = (col == -1) ? 0 : col;
//
col = getColorIndex(genderInfo, SLOTTYPE::HEAD_SLOT);
dest.PropertySubData.HatColor = (col == -1) ? 0 : col;
// sheath are not used
return dest;
}// buildPropVisualA //
//-----------------------------------------------
// buildPropVisualB :
//-----------------------------------------------
SPropVisualB buildPropVisualB(const CGenderInfo &genderInfo)
{
// feet & hands
SPropVisualB dest;
dest.PropertyB = 0;
// setup items
dest.PropertySubData.HandsModel = SheetMngr.getVSIndex(genderInfo.Items[SLOTTYPE::HANDS_SLOT], SLOTTYPE::HANDS_SLOT);
dest.PropertySubData.FeetModel = SheetMngr.getVSIndex(genderInfo.Items[SLOTTYPE::FEET_SLOT], SLOTTYPE::FEET_SLOT);
// setup colors (fixed or user)
sint col = getColorIndex(genderInfo, SLOTTYPE::HANDS_SLOT);
dest.PropertySubData.HandsColor = (col == -1) ? 0 : col;
//
col = getColorIndex(genderInfo, SLOTTYPE::FEET_SLOT);
dest.PropertySubData.FeetColor = (col == -1) ? 0 : col;
return dest;
}// buildPropVisualB //
//-----------------------------------------------
// isUserColorSupported :
// Test whether user color is supported for this equipment
//-----------------------------------------------
bool isUserColorSupported(const CPlayerSheet::CEquipment &equip)
{
NLMISC::CSheetId si;
if(!si.buildSheetId(equip.Item))
return false;
CItemSheet *is = dynamic_cast(SheetMngr.get(si));
if(!is)
return false;
return is->Color == -1; // user color supported by the item
}// isUserColorSupported //
//-----------------------------------------------
// isUserColorSupported :
// Test whether user color is supported for a given visual slot
//-----------------------------------------------
bool isUserColorSupported(const CPlayerSheet &playerSheet, SLOTTYPE::EVisualSlot vs)
{
switch(vs)
{
case SLOTTYPE::CHEST_SLOT: return isUserColorSupported(playerSheet.Body);
case SLOTTYPE::LEGS_SLOT: return isUserColorSupported(playerSheet.Legs);
case SLOTTYPE::HEAD_SLOT: return isUserColorSupported(playerSheet.Head);
case SLOTTYPE::ARMS_SLOT: return isUserColorSupported(playerSheet.Arms);
case SLOTTYPE::HANDS_SLOT: return isUserColorSupported(playerSheet.Hands);
case SLOTTYPE::FEET_SLOT: return isUserColorSupported(playerSheet.Feet);
case SLOTTYPE::RIGHT_HAND_SLOT: return isUserColorSupported(playerSheet.ObjectInRightHand);
case SLOTTYPE::LEFT_HAND_SLOT: return isUserColorSupported(playerSheet.ObjectInLeftHand);
default: break;
}
return false;
}// isUserColorSupported //
//-----------------------------------------------
// getColor :
//-----------------------------------------------
sint8 getColor(const CPlayerSheet::CEquipment &equip)
{
NLMISC::CSheetId si;
if(!si.buildSheetId(equip.Item))
return -2;
CItemSheet *is = dynamic_cast(SheetMngr.get(si));
if(!is)
return -2;
if(is->Color == -1) // user color supported by the item
{
return equip.Color;
}
else
{
return is->Color;
}
}// getColor //
//-----------------------------------------------
// buildPropVisualA :
//-----------------------------------------------
SPropVisualA buildPropVisualA(const CPlayerSheet &playerSheet)
{
SPropVisualA vp;
// setup items
vp.PropertySubData.JacketModel = SheetMngr.getVSIndex(playerSheet.Body.Item, SLOTTYPE::CHEST_SLOT);
vp.PropertySubData.TrouserModel = SheetMngr.getVSIndex(playerSheet.Legs.Item, SLOTTYPE::LEGS_SLOT);
vp.PropertySubData.ArmModel = SheetMngr.getVSIndex(playerSheet.Arms.Item, SLOTTYPE::ARMS_SLOT);
vp.PropertySubData.HatModel = SheetMngr.getVSIndex(playerSheet.Head.Item, SLOTTYPE::HEAD_SLOT);
vp.PropertySubData.WeaponRightHand = SheetMngr.getVSIndex(playerSheet.ObjectInRightHand.Item, SLOTTYPE::RIGHT_HAND_SLOT);
vp.PropertySubData.WeaponLeftHand = SheetMngr.getVSIndex(playerSheet.ObjectInLeftHand.Item, SLOTTYPE::LEFT_HAND_SLOT);
// setup colors
vp.PropertySubData.JacketColor = getColor(playerSheet.Body);
vp.PropertySubData.TrouserColor = getColor(playerSheet.Legs);
vp.PropertySubData.ArmColor = getColor(playerSheet.Arms);
vp.PropertySubData.HatColor = getColor(playerSheet.Head);
return vp;
}// buildPropVisualA //
//-----------------------------------------------
// buildPropVisualB :
//-----------------------------------------------
SPropVisualB buildPropVisualB(const CPlayerSheet &playerSheet)
{
SPropVisualB vp;
// setup items
vp.PropertySubData.Name = 0;
vp.PropertySubData.HandsModel = SheetMngr.getVSIndex(playerSheet.Hands.Item, SLOTTYPE::HANDS_SLOT);
vp.PropertySubData.FeetModel = SheetMngr.getVSIndex(playerSheet.Feet.Item, SLOTTYPE::FEET_SLOT);
// setup colors
vp.PropertySubData.HandsColor = getColor(playerSheet.Hands);
vp.PropertySubData.FeetColor = getColor(playerSheet.Feet);
return vp;
}// buildPropVisualB //
//-----------------------------------------------
// drawBox :
// Draw a Box from 2 vectors.
//-----------------------------------------------
void drawBox(const CVector &vMin, const CVector &vMax, const CRGBA &color)
{
CLineColor line;
line.Color0 = color;
line.Color1 = color;
// Bottom quad
line = CLine(CVector(vMin.x,vMin.y,vMin.z), CVector(vMax.x,vMin.y,vMin.z));
Driver->drawLine(line, GenericMat);
line = CLine(CVector(vMax.x,vMin.y,vMin.z), CVector(vMax.x,vMax.y,vMin.z));
Driver->drawLine(line, GenericMat);
line = CLine(CVector(vMax.x,vMax.y,vMin.z), CVector(vMin.x,vMax.y,vMin.z));
Driver->drawLine(line, GenericMat);
line = CLine(CVector(vMin.x,vMax.y,vMin.z), CVector(vMin.x,vMin.y,vMin.z));
Driver->drawLine(line, GenericMat);
// Top quad
line = CLine(CVector(vMin.x,vMin.y,vMax.z), CVector(vMax.x,vMin.y,vMax.z));
Driver->drawLine(line, GenericMat);
line = CLine(CVector(vMax.x,vMin.y,vMax.z), CVector(vMax.x,vMax.y,vMax.z));
Driver->drawLine(line, GenericMat);
line = CLine(CVector(vMax.x,vMax.y,vMax.z), CVector(vMin.x,vMax.y,vMax.z));
Driver->drawLine(line, GenericMat);
line = CLine(CVector(vMin.x,vMax.y,vMax.z), CVector(vMin.x,vMin.y,vMax.z));
Driver->drawLine(line, GenericMat);
// Sides Quad
line = CLine(CVector(vMin.x,vMin.y,vMin.z), CVector(vMin.x,vMin.y,vMax.z));
Driver->drawLine(line, GenericMat);
line = CLine(CVector(vMax.x,vMin.y,vMin.z), CVector(vMax.x,vMin.y,vMax.z));
Driver->drawLine(line, GenericMat);
line = CLine(CVector(vMax.x,vMax.y,vMin.z), CVector(vMax.x,vMax.y,vMax.z));
Driver->drawLine(line, GenericMat);
line = CLine(CVector(vMin.x,vMax.y,vMin.z), CVector(vMin.x,vMax.y,vMax.z));
Driver->drawLine(line, GenericMat);
}// drawBox //
//-----------------------------------------------
// drawSphere :
// Draw a Sphere
//-----------------------------------------------
void drawSphere(const NLMISC::CVector ¢er, float radius, const NLMISC::CRGBA &color)
{
const uint numSegs= 12;
CLineColor line;
line.Color0 = color;
line.Color1 = color;
// For all faces
for(uint face=0;face<3;face++)
{
for(uint i=0;idrawLine(line, GenericMat);
}
}
}
//-----------------------------------------------
// getSeedsFromDB :
//-----------------------------------------------
void getSeedsFromDB(CSeeds &dest)
{
CInterfaceManager *im =CInterfaceManager::getInstance();
nlctassert(sizeof(CSeeds::TUInt) == 4); // excpect that the number of each seed type is encoded on 32 bits
// if this assert at compile, change the following code
string ls = im->getDefine("money_1");
string ms = im->getDefine("money_2");
string bs = im->getDefine("money_3");
string vbs = im->getDefine("money_4");
dest = CSeeds(im->getDbProp(ls)->getValue32(),
im->getDbProp(ms)->getValue32(),
im->getDbProp(bs)->getValue32(),
im->getDbProp(vbs)->getValue32());
} // getSeedsFromDB //
//-----------------------------------------------
// setVect :
// Change a 'direction' vector.
// \param vectToChange : the vector to change.
// \param vect : new vector to use.
// \param compute : adjust the param 'vect' to be valid or leave the old one unchanged if impossible.
// \param check : warning if the param 'vect' is not valid (vector Null) even with compute=true.
// \return bool : 'true' if the vectToChange has been filled, else 'false'.
//-----------------------------------------------
bool setVect(CVector &vectToChange, const CVector &vect, bool compute, bool check)
{
// Compute the vector.
if(compute)
{
CVector vectTmp(vect);
// No need of the Z component for the vector.
vectTmp.z = 0.0f;
// Check vector.
if(vectTmp != CVector::Null)
{
vectTmp.normalize();
vectToChange = vectTmp;
return true;
}
// Bad Vector -> vectToChange remains the same
else
{
// Warning
if(check)
nlwarning("setVect: cannot compute the vector, keep the old one.");
return false;
}
}
// Bad Vector -> vectToChange remains the same
if(vect == CVector::Null)
{
// Warning
if(check)
nlwarning("setVect: param is a vector Null, vectToChange remains the same.");
return false;
}
// Set the new front vector.
vectToChange = vect;
return true;
}// setVect //
NLMISC::CRGBA interpClientCfgColor(const ucstring &src, ucstring &dest)
{
CRGBA color = CRGBA::White;
if (src.size() >= 3)
{
if (src[0] == (ucchar) '&')
{
ucstring::size_type nextPos = src.find((ucchar) '&', 1);
if (nextPos != ucstring::npos)
{
std::string colorCode;
colorCode.resize(nextPos - 1);
for(uint k = 0; k < nextPos - 1; ++k)
{
colorCode[k] = tolower((char) src[k + 1]);
}
std::map::const_iterator it = ClientCfg.SystemInfoParams.find(colorCode);
if (it != ClientCfg.SystemInfoParams.end())
{
color = it->second.Color;
}
dest = src.substr(nextPos + 1);
}
else
{
dest = src;
}
}
else
{
dest = src;
}
}
else
{
dest = src;
}
return color;
}
std::string getStringCategory(const ucstring &src, ucstring &dest, bool alwaysAddSysByDefault)
{
std::string str = getStringCategoryIfAny(src, dest);
if (alwaysAddSysByDefault)
return str.empty()?"SYS":str;
else
return str;
}
std::string getStringCategoryIfAny(const ucstring &src, ucstring &dest)
{
std::string colorCode = "";
if (src.size() >= 3)
{
uint startPos = 0;
// Skip or if present at beginning
ucstring preTag;
const uint PreTagSize = 5;
const ucstring newTag("");
if ( (src.size() >= PreTagSize) && (src.substr( 0, PreTagSize ) == newTag) )
{
startPos = PreTagSize;
preTag = newTag;
}
const ucstring chgTag("");
if ( (src.size() >= PreTagSize) && (src.substr( 0, PreTagSize ) == chgTag) )
{
startPos = PreTagSize;
preTag = chgTag;
}
if (src[startPos] == (ucchar) '&')
{
ucstring::size_type nextPos = src.find((ucchar) '&', startPos+1);
if (nextPos != ucstring::npos)
{
uint codeSize = (uint)nextPos - startPos - 1;
colorCode.resize( codeSize );
for(uint k = 0; k < codeSize; ++k)
{
colorCode[k] = tolower((char) src[k + startPos + 1]);
}
ucstring destTmp;
if ( startPos != 0 )
destTmp = preTag; // leave or in the dest string
destTmp += src.substr(nextPos + 1);
dest = destTmp;
}
else
{
dest = src;
}
}
else
{
dest = src;
}
}
else
{
dest = src;
}
return colorCode;
}
NLMISC::CRGBA stringToRGBA(const char *ptr)
{
if (!ptr) return NLMISC::CRGBA::White;
int r = 255, g = 255, b = 255, a = 255;
sscanf (ptr, "%d %d %d %d", &r, &g, &b, &a);
NLMISC::clamp (r, 0, 255);
NLMISC::clamp (g, 0, 255);
NLMISC::clamp (b, 0, 255);
NLMISC::clamp (a, 0, 255);
return CRGBA(r,g,b,a);
}
// ***************************************************************************
inline bool isSeparator (ucchar c)
{
return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r');
}
// ***************************************************************************
inline bool isEndSentence (ucstring& str, uint index)
{
// Ex: One sentence. Another sentence.
// ^
// Counterexample: nevrax.com
// ^
ucchar c = str[index];
if ((str[index] == ' ') || (str[index] == '\n'))
{
if (index < 1)
return false;
c = str[index-1];
return (c == '.') || (c == '!') || (c == '?');
}
return false;
}
void setCase (ucstring &str, TCaseMode mode)
{
const uint length = (uint)str.length();
uint i;
bool newString = true;
bool newSentence = true;
bool newWord = true;
switch (mode)
{
case CaseLower:
str = toLower (str);
break;
case CaseUpper:
str = toUpper (str);
break;
case CaseFirstStringLetterUp:
for (i=0; i0 && lenS1>0)
{
ucchar c0= toLower(*start0++);
ucchar c1= toLower(*start1++);
if(c0!=c1)
return c0s0, 1 if s0>s1, or 0 if equals
if(lenS1>0)
return -1;
else if(lenS0>0)
return 1;
else
return 0;
}
// *******************************************************************************************
float computeUniformNoise(const NLMISC::CNoiseValue &nv, const CVector &pos)
{
NLMISC::OptFastFloorBegin();
float value = nv.eval(pos);
value = 10.f * fmodf(value, 0.1f); // make repartition more uniform
NLMISC::OptFastFloorEnd();
return value;
}
// ***************************************************************************
void computeCurrentFovAspectRatio(float &fov, float &ar)
{
// compute the fov
fov = (float)(ClientCfg.FoV*Pi/180.0);
// get the screen aspect ratio from CFG
ar = ClientCfg.ScreenAspectRatio;
//nlinfo("AR: clientcfg ar %f", ar);
// if Driver is not created, we can't get current screen mode
if (!Driver) return;
// if windowed, must modulate aspect ratio by (WindowResolution / ScreenResolution)
if(ClientCfg.Windowed)
{
uint32 wndW, wndH;
Driver->getWindowSize(wndW, wndH);
UDriver::CMode mode;
Driver->getCurrentScreenMode(mode);
if(wndH)
{
// compute window aspect ratio
float arWnd= float(wndW) / float(wndH);
if (ar == 0.f)
{
// auto mode, we are using window aspect ratio
ar = arWnd;
//nlinfo("AR: windowed auto => use window size %d %d => %f", wndW, wndH, arWnd);
}
else if (mode.Width && mode.Height)
{
// compute screen aspect ratio
float arScreen= float(mode.Width) / float(mode.Height);
ar *= arWnd / arScreen;
//nlinfo("AR: windowed not auto => monitor size %d %d window size %d %d => arsc %f arwnd %f, ar finale %f", mode.Width, mode.Height, wndW, wndH, arScreen, arWnd, ar);
}
}
}
// if fullscreen, must modulate aspect ratio by ScreenResolution
else
{
//nlinfo("AR: fullscreen ratio");
if (ar == 0.f)
{
UDriver::CMode mode;
Driver->getCurrentScreenMode(mode);
if(mode.Height)
{
ar = float(mode.Width) / float(mode.Height);
}
}
}
}
// ***************************************************************************
void drawDisc(CBitmap &dest, float x, float y, float radius, const CRGBA &color, bool additif /*= false*/, uint numSegs /*= 127*/)
{
CPolygon2D poly;
poly.Vertices.resize(numSegs);
for(uint k = 0; k < numSegs; ++k)
{
poly.Vertices[k].set(x + radius * (float) cos(k / (float) numSegs * 2 * Pi), y + radius * (float) sin(k / (float) numSegs * 2 * Pi));
}
CPolygon2D::TRasterVect rasters;
sint minY;
poly.computeOuterBorders(rasters, minY);
sint maxY = std::min((sint) dest.getHeight(), (sint) rasters.size() + minY);
for (sint y = std::max((sint) 0, minY); y < maxY; ++y)
{
nlassert(y >= 0 && y < (sint) dest.getHeight());
sint minX = std::max((sint) 0, rasters[y - minY].first);
sint maxX = std::min((sint) dest.getWidth(), rasters[y - minY].second);
if (maxX > minX)
{
CRGBA *pt = (CRGBA *) &dest.getPixels(0)[0];
pt += y * dest.getWidth() + minX;
const CRGBA *endPt = pt + (maxX - minX);
while (pt != endPt)
{
if (additif)
{
pt->add(*pt, color);
}
else
{
*pt = color;
}
++ pt;
}
}
}
}
// *************************************************************************************************
NL3D::UScene *getSkyScene()
{
if (ContinentMngr.cur() && !ContinentMngr.cur()->Indoor)
{
CSky &sky = ContinentMngr.cur()->CurrentSky;
if (sky.getScene())
{
return sky.getScene();
}
else
{
return SkyScene; // old sky rendering
}
}
return NULL;
}
// *************************************************************************************************
void setEmissive(NL3D::UInstance instance, const NLMISC::CRGBA &color)
{
if (instance.empty()) return;
for(uint k = 0; k < instance.getNumMaterials(); ++k)
{
NL3D::UInstanceMaterial mat = instance.getMaterial(k);
mat.setEmissive(color);
}
}
// *************************************************************************************************
void setDiffuse(NL3D::UInstance instance, bool onOff, const NLMISC::CRGBA &color)
{
if (instance.empty()) return;
for(uint k = 0; k < instance.getNumMaterials(); ++k)
{
NL3D::UInstanceMaterial mat = instance.getMaterial(k);
if (mat.isLighted())
{
CRGBA src;
if (onOff)
{
src = mat.getDiffuse();
src.R = color.R;
src.G = color.G;
src.B = color.B;
}
else
{
NL3D::UMaterial matShape = instance.getShape().getMaterial(k);
src = matShape.getDiffuse();
src.A = mat.getDiffuse().A;
}
mat.setDiffuse(src);
}
else
{
CRGBA src;
if (onOff)
{
src = mat.getColor();
src.R = color.R;
src.G = color.G;
src.B = color.B;
}
else
{
NL3D::UMaterial matShape = instance.getShape().getMaterial(k);
src = matShape.getColor();
src.A = mat.getColor().A;
}
mat.setColor(src);
}
}
}
// *************************************************************************************************
void makeInstanceTransparent(UInstance &inst, uint8 opacity, bool disableZWrite)
{
UShape shape= inst.getShape();
if(shape.empty())
return;
uint numMats= shape.getNumMaterials();
if(numMats==0)
return;
if(numMats!=inst.getNumMaterials())
return;
// instance transparent or not?
if (opacity == 255)
{
// reset default shape opacity / transparency
inst.setOpacity(shape.getDefaultOpacity());
inst.setTransparency(shape.getDefaultTransparency());
inst.setBypassLODOpacityFlag(false);
}
else
{
// Will have some blend material => sure not to be rendered in Opaque pass
inst.setOpacity(false);
inst.setTransparency(true);
inst.setBypassLODOpacityFlag(true); // these flags prevails over the current lods flags for multi-lod objects
}
// set all materials
for (uint32 j = 0; j < numMats; ++j)
{
NL3D::UInstanceMaterial matInst = inst.getMaterial(j);
NL3D::UMaterial matShape= shape.getMaterial(j);
// disalbe zwrite?
if(disableZWrite)
matInst.setZWrite(false);
else
matInst.setZWrite(matShape.getZWrite());
// if no more transparent
if (opacity == 255)
{
// reset to default
matInst.setBlend(matShape.getBlend());
matInst.setBlendFunc((NL3D::UInstanceMaterial::TBlend)matShape.getSrcBlend(),
(NL3D::UInstanceMaterial::TBlend)matShape.getDstBlend());
// if orginal material is opaque or additif and has no alpha test, then ensure restore last tex env if needed
CMaterial *destInternalMat = matInst.getObjectPtr();
if (!matShape.getBlend() && !matShape.getAlphaTest())
{
if (destInternalMat->getShader() == CMaterial::Normal)
{
CMaterial *srcInternalMat = matShape.getObjectPtr();
uint numTex = 0;
for (;numTex < 4 && srcInternalMat->getTexture(numTex) != NULL; ++numTex) {}
if (numTex > 0)
{
if (srcInternalMat->getTexEnvMode(numTex - 1) != destInternalMat->getTexEnvMode(numTex - 1))
{
destInternalMat->setTexEnvMode(numTex - 1, srcInternalMat->getTexEnvMode(numTex - 1));
}
}
}
}
if (destInternalMat->getShader() == CMaterial::Normal)
{
// if !lighted, restore color
if (!destInternalMat->isLighted())
{
CMaterial *srcInternalMat = matShape.getObjectPtr();
// restore alpha in color
CRGBA color = destInternalMat->getColor();
color.A = srcInternalMat->getColor().A;
destInternalMat->setColor(color);
}
}
}
else
{
// Enable blend
matInst.setBlend(true);
// If default is ???/one or , then use a srcalpha/one (eg: for Diamond-like weapons)
if(matShape.getBlend() && (sint32)matShape.getDstBlend()==(sint32)NL3D::UInstanceMaterial::one)
matInst.setBlendFunc(NL3D::UInstanceMaterial::srcalpha, NL3D::UInstanceMaterial::one);
// else use a standard srcalpha/invsrcalpha
else
matInst.setBlendFunc(NL3D::UInstanceMaterial::srcalpha, NL3D::UInstanceMaterial::invsrcalpha);
// if orginal material is opaque or additif and has no alpha test, then ensure that the alpha output is 'diffuse'
CMaterial *internalMat = matInst.getObjectPtr();
if (!matShape.getBlend() && !matShape.getAlphaTest())
{
if (internalMat->getShader() == CMaterial::Normal)
{
uint numTex = 0;
for (;numTex < 4 && internalMat->getTexture(numTex) != NULL; ++numTex) {}
if (numTex > 0)
{
internalMat->texEnvOpAlpha(numTex - 1, CMaterial::Replace);
// if material is unlighted, then use the constant at this stage to set the alpha
internalMat->texEnvArg0Alpha(numTex - 1, CMaterial::Diffuse, CMaterial::SrcAlpha);
}
}
}
if (internalMat->getShader() == CMaterial::Normal)
{
if (!internalMat->isLighted())
{
// replace alpha in color
CRGBA color = internalMat->getColor();
color.A = opacity;
internalMat->setColor(color);
}
}
}
// suppose that default opacity is always 255
if (matInst.isLighted())
{
matInst.setOpacity(opacity);
}
matInst.setAlphaTestThreshold(matShape.getAlphaTestThreshold()*((float)opacity)/255.0f);
}
}
void setVideoMode(const UDriver::CMode &mode)
{
UDriver::CMode oldMode, newMode = mode;
oldMode.Windowed = true; // getCurrentScreenMode may fail if first init ...
Driver->getCurrentScreenMode(oldMode);
bool wasMaximized = isWindowMaximized();
if (!Driver->setMode(newMode) && !newMode.Windowed)
{
// failed to switch to mode, fall back to windowed
newMode.Windowed = true;
ClientCfg.Windowed = true;
ClientCfg.writeInt("FullScreen", 0);
// set the window mode
Driver->setMode(newMode);
}
bool isMaximized = isWindowMaximized();
if (oldMode.Windowed && !newMode.Windowed) // going to fullscreen ?
{
/*CInterfaceManager *pIM = CInterfaceManager::getInstance();
pIM->movePointerAbs((sint32) mode.Width / 2, (sint32) mode.Height / 2);
Driver->setMousePos(0.5f, 0.5f);*/
}
else if ((!oldMode.Windowed || wasMaximized) && (newMode.Windowed && !isMaximized)) // leaving fullscreen ?
{
UDriver::CMode screenMode;
uint32 posX = 0;
uint32 posY = 0;
if (Driver->getCurrentScreenMode(screenMode))
{
// position is not saved in config so center the window
posX = (screenMode.Width - Driver->getWindowWidth())/2;
posY = (screenMode.Height - Driver->getWindowHeight())/2;
}
Driver->setWindowPos(posX, posY);
}
}
uint getCurrentColorDepth()
{
if (Driver && Driver->isActive())
{
UDriver::CMode videoMode;
Driver->getCurrentScreenMode(videoMode);
if (!videoMode.Windowed)
{
return videoMode.Depth;
}
}
return CSystemUtils::getCurrentColorDepth();
}
bool isWindowMaximized()
{
UDriver::CMode screenMode;
uint32 width, height;
Driver->getWindowSize(width, height);
return (Driver->getCurrentScreenMode(screenMode) && screenMode.Windowed &&
screenMode.Width == width && screenMode.Height == height);
}
sint getRyzomModes(std::vector &videoModes, std::vector &stringModeList)
{
// **** Init Video Modes
Driver->getModes(videoModes);
// Remove modes under 800x600 and get the unique strings
sint i, j, nFoundMode = -1;
for (i=0; i < (sint)videoModes.size(); ++i)
{
if ((videoModes[i].Width < 800) || (videoModes[i].Height < 600))
{
videoModes.erase(videoModes.begin()+i);
--i;
}
else
{
bool bFound = false;
string tmp = toString(videoModes[i].Width)+" x "+toString(videoModes[i].Height);
for (j = 0; j < (sint)stringModeList.size(); ++j)
{
if (stringModeList[j] == tmp)
{
bFound = true;
break;
}
}
if (!bFound)
{
stringModeList.push_back(tmp);
if ((videoModes[i].Width <= ClientCfg.Width) && (videoModes[i].Height <= ClientCfg.Height))
{
if (nFoundMode == -1)
{
nFoundMode = j;
}
else
{
if ((videoModes[i].Width >= videoModes[nFoundMode].Width) &&
(videoModes[i].Height >= videoModes[nFoundMode].Height))
nFoundMode = j;
}
}
}
}
}
// If no modes are available, fallback to windowed mode
if (nFoundMode == -1)
{
nlwarning("Mode %ux%u not found, fall back to windowed", (uint)ClientCfg.Width, (uint)ClientCfg.Height);
ClientCfg.Windowed = true;
ClientCfg.writeInt("FullScreen", 0);
}
return nFoundMode;
}