DTS/Dynamic Skin Modifiers
From TDN
Introduction
This resource will let you know how to add to your game an ability to dynamically "paint" on any ShapeBase derived objects by specifying mask layers and up to four colors per single mask.
For reference see this .blog post.
This is a combination of two resources: setSkinSpecific/setSkinMesh and Skin Modifiers with major changes/additions. Also, I've made some improvements on network side.
Based on Stock TGE SDK 1.5, but can be used in any TGE version (1.3, 1.4.x, 1.5.x, AFX, MMOKITs, etc). I can think it also can be used in TGEA, but it's better to use Shaders to use it's advantages.
To be able to use it, you need to have shapes to be modeled/textured with a "skinning support" enabled (see Help with setSkinName forum thread for more info). Also, see Re-Skinning.
Note:
If anyone knows good description on usage of .setSkinName() please add the link.
This resource could be useful to add/change skins on your objects in game. Be that players, cars or any other objects - you can change hair color, add tattoo or scars, paint cars with different colors and textures. The imagination is only limit.
Discussion
To discuss this resource you can use the TDN-based article discussion, or (preferred) post to the dedicated forum thread.
How it works
The basic functionality of this resource is applying custom-painted bitmap (by blending) to the original texture. You can specify up to four colors per one "slot" (layer to use for painting).
In script it's done by calling a function on any ShapeBase-derived object:
%obj.addSkinModifier(%slot, %skinLocation, %skinName, %maskName, %color1, %color2, %color3, %color4);
Where
%slot - the integer from 0 to 29 (can be changed in code to suit your needs). This is the slot that the skin modifier is to occupy. Assigning a second skin modifier to the same slot will override the previous modifier.
%skinLocation - is the name of the model's base skin texture. E.g. if you have base.myobject.jpg as a base texture, if will be myobject. You can have many textures on your model (base.head.jpg, base.torso.jpg, etc).
%skinName - the skin name to use. It's the image to use - red.head.jpg, armor.torso.jpg, etc.
%maskName - the mask image.
%color1, color2, color3, color4 - the colors to use for painting. The color1 will use the red channel of mask, second - green, third - blue and fourth - alpha.
You can specify all four, or any combination of those. You can just call
%obj.addSkinModifier(0, "torso", "armor");
to change the skin (similar to .setSkinName(), but used for multiple parts on object).
Improvement needed
- Not sure, but may be it's possible to use OpenGL to do blending and rendering or modified textures to make it faster?
- Another possible way of doing covered here - is by using something like RenderToTexture, but I'm not familiar with this, hopefully someone can improve it.
Engine changes description
The good think, that this resource doesn't break anything from Stock SDK. It adds a lot, but actual changes are only in a couple of functions and they are "safe to use".
Affected files:
engine/core/bitStream.*
We are adding new functions to BitStream, to be able transmit colors over the network.
Implemented:
void writeColor3I(const ColorI& sColor); void readColor3I(ColorI *sColor);
void writeColor3F(const ColorF& sColor, F32 scale = 0.01f); void readColor3F(ColorF *sColor, F32 scale = 0.01f);
void writeColor4I(const ColorI& sColor); void readColor4I(ColorI *sColor);
void writeColor4F(const ColorF& sColor, F32 scale = 0.01f); void readColor4F(ColorF *sColor, F32 scale = 0.01f);
but only *Color4I() pair is used. You can remove the rest from your engine to keep it "clean".
engine/dgl/gBitmap.*
In GBitmap class we need to add some custom functionality. This is a major point when the optimization is needed.
void fill(const U8 in_r, const U8 in_g, const U8 in_b, const U8 in_a); void fill(ColorI fColor);
These two are used to "fill" the new created image with specified color.
void fillByChannels(const GBitmap& rBlend, U8 channel, ColorI fColor); void fillByChannels(const GBitmap& rBlend, U8 channel1, U8 channel2, ColorI fColor1, ColorI fColor2); void fillByChannels(const GBitmap& rBlend, U8 channel1, U8 channel2, U8 channel3, ColorI fColor1, ColorI fColor2, ColorI fColor3); void fillByChannels(const GBitmap& rBlend, U8 channel1, U8 channel2, U8 channel3, U8 channel4, ColorI fColor1, ColorI fColor2, ColorI fColor3, ColorI fColor4);
The main "magic" is here. You call this function(s) on destination bitmap with specifying rBlend bitmap (the MASK) and channel (R,G,B or A - 0,1,2 or 3) and color(s) to use for filling the destination with.
void alphaBlend(const GBitmap& rBlend);
This function blends the specified rBlend bitmap on current bitmap instance (applying our "modifications").
engine/game/shapeBase.*
bool addSkinModifier(U32 slot, const char *skinLocation, const char *skinName, const char *maskName, const ColorI& printColor1, const ColorI& printColor2, const ColorI& printColor3, const ColorI& printColor4 );
Add new skin modifier to the object. It stores parameters in local "storage" and adds network mask, so all clients around will be aware of the change.
bool removeSkinModifier(U32 slot);
remove skin modifier from object for specified slot.
void clearSkinModifiers();
clears all skin modifiers on this shapeBase object.
engine/ts/tsShapeInstance.*
TextureHandle modifySkin(const char *baseSkinName, const char *skinName, U32 matIndex);
This function is substitute to TextureHandle(), so engine will use modified textures instead of "normal" behaviour.
void reSkin(StringHandle& newBaseHandle);
This one is changed to use modifySkin() function instead of TextureHandle, additionally, it changed to apply skinning by "parts".
void uploadSkinModifier(U32 slot, StringHandle& skinLocation, StringHandle& skinName, StringHandle& maskName, const ColorI& printColor1, const ColorI& printColor2, const ColorI& printColor3, const ColorI& printColor4 );
uploadSkinModifier() is called from ShapeBase::unpackUpdate - here we adding modifiers to our TSShape instance in game.
void removeSkinModifier(U32 slot);
clear unneeded skin modifier - same, called from unpack.
void clearSkinModifiers();
clears all modifiers - this one is called when the TSShapeInstance got destructed (to keep everything clean, freeing memory)
long whichMaterial(const char *skinLoc);
get the index of speficied material - used for setting modifiers.
void modifySkins();
force applying the modifiers on skins after all skin modifiers are set.
Code
In this section is listed all the changes needed to be done with Torque to make it work.
core/bitStream.h
In a public section of BitStream class, paste the following (I've put right before writeNormalVector()):
// colors void writeColor3I(const ColorI& sColor); void readColor3I(ColorI *sColor); void writeColor3F(const ColorF& sColor, F32 scale = 0.01f); void readColor3F(ColorF *sColor, F32 scale = 0.01f); void writeColor4I(const ColorI& sColor); void readColor4I(ColorI *sColor); void writeColor4F(const ColorF& sColor, F32 scale = 0.01f); void readColor4F(ColorF *sColor, F32 scale = 0.01f);
Actually, the only writeColor4I / readColor4I are used by this resource, you can skin the rest.
core/bitStream.cc
Again, I've put it before before writeNormalVector():
void BitStream::writeColor3I(const ColorI& sColor) { writeInt(sColor.red, 8); writeInt(sColor.green, 8); writeInt(sColor.blue, 8); } void BitStream::readColor3I(ColorI *sColor) { sColor->red = readInt(8); sColor->green = readInt(8); sColor->blue = readInt(8); sColor->alpha = 255; } void BitStream::writeColor4I(const ColorI& sColor) { writeInt(sColor.red, 8); writeInt(sColor.green, 8); writeInt(sColor.blue, 8); writeInt(sColor.alpha, 8); } void BitStream::readColor4I(ColorI *sColor) { sColor->red = readInt(8); sColor->green = readInt(8); sColor->blue = readInt(8); sColor->alpha = readInt(8); } void BitStream::writeColor3F(const ColorF& sColor, F32 scale) { writeCompressedPoint(Point3F(sColor.red, sColor.green, sColor.blue), scale); } void BitStream::readColor3F(ColorF *sColor, F32 scale) { Point3F pnt; readCompressedPoint(&pnt, scale); sColor->red = pnt.x; sColor->green = pnt.y; sColor->blue = pnt.z; } void BitStream::writeColor4F(const ColorF& sColor, F32 scale) { writeCompressedPoint(Point3F(sColor.red, sColor.green, sColor.blue), scale); write(sColor.alpha); } void BitStream::readColor4F(ColorF *sColor, F32 scale) { Point3F pnt; readCompressedPoint(&pnt, scale); sColor->red = pnt.x; sColor->green = pnt.y; sColor->blue = pnt.z; read(&sColor->alpha); }
dgl/gBitmap.h
In public section of class GBitmap, paste the following (around line 100, after getColor/setColor() is okay):
//skinModifiers void fill(const U8 in_r, const U8 in_g, const U8 in_b, const U8 in_a); void fill(ColorI fColor); void fillByChannels(const GBitmap& rBlend, U8 channel, ColorI fColor); void fillByChannels(const GBitmap& rBlend, U8 channel1, U8 channel2, ColorI fColor1, ColorI fColor2); void fillByChannels(const GBitmap& rBlend, U8 channel1, U8 channel2, U8 channel3, ColorI fColor1, ColorI fColor2, ColorI fColor3); void fillByChannels(const GBitmap& rBlend, U8 channel1, U8 channel2, U8 channel3, U8 channel4, ColorI fColor1, ColorI fColor2, ColorI fColor3, ColorI fColor4); void alphaBlend(const GBitmap& rBlend);
dgl/gBitmap.cc
After setColor(), around line 676, paste:
//skinModifiers //------------------------------------------------------------------------------ void GBitmap::fill(const U8 in_r, const U8 in_g, const U8 in_b, const U8 in_a) { fill(ColorI(in_r, in_g, in_b, in_a)); } void GBitmap::fill(ColorI fColor) { for (U32 yy=0; (yy<height); yy++) { for (U32 xx=0; (xx<width); xx++) { setColor(xx,yy,fColor); } } } // one channel case void GBitmap::fillByChannels(const GBitmap& rBlend, U8 channel, ColorI fColor) { for (U32 yy=0; (yy<height) && (yy<rBlend.height); yy++) { for (U32 xx=0; (xx<width) && (xx<rBlend.width); xx++) { ColorI tempColor; rBlend.getColor(xx,yy,tempColor); U8 level; if (channel==0) level = tempColor.red; else if (channel==1) level = tempColor.green; else if (channel==2) level = tempColor.blue; else level = tempColor.alpha; if (level != 0 ) { ColorI fill = ColorI(0,0,0,0); F32 t; t = fColor.red * level /255; fill.red = U8(t); t = fColor.green * level /255; fill.green = U8(t); t = fColor.blue * level /255; fill.blue = U8(t); t = fColor.alpha * level /255; fill.alpha = U8(t); setColor(xx, yy, fill); } } } } // two channels case void GBitmap::fillByChannels(const GBitmap& rBlend, U8 channel1, U8 channel2, ColorI fColor1, ColorI fColor2) { for (U32 yy=0; (yy<height) && (yy<rBlend.height); yy++) { for (U32 xx=0; (xx<width) && (xx<rBlend.width); xx++) { ColorI tempColor; rBlend.getColor(xx,yy,tempColor); U8 level1, level2; if (channel1==0) level1 = tempColor.red; else if (channel1==1) level1 = tempColor.green; else if (channel1==2) level1 = tempColor.blue; else level1 = tempColor.alpha; if (channel2==0) level2 = tempColor.red; else if (channel2==1) level2 = tempColor.green; else if (channel2==2) level2 = tempColor.blue; else level2 = tempColor.alpha; ColorI fill1 = ColorI(0,0,0,0); ColorI fill2 = ColorI(0,0,0,0); F32 t; //if (level1 != 0 ) t = fColor1.red * level1 /255; fill1.red = U8(t); t = fColor1.green * level1 /255; fill1.green = U8(t); t = fColor1.blue * level1 /255; fill1.blue = U8(t); t = fColor1.alpha * level1 /255; fill1.alpha = U8(t); //if (level2 != 0 ) t = fColor2.red * level2 /255; fill2.red = U8(t); t = fColor2.green * level2 /255; fill2.green = U8(t); t = fColor2.blue * level2 /255; fill2.blue = U8(t); t = fColor2.alpha * level2 /255; fill2.alpha = U8(t); if (level1 != 0 || level2 != 0) { tempColor.red = getMin(fill1.red + fill2.red, 255); tempColor.green = getMin(fill1.green + fill2.green, 255); tempColor.blue = getMin(fill1.blue + fill2.blue, 255); tempColor.alpha = getMin(fill1.alpha + fill2.alpha, 255); setColor(xx, yy, tempColor); } } } } // three channels case void GBitmap::fillByChannels(const GBitmap& rBlend, U8 channel1, U8 channel2, U8 channel3, ColorI fColor1, ColorI fColor2, ColorI fColor3) { for (U32 yy=0; (yy<height) && (yy<rBlend.height); yy++) { for (U32 xx=0; (xx<width) && (xx<rBlend.width); xx++) { ColorI tempColor; rBlend.getColor(xx,yy,tempColor); U8 level1, level2, level3; if (channel1==0) level1 = tempColor.red; else if (channel1==1) level1 = tempColor.green; else if (channel1==2) level1 = tempColor.blue; else level1 = tempColor.alpha; if (channel2==0) level2 = tempColor.red; else if (channel2==1) level2 = tempColor.green; else if (channel2==2) level2 = tempColor.blue; else level2 = tempColor.alpha; if (channel3==0) level3 = tempColor.red; else if (channel3==1) level3 = tempColor.green; else if (channel3==2) level3 = tempColor.blue; else level3 = tempColor.alpha; ColorI fill1 = ColorI(0,0,0,0); ColorI fill2 = ColorI(0,0,0,0); ColorI fill3 = ColorI(0,0,0,0); F32 t; //if (level1 != 0 ) t = fColor1.red * level1 /255; fill1.red = U8(t); t = fColor1.green * level1 /255; fill1.green = U8(t); t = fColor1.blue * level1 /255; fill1.blue = U8(t); t = fColor1.alpha * level1 /255; fill1.alpha = U8(t); //if (level2 != 0 ) t = fColor2.red * level2 /255; fill2.red = U8(t); t = fColor2.green * level2 /255; fill2.green = U8(t); t = fColor2.blue * level2 /255; fill2.blue = U8(t); t = fColor2.alpha * level2 /255; fill2.alpha = U8(t); //if (level3 != 0 ) t = fColor3.red * level3 /255; fill3.red = U8(t); t = fColor3.green * level3 /255; fill3.green = U8(t); t = fColor3.blue * level3 /255; fill3.blue = U8(t); t = fColor3.alpha * level3 /255; fill3.alpha = U8(t); if (level1 != 0 || level2 != 0 || level3 != 0) { tempColor.red = getMin(fill1.red + fill2.red + fill3.red, 255); tempColor.green = getMin(fill1.green + fill2.green + fill3.green, 255); tempColor.blue = getMin(fill1.blue + fill2.blue + fill3.blue, 255); tempColor.alpha = getMin(fill1.alpha + fill2.alpha + fill3.alpha, 255); setColor(xx, yy, tempColor); } } } } // four channels case void GBitmap::fillByChannels(const GBitmap& rBlend, U8 channel1, U8 channel2, U8 channel3, U8 channel4, ColorI fColor1, ColorI fColor2, ColorI fColor3, ColorI fColor4) { for (U32 yy=0; (yy<height) && (yy<rBlend.height); yy++) { for (U32 xx=0; (xx<width) && (xx<rBlend.width); xx++) { ColorI tempColor; rBlend.getColor(xx,yy,tempColor); U8 level1, level2, level3, level4; if (channel1==0) level1 = tempColor.red; else if (channel1==1) level1 = tempColor.green; else if (channel1==2) level1 = tempColor.blue; else level1 = tempColor.alpha; if (channel2==0) level2 = tempColor.red; else if (channel2==1) level2 = tempColor.green; else if (channel2==2) level2 = tempColor.blue; else level2 = tempColor.alpha; if (channel3==0) level3 = tempColor.red; else if (channel3==1) level3 = tempColor.green; else if (channel3==2) level3 = tempColor.blue; else level3 = tempColor.alpha; if (channel4==0) level4 = tempColor.red; else if (channel4==1) level4 = tempColor.green; else if (channel4==2) level4 = tempColor.blue; else level4 = tempColor.alpha; ColorI fill1 = ColorI(0,0,0,0); ColorI fill2 = ColorI(0,0,0,0); ColorI fill3 = ColorI(0,0,0,0); ColorI fill4 = ColorI(0,0,0,0); F32 t; //if (level1 != 0 ) t = fColor1.red * level1 /255; fill1.red = U8(t); t = fColor1.green * level1 /255; fill1.green = U8(t); t = fColor1.blue * level1 /255; fill1.blue = U8(t); t = fColor1.alpha * level1 /255; fill1.alpha = U8(t); //if (level2 != 0 ) t = fColor2.red * level2 /255; fill2.red = U8(t); t = fColor2.green * level2 /255; fill2.green = U8(t); t = fColor2.blue * level2 /255; fill2.blue = U8(t); t = fColor2.alpha * level2 /255; fill2.alpha = U8(t); //if (level3 != 0 ) t = fColor3.red * level3 /255; fill3.red = U8(t); t = fColor3.green * level3 /255; fill3.green = U8(t); t = fColor3.blue * level3 /255; fill3.blue = U8(t); t = fColor3.alpha * level3 /255; fill3.alpha = U8(t); //if (level4 != 0 ) t = fColor4.red * level4 /255; fill4.red = U8(t); t = fColor4.green * level4 /255; fill4.green = U8(t); t = fColor4.blue * level4 /255; fill4.blue = U8(t); t = fColor4.alpha * level4 /255; fill4.alpha = U8(t); if (level1 != 0 || level2 != 0 || level3 != 0 || level4 != 0) { tempColor.red = getMin(fill1.red + fill2.red + fill3.red + fill4.red, 255); tempColor.green = getMin(fill1.green + fill2.green + fill3.green + fill4.green, 255); tempColor.blue = getMin(fill1.blue + fill2.blue + fill3.blue + fill4.blue, 255); tempColor.alpha = getMin(fill1.alpha + fill2.alpha + fill3.alpha + fill4.alpha, 255); setColor(xx, yy, tempColor); } } } } void GBitmap::alphaBlend(const GBitmap& rBlend) { for (U32 yy=0; (yy<height) && (yy<rBlend.height); yy++) { for (U32 xx=0; (xx<width) && (xx<rBlend.width); xx++) { ColorI sColor, dColor; rBlend.getColor(xx,yy,sColor); if (sColor.alpha==255) setColor(xx,yy,sColor); else if (sColor.alpha>0) { getColor(xx,yy,dColor); dColor.red=((sColor.alpha*(sColor.red-dColor.red))/256)+dColor.red; dColor.green=((sColor.alpha*(sColor.green-dColor.green))/256)+dColor.green; dColor.blue=((sColor.alpha*(sColor.blue-dColor.blue))/256)+dColor.blue; setColor(xx,yy,dColor); } } } }
game/shapeBase.h
Adding constants:
in public section of ShapeBase class definition, change the end of enum PublicConstants {
from this
CollisionTimeoutValue = 250 ///< Timeout in ms. };
to this:
CollisionTimeoutValue = 250, ///< Timeout in ms. MaxSkinModifiers = 30 ///< Maximum allowed slots for skin modifiers };
In protected section, right after onImpact and before constructor/destructor of ShapeBase, paste (make it to look like this):
virtual void onImpact(VectorF vec); /// @} /// @name skinModifiers /// @{ struct SkinMod2 { StringHandle skinLocation; StringHandle skinName; StringHandle maskName; ColorI printColors[4]; SkinMod2() { skinLocation = StringHandle(); }; }; SkinMod2 mSkinModifiers2[MaxSkinModifiers]; U32 mSkinModMaskBits; /// @} public: ShapeBase(); ~ShapeBase();
Few lines down, in enum ShapeBaseMasks { change
SkinMask = Parent::NextFreeMask << 7, SoundMaskN = Parent::NextFreeMask << 8, ///< Extends + MaxSoundThreads bits ThreadMaskN = SoundMaskN << MaxSoundThreads, ///< Extends + MaxScriptThreads bits
to this:
SkinMask = Parent::NextFreeMask << 7, SkinMaskMod = Parent::NextFreeMask << 8, SoundMaskN = Parent::NextFreeMask << 9, ///< Extends + MaxSoundThreads bits ThreadMaskN = SoundMaskN << MaxSoundThreads, ///< Extends + MaxScriptThreads bits
(You can use existing SkinMask if you want, then it will not cause to you any problems if you already have all your bits used, then you need to pay attention in the code later, when you do pack/unpack and set masks in functions that change skin modifiers)
Again, few lines down, after the enum BaseMaskConstants { ... };, make your code to look like this:
ImageMask = (ImageMaskN << MaxMountedImages) - ImageMaskN }; /// @} /// @name skinModifiers /// @{ bool addSkinModifier(U32 slot, const char *skinLocation, const char *skinName, const char *maskName, const ColorI& printColor1, const ColorI& printColor2, const ColorI& printColor3, const ColorI& printColor4 ); ///< set/update skin modifier bool removeSkinModifier(U32 slot); ///< removes custom modifier void clearSkinModifiers(); ///< clears all modifiers on this shapeBase object /// @} static bool gRenderEnvMaps; ///< Global flag which turns on or off all environment maps
game/shapeBase.cc
ShapeBase::ShapeBase()
At the end of ShapeBase constructor function - ShapeBase::ShapeBase(), add:
//skinModifiers mSkinModMaskBits = 0;
bool ShapeBase::onNewDataBlock(GameBaseData* dptr)
Further down, in bool ShapeBase::onNewDataBlock(GameBaseData* dptr), after the mSkinNameHandle handling, change (~line 842):
if (isGhost() && mSkinNameHandle.isValidString() && mShapeInstance) { mShapeInstance->reSkin(mSkinNameHandle); mSkinHash = _StringTable::hashString(mSkinNameHandle.getString()); }
to this:
if (isGhost() && mSkinNameHandle.isValidString() && mShapeInstance) { mShapeInstance->reSkin(mSkinNameHandle); mSkinHash = _StringTable::hashString(mSkinNameHandle.getString()); } //skinModifiers if (isGhost() && mShapeInstance) { mShapeInstance->modifySkins(); }
U32 ShapeBase::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
In U32 ShapeBase::packUpdate(NetConnection *con, U32 mask, BitStream *stream) change: In case you are using separate netmask from this:
if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask | ThreadMask | ImageMask | CloakMask | MountedMask | InvincibleMask | ShieldMask | SkinMask))) return retMask;
to this:
if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask | ThreadMask | ImageMask | CloakMask | MountedMask | InvincibleMask | ShieldMask | SkinMask | SkinMaskMod))) return retMask;
Further down change from this:
// Group some of the uncommon stuff together. if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask | SkinMask))) {
to this:
// Group some of the uncommon stuff together. if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask | SkinMask | SkinMaskMod))) {
In the same function, after
if (stream->writeFlag(mask & SkinMask)) { con->packStringHandleU(stream, mSkinNameHandle); }
add:
if (stream->writeFlag(mask & SkinMaskMod)) { //skinModifiers Con::errorf("packing skins! %d", mSkinModMaskBits); for (U32 ss=0; ss<MaxSkinModifiers; ss++) { if (stream->writeFlag(mSkinModMaskBits & (1<<ss))) { con->packStringHandleU(stream, mSkinModifiers2[ss].skinLocation); con->packStringHandleU(stream, mSkinModifiers2[ss].skinName); con->packStringHandleU(stream, mSkinModifiers2[ss].maskName); // we only transmit colors if alpha is not zero, by default - no colouring if (stream->writeFlag(mSkinModifiers2[ss].printColors[0].alpha!=0)) stream->writeColor4I(mSkinModifiers2[ss].printColors[0]); if (stream->writeFlag(mSkinModifiers2[ss].printColors[1].alpha!=0)) stream->writeColor4I(mSkinModifiers2[ss].printColors[1]); if (stream->writeFlag(mSkinModifiers2[ss].printColors[2].alpha!=0)) stream->writeColor4I(mSkinModifiers2[ss].printColors[2]); if (stream->writeFlag(mSkinModifiers2[ss].printColors[3].alpha!=0)) stream->writeColor4I(mSkinModifiers2[ss].printColors[3]); } } }
(in case you go with using same mask, paste this codeblock right inside the SkinMask.)
void ShapeBase::unpackUpdate(NetConnection *con, BitStream *stream)
For now - we packed data, now we need to unpack it on client.
in void ShapeBase::unpackUpdate(NetConnection *con, BitStream *stream) after:
if (stream->readFlag()) { // SkinMask ... }
code block, paste the following:
if (stream->readFlag()) { // SkinMaskMod //skinModifiers bool modifyFlag=false; for (U32 ss=0; ss<MaxSkinModifiers; ss++) { if (stream->readFlag()) { StringHandle skinLocation = con->unpackStringHandleU(stream); StringHandle skinName = con->unpackStringHandleU(stream); StringHandle maskName = con->unpackStringHandleU(stream); ColorI printColors0 = ColorI(0,0,0,0); ColorI printColors1 = ColorI(0,0,0,0); ColorI printColors2 = ColorI(0,0,0,0); ColorI printColors3 = ColorI(0,0,0,0); if (stream->readFlag()) stream->readColor4I(&printColors0); if (stream->readFlag()) stream->readColor4I(&printColors1); if (stream->readFlag()) stream->readColor4I(&printColors2); if (stream->readFlag()) stream->readColor4I(&printColors3); if ((skinName!=mSkinModifiers2[ss].skinName) || (maskName!=mSkinModifiers2[ss].maskName) || (skinLocation!=mSkinModifiers2[ss].skinLocation) || (printColors0!=mSkinModifiers2[ss].printColors[0]) || (printColors1!=mSkinModifiers2[ss].printColors[1]) || (printColors2!=mSkinModifiers2[ss].printColors[2]) || (printColors3!=mSkinModifiers2[ss].printColors[3])) { mSkinModifiers2[ss].skinLocation=skinLocation; mSkinModifiers2[ss].skinName=skinName; mSkinModifiers2[ss].maskName=maskName; mSkinModifiers2[ss].printColors[0] = printColors0; mSkinModifiers2[ss].printColors[1] = printColors1; mSkinModifiers2[ss].printColors[2] = printColors2; mSkinModifiers2[ss].printColors[3] = printColors3; modifyFlag=true; if (mSkinModifiers2[ss].skinName.getString()) { mShapeInstance->uploadSkinModifier(ss, mSkinModifiers2[ss].skinLocation, mSkinModifiers2[ss].skinName, mSkinModifiers2[ss].maskName, mSkinModifiers2[ss].printColors[0], mSkinModifiers2[ss].printColors[1], mSkinModifiers2[ss].printColors[2], mSkinModifiers2[ss].printColors[3]); } else { mShapeInstance->removeSkinModifier(ss); } } } } if (modifyFlag && (mShapeInstance)) { mShapeInstance->modifySkins(); } }
(again, in case you go with using same mask as for setSkinName, the code above should go inside SkinMask handling)
bool ShapeBase::addSkinModifier( ... )
Now, right after the final closing braket of unpackUpdate add the rest:
//skinModifiers bool ShapeBase::addSkinModifier(U32 slot, const char *skinLocation, const char *skinName, const char *maskName, const ColorI& printColor1, const ColorI& printColor2, const ColorI& printColor3, const ColorI& printColor4 ) { // we can be called only on server! if (isGhost()) return false; if (slot<0 || slot>MaxSkinModifiers) return false; // we do not want to proceed if we don't have location, right? if (skinLocation[0] == '\0') return false; // tag the strings if needed StringHandle newSkinLocation; if (skinLocation[0] == StringTagPrefixByte) newSkinLocation = StringHandle(U32(dAtoi(skinLocation + 1))); else newSkinLocation = StringHandle(skinLocation); // are we setting the name (if empty - resetting to base) StringHandle newSkinName; if (skinName[0] != '\0') { if (skinLocation[0] == StringTagPrefixByte) newSkinName = StringHandle(U32(dAtoi(skinName + 1))); else newSkinName = StringHandle(skinName); } else newSkinName = StringHandle(); // what mask to use? StringHandle newMaskName; if (maskName[0] != '\0') { if (skinLocation[0] == StringTagPrefixByte) newMaskName = StringHandle(U32(dAtoi(maskName + 1))); else newMaskName = StringHandle(maskName); } else newMaskName = StringHandle(); // dont' forget about colors! ColorI newPrintColors0 = printColor1; ColorI newPrintColors1 = printColor2; ColorI newPrintColors2 = printColor3; ColorI newPrintColors3 = printColor4; //anything changed? if ((newSkinLocation != mSkinModifiers2[slot].skinLocation) || (newSkinName != mSkinModifiers2[slot].skinName) || (newMaskName != mSkinModifiers2[slot].maskName) || (newPrintColors0 != mSkinModifiers2[slot].printColors[0]) || (newPrintColors1 != mSkinModifiers2[slot].printColors[1]) || (newPrintColors2 != mSkinModifiers2[slot].printColors[2]) || (newPrintColors3 != mSkinModifiers2[slot].printColors[3])) { mSkinModifiers2[slot].skinLocation = newSkinLocation; mSkinModifiers2[slot].skinName = newSkinName; mSkinModifiers2[slot].maskName = newMaskName; mSkinModifiers2[slot].printColors[0] = newPrintColors0; mSkinModifiers2[slot].printColors[1] = newPrintColors1; mSkinModifiers2[slot].printColors[2] = newPrintColors2; mSkinModifiers2[slot].printColors[3] = newPrintColors3; // flags mSkinModMaskBits|=(1<<slot); } setMaskBits(SkinMaskMod); return true; }
bool ShapeBase::removeSkinModifier(U32 slot)
bool ShapeBase::removeSkinModifier(U32 slot) { // we can be called only on server! if (isGhost()) return false; if (slot<0 || slot>MaxSkinModifiers) return false; if ((mSkinModifiers2[slot].skinLocation.getString() != NULL) || (mSkinModifiers2[slot].skinName.getString() != NULL) || (mSkinModifiers2[slot].maskName.getString() != NULL)) { mSkinModifiers2[slot].skinLocation = StringHandle(); mSkinModifiers2[slot].skinName = StringHandle(); mSkinModifiers2[slot].maskName = StringHandle(); mSkinModifiers2[slot].printColors[0] = ColorI(0,0,0,0); mSkinModifiers2[slot].printColors[1] = ColorI(0,0,0,0); mSkinModifiers2[slot].printColors[2] = ColorI(0,0,0,0); mSkinModifiers2[slot].printColors[3] = ColorI(0,0,0,0); // set flags mSkinModMaskBits|=(1<<slot); } setMaskBits(ShapeBase::SkinMask); return true; }
void ShapeBase::clearSkinModifiers()
void ShapeBase::clearSkinModifiers() { // we can be called only on server! if (isGhost()) return; for (U32 ss=0; ss<MaxSkinModifiers; ss++) removeSkinModifier(ss); setMaskBits(SkinMaskMod); }
Console Methods
// Console methods ConsoleMethod(ShapeBase, addSkinModifier, bool, 6, 10, "(U32 slot, string skinLocation, string skinName, string maskName, ColorI color1 [,color2,3,4='']) Add a skin modifier.") { ColorI col1 = ColorI(0,0,0,0); ColorI col2 = ColorI(0,0,0,0); ColorI col3 = ColorI(0,0,0,0); ColorI col4 = ColorI(0,0,0,0); U32 r, g, b, a; char buf[64]; if (argc>6) { dStrcpy( buf, argv[6] ); char* temp = dStrtok( buf, " \0" ); r = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); g = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); b = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); a = temp ? dAtoi( temp ) : 0; col1.set( r, g, b, a ); } //dSscanf(argv[6], "%d %d %d %d", &col1.red, &col1.green, &col1.blue, &col1.alpha); if (argc>7) { dStrcpy( buf, argv[7] ); char* temp = dStrtok( buf, " \0" ); r = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); g = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); b = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); a = temp ? dAtoi( temp ) : 0; col2.set( r, g, b, a ); } //dSscanf(argv[7], "%d %d %d %d", &col2.red, &col2.green, &col2.blue, &col2.alpha); if (argc>8) { dStrcpy( buf, argv[8] ); char* temp = dStrtok( buf, " \0" ); r = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); g = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); b = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); a = temp ? dAtoi( temp ) : 0; col3.set( r, g, b, a ); } //dSscanf(argv[8], "%d %d %d %d", &col3.red, &col3.green, &col3.blue, &col3.alpha); if (argc>9) { dStrcpy( buf, argv[9] ); char* temp = dStrtok( buf, " \0" ); r = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); g = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); b = temp ? dAtoi( temp ) : 0; temp = dStrtok( NULL, " \0" ); a = temp ? dAtoi( temp ) : 0; col4.set( r, g, b, a ); } //dSscanf(argv[9], "%d %d %d %d", &col4.red, &col4.green, &col4.blue, &col4.alpha); // I'm using this way of getting color values, as if I use dSscanf() // then after object->setCustomSkin() call I get "stack around col1 corrupted" error return object->addSkinModifier(dAtoi(argv[2]),argv[3],argv[4],argv[5],col1,col2,col3,col4); } ConsoleMethod(ShapeBase, removeSkinModifier, bool, 3, 3, "(U32 slot) Remove a specific skin modifier.") { return object->removeSkinModifier(dAtoi(argv[2])); } ConsoleMethod(ShapeBase, clearSkinModifiers, void, 2, 2, "() Clear all skin modifiers.") { object->clearSkinModifiers(); }
ts/tsShapeInstance.h
In public section of TSShapeInstance definition, right before // Misc. comment (line around 236), add:
/// @name skinModifiers /// keep this to be the same with the value that in shapeBase.h file /// @{ enum Constants { MaxSkinModifiers = 30 }; /// @}
Further down, in protected section, right before public: starts, add:
/// @name skinModifiers /// This is different from shapeBase's one, as we have to perform different tasks /// @{ struct SkinMod2 { StringHandle skinName; ///< the handle of skin to use const char * skinPath; ///< the path to the skin long matIndex; ///< assiciated material index char modTag[32]; ///< unique tag (+[skinHash+maskHash+colors]\0 GBitmap *modBmp; ///< the modifier bitmap that we apply to the skin SkinMod2() {modBmp=NULL; matIndex=-1;}; ~SkinMod2() {if (modBmp) delete modBmp;}; }; SkinMod2 mSkinModifiers2[MaxSkinModifiers]; TextureHandle modifySkin(const char *baseSkinName, const char *skinName, U32 matIndex); ///@}
Few lines down, after void reSkin(StringHandle& newBaseHandle); paste:
/// @name skinModifiers /// @{ void uploadSkinModifier(U32 slot, StringHandle& skinLocation, StringHandle& skinName, StringHandle& maskName, const ColorI& printColor1, const ColorI& printColor2, const ColorI& printColor3, const ColorI& printColor4 ); ///< set/update skin modifier void removeSkinModifier(U32 slot); ///< clear unneeded skin modifier void clearSkinModifiers(); ///< clear everything long whichMaterial(const char *skinLoc); ///< get the index of speficied material void modifySkins(); ///< force applying the modifiers on skins /// @}
ts/tsShapeInstance.cc
Here we add the functions declared in the header, plus we have to modify some of the other member functions, primarily reskin().
TSShapeInstance::~TSShapeInstance()
In TSShapeInstance::~TSShapeInstance(), we have to call clearSkinModifiers(). I placed it before the call to setMaterialList() just to be safe. So, (~line 110) add:
//skinModidierfs clearSkinModifiers();
TSShapeInstance::reSkin(StringHandle& newBaseHandle)
In TSShapeInstance::reSkin(StringHandle& newBaseHandle), we need to check for new skin and change how the skin textures are acquired for the material list. If a skin modifier has been applied to the base skin, the new skin should get the same modifiers.
At the beginning of reSkin change:
#define NAME_BUFFER_LENGTH 256 static char pathName[NAME_BUFFER_LENGTH]; const char* defaultBaseName = "base"; const char* newBaseName;
to
#define NAME_BUFFER_LENGTH 256 static char pathName[NAME_BUFFER_LENGTH]; const char* defaultBaseName = "base"; const char* newBaseName; const char* saveBaseName; //skinModifiers
/* Hey Whistle Whistle Guys. I think this already present. shouldn't newBaseName actually be saveBaseName in this following chunk?? */
//skinModifiers if ( newBaseHandle.isValidString()) { newBaseName = newBaseHandle.getString(); // fail if no skin specified if (newBaseName == NULL) return; } else newBaseName = defaultBaseName;
Further down, after
// Bail if no name. if (pName == NULL) { continue; }
add:
//skinModifiers bool updated = false; for (S32 k=0; k < MaxSkinModifiers; k++) { if (j == mSkinModifiers2[k].matIndex) { if (!mSkinModifiers2[k].skinName.isNull()) { newBaseName = mSkinModifiers2[k].skinName.getString(); if ((dStrlen(newBaseName)==0) || (dStrlen(newBaseName)>32)) { newBaseName = defaultBaseName; } updated = true; } break; } } if (!updated) newBaseName = saveBaseName;
Then, change (~line 466):
pMatList->mMaterials[j] = TextureHandle(pathName, MeshTexture, false);
to this:
//skinModifiers //pMatList->mMaterials[j] = TextureHandle(pathName, MeshTexture, false); pMatList->mMaterials[j] = modifySkin(pName,pathName, j);
And change (~line 480):
makeSkinPath(pathName, NAME_BUFFER_LENGTH, resourcePath, pName, NULL, NULL); pMatList->mMaterials[j] = TextureHandle(pathName, MeshTexture, false);
to this:
makeSkinPath(pathName, NAME_BUFFER_LENGTH, resourcePath, pName, NULL, NULL); //skinModifiers //pMatList->mMaterials[j] = TextureHandle(pathName, MeshTexture, false); pMatList->mMaterials[j] = modifySkin(pName,pathName, j);
void TSShapeInstance::uploadSkinModifier(....)
Right after reSkin function, add the rest:
void TSShapeInstance::uploadSkinModifier(U32 slot, StringHandle& skinLocation, StringHandle& skinName, StringHandle& maskName, const ColorI& printColor1, const ColorI& printColor2, const ColorI& printColor3, const ColorI& printColor4 ) { if (skinLocation.isValidString() && skinName.isValidString()) { // We can't go if bad location or skin are given if ((skinLocation.getString()==NULL) || (skinName.getString()==NULL)) { return; } } else return; // fail if we are trying to set non-existing material long index = whichMaterial( skinLocation.getString() ); if (index < 0) { Con::errorf("Non existant material ( %s )!!!", skinLocation.getString(), index); return; } // Pardon me for this mess. It needs optimizing! // Here we assign colors per channels. As it's possible to call like: // .(skinLoc, skin, mask, "", "", ColorI, ColorI) // to use only blue and alpha channels, we need to handle it // we go through all four colors and checking if it's set. U8 cnt = 0; U8 chn1, chn2, chn3, chn4; ColorI clr1, clr2, clr3, clr4; if (printColor1.alpha!=0) { chn1 = 0; clr1 = printColor1; cnt++; } if (printColor2.alpha!=0) { if (cnt==0) { chn1 = 1; clr1 = printColor2; } else { chn2 = 1; clr2 = printColor2; } cnt++; } if (printColor3.alpha!=0) { if (cnt==0) { chn1 = 2; clr1 = printColor3; } else if (cnt==1) { chn2 = 2; clr2 = printColor3; } else { chn3 = 2; clr3 = printColor3; } cnt++; } if (printColor4.alpha!=0) { if (cnt==0) { chn1 = 3; clr1 = printColor4; } else if (cnt==1) { chn2 = 3; clr2 = printColor4; } else if (cnt==2) { chn3 = 3; clr3 = printColor4; } else { chn4 = 3; clr4 = printColor4; } cnt++; } // construct modifier bitmap // Make our own copy of the materials list from the resource // if necessary. if (ownMaterialList() == false) { cloneMaterialList(); } #define NAME_BUFFER_LENGTH 256 static char skinPath[NAME_BUFFER_LENGTH]; static char maskPath[NAME_BUFFER_LENGTH]; const char* defaultBaseName = "base"; const char* resourcePath = hShape.getFilePath(); // get the material TSMaterialList* pMatList = getMaterialList(); const char* pName = pMatList->mMaterialNames[index]; // Bail if no name. if (pName == NULL) { return; } // This one is a bit hacky. I'm not good with char**-related stuff, so all of this // is need only to get rid off of the file extention if present. // This is needed as you can have different extensions on base image and skin (e.g. base.smth.jpg & mask.smth.png) static char newM[NAME_BUFFER_LENGTH]; const char* extArray[4] = { "jpg", "png", "gif", "bmp" }; char * ext; Con::errorf("preparing: %s %s", pName, maskName.getString()); for( U32 i = 0; i < 4; i++ ) { ext = dStrstr(pName, extArray[i]); if (ext != NULL) { U32 len = dStrlen(pName); dStrcpy(newM, pName); newM[len-4] = '\0'; break; } } if (ext == NULL) { U32 len = dStrlen(pName); dStrcpy(newM, pName); newM[len] = '\0'; } // Make a texture file pathname with the new root if this name // has the old root in it; otherwise just make a path with the // original name. makeSkinPath(skinPath, NAME_BUFFER_LENGTH, resourcePath, pName, defaultBaseName, skinName.getString()); // If no mask specified we are only changing the skin and getting outta here without anything else if (!maskName.isValidString()) { removeSkinModifier(slot); mSkinModifiers2[slot].matIndex = index; mSkinModifiers2[slot].skinName = skinName; mSkinModifiers2[slot].skinPath = skinPath; dSprintf(mSkinModifiers2[slot].modTag,32,"+[%d+0]",_StringTable::hashString(skinName.getString())); return; } makeSkinPath(maskPath, NAME_BUFFER_LENGTH, resourcePath, newM, defaultBaseName, maskName.getString()); GBitmap *maskBmp=TextureManager::loadBitmapInstance(maskPath); // fail if no mask found if (!maskBmp) { Con::errorf("Can't open mask! %s %s", newM, maskName.getString()); return; } // This one if our bitmap where most "magic" happens GBitmap *printBmp=new GBitmap(maskBmp->getWidth(),maskBmp->getHeight(),false,GBitmap::RGBA); // Make it "black" transparent first printBmp->fill(ColorI(0,0,0,0)); // Fill & Blend if needed if (cnt==1) printBmp->fillByChannels(*maskBmp, chn1, clr1); else if (cnt==2) printBmp->fillByChannels(*maskBmp, chn1, chn2, clr1, clr2); else if (cnt==3) printBmp->fillByChannels(*maskBmp, chn1, chn2, chn3, clr1, clr2, clr3); else if (cnt==4) printBmp->fillByChannels(*maskBmp, chn1, chn2, chn3, chn4, clr1, clr2, clr3, clr4); else Con::errorf("No colors assigned! Will use transparent picture on filling! (why calling with mask and no colors?)"); // creating modifier removeSkinModifier(slot); mSkinModifiers2[slot].matIndex = index; mSkinModifiers2[slot].skinName = skinName; mSkinModifiers2[slot].skinPath = skinPath; mSkinModifiers2[slot].modBmp=printBmp; dSprintf(mSkinModifiers2[slot].modTag,32,"+[%d+%d+%d+%d+%d+%d]",_StringTable::hashString(skinName.getString()), _StringTable::hashString(maskName.getString()), clr1.alpha, clr2.alpha, clr3.alpha, clr4.alpha); if (maskBmp) delete maskBmp; }
void TSShapeInstance::removeSkinModifier(U32 slot)
void TSShapeInstance::removeSkinModifier(U32 slot) { mSkinModifiers2[slot].skinName=StringHandle(); mSkinModifiers2[slot].matIndex=-1; mSkinModifiers2[slot].modTag[0]='\0'; if (mSkinModifiers2[slot].modBmp) { GBitmap *tmpBmp=mSkinModifiers2[slot].modBmp; mSkinModifiers2[slot].modBmp=NULL; delete tmpBmp; } }
void TSShapeInstance::clearSkinModifiers()
void TSShapeInstance::clearSkinModifiers() { for (U32 ss=0; ss<MaxSkinModifiers; ss++) { removeSkinModifier(ss); } }
TextureHandle TSShapeInstance::modifySkin( ... )
TextureHandle TSShapeInstance::modifySkin(const char *baseSkinName, const char *skinName, U32 matIndex) { char modTextureName[512]; GBitmap *modTextureBmp=NULL; // check all skin modifier slots for (U32 ss=0; ss<MaxSkinModifiers; ss++) { if (mSkinModifiers2[ss].matIndex == -1) continue; if (mSkinModifiers2[ss].matIndex == matIndex && (mSkinModifiers2[ss].modBmp)) { if (!modTextureBmp) { modTextureBmp=TextureManager::loadBitmapInstance(skinName); if (!modTextureBmp) break; Con::errorf("making skin: %s", skinName); dStrcpy(modTextureName,skinName); } modTextureBmp->alphaBlend(*mSkinModifiers2[ss].modBmp); dStrcat(modTextureName,mSkinModifiers2[ss].modTag); } } if (!modTextureBmp) return TextureHandle(skinName,MeshTexture,false); else { Con::errorf("returning Texture handle - %s", modTextureName); return TextureHandle(modTextureName,modTextureBmp,MeshTexture,false); } }
void TSShapeInstance::modifySkins()
void TSShapeInstance::modifySkins() { char skinName[512]; // Make our own copy of the materials list from the resource // if necessary. if (!ownMaterialList()) { cloneMaterialList(); } TSMaterialList* pMatList = getMaterialList(); for (U32 mm=0; mm<pMatList->mMaterialNames.size(); mm++) { // Get the name of this material. const char* baseSkinName=pMatList->mMaterialNames[mm]; // a different skin could be in use, so use that name instead const char* materialName=pMatList->mMaterials[mm].getName(); if(materialName == NULL) { Con::errorf("DEBUG: ModifySkins: No existing skin in use!"); continue; } dStrcpy(skinName,materialName); char *modTag=dStrstr((const char*)skinName,"+["); if (modTag) { skinName[modTag-skinName]='\0'; } // modify skin pMatList->mMaterials[mm]=modifySkin(baseSkinName,skinName, mm); } }
long TSShapeInstance::whichMaterial(const char *skinLoc)
long TSShapeInstance::whichMaterial(const char *skinLoc) { //Con::errorf("TSShapeInstance::whichMaterial(%s)",skinLoc); char *period, *specificName; // Make our own copy of the materials list from the resource if necessary. setMaterialList(mShape->materialList); if (ownMaterialList() == false) cloneMaterialList(); // This one is also a bit hacky. Please see if it's possible to improve //Con::errorf("whichMaterial(%s)",skinLoc); // Cycle through the materials. TSMaterialList* pMatList = getMaterialList(); for (S32 j = 0; j < pMatList->mMaterialNames.size(); j++) { // Get the name of this material. const char* pName = pMatList->mMaterialNames[j]; //Con::warnf("index=%d, name=%s",j,pName); // Bail if no name. if (pName == NULL) continue; // find the second part of the material name period = dStrchr((char *) pName, '.'); if (period == NULL) continue; char *begin = dStrchr((char *) pName, '.') + 1; char *end = begin; char *t; while((t = dStrchr(end, '.'))) end = t + 1; const char* extArray[4] = { "jpg", "png", "gif", "bmp" }; for( U32 i = 0; i < 4; i++ ) { if (dStricmp(end, extArray[i]) == 0) { char buf[128]; S32 i; for(i = 0; begin < end - 1; ++i, ++begin) buf[i] = *begin; if (buf == NULL) continue; buf[i] = '\0'; begin = buf; if (dStricmp(buf, skinLoc) == 0) return(j); break; } } if (dStricmp(end, skinLoc)== 0) return(j); } return(-1); }
Art
base.skinname.jpg
You need to use "base.texname" naming on your ShapeBase objects to be able to use this resource.
Ask your artist(s) to change the texture names. E.g. if you have myplayer.jpg or someobject.png - rename textures to base.myplayer.jpg (base.someobject.png) - so the Torque will be aware, that the skinning can be used on this objects.
Then in a stock Torque you can use %obj.setSkinName("red"); and then it will search for red.someobject.png.
In case you don't have sources of objects you still can "hack" it - if the texture name (without extension) is more than 5 characters you can use any HEX editor to open the file in HEX mode, browse down to the bottom and you will see there something like:
................ ..someobjectC... ................
The texture name can be broken into two lines. change the someobject to base.smobj leaving the trailing "C" char at the place (sometimes there can be G character - depends on exporter used). Then you rename your someobject.jpg file to base.smobj.jpg and all should work. More info/discussion can be found here.
The main point here, is to make base. to be in name of texture. If your texture name is less or equal 5 chars - it's not possible to use this "rename hack". Assuming you have boxer.png - you have space only to make it "base." and no space left. The player is fine - base.p is still good name for a texture.
Some exporters (IIRC - it's Maya's) are saving extension of texture into DTS file. So, if you see inside DTS (with HEX editor) boxer.png - you can rename it taking off the extension: From:
................ ..boxer.pngG.... ................
You can make:
................ ..base.boxrG.... ................
And rename your texture file from "boxer.png" to "base.boxr.png" - and you are ready.
mask
The mask bitmap should be either the RGB or RGBA. If you go with JPEG (or BMP) you can use up to three colors per image. If you use PNG, you have additional channel (alpha) to use.
RGBA Channels
The artists, when creating a bitmap need to make special images to use with this resource. The color level in "mask" image (from 0 upto 255) is used as opacity level for blending with specified color.
As you can see - red channel covers all "skin" while green adds some "marks" on it (in combination it looks like yellow).
Using colors
Let's make it a bit "death-looking" and add some "red stuff" in there.
%obj.addSkinModifier(0, "p", "base", "mask", "255 255 255 255", "255 0 0 255");
And now, skin - red, and some white paintings on it:
%obj.addSkinModifier(0, "p", "base", "mask", "0 0 255 255", "255 255 255 255");
The only "problem" now - the currently implemented "blending" procedure in GBitmap::fillByChannels() is only adding colors, so if you paint your skin with white, you can't add black "scars" on it.
If someone can make a good blending (that work for many solutions) - please share.
Or, it's possible to extend this resource, you can even specify what kind of blending to use (add/multiply/etc).
Categories: TGE | DTS