Merge with develop
--HG-- branch : experimental-ui-scaling
This commit is contained in:
commit
7ccd9da497
32 changed files with 1651 additions and 436 deletions
173
code/CMakeModules/FindFFmpeg.cmake
Normal file
173
code/CMakeModules/FindFFmpeg.cmake
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
# vim: ts=2 sw=2
|
||||||
|
# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC)
|
||||||
|
#
|
||||||
|
# Once done this will define
|
||||||
|
# FFMPEG_FOUND - System has the all required components.
|
||||||
|
# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers.
|
||||||
|
# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components.
|
||||||
|
# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components.
|
||||||
|
#
|
||||||
|
# For each of the components it will additionaly set.
|
||||||
|
# - AVCODEC
|
||||||
|
# - AVDEVICE
|
||||||
|
# - AVFORMAT
|
||||||
|
# - AVUTIL
|
||||||
|
# - POSTPROC
|
||||||
|
# - SWSCALE
|
||||||
|
# - SWRESAMPLE
|
||||||
|
# the following variables will be defined
|
||||||
|
# <component>_FOUND - System has <component>
|
||||||
|
# <component>_INCLUDE_DIRS - Include directory necessary for using the <component> headers
|
||||||
|
# <component>_LIBRARIES - Link these to use <component>
|
||||||
|
# <component>_DEFINITIONS - Compiler switches required for using <component>
|
||||||
|
# <component>_VERSION - The components version
|
||||||
|
#
|
||||||
|
# Copyright (c) 2006, Matthias Kretz, <kretz@kde.org>
|
||||||
|
# Copyright (c) 2008, Alexander Neundorf, <neundorf@kde.org>
|
||||||
|
# Copyright (c) 2011, Michael Jansen, <kde@michael-jansen.biz>
|
||||||
|
#
|
||||||
|
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
|
||||||
|
if(NOT FFmpeg_FIND_COMPONENTS)
|
||||||
|
set(FFmpeg_FIND_COMPONENTS AVFORMAT AVCODEC AVUTIL)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
#
|
||||||
|
### Macro: set_component_found
|
||||||
|
#
|
||||||
|
# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present.
|
||||||
|
#
|
||||||
|
macro(set_component_found _component)
|
||||||
|
if(${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS)
|
||||||
|
# message(STATUS " - ${_component} found.")
|
||||||
|
set(${_component}_FOUND TRUE)
|
||||||
|
else()
|
||||||
|
# message(STATUS " - ${_component} not found.")
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
#
|
||||||
|
### Macro: find_component
|
||||||
|
#
|
||||||
|
# Checks for the given component by invoking pkgconfig and then looking up the libraries and
|
||||||
|
# include directories.
|
||||||
|
#
|
||||||
|
macro(find_component _component _pkgconfig _library _header)
|
||||||
|
if(NOT WIN32)
|
||||||
|
# use pkg-config to get the directories and then use these values
|
||||||
|
# in the FIND_PATH() and FIND_LIBRARY() calls
|
||||||
|
find_package(PkgConfig)
|
||||||
|
if(PKG_CONFIG_FOUND)
|
||||||
|
pkg_check_modules(PC_${_component} ${_pkgconfig})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_path(${_component}_INCLUDE_DIRS ${_header}
|
||||||
|
HINTS
|
||||||
|
${FFMPEGSDK_INC}
|
||||||
|
${PC_LIB${_component}_INCLUDEDIR}
|
||||||
|
${PC_LIB${_component}_INCLUDE_DIRS}
|
||||||
|
PATH_SUFFIXES
|
||||||
|
ffmpeg
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(${_component}_LIBRARIES NAMES ${_library}
|
||||||
|
HINTS
|
||||||
|
${FFMPEGSDK_LIB}
|
||||||
|
${PC_LIB${_component}_LIBDIR}
|
||||||
|
${PC_LIB${_component}_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
STRING(REGEX REPLACE "/.*" "/version.h" _ver_header ${_header})
|
||||||
|
if(EXISTS "${${_component}_INCLUDE_DIRS}/${_ver_header}")
|
||||||
|
file(STRINGS "${${_component}_INCLUDE_DIRS}/${_ver_header}" version_str REGEX "^#define[\t ]+LIB${_component}_VERSION_M.*")
|
||||||
|
|
||||||
|
foreach(_str "${version_str}")
|
||||||
|
if(NOT version_maj)
|
||||||
|
string(REGEX REPLACE "^.*LIB${_component}_VERSION_MAJOR[\t ]+([0-9]*).*$" "\\1" version_maj "${_str}")
|
||||||
|
endif()
|
||||||
|
if(NOT version_min)
|
||||||
|
string(REGEX REPLACE "^.*LIB${_component}_VERSION_MINOR[\t ]+([0-9]*).*$" "\\1" version_min "${_str}")
|
||||||
|
endif()
|
||||||
|
if(NOT version_mic)
|
||||||
|
string(REGEX REPLACE "^.*LIB${_component}_VERSION_MICRO[\t ]+([0-9]*).*$" "\\1" version_mic "${_str}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
unset(version_str)
|
||||||
|
|
||||||
|
set(${_component}_VERSION "${version_maj}.${version_min}.${version_mic}" CACHE STRING "The ${_component} version number.")
|
||||||
|
unset(version_maj)
|
||||||
|
unset(version_min)
|
||||||
|
unset(version_mic)
|
||||||
|
endif(EXISTS "${${_component}_INCLUDE_DIRS}/${_ver_header}")
|
||||||
|
set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.")
|
||||||
|
set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.")
|
||||||
|
|
||||||
|
set_component_found(${_component})
|
||||||
|
|
||||||
|
mark_as_advanced(
|
||||||
|
${_component}_INCLUDE_DIRS
|
||||||
|
${_component}_LIBRARIES
|
||||||
|
${_component}_DEFINITIONS
|
||||||
|
${_component}_VERSION)
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
|
||||||
|
set(FFMPEGSDK $ENV{FFMPEG_HOME})
|
||||||
|
if(FFMPEGSDK)
|
||||||
|
set(FFMPEGSDK_INC "${FFMPEGSDK}/include")
|
||||||
|
set(FFMPEGSDK_LIB "${FFMPEGSDK}/lib")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Check for all possible components.
|
||||||
|
find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h)
|
||||||
|
find_component(AVFORMAT libavformat avformat libavformat/avformat.h)
|
||||||
|
find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h)
|
||||||
|
find_component(AVUTIL libavutil avutil libavutil/avutil.h)
|
||||||
|
find_component(SWSCALE libswscale swscale libswscale/swscale.h)
|
||||||
|
find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h)
|
||||||
|
find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h)
|
||||||
|
|
||||||
|
# Check if the required components were found and add their stuff to the FFMPEG_* vars.
|
||||||
|
foreach(_component ${FFmpeg_FIND_COMPONENTS})
|
||||||
|
if(${_component}_FOUND)
|
||||||
|
# message(STATUS "Required component ${_component} present.")
|
||||||
|
set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES})
|
||||||
|
set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS})
|
||||||
|
list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS})
|
||||||
|
else()
|
||||||
|
# message(STATUS "Required component ${_component} missing.")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Build the include path and library list with duplicates removed.
|
||||||
|
if(FFMPEG_INCLUDE_DIRS)
|
||||||
|
list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(FFMPEG_LIBRARIES)
|
||||||
|
list(REMOVE_DUPLICATES FFMPEG_LIBRARIES)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# cache the vars.
|
||||||
|
set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE)
|
||||||
|
set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE)
|
||||||
|
set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE)
|
||||||
|
|
||||||
|
mark_as_advanced(FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES FFMPEG_DEFINITIONS)
|
||||||
|
|
||||||
|
# Now set the noncached _FOUND vars for the components.
|
||||||
|
foreach(_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWRESAMPLE SWSCALE)
|
||||||
|
set_component_found(${_component})
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
# Compile the list of required vars
|
||||||
|
set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS)
|
||||||
|
foreach(_component ${FFmpeg_FIND_COMPONENTS})
|
||||||
|
list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Give a nice error message if some of the required vars are missing.
|
||||||
|
find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS})
|
|
@ -20,6 +20,7 @@ ENDIF()
|
||||||
IF(WITH_SOUND)
|
IF(WITH_SOUND)
|
||||||
FIND_PACKAGE(Ogg)
|
FIND_PACKAGE(Ogg)
|
||||||
FIND_PACKAGE(Vorbis)
|
FIND_PACKAGE(Vorbis)
|
||||||
|
FIND_PACKAGE(FFmpeg COMPONENTS AVCODEC AVFORMAT AVUTIL SWRESAMPLE)
|
||||||
|
|
||||||
IF(WITH_DRIVER_OPENAL)
|
IF(WITH_DRIVER_OPENAL)
|
||||||
FIND_PACKAGE(OpenAL)
|
FIND_PACKAGE(OpenAL)
|
||||||
|
|
|
@ -178,6 +178,10 @@ public:
|
||||||
CVertexBuffer Vertices;
|
CVertexBuffer Vertices;
|
||||||
CMaterial *Material;
|
CMaterial *Material;
|
||||||
CRGBA Color;
|
CRGBA Color;
|
||||||
|
ucstring Text;
|
||||||
|
|
||||||
|
uint32 CacheVersion;
|
||||||
|
|
||||||
/// The width of the string, in pixels (eg: 30)
|
/// The width of the string, in pixels (eg: 30)
|
||||||
float StringWidth;
|
float StringWidth;
|
||||||
/// The height of the string, in pixels (eg: 10)
|
/// The height of the string, in pixels (eg: 10)
|
||||||
|
@ -223,6 +227,7 @@ public:
|
||||||
*/
|
*/
|
||||||
CComputedString (bool bSetupVB=true)
|
CComputedString (bool bSetupVB=true)
|
||||||
{
|
{
|
||||||
|
CacheVersion = 0;
|
||||||
StringWidth = 0;
|
StringWidth = 0;
|
||||||
StringHeight = 0;
|
StringHeight = 0;
|
||||||
if (bSetupVB)
|
if (bSetupVB)
|
||||||
|
|
|
@ -74,6 +74,8 @@ public:
|
||||||
|
|
||||||
uint32 getUID() { return _UID; }
|
uint32 getUID() { return _UID; }
|
||||||
|
|
||||||
|
std::string getFontFileName() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static uint32 _FontGeneratorCounterUID;
|
static uint32 _FontGeneratorCounterUID;
|
||||||
|
|
|
@ -59,6 +59,9 @@ class CFontManager
|
||||||
CSmartPtr<CMaterial> _MatFont;
|
CSmartPtr<CMaterial> _MatFont;
|
||||||
CSmartPtr<CTextureFont> _TexFont;
|
CSmartPtr<CTextureFont> _TexFont;
|
||||||
|
|
||||||
|
// Keep track number of textures created to properly report cache version
|
||||||
|
uint32 _TexCacheNr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,6 +74,7 @@ public:
|
||||||
_NbChar = 0;
|
_NbChar = 0;
|
||||||
_MatFont = NULL;
|
_MatFont = NULL;
|
||||||
_TexFont = NULL;
|
_TexFont = NULL;
|
||||||
|
_TexCacheNr = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,7 +98,6 @@ public:
|
||||||
*/
|
*/
|
||||||
CMaterial* getFontMaterial();
|
CMaterial* getFontMaterial();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute primitive blocks and materials of each character of
|
* Compute primitive blocks and materials of each character of
|
||||||
* the string.
|
* the string.
|
||||||
|
@ -152,7 +155,8 @@ public:
|
||||||
|
|
||||||
void dumpCache (const char *filename)
|
void dumpCache (const char *filename)
|
||||||
{
|
{
|
||||||
_TexFont->dumpTextureFont (filename);
|
if (_TexFont)
|
||||||
|
_TexFont->dumpTextureFont (filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,6 +164,15 @@ public:
|
||||||
*/
|
*/
|
||||||
void invalidate();
|
void invalidate();
|
||||||
|
|
||||||
|
// get font atlas rebuild count
|
||||||
|
uint32 getCacheVersion() const
|
||||||
|
{
|
||||||
|
if (_TexFont)
|
||||||
|
return (_TexFont->getCacheVersion() << 16) + _TexCacheNr;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -150,6 +150,10 @@ public:
|
||||||
{
|
{
|
||||||
nlassert (index < _CacheStrings.size());
|
nlassert (index < _CacheStrings.size());
|
||||||
CComputedString &rCS = _CacheStrings[index];
|
CComputedString &rCS = _CacheStrings[index];
|
||||||
|
if (rCS.CacheVersion != _FontManager->getCacheVersion())
|
||||||
|
{
|
||||||
|
computeString(rCS.Text, rCS);
|
||||||
|
}
|
||||||
if (_Shaded)
|
if (_Shaded)
|
||||||
{
|
{
|
||||||
CRGBA bkup = rCS.Color;
|
CRGBA bkup = rCS.Color;
|
||||||
|
@ -184,6 +188,10 @@ public:
|
||||||
{
|
{
|
||||||
nlassert (index < _CacheStrings.size());
|
nlassert (index < _CacheStrings.size());
|
||||||
CComputedString &rCS = _CacheStrings[index];
|
CComputedString &rCS = _CacheStrings[index];
|
||||||
|
if (rCS.CacheVersion != _FontManager->getCacheVersion())
|
||||||
|
{
|
||||||
|
computeString(rCS.Text, rCS);
|
||||||
|
}
|
||||||
if(_Shaded)
|
if(_Shaded)
|
||||||
{
|
{
|
||||||
CRGBA bkup = rCS.Color;
|
CRGBA bkup = rCS.Color;
|
||||||
|
@ -218,6 +226,11 @@ public:
|
||||||
{
|
{
|
||||||
nlassert (index < _CacheStrings.size());
|
nlassert (index < _CacheStrings.size());
|
||||||
CComputedString &rCS = _CacheStrings[index];
|
CComputedString &rCS = _CacheStrings[index];
|
||||||
|
if (rCS.CacheVersion != _FontManager->getCacheVersion())
|
||||||
|
{
|
||||||
|
computeString(rCS.Text, rCS);
|
||||||
|
}
|
||||||
|
|
||||||
if (_Shaded)
|
if (_Shaded)
|
||||||
{
|
{
|
||||||
CRGBA bkup = rCS.Color;
|
CRGBA bkup = rCS.Color;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#define NL_TEXTURE_FONT_H
|
#define NL_TEXTURE_FONT_H
|
||||||
|
|
||||||
#include "nel/misc/types_nl.h"
|
#include "nel/misc/types_nl.h"
|
||||||
|
#include "nel/misc/rect.h"
|
||||||
#include "nel/3d/texture.h"
|
#include "nel/3d/texture.h"
|
||||||
|
|
||||||
namespace NL3D
|
namespace NL3D
|
||||||
|
@ -25,9 +26,6 @@ namespace NL3D
|
||||||
|
|
||||||
class CFontGenerator;
|
class CFontGenerator;
|
||||||
|
|
||||||
#define TEXTUREFONT_NBCATEGORY 5 // Config 1
|
|
||||||
//#define TEXTUREFONT_NBCATEGORY 4
|
|
||||||
|
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
/**
|
/**
|
||||||
* CTextureFont
|
* CTextureFont
|
||||||
|
@ -37,32 +35,59 @@ class CTextureFont : public ITexture
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
struct SLetterInfo
|
// Holds info for glyphs rendered on atlas
|
||||||
|
struct SGlyphInfo
|
||||||
{
|
{
|
||||||
// To generate the letter
|
// font atlas info
|
||||||
ucchar Char;
|
uint32 CacheVersion;
|
||||||
CFontGenerator *FontGenerator;
|
|
||||||
|
// atlas region with padding
|
||||||
|
uint32 X, Y, W, H;
|
||||||
|
|
||||||
|
// rendered glyph size without padding
|
||||||
|
uint32 CharWidth;
|
||||||
|
uint32 CharHeight;
|
||||||
|
|
||||||
|
// UV coords for rendered glyph without padding
|
||||||
|
float U0, V0, U1, V1;
|
||||||
|
|
||||||
|
uint32 GlyphIndex;
|
||||||
sint Size;
|
sint Size;
|
||||||
bool Embolden;
|
bool Embolden;
|
||||||
bool Oblique;
|
bool Oblique;
|
||||||
|
CFontGenerator *FontGenerator;
|
||||||
|
|
||||||
|
SGlyphInfo()
|
||||||
|
: CacheVersion(0),
|
||||||
|
U0(0.f), V0(0.f), U1(0.f), V1(0.f),
|
||||||
|
X(0), Y(0), W(0), H(0), CharWidth(0), CharHeight(0),
|
||||||
|
GlyphIndex(0), Size(0), Embolden(false), Oblique(false), FontGenerator(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// The less recently used infos
|
// Holds info for glyphs displayed on screen
|
||||||
SLetterInfo *Next, *Prev;
|
struct SLetterInfo
|
||||||
|
{
|
||||||
|
ucchar Char;
|
||||||
|
sint Size;
|
||||||
|
bool Embolden;
|
||||||
|
bool Oblique;
|
||||||
|
CFontGenerator *FontGenerator;
|
||||||
|
|
||||||
uint Cat; // 8x8, 16x16, 24x24, 32x32
|
uint32 GlyphIndex;
|
||||||
|
uint32 CharWidth; // Displayed glyph height
|
||||||
//////////////////////////////////////////////////////////////////////
|
uint32 CharHeight; // Displayed glyph height
|
||||||
|
|
||||||
float U ,V;
|
|
||||||
uint32 CharWidth;
|
|
||||||
uint32 CharHeight;
|
|
||||||
uint32 GlyphIndex; // number of the character in the this font
|
|
||||||
sint32 Top; // Distance between origin and top of the texture
|
sint32 Top; // Distance between origin and top of the texture
|
||||||
sint32 Left; // Distance between origin and left of the texture
|
sint32 Left; // Distance between origin and left of the texture
|
||||||
sint32 AdvX; // Advance to the next caracter
|
sint32 AdvX; // Advance to the next caracter
|
||||||
|
|
||||||
SLetterInfo():Char(0), FontGenerator(NULL), Size(0), Embolden(false), Oblique(false), Next(NULL), Prev(NULL), Cat(0), CharWidth(0), CharHeight(0), GlyphIndex(0), Top(0), Left(0), AdvX(0)
|
SGlyphInfo* glyph;
|
||||||
|
|
||||||
|
SLetterInfo()
|
||||||
|
: Char(0), Size(0), Embolden(false), Oblique(false), FontGenerator(NULL),
|
||||||
|
GlyphIndex(0), CharWidth(0), CharHeight(0), Top(0), Left(0), AdvX(0),
|
||||||
|
glyph(NULL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -70,14 +95,13 @@ public:
|
||||||
struct SLetterKey
|
struct SLetterKey
|
||||||
{
|
{
|
||||||
ucchar Char;
|
ucchar Char;
|
||||||
CFontGenerator *FontGenerator;
|
|
||||||
sint Size;
|
sint Size;
|
||||||
bool Embolden;
|
bool Embolden;
|
||||||
bool Oblique;
|
bool Oblique;
|
||||||
|
CFontGenerator *FontGenerator;
|
||||||
|
|
||||||
|
// Does not use FontGenerator in return value
|
||||||
uint32 getVal();
|
uint32 getVal();
|
||||||
//bool operator < (const SLetterKey&k) const;
|
|
||||||
//bool operator == (const SLetterKey&k) const;
|
|
||||||
|
|
||||||
SLetterKey():Char(0), FontGenerator(NULL), Size(0), Embolden(false), Oblique(false)
|
SLetterKey():Char(0), FontGenerator(NULL), Size(0), Embolden(false), Oblique(false)
|
||||||
{
|
{
|
||||||
|
@ -96,19 +120,76 @@ public:
|
||||||
void doGenerate (bool async = false);
|
void doGenerate (bool async = false);
|
||||||
|
|
||||||
// This function manage the cache if the letter wanted does not exist
|
// This function manage the cache if the letter wanted does not exist
|
||||||
SLetterInfo* getLetterInfo (SLetterKey& k);
|
// \param render Set to true if letter is currently visible on screen
|
||||||
|
SLetterInfo* getLetterInfo (SLetterKey& k, bool render);
|
||||||
|
|
||||||
void dumpTextureFont (const char *filename);
|
void dumpTextureFont (const char *filename);
|
||||||
|
|
||||||
|
// Version is increased with each rebuild of font atlas
|
||||||
|
uint32 getCacheVersion() const { return _CacheVersion; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
uint32 _CacheVersion;
|
||||||
|
|
||||||
|
// current texture size
|
||||||
|
uint32 _TextureSizeX;
|
||||||
|
uint32 _TextureSizeY;
|
||||||
|
|
||||||
|
// maximum texture size allowed
|
||||||
|
uint32 _TextureMaxW;
|
||||||
|
uint32 _TextureMaxH;
|
||||||
|
|
||||||
|
// padding around glyphs
|
||||||
|
uint8 _PaddingL, _PaddingT;
|
||||||
|
uint8 _PaddingR, _PaddingB;
|
||||||
|
|
||||||
// To find a letter in the texture
|
// To find a letter in the texture
|
||||||
std::map<uint32, SLetterInfo*> Accel;
|
|
||||||
|
|
||||||
std::vector<SLetterInfo> Letters[TEXTUREFONT_NBCATEGORY];
|
// Keep track of available space in main texture
|
||||||
SLetterInfo *Front[TEXTUREFONT_NBCATEGORY], *Back[TEXTUREFONT_NBCATEGORY];
|
std::vector<NLMISC::CRect> _AtlasNodes;
|
||||||
|
|
||||||
void rebuildLetter (sint cat, sint x, sint y);
|
std::vector <SLetterInfo> _Letters;
|
||||||
|
|
||||||
|
// lookup letter from letter cache or create new
|
||||||
|
SLetterInfo* findLetter(SLetterKey& k, bool insert);
|
||||||
|
|
||||||
|
// lower/upper bound of glyphs to render, sizes outside are scaled bitmaps
|
||||||
|
uint _MinGlyphSize;
|
||||||
|
uint _MaxGlyphSize;
|
||||||
|
// start using size stem from this font size
|
||||||
|
uint _GlyphSizeStepMin;
|
||||||
|
// every n'th font size is rendered, intermediates are using bitmap scaling
|
||||||
|
uint _GlyphSizeStep;
|
||||||
|
|
||||||
|
// rendered glyph cache
|
||||||
|
std::list<SGlyphInfo> _GlyphCache;
|
||||||
|
SGlyphInfo* findLetterGlyph(SLetterInfo *letter, bool insert);
|
||||||
|
|
||||||
|
// render letter glyph into glyph cache
|
||||||
|
SGlyphInfo* renderLetterGlyph(SLetterInfo *letter, uint32 bitmapFontSize);
|
||||||
|
|
||||||
|
// copy glyph bitmap into texture and invalidate that region
|
||||||
|
void copyGlyphBitmap(uint8* bitmap, uint32 bitmapW, uint32 bitmapH, uint32 atlasX, uint32 atlasY);
|
||||||
|
|
||||||
|
// Find best fit for WxH rect in atlas
|
||||||
|
uint fitRegion(uint index, uint width, uint height);
|
||||||
|
|
||||||
|
// Return top/left from font texture or false if there is no more room
|
||||||
|
bool reserveAtlas(const uint32 width, const uint32 height, uint32 &x, uint32 &y);
|
||||||
|
|
||||||
|
// repack glyphs, resize texture, and invalidate unused glyphs.
|
||||||
|
void repackAtlas();
|
||||||
|
void repackAtlas(uint32 width, uint32 height);
|
||||||
|
|
||||||
|
// resize texture,
|
||||||
|
bool resizeAtlas();
|
||||||
|
|
||||||
|
// remove all glyphs from atlas, clear glyph cache, letter info is kept
|
||||||
|
void clearAtlas();
|
||||||
|
|
||||||
|
// if return true: newW, newH contains next size font atlas should be resized
|
||||||
|
// if return false: _TextureMaxW and _TextureMaxH is reached
|
||||||
|
bool getNextTextureSize(uint32 &newW, uint32 &newH) const;
|
||||||
|
|
||||||
/// Todo: serialize a font texture.
|
/// Todo: serialize a font texture.
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -208,6 +208,7 @@ namespace NLGUI
|
||||||
HTML_ATTR(P,QUICK_HELP_EVENTS),
|
HTML_ATTR(P,QUICK_HELP_EVENTS),
|
||||||
HTML_ATTR(P,QUICK_HELP_LINK),
|
HTML_ATTR(P,QUICK_HELP_LINK),
|
||||||
HTML_ATTR(P,NAME),
|
HTML_ATTR(P,NAME),
|
||||||
|
HTML_ATTR(P,STYLE),
|
||||||
};
|
};
|
||||||
|
|
||||||
enum
|
enum
|
||||||
|
|
108
code/nel/include/nel/sound/audio_decoder_ffmpeg.h
Normal file
108
code/nel/include/nel/sound/audio_decoder_ffmpeg.h
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
||||||
|
// Copyright (C) 2018 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/>.
|
||||||
|
|
||||||
|
#ifndef NLSOUND_AUDIO_DECODER_FFMPEG_H
|
||||||
|
#define NLSOUND_AUDIO_DECODER_FFMPEG_H
|
||||||
|
#include <nel/misc/types_nl.h>
|
||||||
|
|
||||||
|
#include <nel/sound/audio_decoder.h>
|
||||||
|
|
||||||
|
struct AVCodecContext;
|
||||||
|
struct AVFormatContext;
|
||||||
|
struct AVIOContext;
|
||||||
|
struct AVPacket;
|
||||||
|
struct SwrContext;
|
||||||
|
|
||||||
|
namespace NLSOUND {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief CAudioDecoderFfmpeg
|
||||||
|
* \date 2018-10-21 08:08GMT
|
||||||
|
* \author Meelis Mägi (Nimetu)
|
||||||
|
* CAudioDecoderFfmpeg
|
||||||
|
* Create trough IAudioDecoder
|
||||||
|
*/
|
||||||
|
class CAudioDecoderFfmpeg : public IAudioDecoder
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
NLMISC::IStream *_Stream;
|
||||||
|
|
||||||
|
bool _IsSupported;
|
||||||
|
bool _Loop;
|
||||||
|
bool _IsMusicEnded;
|
||||||
|
sint32 _StreamOffset;
|
||||||
|
sint32 _StreamSize;
|
||||||
|
|
||||||
|
AVIOContext *_AvioContext;
|
||||||
|
AVFormatContext *_FormatContext;
|
||||||
|
AVCodecContext *_AudioContext;
|
||||||
|
SwrContext *_SwrContext;
|
||||||
|
|
||||||
|
// selected stream
|
||||||
|
sint32 _AudioStreamIndex;
|
||||||
|
|
||||||
|
// output buffer for decoded frame
|
||||||
|
SwrContext *_ConvertContext;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// called from constructor if ffmpeg fails to initialize
|
||||||
|
// or from destructor to cleanup ffmpeg pointers
|
||||||
|
void release();
|
||||||
|
|
||||||
|
public:
|
||||||
|
CAudioDecoderFfmpeg(NLMISC::IStream *stream, bool loop);
|
||||||
|
virtual ~CAudioDecoderFfmpeg();
|
||||||
|
|
||||||
|
inline NLMISC::IStream *getStream() { return _Stream; }
|
||||||
|
inline sint32 getStreamSize() { return _StreamSize; }
|
||||||
|
inline sint32 getStreamOffset() { return _StreamOffset; }
|
||||||
|
|
||||||
|
// Return true if ffmpeg is able to decode the stream
|
||||||
|
bool isFormatSupported() const;
|
||||||
|
|
||||||
|
/// Get information on a music file (only artist and title at the moment).
|
||||||
|
static bool getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title, float &length);
|
||||||
|
|
||||||
|
/// Get how many bytes the music buffer requires for output minimum.
|
||||||
|
virtual uint32 getRequiredBytes();
|
||||||
|
|
||||||
|
/// Get an amount of bytes between minimum and maximum (can be lower than minimum if at end).
|
||||||
|
virtual uint32 getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum);
|
||||||
|
|
||||||
|
/// Get the amount of channels (2 is stereo) in output.
|
||||||
|
virtual uint8 getChannels();
|
||||||
|
|
||||||
|
/// Get the samples per second (often 44100) in output.
|
||||||
|
virtual uint getSamplesPerSec();
|
||||||
|
|
||||||
|
/// Get the bits per sample (often 16) in output.
|
||||||
|
virtual uint8 getBitsPerSample();
|
||||||
|
|
||||||
|
/// Get if the music has ended playing (never true if loop).
|
||||||
|
virtual bool isMusicEnded();
|
||||||
|
|
||||||
|
/// Get the total time in seconds.
|
||||||
|
virtual float getLength();
|
||||||
|
|
||||||
|
/// Set looping
|
||||||
|
virtual void setLooping(bool loop);
|
||||||
|
}; /* class CAudioDecoderFfmpeg */
|
||||||
|
|
||||||
|
} /* namespace NLSOUND */
|
||||||
|
|
||||||
|
#endif // NLSOUND_AUDIO_DECODER_FFMPEG_H
|
||||||
|
|
||||||
|
/* end of file */
|
|
@ -100,6 +100,7 @@ private:
|
||||||
IAudioDecoder *m_AudioDecoder;
|
IAudioDecoder *m_AudioDecoder;
|
||||||
|
|
||||||
bool m_Paused;
|
bool m_Paused;
|
||||||
|
bool m_DecodingEnded;
|
||||||
|
|
||||||
}; /* class CStreamFileSource */
|
}; /* class CStreamFileSource */
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,11 @@ const char *CFontGenerator::getFT2Error(FT_Error fte)
|
||||||
return ukn;
|
return ukn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string CFontGenerator::getFontFileName() const
|
||||||
|
{
|
||||||
|
return _FontFileName;
|
||||||
|
}
|
||||||
|
|
||||||
CFontGenerator *newCFontGenerator(const std::string &fontFileName)
|
CFontGenerator *newCFontGenerator(const std::string &fontFileName)
|
||||||
{
|
{
|
||||||
return new CFontGenerator(fontFileName);
|
return new CFontGenerator(fontFileName);
|
||||||
|
|
|
@ -46,6 +46,7 @@ CMaterial* CFontManager::getFontMaterial()
|
||||||
if (_TexFont == NULL)
|
if (_TexFont == NULL)
|
||||||
{
|
{
|
||||||
_TexFont = new CTextureFont;
|
_TexFont = new CTextureFont;
|
||||||
|
_TexCacheNr++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_MatFont == NULL)
|
if (_MatFont == NULL)
|
||||||
|
@ -142,11 +143,17 @@ void CFontManager::computeString (const ucstring &s,
|
||||||
sint32 nMaxZ = -1000000, nMinZ = 1000000;
|
sint32 nMaxZ = -1000000, nMinZ = 1000000;
|
||||||
output.StringHeight = 0;
|
output.StringHeight = 0;
|
||||||
|
|
||||||
|
// save string info for later rebuild as needed
|
||||||
|
output.Text = s;
|
||||||
|
output.CacheVersion = getCacheVersion();
|
||||||
|
|
||||||
uint j = 0;
|
uint j = 0;
|
||||||
{
|
{
|
||||||
CVertexBufferReadWrite vba;
|
CVertexBufferReadWrite vba;
|
||||||
output.Vertices.lock (vba);
|
output.Vertices.lock (vba);
|
||||||
|
|
||||||
|
hlfPixScrW = 0.f;
|
||||||
|
hlfPixScrH = 0.f;
|
||||||
|
|
||||||
// For all chars
|
// For all chars
|
||||||
for (uint i = 0; i < s.size(); i++)
|
for (uint i = 0; i < s.size(); i++)
|
||||||
|
@ -157,38 +164,43 @@ void CFontManager::computeString (const ucstring &s,
|
||||||
k.Size = fontSize;
|
k.Size = fontSize;
|
||||||
k.Embolden = embolden;
|
k.Embolden = embolden;
|
||||||
k.Oblique = oblique;
|
k.Oblique = oblique;
|
||||||
CTextureFont::SLetterInfo *pLI = pTexFont->getLetterInfo (k);
|
// render letter
|
||||||
|
CTextureFont::SLetterInfo *pLI = pTexFont->getLetterInfo (k, true);
|
||||||
if(pLI != NULL)
|
if(pLI != NULL)
|
||||||
{
|
{
|
||||||
if ((pLI->CharWidth > 0) && (pLI->CharHeight > 0))
|
if (pLI->glyph)
|
||||||
{
|
{
|
||||||
|
// If letter is heavily upscaled, then there is noticeable clipping on edges
|
||||||
|
// fixing UV will make it bit better
|
||||||
|
if ((pLI->Size >> 1) > pLI->glyph->Size)
|
||||||
|
{
|
||||||
|
hlfPixTexW = 0.5f * TexRatioW;
|
||||||
|
hlfPixTexH = 0.5f * TexRatioH;
|
||||||
|
}
|
||||||
|
|
||||||
// Creating vertices
|
// Creating vertices
|
||||||
dx = pLI->Left;
|
dx = pLI->Left;
|
||||||
dz = -((sint32)pLI->CharHeight-(sint32)(pLI->Top));
|
dz = -((sint32)pLI->CharHeight - (sint32)(pLI->Top));
|
||||||
u1 = pLI->U - hlfPixTexW;
|
|
||||||
v1 = pLI->V - hlfPixTexH;
|
|
||||||
u2 = pLI->U + ((float)pLI->CharWidth) * TexRatioW + hlfPixTexW;
|
|
||||||
v2 = pLI->V + ((float)pLI->CharHeight) * TexRatioH + hlfPixTexH;
|
|
||||||
|
|
||||||
x1 = (penx + dx) - hlfPixScrW;
|
x1 = (penx + dx) - hlfPixScrW;
|
||||||
z1 = (penz + dz) - hlfPixScrH;
|
z1 = (penz + dz) - hlfPixScrH;
|
||||||
x2 = (penx + dx + (sint32)pLI->CharWidth) + hlfPixScrW;
|
x2 = (penx + dx + (sint32)pLI->CharWidth) + hlfPixScrW;
|
||||||
z2 = (penz + dz + (sint32)pLI->CharHeight) + hlfPixScrH;
|
z2 = (penz + dz + (sint32)pLI->CharHeight) + hlfPixScrH;
|
||||||
|
|
||||||
vba.setVertexCoord (j, x1, 0, z1);
|
vba.setVertexCoord (j, x1, 0, z1);
|
||||||
vba.setTexCoord (j, 0, u1, v2);
|
vba.setTexCoord (j, 0, pLI->glyph->U0-hlfPixTexW, pLI->glyph->V1+hlfPixTexH);
|
||||||
++j;
|
++j;
|
||||||
|
|
||||||
vba.setVertexCoord (j, x2, 0, z1);
|
vba.setVertexCoord (j, x2, 0, z1);
|
||||||
vba.setTexCoord (j, 0, u2, v2);
|
vba.setTexCoord (j, 0, pLI->glyph->U1+hlfPixTexW, pLI->glyph->V1+hlfPixTexH);
|
||||||
++j;
|
++j;
|
||||||
|
|
||||||
vba.setVertexCoord (j, x2, 0, z2);
|
vba.setVertexCoord (j, x2, 0, z2);
|
||||||
vba.setTexCoord (j, 0, u2, v1);
|
vba.setTexCoord (j, 0, pLI->glyph->U1+hlfPixTexW, pLI->glyph->V0-hlfPixTexH);
|
||||||
++j;
|
++j;
|
||||||
|
|
||||||
vba.setVertexCoord (j, x1, 0, z2);
|
vba.setVertexCoord (j, x1, 0, z2);
|
||||||
vba.setTexCoord (j, 0, u1, v1);
|
vba.setTexCoord (j, 0, pLI->glyph->U0-hlfPixTexW, pLI->glyph->V0-hlfPixTexH);
|
||||||
++j;
|
++j;
|
||||||
|
|
||||||
// String Bound
|
// String Bound
|
||||||
|
@ -245,6 +257,19 @@ void CFontManager::computeStringInfo ( const ucstring &s,
|
||||||
{
|
{
|
||||||
output.Color = color;
|
output.Color = color;
|
||||||
|
|
||||||
|
// save string info for later rebuild as needed
|
||||||
|
output.Text = s;
|
||||||
|
output.CacheVersion = 0;
|
||||||
|
|
||||||
|
if (s.empty())
|
||||||
|
{
|
||||||
|
output.StringWidth = 0.f;
|
||||||
|
output.StringHeight = 0;
|
||||||
|
output.StringLine = 0;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// resize fontSize if window not of 800x600.
|
// resize fontSize if window not of 800x600.
|
||||||
if (keep800x600Ratio)
|
if (keep800x600Ratio)
|
||||||
{
|
{
|
||||||
|
@ -273,7 +298,7 @@ void CFontManager::computeStringInfo ( const ucstring &s,
|
||||||
k.Size = fontSize;
|
k.Size = fontSize;
|
||||||
k.Embolden = embolden;
|
k.Embolden = embolden;
|
||||||
k.Oblique = oblique;
|
k.Oblique = oblique;
|
||||||
pLI = pTexFont->getLetterInfo (k);
|
pLI = pTexFont->getLetterInfo (k, false);
|
||||||
if(pLI != NULL)
|
if(pLI != NULL)
|
||||||
{
|
{
|
||||||
if ((pLI->CharWidth > 0) && (pLI->CharHeight > 0))
|
if ((pLI->CharWidth > 0) && (pLI->CharHeight > 0))
|
||||||
|
@ -318,7 +343,11 @@ void CFontManager::invalidate()
|
||||||
{
|
{
|
||||||
if (_TexFont)
|
if (_TexFont)
|
||||||
_TexFont = NULL;
|
_TexFont = NULL;
|
||||||
|
|
||||||
_TexFont = new CTextureFont;
|
_TexFont = new CTextureFont;
|
||||||
|
_TexCacheNr++;
|
||||||
|
|
||||||
|
getFontMaterial()->setTexture(0, _TexFont);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -74,25 +74,9 @@ uint32 CTextContext::textPush (const char *format, ...)
|
||||||
char *str;
|
char *str;
|
||||||
NLMISC_CONVERT_VARGS (str, format, NLMISC::MaxCStringSize);
|
NLMISC_CONVERT_VARGS (str, format, NLMISC::MaxCStringSize);
|
||||||
|
|
||||||
if (_CacheNbFreePlaces == 0)
|
ucstring uc;
|
||||||
{
|
uc.fromUtf8((const char *)str);
|
||||||
CComputedString csTmp;
|
return textPush(uc);
|
||||||
|
|
||||||
_CacheStrings.push_back (csTmp);
|
|
||||||
if (_CacheFreePlaces.empty())
|
|
||||||
_CacheFreePlaces.resize (1);
|
|
||||||
_CacheFreePlaces[0] = (uint32)_CacheStrings.size()-1;
|
|
||||||
_CacheNbFreePlaces = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute the string.
|
|
||||||
uint32 index = _CacheFreePlaces[_CacheNbFreePlaces-1];
|
|
||||||
CComputedString &strToFill = _CacheStrings[index];
|
|
||||||
_FontManager->computeString (str, _FontGen, _Color, _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio);
|
|
||||||
|
|
||||||
_CacheNbFreePlaces--;
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
@ -115,8 +99,10 @@ uint32 CTextContext::textPush (const ucstring &str)
|
||||||
uint32 index = _CacheFreePlaces[_CacheNbFreePlaces-1];
|
uint32 index = _CacheFreePlaces[_CacheNbFreePlaces-1];
|
||||||
nlassert (index < _CacheStrings.size());
|
nlassert (index < _CacheStrings.size());
|
||||||
CComputedString &strToFill = _CacheStrings[index];
|
CComputedString &strToFill = _CacheStrings[index];
|
||||||
_FontManager->computeString (str, _FontGen, _Color
|
|
||||||
, _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio);
|
_FontManager->computeString (str, _FontGen, _Color, _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio);
|
||||||
|
// just compute letters, glyphs are rendered on demand before first draw
|
||||||
|
//_FontManager->computeStringInfo(str, _FontGen, _Color, _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio);
|
||||||
|
|
||||||
_CacheNbFreePlaces--;
|
_CacheNbFreePlaces--;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
#include "nel/misc/common.h"
|
#include "nel/misc/common.h"
|
||||||
#include "nel/misc/rect.h"
|
#include "nel/misc/rect.h"
|
||||||
#include "nel/misc/file.h"
|
#include "nel/misc/file.h"
|
||||||
|
#include "nel/misc/path.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace NLMISC;
|
using namespace NLMISC;
|
||||||
|
@ -35,37 +35,14 @@ using namespace NLMISC;
|
||||||
namespace NL3D
|
namespace NL3D
|
||||||
{
|
{
|
||||||
|
|
||||||
// Config 1
|
|
||||||
const int TextureSizeX = 1024;
|
|
||||||
const int TextureSizeY = 1024; // If change this value -> change NbLine too
|
|
||||||
const int Categories[TEXTUREFONT_NBCATEGORY] = { 8, 16, 24, 32, 64 };
|
|
||||||
const int NbLine[TEXTUREFONT_NBCATEGORY] = { 8, 24, 16, 4, 1 }; // Based on textsize
|
|
||||||
|
|
||||||
/*
|
|
||||||
const int TextureSizeX = 256;
|
|
||||||
const int TextureSizeY = 256;
|
|
||||||
const int Categories[TEXTUREFONT_NBCATEGORY] = { 8, 16, 24, 32 };
|
|
||||||
const int NbLine[TEXTUREFONT_NBCATEGORY] = { 4, 6, 4, 1 }; // Based on textsize
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
inline uint32 CTextureFont::SLetterKey::getVal()
|
|
||||||
{
|
|
||||||
// this limits Size to 6bits
|
|
||||||
// Large sizes already render wrong when many
|
|
||||||
// different glyphs are used due to limited texture atlas
|
|
||||||
uint8 eb = ((uint)Embolden) + ((uint)Oblique << 1);
|
|
||||||
if (FontGenerator == NULL)
|
|
||||||
return Char + ((Size&255)<<16) + (eb << 22);
|
|
||||||
else
|
|
||||||
return Char + ((Size&255)<<16) + (eb << 22) + ((FontGenerator->getUID()&0xFF)<<24);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
CTextureFont::CTextureFont()
|
CTextureFont::CTextureFont()
|
||||||
|
: _CacheVersion(1),
|
||||||
|
_TextureSizeX(512), _TextureSizeY(512), _TextureMaxW(4096), _TextureMaxH(4096),
|
||||||
|
_PaddingL(0), _PaddingT(0), _PaddingR(1), _PaddingB(1),
|
||||||
|
_MinGlyphSize(5), _MaxGlyphSize(200),
|
||||||
|
_GlyphSizeStepMin(50), _GlyphSizeStep(5)
|
||||||
{
|
{
|
||||||
uint i;
|
|
||||||
|
|
||||||
setFilterMode (ITexture::Linear, ITexture::LinearMipMapOff);
|
setFilterMode (ITexture::Linear, ITexture::LinearMipMapOff);
|
||||||
|
|
||||||
setWrapS (ITexture::Repeat);
|
setWrapS (ITexture::Repeat);
|
||||||
|
@ -75,53 +52,9 @@ CTextureFont::CTextureFont()
|
||||||
|
|
||||||
setReleasable (false);
|
setReleasable (false);
|
||||||
|
|
||||||
resize (TextureSizeX, TextureSizeY, CBitmap::Alpha);
|
resize (_TextureSizeX, _TextureSizeY, CBitmap::Alpha, true);
|
||||||
for(i = 0; i < TextureSizeX*TextureSizeY; ++i)
|
|
||||||
getPixels()[i] = 0;
|
|
||||||
// convertToType (CBitmap::Alpha);
|
|
||||||
|
|
||||||
sint posY = 0;
|
_AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY));
|
||||||
|
|
||||||
for(i = 0; i < TEXTUREFONT_NBCATEGORY; ++i)
|
|
||||||
{
|
|
||||||
// Number of chars per cache
|
|
||||||
Letters[i].resize ((TextureSizeX/Categories[i])*NbLine[i]);
|
|
||||||
|
|
||||||
for(uint32 j = 0; j < Letters[i].size(); ++j)
|
|
||||||
{
|
|
||||||
SLetterInfo &rLetter = Letters[i][j];
|
|
||||||
rLetter.Char = 0xffff;
|
|
||||||
rLetter.FontGenerator = NULL;
|
|
||||||
rLetter.Size= 0;
|
|
||||||
rLetter.Embolden = false;
|
|
||||||
rLetter.Oblique = false;
|
|
||||||
|
|
||||||
// The less recently used infos
|
|
||||||
if (j < Letters[i].size()-1)
|
|
||||||
rLetter.Next = &Letters[i][j+1];
|
|
||||||
else
|
|
||||||
rLetter.Next = NULL;
|
|
||||||
|
|
||||||
if (j > 0)
|
|
||||||
rLetter.Prev = &Letters[i][j-1];
|
|
||||||
else
|
|
||||||
rLetter.Prev = NULL;
|
|
||||||
|
|
||||||
rLetter.Cat = i;
|
|
||||||
|
|
||||||
sint sizeX = TextureSizeX/Categories[i];
|
|
||||||
rLetter.U = (Categories[i]*(j%sizeX)) / ((float)TextureSizeX);
|
|
||||||
rLetter.V = (posY + Categories[i]*((sint)(j/sizeX))) / ((float)TextureSizeY);
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
|
|
||||||
rLetter.CharWidth = rLetter.CharHeight = 0;
|
|
||||||
rLetter.GlyphIndex = rLetter.Top = rLetter.Left = rLetter.AdvX = 0;
|
|
||||||
}
|
|
||||||
Front[i] = &Letters[i][0];
|
|
||||||
Back[i] = &Letters[i][Letters[i].size()-1];
|
|
||||||
posY += NbLine[i] * Categories[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,17 +62,16 @@ CTextureFont::~CTextureFont()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
void CTextureFont::dumpTextureFont(const char *filename)
|
void CTextureFont::dumpTextureFont(const char *filename)
|
||||||
{
|
{
|
||||||
CBitmap b;
|
CBitmap b;
|
||||||
COFile f( filename );
|
COFile f( filename );
|
||||||
b.resize (TextureSizeX, TextureSizeY, CBitmap::RGBA);
|
b.resize (_TextureSizeX, _TextureSizeY, CBitmap::RGBA);
|
||||||
CObjectVector<uint8>&bits = b.getPixels();
|
CObjectVector<uint8>&bits = b.getPixels();
|
||||||
CObjectVector<uint8>&src = getPixels();
|
CObjectVector<uint8>&src = getPixels();
|
||||||
|
|
||||||
for (uint i = 0; i < (TextureSizeX*TextureSizeY); ++i)
|
for (uint i = 0; i < (_TextureSizeX*_TextureSizeY); ++i)
|
||||||
{
|
{
|
||||||
bits[i*4+0] = bits[i*4+1] = bits[i*4+2] = bits[i*4+3] = src[i];
|
bits[i*4+0] = bits[i*4+1] = bits[i*4+2] = bits[i*4+3] = src[i];
|
||||||
}
|
}
|
||||||
|
@ -147,242 +79,468 @@ void CTextureFont::dumpTextureFont(const char *filename)
|
||||||
b.writeTGA (f, 32);
|
b.writeTGA (f, 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
bool CTextureFont::getNextTextureSize(uint32 &newW, uint32 &newH) const
|
||||||
|
{
|
||||||
|
// width will be resized first (256x256 -> 512x256)
|
||||||
|
if (_TextureSizeX <= _TextureSizeY)
|
||||||
|
{
|
||||||
|
newW = _TextureSizeX * 2;
|
||||||
|
newH = _TextureSizeY;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newW = _TextureSizeX;
|
||||||
|
newH = _TextureSizeY * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no more room
|
||||||
|
return newW <= _TextureMaxW && newH <= _TextureMaxH;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// cat : categories where the letter is
|
// out of room, clear everything and rebuild glyphs on demand
|
||||||
// x : pos x of the letter
|
// note: text will display wrong until glyphs get rendered again
|
||||||
// y : pos y of the letter
|
void CTextureFont::clearAtlas()
|
||||||
void CTextureFont::rebuildLetter (sint cat, sint x, sint y)
|
|
||||||
{
|
{
|
||||||
sint sizex = TextureSizeX / Categories[cat];
|
nlwarning("Glyph cache will be cleared.");
|
||||||
sint index = x + y*sizex;
|
|
||||||
SLetterInfo &rLetter = Letters[cat][index];
|
|
||||||
|
|
||||||
if (rLetter.FontGenerator == NULL)
|
_AtlasNodes.clear();
|
||||||
return;
|
_AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY));
|
||||||
|
|
||||||
sint catTopY = 0;
|
// clear texture
|
||||||
sint c = 0;
|
_Data[0].fill(0);
|
||||||
while (c < cat)
|
|
||||||
|
// clear glyph cache
|
||||||
|
for(uint i = 0; i< _Letters.size(); ++i)
|
||||||
{
|
{
|
||||||
catTopY += NbLine[c] * Categories[c];
|
_Letters[i].glyph = NULL;
|
||||||
++c;
|
|
||||||
}
|
}
|
||||||
// Destination position in pixel of the letter
|
_GlyphCache.clear();
|
||||||
sint posx = x * Categories[cat];
|
|
||||||
sint posy = catTopY + y * Categories[cat];
|
|
||||||
|
|
||||||
uint32 pitch = 0;
|
_CacheVersion++;
|
||||||
uint8 *bitmap = rLetter.FontGenerator->getBitmap ( rLetter.Char, rLetter.Size, rLetter.Embolden, rLetter.Oblique,
|
|
||||||
rLetter.CharWidth, rLetter.CharHeight,
|
|
||||||
pitch, rLetter.Left, rLetter.Top,
|
|
||||||
rLetter.AdvX, rLetter.GlyphIndex );
|
|
||||||
|
|
||||||
// Copy FreeType buffer
|
touch();
|
||||||
uint i;
|
}
|
||||||
for (i = 0; i < rLetter.CharHeight; ++i)
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void CTextureFont::repackAtlas()
|
||||||
|
{
|
||||||
|
repackAtlas(_TextureSizeX, _TextureSizeY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// backup old glyphs and move them to newly resized texture
|
||||||
|
// new atlas will be sorted if _GlyphCache is
|
||||||
|
void CTextureFont::repackAtlas(uint32 newW, uint32 newH)
|
||||||
|
{
|
||||||
|
uint32 newCacheVersion = _CacheVersion+1;
|
||||||
|
|
||||||
|
CBitmap btm;
|
||||||
|
uint32 oldW, oldH;
|
||||||
|
|
||||||
|
oldW = _TextureSizeX;
|
||||||
|
oldH = _TextureSizeY;
|
||||||
|
btm.resize(oldW, oldH, CBitmap::Alpha, true);
|
||||||
|
btm.blit(this, 0, 0);
|
||||||
|
|
||||||
|
// resize texture
|
||||||
|
if (_TextureSizeX != newW || _TextureSizeY != newH)
|
||||||
{
|
{
|
||||||
uint8 *pDst = &_Data[0][posx + (posy+i)*TextureSizeY];
|
_TextureSizeX = newW;
|
||||||
uint8 *pSrc = &bitmap[i*pitch];
|
_TextureSizeY = newH;
|
||||||
for (uint j = 0; j < rLetter.CharWidth; ++j)
|
resize (_TextureSizeX, _TextureSizeY, CBitmap::Alpha, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_Data[0].fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// release atlas and rebuild
|
||||||
|
_AtlasNodes.clear();
|
||||||
|
_AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY));
|
||||||
|
|
||||||
|
CObjectVector<uint8>&src = btm.getPixels();
|
||||||
|
for(std::list<SGlyphInfo>::iterator it = _GlyphCache.begin(); it != _GlyphCache.end(); ++it)
|
||||||
|
{
|
||||||
|
if (it->CacheVersion != _CacheVersion)
|
||||||
{
|
{
|
||||||
*pDst = *pSrc;
|
// TODO: must remove glyph from all letters before removing glyph from cache
|
||||||
++pDst;
|
//continue;
|
||||||
++pSrc;
|
}
|
||||||
|
|
||||||
|
SGlyphInfo &glyph = *it;
|
||||||
|
|
||||||
|
glyph.CacheVersion = newCacheVersion;
|
||||||
|
|
||||||
|
uint32 atlasX, atlasY;
|
||||||
|
if (reserveAtlas(glyph.W, glyph.H, atlasX, atlasY))
|
||||||
|
{
|
||||||
|
for (uint y = 0; y < glyph.H; ++y)
|
||||||
|
{
|
||||||
|
uint8 *pDst = &_Data[0][(atlasY + y) * _TextureSizeX + atlasX];
|
||||||
|
for (uint x = 0; x < glyph.W; ++x)
|
||||||
|
{
|
||||||
|
*pDst = src[(glyph.Y + y) * oldW + glyph.X + x];
|
||||||
|
++pDst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: dup code with renderGlyph
|
||||||
|
glyph.U0 = (atlasX+_PaddingL) / (float)_TextureSizeX;
|
||||||
|
glyph.V0 = (atlasY+_PaddingT) / (float)_TextureSizeY;
|
||||||
|
glyph.U1 = (atlasX+_PaddingL+glyph.CharWidth) / (float)_TextureSizeX;
|
||||||
|
glyph.V1 = (atlasY+_PaddingT+glyph.CharHeight) / (float)_TextureSizeY;
|
||||||
|
|
||||||
|
glyph.X = atlasX;
|
||||||
|
glyph.Y = atlasY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Black border bottom and right
|
_CacheVersion = newCacheVersion;
|
||||||
for (i = 0; i < rLetter.CharHeight+1; ++i)
|
|
||||||
|
// invalidate full texture
|
||||||
|
touch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
bool CTextureFont::resizeAtlas()
|
||||||
|
{
|
||||||
|
uint32 newW, newH;
|
||||||
|
if (!getNextTextureSize(newW, newH))
|
||||||
{
|
{
|
||||||
_Data[0][posx + rLetter.CharWidth + (posy+i)*TextureSizeY] = 0;
|
nlwarning("Font texture at maximum (%d,%d). Resize failed.", _TextureSizeX, _TextureSizeY);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < rLetter.CharWidth+1; ++i)
|
// resize and redraw
|
||||||
{
|
repackAtlas(newW, newH);
|
||||||
_Data[0][posx + i + (posy+rLetter.CharHeight)*TextureSizeY] = 0;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
dumpTextureFont (this);
|
|
||||||
int a = 5;
|
|
||||||
a++;
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
void CTextureFont::doGenerate(bool async)
|
void CTextureFont::doGenerate(bool async)
|
||||||
{
|
{
|
||||||
// Rectangle invalidate ?
|
/*
|
||||||
if (_ListInvalidRect.begin()!=_ListInvalidRect.end())
|
nlinfo("doGenerate: Letters(%d/%d), Glyphs(%d/%d)\n", _Letters.size(), _Letters.size() * sizeof(SLetterInfo),
|
||||||
{
|
_GlyphCache.size(), _GlyphCache.size() * sizeof(SGlyphInfo));
|
||||||
// Yes, rebuild only those rectangles.
|
//std::string fname = CFile::findNewFile("/tmp/font-texture.tga");
|
||||||
|
std::string fname = toString("/tmp/font-texture-%p-%03d.tga", this, _CacheVersion);
|
||||||
// For each rectangle to compute
|
dumpTextureFont (fname.c_str());
|
||||||
std::list<NLMISC::CRect>::iterator ite=_ListInvalidRect.begin();
|
*/
|
||||||
while (ite!=_ListInvalidRect.end())
|
|
||||||
{
|
|
||||||
// Compute rectangle coordinates
|
|
||||||
sint x = ite->left();
|
|
||||||
sint y = ite->bottom();
|
|
||||||
|
|
||||||
// Look in which category is the rectangle
|
|
||||||
sint cat = 0;
|
|
||||||
sint catTopY = 0;
|
|
||||||
sint catBotY = NbLine[cat] * Categories[cat];
|
|
||||||
while (y > catBotY)
|
|
||||||
{
|
|
||||||
if (y < catBotY)
|
|
||||||
break;
|
|
||||||
++cat;
|
|
||||||
nlassert (cat < TEXTUREFONT_NBCATEGORY);
|
|
||||||
catTopY = catBotY;
|
|
||||||
catBotY += NbLine[cat] * Categories[cat];
|
|
||||||
}
|
|
||||||
|
|
||||||
x = x / Categories[cat];
|
|
||||||
y = ite->top();
|
|
||||||
y = y - catTopY;
|
|
||||||
y = y / Categories[cat];
|
|
||||||
|
|
||||||
rebuildLetter (cat, x, y);
|
|
||||||
|
|
||||||
// Next rectangle
|
|
||||||
ite++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for(int cat = 0; cat < TEXTUREFONT_NBCATEGORY; ++cat)
|
|
||||||
{
|
|
||||||
sint sizex = TextureSizeX / Categories[cat];
|
|
||||||
sint sizey = NbLine[cat];
|
|
||||||
for (sint y = 0; y < sizey; y++)
|
|
||||||
for (sint x = 0; x < sizex; x++)
|
|
||||||
{
|
|
||||||
rebuildLetter (cat, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
dumpTextureFont (this);
|
|
||||||
int a = 5;
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
CTextureFont::SLetterInfo* CTextureFont::getLetterInfo (SLetterKey& k)
|
uint CTextureFont::fitRegion(uint index, uint width, uint height)
|
||||||
{
|
{
|
||||||
sint cat;
|
if (_AtlasNodes[index].X + width > _TextureSizeX - 1)
|
||||||
uint32 nTmp = k.getVal();
|
|
||||||
map<uint32, SLetterInfo*>::iterator itAccel = Accel.find (nTmp);
|
|
||||||
if (itAccel != Accel.end())
|
|
||||||
{
|
{
|
||||||
// Put it in the first place
|
return -1;
|
||||||
SLetterInfo *pLetterToMove = itAccel->second;
|
}
|
||||||
cat = pLetterToMove->Cat;
|
|
||||||
if (pLetterToMove != Front[cat])
|
uint x = _AtlasNodes[index].X;
|
||||||
|
uint y = _AtlasNodes[index].Y;
|
||||||
|
sint widthLeft = width;
|
||||||
|
|
||||||
|
while(widthLeft > 0)
|
||||||
|
{
|
||||||
|
if (_AtlasNodes[index].Y > y)
|
||||||
{
|
{
|
||||||
// unlink
|
y = _AtlasNodes[index].Y;
|
||||||
nlassert(pLetterToMove->Prev);
|
|
||||||
pLetterToMove->Prev->Next = pLetterToMove->Next;
|
|
||||||
if (pLetterToMove == Back[cat])
|
|
||||||
{
|
|
||||||
Back[cat] = pLetterToMove->Prev;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pLetterToMove->Next->Prev = pLetterToMove->Prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
// link to front
|
|
||||||
pLetterToMove->Prev = NULL;
|
|
||||||
pLetterToMove->Next = Front[cat];
|
|
||||||
Front[cat]->Prev = pLetterToMove;
|
|
||||||
Front[cat] = pLetterToMove;
|
|
||||||
}
|
}
|
||||||
return pLetterToMove;
|
|
||||||
|
// _AtlasNodes[0] for margin is not used here
|
||||||
|
if (_AtlasNodes[index].Y + height > _TextureSizeY - 1)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
widthLeft -= _AtlasNodes[index].Width;
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The letter is not already present
|
return y;
|
||||||
// Found the category of the new letter
|
}
|
||||||
uint32 width, height;
|
|
||||||
|
|
||||||
//k.FontGenerator->getSizes (k.Char, k.Size, width, height);
|
bool CTextureFont::reserveAtlas(const uint32 width, const uint32 height, uint32 &x, uint32 &y)
|
||||||
// \todo mat : Temp !!! Try to use freetype cache
|
{
|
||||||
uint32 nPitch, nGlyphIndex;
|
if (_AtlasNodes.empty())
|
||||||
sint32 nLeft, nTop, nAdvX;
|
{
|
||||||
k.FontGenerator->getBitmap (k.Char, k.Size, k.Embolden, k.Oblique, width, height, nPitch, nLeft, nTop,
|
nlwarning("No available space in texture atlas (_AtlasNodes.empty() == true)");
|
||||||
nAdvX, nGlyphIndex );
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Add 1 pixel space for black border to get correct category
|
x = 0;
|
||||||
cat = 0;
|
y = 0;
|
||||||
if (((sint)width+1 > Categories[TEXTUREFONT_NBCATEGORY-1]) ||
|
|
||||||
((sint)height+1 > Categories[TEXTUREFONT_NBCATEGORY-1]))
|
sint bestIndex = -1;
|
||||||
|
sint bestWidth = _TextureSizeX;
|
||||||
|
sint bestHeight = _TextureSizeY;
|
||||||
|
|
||||||
|
sint selY=0;
|
||||||
|
|
||||||
|
for (uint i = 0; i < _AtlasNodes.size(); ++i)
|
||||||
|
{
|
||||||
|
selY = fitRegion(i, width, height);
|
||||||
|
if (selY >=0)
|
||||||
|
{
|
||||||
|
if (((selY + height) < bestHeight) || ((selY + height) == bestHeight && _AtlasNodes[i].Width > 0 && _AtlasNodes[i].Width < bestWidth))
|
||||||
|
{
|
||||||
|
bestHeight = selY + height;
|
||||||
|
bestIndex = i;
|
||||||
|
bestWidth = _AtlasNodes[i].Width;
|
||||||
|
x = _AtlasNodes[i].X;
|
||||||
|
y = selY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestIndex == -1)
|
||||||
|
{
|
||||||
|
x = 0;
|
||||||
|
y = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CRect r(x, y + height, width, 0);
|
||||||
|
_AtlasNodes.insert(_AtlasNodes.begin() + bestIndex, r);
|
||||||
|
|
||||||
|
// shrink or remove nodes overlaping with newly inserted node
|
||||||
|
for(uint i = bestIndex+1; i< _AtlasNodes.size(); i++)
|
||||||
|
{
|
||||||
|
if (_AtlasNodes[i].X < (_AtlasNodes[i-1].X + _AtlasNodes[i-1].Width))
|
||||||
|
{
|
||||||
|
sint shrink = _AtlasNodes[i-1].X + _AtlasNodes[i-1].Width - _AtlasNodes[i].X;
|
||||||
|
_AtlasNodes[i].X += shrink;
|
||||||
|
if (_AtlasNodes[i].Width > shrink)
|
||||||
|
{
|
||||||
|
_AtlasNodes[i].Width -= shrink;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_AtlasNodes.erase(_AtlasNodes.begin() + i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge nearby nodes from same row
|
||||||
|
for(uint i = 0; i < _AtlasNodes.size() - 1; i++)
|
||||||
|
{
|
||||||
|
if (_AtlasNodes[i].Y == _AtlasNodes[i+1].Y)
|
||||||
|
{
|
||||||
|
_AtlasNodes[i].Width += _AtlasNodes[i+1].Width;
|
||||||
|
_AtlasNodes.erase(_AtlasNodes.begin() + i + 1);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// bitmap : texture data
|
||||||
|
// bitmapW : bitmap width
|
||||||
|
// bitmapH : bitmap height
|
||||||
|
// atlasX : pos x in font texture
|
||||||
|
// atlasY : pos y in font texture
|
||||||
|
void CTextureFont::copyGlyphBitmap(uint8* bitmap, uint32 bitmapW, uint32 bitmapH, uint32 atlasX, uint32 atlasY)
|
||||||
|
{
|
||||||
|
for (uint bY = 0; bY < bitmapH; ++bY)
|
||||||
|
{
|
||||||
|
uint8 *pDst = &_Data[0][(atlasY+_PaddingT+bY) * _TextureSizeX+atlasX+_PaddingL];
|
||||||
|
for (uint bX = 0; bX < bitmapW; ++bX)
|
||||||
|
{
|
||||||
|
*pDst = bitmap[bY * bitmapW+bX];
|
||||||
|
++pDst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_PaddingR > 0 || _PaddingB > 0 || _PaddingL > 0 || _PaddingT > 0)
|
||||||
|
{
|
||||||
|
for(uint i = 0; i<(bitmapH+_PaddingT+_PaddingB); ++i)
|
||||||
|
{
|
||||||
|
if (_PaddingT > 0) _Data[0][(atlasY + i) * _TextureSizeX + atlasX ] = 0;
|
||||||
|
if (_PaddingB > 0) _Data[0][(atlasY + i) * _TextureSizeX + atlasX + _PaddingL + bitmapW] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint i = 0; i<(bitmapW+_PaddingL+_PaddingR); ++i)
|
||||||
|
{
|
||||||
|
if (_PaddingL > 0) _Data[0][atlasY * _TextureSizeX + atlasX + i] = 0;
|
||||||
|
if (_PaddingB > 0) _Data[0][(atlasY + _PaddingT + bitmapH) * _TextureSizeX + atlasX + i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRect r(atlasX, atlasY, bitmapW + _PaddingL + _PaddingR, bitmapH + _PaddingT + _PaddingB);
|
||||||
|
touchRect(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
CTextureFont::SGlyphInfo* CTextureFont::renderLetterGlyph(SLetterInfo *letter, uint bitmapFontSize)
|
||||||
|
{
|
||||||
|
uint32 nPitch;
|
||||||
|
sint32 left;
|
||||||
|
sint32 top;
|
||||||
|
sint32 advx;
|
||||||
|
uint32 charWidth;
|
||||||
|
uint32 charHeight;
|
||||||
|
uint32 glyphIndex;
|
||||||
|
|
||||||
|
uint8 *bitmap = letter->FontGenerator->getBitmap (letter->Char, bitmapFontSize, letter->Embolden, letter->Oblique,
|
||||||
|
charWidth, charHeight,
|
||||||
|
nPitch, left, top,
|
||||||
|
advx, glyphIndex );
|
||||||
|
|
||||||
|
uint32 atlasX, atlasY;
|
||||||
|
uint32 rectW, rectH;
|
||||||
|
rectW = charWidth + _PaddingL + _PaddingR;
|
||||||
|
rectH = charHeight + _PaddingT + _PaddingB;
|
||||||
|
|
||||||
|
if (!reserveAtlas(rectW, rectH, atlasX, atlasY))
|
||||||
|
{
|
||||||
|
// no room
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
copyGlyphBitmap(bitmap, charWidth, charHeight, atlasX, atlasY);
|
||||||
|
|
||||||
while (((sint)width+1 > Categories[cat]) || ((sint)height+1 > Categories[cat]))
|
SGlyphInfo* glyphInfo = NULL;
|
||||||
{
|
{
|
||||||
++cat;
|
// keep cache sorted by height (smaller first)
|
||||||
nlassert (cat != TEXTUREFONT_NBCATEGORY);
|
std::list<SGlyphInfo>::iterator it = _GlyphCache.begin();
|
||||||
|
while(it != _GlyphCache.end() && it->CharHeight < charHeight)
|
||||||
|
{
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
it = _GlyphCache.insert(it, SGlyphInfo());
|
||||||
|
glyphInfo = &(*it);
|
||||||
}
|
}
|
||||||
|
|
||||||
// And replace the less recently used letter
|
glyphInfo->GlyphIndex = glyphIndex;
|
||||||
SLetterKey k2;
|
glyphInfo->Size = bitmapFontSize;
|
||||||
k2.Char = Back[cat]->Char;
|
glyphInfo->Embolden = letter->Embolden;
|
||||||
k2.FontGenerator = Back[cat]->FontGenerator;
|
glyphInfo->Oblique = letter->Oblique;
|
||||||
k2.Size = Back[cat]->Size;
|
glyphInfo->FontGenerator = letter->FontGenerator;
|
||||||
k2.Embolden = Back[cat]->Embolden;
|
glyphInfo->CacheVersion = _CacheVersion;
|
||||||
k2.Oblique = Back[cat]->Oblique;
|
|
||||||
|
|
||||||
itAccel = Accel.find (k2.getVal());
|
glyphInfo->U0 = (atlasX+_PaddingL) / (float)_TextureSizeX;
|
||||||
if (itAccel != Accel.end())
|
glyphInfo->V0 = (atlasY+_PaddingT) / (float)_TextureSizeY;
|
||||||
|
glyphInfo->U1 = (atlasX+_PaddingL+charWidth) / (float)_TextureSizeX;
|
||||||
|
glyphInfo->V1 = (atlasY+_PaddingT+charHeight) / (float)_TextureSizeY;
|
||||||
|
|
||||||
|
glyphInfo->CharWidth = charWidth;
|
||||||
|
glyphInfo->CharHeight = charHeight;
|
||||||
|
|
||||||
|
glyphInfo->X = atlasX;
|
||||||
|
glyphInfo->Y = atlasY;
|
||||||
|
glyphInfo->W = rectW;
|
||||||
|
glyphInfo->H = rectH;
|
||||||
|
|
||||||
|
return glyphInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
CTextureFont::SGlyphInfo* CTextureFont::findLetterGlyph(SLetterInfo *letter, bool insert)
|
||||||
|
{
|
||||||
|
uint bitmapFontSize = max((sint)_MinGlyphSize, min((sint)_MaxGlyphSize, letter->Size));
|
||||||
|
if (_GlyphSizeStep > 1 && bitmapFontSize > _GlyphSizeStepMin)
|
||||||
{
|
{
|
||||||
Accel.erase (itAccel);
|
uint size = (bitmapFontSize / _GlyphSizeStep) * _GlyphSizeStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
SLetterInfo *NewBack = Back[cat]->Prev;
|
// CacheVersion not checked, all glyphs in cache must be rendered on texture
|
||||||
NewBack->Next = NULL;
|
for(std::list<SGlyphInfo>::iterator it = _GlyphCache.begin(); it != _GlyphCache.end(); ++it)
|
||||||
Back[cat]->Cat = cat;
|
|
||||||
Back[cat]->Char = k.Char;
|
|
||||||
Back[cat]->FontGenerator = k.FontGenerator;
|
|
||||||
Back[cat]->Size = k.Size;
|
|
||||||
Back[cat]->Embolden = k.Embolden;
|
|
||||||
Back[cat]->Oblique = k.Oblique;
|
|
||||||
Back[cat]->CharWidth = width;
|
|
||||||
Back[cat]->CharHeight = height;
|
|
||||||
Back[cat]->Top = nTop;
|
|
||||||
Back[cat]->Left = nLeft;
|
|
||||||
Back[cat]->AdvX = nAdvX;
|
|
||||||
Back[cat]->Prev = NULL;
|
|
||||||
Back[cat]->Next = Front[cat];
|
|
||||||
Front[cat]->Prev = Back[cat];
|
|
||||||
Front[cat] = Back[cat];
|
|
||||||
Back[cat] = NewBack;
|
|
||||||
|
|
||||||
Accel.insert (map<uint32, SLetterInfo*>::value_type(k.getVal(),Front[cat]));
|
|
||||||
|
|
||||||
// Invalidate the zone
|
|
||||||
sint index = (sint)(Front[cat] - &Letters[cat][0]);// / sizeof (SLetterInfo);
|
|
||||||
sint sizex = TextureSizeX / Categories[cat];
|
|
||||||
sint x = index % sizex;
|
|
||||||
sint y = index / sizex;
|
|
||||||
x = x * Categories[cat];
|
|
||||||
y = y * Categories[cat];
|
|
||||||
|
|
||||||
sint c = 0;
|
|
||||||
while (c < cat)
|
|
||||||
{
|
{
|
||||||
y = y + NbLine[c] * Categories[c];
|
if (it->GlyphIndex == letter->GlyphIndex &&
|
||||||
++c;
|
it->Size == bitmapFontSize &&
|
||||||
|
it->Embolden == letter->Embolden &&
|
||||||
|
it->Oblique == letter->Oblique &&
|
||||||
|
it->FontGenerator == letter->FontGenerator)
|
||||||
|
{
|
||||||
|
return &(*it);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// must update the char, WITH the black borders
|
if (insert)
|
||||||
CRect r (x, y, width+1, height+1);
|
{
|
||||||
|
return renderLetterGlyph(letter, bitmapFontSize);
|
||||||
|
}
|
||||||
|
|
||||||
touchRect (r);
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return Front[cat];
|
// ---------------------------------------------------------------------------
|
||||||
|
CTextureFont::SLetterInfo* CTextureFont::findLetter(SLetterKey &k, bool insert)
|
||||||
|
{
|
||||||
|
// TODO: use std::map<uint64>
|
||||||
|
for(uint i = 0; i < _Letters.size(); ++i)
|
||||||
|
{
|
||||||
|
if (_Letters[i].Char == k.Char && _Letters[i].Size == k.Size &&
|
||||||
|
_Letters[i].Embolden == k.Embolden && _Letters[i].Oblique == k.Oblique &&
|
||||||
|
_Letters[i].FontGenerator == k.FontGenerator)
|
||||||
|
{
|
||||||
|
return &_Letters[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insert)
|
||||||
|
{
|
||||||
|
_Letters.push_back(SLetterInfo());
|
||||||
|
SLetterInfo* letter = &_Letters.back();
|
||||||
|
|
||||||
|
// get metrics for requested size
|
||||||
|
letter->Char = k.Char;
|
||||||
|
letter->Size = k.Size;
|
||||||
|
letter->Embolden = k.Embolden;
|
||||||
|
letter->Oblique = k.Oblique;
|
||||||
|
letter->FontGenerator = k.FontGenerator;
|
||||||
|
|
||||||
|
uint32 nPitch;
|
||||||
|
letter->FontGenerator->getBitmap(letter->Char, letter->Size, letter->Embolden, letter->Oblique,
|
||||||
|
letter->CharWidth, letter->CharHeight,
|
||||||
|
nPitch, letter->Left, letter->Top,
|
||||||
|
letter->AdvX, letter->GlyphIndex );
|
||||||
|
|
||||||
|
return letter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
CTextureFont::SLetterInfo* CTextureFont::getLetterInfo (SLetterKey& k, bool render)
|
||||||
|
{
|
||||||
|
// find already cached letter or create new one
|
||||||
|
SLetterInfo* letter = findLetter(k, true);
|
||||||
|
// letter not found (=NULL) or render not requested
|
||||||
|
if (!letter || !render) return letter;
|
||||||
|
|
||||||
|
if (!letter->glyph || letter->glyph->CacheVersion != _CacheVersion)
|
||||||
|
{
|
||||||
|
// render glyph
|
||||||
|
letter->glyph = findLetterGlyph(letter, true);
|
||||||
|
if (letter->glyph == NULL)
|
||||||
|
{
|
||||||
|
// resize/repack and try again
|
||||||
|
if (!resizeAtlas()) repackAtlas();
|
||||||
|
|
||||||
|
letter->glyph = findLetterGlyph(letter, true);
|
||||||
|
if (letter->glyph == NULL)
|
||||||
|
{
|
||||||
|
// make room by clearing all glyphs and reduce max size for glyphs
|
||||||
|
clearAtlas();
|
||||||
|
if (_MaxGlyphSize > _MinGlyphSize)
|
||||||
|
{
|
||||||
|
_MaxGlyphSize = max(_MinGlyphSize, _MaxGlyphSize - 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
letter->glyph = findLetterGlyph(letter, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return letter;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // NL3D
|
} // NL3D
|
||||||
|
|
|
@ -899,7 +899,7 @@ namespace NLGUI
|
||||||
}
|
}
|
||||||
if (!(_SizeRef & 2))
|
if (!(_SizeRef & 2))
|
||||||
{
|
{
|
||||||
_H= _BmpH;
|
_H= max(_BmpH, _ViewText->getH());
|
||||||
}
|
}
|
||||||
|
|
||||||
CViewBase::updateCoords();
|
CViewBase::updateCoords();
|
||||||
|
|
|
@ -2281,8 +2281,8 @@ namespace NLGUI
|
||||||
{
|
{
|
||||||
newParagraph(PBeginSpace);
|
newParagraph(PBeginSpace);
|
||||||
pushStyle();
|
pushStyle();
|
||||||
if (present[HTML_BLOCK_STYLE] && value[HTML_BLOCK_STYLE])
|
if (present[MY_HTML_P_STYLE] && value[MY_HTML_P_STYLE])
|
||||||
getStyleParams(value[HTML_BLOCK_STYLE], _Style);
|
getStyleParams(value[MY_HTML_P_STYLE], _Style);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case HTML_PRE:
|
case HTML_PRE:
|
||||||
|
|
|
@ -1407,6 +1407,9 @@ namespace NLGUI
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void CInterfaceGroup::checkCoords()
|
void CInterfaceGroup::checkCoords()
|
||||||
{
|
{
|
||||||
|
// Make XReal same as in updateCoords() as some elements (CViewText) depends on it
|
||||||
|
_XReal += _MarginLeft;
|
||||||
|
|
||||||
//update all children elements
|
//update all children elements
|
||||||
vector<CViewBase*>::const_iterator ite;
|
vector<CViewBase*>::const_iterator ite;
|
||||||
for (ite = _EltOrder.begin() ; ite != _EltOrder.end(); ite++)
|
for (ite = _EltOrder.begin() ; ite != _EltOrder.end(); ite++)
|
||||||
|
@ -1415,6 +1418,8 @@ namespace NLGUI
|
||||||
if(pIE->getActive())
|
if(pIE->getActive())
|
||||||
pIE->checkCoords();
|
pIE->checkCoords();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_XReal -= _MarginLeft;
|
||||||
executeLuaScriptOnDraw();
|
executeLuaScriptOnDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -213,6 +213,7 @@ namespace NLGUI
|
||||||
HTML_ATTR(P,QUICK_HELP_EVENTS),
|
HTML_ATTR(P,QUICK_HELP_EVENTS),
|
||||||
HTML_ATTR(P,QUICK_HELP_LINK),
|
HTML_ATTR(P,QUICK_HELP_LINK),
|
||||||
HTML_ATTR(P,NAME),
|
HTML_ATTR(P,NAME),
|
||||||
|
HTML_ATTR(P,STYLE),
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1720,12 +1720,12 @@ static bool openDocWithExtension (const std::string &document, const std::string
|
||||||
const char *previousEnv = getenv("LD_LIBRARY_PATH");
|
const char *previousEnv = getenv("LD_LIBRARY_PATH");
|
||||||
|
|
||||||
// clear LD_LIBRARY_PATH to avoid problems with Steam Runtime
|
// clear LD_LIBRARY_PATH to avoid problems with Steam Runtime
|
||||||
setenv("LD_LIBRARY_PATH", "", 1);
|
if (previousEnv) setenv("LD_LIBRARY_PATH", "", 1);
|
||||||
|
|
||||||
bool res = launchProgram(command, document);
|
bool res = launchProgram(command, document);
|
||||||
|
|
||||||
// restore previous LD_LIBRARY_PATH
|
// restore previous LD_LIBRARY_PATH
|
||||||
setenv("LD_LIBRARY_PATH", previousEnv, 1);
|
if (previousEnv) setenv("LD_LIBRARY_PATH", previousEnv, 1);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
#endif // NL_OS_WINDOWS
|
#endif // NL_OS_WINDOWS
|
||||||
|
|
|
@ -185,7 +185,7 @@ bool CIFile::open(const std::string &path, bool text)
|
||||||
|
|
||||||
// Bigfile or xml pack access requested ?
|
// Bigfile or xml pack access requested ?
|
||||||
string::size_type pos;
|
string::size_type pos;
|
||||||
if ((pos = path.find('@')) != string::npos)
|
if (!CFile::fileExists(path) && (pos = path.find('@')) != string::npos)
|
||||||
{
|
{
|
||||||
// check for a double @ to identify XML pack file
|
// check for a double @ to identify XML pack file
|
||||||
if (pos+1 < path.size() && path[pos+1] == '@')
|
if (pos+1 < path.size() && path[pos+1] == '@')
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
FILE(GLOB SRC *.cpp *.h)
|
FILE(GLOB SRC *.cpp *.h)
|
||||||
FILE(GLOB HEADERS ../../include/nel/sound/*.h)
|
FILE(GLOB HEADERS ../../include/nel/sound/*.h)
|
||||||
|
|
||||||
|
IF(NOT FFMPEG_FOUND)
|
||||||
|
LIST(REMOVE_ITEM SRC ${CMAKE_CURRENT_SOURCE_DIR}/audio_decoder_ffmpeg.cpp)
|
||||||
|
LIST(REMOVE_ITEM HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/../../include/nel/sound/audio_decoder_ffmpeg.h)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
FILE(GLOB ANIMATION
|
FILE(GLOB ANIMATION
|
||||||
sound_anim_manager.cpp ../../include/nel/sound/sound_anim_manager.h
|
sound_anim_manager.cpp ../../include/nel/sound/sound_anim_manager.h
|
||||||
|
@ -58,6 +62,7 @@ FILE(GLOB STREAM
|
||||||
FILE(GLOB STREAM_FILE
|
FILE(GLOB STREAM_FILE
|
||||||
audio_decoder.cpp ../../include/nel/sound/audio_decoder.h
|
audio_decoder.cpp ../../include/nel/sound/audio_decoder.h
|
||||||
audio_decoder_vorbis.cpp ../../include/nel/sound/audio_decoder_vorbis.h
|
audio_decoder_vorbis.cpp ../../include/nel/sound/audio_decoder_vorbis.h
|
||||||
|
audio_decoder_ffmpeg.cpp ../../include/nel/sound/audio_decoder_ffmpeg.h
|
||||||
stream_file_sound.cpp ../../include/nel/sound/stream_file_sound.h
|
stream_file_sound.cpp ../../include/nel/sound/stream_file_sound.h
|
||||||
stream_file_source.cpp ../../include/nel/sound/stream_file_source.h
|
stream_file_source.cpp ../../include/nel/sound/stream_file_source.h
|
||||||
)
|
)
|
||||||
|
@ -95,6 +100,12 @@ IF(WITH_STATIC)
|
||||||
TARGET_LINK_LIBRARIES(nelsound ${OGG_LIBRARY})
|
TARGET_LINK_LIBRARIES(nelsound ${OGG_LIBRARY})
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
|
||||||
|
IF(FFMPEG_FOUND)
|
||||||
|
ADD_DEFINITIONS(-DFFMPEG_ENABLED)
|
||||||
|
INCLUDE_DIRECTORIES(${FFMPEG_INCLUDE_DIRS})
|
||||||
|
TARGET_LINK_LIBRARIES(nelsound ${FFMPEG_LIBRARIES})
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
|
||||||
INCLUDE_DIRECTORIES(${LIBXML2_INCLUDE_DIR})
|
INCLUDE_DIRECTORIES(${LIBXML2_INCLUDE_DIR})
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,10 @@
|
||||||
// Project includes
|
// Project includes
|
||||||
#include <nel/sound/audio_decoder_vorbis.h>
|
#include <nel/sound/audio_decoder_vorbis.h>
|
||||||
|
|
||||||
|
#ifdef FFMPEG_ENABLED
|
||||||
|
#include <nel/sound/audio_decoder_ffmpeg.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace NLMISC;
|
using namespace NLMISC;
|
||||||
|
|
||||||
|
@ -82,6 +86,17 @@ IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &type, NLMISC
|
||||||
nlwarning("Stream is NULL");
|
nlwarning("Stream is NULL");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
#ifdef FFMPEG_ENABLED
|
||||||
|
try {
|
||||||
|
CAudioDecoderFfmpeg *decoder = new CAudioDecoderFfmpeg(stream, loop);
|
||||||
|
return static_cast<IAudioDecoder *>(decoder);
|
||||||
|
}
|
||||||
|
catch(const Exception &e)
|
||||||
|
{
|
||||||
|
nlwarning("Exception %s during ffmpeg setup", e.what());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
std::string type_lower = toLower(type);
|
std::string type_lower = toLower(type);
|
||||||
if (type_lower == "ogg")
|
if (type_lower == "ogg")
|
||||||
{
|
{
|
||||||
|
@ -92,6 +107,7 @@ IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &type, NLMISC
|
||||||
nlwarning("Music file type unknown: '%s'", type_lower.c_str());
|
nlwarning("Music file type unknown: '%s'", type_lower.c_str());
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, std::string &title, float &length)
|
bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, std::string &title, float &length)
|
||||||
|
@ -102,6 +118,14 @@ bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, st
|
||||||
nlwarning("Music file %s does not exist!", filepath.c_str());
|
nlwarning("Music file %s does not exist!", filepath.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef FFMPEG_ENABLED
|
||||||
|
CIFile ifile;
|
||||||
|
ifile.setCacheFileOnOpen(false);
|
||||||
|
ifile.allowBNPCacheFileOnOpen(false);
|
||||||
|
if (ifile.open(lookup))
|
||||||
|
return CAudioDecoderFfmpeg::getInfo(&ifile, artist, title, length);
|
||||||
|
#else
|
||||||
std::string type = CFile::getExtension(filepath);
|
std::string type = CFile::getExtension(filepath);
|
||||||
std::string type_lower = NLMISC::toLower(type);
|
std::string type_lower = NLMISC::toLower(type);
|
||||||
|
|
||||||
|
@ -119,6 +143,7 @@ bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, st
|
||||||
{
|
{
|
||||||
nlwarning("Music file type unknown: '%s'", type_lower.c_str());
|
nlwarning("Music file type unknown: '%s'", type_lower.c_str());
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
artist.clear(); title.clear();
|
artist.clear(); title.clear();
|
||||||
return false;
|
return false;
|
||||||
|
@ -132,6 +157,11 @@ void IAudioDecoder::getMusicExtensions(std::vector<std::string> &extensions)
|
||||||
{
|
{
|
||||||
extensions.push_back("ogg");
|
extensions.push_back("ogg");
|
||||||
}
|
}
|
||||||
|
#ifdef FFMPEG_ENABLED
|
||||||
|
extensions.push_back("mp3");
|
||||||
|
extensions.push_back("flac");
|
||||||
|
extensions.push_back("aac");
|
||||||
|
#endif
|
||||||
|
|
||||||
// extensions.push_back("wav"); // TODO: Easy.
|
// extensions.push_back("wav"); // TODO: Easy.
|
||||||
}
|
}
|
||||||
|
@ -139,7 +169,11 @@ void IAudioDecoder::getMusicExtensions(std::vector<std::string> &extensions)
|
||||||
/// Return if a music extension is supported by the nel sound library.
|
/// Return if a music extension is supported by the nel sound library.
|
||||||
bool IAudioDecoder::isMusicExtensionSupported(const std::string &extension)
|
bool IAudioDecoder::isMusicExtensionSupported(const std::string &extension)
|
||||||
{
|
{
|
||||||
|
#ifdef FFMPEG_ENABLED
|
||||||
|
return (extension == "ogg" || extension == "mp3" || extension == "flac" || extension == "aac");
|
||||||
|
#else
|
||||||
return (extension == "ogg");
|
return (extension == "ogg");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace NLSOUND */
|
} /* namespace NLSOUND */
|
||||||
|
|
435
code/nel/src/sound/audio_decoder_ffmpeg.cpp
Normal file
435
code/nel/src/sound/audio_decoder_ffmpeg.cpp
Normal file
|
@ -0,0 +1,435 @@
|
||||||
|
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
||||||
|
// Copyright (C) 2018 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 "stdsound.h"
|
||||||
|
|
||||||
|
#include <nel/sound/audio_decoder_ffmpeg.h>
|
||||||
|
|
||||||
|
#define __STDC_CONSTANT_MACROS
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libswresample/swresample.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace NLMISC;
|
||||||
|
using namespace NLSOUND;
|
||||||
|
|
||||||
|
// Visual Studio does not support AV_TIME_BASE_Q macro in C++
|
||||||
|
#undef AV_TIME_BASE_Q
|
||||||
|
static const AVRational AV_TIME_BASE_Q = {1, AV_TIME_BASE};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const std::string av_err2string(sint err)
|
||||||
|
{
|
||||||
|
char buf[AV_ERROR_MAX_STRING_SIZE];
|
||||||
|
av_strerror(err, buf, AV_ERROR_MAX_STRING_SIZE);
|
||||||
|
return (std::string)buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nel_logger(void *ptr, int level, const char *fmt, va_list vargs)
|
||||||
|
{
|
||||||
|
static char msg[1024];
|
||||||
|
|
||||||
|
const char *module = NULL;
|
||||||
|
|
||||||
|
// AV_LOG_DEBUG, AV_LOG_TRACE
|
||||||
|
if (level >= AV_LOG_DEBUG) return;
|
||||||
|
|
||||||
|
if (ptr)
|
||||||
|
{
|
||||||
|
AVClass *avc = *(AVClass**) ptr;
|
||||||
|
module = avc->item_name(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
vsnprintf(msg, sizeof(msg), fmt, vargs);
|
||||||
|
msg[sizeof(msg)-1] = '\0';
|
||||||
|
|
||||||
|
switch(level)
|
||||||
|
{
|
||||||
|
case AV_LOG_PANIC:
|
||||||
|
// ffmpeg is about to crash so lets throw
|
||||||
|
nlerror("FFMPEG(P): (%s) %s", module, msg);
|
||||||
|
break;
|
||||||
|
case AV_LOG_FATAL:
|
||||||
|
// ffmpeg had unrecoverable error, corrupted stream or such
|
||||||
|
nlerrornoex("FFMPEG(F): (%s) %s", module, msg);
|
||||||
|
break;
|
||||||
|
case AV_LOG_ERROR:
|
||||||
|
nlwarning("FFMPEG(E): (%s) %s", module, msg);
|
||||||
|
break;
|
||||||
|
case AV_LOG_WARNING:
|
||||||
|
nlwarning("FFMPEG(W): (%s) %s", module, msg);
|
||||||
|
break;
|
||||||
|
case AV_LOG_INFO:
|
||||||
|
nlinfo("FFMPEG(I): (%s) %s", module, msg);
|
||||||
|
break;
|
||||||
|
case AV_LOG_VERBOSE:
|
||||||
|
nldebug("FFMPEG(V): (%s) %s", module, msg);
|
||||||
|
break;
|
||||||
|
case AV_LOG_DEBUG:
|
||||||
|
nldebug("FFMPEG(D): (%s) %s", module, msg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
nlinfo("FFMPEG: invalid log level:%d (%s) %s", level, module, msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CFfmpegInstance
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CFfmpegInstance()
|
||||||
|
{
|
||||||
|
av_log_set_level(AV_LOG_DEBUG);
|
||||||
|
av_log_set_callback(nel_logger);
|
||||||
|
|
||||||
|
av_register_all();
|
||||||
|
|
||||||
|
//avformat_network_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~CFfmpegInstance()
|
||||||
|
{
|
||||||
|
//avformat_network_deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CFfmpegInstance ffmpeg;
|
||||||
|
|
||||||
|
// Send bytes to ffmpeg
|
||||||
|
int avio_read_packet(void *opaque, uint8 *buf, int buf_size)
|
||||||
|
{
|
||||||
|
NLSOUND::CAudioDecoderFfmpeg *decoder = static_cast<NLSOUND::CAudioDecoderFfmpeg *>(opaque);
|
||||||
|
NLMISC::IStream *stream = decoder->getStream();
|
||||||
|
nlassert(stream->isReading());
|
||||||
|
|
||||||
|
uint32 available = decoder->getStreamSize() - stream->getPos();
|
||||||
|
if (available == 0) return 0;
|
||||||
|
|
||||||
|
buf_size = FFMIN(buf_size, available);
|
||||||
|
stream->serialBuffer((uint8 *)buf, buf_size);
|
||||||
|
return buf_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
sint64 avio_seek(void *opaque, sint64 offset, int whence)
|
||||||
|
{
|
||||||
|
NLSOUND::CAudioDecoderFfmpeg *decoder = static_cast<NLSOUND::CAudioDecoderFfmpeg *>(opaque);
|
||||||
|
NLMISC::IStream *stream = decoder->getStream();
|
||||||
|
nlassert(stream->isReading());
|
||||||
|
|
||||||
|
NLMISC::IStream::TSeekOrigin origin;
|
||||||
|
switch(whence)
|
||||||
|
{
|
||||||
|
case SEEK_SET:
|
||||||
|
origin = NLMISC::IStream::begin;
|
||||||
|
break;
|
||||||
|
case SEEK_CUR:
|
||||||
|
origin = NLMISC::IStream::current;
|
||||||
|
break;
|
||||||
|
case SEEK_END:
|
||||||
|
origin = NLMISC::IStream::end;
|
||||||
|
break;
|
||||||
|
case AVSEEK_SIZE:
|
||||||
|
return decoder->getStreamSize();
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->seek((sint32) offset, origin);
|
||||||
|
return stream->getPos();
|
||||||
|
}
|
||||||
|
|
||||||
|
}//ns
|
||||||
|
|
||||||
|
namespace NLSOUND {
|
||||||
|
|
||||||
|
// swresample will convert audio to this format
|
||||||
|
#define FFMPEG_SAMPLE_RATE 44100
|
||||||
|
#define FFMPEG_CHANNELS 2
|
||||||
|
#define FFMPEG_CHANNEL_LAYOUT AV_CH_LAYOUT_STEREO
|
||||||
|
#define FFMPEG_BITS_PER_SAMPLE 16
|
||||||
|
#define FFMPEG_SAMPLE_FORMAT AV_SAMPLE_FMT_S16
|
||||||
|
|
||||||
|
CAudioDecoderFfmpeg::CAudioDecoderFfmpeg(NLMISC::IStream *stream, bool loop)
|
||||||
|
: IAudioDecoder(),
|
||||||
|
_Stream(stream), _Loop(loop), _IsMusicEnded(false), _StreamSize(0), _IsSupported(false),
|
||||||
|
_AvioContext(NULL), _FormatContext(NULL),
|
||||||
|
_AudioContext(NULL), _AudioStreamIndex(0),
|
||||||
|
_SwrContext(NULL)
|
||||||
|
{
|
||||||
|
_StreamOffset = stream->getPos();
|
||||||
|
stream->seek(0, NLMISC::IStream::end);
|
||||||
|
_StreamSize = stream->getPos();
|
||||||
|
stream->seek(_StreamOffset, NLMISC::IStream::begin);
|
||||||
|
|
||||||
|
try {
|
||||||
|
_FormatContext = avformat_alloc_context();
|
||||||
|
if (!_FormatContext)
|
||||||
|
throw Exception("Can't create AVFormatContext");
|
||||||
|
|
||||||
|
// avio_ctx_buffer can be reallocated by ffmpeg and assigned to avio_ctx->buffer
|
||||||
|
uint8 *avio_ctx_buffer = NULL;
|
||||||
|
size_t avio_ctx_buffer_size = 4096;
|
||||||
|
avio_ctx_buffer = static_cast<uint8 *>(av_malloc(avio_ctx_buffer_size));
|
||||||
|
if (!avio_ctx_buffer)
|
||||||
|
throw Exception("Can't allocate avio context buffer");
|
||||||
|
|
||||||
|
_AvioContext = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this, &avio_read_packet, NULL, &avio_seek);
|
||||||
|
if (!_AvioContext)
|
||||||
|
throw Exception("Can't allocate avio context");
|
||||||
|
|
||||||
|
_FormatContext->pb = _AvioContext;
|
||||||
|
sint ret = avformat_open_input(&_FormatContext, NULL, NULL, NULL);
|
||||||
|
if (ret < 0)
|
||||||
|
throw Exception("avformat_open_input: %d", ret);
|
||||||
|
|
||||||
|
// find stream and then audio codec to see if ffmpeg supports this
|
||||||
|
_IsSupported = false;
|
||||||
|
if (avformat_find_stream_info(_FormatContext, NULL) >= 0)
|
||||||
|
{
|
||||||
|
_AudioStreamIndex = av_find_best_stream(_FormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
|
||||||
|
if (_AudioStreamIndex >= 0)
|
||||||
|
{
|
||||||
|
_AudioContext = _FormatContext->streams[_AudioStreamIndex]->codec;
|
||||||
|
av_opt_set_int(_AudioContext, "refcounted_frames", 1, 0);
|
||||||
|
|
||||||
|
AVCodec *codec = avcodec_find_decoder(_AudioContext->codec_id);
|
||||||
|
if (codec != NULL && avcodec_open2(_AudioContext, codec, NULL) >= 0)
|
||||||
|
{
|
||||||
|
_IsSupported = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(...)
|
||||||
|
{
|
||||||
|
release();
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_IsSupported)
|
||||||
|
{
|
||||||
|
nlwarning("FFMPEG: Decoder created, unknown stream format / codec");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CAudioDecoderFfmpeg::~CAudioDecoderFfmpeg()
|
||||||
|
{
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAudioDecoderFfmpeg::release()
|
||||||
|
{
|
||||||
|
if (_SwrContext)
|
||||||
|
swr_free(&_SwrContext);
|
||||||
|
|
||||||
|
if (_AudioContext)
|
||||||
|
avcodec_close(_AudioContext);
|
||||||
|
|
||||||
|
if (_FormatContext)
|
||||||
|
avformat_close_input(&_FormatContext);
|
||||||
|
|
||||||
|
if (_AvioContext && _AvioContext->buffer)
|
||||||
|
av_freep(&_AvioContext->buffer);
|
||||||
|
|
||||||
|
if (_AvioContext)
|
||||||
|
av_freep(&_AvioContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAudioDecoderFfmpeg::isFormatSupported() const
|
||||||
|
{
|
||||||
|
return _IsSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get information on a music file.
|
||||||
|
bool CAudioDecoderFfmpeg::getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title, float &length)
|
||||||
|
{
|
||||||
|
CAudioDecoderFfmpeg ffmpeg(stream, false);
|
||||||
|
if (!ffmpeg.isFormatSupported())
|
||||||
|
{
|
||||||
|
title.clear();
|
||||||
|
artist.clear();
|
||||||
|
length = 0.f;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVDictionaryEntry *tag = NULL;
|
||||||
|
while((tag = av_dict_get(ffmpeg._FormatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
|
||||||
|
{
|
||||||
|
if (!strcmp(tag->key, "artist"))
|
||||||
|
{
|
||||||
|
artist = tag->value;
|
||||||
|
}
|
||||||
|
else if (!strcmp(tag->key, "title"))
|
||||||
|
{
|
||||||
|
title = tag->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ffmpeg._FormatContext->duration != AV_NOPTS_VALUE)
|
||||||
|
{
|
||||||
|
length = ffmpeg._FormatContext->duration * av_q2d(AV_TIME_BASE_Q);
|
||||||
|
}
|
||||||
|
else if (ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->duration != AV_NOPTS_VALUE)
|
||||||
|
{
|
||||||
|
length = ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->duration * av_q2d(ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->time_base);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
length = 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 CAudioDecoderFfmpeg::getRequiredBytes()
|
||||||
|
{
|
||||||
|
return 0; // no minimum requirement of bytes to buffer out
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 CAudioDecoderFfmpeg::getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum)
|
||||||
|
{
|
||||||
|
if (_IsMusicEnded) return 0;
|
||||||
|
nlassert(minimum <= maximum); // can't have this..
|
||||||
|
|
||||||
|
// TODO: CStreamFileSource::play() will stall when there is no frames on warmup
|
||||||
|
// supported can be set false if there is an issue creating converter
|
||||||
|
if (!_IsSupported)
|
||||||
|
{
|
||||||
|
_IsMusicEnded = true;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 bytes_read = 0;
|
||||||
|
|
||||||
|
AVFrame frame = {0};
|
||||||
|
AVPacket packet = {0};
|
||||||
|
|
||||||
|
if (!_SwrContext)
|
||||||
|
{
|
||||||
|
sint64 in_channel_layout = av_get_default_channel_layout(_AudioContext->channels);
|
||||||
|
_SwrContext = swr_alloc_set_opts(NULL,
|
||||||
|
// output
|
||||||
|
FFMPEG_CHANNEL_LAYOUT, FFMPEG_SAMPLE_FORMAT, FFMPEG_SAMPLE_RATE,
|
||||||
|
// input
|
||||||
|
in_channel_layout, _AudioContext->sample_fmt, _AudioContext->sample_rate,
|
||||||
|
0, NULL);
|
||||||
|
swr_init(_SwrContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
sint ret;
|
||||||
|
while(bytes_read < minimum)
|
||||||
|
{
|
||||||
|
// read packet from stream
|
||||||
|
if ((ret = av_read_frame(_FormatContext, &packet)) < 0)
|
||||||
|
{
|
||||||
|
_IsMusicEnded = true;
|
||||||
|
// TODO: looping
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.stream_index == _AudioStreamIndex)
|
||||||
|
{
|
||||||
|
// packet can contain multiple frames
|
||||||
|
AVPacket first = packet;
|
||||||
|
int got_frame = 0;
|
||||||
|
do {
|
||||||
|
got_frame = 0;
|
||||||
|
ret = avcodec_decode_audio4(_AudioContext, &frame, &got_frame, &packet);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
nlwarning("FFMPEG: error decoding audio frame: %s", av_err2string(ret).c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
packet.size -= ret;
|
||||||
|
packet.data += ret;
|
||||||
|
|
||||||
|
if (got_frame)
|
||||||
|
{
|
||||||
|
uint32 out_bps = av_get_bytes_per_sample(FFMPEG_SAMPLE_FORMAT) * FFMPEG_CHANNELS;
|
||||||
|
uint32 max_samples = (maximum - bytes_read) / out_bps;
|
||||||
|
|
||||||
|
uint32 out_samples = av_rescale_rnd(swr_get_delay(_SwrContext, _AudioContext->sample_rate) + frame.nb_samples,
|
||||||
|
FFMPEG_SAMPLE_RATE, _AudioContext->sample_rate, AV_ROUND_UP);
|
||||||
|
|
||||||
|
if (max_samples > out_samples)
|
||||||
|
max_samples = out_samples;
|
||||||
|
|
||||||
|
uint32 converted = swr_convert(_SwrContext, &buffer, max_samples, (const uint8 **)frame.extended_data, frame.nb_samples);
|
||||||
|
uint32 size = out_bps * converted;
|
||||||
|
|
||||||
|
bytes_read += size;
|
||||||
|
buffer += size;
|
||||||
|
|
||||||
|
av_frame_unref(&frame);
|
||||||
|
}
|
||||||
|
} while (got_frame && packet.size > 0);
|
||||||
|
|
||||||
|
av_packet_unref(&first);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret = 0;
|
||||||
|
av_packet_unref(&packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 CAudioDecoderFfmpeg::getChannels()
|
||||||
|
{
|
||||||
|
return FFMPEG_CHANNELS;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint CAudioDecoderFfmpeg::getSamplesPerSec()
|
||||||
|
{
|
||||||
|
return FFMPEG_SAMPLE_RATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 CAudioDecoderFfmpeg::getBitsPerSample()
|
||||||
|
{
|
||||||
|
return FFMPEG_BITS_PER_SAMPLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAudioDecoderFfmpeg::isMusicEnded()
|
||||||
|
{
|
||||||
|
return _IsMusicEnded;
|
||||||
|
}
|
||||||
|
|
||||||
|
float CAudioDecoderFfmpeg::getLength()
|
||||||
|
{
|
||||||
|
printf(">> CAudioDecoderFfmpeg::getLength\n");
|
||||||
|
// TODO: return (float)ov_time_total(&_OggVorbisFile, -1);
|
||||||
|
return 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAudioDecoderFfmpeg::setLooping(bool loop)
|
||||||
|
{
|
||||||
|
_Loop = loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace NLSOUND */
|
||||||
|
|
||||||
|
/* end of file */
|
|
@ -45,7 +45,7 @@ using namespace std;
|
||||||
namespace NLSOUND {
|
namespace NLSOUND {
|
||||||
|
|
||||||
CStreamFileSource::CStreamFileSource(CStreamFileSound *streamFileSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController)
|
CStreamFileSource::CStreamFileSource(CStreamFileSound *streamFileSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController)
|
||||||
: CStreamSource(streamFileSound, spawn, cb, cbUserParam, cluster, groupController), m_AudioDecoder(NULL), m_Paused(false)
|
: CStreamSource(streamFileSound, spawn, cb, cbUserParam, cluster, groupController), m_AudioDecoder(NULL), m_Paused(false), m_DecodingEnded(false)
|
||||||
{
|
{
|
||||||
m_Thread = NLMISC::IThread::create(this);
|
m_Thread = NLMISC::IThread::create(this);
|
||||||
}
|
}
|
||||||
|
@ -244,7 +244,7 @@ void CStreamFileSource::resume()
|
||||||
|
|
||||||
bool CStreamFileSource::isEnded()
|
bool CStreamFileSource::isEnded()
|
||||||
{
|
{
|
||||||
return (!m_Thread->isRunning() && !_Playing && !m_WaitingForPlay && !m_Paused);
|
return m_DecodingEnded || (!m_Thread->isRunning() && !_Playing && !m_WaitingForPlay && !m_Paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
float CStreamFileSource::getLength()
|
float CStreamFileSource::getLength()
|
||||||
|
@ -319,6 +319,7 @@ void CStreamFileSource::run()
|
||||||
this->getRecommendedBufferSize(samples, bytes);
|
this->getRecommendedBufferSize(samples, bytes);
|
||||||
uint32 recSleep = 40;
|
uint32 recSleep = 40;
|
||||||
uint32 doSleep = 10;
|
uint32 doSleep = 10;
|
||||||
|
m_DecodingEnded = false;
|
||||||
while (_Playing || m_WaitingForPlay)
|
while (_Playing || m_WaitingForPlay)
|
||||||
{
|
{
|
||||||
if (!m_AudioDecoder->isMusicEnded())
|
if (!m_AudioDecoder->isMusicEnded())
|
||||||
|
@ -369,6 +370,9 @@ void CStreamFileSource::run()
|
||||||
{
|
{
|
||||||
delete m_AudioDecoder;
|
delete m_AudioDecoder;
|
||||||
m_AudioDecoder = NULL;
|
m_AudioDecoder = NULL;
|
||||||
|
// _Playing cannot be used to detect play state because its required in cleanup
|
||||||
|
// Using m_AudioDecoder in isEnded() may result race condition (decoder is only created after thread is started)
|
||||||
|
m_DecodingEnded = true;
|
||||||
}
|
}
|
||||||
// drop buffers
|
// drop buffers
|
||||||
m_FreeBuffers = 3;
|
m_FreeBuffers = 3;
|
||||||
|
|
|
@ -201,9 +201,9 @@ void connectionRestoreVideoMode ()
|
||||||
mode.Height = height;
|
mode.Height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't allow sizes smaller than 800x600
|
// don't allow sizes smaller than 1024x768
|
||||||
if (ClientCfg.Width < 800) ClientCfg.Width = 800;
|
if (ClientCfg.Width < 1024) ClientCfg.Width = 1024;
|
||||||
if (ClientCfg.Height < 600) ClientCfg.Height = 600;
|
if (ClientCfg.Height < 768) ClientCfg.Height = 768;
|
||||||
|
|
||||||
if (StereoDisplay)
|
if (StereoDisplay)
|
||||||
StereoDisplayAttached = StereoDisplay->attachToDisplay();
|
StereoDisplayAttached = StereoDisplay->attachToDisplay();
|
||||||
|
@ -1252,6 +1252,16 @@ TInterfaceState globalMenu()
|
||||||
// Restore video mode
|
// Restore video mode
|
||||||
if (ClientCfg.SelectCharacter == -1)
|
if (ClientCfg.SelectCharacter == -1)
|
||||||
{
|
{
|
||||||
|
if (ClientCfg.Windowed)
|
||||||
|
{
|
||||||
|
// if used changed window resolution in char select
|
||||||
|
// if we don't update ClientCfg, then UI from icfg is restored wrong
|
||||||
|
uint32 width, height;
|
||||||
|
Driver->getWindowSize(width, height);
|
||||||
|
ClientCfg.Width = width;
|
||||||
|
ClientCfg.Height = height;
|
||||||
|
}
|
||||||
|
|
||||||
connectionRestoreVideoMode ();
|
connectionRestoreVideoMode ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2665,18 +2665,8 @@ CCtrlButton *CGroupMap::addUserLandMark(const NLMISC::CVector2f &pos, const ucst
|
||||||
addLandMark(_UserLM, pos, title, getUserLandMarkOptions((uint32)_CurContinent->UserLandMarks.size() - 1));
|
addLandMark(_UserLM, pos, title, getUserLandMarkOptions((uint32)_CurContinent->UserLandMarks.size() - 1));
|
||||||
|
|
||||||
// Save the config file each time a user landmark is created
|
// Save the config file each time a user landmark is created
|
||||||
CInterfaceManager *pIM = CInterfaceManager::getInstance();
|
CInterfaceManager::getInstance()->saveConfig();
|
||||||
uint8 currMode = pIM->getMode();
|
|
||||||
std::string filename = "save/interface_" + PlayerSelectedFileName + ".icfg";
|
|
||||||
if (!CFile::fileExists(filename) && CFile::fileExists("save/shared_interface.icfg"))
|
|
||||||
{
|
|
||||||
filename = "save/shared_interface.icfg";
|
|
||||||
}
|
|
||||||
pIM->saveConfig (filename);
|
|
||||||
if (currMode != pIM->getMode())
|
|
||||||
{
|
|
||||||
pIM->setMode(currMode);
|
|
||||||
}
|
|
||||||
return _UserLM.back();
|
return _UserLM.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1272,32 +1272,19 @@ void CInterfaceManager::loadInterfaceConfig()
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void CInterfaceManager::uninitInGame0 ()
|
void CInterfaceManager::uninitInGame0 ()
|
||||||
{
|
{
|
||||||
|
|
||||||
// Autosave of the keys
|
// Autosave of the keys
|
||||||
if (_KeysLoaded)
|
if (_KeysLoaded)
|
||||||
{
|
{
|
||||||
if (!ClientCfg.R2EDEnabled)
|
saveKeys();
|
||||||
{
|
|
||||||
string filename = "save/keys_" + PlayerSelectedFileName + ".xml";
|
|
||||||
if (!CFile::fileExists(filename) && CFile::fileExists("save/shared_keys.xml"))
|
|
||||||
filename = "save/shared_keys.xml";
|
|
||||||
|
|
||||||
saveKeys(filename);
|
|
||||||
}
|
|
||||||
_KeysLoaded = false;
|
_KeysLoaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Autosave of the interface in interface.cfg
|
// Autosave of the interface in interface.cfg
|
||||||
if (_ConfigLoaded)
|
if (_ConfigLoaded)
|
||||||
{
|
{
|
||||||
if (!ClientCfg.R2EDEnabled)
|
saveConfig();
|
||||||
{
|
|
||||||
string filename = "save/interface_" + PlayerSelectedFileName + ".icfg";
|
|
||||||
if (!CFile::fileExists(filename) && CFile::fileExists("save/shared_interface.icfg"))
|
|
||||||
filename = "save/shared_interface.icfg";
|
|
||||||
|
|
||||||
saveConfig(filename);
|
|
||||||
}
|
|
||||||
_ConfigLoaded = false;
|
_ConfigLoaded = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1890,6 +1877,29 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
bool CInterfaceManager::saveConfig (bool verbose)
|
||||||
|
{
|
||||||
|
bool ret = true;
|
||||||
|
|
||||||
|
if (!ClientCfg.R2EDEnabled)
|
||||||
|
{
|
||||||
|
uint8 currMode = getMode();
|
||||||
|
|
||||||
|
string filename = "save/interface_" + PlayerSelectedFileName + ".icfg";
|
||||||
|
if (!CFile::fileExists(filename) && CFile::fileExists("save/shared_interface.icfg"))
|
||||||
|
filename = "save/shared_interface.icfg";
|
||||||
|
|
||||||
|
if (verbose) CInterfaceManager::getInstance()->displaySystemInfo("Saving " + filename);
|
||||||
|
ret = saveConfig(filename);
|
||||||
|
|
||||||
|
if (currMode != getMode())
|
||||||
|
setMode(currMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool CInterfaceManager::saveConfig (const string &filename)
|
bool CInterfaceManager::saveConfig (const string &filename)
|
||||||
|
@ -2735,7 +2745,25 @@ void writeMacros (xmlNodePtr node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
|
bool CInterfaceManager::saveKeys(bool verbose)
|
||||||
|
{
|
||||||
|
bool ret = true;
|
||||||
|
|
||||||
|
if (!ClientCfg.R2EDEnabled)
|
||||||
|
{
|
||||||
|
string filename = "save/keys_" + PlayerSelectedFileName + ".xml";
|
||||||
|
if (!CFile::fileExists(filename) && CFile::fileExists("save/shared_keys.xml"))
|
||||||
|
filename = "save/shared_keys.xml";
|
||||||
|
|
||||||
|
if (verbose) CInterfaceManager::getInstance()->displaySystemInfo("Saving " + filename);
|
||||||
|
|
||||||
|
ret = saveKeys(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************************************************
|
||||||
bool CInterfaceManager::saveKeys(const std::string &filename)
|
bool CInterfaceManager::saveKeys(const std::string &filename)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
@ -2936,6 +2964,18 @@ void CInterfaceManager::displayWebWindow(const string & name, const string & url
|
||||||
|
|
||||||
CAHManager::getInstance()->runActionHandler("browse", NULL, "name="+name+":content:html|url="+url);
|
CAHManager::getInstance()->runActionHandler("browse", NULL, "name="+name+":content:html|url="+url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ***************************************************************************
|
||||||
|
class CAHSaveUI : public IActionHandler
|
||||||
|
{
|
||||||
|
virtual void execute (CCtrlBase *pCaller, const string &Params)
|
||||||
|
{
|
||||||
|
CInterfaceManager::getInstance()->saveKeys(true);
|
||||||
|
CInterfaceManager::getInstance()->saveConfig(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
REGISTER_ACTION_HANDLER (CAHSaveUI, "save_ui");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
class CHandlerDispWebOnQuit : public IActionHandler
|
class CHandlerDispWebOnQuit : public IActionHandler
|
||||||
|
|
|
@ -205,10 +205,14 @@ public:
|
||||||
|
|
||||||
// Load/Save position, size, etc.. of windows
|
// Load/Save position, size, etc.. of windows
|
||||||
bool loadConfig (const std::string &filename);
|
bool loadConfig (const std::string &filename);
|
||||||
|
// Save config to default location, if verbose is true, display message in game sysinfo
|
||||||
|
bool saveConfig (bool verbose = false);
|
||||||
bool saveConfig (const std::string &filename);
|
bool saveConfig (const std::string &filename);
|
||||||
// delete the user config (give the player ident fileName)
|
// delete the user config (give the player ident fileName)
|
||||||
bool deletePlayerConfig (const std::string &playerFileIdent);
|
bool deletePlayerConfig (const std::string &playerFileIdent);
|
||||||
|
|
||||||
|
// Save keys to default location, if verbose is true, display message in game sysinfo
|
||||||
|
bool saveKeys (bool verbose = false);
|
||||||
// Save the keys config file
|
// Save the keys config file
|
||||||
bool saveKeys (const std::string &filename);
|
bool saveKeys (const std::string &filename);
|
||||||
// delete the user Keysconfig (give the player ident fileName)
|
// delete the user Keysconfig (give the player ident fileName)
|
||||||
|
|
|
@ -39,6 +39,9 @@ extern UDriver *Driver;
|
||||||
#define TEMPLATE_PLAYLIST_SONG "playlist_song"
|
#define TEMPLATE_PLAYLIST_SONG "playlist_song"
|
||||||
#define TEMPLATE_PLAYLIST_SONG_TITLE "title"
|
#define TEMPLATE_PLAYLIST_SONG_TITLE "title"
|
||||||
#define TEMPLATE_PLAYLIST_SONG_DURATION "duration"
|
#define TEMPLATE_PLAYLIST_SONG_DURATION "duration"
|
||||||
|
// ui state
|
||||||
|
#define MP3_SAVE_SHUFFLE "UI:SAVE:MP3_SHUFFLE"
|
||||||
|
#define MP3_SAVE_REPEAT "UI:SAVE:MP3_REPEAT"
|
||||||
|
|
||||||
static const std::string MediaPlayerDirectory("music/");
|
static const std::string MediaPlayerDirectory("music/");
|
||||||
|
|
||||||
|
@ -48,8 +51,20 @@ CMusicPlayer MusicPlayer;
|
||||||
|
|
||||||
CMusicPlayer::CMusicPlayer ()
|
CMusicPlayer::CMusicPlayer ()
|
||||||
{
|
{
|
||||||
_CurrentSong = 0;
|
_CurrentSongIndex = 0;
|
||||||
_State = Stopped;
|
_State = Stopped;
|
||||||
|
_PlayStart = 0;
|
||||||
|
_PauseTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CMusicPlayer::isRepeatEnabled() const
|
||||||
|
{
|
||||||
|
return (NLGUI::CDBManager::getInstance()->getDbProp(MP3_SAVE_REPEAT)->getValue32() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CMusicPlayer::isShuffleEnabled() const
|
||||||
|
{
|
||||||
|
return (NLGUI::CDBManager::getInstance()->getDbProp(MP3_SAVE_SHUFFLE)->getValue32() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,16 +74,61 @@ void CMusicPlayer::playSongs (const std::vector<CSongs> &songs)
|
||||||
_Songs = songs;
|
_Songs = songs;
|
||||||
|
|
||||||
// reset song index if out of bounds
|
// reset song index if out of bounds
|
||||||
if (_CurrentSong > _Songs.size())
|
if (_CurrentSongIndex > _Songs.size())
|
||||||
_CurrentSong = 0;
|
_CurrentSongIndex = 0;
|
||||||
|
|
||||||
|
if (isShuffleEnabled())
|
||||||
|
shuffleAndRebuildPlaylist();
|
||||||
|
else
|
||||||
|
rebuildPlaylist();
|
||||||
|
|
||||||
|
// If pause, stop, else play will resume
|
||||||
|
if (_State == Paused)
|
||||||
|
_State = Stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************************************************
|
||||||
|
void CMusicPlayer::updatePlaylist(sint prevIndex)
|
||||||
|
{
|
||||||
|
CInterfaceElement *pIE;
|
||||||
|
std::string rowId;
|
||||||
|
|
||||||
|
if (prevIndex >= 0 && prevIndex < _Songs.size())
|
||||||
|
{
|
||||||
|
rowId = toString("%s:s%d:bg", MP3_PLAYER_PLAYLIST_LIST, prevIndex);
|
||||||
|
pIE = dynamic_cast<CInterfaceElement*>(CWidgetManager::getInstance()->getElementFromId(rowId));
|
||||||
|
if (pIE) pIE->setActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
rowId = toString("%s:s%d:bg", MP3_PLAYER_PLAYLIST_LIST, _CurrentSongIndex);
|
||||||
|
pIE = dynamic_cast<CInterfaceElement*>(CWidgetManager::getInstance()->getElementFromId(rowId));
|
||||||
|
if (pIE) pIE->setActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************************************************
|
||||||
|
void CMusicPlayer::shuffleAndRebuildPlaylist()
|
||||||
|
{
|
||||||
|
std::random_shuffle(_Songs.begin(), _Songs.end());
|
||||||
|
rebuildPlaylist();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************************************************
|
||||||
|
void CMusicPlayer::rebuildPlaylist()
|
||||||
|
{
|
||||||
CGroupList *pList = dynamic_cast<CGroupList *>(CWidgetManager::getInstance()->getElementFromId(MP3_PLAYER_PLAYLIST_LIST));
|
CGroupList *pList = dynamic_cast<CGroupList *>(CWidgetManager::getInstance()->getElementFromId(MP3_PLAYER_PLAYLIST_LIST));
|
||||||
if (pList)
|
if (pList)
|
||||||
{
|
{
|
||||||
pList->clearGroups();
|
pList->clearGroups();
|
||||||
pList->setDynamicDisplaySize(true);
|
pList->setDynamicDisplaySize(true);
|
||||||
|
bool found = _CurrentSong.Filename.empty();
|
||||||
for (uint i=0; i < _Songs.size(); ++i)
|
for (uint i=0; i < _Songs.size(); ++i)
|
||||||
{
|
{
|
||||||
|
if (!found && _CurrentSong.Filename == _Songs[i].Filename)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
_CurrentSongIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
uint min = (sint32)(_Songs[i].Length / 60) % 60;
|
uint min = (sint32)(_Songs[i].Length / 60) % 60;
|
||||||
uint sec = (sint32)(_Songs[i].Length) % 60;
|
uint sec = (sint32)(_Songs[i].Length) % 60;
|
||||||
uint hour = _Songs[i].Length / 3600;
|
uint hour = _Songs[i].Length / 3600;
|
||||||
|
@ -103,9 +163,7 @@ void CMusicPlayer::playSongs (const std::vector<CSongs> &songs)
|
||||||
pList->invalidateCoords();
|
pList->invalidateCoords();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If pause, stop, else play will resume
|
updatePlaylist();
|
||||||
if (_State == Paused)
|
|
||||||
_State = Stopped;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,31 +174,40 @@ void CMusicPlayer::play (sint index)
|
||||||
if(!SoundMngr)
|
if(!SoundMngr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
sint prevSongIndex = _CurrentSongIndex;
|
||||||
|
|
||||||
if (index >= 0 && index < (sint)_Songs.size())
|
if (index >= 0 && index < (sint)_Songs.size())
|
||||||
{
|
{
|
||||||
if (_State == Paused)
|
if (_State == Paused)
|
||||||
|
{
|
||||||
stop();
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
_CurrentSong = index;
|
_CurrentSongIndex = index;
|
||||||
|
_PauseTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_Songs.empty())
|
if (!_Songs.empty())
|
||||||
{
|
{
|
||||||
nlassert (_CurrentSong<_Songs.size());
|
nlassert (_CurrentSongIndex<_Songs.size());
|
||||||
|
|
||||||
/* If the player is paused, resume, else, play the current song */
|
/* If the player is paused, resume, else, play the current song */
|
||||||
if (_State == Paused)
|
if (_State == Paused)
|
||||||
|
{
|
||||||
SoundMngr->resumeMusic();
|
SoundMngr->resumeMusic();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
SoundMngr->playMusic(_Songs[_CurrentSong].Filename, 0, true, false, false);
|
{
|
||||||
|
SoundMngr->playMusic(_Songs[_CurrentSongIndex].Filename, 0, true, false, false);
|
||||||
|
_PauseTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
_State = Playing;
|
_State = Playing;
|
||||||
|
_PlayStart = CTime::getLocalTime() - _PauseTime;
|
||||||
|
|
||||||
/* Show the song title */
|
_CurrentSong = _Songs[_CurrentSongIndex];
|
||||||
CInterfaceManager *pIM = CInterfaceManager::getInstance();
|
|
||||||
CViewText *pVT = dynamic_cast<CViewText*>(CWidgetManager::getInstance()->getElementFromId("ui:interface:mp3_player:screen:text"));
|
updatePlaylist(prevSongIndex);
|
||||||
if (pVT)
|
|
||||||
pVT->setText (ucstring::makeFromUtf8(_Songs[_CurrentSong].Title));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +222,9 @@ void CMusicPlayer::pause ()
|
||||||
{
|
{
|
||||||
SoundMngr->pauseMusic();
|
SoundMngr->pauseMusic();
|
||||||
_State = Paused;
|
_State = Paused;
|
||||||
|
|
||||||
|
if (_PlayStart > 0)
|
||||||
|
_PauseTime = CTime::getLocalTime() - _PlayStart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +237,8 @@ void CMusicPlayer::stop ()
|
||||||
// stop the music only if we are really playing (else risk to stop a background music!)
|
// stop the music only if we are really playing (else risk to stop a background music!)
|
||||||
SoundMngr->stopMusic(0);
|
SoundMngr->stopMusic(0);
|
||||||
_State = Stopped;
|
_State = Stopped;
|
||||||
|
_PlayStart = 0;
|
||||||
|
_PauseTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
|
@ -176,12 +248,13 @@ void CMusicPlayer::previous ()
|
||||||
if (!_Songs.empty())
|
if (!_Songs.empty())
|
||||||
{
|
{
|
||||||
// Point the previous song
|
// Point the previous song
|
||||||
if (_CurrentSong == 0)
|
sint index;
|
||||||
_CurrentSong = (uint)_Songs.size()-1;
|
if (_CurrentSongIndex == 0)
|
||||||
|
index = (uint)_Songs.size()-1;
|
||||||
else
|
else
|
||||||
_CurrentSong--;
|
index = _CurrentSongIndex-1;
|
||||||
|
|
||||||
play ();
|
play(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,9 +264,11 @@ void CMusicPlayer::next ()
|
||||||
{
|
{
|
||||||
if (!_Songs.empty())
|
if (!_Songs.empty())
|
||||||
{
|
{
|
||||||
_CurrentSong++;
|
sint index = _CurrentSongIndex+1;
|
||||||
_CurrentSong%=_Songs.size();
|
if (index == _Songs.size())
|
||||||
play ();
|
index = 0;
|
||||||
|
|
||||||
|
play(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,17 +280,35 @@ void CMusicPlayer::update ()
|
||||||
return;
|
return;
|
||||||
if (_State == Playing)
|
if (_State == Playing)
|
||||||
{
|
{
|
||||||
|
CViewText *pVT = dynamic_cast<CViewText*>(CWidgetManager::getInstance()->getElementFromId("ui:interface:mp3_player:screen:text"));
|
||||||
|
if (pVT)
|
||||||
|
{
|
||||||
|
TTime dur = (CTime::getLocalTime() - _PlayStart) / 1000;
|
||||||
|
uint min = (dur / 60) % 60;
|
||||||
|
uint sec = dur % 60;
|
||||||
|
uint hour = dur / 3600;
|
||||||
|
|
||||||
|
std::string title(toString("%02d:%02d", min, sec));
|
||||||
|
if (hour > 0) title = toString("%02d:", hour) + title;
|
||||||
|
title += " " + _CurrentSong.Title;
|
||||||
|
pVT->setText(ucstring::makeFromUtf8(title));
|
||||||
|
}
|
||||||
|
|
||||||
if (SoundMngr->isMusicEnded ())
|
if (SoundMngr->isMusicEnded ())
|
||||||
{
|
{
|
||||||
// Point the next song
|
// select next song from playlist
|
||||||
_CurrentSong++;
|
sint index = _CurrentSongIndex + 1;
|
||||||
_CurrentSong%=_Songs.size();
|
if (isRepeatEnabled() || index < _Songs.size())
|
||||||
|
|
||||||
// End of the playlist ?
|
|
||||||
if (_CurrentSong != 0)
|
|
||||||
{
|
{
|
||||||
// No, play the next song
|
if (index == _Songs.size())
|
||||||
play ();
|
{
|
||||||
|
index = 0;
|
||||||
|
|
||||||
|
if (isShuffleEnabled())
|
||||||
|
shuffleAndRebuildPlaylist();
|
||||||
|
}
|
||||||
|
|
||||||
|
play(index);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -284,22 +377,10 @@ public:
|
||||||
// no format supported
|
// no format supported
|
||||||
if (extensions.empty()) return;
|
if (extensions.empty()) return;
|
||||||
|
|
||||||
bool oggSupported = false;
|
|
||||||
bool mp3Supported = false;
|
|
||||||
|
|
||||||
std::string message;
|
std::string message;
|
||||||
for(uint i = 0; i < extensions.size(); ++i)
|
for(uint i = 0; i < extensions.size(); ++i)
|
||||||
{
|
{
|
||||||
if (extensions[i] == "ogg")
|
message += " " + extensions[i];
|
||||||
{
|
|
||||||
oggSupported = true;
|
|
||||||
message += " ogg";
|
|
||||||
}
|
|
||||||
else if (extensions[i] == "mp3")
|
|
||||||
{
|
|
||||||
mp3Supported = true;
|
|
||||||
message += " mp3";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
message += " m3u m3u8";
|
message += " m3u m3u8";
|
||||||
nlinfo("Media player supports: '%s'", message.substr(1).c_str());
|
nlinfo("Media player supports: '%s'", message.substr(1).c_str());
|
||||||
|
@ -316,15 +397,9 @@ public:
|
||||||
for (i = 0; i < filesToProcess.size(); ++i)
|
for (i = 0; i < filesToProcess.size(); ++i)
|
||||||
{
|
{
|
||||||
std::string ext = toLower(CFile::getExtension(filesToProcess[i]));
|
std::string ext = toLower(CFile::getExtension(filesToProcess[i]));
|
||||||
if (ext == "ogg")
|
if (std::find(extensions.begin(), extensions.end(), ext) != extensions.end())
|
||||||
{
|
{
|
||||||
if (oggSupported)
|
filenames.push_back(filesToProcess[i]);
|
||||||
filenames.push_back(filesToProcess[i]);
|
|
||||||
}
|
|
||||||
else if (ext == "mp3" || ext == "mp2" || ext == "mp1")
|
|
||||||
{
|
|
||||||
if (mp3Supported)
|
|
||||||
filenames.push_back(filesToProcess[i]);
|
|
||||||
}
|
}
|
||||||
else if (ext == "m3u" || ext == "m3u8")
|
else if (ext == "m3u" || ext == "m3u8")
|
||||||
{
|
{
|
||||||
|
@ -345,14 +420,6 @@ public:
|
||||||
std::vector<CMusicPlayer::CSongs> songs;
|
std::vector<CMusicPlayer::CSongs> songs;
|
||||||
for (i=0; i<filenames.size(); i++)
|
for (i=0; i<filenames.size(); i++)
|
||||||
{
|
{
|
||||||
// '@' in filenames are reserved for .bnp files
|
|
||||||
// and sound system fails to open such file
|
|
||||||
if (filenames[i].find("@") != string::npos)
|
|
||||||
{
|
|
||||||
nlwarning("Ignore media file containing '@' in name: '%s'", filenames[i].c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CFile::fileExists(filenames[i])) {
|
if (!CFile::fileExists(filenames[i])) {
|
||||||
nlwarning("Ignore non-existing file '%s'", filenames[i].c_str());
|
nlwarning("Ignore non-existing file '%s'", filenames[i].c_str());
|
||||||
continue;
|
continue;
|
||||||
|
@ -360,12 +427,21 @@ public:
|
||||||
|
|
||||||
CMusicPlayer::CSongs song;
|
CMusicPlayer::CSongs song;
|
||||||
song.Filename = filenames[i];
|
song.Filename = filenames[i];
|
||||||
|
// TODO: cache the result for next refresh
|
||||||
SoundMngr->getMixer()->getSongTitle(filenames[i], song.Title, song.Length);
|
SoundMngr->getMixer()->getSongTitle(filenames[i], song.Title, song.Length);
|
||||||
songs.push_back (song);
|
if (song.Length > 0)
|
||||||
|
songs.push_back (song);
|
||||||
}
|
}
|
||||||
|
|
||||||
MusicPlayer.playSongs(songs);
|
MusicPlayer.playSongs(songs);
|
||||||
}
|
}
|
||||||
|
else if (Params == "update_playlist")
|
||||||
|
{
|
||||||
|
if (MusicPlayer.isShuffleEnabled())
|
||||||
|
MusicPlayer.shuffleAndRebuildPlaylist();
|
||||||
|
|
||||||
|
MusicPlayer.rebuildPlaylist();
|
||||||
|
}
|
||||||
else if (Params == "previous")
|
else if (Params == "previous")
|
||||||
MusicPlayer.previous();
|
MusicPlayer.previous();
|
||||||
else if (Params == "play")
|
else if (Params == "play")
|
||||||
|
|
|
@ -55,14 +55,28 @@ public:
|
||||||
|
|
||||||
void update ();
|
void update ();
|
||||||
|
|
||||||
|
bool isRepeatEnabled() const;
|
||||||
|
bool isShuffleEnabled() const;
|
||||||
|
|
||||||
|
// Build playlist UI from songs
|
||||||
|
void rebuildPlaylist();
|
||||||
|
// Randomize playlist and rebuild the ui
|
||||||
|
void shuffleAndRebuildPlaylist();
|
||||||
|
// Update playlist active row
|
||||||
|
void updatePlaylist(sint prevIndex = -1);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// The playlist
|
// The playlist
|
||||||
uint _CurrentSong; // If (!_Songs.empty()) must always be <_Songs.size()
|
CSongs _CurrentSong;
|
||||||
|
uint _CurrentSongIndex; // If (!_Songs.empty()) must always be <_Songs.size()
|
||||||
std::vector<CSongs> _Songs;
|
std::vector<CSongs> _Songs;
|
||||||
|
|
||||||
// State
|
// State
|
||||||
enum TState { Stopped, Playing, Paused } _State;
|
enum TState { Stopped, Playing, Paused } _State;
|
||||||
|
|
||||||
|
TTime _PlayStart;
|
||||||
|
TTime _PauseTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern CMusicPlayer MusicPlayer;
|
extern CMusicPlayer MusicPlayer;
|
||||||
|
|
|
@ -3419,6 +3419,22 @@ void displayDebugClusters()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NLMISC_COMMAND(dumpFontTexture, "Write font texture to file", "")
|
||||||
|
{
|
||||||
|
CInterfaceManager *im = CInterfaceManager::getInstance();
|
||||||
|
if (TextContext)
|
||||||
|
{
|
||||||
|
std::string fname = CFile::findNewFile("font-texture.tga");
|
||||||
|
TextContext->dumpCacheTexture(fname.c_str());
|
||||||
|
im->displaySystemInfo(ucstring(fname + " created"), "SYS");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
im->displaySystemInfo(ucstring("Error: TextContext == NULL"), "SYS");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
void inGamePatchUncompleteWarning()
|
void inGamePatchUncompleteWarning()
|
||||||
|
|
|
@ -1404,13 +1404,12 @@ bool getRyzomModes(std::vector<NL3D::UDriver::CMode> &videoModes, std::vector<st
|
||||||
// **** Init Video Modes
|
// **** Init Video Modes
|
||||||
Driver->getModes(videoModes);
|
Driver->getModes(videoModes);
|
||||||
|
|
||||||
// Remove modes under 800x600 and get the unique strings
|
// Remove modes under 1024x768 (outgame ui limitation) and get the unique strings
|
||||||
sint i, j;
|
sint i, j;
|
||||||
for (i = 0; i < (sint)videoModes.size(); ++i)
|
for (i = 0; i < (sint)videoModes.size(); ++i)
|
||||||
{
|
{
|
||||||
if ((videoModes[i].Width < 800) || (videoModes[i].Height < 600))
|
if ((videoModes[i].Width < 1024) || (videoModes[i].Height < 768))
|
||||||
{
|
{
|
||||||
// discard modes under 800x600
|
|
||||||
videoModes.erase(videoModes.begin()+i);
|
videoModes.erase(videoModes.begin()+i);
|
||||||
--i;
|
--i;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue