From 60ecf625aa56002c44b6c4e7c4dd523c303152af Mon Sep 17 00:00:00 2001 From: n00b Date: Thu, 23 May 2024 23:07:11 -0500 Subject: [PATCH] GFX Primitives and Image drawing functions --- rcbasic_build/rcbasic4_changes.ods | Bin 31459 -> 30198 bytes rcbasic_runtime/irr_gfx/gui_freetype_font.cpp | 538 +++ rcbasic_runtime/irr_gfx/gui_freetype_font.h | 124 + rcbasic_runtime/irr_gfx/rc_font.h | 6 + rcbasic_runtime/irr_gfx/rc_gfx.h | 3171 +++++++++++++++++ rcbasic_runtime/irr_gfx/rc_utf8.h | 45 + 6 files changed, 3884 insertions(+) create mode 100644 rcbasic_runtime/irr_gfx/gui_freetype_font.cpp create mode 100644 rcbasic_runtime/irr_gfx/gui_freetype_font.h create mode 100644 rcbasic_runtime/irr_gfx/rc_font.h create mode 100644 rcbasic_runtime/irr_gfx/rc_gfx.h create mode 100644 rcbasic_runtime/irr_gfx/rc_utf8.h diff --git a/rcbasic_build/rcbasic4_changes.ods b/rcbasic_build/rcbasic4_changes.ods index 96e5acdb21890fddb81bfca8efd1d9128bbc02d4..a402f2584513e3d64706ecab48ec2881aea87111 100644 GIT binary patch delta 28296 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'Ih(^hlkfns`DAu#H(_Q zw5Gx3t!1x3?u3;VQ4!a&#Z)*HC1F3>lz5~_BEj&!Q zJCnksT0z&b!^4)j#~FC`@S*Amk3A{$wixfu(5qxq8#ERICPDRkysQVGSZT3-Z)3ST zX?)>+?Yn#&LxAIgu0`AQRxkf5MPLck<`){OSZgCK{IifE{I!ghx^cRmV{e22I5vkp z=&7*NhmgAdSbe|D%V%Z7_h))oth5vfM7+a|K&j@qXDMHrf^6Dq%}AYOqZ)DM7$@VG z%=%~C9rW>=H(op{8!0p^amB*fvu?Z?kB|38)|X%URDaQ6&aG6Eh!PB9D}Ip>%dDhf z*b6=5jUw%}#VPmJ@^X_8th1mvi6a|*@ieYHUDaH#Je^Xm^@Fe0mCJaLFw#T5yRMWp zRu~JE53_r{R>*2Z$G*Q55p0n55GzKA^zVqjIRayw9v^&B$Ki^f9v4TOj z(EA^gDlUmen&PAV7JD|q#Wwyz$;LVf+JtPg74Tc&Rup@sHCYqHD6aSk0`LffCk7I{ zZB=}_i&%}Fef{U0V(SUPLAZM05f_8vFXXC8b*ox|2>@_%KcMi*xg z+73H z#(M5LxxAgGjt?Ed*5rJtnNG|+iNskw7IG76`#j_IkCE-6853cO$BOd|_`$r+sHY@5p|bW%1|`zUz565q#LU@O zoy=Ek28z9Rg!~@v3LY5-(*YIquIcLdQER^e&mLnj{Gy{b!IGa$S(xDhq^PEyOYkvg z-nCQGC5%L}l5Ew^=&i(mSJMpp5IwUy>CP~;&bDX}gTEH|8S2G#Caa~2Z9Flt_q$qh zTH;*gW9Hlt?SA{RGH%TtvFsfM%H_@rP#;=x|CsB6d@YRaQ9X5$P-?S*u!X$%jaj3a zk2;82>cSJy1pZ9g!-=!yIrX#C3-LTzGn_{`<$Q5}0 z*)c6#FbavLLt@y{F6CT}25rHa1W2*yFi@PJM|+#58U1*9`SYisTc~*=JB5Z_KJVF4 z=dB)b?~Q3N!=`mjP0GLIHEU1cT>YZXv!B(f6W86%RJI>Zq0q3jLB?+VSna&TD(7L4 z$jXtSF~sSH?=ZMjtnR0_NsDTXXuS?y+!L$@kGW4+Qlh?-K5{qx7myEeYi`kiq1wVyWF(*LI@??DndB7EU?QCER?Z zl6ymkAp))nC`9YV^n}_nrp?)^N*sPcl_7H#e5S;PCX`{iYSv8f-EZqUeNJC$g@MZf z zTeEN~sgIZqSOMWmD5Be|t6lnK&=DjJ!i%&P4`a#}s-JiXz@~}*X%_3VT<3AY3P6c( z>}r9U+or@{`s}k4SflT7qdhOzl%+Z8ELXBFZOX=``D3~|qgXu|lPzH0qjK_V`W(6UzJS`C^Hk~}E(^;eX1d-$%MQ5_)jkYWo{9#P66{@qA2`Xh^OwOEC&N}8 zl1$R8aFycUi+5{kwOu3tSJ>YJ022>h9O9f;QCsyT9O4dChRoCGnABG*!^Np~rY|*u z#XDwjj~uC7%Ys$W)A@qrFDW>Bk|)I{cTVrtf`wKC7tjIHC~TCfI_EM<<;t(I6W0%u z_EEx?>PDuOcK$;Hty2x-9frSs5~!x`)N8<4Nl@S(W zrHD~|4gfV#66Slum0+c%(V7`t=+08uBA89Ff4Gp2afT}Q3`t`=Ky_x4!~~|aIN|KjN@PtDE`SfPN3?n zPH&-qrh(j{!6%i|MO6cW)P2u)xr@`K_&qEO-K^jz__lheN-IU1qbh2VB{E!YT>~&f z?Q8lR1P{#0DeLo+*QB_pP&9W$M;}@g)m-p4 zPPuyMKuJ;I@6|1$TLY_Lk6mKRJa?GsF1VJqHRdHM&qA{3-dF*)n-t<^Xjo$-j}~D} zzK{G}kTe%FxNDf=59uo^sL_YRvP;*HI$W3R^23(6@)u-@Cx6e6Q_ZaW$hq1S=OASj z(}t}y4rXU!t(v*KQ-rOt;M+(+`=TAsJD55%M*Va`V3l1j z)Ba*%ZEI$it5bb59NXZdnmEaV#;|FE{+!i>8Fq7PNM#doQK0YgRh|;A z4g)5&s^*v&ot@N|J%((HwkcfZCP!T^Q*4^gN`Ki^X_6AP!0`sx%ji3kP&jDV4&I94 zZD3aR*lu`&6>*~TZw3WZ__-by<>R4R(Pwn5@9StfITagi13lgiM80-(*B|1X(uc7q zUrlwW%f4X`0@RsgSWjPhJoDKY*p&($0Ux=2Uy5e8r%eK=XNn-^6v2KbZfj&^D5Cf$tx5Q+3CpxZC`zx$?4FAABjkV7jeLZTEQO09U zZ5Dv~A&~~Ze)sezitL5z{6~DX?xUmRa2x@VK$HD>sb1Dee+I#~FVgb9Wm|aa+poWW z9rxGME$5R66L>y#=#b^sJ1vI!wc>kCt>zajmhzQTNx85NNPPES29tn}<^_z`P;}%c z)I(#buM>XrU48}96qAvl!j5japI!<0=}TEakJRYF6JO3~KTg^^@rMt88M$ zUQW7ZI}~zZIqY*wAc7KTLeR+1`dJO?F`J;~^CXnXCTzI#6s+dom(vBdmS-M~az=(Z zz3zbWm|*@1QTdN&L&k(@o8Clwt2`}fO!i|gc= z-9dbEKtNAj9mdUVaIeV1a)bpz^aMI?-CBSnS{GM7c9{T=(Jd>8es(V8F@owk;qLL5 z<+d6baFfCpG6OxcbSAoNn{Ar?npH$EIBn5M?%nhP&L)H1I;FTQiwNcwMD6~a4bJXq z$VTG@ijKl{%@XJaW`#2$ln0Ww9e86XbQ>`?zS#>Khxu}Ze!xz3WR(FlcCT8^RX;Q1Hn4p;UPUHK6+2*k!}uqk zb#+SN$s8E~Ajby%j(7nDjSKh>dRnrV=YIj5)p*MPYe6^mCP~KgV)*xZ){7+Bz>EGr z<;DLgm;AR3-&^*-pGp6FuK4f!f9j$6(4+kWrSTuMjbsTQ#(&>(_921dPmc8=`SQ>6 z|Bch|57fs096o6O1HAG7Tm9ci9oqjpLpSRGRC&KrPk#0xq4?*^|C6g503iH7@8seo p^ZL^MSJQn-pkGvy1AM9et@FP}3g!RQaZyVK`;x-@x%^Z5{{W)zPq+X8 delta 29641 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%0AY@ z`44ad(*1+qN#ISRYKHxONW~l!T5dSm#zI2lH#k@3mfj7u!MUZFp+HJ-p>QD6zg#n%ln+B+O$Fn8k)t@4m zHkrDco6e^jhE)nrx4=09>6(Omr$o8=cTd4t^a;dr5N0%P4P?C4Ck04za~WS+@$-04 zK!64#re=mv2}1jP9Qi_tLB5hHhKANvZ>hbWp>r+F-L$!e$VG=~az$0?HT6FJVhwU9 ziq4CIV}yj<3a94qyZH_ri8X5#P{DRWKb+VX#t>F*^)1EGpQKo~s0|Hwzig**P2+GCfa+`s8#bSJ5QKQ){r|NEFUuEB}r-}cUb z_q95c#(|t!|L1&^^M9Rhb^i2kAH0hs`ad#5|D!|{r{VIB@}Hi%d#f1)sA^3BRF(k^ zn!o?giy`ip3#rCGWB#|Y4bp!GKwB2*|B)*C|9;1RYa40(Z>fm?JF{3ErYkAcKQsUT zMI-(*`3Jl>Iah{%2Ys#cQ{jSsMOLWp1pa?UKwHOO;$mEBCI6Y}fB&PX|7Vb)764%S e&ECb_-sK-fC1rVdgn!OsL*M4m8Liy@Ir@JXtjGue diff --git a/rcbasic_runtime/irr_gfx/gui_freetype_font.cpp b/rcbasic_runtime/irr_gfx/gui_freetype_font.cpp new file mode 100644 index 0000000..fbbefc0 --- /dev/null +++ b/rcbasic_runtime/irr_gfx/gui_freetype_font.cpp @@ -0,0 +1,538 @@ +#include "gui_freetype_font.h" + +#if COMPILE_WITH_FREETYPE + +#include + +using namespace irr; +using namespace gui; + +#ifdef _MSC_VER +#pragma warning(disable: 4996) +#endif + + +// -------------------------------------------------------- +CGUITTGlyph::CGUITTGlyph() +: IReferenceCounted() + ,cached(false) + ,size(0) + ,top(0) + ,left(0) + ,texw(0) + ,texh(0) + ,imgw(0) + ,imgh(0) + ,tex(NULL) + ,top16(0) + ,left16(0) + ,texw16(0) + ,texh16(0) + ,imgw16(0) + ,imgh16(0) + ,tex16(NULL) + ,image(NULL) +{ +} + +CGUITTGlyph::~CGUITTGlyph() +{ + delete[] image; +} + +//void CGUITTGlyph::cache(u32 idx_, CGUITTFace& ttFace_, video::IVideoDriver* driver_, irr::core::dimension2d &largestSize) +void CGUITTGlyph::cache(u32 idx_, const CGUIFreetypeFont * freetypeFont) +{ + assert(freetypeFont); + + FT_Face face = freetypeFont->TrueTypeFace->face; + + FT_Set_Pixel_Sizes(face, 0, size); + if ( size > freetypeFont->LargestGlyph.Height ) + freetypeFont->LargestGlyph.Height = size; + + if ( !FT_Load_Glyph(face, idx_, FT_LOAD_NO_HINTING|FT_LOAD_NO_BITMAP) ) + { + FT_GlyphSlot glyph = face->glyph; + FT_Bitmap bits; + + if (glyph->format == ft_glyph_format_outline ) + { + if (!FT_Render_Glyph( glyph, FT_RENDER_MODE_NORMAL)) + { + bits = glyph->bitmap; + u8 *pt = bits.buffer; + delete[] image; + image = new u8[bits.width * bits.rows]; + memcpy(image,pt,bits.width * bits.rows); + top = glyph->bitmap_top; + left = glyph->bitmap_left; + imgw = 1; + imgh = 1; + texw = bits.width; + texh = bits.rows; + for(;;) + { + if (imgw > texw) + { + break; + } + else + { + imgw <<= 1; + } + } + for(;;) + { + if (imgh > texh) + { + break; + } + else + { + imgh <<= 1; + } + } + if (imgw > imgh) + { + imgh = imgw; + } + else + { + imgw = imgh; + } + + s32 offx = left; + s32 offy = size - top; + if ( offx+texw > freetypeFont->LargestGlyph.Width ) + freetypeFont->LargestGlyph.Width = offx+texw; + if ( offy+texh > freetypeFont->LargestGlyph.Height ) + freetypeFont->LargestGlyph.Height = offy+texh; + + u32 *texd = new u32[imgw*imgh]; + memset(texd,0,imgw*imgh*sizeof(u32)); + u32 *texp = texd; + bool cflag = (freetypeFont->Driver->getDriverType() == video::EDT_DIRECT3D9); + for (int i = 0;i < bits.rows;i++) + { + u32 *rowp = texp; + for (int j = 0;j < bits.width;j++) + { + if (*pt) + { + if (cflag) + { + *rowp = *pt; + *rowp *= 0x01010101; + } + else + { + *rowp = *pt << 24; + *rowp |= 0xffffff; + } + } + else + { + *rowp = 0; + } + pt++; + rowp++; + } + texp += imgw; + } + + c8 name[128]; + sprintf(name,"ttf%d_%d_%p",idx_, size, freetypeFont ); + video::IImage *img = freetypeFont->Driver->createImageFromData(video::ECF_A8R8G8B8,core::dimension2d(imgw,imgh),texd); + setGlyphTextureFlags(freetypeFont->Driver); + tex = freetypeFont->Driver->addTexture(name,img); + img->drop(); + restoreTextureFlags(freetypeFont->Driver); + delete[] texd; + cached = true; + } + } + } + + if (!FT_Load_Glyph(face,idx_,FT_LOAD_NO_HINTING|FT_LOAD_RENDER|FT_LOAD_MONOCHROME)) + { + FT_GlyphSlot glyph = face->glyph; + FT_Bitmap bits = glyph->bitmap; + u8 *pt = bits.buffer; + top16 = glyph->bitmap_top; + left16 = glyph->bitmap_left; + imgw16 = 1; + imgh16 = 1; + texw16 = bits.width; + texh16 = bits.rows; + for(;;) + { + if (imgw16 >= texw16) + { + break; + } + else + { + imgw16 <<= 1; + } + } + for(;;) + { + if (imgh16 >= texh16) + { + break; + } + else + { + imgh16 <<= 1; + } + } + if (imgw16 > imgh16) + { + imgh16 = imgw16; + } + else + { + imgw16 = imgh16; + } + + s32 offx = left; + s32 offy = size - top; + if ( offx+texw > freetypeFont->LargestGlyph.Width ) + freetypeFont->LargestGlyph.Width = offx+texw; + if ( offy+texh > freetypeFont->LargestGlyph.Height ) + freetypeFont->LargestGlyph.Height = offy+texh; + + + u16 *texd16 = new u16[imgw16*imgh16]; + memset(texd16,0,imgw16*imgh16*sizeof(u16)); + u16 *texp16 = texd16; + for (int y = 0;y < bits.rows;y++) + { + u16 *rowp = texp16; + for (int x = 0;x < bits.width;x++) + { + if (pt[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8))) + { + *rowp = 0xffff; + } + rowp++; + } + texp16 += imgw16; + } + c8 name[128]; + sprintf(name,"ttf%d_%d_%p_16",idx_, size, freetypeFont ); + video::IImage *img = freetypeFont->Driver->createImageFromData(video::ECF_A1R5G5B5,core::dimension2d(imgw16,imgh16),texd16); + setGlyphTextureFlags(freetypeFont->Driver); + tex16 = freetypeFont->Driver->addTexture(name,img); + img->drop(); + restoreTextureFlags(freetypeFont->Driver); +// freetypeFont->Driver->makeColorKeyTexture(tex16,video::SColor(0,0,0,0)); + delete[] texd16; + } +} + +bool CGUITTGlyph::mTexFlag16 = false; +bool CGUITTGlyph::mTexFlag32 = true; +bool CGUITTGlyph::mTexFlagMip = false; + +void CGUITTGlyph::setGlyphTextureFlags(video::IVideoDriver* driver_) +{ + mTexFlag16 = driver_->getTextureCreationFlag(video::ETCF_ALWAYS_16_BIT); + mTexFlag32 = driver_->getTextureCreationFlag(video::ETCF_ALWAYS_32_BIT); + mTexFlagMip = driver_->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); + driver_->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT,false); + driver_->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT,true); + driver_->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); +} + +void CGUITTGlyph::restoreTextureFlags(video::IVideoDriver* driver_) +{ + driver_->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT, mTexFlag16); + driver_->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, mTexFlag32); + driver_->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, mTexFlagMip); +} + +// -------------------------------------------------------- +FT_Library CGUITTFace::library = 0; +int CGUITTFace::countClassObjects = 0; + +CGUITTFace::CGUITTFace() +: face(0) +{ + ++countClassObjects; +} + +CGUITTFace::~CGUITTFace() +{ + if ( face ) + FT_Done_Face( face ); + + --countClassObjects; + assert(countClassObjects >= 0 ); + if ( !countClassObjects && library ) + { + FT_Done_FreeType( library ); + library = 0; + } +} + +//! loads a font file +bool CGUITTFace::load(const irr::io::path& filename) +{ + if ( !library ) + { + if (FT_Init_FreeType( &library )) + { + return false; + } + } + core::stringc ansiFilename(filename); // path can be anything but freetype can only work with ansi-filenames + if (FT_New_Face( library,ansiFilename.c_str(),0,&face )) + { + return false; + } + return true; +} + +// -------------------------------------------------------- +//! constructor +CGUIFreetypeFont::CGUIFreetypeFont(video::IVideoDriver* driver) +: Driver(driver) +, TrueTypeFace(0) +{ + #ifdef _DEBUG + setDebugName("CGUIFreetypeFont"); + #endif + + if (Driver) + Driver->grab(); + AntiAlias = false; + Transparency = false; +} + + + +//! destructor +CGUIFreetypeFont::~CGUIFreetypeFont() +{ + if ( TrueTypeFace ) + TrueTypeFace->drop(); + if (Driver) + Driver->drop(); + clearGlyphs(); +} + +bool CGUIFreetypeFont::attach(CGUITTFace *Face,u32 size) +{ + if (!Driver || !Face) + return false; + + Face->grab(); + if ( TrueTypeFace ) + TrueTypeFace->drop(); + TrueTypeFace = Face; + if ( !TrueTypeFace ) + return false; + + clearGlyphs(); + Glyphs.reallocate(TrueTypeFace->face->num_glyphs); + Glyphs.set_used(TrueTypeFace->face->num_glyphs); + for (int i = 0;i < TrueTypeFace->face->num_glyphs;i++) + { + CGUITTGlyph * glyph = new CGUITTGlyph(); + + glyph->size = size; +// glyph->cache((wchar_t)i + 1); + + Glyphs[i] = glyph; + } + + // TODO: this is a workaround to get a probably ok height for getDimensions. So we check a few extreme characters which usually make trouble. + getGlyphByChar(L'A'); + getGlyphByChar(L'g'); + getGlyphByChar(L'.'); + getGlyphByChar(L'('); + + return true; +} + +void CGUIFreetypeFont::clearGlyphs() +{ + for ( unsigned int i=0; i < Glyphs.size(); ++i ) + { + if ( Glyphs[i] ) + { + Glyphs[i]->drop(); + } + Glyphs[i] = 0; + } +} + +u32 CGUIFreetypeFont::getGlyphByChar(wchar_t c) const +{ + u32 idx = FT_Get_Char_Index( TrueTypeFace->face, c ); + if ( idx && !Glyphs[idx - 1]->cached ) + Glyphs[idx - 1]->cache(idx, this); + return idx; +} + +//! returns the dimension of a text +core::dimension2d CGUIFreetypeFont::getDimension(const wchar_t* text) const +{ + core::dimension2d dim(0, Glyphs[0]->size); + + for(const wchar_t* p = text; *p; ++p) + { + dim.Width += getWidthFromCharacter(*p); + } + + // TODO: The correct solution might be working with TrueTypeFace->height but I can't figure out how to use units_per_EM + // even if I know which FT_Render_Mode I used. I'm sure there is some way to figure that out, but I have to give up for now. + if ( TrueTypeFace && LargestGlyph.Height > dim.Height) + dim.Height = LargestGlyph.Height; + + return dim; +} + + +inline u32 CGUIFreetypeFont::getWidthFromCharacter(wchar_t c) const +{ + u32 n = getGlyphByChar(c); + if ( n > 0) + { + int w = Glyphs[n - 1]->texw; + s32 left = Glyphs[n - 1]->left; + if (w + left > 0) + return w + left; + } + if (c >= 0x2000) + { + return Glyphs[0]->size; + } + else + { + return Glyphs[0]->size / 2; + } +} + + +//! draws an text and clips it to the specified rectangle if wanted +void CGUIFreetypeFont::draw(const irr::core::stringw& textstring, const irr::core::rect& position, video::SColor color, bool hcenter, bool vcenter, const core::rect* clip) +{ + if (!Driver) + return; + + core::dimension2d textDimension; + core::position2d offset = position.UpperLeftCorner; + video::SColor colors[4]; + for (int i = 0;i < 4;i++) + { + colors[i] = color; + } + + const wchar_t * text = textstring.c_str(); + if (hcenter || vcenter) + { + textDimension = getDimension(text); + + if (hcenter) + offset.X = ((position.getWidth() - textDimension.Width)>>1) + offset.X; + + if (vcenter) + offset.Y = ((position.getHeight() - textDimension.Height)>>1) + offset.Y; + } + + u32 n; + + while(*text) + { + n = getGlyphByChar(*text); + if ( n > 0) + { + if (AntiAlias) + { +// s32 imgw = Glyphs[n-1]->imgw; +// s32 imgh = Glyphs[n-1]->imgh; + s32 texw = Glyphs[n-1]->texw; + s32 texh = Glyphs[n-1]->texh; + s32 offx = Glyphs[n-1]->left; + s32 offy = Glyphs[n-1]->size - Glyphs[n-1]->top; + if (Driver->getDriverType() != video::EDT_SOFTWARE) + { + if (!Transparency) + color.color |= 0xff000000; + Driver->draw2DImage(Glyphs[n-1]->tex,core::position2d(offset.X+offx,offset.Y+offy),core::rect(0,0,texw,texh),clip,color,true); + } + else + { + s32 a = color.getAlpha(); + s32 r = color.getRed(); + s32 g = color.getGreen(); + s32 b = color.getBlue(); + u8 *pt = Glyphs[n-1]->image; + if (!Transparency) a = 255; + for (int y = 0;y < texh;y++) + { + for (int x = 0;x < texw;x++) + { + if (!clip || clip->isPointInside(core::position2d(offset.X+x+offx,offset.Y+y+offy))) + { + if (*pt) + { + Driver->draw2DRectangle(video::SColor((a * *pt)/255,r,g,b),core::rect(offset.X+x+offx,offset.Y+y+offy,offset.X+x+offx+1,offset.Y+y+offy+1)); + } + pt++; + } + } + } + } + } + else + { +// s32 imgw = Glyphs[n-1]->imgw16; +// s32 imgh = Glyphs[n-1]->imgh16; + s32 texw = Glyphs[n-1]->texw16; + s32 texh = Glyphs[n-1]->texh16; + s32 offx = Glyphs[n-1]->left16; + s32 offy = Glyphs[n-1]->size - Glyphs[n-1]->top16; + if (!Transparency) + { + color.color |= 0xff000000; + } + Driver->draw2DImage(Glyphs[n-1]->tex16, + core::position2d(offset.X+offx,offset.Y+offy), + core::rect(0,0,texw,texh), + clip, color, true); + } + offset.X += getWidthFromCharacter(*text); + } + else + { + offset.X += getWidthFromCharacter(*text); + } + + ++text; + } +} + +//! Calculates the index of the character in the text which is on a specific position. +s32 CGUIFreetypeFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const +{ + s32 x = 0; + s32 idx = 0; + + while (text[idx]) + { + x += getWidthFromCharacter(text[idx]); + + if (x >= pixel_x) + return idx; + + ++idx; + } + + return -1; +} + +#endif // #if COMPILE_WITH_FREETYPE diff --git a/rcbasic_runtime/irr_gfx/gui_freetype_font.h b/rcbasic_runtime/irr_gfx/gui_freetype_font.h new file mode 100644 index 0000000..8b9b265 --- /dev/null +++ b/rcbasic_runtime/irr_gfx/gui_freetype_font.h @@ -0,0 +1,124 @@ +#ifndef _GUI_FREETYPE_FONT_H +#define _GUI_FREETYPE_FONT_H + +//! freetype support enabled with 1 and disabled with 0 +#define COMPILE_WITH_FREETYPE 1 + + +#if COMPILE_WITH_FREETYPE + +#include +#include +#include + +class CGUITTFace : public irr::IReferenceCounted +{ +public: + CGUITTFace(); + virtual ~CGUITTFace(); + + bool load(const irr::io::path& filename); + + FT_Face face; // handle to face + +private: + static int countClassObjects; + static FT_Library library; // handle to library +}; + +class CGUIFreetypeFont; + +class CGUITTGlyph : public irr::IReferenceCounted +{ +public: + CGUITTGlyph(); + virtual ~CGUITTGlyph(); + + bool cached; + void cache(irr::u32 idx_, const CGUIFreetypeFont * freetypeFont); + + irr::u32 size; + irr::u32 top; + irr::u32 left; + irr::u32 texw; + irr::u32 texh; + irr::u32 imgw; + irr::u32 imgh; + irr::video::ITexture *tex; + irr::u32 top16; + irr::u32 left16; + irr::u32 texw16; + irr::u32 texh16; + irr::u32 imgw16; + irr::u32 imgh16; + irr::video::ITexture *tex16; + irr::u8 *image; + +private: + void setGlyphTextureFlags(irr::video::IVideoDriver* driver_); + void restoreTextureFlags(irr::video::IVideoDriver* driver_); + + static bool mTexFlag16; + static bool mTexFlag32; + static bool mTexFlagMip; +}; + +class CGUIFreetypeFont : public irr::gui::IGUIFont +{ + friend class CGUITTGlyph; + +public: + + //! constructor + CGUIFreetypeFont(irr::video::IVideoDriver* Driver); + + //! destructor + virtual ~CGUIFreetypeFont(); + + //! loads a truetype font file + bool attach(CGUITTFace *Face,irr::u32 size); + + //! draws an text and clips it to the specified rectangle if wanted + virtual void draw(const irr::core::stringw& text, const irr::core::rect& position, irr::video::SColor color, bool hcenter=false, bool vcenter=false, const irr::core::rect* clip=0); + + //! returns the dimension of a text + virtual irr::core::dimension2d getDimension(const wchar_t* text) const; + + //! Calculates the index of the character in the text which is on a specific position. + virtual irr::s32 getCharacterFromPos(const wchar_t* text, irr::s32 pixel_x) const; + + //! Not yet supported + virtual void setKerningWidth (irr::s32 kerning) {} + + //! Not yet supported + virtual void setKerningHeight (irr::s32 kerning) {} + + //! Not yet supported + virtual irr::s32 getKerningWidth(const wchar_t* thisLetter=0, const wchar_t* previousLetter=0) const { return 0; } + + //! Not yet supported + virtual irr::s32 getKerningHeight() const { return 0; } + + //! Not yet supported + virtual void setInvisibleCharacters( const wchar_t *s ) {} + + + bool AntiAlias; + bool Transparency; + +protected: + void clearGlyphs(); + +private: + irr::u32 getWidthFromCharacter(wchar_t c) const; + irr::u32 getGlyphByChar(wchar_t c) const; + irr::video::IVideoDriver* Driver; + irr::core::array< CGUITTGlyph* > Glyphs; + CGUITTFace * TrueTypeFace; + mutable irr::core::dimension2d LargestGlyph; +}; + +#endif // #if COMPILE_WITH_FREETYPE + +#endif // _GUI_FREETYPE_FONT_H + diff --git a/rcbasic_runtime/irr_gfx/rc_font.h b/rcbasic_runtime/irr_gfx/rc_font.h new file mode 100644 index 0000000..5d1d81f --- /dev/null +++ b/rcbasic_runtime/irr_gfx/rc_font.h @@ -0,0 +1,6 @@ +#ifndef RC_FONT_H_INCLUDED +#define RC_FONT_H_INCLUDED + + + +#endif // RC_FONT_H_INCLUDED diff --git a/rcbasic_runtime/irr_gfx/rc_gfx.h b/rcbasic_runtime/irr_gfx/rc_gfx.h new file mode 100644 index 0000000..5a7675b --- /dev/null +++ b/rcbasic_runtime/irr_gfx/rc_gfx.h @@ -0,0 +1,3171 @@ +#ifndef RC_GFX_INCLUDED +#define RC_GFX_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gui_freetype_font.h" +#include "rc_utf8.h" + +using namespace irr; + +using namespace core; +using namespace video; +using namespace scene; + + +#define MAX_JOYSTICKS 8 + +#define MAX_FINGERS 10 + +#define MAX_ACCELS 20 +#define MAX_GYROS 20 + +SDL_Joystick * rc_joystick[MAX_JOYSTICKS]; +int rc_joy_axis[MAX_JOYSTICKS][100]; +int rc_numJoysticks = 0; +int rc_joybutton[MAX_JOYSTICKS][100]; +SDL_JoystickID rc_joyID[MAX_JOYSTICKS]; + +SDL_Joystick * tmp_joy; +SDL_JoystickID tmp_joy_id; +int tmp_joy_flag = 0; + +SDL_Haptic * rc_haptic[MAX_JOYSTICKS]; //1 for each joystick + +double rc_pressure = 0; +int rc_touchX = 0; +int rc_touchY = 0; +int rc_motionX = 0; +int rc_motionY = 0; +int rc_touch = 0; +int rc_mt_status = 0; +int rc_mt_x = 0; +int rc_mt_y = 0; +int rc_mt_numFingers = 0; +double rc_mt_theta = 0; +double rc_mt_dist = 0; +SDL_TouchID rc_touchDevice; +SDL_Finger rc_finger[MAX_FINGERS]; +set rc_fingers_pressed; + +SDL_Sensor * rc_accel[MAX_ACCELS]; +int num_accels = 0; + +SDL_Sensor * rc_gyro[MAX_GYROS]; +int num_gyros = 0; + + +struct SDLKeyMap +{ + SDLKeyMap() {} + SDLKeyMap(s32 x11, s32 win32) + : SDLKey(x11), Win32Key(win32) + { + } + + s32 SDLKey; + s32 Win32Key; + + bool operator<(const SDLKeyMap& o) const + { + return SDLKey KeyMap; + +void createKeyMap() +{ + // I don't know if this is the best method to create + // the lookuptable, but I'll leave it like that until + // I find a better version. + + KeyMap.reallocate(105); + + // buttons missing + + KeyMap.push_back(SDLKeyMap(SDLK_BACKSPACE, irr::EKEY_CODE::KEY_BACK)); + KeyMap.push_back(SDLKeyMap(SDLK_TAB, irr::EKEY_CODE::KEY_TAB)); + KeyMap.push_back(SDLKeyMap(SDLK_CLEAR, irr::EKEY_CODE::KEY_CLEAR)); + KeyMap.push_back(SDLKeyMap(SDLK_RETURN, irr::EKEY_CODE::KEY_RETURN)); + + // combined modifiers missing + + KeyMap.push_back(SDLKeyMap(SDLK_PAUSE, irr::EKEY_CODE::KEY_PAUSE)); + KeyMap.push_back(SDLKeyMap(SDLK_CAPSLOCK, irr::EKEY_CODE::KEY_CAPITAL)); + + // asian letter keys missing + + KeyMap.push_back(SDLKeyMap(SDLK_ESCAPE, irr::EKEY_CODE::KEY_ESCAPE)); + + // asian letter keys missing + + KeyMap.push_back(SDLKeyMap(SDLK_SPACE, irr::EKEY_CODE::KEY_SPACE)); + KeyMap.push_back(SDLKeyMap(SDLK_PAGEUP, irr::EKEY_CODE::KEY_PRIOR)); + KeyMap.push_back(SDLKeyMap(SDLK_PAGEDOWN, irr::EKEY_CODE::KEY_NEXT)); + KeyMap.push_back(SDLKeyMap(SDLK_END, irr::EKEY_CODE::KEY_END)); + KeyMap.push_back(SDLKeyMap(SDLK_HOME, irr::EKEY_CODE::KEY_HOME)); + KeyMap.push_back(SDLKeyMap(SDLK_LEFT, irr::EKEY_CODE::KEY_LEFT)); + KeyMap.push_back(SDLKeyMap(SDLK_UP, irr::EKEY_CODE::KEY_UP)); + KeyMap.push_back(SDLKeyMap(SDLK_RIGHT, irr::EKEY_CODE::KEY_RIGHT)); + KeyMap.push_back(SDLKeyMap(SDLK_DOWN, irr::EKEY_CODE::KEY_DOWN)); + + // select missing + KeyMap.push_back(SDLKeyMap(SDLK_PRINTSCREEN, irr::EKEY_CODE::KEY_PRINT)); + // execute missing + KeyMap.push_back(SDLKeyMap(SDLK_PRINTSCREEN, irr::EKEY_CODE::KEY_SNAPSHOT)); + + KeyMap.push_back(SDLKeyMap(SDLK_INSERT, irr::EKEY_CODE::KEY_INSERT)); + KeyMap.push_back(SDLKeyMap(SDLK_DELETE, irr::EKEY_CODE::KEY_DELETE)); + KeyMap.push_back(SDLKeyMap(SDLK_HELP, irr::EKEY_CODE::KEY_HELP)); + + KeyMap.push_back(SDLKeyMap(SDLK_0, irr::EKEY_CODE::KEY_KEY_0)); + KeyMap.push_back(SDLKeyMap(SDLK_1, irr::EKEY_CODE::KEY_KEY_1)); + KeyMap.push_back(SDLKeyMap(SDLK_2, irr::EKEY_CODE::KEY_KEY_2)); + KeyMap.push_back(SDLKeyMap(SDLK_3, irr::EKEY_CODE::KEY_KEY_3)); + KeyMap.push_back(SDLKeyMap(SDLK_4, irr::EKEY_CODE::KEY_KEY_4)); + KeyMap.push_back(SDLKeyMap(SDLK_5, irr::EKEY_CODE::KEY_KEY_5)); + KeyMap.push_back(SDLKeyMap(SDLK_6, irr::EKEY_CODE::KEY_KEY_6)); + KeyMap.push_back(SDLKeyMap(SDLK_7, irr::EKEY_CODE::KEY_KEY_7)); + KeyMap.push_back(SDLKeyMap(SDLK_8, irr::EKEY_CODE::KEY_KEY_8)); + KeyMap.push_back(SDLKeyMap(SDLK_9, irr::EKEY_CODE::KEY_KEY_9)); + + KeyMap.push_back(SDLKeyMap(SDLK_a, irr::EKEY_CODE::KEY_KEY_A)); + KeyMap.push_back(SDLKeyMap(SDLK_b, irr::EKEY_CODE::KEY_KEY_B)); + KeyMap.push_back(SDLKeyMap(SDLK_c, irr::EKEY_CODE::KEY_KEY_C)); + KeyMap.push_back(SDLKeyMap(SDLK_d, irr::EKEY_CODE::KEY_KEY_D)); + KeyMap.push_back(SDLKeyMap(SDLK_e, irr::EKEY_CODE::KEY_KEY_E)); + KeyMap.push_back(SDLKeyMap(SDLK_f, irr::EKEY_CODE::KEY_KEY_F)); + KeyMap.push_back(SDLKeyMap(SDLK_g, irr::EKEY_CODE::KEY_KEY_G)); + KeyMap.push_back(SDLKeyMap(SDLK_h, irr::EKEY_CODE::KEY_KEY_H)); + KeyMap.push_back(SDLKeyMap(SDLK_i, irr::EKEY_CODE::KEY_KEY_I)); + KeyMap.push_back(SDLKeyMap(SDLK_j, irr::EKEY_CODE::KEY_KEY_J)); + KeyMap.push_back(SDLKeyMap(SDLK_k, irr::EKEY_CODE::KEY_KEY_K)); + KeyMap.push_back(SDLKeyMap(SDLK_l, irr::EKEY_CODE::KEY_KEY_L)); + KeyMap.push_back(SDLKeyMap(SDLK_m, irr::EKEY_CODE::KEY_KEY_M)); + KeyMap.push_back(SDLKeyMap(SDLK_n, irr::EKEY_CODE::KEY_KEY_N)); + KeyMap.push_back(SDLKeyMap(SDLK_o, irr::EKEY_CODE::KEY_KEY_O)); + KeyMap.push_back(SDLKeyMap(SDLK_p, irr::EKEY_CODE::KEY_KEY_P)); + KeyMap.push_back(SDLKeyMap(SDLK_q, irr::EKEY_CODE::KEY_KEY_Q)); + KeyMap.push_back(SDLKeyMap(SDLK_r, irr::EKEY_CODE::KEY_KEY_R)); + KeyMap.push_back(SDLKeyMap(SDLK_s, irr::EKEY_CODE::KEY_KEY_S)); + KeyMap.push_back(SDLKeyMap(SDLK_t, irr::EKEY_CODE::KEY_KEY_T)); + KeyMap.push_back(SDLKeyMap(SDLK_u, irr::EKEY_CODE::KEY_KEY_U)); + KeyMap.push_back(SDLKeyMap(SDLK_v, irr::EKEY_CODE::KEY_KEY_V)); + KeyMap.push_back(SDLKeyMap(SDLK_w, irr::EKEY_CODE::KEY_KEY_W)); + KeyMap.push_back(SDLKeyMap(SDLK_x, irr::EKEY_CODE::KEY_KEY_X)); + KeyMap.push_back(SDLKeyMap(SDLK_y, irr::EKEY_CODE::KEY_KEY_Y)); + KeyMap.push_back(SDLKeyMap(SDLK_z, irr::EKEY_CODE::KEY_KEY_Z)); + + // TODO: + //KeyMap.push_back(SDLKeyMap(SDLK_LSUPER, KEY_LWIN)); + // TODO: + //KeyMap.push_back(SDLKeyMap(SDLK_RSUPER, KEY_RWIN)); + // apps missing + KeyMap.push_back(SDLKeyMap(SDLK_POWER, irr::EKEY_CODE::KEY_SLEEP)); //?? + + KeyMap.push_back(SDLKeyMap(SDLK_KP_0, irr::EKEY_CODE::KEY_NUMPAD0)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_1, irr::EKEY_CODE::KEY_NUMPAD1)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_2, irr::EKEY_CODE::KEY_NUMPAD2)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_3, irr::EKEY_CODE::KEY_NUMPAD3)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_4, irr::EKEY_CODE::KEY_NUMPAD4)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_5, irr::EKEY_CODE::KEY_NUMPAD5)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_6, irr::EKEY_CODE::KEY_NUMPAD6)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_7, irr::EKEY_CODE::KEY_NUMPAD7)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_8, irr::EKEY_CODE::KEY_NUMPAD8)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_9, irr::EKEY_CODE::KEY_NUMPAD9)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_MULTIPLY, irr::EKEY_CODE::KEY_MULTIPLY)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_PLUS, irr::EKEY_CODE::KEY_ADD)); +// KeyMap.push_back(SDLKeyMap(SDLK_KP_, KEY_SEPARATOR)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_MINUS, irr::EKEY_CODE::KEY_SUBTRACT)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_PERIOD, irr::EKEY_CODE::KEY_DECIMAL)); + KeyMap.push_back(SDLKeyMap(SDLK_KP_DIVIDE, irr::EKEY_CODE::KEY_DIVIDE)); + + KeyMap.push_back(SDLKeyMap(SDLK_F1, irr::EKEY_CODE::KEY_F1)); + KeyMap.push_back(SDLKeyMap(SDLK_F2, irr::EKEY_CODE::KEY_F2)); + KeyMap.push_back(SDLKeyMap(SDLK_F3, irr::EKEY_CODE::KEY_F3)); + KeyMap.push_back(SDLKeyMap(SDLK_F4, irr::EKEY_CODE::KEY_F4)); + KeyMap.push_back(SDLKeyMap(SDLK_F5, irr::EKEY_CODE::KEY_F5)); + KeyMap.push_back(SDLKeyMap(SDLK_F6, irr::EKEY_CODE::KEY_F6)); + KeyMap.push_back(SDLKeyMap(SDLK_F7, irr::EKEY_CODE::KEY_F7)); + KeyMap.push_back(SDLKeyMap(SDLK_F8, irr::EKEY_CODE::KEY_F8)); + KeyMap.push_back(SDLKeyMap(SDLK_F9, irr::EKEY_CODE::KEY_F9)); + KeyMap.push_back(SDLKeyMap(SDLK_F10, irr::EKEY_CODE::KEY_F10)); + KeyMap.push_back(SDLKeyMap(SDLK_F11, irr::EKEY_CODE::KEY_F11)); + KeyMap.push_back(SDLKeyMap(SDLK_F12, irr::EKEY_CODE::KEY_F12)); + KeyMap.push_back(SDLKeyMap(SDLK_F13, irr::EKEY_CODE::KEY_F13)); + KeyMap.push_back(SDLKeyMap(SDLK_F14, irr::EKEY_CODE::KEY_F14)); + KeyMap.push_back(SDLKeyMap(SDLK_F15, irr::EKEY_CODE::KEY_F15)); + // no higher F-keys + + // TODO: + //KeyMap.push_back(SDLKeyMap(SDLK_NUMLOCK, KEY_NUMLOCK)); + KeyMap.push_back(SDLKeyMap(SDLK_SCROLLLOCK, irr::EKEY_CODE::KEY_SCROLL)); + KeyMap.push_back(SDLKeyMap(SDLK_LSHIFT, irr::EKEY_CODE::KEY_LSHIFT)); + KeyMap.push_back(SDLKeyMap(SDLK_RSHIFT, irr::EKEY_CODE::KEY_RSHIFT)); + KeyMap.push_back(SDLKeyMap(SDLK_LCTRL, irr::EKEY_CODE::KEY_LCONTROL)); + KeyMap.push_back(SDLKeyMap(SDLK_RCTRL, irr::EKEY_CODE::KEY_RCONTROL)); + KeyMap.push_back(SDLKeyMap(SDLK_LALT, irr::EKEY_CODE::KEY_LMENU)); + KeyMap.push_back(SDLKeyMap(SDLK_RALT, irr::EKEY_CODE::KEY_RMENU)); + + KeyMap.push_back(SDLKeyMap(SDLK_PLUS, irr::EKEY_CODE::KEY_PLUS)); + KeyMap.push_back(SDLKeyMap(SDLK_COMMA, irr::EKEY_CODE::KEY_COMMA)); + KeyMap.push_back(SDLKeyMap(SDLK_MINUS, irr::EKEY_CODE::KEY_MINUS)); + KeyMap.push_back(SDLKeyMap(SDLK_PERIOD, irr::EKEY_CODE::KEY_PERIOD)); + + // some special keys missing + + KeyMap.sort(); +} + +IrrlichtDevice* device; +irr::video::IVideoDriver * VideoDriver; +SDL_Window* rc_window; +irr::core::dimension2d rc_window_size; + + +//Canvases +struct rc_canvas_obj +{ + irr::video::ITexture* texture; + + irr::core::dimension2d dimension; + + struct rc_canvas_viewport + { + irr::core::vector2d position; + irr::core::dimension2d dimension; + } viewport; + + irr::core::vector2d offset; + + int mode; + + bool visible = true; + int z = 0; + + irr::u8 alpha; + + irr::u32 color_mod; +}; + +irr::core::array rc_canvas; +irr::core::array rc_canvas_zOrder; +int rc_active_canvas = -1; + +irr::video::SColor rc_active_color(0,0,0,0); +irr::video::SColor rc_clear_color(0,0,0,0); + +bool rc_init_events = false; +bool rc_init_timer = false; +bool rc_init_video = false; +bool rc_init_joystick = false; +bool rc_init_haptic = false; +bool rc_init_sensor = false; +bool rc_init_noparachute = false; +bool rc_init_audio = false; + + +irr::s32 MouseX, MouseY, MouseXRel, MouseYRel; +irr::u32 MouseButtonStates; + + +int rc_win_event = -1; +#define RC_WIN_EVENT_CLOSE 1 +#define RC_WIN_EVENT_MINIMIZE 2 +#define RC_WIN_EVENT_MAXIMIZE 3 +#define RC_WIN_EVENT_RESIZE 4 + +bool rc_win_exitOnClose = true; + +std::string rc_textinput_string = ""; +std::string rc_textinput_char = ""; +int rc_textinput_timer = 0; +int rc_textinput_delay = 100; +bool rc_textinput_flag = true; +bool rc_textinput_isActive = false; +int rc_textinput_waitHold = 800; +bool rc_textinput_hold = false; +bool rc_toggleBackspace = true; + + +static Uint32 baseticks = 0; + +int rc_inkey_val = 0; + +const Uint8 * keyState = NULL; + + + +std::wstring_convert> converter; + +struct rc_font_obj +{ + CGUITTFace* face; + CGUIFreetypeFont* font; + int font_size; +}; + +irr::core::array rc_font; + +int rc_active_font = -1; + + +bool mobile_active_window_flag = true; + +void rc_setTouchFingerEvent(SDL_FingerID fingerID, double x, double y, double pressure) +{ + for(int i = 0; i < MAX_FINGERS; i++) + { + if(rc_finger[i].id == -1 || rc_finger[i].id == fingerID) + { + rc_finger[i].id = fingerID; + rc_finger[i].x = x; + rc_finger[i].y = y; + rc_finger[i].pressure = pressure; + if(rc_finger[i].pressure > 0) + { + rc_fingers_pressed.insert(i); + } + return; + } + } +} + +int mobile_event_filter(void* userdata, SDL_Event* evt) +{ + SDL_Event event = evt[0]; + + int rc_win_width = 0; + int rc_win_height = 0; + + if(rc_window) + SDL_GetWindowSize(rc_window, &rc_win_width, &rc_win_height); + + switch(evt->type) + { + case SDL_APP_WILLENTERBACKGROUND: + mobile_active_window_flag = false; + break; + case SDL_APP_DIDENTERFOREGROUND: + if(!mobile_active_window_flag) + { + //rc_win_renderer[0] = SDL_GetRenderer(rc_win[0]); + } + mobile_active_window_flag = true; + break; + + case SDL_FINGERDOWN: + rc_touch = 1; + rc_touchX = event.tfinger.x * rc_win_width; + rc_touchY = event.tfinger.y * rc_win_height; +#ifdef RC_IOS + rc_pressure = 1; //FIXME: On IOS pressure is always getting reported as 0 on finger down so I am just setting it to 1 until I figure this out +#else + rc_pressure = event.tfinger.pressure; +#endif + rc_setTouchFingerEvent(event.tfinger.fingerId, rc_touchX, rc_touchY, rc_pressure); + break; + case SDL_FINGERUP: + rc_touch = 0; + rc_mt_status = 0; + rc_touchX = event.tfinger.x * rc_win_width; + rc_touchY = event.tfinger.y * rc_win_height; + rc_pressure = event.tfinger.pressure; + rc_setTouchFingerEvent(event.tfinger.fingerId, -1, -1, 0); + break; + case SDL_FINGERMOTION: + rc_touch = 1; + rc_touchX = event.tfinger.x * rc_win_width; + rc_touchY = event.tfinger.y * rc_win_height; + rc_motionX = event.tfinger.dx * rc_win_width; + rc_motionY = event.tfinger.dy * rc_win_height; +#ifdef RC_IOS + rc_pressure = 1; +#else + rc_pressure = event.tfinger.pressure; +#endif + rc_setTouchFingerEvent(event.tfinger.fingerId, rc_touchX, rc_touchY, rc_pressure); + break; + case SDL_MULTIGESTURE: + rc_touch = 2; + rc_mt_status = 1; + rc_mt_x = event.mgesture.x; + rc_mt_y = event.mgesture.y; + rc_mt_numFingers = event.mgesture.numFingers; + rc_mt_dist = event.mgesture.dDist; + rc_mt_theta = event.mgesture.dTheta; +#ifdef RC_IOS + rc_pressure = 1; +#else + rc_pressure = event.tfinger.pressure; +#endif + break; + + } + return 0; +} + + +bool rcbasic_init() +{ + if(SDL_Init(SDL_INIT_EVENTS | SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_SENSOR | SDL_INIT_NOPARACHUTE) < 0) //Audio causes init to fail on Fedora40 so I am leaving it out for now + { + bool rc_init_events = true; + bool rc_init_timer = true; + bool rc_init_video = true; + bool rc_init_joystick = true; + bool rc_init_haptic = true; + bool rc_init_sensor = true; + bool rc_init_noparachute = true; + //os::Printer::log("SDL_Init Error: ", SDL_GetError()); + std::cout << "SDL_Init Error: " << SDL_GetError() << std::endl; + return false; + } + + if(SDL_Init(SDL_INIT_AUDIO) < 0) + { + std::cout << "SDL_Init Error: " << SDL_GetError() << std::endl; + rc_init_audio = false; + } + + #ifdef RC_MOBILE + SDL_SetEventFilter(mobile_event_filter, NULL); + #endif // RC_MOBILE + + device = NULL; + VideoDriver = NULL; + rc_window = NULL; + + keyState = SDL_GetKeyboardState(NULL); + createKeyMap(); + + + for(int i = 0; i < MAX_FINGERS; i++) + { + rc_finger[i].id = -1; + rc_finger[i].x = -1; + rc_finger[i].y = -1; + rc_finger[i].pressure = 0; + } + + for(int i = 0; i < SDL_NumSensors(); i++) + { + rc_accel[num_accels] = NULL; + rc_gyro[num_gyros] = NULL; + switch(SDL_SensorGetDeviceType(i)) + { + case SDL_SENSOR_ACCEL: + rc_accel[num_accels] = SDL_SensorOpen(i); + num_accels++; + break; + case SDL_SENSOR_GYRO: + rc_gyro[num_gyros] = SDL_SensorOpen(i); + num_gyros++; + break; + } + } + + for(int i = 0; i < MAX_JOYSTICKS; i++) + { + if(i < SDL_NumJoysticks()) + { + rc_joystick[i] = SDL_JoystickOpen(i); + if(rc_joystick[i]==NULL) + { + cout << "Joystick " << i << " could not be opened: " << SDL_GetError() << endl; + } + rc_joyID[i] = SDL_JoystickInstanceID(rc_joystick[i]); + #ifdef RC_WEB + rc_haptic[i] = NULL; + #else + rc_haptic[i] = SDL_HapticOpenFromJoystick(rc_joystick[i]); + SDL_HapticRumbleInit(rc_haptic[i]); + #endif + //if(rc_haptic[i] == NULL){ cout << "HAP NULL: " << SDL_GetError() << endl; } + rc_numJoysticks++; + } + else + { + rc_joystick[i] = NULL; + rc_haptic[i] = NULL; + rc_joyID[i] = -1; + } + } + SDL_SetHint("SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS", "1"); + + return true; + +} + +bool rc_windowOpenEx(std::string title, int x, int y, int w, int h, uint32_t window_flags, irr::u8 AntiAlias, bool stencil_buffer, bool vsync) +{ + if(rc_window) + { + return false; + } + + bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) || (window_flags & SDL_WINDOW_FULLSCREEN); + bool high_dpi = window_flags & SDL_WINDOW_ALLOW_HIGHDPI; + bool borderless = window_flags & SDL_WINDOW_BORDERLESS; + bool resizable = window_flags & SDL_WINDOW_RESIZABLE; + bool visible = window_flags & SDL_WINDOW_SHOWN; + + uint32_t flags = SDL_WINDOW_OPENGL; + flags |= (fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + flags |= (high_dpi ? SDL_WINDOW_ALLOW_HIGHDPI : 0); + flags |= (borderless ? SDL_WINDOW_BORDERLESS : 0); + flags |= (resizable ? SDL_WINDOW_RESIZABLE : 0); + flags |= (visible ? SDL_WINDOW_SHOWN : SDL_WINDOW_HIDDEN); + + //This size is used for virtual resolution + rc_window_size.Width = w; + rc_window_size.Height = h; + + rc_window = SDL_CreateWindow(title.c_str(), x, y, w, h, flags); + + //Get the actual size of the window to set the dimensions of the device + if(rc_window) + SDL_GetWindowSize(rc_window, &w, &h); + + SIrrlichtCreationParameters irr_creation_params; + irr_creation_params.DeviceType = EIDT_SDL; + irr_creation_params.DriverType = video::EDT_OPENGL; + irr_creation_params.WindowId = rc_window; + irr_creation_params.WindowSize = dimension2d((u32)w, (u32)h); + irr_creation_params.Bits = 16; + irr_creation_params.Fullscreen = fullscreen; + irr_creation_params.Stencilbuffer = stencil_buffer; + irr_creation_params.Vsync = vsync; + irr_creation_params.EventReceiver = 0; + irr_creation_params.WindowPosition = position2d(x, y); + irr_creation_params.AntiAlias = AntiAlias; + + device = createDeviceEx(irr_creation_params); + + + if (!device) + { + std::cout << "WindowOpen Error: Failed to Create Renderer" << std::endl; + return false; + } + + VideoDriver = device->getVideoDriver(); + + rc_canvas.clear(); + rc_canvas_zOrder.clear(); + rc_font.clear(); + + rc_canvas_obj back_buffer; + back_buffer.texture = VideoDriver->addRenderTargetTexture(irr::core::dimension2d((irr::u32)w, (irr::u32)h), "rt", ECF_A8R8G8B8); + back_buffer.dimension.Width = w; + back_buffer.dimension.Height = h; + back_buffer.viewport.position.set(0,0); + back_buffer.viewport.dimension.set(w,h); + VideoDriver->setRenderTarget(back_buffer.texture, true, true); + rc_canvas.push_back(back_buffer); + + return true; +} + +bool rc_windowOpen(std::string title, int w, int h, bool fullscreen, bool vsync) +{ + uint32_t flags = SDL_WINDOW_SHOWN | (fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + if(!rc_windowOpenEx(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, flags, 0, false, vsync)) + { + return false; + } + + return true; +} + +void rc_closeWindow_hw() +{ + if(rc_window!=NULL) + SDL_DestroyWindow(rc_window); + rc_window = NULL; + + rc_canvas.clear(); + rc_canvas_zOrder.clear(); + rc_font.clear(); + + device->drop(); +} + +Uint32 rc_windowMode(int visible_flag, int fullscreen_flag, int resizable_flag, int borderless_flag, int highDPI_flag) +{ + Uint32 window_mode = ( visible_flag == 0 ? SDL_WINDOW_HIDDEN : SDL_WINDOW_SHOWN ) | + ( fullscreen_flag == 0 ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP ) | + ( resizable_flag == 0 ? 0 : SDL_WINDOW_RESIZABLE ) | + ( borderless_flag == 0 ? 0 : SDL_WINDOW_BORDERLESS ) | + ( highDPI_flag == 0 ? 0 : SDL_WINDOW_ALLOW_HIGHDPI ); + return window_mode; +} + +Uint32 rc_getWindowMode() +{ + if(rc_window == NULL) + { + return 0; + } + return SDL_GetWindowFlags(rc_window); +} + +void rc_raiseWindow() +{ + if(rc_window==NULL) + { + return; + } + SDL_RaiseWindow(rc_window); +} + +void rc_showWindow() +{ + if(rc_window==NULL) + { + return; + } + SDL_ShowWindow(rc_window); +} + +void rc_hideWindow() +{ + if(rc_window==NULL) + { + return; + } + SDL_HideWindow(rc_window); +} + +void rc_getDesktopDisplayMode(int index, double * w, double * h, double * freq) +{ + SDL_DisplayMode dm; + SDL_GetDesktopDisplayMode(index, &dm); + *w = (double)dm.w; + *h = (double)dm.h; + *freq = (double)dm.refresh_rate; +} + +void rc_setWindowTitle(std::string title) +{ + if(rc_window) + { + SDL_SetWindowTitle(rc_window, title.c_str()); + } +} + +std::string rc_getWindowTitle() +{ + if(rc_window) + { + return SDL_GetWindowTitle(rc_window); + } + return ""; +} + +void rc_setWindowPosition(int x, int y) +{ + if(rc_window) + SDL_SetWindowPosition(rc_window, x, y); +} + +void rc_getWindowPosition(double * x, double * y) +{ + int x_data=0, y_data=0; + if(rc_window) + SDL_GetWindowPosition(rc_window,&x_data,&y_data); + *x = x_data; + *y = y_data; +} + +void rc_setWindowSize(int w, int h) +{ + if(rc_window) + { + SDL_SetWindowSize(rc_window, w, h); + + irr::core::dimension2d win_size; + int w, h; + SDL_GetWindowSize(rc_window, &w, &h); + win_size.Width = w; + win_size.Height = h; + + device->setWindowSize(win_size); + + rc_window_size.Width = w; + rc_window_size.Height = h; + + } +} + +void rc_getWindowSize(double * w, double * h) +{ + int w_data = -1, h_data = -1; + if(rc_window) + SDL_GetWindowSize(rc_window, &w_data, &h_data); + *w = w_data; + *h = h_data; +} + +void rc_setWindowMinSize(int w, int h) +{ + if(rc_window) + SDL_SetWindowMinimumSize(rc_window, w, h); +} + +void rc_getWindowMinSize(double * w, double * h) +{ + int w_data=0, h_data=0; + if(rc_window) + SDL_GetWindowMinimumSize(rc_window, &w_data, &h_data); + *w = w_data; + *h = h_data; +} + +void rc_setWindowMaxSize(int w, int h) +{ + if(rc_window) + SDL_SetWindowMaximumSize(rc_window, w, h); +} + +void rc_getWindowMaxSize(double * w, double * h) +{ + int w_data=0, h_data=0; + if(rc_window) + SDL_GetWindowMaximumSize(rc_window, &w_data, &h_data); + *w = w_data; + *h = h_data; +} + +bool rc_windowIsFullscreen() +{ + if(rc_window) + { + Uint32 wflags = SDL_GetWindowFlags(rc_window); + Uint32 wflags_cmp1 = wflags & SDL_WINDOW_FULLSCREEN; + Uint32 wflags_cmp2 = wflags & SDL_WINDOW_FULLSCREEN_DESKTOP; + + if(wflags_cmp1 || wflags_cmp2) + return true; + else + return false; + } + + return false; + +} + +bool rc_windowIsVisible() +{ + if(rc_window) + { + Uint32 wflags = SDL_GetWindowFlags(rc_window); + if(wflags & SDL_WINDOW_SHOWN) + return true; + else + return false; + } + + return false; +} + +bool rc_windowHasMouseFocus() +{ + if(rc_window) + { + Uint32 wflags = SDL_GetWindowFlags(rc_window); + if(wflags & SDL_WINDOW_MOUSE_FOCUS) + return true; + else + return false; + } + + return false; +} + +bool rc_windowHasInputFocus() +{ + if(rc_window) + { + Uint32 wflags = SDL_GetWindowFlags(rc_window); + if(wflags & SDL_WINDOW_INPUT_FOCUS) + return true; + else + return false; + } + + return false; +} + +bool rc_windowIsBordered() +{ + if(rc_window) + { + Uint32 wflags = SDL_GetWindowFlags(rc_window); + if(wflags & SDL_WINDOW_BORDERLESS) + return false; + else + return true; + } + + return false; +} + +bool rc_windowIsResizable() +{ + if(rc_window) + { + Uint32 wflags = SDL_GetWindowFlags(rc_window); + if(wflags & SDL_WINDOW_RESIZABLE) + return true; + else + return false; + } + + return false; +} + +bool rc_windowIsMinimized() +{ + if(rc_window) + { + Uint32 wflags = SDL_GetWindowFlags(rc_window); + if(wflags & SDL_WINDOW_MINIMIZED) + return true; + else + return false; + } + + return false; +} + +bool rc_windowIsMaximized() +{ + if(rc_window) + { + Uint32 wflags = SDL_GetWindowFlags(rc_window); + if(wflags & SDL_WINDOW_MAXIMIZED) + return true; + else + return false; + } + + return false; +} + +bool rc_setWindowFullscreen(int flag) +{ + if(rc_window) + { + switch(flag) + { + case 0: + flag = 0; + break; + default: + flag = SDL_WINDOW_FULLSCREEN_DESKTOP; + break; + } + + Uint32 wflags_preOp = SDL_GetWindowFlags(rc_window); + if( flag != 0 && ( (wflags_preOp & SDL_WINDOW_FULLSCREEN_DESKTOP) || (wflags_preOp & SDL_WINDOW_FULLSCREEN) ) ) + return true; + else if( flag == 0 && !((wflags_preOp & SDL_WINDOW_FULLSCREEN_DESKTOP) || (wflags_preOp & SDL_WINDOW_FULLSCREEN))) + return true; + + if(SDL_SetWindowFullscreen(rc_window, flag) < 0) + { + return false; + } + + + int w, h; + SDL_GetWindowSize(rc_window, &w, &h); + + irr::core::dimension2d win_size(w, h); + device->setWindowSize(win_size); + + if(!(w==rc_window_size.Width && h==rc_window_size.Height)) + { + //TODO: change mouse scale adjust + } + + SDL_PumpEvents(); + SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT); + + return true; + } + + return false; +} + +bool rc_maximizeWindow() +{ + if(rc_window) + { + SDL_MaximizeWindow(rc_window); + + SDL_DisplayMode mode; + SDL_GetWindowDisplayMode(rc_window, &mode); + + irr::core::dimension2d win_size(mode.w, mode.h); + device->setWindowSize(win_size); + + return true; + } + + return false; +} + +bool rc_minimizeWindow() +{ + if(rc_window) + { + SDL_MinimizeWindow(rc_window); + + SDL_DisplayMode mode; + SDL_GetWindowDisplayMode(rc_window, &mode); + + irr::core::dimension2d win_size(mode.w, mode.h); + device->setWindowSize(win_size); + + return true; + } + + return false; +} + +void rc_setWindowBordered(bool b) +{ + SDL_bool bswitch = SDL_FALSE; + if(b) + bswitch = SDL_TRUE; + if(rc_window) + SDL_SetWindowBordered(rc_window, bswitch); +} + +void rc_setWindowResizable(bool b) +{ + SDL_bool bswitch = SDL_FALSE; + if(b) + bswitch = SDL_TRUE; + if(rc_window) + { + SDL_SetWindowResizable(rc_window, bswitch); + device->setResizable(true); + } +} + +bool rc_media_windowExists() +{ + return (rc_window!=NULL); +} + +bool rc_restoreWindow() +{ + if(rc_window) + { + SDL_RestoreWindow(rc_window); + + SDL_DisplayMode mode; + SDL_GetWindowDisplayMode(rc_window, &mode); + + irr::core::dimension2d win_size(mode.w, mode.h); + device->setWindowSize(win_size); + + return true; + } + + return false; +} + +void rc_setWindowIcon(int slot) +{ + SDL_Rect img_rect; + img_rect.x = 0; + img_rect.y = 0; + //img_rect.w = rc_image_width[slot]; + //img_rect.h = rc_image_height[slot]; + /* + if(rc_himage[slot][win_num] != NULL) + { + //SDL_RendererFlip rf = (SDL_RendererFlip)(SDL_FLIP_VERTICAL); + + SDL_Surface * tmp_surf = SDL_CreateRGBSurface(0, rc_image_width[slot], rc_image_height[slot], 32, 0, 0, 0, 0); + SDL_Texture * tmp_tex = SDL_CreateTexture(rc_win_renderer[rc_active_window], rc_pformat->format, SDL_TEXTUREACCESS_TARGET, rc_image_width[slot], rc_image_height[slot]); + SDL_SetRenderTarget(rc_win_renderer[rc_active_window],NULL); + SDL_RenderCopy(rc_win_renderer[rc_active_window],rc_himage[slot][rc_active_window],NULL,&img_rect); + //SDL_RenderCopyEx(rc_win_renderer[rc_active_window],rc_himage[slot][rc_active_window],NULL,NULL,0,NULL,rf); + + SDL_RenderReadPixels(rc_win_renderer[rc_active_window], &img_rect, rc_pformat->format,tmp_surf->pixels,tmp_surf->pitch); + + SDL_SetColorKey(tmp_surf,SDL_TRUE,rc_image_colorKey[slot]); + + SDL_SetWindowIcon(rc_win[rc_active_window], tmp_surf); + + + if(rc_active_screen >= 0) + SDL_SetRenderTarget(rc_win_renderer[rc_active_window], rc_hscreen[rc_active_window][rc_active_screen]); + + SDL_DestroyTexture(tmp_tex); + SDL_FreeSurface(tmp_surf); + } + */ +} + +std::string rc_getClipboardText() +{ + return (std::string) SDL_GetClipboardText(); +} + +void rc_setClipboardText(std::string txt) +{ + SDL_SetClipboardText(txt.c_str()); +} + +int rc_hasClipboardText() +{ + return (int)SDL_HasClipboardText(); +} + + + + +Uint32 rc_canvasOpen(int w, int h, int vx, int vy, int vw, int vh, int mode) +{ + if(!VideoDriver) + return -1; + + rc_canvas_obj canvas; + canvas.texture = VideoDriver->addRenderTargetTexture(irr::core::dimension2d(w,h), "rt", ECF_A8R8G8B8); + + //std::cout << "texture format = " << canvas.texture->getColorFormat() << std::endl; + + canvas.dimension.Width = w; + canvas.dimension.Height = h; + + canvas.viewport.position.X = vx; + canvas.viewport.position.Y = vy; + canvas.viewport.dimension.Width = vw; + canvas.viewport.dimension.Height = vh; + + canvas.offset.X = 0; + canvas.offset.Y = 0; + + canvas.mode = mode; + + switch(mode) + { + case 0: + break; + case 1: + VideoDriver->makeColorKeyTexture(canvas.texture, irr::video::SColor(0,0,0,0)); + break; + } + + int canvas_id = -1; + + for(int i = 0; i < rc_canvas.size(); i++) + { + if(!rc_canvas[i].texture) + { + canvas_id = i; + break; + } + } + + if(canvas_id < 0) + { + canvas_id = rc_canvas.size(); + rc_canvas.push_back(canvas); + } + + if(rc_active_canvas < 0) + rc_active_canvas = canvas_id; + + for(int i = 0; i < rc_canvas_zOrder.size(); i++) + { + if(rc_canvas_zOrder[i] == canvas_id) + { + rc_canvas_zOrder.erase(i); + i--; + } + } + + rc_canvas_zOrder.push_back(canvas_id); + + + return canvas_id; +} + + +void rc_canvasClose(int canvas_id) +{ + if(canvas_id <= 0 || canvas_id >= rc_canvas.size()) //canvas 0 is being excluded because its the back buffer + return; + + if(rc_canvas[canvas_id].texture != NULL) + VideoDriver->removeTexture(rc_canvas[canvas_id].texture); + + rc_canvas[canvas_id].texture = NULL; + + if(rc_active_canvas == canvas_id) + rc_active_canvas = -1; + + for(int i = 0; i < rc_canvas_zOrder.size(); i++) + { + if(rc_canvas_zOrder[i] == canvas_id) + { + rc_canvas_zOrder.erase(i); + break; + } + } +} + +void rc_setActiveCanvas(int canvas_id) +{ + rc_active_canvas = canvas_id; + + if(rc_active_canvas >= 0 && rc_active_canvas < rc_canvas.size()) + { + if(rc_canvas[rc_active_canvas].texture) + VideoDriver->setRenderTarget(rc_canvas[rc_active_canvas].texture, false, false); + } +} + +int rc_getActiveCanvas() +{ + return rc_active_canvas; +} + +void rc_clearCanvas() +{ + if(rc_active_canvas >= 0 && rc_active_canvas < rc_canvas.size()) + { + if(rc_canvas[rc_active_canvas].texture) + VideoDriver->clearBuffers(true, true, true, rc_clear_color); + } +} + +void rc_setClearColor(Uint32 color) +{ + rc_clear_color.set(color); +} + + + +Uint32 rc_rgba(Uint32 r, Uint32 g, Uint32 b, Uint32 a) +{ + irr::video::SColor color(a, r, g, b); + return color.color; +} + +Uint32 rc_rgb(Uint32 r, Uint32 g, Uint32 b) +{ + irr::video::SColor color(255, r, g, b); + return color.color; +} + + +void rc_setColor(Uint32 color) +{ + rc_active_color.set(color); +} + +Uint32 rc_getPixel(int x, int y) +{ + if(!rc_canvas[0].texture) + { + return 0; + } + + if(x < 0 || x >= rc_window_size.Width) + x = 0; + + if(y < 0 || y >= rc_window_size.Height) + y = 0; + + + irr::video::ITexture* texture = rc_canvas[0].texture; + + video::ECOLOR_FORMAT format = texture->getColorFormat(); //std::cout << "format = " << (int) format << std::endl; + + Uint32 color = 0; + + //this if statement is unnessesary since right now ECF_A8R8G8B8 is the only color format supported. + //I am leaving it here since I may want to support more color formats in the future + if(video::ECF_A8R8G8B8 == format) + { + u8 * texels = (u8 *)texture->lock(irr::video::ETLM_READ_ONLY); + + u32 pitch = texture->getPitch(); + + irr::video::SColor * texel = (SColor *)(texels + ((y * pitch) + (x * sizeof(SColor)))); + + irr::video::SColor c = texel[0]; + + texture->unlock(); + + //std::cout << "color(" << x << ", " << y << ") = " << c.getRed() << ", " << c.getGreen() << ", " << c.getBlue() << std::endl; + } + + return color; + +} + +void rc_drawRect(int x, int y, int w, int h) +{ + irr::core::vector2d r_pos(x,y); + irr::core::dimension2d r_dim(w,h); + irr::core::rect r(r_pos, r_dim); + //std::cout << "drawRect: color=" << rc_active_color.color << " ( " << x << ", " << y << ", " << w << ", " << h << " ) " << std::endl; + VideoDriver->draw2DRectangleOutline(r, rc_active_color); +} + +void rc_drawRectFill(int x, int y, int w, int h) +{ + irr::core::vector2d r_pos(x,y); + irr::core::dimension2d r_dim(w,h); + irr::core::rect r(r_pos, r_dim); + //std::cout << "drawRect: color=" << rc_active_color.color << " ( " << x << ", " << y << ", " << w << ", " << h << " ) " << std::endl; + VideoDriver->draw2DRectangle(rc_active_color, r); +} + +void rc_drawCircle(int x, int y, double r) +{ + irr::core::vector2d r_pos(x,y); + + VideoDriver->draw2DPolygon(r_pos, r, rc_active_color, 30); +} + + + +//Filled Circle Code from CuteAlien on Irrlicht forum +struct CircleSettings +{ + vector2di center; // in screen coordinates + f32 radius; // in pixels + f32 radius2; + video::SColor color; + u32 numVertices = 21; // including center +}; + +void makeCircle(irr::core::array& vertices, irr::core::array& indices, const CircleSettings& settings) +{ + const f64 stepSize = 360.0 / (f64)(settings.numVertices-1); // degree angles between vertex points on circle + indices.set_used(settings.numVertices+1); // one more as first and last vertex in circle is identical + for ( u32 i=0; i r_pos(x,y); + + // create the circle + irr::core::array verticesCircle; + irr::core::array indicesCircle; + CircleSettings circle; + circle.center = r_pos; + circle.radius = r; + circle.color = rc_active_color; + makeCircle(verticesCircle, indicesCircle, circle); + + VideoDriver->draw2DVertexPrimitiveList(verticesCircle.pointer(), verticesCircle.size(), + indicesCircle.pointer(), indicesCircle.size()-2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, + video::EIT_16BIT); +} + +void rc_drawLine(int x1, int y1, int x2, int y2) +{ + irr::core::vector2d r_pos_start(x1,y1); + irr::core::vector2d r_pos_end(x2,y2); + + VideoDriver->draw2DLine(r_pos_start, r_pos_end, rc_active_color); +} + +void rc_poly(Uint32 n, double* vx_d, double* vy_d) +{ + if(n <= 0) + return; + + for(int i = 1; i < n; i++) + { + rc_drawLine((int)vx_d[i-1], (int)vy_d[i-1], (int)vx_d[i], (int)vy_d[i]); + } + + rc_drawLine((int)vx_d[n-1], (int)vy_d[n-1], (int)vx_d[0], (int)vy_d[0]); +} + +void rc_drawPixel(int x, int y) +{ + VideoDriver->drawPixel(x, y, rc_active_color); +} + + +double radians(double degree) +{ + double pi = 3.14159265359; + return (degree * (pi / 180)); +} + +void makeEllipse(irr::core::array& vertices, irr::core::array& indices, const CircleSettings& settings) +{ + const f64 stepSize = 360.0 / (f64)(settings.numVertices-1); // degree angles between vertex points on circle + indices.set_used(settings.numVertices+1); // one more as first and last vertex in circle is identical + for ( u32 i=0; i r_pos(x,y); + + // create the circle + irr::core::array verticesCircle; + irr::core::array indicesCircle; + CircleSettings circle; + circle.center = r_pos; + circle.radius = ry; + circle.radius2 = rx; + circle.color = rc_active_color; + circle.numVertices = 21; + makeEllipse(verticesCircle, indicesCircle, circle); + + for(int i = 2; i < verticesCircle.size(); i++) + { + rc_drawLine(verticesCircle[i-1].Pos.X, verticesCircle[i-1].Pos.Y, verticesCircle[i].Pos.X, verticesCircle[i].Pos.Y); + } + + int n = verticesCircle.size()-1; + rc_drawLine(verticesCircle[n].Pos.X, verticesCircle[n].Pos.Y, verticesCircle[1].Pos.X, verticesCircle[1].Pos.Y); +} + +void rc_drawEllipseFill(int x, int y, int rx, int ry) +{ + irr::core::vector2d r_pos(x,y); + + // create the circle + irr::core::array verticesCircle; + irr::core::array indicesCircle; + CircleSettings circle; + circle.center = r_pos; + circle.radius = ry; + circle.radius2 = rx; + circle.color = rc_active_color; + circle.numVertices = 21; + makeEllipse(verticesCircle, indicesCircle, circle); + + VideoDriver->draw2DVertexPrimitiveList(verticesCircle.pointer(), verticesCircle.size(), + indicesCircle.pointer(), indicesCircle.size()-2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, + video::EIT_16BIT); +} + + + +int rc_loadFont(irr::io::path file_path, int font_size) +{ + int font_id = -1; + for(int i = 0; i < rc_font.size(); i++) + { + if(rc_font[i]!=NULL) + { + font_id = i; + break; + } + } + + CGUITTFace* Face; + CGUIFreetypeFont* dfont; + + Face = new CGUITTFace(); + Face->load(file_path); + + dfont = new CGUIFreetypeFont(VideoDriver); + dfont->attach(Face, font_size); + + + if(font_id < 0) + { + font_id = rc_font.size(); + + rc_font_obj* f = new rc_font_obj(); + + f->face = Face; + f->font = dfont; + f->font_size = font_size; + + rc_font.push_back(f); + + rc_active_font = font_id; + } + else + { + rc_font[font_id]->face = Face; + rc_font[font_id]->font = dfont; + rc_font[font_id]->font_size = font_size; + } + + return font_id; +} + +bool rc_fontExists(int font_id) +{ + if(font_id >= 0 && font_id < rc_font.size()) + { + if(rc_font[font_id]->font != NULL) + return true; + } + return false; +} + +void rc_deleteFont(int font_id) +{ + if(rc_fontExists(font_id)) + { + delete rc_font[font_id]->font; + delete rc_font[font_id]->face; + rc_font[font_id]->font = NULL; + rc_font[font_id]->face = NULL; + rc_font[font_id]->font_size = 0; + } +} + +void rc_setFont(int font_id) +{ + if(rc_fontExists(font_id)) + rc_active_font = font_id; +} + +void rc_drawText(std::string txt, int x, int y) +{ + if(rc_fontExists(rc_active_font)) + { + std::wstring text = utf8_to_wstring(txt); + irr::core::dimension2d text_dim = rc_font[rc_active_font]->font->getDimension(text.c_str()); + irr::core::rect tpos(x, y, text_dim.Width, rc_font[rc_active_font]->font_size); + + //std::cout << "Start drawing text: " << tpos.getWidth() << ", " << tpos.getHeight() << std::endl; + rc_font[rc_active_font]->font->draw(text.c_str(), tpos, rc_active_color); + //std::cout << "------------------" << std::endl; + } +} + +Uint32 rc_getTextWidth(const std::string txt) +{ + if(rc_fontExists(rc_active_font)) + { + std::wstring text = utf8_to_wstring(txt); + irr::core::dimension2d text_dim = rc_font[rc_active_font]->font->getDimension(text.c_str()); + return text_dim.Width; + } + return 0; +} + +Uint32 rc_getTextHeight(const std::string txt) +{ + if(rc_fontExists(rc_active_font)) + { + std::wstring text = utf8_to_wstring(txt); + //std::wstring wide = converter.from_bytes(txt); + //irr::core::dimension2d text_dim = rc_font[rc_active_font]->getDimension(wide.c_str()); + return rc_font[rc_active_font]->font_size; + } + return 0; +} + +void rc_getTextSize(const std::string txt, double* w, double* h) +{ + if(rc_fontExists(rc_active_font)) + { + std::wstring text = utf8_to_wstring(txt); + irr::core::dimension2d text_dim = rc_font[rc_active_font]->font->getDimension(text.c_str()); + *w = text_dim.Width; + *h = rc_font[rc_active_font]->font_size; + } + else + { + *w = 0; + *h = 0; + } +} + + +#define RC_MOUSE_BUTTON_LEFT 0 +#define RC_MOUSE_BUTTON_MIDDLE 1 +#define RC_MOUSE_BUTTON_RIGHT 2 + +int rc_mwheelx = -1; +int rc_mwheely = -1; + +bool rc_cursor_visible = true; + + +bool rc_mouseButton(int b) +{ + bool button_state = false; + switch(b) + { + case 0: + button_state = ((MouseButtonStates & irr::EMBSM_LEFT)!=0); + break; + case 1: + button_state = ((MouseButtonStates & irr::EMBSM_MIDDLE)!=0); + break; + case 2: + button_state = ((MouseButtonStates & irr::EMBSM_RIGHT)!=0); + break; + } + return button_state; +} + +int rc_mouseX() +{ + int x, y; + SDL_GetMouseState(&x, &y); + return x; +} + +int rc_mouseY() +{ + int x, y; + SDL_GetMouseState(&x, &y); + return y; +} + +void rc_getMouse(double* x, double* y) +{ + int ix, iy; + SDL_GetMouseState(&ix, &iy); + *x = ix; + *y = iy; +} + +int rc_mouseWheelX() +{ + return rc_mwheelx; +} + +int rc_mouseWheelY() +{ + return rc_mwheely; +} + +int rc_globalMouseX() +{ + int x, y; + SDL_GetGlobalMouseState(&x,&y); + return x; +} + +int rc_globalMouseY() +{ + int x, y; + SDL_GetGlobalMouseState(&x,&y); + return y; +} + +void rc_globalMouse(double * x, double* y) +{ + int ix, iy; + SDL_GetGlobalMouseState(&ix,&iy); + *x = ix; + *y = iy; +} + +void rc_getMouseWheel(double* x, double* y) +{ + *x = rc_mwheelx; + *y = rc_mwheely; +} + +void rc_hideMouse() +{ + SDL_ShowCursor(0); + rc_cursor_visible = false; +} + +void rc_showMouse() +{ + SDL_ShowCursor(1); + rc_cursor_visible = true; +} + +bool rc_mouseIsVisible() +{ + return rc_cursor_visible; +} + + +int rc_inkey() +{ + return rc_inkey_val; +} + +int rc_key(int check_Key) +{ + return keyState[SDL_GetScancodeFromKey(check_Key)]; +} + +int rc_waitKey() +{ + bool wk_loop = true; + SDL_Event e; + while(wk_loop) + { + while(SDL_WaitEvent(&e)) + { + if(e.type == SDL_KEYDOWN) + return (int)e.key.keysym.sym; + } + } + return 0; +} + +void rc_wait(Uint32 ms) +{ + SDL_Delay(ms); +} + + + +void rc_grabInput(bool flag) +{ + SDL_SetWindowGrab(rc_window, flag ? SDL_TRUE : SDL_FALSE); +} + +int rc_windowIsGrabbed() +{ + return SDL_GetWindowGrab(rc_window); +} + +void rc_warpMouse(int x, int y) +{ + SDL_WarpMouseInWindow(rc_window, x, y); +} + +void rc_warpMouseGlobal(int x, int y) +{ + SDL_WarpMouseGlobal(x, y); +} + +void rc_setMouseZone(int x, int y, int w, int h) +{ + SDL_Rect r; + r.x = x; + r.y = y; + r.w = w; + r.h = h; + SDL_SetWindowMouseRect(rc_window, &r); +} + +void rc_clearMouseZone() +{ + SDL_SetWindowMouseRect(rc_window, NULL); +} + +void rc_setWindowAlwaysOnTop(bool flag) +{ + SDL_SetWindowAlwaysOnTop(rc_window, flag ? SDL_TRUE : SDL_FALSE); +} + +void rc_setMouseRelative(bool flag) +{ + SDL_SetRelativeMouseMode(flag ? SDL_TRUE : SDL_FALSE); +} + +void rc_setWindowVSync(bool flag) +{ + //TODO +} + +int rc_openURL(std::string url) +{ + return SDL_OpenURL(url.c_str()); +} + +std::string rc_SDLVersion() +{ + SDL_version version; + SDL_GetVersion(&version); + + std::stringstream ss; + ss << (uint32_t)version.major << "." << (uint32_t)version.minor << "." << (uint32_t)version.patch; + return ss.str(); + +} + +int rc_flashWindow(int flag) +{ + switch(flag) + { + case 0: + return SDL_FlashWindow(rc_window, SDL_FLASH_CANCEL); + case 1: + return SDL_FlashWindow(rc_window, SDL_FLASH_BRIEFLY); + case 2: + return SDL_FlashWindow(rc_window, SDL_FLASH_UNTIL_FOCUSED); + } + + return -1; +} + +int rc_messageBox(std::string title, std::string msg) +{ + return SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, title.c_str(), msg.c_str(), NULL); +} + +int rc_FPS() +{ + return VideoDriver->getFPS(); +} + + + + +int rc_getNumJoysticks() +{ + return SDL_NumJoysticks(); +} + +int rc_joyAxis(int joy_num, int axis) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + return SDL_JoystickGetAxis(rc_joystick[joy_num], axis); + return 0; +} + +int rc_joyButton(int joy_num, int jbutton) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + return SDL_JoystickGetButton(rc_joystick[joy_num], jbutton); + return 0; +} + +std::string rc_joystickName(int joy_num) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + return (std::string)SDL_JoystickName(rc_joystick[joy_num]); + return ""; +} + +int rc_numJoyButtons(int joy_num) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + return SDL_JoystickNumButtons(rc_joystick[joy_num]); + return 0; +} + +int rc_numJoyAxes(int joy_num) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + return SDL_JoystickNumAxes(rc_joystick[joy_num]); + return 0; +} + +int rc_numJoyHats(int joy_num) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + return SDL_JoystickNumHats(rc_joystick[joy_num]); + return 0; +} + +int rc_joyHat(int joy_num, int hat) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + return SDL_JoystickGetHat(rc_joystick[joy_num], hat); + return 0; +} + +int rc_numJoyTrackBalls(int joy_num) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + return SDL_JoystickNumBalls(rc_joystick[joy_num]); + return 0; +} + +void rc_getJoyTrackBall(int joy_num, int ball, double * dx, double * dy) +{ + int x = 0; + int y = 0; + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + SDL_JoystickGetBall(rc_joystick[joy_num], ball, &x, &y); + *dx = x; + *dy = y; +} + +bool rc_joystickIsConnected( int joy_num ) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + { + if(rc_joystick[joy_num]) + return true; + return false; + } + return false; +} + +void rc_joyRumblePlay(int joy_num, double strength, double duration) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + { + SDL_HapticRumblePlay(rc_haptic[joy_num], strength, (Uint32)duration); + } +} + +void rc_joyRumbleStop(int joy_num) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + SDL_HapticRumbleStop(rc_haptic[joy_num]); +} + +int rc_joystickIsHaptic( int joy_num ) +{ + if(joy_num >= 0 && joy_num < MAX_JOYSTICKS) + { + if(rc_haptic[joy_num]) + return 1; + } + return 0; +} + + + + + +void rc_getTouchFinger(int finger, double * x, double * y, double * pressure) +{ + if(finger < MAX_FINGERS) + { + *x = rc_finger[finger].x; + *y = rc_finger[finger].y; + *pressure = rc_finger[finger].pressure; + } +} + +int rc_numFingers() +{ + return rc_fingers_pressed.size(); +} + +double rc_touchPressure() +{ + return rc_pressure; +} + +void rc_getTouch(double * status, double * x, double * y, double * distX, double * distY) +{ + *status = (double)rc_touch; + *x = (double)rc_touchX; + *y = (double)rc_touchY; + *distX = (double)rc_motionX; + *distY = (double)rc_motionY; + return; +} + +void rc_getMultiTouch(double * status, double * x, double * y, double * numFingers, double * dist, double * theta) +{ + *status = (double)rc_mt_status; + *x = (double)rc_mt_x; + *y = (double)rc_mt_y; + *numFingers = (double)rc_mt_numFingers; + *dist = rc_mt_dist; + *theta = rc_mt_theta; + return; +} + +void rc_getAccel(uint32_t accel_num, double * x, double * y, double * z) +{ + float sensor_data[4]; + if(accel_num < num_accels) + SDL_SensorGetData(rc_accel[accel_num], &sensor_data[0], 3); + *x = sensor_data[0]; + *y = sensor_data[1]; + *z = sensor_data[2]; +} + +std::string rc_accelName(uint32_t accel_num) +{ + if(accel_num < num_accels) + return (std::string)SDL_SensorGetName(rc_accel[accel_num]); + return ""; +} + +int rc_numAccels() +{ + return num_accels; +} + +void rc_getGyro(uint32_t gyro_num, double * x, double * y, double * z) +{ + float sensor_data[4]; + if(gyro_num < num_gyros) + SDL_SensorGetData(rc_gyro[gyro_num], &sensor_data[0], 3); + *x = sensor_data[0]; + *y = sensor_data[1]; + *z = sensor_data[2]; +} + +std::string rc_gyroName(uint32_t gyro_num) +{ + if(gyro_num < num_gyros) + return (std::string)SDL_SensorGetName(rc_gyro[gyro_num]); + return ""; +} + +int rc_numGyros() +{ + return num_gyros; +} + +void rc_readInput_Start() +{ + SDL_StartTextInput(); + rc_textinput_isActive = true; + rc_textinput_string = ""; + rc_textinput_timer = clock() / (double)(CLOCKS_PER_SEC / 1000); +} + +void rc_readInput_Stop() +{ + rc_textinput_isActive = false; + rc_textinput_timer = 0; + rc_textinput_string = ""; + SDL_StopTextInput(); +} + +std::string rc_readInput_Text() +{ + return rc_textinput_string; +} + +void rc_readInput_SetText(std::string txt) +{ + rc_textinput_string = txt; +} + +void rc_readInput_ToggleBackspace(bool flag) +{ + rc_toggleBackspace = flag; +} + + +struct rc_image_obj +{ + irr::video::ITexture* image; + Uint8 alpha = 255; + irr::video::SColor color_mod = irr::video::SColor(255,255,255,255); +}; +irr::core::array rc_image; + +irr::video::E_BLEND_OPERATION rc_blend_mode = irr::video::EBO_ADD; +bool rc_bilinear_filter = false; + + +int rc_loadImageEx(std::string img_file, Uint32 color_key = 0, bool use_color_key = false) +{ + rc_image_obj img; + img.image = VideoDriver->getTexture(img_file.c_str()); + + if(img.image == NULL) + return -1; + + if(use_color_key) + VideoDriver->makeColorKeyTexture(img.image, irr::video::SColor(color_key), false); + + int img_id = -1; + + for(int i = 0; i < rc_image.size(); i++) + { + if(rc_image[i].image == NULL) + { + img_id = i; + break; + } + } + + if(img_id < 0) + { + img_id = rc_image.size(); + rc_image.push_back(img); + } + else + { + rc_image[img_id] = img; + } + + return img_id; +} + +int rc_loadImage(std::string img_file) +{ + return rc_loadImageEx(img_file); +} + +void rc_deleteImage(int img_id) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + rc_image[img_id].image->drop(); + rc_image[img_id].image = NULL; + rc_image[img_id].alpha = 255; + } +} + +int rc_createImageEx(int w, int h, double * pdata, Uint32 colorkey, bool use_color_key=false) +{ + if(w <= 0 || h <=0) + return -1; + + irr::video::IImage* image = VideoDriver->createImage(irr::video::ECF_A8R8G8B8, irr::core::dimension2d((irr::u32)w,(irr::u32)h)); + if(!image) + return -1; + + Uint32 * img_pixels = (Uint32*)image->getData(); + for(int i = 0; i < (w*h); i++) + { + img_pixels[i] = (Uint32)pdata[i]; + } + + irr::video::ITexture* texture = VideoDriver->addTexture("buffer_image", image); + image->drop(); + + if(!texture) + return -1; + + if(use_color_key) + VideoDriver->makeColorKeyTexture(texture, irr::video::SColor(colorkey)); + + int img_id = -1; + rc_image_obj img; + img.image = texture; + img.alpha = 255; + + for(int i = 0; i < rc_image.size(); i++) + { + if(rc_image[i].image == NULL) + { + img_id = i; + break; + } + } + + if(img_id < 0) + { + img_id = rc_image.size(); + rc_image.push_back(img); + } + else + { + rc_image[img_id] = img; + } + + return img_id; +} + +int rc_createImage(int w, int h, double* pdata) +{ + return rc_createImageEx(w, h, pdata, 0); +} + + +void rc_getImageBuffer(int img_id, double * pdata) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(!rc_image[img_id].image) + return; + + Uint32* img_pixels = (Uint32*)rc_image[img_id].image->lock(); + + int image_size = rc_image[img_id].image->getSize().Width * rc_image[img_id].image->getSize().Height; + + for(int i = 0; i < image_size; i++) + pdata[i] = (double)img_pixels[i]; + + rc_image[img_id].image->unlock(); +} + +void rc_setBilinearFilter(bool flag) +{ + rc_bilinear_filter = flag; +} + +bool rc_getBilinearFilter() +{ + return rc_bilinear_filter; +} + +void rc_setColorMod(int img_id, Uint32 color) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + rc_image[img_id].color_mod = irr::video::SColor(color); + } +} + +Uint32 rc_getColorMod(int img_id) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return 0; + + if(rc_image[img_id].image) + { + return rc_image[img_id].color_mod.color; + } + + return 0; +} + + +void rc_setBlendMode(int blend_mode) +{ + switch(blend_mode) + { + case 0: rc_blend_mode = EBO_NONE; break; + case 1: rc_blend_mode = EBO_ADD; break; + case 2: rc_blend_mode = EBO_SUBTRACT; break; + case 3: rc_blend_mode = EBO_REVSUBTRACT; break; + case 4: rc_blend_mode = EBO_MIN; break; + case 5: rc_blend_mode = EBO_MAX; break; + case 6: rc_blend_mode = EBO_MIN_FACTOR; break; + case 7: rc_blend_mode = EBO_MAX_FACTOR; break; + case 8: rc_blend_mode = EBO_MIN_ALPHA; break; + case 9: rc_blend_mode = EBO_MAX_ALPHA; break; + } +} + +int rc_getBlendMode() +{ + return (int)rc_blend_mode; +} + +void draw2DImage(irr::video::IVideoDriver *driver, irr::video::ITexture* texture, irr::core::rect sourceRect, irr::core::position2d position, irr::core::position2d rotationPoint, irr::f32 rotation, irr::core::vector2df scale, bool useAlphaChannel, irr::video::SColor color, irr::core::vector2d screenSize) +{ + if(rc_active_canvas < 0 || rc_active_canvas >= rc_canvas.size()) + return; + + // Store and clear the projection matrix + irr::core::matrix4 oldProjMat = driver->getTransform(irr::video::ETS_PROJECTION); + driver->setTransform(irr::video::ETS_PROJECTION,irr::core::matrix4()); + + // Store and clear the view matrix + irr::core::matrix4 oldViewMat = driver->getTransform(irr::video::ETS_VIEW); + driver->setTransform(irr::video::ETS_VIEW,irr::core::matrix4()); + + // Store and clear the world matrix + irr::core::matrix4 oldWorldMat = driver->getTransform(irr::video::ETS_WORLD); + driver->setTransform(irr::video::ETS_WORLD,irr::core::matrix4()); + + // Find horizontal and vertical axes after rotation + irr::f32 c = cos(-rotation*irr::core::DEGTORAD); + irr::f32 s = sin(-rotation*irr::core::DEGTORAD); + irr::core::vector2df horizontalAxis(c,s); + irr::core::vector2df verticalAxis(s,-c); + + // First, we'll find the offset of the center and then where the center would be after rotation + irr::core::vector2df centerOffset(position.X+sourceRect.getWidth()/2.0f*scale.X-rotationPoint.X,position.Y+sourceRect.getHeight()/2.0f*scale.Y-rotationPoint.Y); + irr::core::vector2df center = centerOffset.X*horizontalAxis - centerOffset.Y*verticalAxis; + center.X += rotationPoint.X; + center.Y += rotationPoint.Y; + + // Now find the corners based off the center + irr::core::vector2df cornerOffset(sourceRect.getWidth()*scale.X/2.0f,sourceRect.getHeight()*scale.Y/2.0f); + verticalAxis *= cornerOffset.Y; + horizontalAxis *= cornerOffset.X; + irr::core::vector2df corner[4]; + corner[0] = center + verticalAxis - horizontalAxis; + corner[1] = center + verticalAxis + horizontalAxis; + corner[2] = center - verticalAxis - horizontalAxis; + corner[3] = center - verticalAxis + horizontalAxis; + + // Find the uv coordinates of the sourceRect + irr::core::vector2df textureSize(texture->getSize().Width, texture->getSize().Height); + irr::core::vector2df uvCorner[4]; + uvCorner[0] = irr::core::vector2df(sourceRect.UpperLeftCorner.X,sourceRect.UpperLeftCorner.Y); + uvCorner[1] = irr::core::vector2df(sourceRect.LowerRightCorner.X,sourceRect.UpperLeftCorner.Y); + uvCorner[2] = irr::core::vector2df(sourceRect.UpperLeftCorner.X,sourceRect.LowerRightCorner.Y); + uvCorner[3] = irr::core::vector2df(sourceRect.LowerRightCorner.X,sourceRect.LowerRightCorner.Y); + for (irr::s32 i = 0; i < 4; i++) + uvCorner[i] /= textureSize; + + // Vertices for the image + irr::video::S3DVertex vertices[4]; + irr::u16 indices[6] = { 0, 1, 2, 3 ,2 ,1 }; + + // Convert pixels to world coordinates + //irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + for (irr::s32 i = 0; i < 4; i++) { + vertices[i].Pos = irr::core::vector3df(((corner[i].X/screenSize.X)-0.5f)*2.0f,((corner[i].Y/screenSize.Y)-0.5f)*-2.0f,1); + vertices[i].TCoords = uvCorner[i]; + vertices[i].Color = color; + } + + // Create the material + // IMPORTANT: For irrlicht 1.8 and above you MUST ADD THIS LINE: + // material.BlendOperation = irr::video::EBO_ADD; + irr::video::SMaterial material; + material.Lighting = false; + material.ZWriteEnable = irr::video::EZW_OFF; + material.ZBuffer = false; + material.BackfaceCulling = false; + material.TextureLayer[0].Texture = texture; + material.TextureLayer[0].BilinearFilter = rc_bilinear_filter; + material.MaterialTypeParam = irr::video::pack_textureBlendFunc(irr::video::EBF_SRC_ALPHA, irr::video::EBF_ONE_MINUS_SRC_ALPHA, irr::video::EMFN_MODULATE_1X, irr::video::EAS_TEXTURE | irr::video::EAS_VERTEX_COLOR); + material.BlendOperation = rc_blend_mode; + //material.BlendOperation = irr::video::EBO_ADD; + + if (useAlphaChannel) + material.MaterialType = irr::video::EMT_ONETEXTURE_BLEND; + else + material.MaterialType = irr::video::EMT_SOLID; + + driver->setMaterial(material); + driver->drawIndexedTriangleList(&vertices[0],4,&indices[0],2); + + // Restore projection, world, and view matrices + driver->setTransform(irr::video::ETS_PROJECTION,oldProjMat); + driver->setTransform(irr::video::ETS_VIEW,oldViewMat); + driver->setTransform(irr::video::ETS_WORLD,oldWorldMat); +} + +void draw2DImage2(irr::video::IVideoDriver *driver, irr::video::ITexture* texture, irr::core::rect sourceRect, irr::core::rect destRect, irr::core::position2d rotationPoint, irr::f32 rotation, bool useAlphaChannel, irr::video::SColor color, irr::core::vector2d screenSize ) +{ + if(rc_active_canvas < 0 || rc_active_canvas >= rc_canvas.size()) + return; + + // Store and clear the projection matrix + irr::core::matrix4 oldProjMat = driver->getTransform(irr::video::ETS_PROJECTION); + driver->setTransform(irr::video::ETS_PROJECTION,irr::core::matrix4()); + + // Store and clear the view matrix + irr::core::matrix4 oldViewMat = driver->getTransform(irr::video::ETS_VIEW); + driver->setTransform(irr::video::ETS_VIEW,irr::core::matrix4()); + + // Store and clear the world matrix + irr::core::matrix4 oldWorldMat = driver->getTransform(irr::video::ETS_WORLD); + driver->setTransform(irr::video::ETS_WORLD,irr::core::matrix4()); + + // Find horizontal and vertical axes after rotation + irr::f32 c = cos(-rotation*irr::core::DEGTORAD); + irr::f32 s = sin(-rotation*irr::core::DEGTORAD); + irr::core::vector2df horizontalAxis(c,s); + irr::core::vector2df verticalAxis(s,-c); + + // First, we'll find the offset of the center and then where the center would be after rotation + irr::core::vector2df centerOffset(destRect.UpperLeftCorner.X+destRect.getWidth()/2.0f-rotationPoint.X,destRect.UpperLeftCorner.Y+destRect.getHeight()/2.0f-rotationPoint.Y); + irr::core::vector2df center = centerOffset.X*horizontalAxis - centerOffset.Y*verticalAxis; + center.X += rotationPoint.X; + center.Y += rotationPoint.Y; + + // Now find the corners based off the center + irr::core::vector2df cornerOffset(destRect.getWidth()/2.0f,destRect.getHeight()/2.0f); + verticalAxis *= cornerOffset.Y; + horizontalAxis *= cornerOffset.X; + irr::core::vector2df corner[4]; + corner[0] = center + verticalAxis - horizontalAxis; + corner[1] = center + verticalAxis + horizontalAxis; + corner[2] = center - verticalAxis - horizontalAxis; + corner[3] = center - verticalAxis + horizontalAxis; + + // Find the uv coordinates of the sourceRect + irr::core::vector2df textureSize(texture->getSize().Width, texture->getSize().Height); + irr::core::vector2df uvCorner[4]; + uvCorner[0] = irr::core::vector2df(sourceRect.UpperLeftCorner.X,sourceRect.UpperLeftCorner.Y); + uvCorner[1] = irr::core::vector2df(sourceRect.LowerRightCorner.X,sourceRect.UpperLeftCorner.Y); + uvCorner[2] = irr::core::vector2df(sourceRect.UpperLeftCorner.X,sourceRect.LowerRightCorner.Y); + uvCorner[3] = irr::core::vector2df(sourceRect.LowerRightCorner.X,sourceRect.LowerRightCorner.Y); + for (irr::s32 i = 0; i < 4; i++) + uvCorner[i] /= textureSize; + + // Vertices for the image + irr::video::S3DVertex vertices[4]; + irr::u16 indices[6] = { 0, 1, 2, 3 ,2 ,1 }; + + // Convert pixels to world coordinates + //irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + for (irr::s32 i = 0; i < 4; i++) { + vertices[i].Pos = irr::core::vector3df(((corner[i].X/screenSize.X)-0.5f)*2.0f,((corner[i].Y/screenSize.Y)-0.5f)*-2.0f,1); + vertices[i].TCoords = uvCorner[i]; + vertices[i].Color = color; + } + + // Create the material + // IMPORTANT: For irrlicht 1.8 and above you MUST ADD THIS LINE: + // material.BlendOperation = irr::video::EBO_ADD; + irr::video::SMaterial material; + material.Lighting = false; + material.ZWriteEnable = irr::video::EZW_OFF; + material.ZBuffer = false; + material.BackfaceCulling = false; + material.TextureLayer[0].Texture = texture; + material.TextureLayer[0].BilinearFilter = rc_bilinear_filter; //TODO: Add option to switch this on/off + material.BlendOperation = rc_blend_mode; + material.MaterialTypeParam = irr::video::pack_textureBlendFunc(irr::video::EBF_SRC_ALPHA, irr::video::EBF_ONE_MINUS_SRC_ALPHA, irr::video::EMFN_MODULATE_1X, irr::video::EAS_TEXTURE | irr::video::EAS_VERTEX_COLOR); + //material.AntiAliasing = irr::video::EAAM_OFF; + + if (useAlphaChannel) + material.MaterialType = irr::video::EMT_ONETEXTURE_BLEND; + else + material.MaterialType = irr::video::EMT_SOLID; + + driver->setMaterial(material); + driver->drawIndexedTriangleList(&vertices[0],4,&indices[0],2); + + // Restore projection, world, and view matrices + driver->setTransform(irr::video::ETS_PROJECTION,oldProjMat); + driver->setTransform(irr::video::ETS_VIEW,oldViewMat); + driver->setTransform(irr::video::ETS_WORLD,oldWorldMat); +} + + +void rc_drawImage(int img_id, int x, int y) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + irr::core::dimension2d src_size = rc_image[img_id].image->getSize(); + irr::core::rect sourceRect( irr::core::vector2d(0, 0), src_size); + + irr::core::position2d position(x, y); + + irr::core::position2d rotationPoint(0, 0); //since we are not rotating it doesn't matter + + irr::f32 rotation = 0; + irr::core::vector2df scale(1.0, 1.0); + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[img_id].alpha, + rc_image[img_id].color_mod.getRed(), + rc_image[img_id].color_mod.getGreen(), + rc_image[img_id].color_mod.getBlue()); + + //irr::core::rect dest( irr::core::vector2d(x, y), irr::core::dimension2d(src_w, src_h));; + + irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + draw2DImage(VideoDriver, rc_image[img_id].image, sourceRect, position, rotationPoint, rotation, scale, useAlphaChannel, color, screenSize); + } +} + +int rc_copyImage(int src_id) +{ + if(src_id < 0 || src_id >= rc_image.size()) + return -1; + + if(!rc_image[src_id].image) + return -1; + + irr::video::ITexture* texture = VideoDriver->addRenderTargetTexture(rc_image[src_id].image->getSize(), "img_copy", irr::video::ECF_A8R8G8B8); + + if(!texture) + return -1; + + VideoDriver->setRenderTarget(texture, true, false, irr::video::SColor(0)); + + + irr::core::dimension2d src_size = rc_image[src_id].image->getSize(); + irr::core::rect sourceRect( irr::core::vector2d(0, 0), src_size); + irr::core::position2d position(0, 0); + irr::core::position2d rotationPoint(0, 0); //since we are not rotating it doesn't matter + irr::f32 rotation = 0; + irr::core::vector2df scale(1.0, 1.0); + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[src_id].alpha, + rc_image[src_id].color_mod.getRed(), + rc_image[src_id].color_mod.getGreen(), + rc_image[src_id].color_mod.getBlue()); + irr::core::vector2df screenSize(src_size.Width, src_size.Height); + + draw2DImage(VideoDriver, rc_image[src_id].image, sourceRect, position, rotationPoint, rotation, scale, useAlphaChannel, color, screenSize); + + rc_setActiveCanvas(rc_active_canvas); + + int img_id = -1; + rc_image_obj img; + img.image = texture; + img.alpha = 255; + + for(int i = 0; i < rc_image.size(); i++) + { + if(rc_image[i].image == NULL) + { + img_id = i; + break; + } + } + + if(img_id < 0) + { + img_id = rc_image.size(); + rc_image.push_back(img); + } + else + { + rc_image[img_id] = img; + } + + return img_id; +} + + +void rc_drawImage_rotate(int img_id, int x, int y, double angle) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + irr::core::dimension2d src_size = rc_image[img_id].image->getSize(); + irr::core::rect sourceRect(0, 0, src_size.Width, src_size.Height); + + irr::core::position2d position(x, y); + + irr::core::position2d rotationPoint(x + (src_size.Width/2), y + (src_size.Height/2)); + + irr::f32 rotation = -1*angle; + irr::core::vector2df scale(1.0, 1.0); + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[img_id].alpha, + rc_image[img_id].color_mod.getRed(), + rc_image[img_id].color_mod.getGreen(), + rc_image[img_id].color_mod.getBlue()); + + irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + draw2DImage(VideoDriver, rc_image[img_id].image, sourceRect, position, rotationPoint, rotation, scale, useAlphaChannel, color, screenSize); + } +} + +void rc_drawImage_zoom(int img_id, int x, int y, double zx, double zy) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + irr::core::dimension2d src_size = rc_image[img_id].image->getSize(); + irr::core::rect sourceRect(0, 0, src_size.Width, src_size.Height); + + irr::core::position2d position(x, y); + + irr::core::position2d rotationPoint(x + (src_size.Width/2), y + (src_size.Height/2)); + + irr::f32 rotation = 0; + irr::core::vector2df scale((irr::f32)zx, (irr::f32)zy); + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[img_id].alpha, + rc_image[img_id].color_mod.getRed(), + rc_image[img_id].color_mod.getGreen(), + rc_image[img_id].color_mod.getBlue()); + + irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + draw2DImage(VideoDriver, rc_image[img_id].image, sourceRect, position, rotationPoint, rotation, scale, useAlphaChannel, color, screenSize); + } +} + +void rc_drawImage_zoomEx(int img_id, int x, int y, int src_x, int src_y, int src_w, int src_h, double zx, double zy) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + //irr::core::dimension2d src_size = rc_image[img_id].image->getSize(); + irr::core::rect sourceRect( irr::core::vector2d(src_x, src_y), irr::core::dimension2d(src_w, src_h)); + + irr::core::position2d position(x, y); + + irr::core::position2d rotationPoint(x + (src_w/2), y + (src_h/2)); + + irr::f32 rotation = 0; + irr::core::vector2df scale((irr::f32)zx, (irr::f32)zy); + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[img_id].alpha, + rc_image[img_id].color_mod.getRed(), + rc_image[img_id].color_mod.getGreen(), + rc_image[img_id].color_mod.getBlue()); + + irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + draw2DImage(VideoDriver, rc_image[img_id].image, sourceRect, position, rotationPoint, rotation, scale, useAlphaChannel, color, screenSize); + } +} + +void rc_drawImage_rotozoom(int img_id, int x, int y, double angle, double zx, double zy) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + irr::core::dimension2d src_size = rc_image[img_id].image->getSize(); + irr::core::rect sourceRect(0, 0, src_size.Width, src_size.Height); + + irr::core::position2d position(x, y); + + irr::core::position2d rotationPoint(x + (src_size.Width/2)*zx, y + (src_size.Height/2)*zy); + + irr::f32 rotation = -1*angle; + irr::core::vector2df scale((irr::f32)zx, (irr::f32)zy); + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[img_id].alpha, + rc_image[img_id].color_mod.getRed(), + rc_image[img_id].color_mod.getGreen(), + rc_image[img_id].color_mod.getBlue()); + + irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + draw2DImage(VideoDriver, rc_image[img_id].image, sourceRect, position, rotationPoint, rotation, scale, useAlphaChannel, color, screenSize); + } +} + +void rc_drawImage_rotozoomEx(int img_id, int x, int y, int src_x, int src_y, int src_w, int src_h, double angle, double zx, double zy) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + //irr::core::dimension2d src_size = rc_image[img_id].image->getSize(); + irr::core::rect sourceRect( irr::core::vector2d(src_x, src_y), irr::core::dimension2d(src_w, src_h)); + + irr::core::position2d position(x, y); + + irr::core::position2d rotationPoint(x + (src_w/2)*zx, y + (src_h/2)*zy); + + irr::f32 rotation = -1*angle; + irr::core::vector2df scale((irr::f32)zx, (irr::f32)zy); + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[img_id].alpha, + rc_image[img_id].color_mod.getRed(), + rc_image[img_id].color_mod.getGreen(), + rc_image[img_id].color_mod.getBlue()); + + irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + draw2DImage(VideoDriver, rc_image[img_id].image, sourceRect, position, rotationPoint, rotation, scale, useAlphaChannel, color, screenSize); + } +} + + +void rc_drawImage_flip(int img_id, int x, int y, bool h, bool v) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + irr::core::dimension2d src_size = rc_image[img_id].image->getSize(); + irr::core::rect sourceRect(0, 0, src_size.Width, src_size.Height); + + irr::core::position2d rotationPoint(x + (src_size.Width/2), y + (src_size.Height/2)); + + irr::f32 rotation = 0; + irr::core::vector2df scale((irr::f32)(h ? -1 : 1), (irr::f32) (v ? -1 : 1)); + + irr::core::position2d position( (h ? x+src_size.Width : x), (v ? y+src_size.Height : y)); + + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[img_id].alpha, + rc_image[img_id].color_mod.getRed(), + rc_image[img_id].color_mod.getGreen(), + rc_image[img_id].color_mod.getBlue()); + + irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + draw2DImage(VideoDriver, rc_image[img_id].image, sourceRect, position, rotationPoint, rotation, scale, useAlphaChannel, color, screenSize); + } +} + +void rc_drawImage_flipEx(int img_id, int x, int y, int src_x, int src_y, int src_w, int src_h, bool h, bool v) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + //irr::core::dimension2d src_size = rc_image[img_id].image->getSize(); + irr::core::rect sourceRect( irr::core::vector2d(src_x, src_y), irr::core::dimension2d(src_w, src_h)); + + irr::core::position2d rotationPoint(x + (src_w/2), y + (src_h/2)); + + irr::f32 rotation = 0; + irr::core::vector2df scale((irr::f32)(h ? -1 : 1), (irr::f32) (v ? -1 : 1)); + + irr::core::position2d position( (h ? x+src_w : x), (v ? y+src_h : y)); + + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[img_id].alpha, + rc_image[img_id].color_mod.getRed(), + rc_image[img_id].color_mod.getGreen(), + rc_image[img_id].color_mod.getBlue()); + + irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + draw2DImage(VideoDriver, rc_image[img_id].image, sourceRect, position, rotationPoint, rotation, scale, useAlphaChannel, color, screenSize); + } +} + + +void rc_drawImage_blit(int img_id, int x, int y, int src_x, int src_y, int src_w, int src_h) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + //irr::core::dimension2d src_size = rc_image[img_id].image->getSize(); + irr::core::rect sourceRect( irr::core::vector2d(src_x, src_y), irr::core::dimension2d(src_w, src_h)); + + irr::core::position2d position(x, y); + + irr::core::position2d rotationPoint(0, 0); //since we are not rotating it doesn't matter + + irr::f32 rotation = 0; + irr::core::vector2df scale(1.0, 1.0); + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[img_id].alpha, + rc_image[img_id].color_mod.getRed(), + rc_image[img_id].color_mod.getGreen(), + rc_image[img_id].color_mod.getBlue()); + + irr::core::rect dest( irr::core::vector2d(x, y), irr::core::dimension2d(src_w, src_h)); + + irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + draw2DImage(VideoDriver, rc_image[img_id].image, sourceRect, position, rotationPoint, rotation, scale, useAlphaChannel, color, screenSize); + } +} + + +void rc_drawImage_rotateEx(int img_id, int x, int y, int src_x, int src_y, int src_w, int src_h, int angle) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + //irr::core::dimension2d src_size = rc_image[img_id].image->getSize(); + irr::core::rect sourceRect( irr::core::vector2d(src_x, src_y), irr::core::dimension2d(src_w, src_h)); + + //irr::core::position2d position(x, y); + + irr::core::vector2d rotationPoint(x + (src_w/2), y + (src_h/2)); + + irr::f32 rotation = -1*angle; + //irr::core::vector2df scale(1.0, 1.0); + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[img_id].alpha, + rc_image[img_id].color_mod.getRed(), + rc_image[img_id].color_mod.getGreen(), + rc_image[img_id].color_mod.getBlue()); + + irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + irr::core::rect dest( irr::core::vector2d(x, y), irr::core::dimension2d(src_w, src_h)); + + draw2DImage2(VideoDriver, rc_image[img_id].image, sourceRect, dest, rotationPoint, rotation, useAlphaChannel, color, screenSize); + } +} + +void rc_drawImage_blitEx(int img_id, int x, int y, int w, int h, int src_x, int src_y, int src_w, int src_h) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + //irr::core::dimension2d src_size = rc_image[img_id].image->getSize(); + irr::core::rect sourceRect( irr::core::vector2d(src_x, src_y), irr::core::dimension2d(src_w, src_h)); + + //irr::core::position2d position(x, y); + + irr::core::position2d rotationPoint(0, 0); //since we are not rotating it doesn't matter + + irr::f32 rotation = 0; + irr::core::vector2df scale(1.0, 1.0); + bool useAlphaChannel = true; + irr::video::SColor color(rc_image[img_id].alpha, + rc_image[img_id].color_mod.getRed(), + rc_image[img_id].color_mod.getGreen(), + rc_image[img_id].color_mod.getBlue()); + + irr::core::rect dest( irr::core::vector2d(x, y), irr::core::dimension2d(w, h)); + + irr::core::vector2df screenSize(rc_canvas[rc_active_canvas].dimension.Width, rc_canvas[rc_active_canvas].dimension.Height); + + draw2DImage2(VideoDriver, rc_image[img_id].image, sourceRect, dest, rotationPoint, rotation, useAlphaChannel, color, screenSize ); + } +} + +void rc_setImageAlpha(int img_id, Uint8 alpha) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + rc_image[img_id].alpha = alpha; + } +} + +Uint32 rc_getImageAlpha(int img_id) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return 0; + + if(rc_image[img_id].image) + { + return rc_image[img_id].alpha; + } + + return 0; +} + +bool rc_imageExists(int img_id) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return false; + + if(rc_image[img_id].image) + return true; + + return false; +} + +void rc_getImageSize(int img_id, double* w, double* h) +{ + if(img_id < 0 || img_id >= rc_image.size()) + return; + + if(rc_image[img_id].image) + { + *w = (double)rc_image[img_id].image->getSize().Width; + *h = (double)rc_image[img_id].image->getSize().Height; + } +} + +void rc_setColorKey(int img_id, Uint32 colorkey) +{ + if(!rc_imageExists(img_id)) + return; + + VideoDriver->makeColorKeyTexture(rc_image[img_id].image, irr::video::SColor(colorkey)); +} + + + + + +bool rc_update() +{ + if(!device->run()) + return false; + + int win_w = 0, win_h = 0; + int w_scale = 1, h_scale = 1; + + if(rc_window) + { + SDL_GetWindowSize(rc_window, &win_w, &win_h); + //std::cout << "size = " << win_w << ", " << win_h << std::endl; + } + + SEvent irrevent; + SDL_Event SDL_event; + bool Close = false; + + while ( !Close && SDL_PollEvent( &SDL_event ) ) + { + // os::Printer::log("event: ", core::stringc((int)SDL_event.type).c_str(), ELL_INFORMATION); // just for debugging + + switch ( SDL_event.type ) + { + case SDL_QUIT: + SDL_PumpEvents(); + Close = true; + break; + case SDL_MOUSEMOTION: + irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT; + irrevent.MouseInput.Event = irr::EMIE_MOUSE_MOVED; + MouseX = irrevent.MouseInput.X = SDL_event.motion.x; + MouseY = irrevent.MouseInput.Y = SDL_event.motion.y; + MouseXRel = SDL_event.motion.xrel; + MouseYRel = SDL_event.motion.yrel; + irrevent.MouseInput.ButtonStates = MouseButtonStates; + + device->postEventFromUser(irrevent); + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + + irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT; + irrevent.MouseInput.X = SDL_event.button.x; + irrevent.MouseInput.Y = SDL_event.button.y; + + irrevent.MouseInput.Event = irr::EMIE_MOUSE_MOVED; + + switch(SDL_event.button.button) + { + case SDL_BUTTON_LEFT: + if (SDL_event.type == SDL_MOUSEBUTTONDOWN) + { + irrevent.MouseInput.Event = irr::EMIE_LMOUSE_PRESSED_DOWN; + MouseButtonStates |= irr::EMBSM_LEFT; + } + else + { + irrevent.MouseInput.Event = irr::EMIE_LMOUSE_LEFT_UP; + MouseButtonStates &= !irr::EMBSM_LEFT; + } + + //std::cout << "Position = " << SDL_event.button.x << ", " << SDL_event.button.y << std::endl; + //rc_canvas[0].offset.X++; + break; + + case SDL_BUTTON_RIGHT: + if (SDL_event.type == SDL_MOUSEBUTTONDOWN) + { + irrevent.MouseInput.Event = irr::EMIE_RMOUSE_PRESSED_DOWN; + MouseButtonStates |= irr::EMBSM_RIGHT; + } + else + { + irrevent.MouseInput.Event = irr::EMIE_RMOUSE_LEFT_UP; + MouseButtonStates &= !irr::EMBSM_RIGHT; + } + + //rc_setWindowFullscreen(1); + //rc_canvas[0].offset.X--; + break; + + case SDL_BUTTON_MIDDLE: + if (SDL_event.type == SDL_MOUSEBUTTONDOWN) + { + irrevent.MouseInput.Event = irr::EMIE_MMOUSE_PRESSED_DOWN; + MouseButtonStates |= irr::EMBSM_MIDDLE; + } + else + { + irrevent.MouseInput.Event = irr::EMIE_MMOUSE_LEFT_UP; + MouseButtonStates &= !irr::EMBSM_MIDDLE; + } + break; + + } + + irrevent.MouseInput.ButtonStates = MouseButtonStates; + + if (irrevent.MouseInput.Event != irr::EMIE_MOUSE_MOVED) + { + device->postEventFromUser(irrevent); + + if ( irrevent.MouseInput.Event >= EMIE_LMOUSE_PRESSED_DOWN && irrevent.MouseInput.Event <= EMIE_MMOUSE_PRESSED_DOWN ) + { + u32 clicks = device->checkSuccessiveClicks(irrevent.MouseInput.X, irrevent.MouseInput.Y, irrevent.MouseInput.Event); + if ( clicks == 2 ) + { + irrevent.MouseInput.Event = (EMOUSE_INPUT_EVENT)(EMIE_LMOUSE_DOUBLE_CLICK + irrevent.MouseInput.Event-EMIE_LMOUSE_PRESSED_DOWN); + device->postEventFromUser(irrevent); + } + else if ( clicks == 3 ) + { + irrevent.MouseInput.Event = (EMOUSE_INPUT_EVENT)(EMIE_LMOUSE_TRIPLE_CLICK + irrevent.MouseInput.Event-EMIE_LMOUSE_PRESSED_DOWN); + device->postEventFromUser(irrevent); + } + } + } + break; + + case SDL_MOUSEWHEEL: + irrevent.MouseInput.Event = irr::EMIE_MOUSE_WHEEL; + irrevent.MouseInput.Wheel = SDL_event.wheel.y; + rc_mwheelx = SDL_event.wheel.x; + rc_mwheely = SDL_event.wheel.y; + break; + + case SDL_TEXTINPUT: + if(rc_textinput_flag == true) + { + rc_textinput_string += SDL_event.text.text; + } + break; + + case SDL_KEYUP: + case SDL_KEYDOWN: + { + SDLKeyMap mp; + mp.SDLKey = SDL_event.key.keysym.sym; + s32 idx = KeyMap.binary_search(mp); + + EKEY_CODE key; + if (idx == -1) + key = (EKEY_CODE)0; + else + key = (EKEY_CODE)KeyMap[idx].Win32Key; + + irrevent.EventType = irr::EET_KEY_INPUT_EVENT; + irrevent.KeyInput.Char = SDL_event.key.keysym.sym; + irrevent.KeyInput.Key = key; + irrevent.KeyInput.PressedDown = (SDL_event.type == SDL_KEYDOWN); + irrevent.KeyInput.Shift = (SDL_event.key.keysym.mod & KMOD_SHIFT) != 0; + irrevent.KeyInput.Control = (SDL_event.key.keysym.mod & KMOD_CTRL ) != 0; + device->postEventFromUser(irrevent); + } + + if(SDL_event.type == SDL_KEYDOWN) + { + if(rc_textinput_flag && SDL_event.key.keysym.sym == SDLK_BACKSPACE && rc_textinput_string.length() > 0 + && rc_toggleBackspace) + { + rc_textinput_string = rc_utf8_substr(rc_textinput_string, 0, rc_utf8_length(rc_textinput_string)-1); + } + + rc_inkey_val = SDL_event.key.keysym.sym; + } + break; + + + case SDL_WINDOWEVENT: + if (SDL_event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + // FIXME: Implement more precise window control + // FIXME: Check if the window is game window + s32 Width = SDL_event.window.data1; + s32 Height = SDL_event.window.data2; + + rc_win_event = RC_WIN_EVENT_RESIZE; + + //resizeWindow(Width, Height); + if (VideoDriver) + VideoDriver->OnResize(core::dimension2d(Width, Height)); + + } + else if(SDL_event.window.event == SDL_WINDOWEVENT_CLOSE) + { + if(rc_window) + { + rc_win_event = RC_WIN_EVENT_CLOSE; + + if(SDL_QuitRequested() != 0) + { + SDL_FlushEvent(SDL_QUIT); + } + if(rc_win_exitOnClose) + { + rc_closeWindow_hw(); + Close = true; + } + + } + } + else if(SDL_event.window.event == SDL_WINDOWEVENT_MINIMIZED) + { + if(rc_window) + { + rc_win_event = RC_WIN_EVENT_MINIMIZE; + } + } + else if(SDL_event.window.event == SDL_WINDOWEVENT_MAXIMIZED) + { + if(rc_window) + { + rc_win_event = RC_WIN_EVENT_MAXIMIZE; + } + } + + break; + + case SDL_JOYDEVICEREMOVED: + //cout << "Joystick Removed: Instance " << event.jdevice.which << endl; + for(int i = 0; i < 8; i++) + { + if(SDL_event.jdevice.which == rc_joyID[i] && rc_joystick[i]) + { + //cout << "Joystick [" << i << "] was removed" << endl; + SDL_HapticClose(rc_haptic[i]); + SDL_JoystickClose(rc_joystick[i]); + rc_joystick[i] = NULL; + rc_haptic[i] = NULL; + rc_joyID[i] = -1; + rc_numJoysticks--; + break; + } + } + break; + case SDL_JOYDEVICEADDED: + //cout << "Joystick Added: " << event.jdevice.which << endl; + tmp_joy = SDL_JoystickOpen(SDL_event.jdevice.which); + tmp_joy_id = SDL_JoystickInstanceID(tmp_joy); + tmp_joy_flag = 0; + + for(int i = 0; i < 8; i++) + { + if(tmp_joy_id == rc_joyID[i]) + { + tmp_joy_flag = 1; + break; + } + } + + if(SDL_event.jdevice.which >= 0 && tmp_joy_flag == 0) + { + for(int i = 0; i < 8; i++) + { + if(rc_joystick[i] == NULL) + { + //cout << "Assigned " << i << endl; + rc_joystick[i] = tmp_joy; + rc_haptic[i] = SDL_HapticOpenFromJoystick(rc_joystick[i]); + SDL_HapticRumbleInit(rc_haptic[i]); + rc_joyID[i] = tmp_joy_id; + rc_numJoysticks++; + break; + } + } + } + break; + +#ifndef RC_MOBILE //This block handles touch events for non-mobile devices, Just in case it has a touch screen that SDL2 can get events for + case SDL_FINGERDOWN: + rc_touch = 1; + rc_touchX = SDL_event.tfinger.x * win_w; + rc_touchY = SDL_event.tfinger.y * win_h; +#ifdef RC_IOS + rc_pressure = 1; //FIXME: On IOS pressure is always getting reported as 0 on finger down so I am just setting it to 1 until I figure this out +#else + rc_pressure = SDL_event.tfinger.pressure; +#endif + rc_setTouchFingerEvent(SDL_event.tfinger.fingerId, rc_touchX, rc_touchY, rc_pressure); + break; + case SDL_FINGERUP: + rc_touch = 0; + rc_mt_status = 0; + rc_touchX = SDL_event.tfinger.x * win_w; + rc_touchY = SDL_event.tfinger.y * win_h; + rc_pressure = SDL_event.tfinger.pressure; + rc_setTouchFingerEvent(SDL_event.tfinger.fingerId, -1, -1, 0); + break; + case SDL_FINGERMOTION: + rc_touch = 1; + rc_touchX = SDL_event.tfinger.x * win_w; + rc_touchY = SDL_event.tfinger.y * win_h; + rc_motionX = SDL_event.tfinger.dx * win_w; + rc_motionY = SDL_event.tfinger.dy * win_h; +#ifdef RC_IOS + rc_pressure = 1; +#else + rc_pressure = SDL_event.tfinger.pressure; +#endif + rc_setTouchFingerEvent(SDL_event.tfinger.fingerId, rc_touchX, rc_touchY, rc_pressure); + break; + case SDL_MULTIGESTURE: + rc_touch = 2; + rc_mt_status = 1; + rc_mt_x = SDL_event.mgesture.x; + rc_mt_y = SDL_event.mgesture.y; + rc_mt_numFingers = SDL_event.mgesture.numFingers; + rc_mt_dist = SDL_event.mgesture.dDist; + rc_mt_theta = SDL_event.mgesture.dTheta; +#ifdef RC_IOS + rc_pressure = 1; +#else + rc_pressure = SDL_event.tfinger.pressure; +#endif + break; +#endif + + case SDL_USEREVENT: + irrevent.EventType = irr::EET_USER_EVENT; + irrevent.UserEvent.UserData1 = reinterpret_cast(SDL_event.user.data1); + irrevent.UserEvent.UserData2 = reinterpret_cast(SDL_event.user.data2); + + device->postEventFromUser(irrevent); + break; + + default: + break; + } // end switch + + } // end while + + if(!Close) + { + VideoDriver->setRenderTarget(rc_canvas[0].texture); + + for(int cz = 0; cz < rc_canvas_zOrder.size(); cz++) + { + int canvas_id = rc_canvas_zOrder[cz]; + + if(rc_canvas[canvas_id].texture) + { + irr::core::rect dest(rc_canvas[canvas_id].viewport.position, rc_canvas[canvas_id].viewport.dimension); + irr::core::rect src(rc_canvas[canvas_id].offset, rc_canvas[canvas_id].viewport.dimension); + + //std::cout << "draw canvas[" << canvas_id << "]" << std::endl; + + VideoDriver->draw2DImage(rc_canvas[canvas_id].texture, dest, src); + } + } + + //env->drawAll(); + + VideoDriver->setRenderTarget(0); + VideoDriver->beginScene(true, true); + VideoDriver->draw2DImage(rc_canvas[0].texture, irr::core::vector2d(0,0)); + //device->getGUIEnvironment()->drawAll(); + VideoDriver->endScene(); + + rc_setActiveCanvas(rc_active_canvas); + } + + return (!Close); +} + +#endif // RC_GFX_INCLUDED diff --git a/rcbasic_runtime/irr_gfx/rc_utf8.h b/rcbasic_runtime/irr_gfx/rc_utf8.h new file mode 100644 index 0000000..796e49e --- /dev/null +++ b/rcbasic_runtime/irr_gfx/rc_utf8.h @@ -0,0 +1,45 @@ +#ifndef RC_UTF8_H_INCLUDED +#define RC_UTF8_H_INCLUDED + +#include +#include + +std::string rc_utf8_substr(const std::string& str, unsigned int start, unsigned int leng) +{ + if (leng==0) { return ""; } + unsigned int c, i, ix, q, min=std::string::npos, max=std::string::npos; + for (q=0, i=0, ix=str.length(); i < ix; i++, q++) + { + if (q==start){ min=i; } + if (q<=start+leng || leng==std::string::npos){ max=i; } + + c = (unsigned char) str[i]; + if ( + //c>=0 && + c<=127) i+=0; + else if ((c & 0xE0) == 0xC0) i+=1; + else if ((c & 0xF0) == 0xE0) i+=2; + else if ((c & 0xF8) == 0xF0) i+=3; + //else if (($c & 0xFC) == 0xF8) i+=4; // 111110bb //byte 5, unnecessary in 4 byte UTF-8 + //else if (($c & 0xFE) == 0xFC) i+=5; // 1111110b //byte 6, unnecessary in 4 byte UTF-8 + else return "";//invalid utf8 + } + if (q<=start+leng || leng==std::string::npos){ max=i; } + if (min==std::string::npos || max==std::string::npos) { return ""; } + return str.substr(min,max-min); +} + +std::size_t rc_utf8_length(std::string const &s) +{ + return std::count_if(s.begin(), s.end(), + [](char c) { return (static_cast(c) & 0xC0) != 0x80; } ); +} + +// convert UTF-8 string to wstring +std::wstring utf8_to_wstring (const std::string& str) +{ + std::wstring_convert> myconv; + return myconv.from_bytes(str); +} + +#endif // RC_UTF8_H_INCLUDED