Skip to content

Commit

Permalink
Hardware coloring for itemstacks
Browse files Browse the repository at this point in the history
Adds the possibility to colorize item stacks based on their metadata.

In the item/node definition you can specify palette (an image file)
and color (fallback color if the item has no palette or metadata).
Then you can add palette_index to the metadata.

Dropped itemstacks with different colors do not merge.
  • Loading branch information
juhdanad authored and sofar committed Apr 9, 2017
1 parent d4e9dd4 commit 58d83a7
Show file tree
Hide file tree
Showing 23 changed files with 308 additions and 139 deletions.
31 changes: 22 additions & 9 deletions builtin/game/item_entity.lua
Expand Up @@ -53,6 +53,8 @@ core.register_entity(":__builtin:item", {
if itemtable then
itemname = stack:to_table().name
end
-- Backwards compatibility: old clients use the texture
-- to get the type of the item
local item_texture = nil
local item_type = ""
if core.registered_items[itemname] then
Expand All @@ -66,6 +68,7 @@ core.register_entity(":__builtin:item", {
visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c},
automatic_rotate = math.pi * 0.5,
wield_item = itemstring,
}
self.object:set_properties(prop)
end,
Expand Down Expand Up @@ -101,50 +104,60 @@ core.register_entity(":__builtin:item", {
self:set_item(self.itemstring)
end,

-- moves items from this stack to an other stack
try_merge_with = function(self, own_stack, object, obj)
-- other item's stack
local stack = ItemStack(obj.itemstring)
if own_stack:get_name() == stack:get_name() and stack:get_free_space() > 0 then
-- only merge if items are the same
if own_stack:get_name() == stack:get_name() and
own_stack:get_meta() == stack:get_meta() and
own_stack:get_wear() == stack:get_wear() and
stack:get_free_space() > 0 then
local overflow = false
local count = stack:get_count() + own_stack:get_count()
local max_count = stack:get_stack_max()
if count > max_count then
overflow = true
stack:set_count(max_count)
count = count - max_count
own_stack:set_count(count)
else
self.itemstring = ''
stack:set_count(count)
end
local pos = object:getpos()
pos.y = pos.y + (count - stack:get_count()) / max_count * 0.15
object:moveto(pos, false)
local s, c
local max_count = stack:get_stack_max()
local name = stack:get_name()
if not overflow then
obj.itemstring = name .. " " .. count
obj.itemstring = stack:to_string()
s = 0.2 + 0.1 * (count / max_count)
c = s
object:set_properties({
visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c}
collisionbox = {-c, -c, -c, c, c, c},
wield_item = obj.itemstring
})
self.object:remove()
-- merging succeeded
return true
else
s = 0.4
c = 0.3
obj.itemstring = stack:to_string()
object:set_properties({
visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c}
collisionbox = {-c, -c, -c, c, c, c},
wield_item = obj.itemstring
})
obj.itemstring = name .. " " .. max_count
s = 0.2 + 0.1 * (count / max_count)
c = s
self.itemstring = own_stack:to_string()
self.object:set_properties({
visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c}
collisionbox = {-c, -c, -c, c, c, c},
wield_item = self.itemstring
})
self.itemstring = name .. " " .. count
end
end
-- merging didn't succeed
Expand Down
18 changes: 18 additions & 0 deletions doc/lua_api.txt
Expand Up @@ -1480,6 +1480,9 @@ Item metadata only contains a key-value store.
Some of the values in the key-value store are handled specially:

* `description`: Set the itemstack's description. Defaults to idef.description
* `color`: A `ColorString`, which sets the stack's color.
* `palette_index`: If the item has a palette, this is used to get the
current color from the palette.

Example stuff:

Expand Down Expand Up @@ -2855,6 +2858,8 @@ See `StorageRef`, `NodeMetaRef` and `ItemStackMetaRef`.
* Any non-table value will clear the metadata
* See "Node Metadata" for an example
* returns `true` on success
* `equals(other)`
* returns `true` if this metadata has the same key-value pairs as `other`

### `NodeMetaRef`
Node metadata: reference extra data and functionality stored in a node.
Expand Down Expand Up @@ -3735,6 +3740,19 @@ Definition tables
{hard = 1, metal = 1, spikes = 1}
inventory_image = "default_tool_steelaxe.png",
wield_image = "",
palette = "",
--[[
^ An image file containing the palette of a node.
^ You can set the currently used color as the
^ "palette_index" field of the item stack metadata.
^ The palette is always stretched to fit indices
^ between 0 and 255, to ensure compatibility with
^ "colorfacedir" and "colorwallmounted" nodes.
]]
color = "0xFFFFFFFF",
--[[
^ The color of the item. The palette overrides this.
]]
wield_scale = {x = 1, y = 1, z = 1},
stack_max = 99,
range = 4.0,
Expand Down
3 changes: 2 additions & 1 deletion src/camera.cpp
Expand Up @@ -501,7 +501,8 @@ void Camera::setDigging(s32 button)

void Camera::wield(const ItemStack &item)
{
if (item.name != m_wield_item_next.name) {
if (item.name != m_wield_item_next.name ||
item.metadata != m_wield_item_next.metadata) {
m_wield_item_next = item;
if (m_wield_change_timer > 0)
m_wield_change_timer = -m_wield_change_timer;
Expand Down
69 changes: 67 additions & 2 deletions src/client/tile.cpp
Expand Up @@ -341,6 +341,8 @@ class TextureSource : public IWritableTextureSource
*/
video::ITexture* getTextureForMesh(const std::string &name, u32 *id);

virtual Palette* getPalette(const std::string &name);

// Returns a pointer to the irrlicht device
virtual IrrlichtDevice* getDevice()
{
Expand Down Expand Up @@ -377,8 +379,6 @@ class TextureSource : public IWritableTextureSource
video::ITexture* generateTextureFromMesh(
const TextureFromMeshParams &params);

video::IImage* generateImage(const std::string &name);

video::ITexture* getNormalTexture(const std::string &name);
video::SColor getTextureAverageColor(const std::string &name);
video::ITexture *getShaderFlagsTexture(bool normamap_present);
Expand All @@ -401,6 +401,13 @@ class TextureSource : public IWritableTextureSource
// if baseimg is NULL, it is created. Otherwise stuff is made on it.
bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);

/*! Generates an image from a full string like
* "stone.png^mineral_coal.png^[crack:1:0".
* Shall be called from the main thread.
* The returned Image should be dropped.
*/
video::IImage* generateImage(const std::string &name);

// Thread-safe cache of what source images are known (true = known)
MutexedMap<std::string, bool> m_source_image_existence;

Expand All @@ -419,6 +426,9 @@ class TextureSource : public IWritableTextureSource
// but can't be deleted because the ITexture* might still be used
std::vector<video::ITexture*> m_texture_trash;

// Maps image file names to loaded palettes.
UNORDERED_MAP<std::string, Palette> m_palettes;

// Cached settings needed for making textures from meshes
bool m_setting_trilinear_filter;
bool m_setting_bilinear_filter;
Expand Down Expand Up @@ -682,6 +692,61 @@ video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *
return getTexture(name + "^[applyfiltersformesh", id);
}

Palette* TextureSource::getPalette(const std::string &name)
{
// Only the main thread may load images
sanity_check(thr_is_current_thread(m_main_thread));

if (name == "")
return NULL;

UNORDERED_MAP<std::string, Palette>::iterator it = m_palettes.find(name);
if (it == m_palettes.end()) {
// Create palette
video::IImage *img = generateImage(name);
if (!img) {
warningstream << "TextureSource::getPalette(): palette \"" << name
<< "\" could not be loaded." << std::endl;
return NULL;
}
Palette new_palette;
u32 w = img->getDimension().Width;
u32 h = img->getDimension().Height;
// Real area of the image
u32 area = h * w;
if (area == 0)
return NULL;
if (area > 256) {
warningstream << "TextureSource::getPalette(): the specified"
<< " palette image \"" << name << "\" is larger than 256"
<< " pixels, using the first 256." << std::endl;
area = 256;
} else if (256 % area != 0)
warningstream << "TextureSource::getPalette(): the "
<< "specified palette image \"" << name << "\" does not "
<< "contain power of two pixels." << std::endl;
// We stretch the palette so it will fit 256 values
// This many param2 values will have the same color
u32 step = 256 / area;
// For each pixel in the image
for (u32 i = 0; i < area; i++) {
video::SColor c = img->getPixel(i % w, i / w);
// Fill in palette with 'step' colors
for (u32 j = 0; j < step; j++)
new_palette.push_back(c);
}
img->drop();
// Fill in remaining elements
while (new_palette.size() < 256)
new_palette.push_back(video::SColor(0xFFFFFFFF));
m_palettes[name] = new_palette;
it = m_palettes.find(name);
}
if (it != m_palettes.end())
return &((*it).second);
return NULL;
}

void TextureSource::processQueue()
{
/*
Expand Down
15 changes: 9 additions & 6 deletions src/client/tile.h
Expand Up @@ -33,6 +33,8 @@ class IGameDef;
struct TileSpec;
struct TileDef;

typedef std::vector<video::SColor> Palette;

/*
tile.{h,cpp}: Texture handling stuff.
*/
Expand Down Expand Up @@ -106,14 +108,15 @@ class ITextureSource : public ISimpleTextureSource
const std::string &name, u32 *id = NULL)=0;
virtual video::ITexture* getTextureForMesh(
const std::string &name, u32 *id = NULL) = 0;
/*!
* Returns a palette from the given texture name.
* The pointer is valid until the texture source is
* destructed.
* Should be called from the main thread.
*/
virtual Palette* getPalette(const std::string &name) = 0;
virtual IrrlichtDevice* getDevice()=0;
virtual bool isKnownSourceImage(const std::string &name)=0;
/*! Generates an image from a full string like
* "stone.png^mineral_coal.png^[crack:1:0".
* Shall be called from the main thread.
* The returned Image should be dropped.
*/
virtual video::IImage* generateImage(const std::string &name)=0;
virtual video::ITexture* generateTextureFromMesh(
const TextureFromMeshParams &params)=0;
virtual video::ITexture* getNormalTexture(const std::string &name)=0;
Expand Down
39 changes: 23 additions & 16 deletions src/content_cao.cpp
Expand Up @@ -933,23 +933,30 @@ void GenericCAO::addToScene(scene::ISceneManager *smgr,
errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
}
else if(m_prop.visual == "wielditem") {
infostream<<"GenericCAO::addToScene(): wielditem"<<std::endl;
infostream<<"textures: "<<m_prop.textures.size()<<std::endl;
if(m_prop.textures.size() >= 1){
infostream<<"textures[0]: "<<m_prop.textures[0]<<std::endl;
IItemDefManager *idef = m_client->idef();
ItemStack item(m_prop.textures[0], 1, 0, idef);

m_wield_meshnode = new WieldMeshSceneNode(
smgr->getRootSceneNode(), smgr, -1);
m_wield_meshnode->setItem(item, m_client);

m_wield_meshnode->setScale(v3f(m_prop.visual_size.X/2,
m_prop.visual_size.Y/2,
m_prop.visual_size.X/2));
u8 li = m_last_light;
m_wield_meshnode->setColor(video::SColor(255,li,li,li));
ItemStack item;
infostream << "GenericCAO::addToScene(): wielditem" << std::endl;
if (m_prop.wield_item == "") {
// Old format, only textures are specified.
infostream << "textures: " << m_prop.textures.size() << std::endl;
if (m_prop.textures.size() >= 1) {
infostream << "textures[0]: " << m_prop.textures[0]
<< std::endl;
IItemDefManager *idef = m_client->idef();
item = ItemStack(m_prop.textures[0], 1, 0, idef);
}
} else {
infostream << "serialized form: " << m_prop.wield_item << std::endl;
item.deSerialize(m_prop.wield_item, m_client->idef());
}
m_wield_meshnode = new WieldMeshSceneNode(smgr->getRootSceneNode(),
smgr, -1);
m_wield_meshnode->setItem(item, m_client);

m_wield_meshnode->setScale(
v3f(m_prop.visual_size.X / 2, m_prop.visual_size.Y / 2,
m_prop.visual_size.X / 2));
u8 li = m_last_light;
m_wield_meshnode->setColor(video::SColor(255, li, li, li));
} else {
infostream<<"GenericCAO::addToScene(): \""<<m_prop.visual
<<"\" not supported"<<std::endl;
Expand Down
20 changes: 17 additions & 3 deletions src/hud.cpp
Expand Up @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "fontengine.h"
#include "guiscalingfilter.h"
#include "mesh.h"
#include "wieldmesh.h"
#include <IGUIStaticText.h>

#ifdef HAVE_TOUCHSCREENGUI
Expand Down Expand Up @@ -642,9 +643,10 @@ void drawItemStack(video::IVideoDriver *driver,
}

const ItemDefinition &def = item.getDefinition(client->idef());
scene::IMesh* mesh = client->idef()->getWieldMesh(def.name, client);
ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client);

if (mesh) {
if (imesh && imesh->mesh) {
scene::IMesh *mesh = imesh->mesh;
driver->clearZBuffer();
s32 delta = 0;
if (rotation_kind < IT_ROT_NONE) {
Expand All @@ -667,16 +669,28 @@ void drawItemStack(video::IVideoDriver *driver,
matrix.makeIdentity();

if (enable_animations) {
float timer_f = (float)delta / 5000.0;
float timer_f = (float) delta / 5000.0;
matrix.setRotationDegrees(core::vector3df(0, 360 * timer_f, 0));
}

driver->setTransform(video::ETS_WORLD, matrix);
driver->setViewPort(rect);

video::SColor basecolor =
client->idef()->getItemstackColor(item, client);

u32 mc = mesh->getMeshBufferCount();
for (u32 j = 0; j < mc; ++j) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
// we can modify vertices relatively fast,
// because these meshes are not buffered.
assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER);
video::SColor c = basecolor;
if (imesh->buffer_colors.size() > j) {
std::pair<bool, video::SColor> p = imesh->buffer_colors[j];
c = p.first ? p.second : basecolor;
}
colorizeMeshBuffer(buf, &c);
video::SMaterial &material = buf->getMaterial();
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
material.Lighting = false;
Expand Down

0 comments on commit 58d83a7

Please sign in to comment.