Add hardware node coloring. Includes:
authorDániel Juhász <juhdanad@gmail.com>
Thu, 12 Jan 2017 14:46:30 +0000 (15:46 +0100)
committerEkdohibs <nathanael.courant@laposte.net>
Mon, 23 Jan 2017 06:27:12 +0000 (07:27 +0100)
- Increase ContentFeatures serialization version
- Color property and palettes for nodes
- paramtype2 = "color", "colored facedir" or "colored wallmounted"

27 files changed:
client/shaders/nodes_shader/opengl_vertex.glsl
client/shaders/water_surface_shader/opengl_vertex.glsl
client/shaders/wielded_shader/opengl_vertex.glsl
doc/lua_api.txt
src/client/tile.cpp
src/client/tile.h
src/clientenvironment.cpp
src/content_mapblock.cpp
src/game.cpp
src/mapblock_mesh.cpp
src/mapblock_mesh.h
src/mapnode.cpp
src/mapnode.h
src/mesh.cpp
src/mesh.h
src/minimap.cpp
src/minimap.h
src/network/networkprotocol.h
src/nodedef.cpp
src/nodedef.h
src/particles.cpp
src/particles.h
src/script/common/c_content.cpp
src/script/cpp_api/s_node.cpp
src/shader.cpp
src/wieldmesh.cpp
src/wieldmesh.h

index 44c48cc4ce139204c0397c4e7cb080fb45e9c794..3ac79c26db8c2898762747ed6535686ee771f5c2 100644 (file)
@@ -1,7 +1,8 @@
 uniform mat4 mWorldViewProj;
 uniform mat4 mWorld;
 
-uniform float dayNightRatio;
+// Color of the light emitted by the sun.
+uniform vec3 dayLight;
 uniform vec3 eyePosition;
 uniform float animationTimer;
 
@@ -14,6 +15,8 @@ varying vec3 tsEyeVec;
 varying vec3 tsLightVec;
 varying float area_enable_parallax;
 
+// Color of the light emitted by the light sources.
+const vec3 artificialLight = vec3(1.04, 1.04, 1.04);
 const float e = 2.718281828459;
 const float BS = 10.0;
 
@@ -119,31 +122,23 @@ float disp_z;
        v.z = dot(eyeVec, normal);
        tsEyeVec = normalize (v);
 
+       // Calculate color.
+       // Red, green and blue components are pre-multiplied with
+       // the brightness, so now we have to multiply these
+       // colors with the color of the incoming light.
+       // The pre-baked colors are halved to prevent overflow.
        vec4 color;
-       float day = gl_Color.r;
-       float night = gl_Color.g;
-       float light_source = gl_Color.b;
-
-       float rg = mix(night, day, dayNightRatio);
-       rg += light_source * 2.5; // Make light sources brighter
-       float b = rg;
-
-       // Moonlight is blue
-       b += (day - night) / 13.0;
-       rg -= (day - night) / 23.0;
-
+       // The alpha gives the ratio of sunlight in the incoming light.
+       float nightRatio = 1 - gl_Color.a;
+       color.rgb = gl_Color.rgb * (gl_Color.a * dayLight.rgb + 
+               nightRatio * artificialLight.rgb) * 2;
+       color.a = 1;
+       
        // Emphase blue a bit in darker places
        // See C++ implementation in mapblock_mesh.cpp finalColorBlend()
-       b += max(0.0, (1.0 - abs(b - 0.13) / 0.17) * 0.025);
-
-       // Artificial light is yellow-ish
-       // See C++ implementation in mapblock_mesh.cpp finalColorBlend()
-       rg += max(0.0, (1.0 - abs(rg - 0.85) / 0.15) * 0.065);
-
-       color.r = rg;
-       color.g = rg;
-       color.b = b;
-
-       color.a = gl_Color.a;
+       float brightness = (color.r + color.g + color.b) / 3;
+       color.b += max(0.0, 0.021 - abs(0.2 * brightness - 0.021) +
+               0.07 * brightness);
+       
        gl_FrontColor = gl_BackColor = clamp(color, 0.0, 1.0);
 }
index a930e7b8fcc7ca99e960ba5f57c41abc89c17fbd..112db9bb541c9c83e0aa6cf778c8a725319b7ad1 100644 (file)
@@ -1,7 +1,8 @@
 uniform mat4 mWorldViewProj;
 uniform mat4 mWorld;
 
-uniform float dayNightRatio;
+// Color of the light emitted by the sun.
+uniform vec3 dayLight;
 uniform vec3 eyePosition;
 uniform float animationTimer;
 
@@ -13,6 +14,8 @@ varying vec3 lightVec;
 varying vec3 tsEyeVec;
 varying vec3 tsLightVec;
 
+// Color of the light emitted by the light sources.
+const vec3 artificialLight = vec3(1.04, 1.04, 1.04);
 const float e = 2.718281828459;
 const float BS = 10.0;
 
@@ -112,31 +115,23 @@ void main(void)
        eyeVec = (gl_ModelViewMatrix * gl_Vertex).xyz;
        tsEyeVec = eyeVec * tbnMatrix;
 
+       // Calculate color.
+       // Red, green and blue components are pre-multiplied with
+       // the brightness, so now we have to multiply these
+       // colors with the color of the incoming light.
+       // The pre-baked colors are halved to prevent overflow.
        vec4 color;
-       float day = gl_Color.r;
-       float night = gl_Color.g;
-       float light_source = gl_Color.b;
-
-       float rg = mix(night, day, dayNightRatio);
-       rg += light_source * 2.5; // Make light sources brighter
-       float b = rg;
-
-       // Moonlight is blue
-       b += (day - night) / 13.0;
-       rg -= (day - night) / 23.0;
-
+       // The alpha gives the ratio of sunlight in the incoming light.
+       float nightRatio = 1 - gl_Color.a;
+       color.rgb = gl_Color.rgb * (gl_Color.a * dayLight.rgb + 
+               nightRatio * artificialLight.rgb) * 2;
+       color.a = 1;
+       
        // Emphase blue a bit in darker places
        // See C++ implementation in mapblock_mesh.cpp finalColorBlend()
-       b += max(0.0, (1.0 - abs(b - 0.13)/0.17) * 0.025);
-
-       // Artificial light is yellow-ish
-       // See C++ implementation in mapblock_mesh.cpp finalColorBlend()
-       rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065);
-
-       color.r = rg;
-       color.g = rg;
-       color.b = b;
-
-       color.a = gl_Color.a;
-       gl_FrontColor = gl_BackColor = clamp(color,0.0,1.0);
+       float brightness = (color.r + color.g + color.b) / 3;
+       color.b += max(0.0, 0.021 - abs(0.2 * brightness - 0.021) +
+               0.07 * brightness);
+       
+       gl_FrontColor = gl_BackColor = clamp(color, 0.0, 1.0);
 }
index 86c62689629db4c3d2d8b36db681891ca2093b9c..9f05b833a7eca4ef8f52da5e6a011f697e7a9a77 100644 (file)
@@ -1,7 +1,6 @@
 uniform mat4 mWorldViewProj;
 uniform mat4 mWorld;
 
-uniform float dayNightRatio;
 uniform vec3 eyePosition;
 uniform float animationTimer;
 
index e5a3362ee096afb224bf8c7bdc8aed9a8a75b9fa..2a0b72053b75f1ec4e81277544648a901241251f 100644 (file)
@@ -638,6 +638,19 @@ node definition:
         bit 4 (0x10) - Makes the plant mesh 1.4x larger
         bit 5 (0x20) - Moves each face randomly a small bit down (1/8 max)
         bits 6-7 are reserved for future use.
+    paramtype2 == "color"
+    ^ `param2` tells which color is picked from the palette.
+      The palette should have 256 pixels.
+    paramtype2 == "colorfacedir"
+    ^ Same as `facedir`, but with colors.
+      The first three bits of `param2` tells which color
+      is picked from the palette.
+      The palette should have 8 pixels.
+    paramtype2 == "colorwallmounted"
+    ^ Same as `wallmounted`, but with colors.
+      The first five bits of `param2` tells which color
+      is picked from the palette.
+      The palette should have 32 pixels.
     collision_box = {
       type = "fixed",
       fixed = {
@@ -3707,6 +3720,9 @@ Definition tables
          when displacement mapping is used
          Directions are from the point of view of the tile texture,
          not the node it's on
+* `{name="image.png", color=ColorSpec}`
+    * the texture's color will be multiplied with this color.
+    * the tile's color overrides the owning node's color in all cases.
 * deprecated, yet still supported field names:
     * `image` (name)
 
@@ -3749,8 +3765,17 @@ Definition tables
         special_tiles = {tile definition 1, Tile definition 2}, --[[
         ^ Special textures of node; used rarely (old field name: special_materials)
         ^ List can be shortened to needed length ]]
-        alpha = 255,
+        color = ColorSpec, --[[
+        ^ The node's original color will be multiplied with this color.
+        ^ If the node has a palette, then this setting only has an effect
+        ^ in the inventory and on the wield item. ]]
         use_texture_alpha = false, -- Use texture's alpha channel
+        palette = "palette.png", --[[
+        ^ The node's `param2` is used to select a pixel from the image
+        ^ (pixels are arranged from left to right and from top to bottom).
+        ^ The node's color will be multiplied with the selected pixel's
+        ^ color. Tiles can override this behavior.
+        ^ Only when `paramtype2` supports palettes. ]]
         post_effect_color = "green#0F", -- If player is inside node, see "ColorSpec"
         paramtype = "none", -- See "Nodes" --[[
         ^ paramtype = "light" allows light to propagate from or through the node with light value
index 4d2166342893fef5e70dc3ecfdd18dc94b74916c..539c29445e92fe081adc564898a87c3aeabb47e6 100644 (file)
@@ -378,9 +378,6 @@ public:
        video::ITexture* generateTextureFromMesh(
                        const TextureFromMeshParams &params);
 
-       // Generates an image from a full string like
-       // "stone.png^mineral_coal.png^[crack:1:0".
-       // Shall be called from the main thread.
        video::IImage* generateImage(const std::string &name);
 
        video::ITexture* getNormalTexture(const std::string &name);
index 45280480104943456d55e591e714e17d96979587..d04ab918a27cac08dc2563ded6e064da7ec41450 100644 (file)
@@ -108,6 +108,12 @@ public:
                        const std::string &name, u32 *id = NULL) = 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;
@@ -192,7 +198,6 @@ struct TileSpec
                texture(NULL),
                normal_texture(NULL),
                flags_texture(NULL),
-               alpha(255),
                material_type(TILE_MATERIAL_BASIC),
                material_flags(
                        //0 // <- DEBUG, Use the one below
@@ -201,22 +206,30 @@ struct TileSpec
                shader_id(0),
                animation_frame_count(1),
                animation_frame_length_ms(0),
-               rotation(0)
+               rotation(0),
+               has_color(false),
+               color(),
+               emissive_light(0)
        {
        }
 
+       /*!
+        * Two tiles are equal if they can be appended to
+        * the same mesh buffer.
+        */
        bool operator==(const TileSpec &other) const
        {
                return (
                        texture_id == other.texture_id &&
-                       /* texture == other.texture && */
-                       alpha == other.alpha &&
                        material_type == other.material_type &&
                        material_flags == other.material_flags &&
                        rotation == other.rotation
                );
        }
 
+       /*!
+        * Two tiles are not equal if they must be in different mesh buffers.
+        */
        bool operator!=(const TileSpec &other) const
        {
                return !(*this == other);
@@ -233,7 +246,7 @@ struct TileSpec
                        material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
                        break;
                case TILE_MATERIAL_LIQUID_TRANSPARENT:
-                       material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
+                       material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
                        break;
                case TILE_MATERIAL_LIQUID_OPAQUE:
                        material.MaterialType = video::EMT_SOLID;
@@ -274,8 +287,6 @@ struct TileSpec
        video::ITexture *normal_texture;
        video::ITexture *flags_texture;
        
-       // Vertex alpha (when MATERIAL_ALPHA_VERTEX is used)
-       u8 alpha;
        // Material parameters
        u8 material_type;
        u8 material_flags;
@@ -286,5 +297,14 @@ struct TileSpec
        std::vector<FrameSpec> frames;
 
        u8 rotation;
+       //! If true, the tile has its own color.
+       bool has_color;
+       /*!
+        * The color of the tile, or if the tile does not own
+        * a color then the color of the node owning this tile.
+        */
+       video::SColor color;
+       //! This much light does the tile emit.
+       u8 emissive_light;
 };
 #endif
index b32a02f2d3baa600103717974c61b70c396ccf89..77f53d214fb2bf2e78d1cc4889db99a61d7e43b4 100644 (file)
@@ -334,9 +334,7 @@ void ClientEnvironment::step(float dtime)
                node_at_lplayer = m_map->getNodeNoEx(p);
 
                u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef());
-               u8 day = light & 0xff;
-               u8 night = (light >> 8) & 0xff;
-               finalColorBlend(lplayer->light_color, day, night, day_night_ratio);
+               final_color_blend(&lplayer->light_color, light, day_night_ratio);
        }
 
        /*
index a7134590b0aa895dc172733c03c5e37f91860b8f..742bfb1fd5392bee3d7396a6f441f66bcabc14f5 100644 (file)
@@ -32,28 +32,29 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 
 // Create a cuboid.
-//  collector - the MeshCollector for the resulting polygons
-//  box       - the position and size of the box
-//  tiles     - the tiles (materials) to use (for all 6 faces)
-//  tilecount - number of entries in tiles, 1<=tilecount<=6
-//  c         - vertex colour - used for all
-//  txc       - texture coordinates - this is a list of texture coordinates
-//              for the opposite corners of each face - therefore, there
-//              should be (2+2)*6=24 values in the list. Alternatively, pass
-//              NULL to use the entire texture for each face. The order of
-//              the faces in the list is up-down-right-left-back-front
-//              (compatible with ContentFeatures). If you specified 0,0,1,1
-//              for each face, that would be the same as passing NULL.
+//  collector     - the MeshCollector for the resulting polygons
+//  box           - the position and size of the box
+//  tiles         - the tiles (materials) to use (for all 6 faces)
+//  tilecount     - number of entries in tiles, 1<=tilecount<=6
+//  c             - colors of the cuboid's six sides
+//  txc           - texture coordinates - this is a list of texture coordinates
+//                  for the opposite corners of each face - therefore, there
+//                  should be (2+2)*6=24 values in the list. Alternatively,
+//                  pass NULL to use the entire texture for each face. The
+//                  order of the faces in the list is up-down-right-left-back-
+//                  front (compatible with ContentFeatures). If you specified
+//                  0,0,1,1 for each face, that would be the same as
+//                  passing NULL.
+//  light source  - if greater than zero, the box's faces will not be shaded
 void makeCuboid(MeshCollector *collector, const aabb3f &box,
-       TileSpec *tiles, int tilecount, video::SColor &c, const f32* txc)
+       TileSpec *tiles, int tilecount, const video::SColor *c,
+       const f32* txc, const u8 light_source)
 {
        assert(tilecount >= 1 && tilecount <= 6); // pre-condition
 
        v3f min = box.MinEdge;
        v3f max = box.MaxEdge;
 
-
-
        if(txc == NULL) {
                static const f32 txc_default[24] = {
                        0,0,1,1,
@@ -66,38 +67,53 @@ void makeCuboid(MeshCollector *collector, const aabb3f &box,
                txc = txc_default;
        }
 
+       video::SColor c1 = c[0];
+       video::SColor c2 = c[1];
+       video::SColor c3 = c[2];
+       video::SColor c4 = c[3];
+       video::SColor c5 = c[4];
+       video::SColor c6 = c[5];
+       if (!light_source) {
+               applyFacesShading(c1, v3f(0, 1, 0));
+               applyFacesShading(c2, v3f(0, -1, 0));
+               applyFacesShading(c3, v3f(1, 0, 0));
+               applyFacesShading(c4, v3f(-1, 0, 0));
+               applyFacesShading(c5, v3f(0, 0, 1));
+               applyFacesShading(c6, v3f(0, 0, -1));
+       }
+
        video::S3DVertex vertices[24] =
        {
                // up
-               video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c, txc[0],txc[1]),
-               video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c, txc[2],txc[1]),
-               video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c, txc[2],txc[3]),
-               video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c, txc[0],txc[3]),
+               video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c1, txc[0],txc[1]),
+               video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c1, txc[2],txc[1]),
+               video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c1, txc[2],txc[3]),
+               video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c1, txc[0],txc[3]),
                // down
-               video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c, txc[4],txc[5]),
-               video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c, txc[6],txc[5]),
-               video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c, txc[6],txc[7]),
-               video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c, txc[4],txc[7]),
+               video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c2, txc[4],txc[5]),
+               video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c2, txc[6],txc[5]),
+               video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c2, txc[6],txc[7]),
+               video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c2, txc[4],txc[7]),
                // right
-               video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c, txc[ 8],txc[9]),
-               video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c, txc[10],txc[9]),
-               video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c, txc[10],txc[11]),
-               video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c, txc[ 8],txc[11]),
+               video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c3, txc[ 8],txc[9]),
+               video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c3, txc[10],txc[9]),
+               video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c3, txc[10],txc[11]),
+               video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c3, txc[ 8],txc[11]),
                // left
-               video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c, txc[12],txc[13]),
-               video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c, txc[14],txc[13]),
-               video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c, txc[14],txc[15]),
-               video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c, txc[12],txc[15]),
+               video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c4, txc[12],txc[13]),
+               video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c4, txc[14],txc[13]),
+               video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c4, txc[14],txc[15]),
+               video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c4, txc[12],txc[15]),
                // back
-               video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c, txc[16],txc[17]),
-               video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c, txc[18],txc[17]),
-               video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c, txc[18],txc[19]),
-               video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c, txc[16],txc[19]),
+               video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c5, txc[16],txc[17]),
+               video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c5, txc[18],txc[17]),
+               video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c5, txc[18],txc[19]),
+               video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c5, txc[16],txc[19]),
                // front
-               video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c, txc[20],txc[21]),
-               video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c, txc[22],txc[21]),
-               video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c, txc[22],txc[23]),
-               video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c, txc[20],txc[23]),
+               video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c6, txc[20],txc[21]),
+               video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c6, txc[22],txc[21]),
+               video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c6, txc[22],txc[23]),
+               video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c6, txc[20],txc[23]),
        };
 
        for(int i = 0; i < 6; i++)
@@ -164,6 +180,31 @@ void makeCuboid(MeshCollector *collector, const aabb3f &box,
        }
 }
 
+// Create a cuboid.
+//  collector     - the MeshCollector for the resulting polygons
+//  box           - the position and size of the box
+//  tiles         - the tiles (materials) to use (for all 6 faces)
+//  tilecount     - number of entries in tiles, 1<=tilecount<=6
+//  c             - color of the cuboid
+//  txc           - texture coordinates - this is a list of texture coordinates
+//                  for the opposite corners of each face - therefore, there
+//                  should be (2+2)*6=24 values in the list. Alternatively,
+//                  pass NULL to use the entire texture for each face. The
+//                  order of the faces in the list is up-down-right-left-back-
+//                  front (compatible with ContentFeatures). If you specified
+//                  0,0,1,1 for each face, that would be the same as
+//                  passing NULL.
+//  light source  - if greater than zero, the box's faces will not be shaded
+void makeCuboid(MeshCollector *collector, const aabb3f &box, TileSpec *tiles,
+       int tilecount, const video::SColor &c, const f32* txc,
+       const u8 light_source)
+{
+       video::SColor color[6];
+       for (u8 i = 0; i < 6; i++)
+               color[i] = c;
+       makeCuboid(collector, box, tiles, tilecount, color, txc, light_source);
+}
+
 static inline void getNeighborConnectingFace(v3s16 p, INodeDefManager *nodedef,
                MeshMakeData *data, MapNode n, int v, int *neighbors)
 {
@@ -181,6 +222,18 @@ static inline int NeighborToIndex(const v3s16 &pos)
        return 9 * pos.X + 3 * pos.Y + pos.Z + 13;
 }
 
+/*!
+ * Returns the i-th special tile for a map node.
+ */
+static TileSpec getSpecialTile(const ContentFeatures &f,
+       const MapNode &n, u8 i)
+{
+       TileSpec copy = f.special_tiles[i];
+       if (!copy.has_color)
+               n.getColor(f, &copy.color);
+       return copy;
+}
+
 /*
        TODO: Fix alpha blending for special nodes
        Currently only the last element rendered is blended correct
@@ -227,8 +280,13 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        /*
                                Add water sources to mesh if using new style
                        */
-                       TileSpec tile_liquid = f.special_tiles[0];
+                       TileSpec tile_liquid = getSpecialTile(f, n, 0);
                        TileSpec tile_liquid_bfculled = getNodeTile(n, p, v3s16(0,0,0), data);
+                       u16 l = getInteriorLight(n, 0, nodedef);
+                       video::SColor c1 = encode_light_and_color(l,
+                               tile_liquid.color, f.light_source);
+                       video::SColor c2 = encode_light_and_color(l,
+                               tile_liquid_bfculled.color, f.light_source);
 
                        bool top_is_same_liquid = false;
                        MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
@@ -237,9 +295,6 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        if(ntop.getContent() == c_flowing || ntop.getContent() == c_source)
                                top_is_same_liquid = true;
 
-                       u16 l = getInteriorLight(n, 0, nodedef);
-                       video::SColor c = MapBlock_LightColor(f.alpha, l, f.light_source);
-
                        /*
                                Generate sides
                         */
@@ -285,15 +340,18 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                // Use backface culled material if neighbor doesn't have a
                                // solidness of 0
                                const TileSpec *current_tile = &tile_liquid;
-                               if(n_feat.solidness != 0 || n_feat.visual_solidness != 0)
+                               video::SColor *c = &c1;
+                               if(n_feat.solidness != 0 || n_feat.visual_solidness != 0) {
                                        current_tile = &tile_liquid_bfculled;
+                                       c = &c2;
+                               }
 
                                video::S3DVertex vertices[4] =
                                {
-                                       video::S3DVertex(-BS/2,0,BS/2,0,0,0, c, 0,1),
-                                       video::S3DVertex(BS/2,0,BS/2,0,0,0, c, 1,1),
-                                       video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,0),
-                                       video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,0),
+                                       video::S3DVertex(-BS/2,0,BS/2,0,0,0, *c, 0,1),
+                                       video::S3DVertex(BS/2,0,BS/2,0,0,0, *c, 1,1),
+                                       video::S3DVertex(BS/2,0,BS/2, 0,0,0, *c, 1,0),
+                                       video::S3DVertex(-BS/2,0,BS/2, 0,0,0, *c, 0,0),
                                };
 
                                /*
@@ -359,10 +417,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 
                        video::S3DVertex vertices[4] =
                        {
-                               video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,1),
-                               video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,1),
-                               video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c, 1,0),
-                               video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c, 0,0),
+                               video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c1, 0,1),
+                               video::S3DVertex(BS/2,0,BS/2, 0,0,0, c1, 1,1),
+                               video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c1, 1,0),
+                               video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c1, 0,0),
                        };
 
                        v3f offset(p.X * BS, (p.Y + 0.5) * BS, p.Z * BS);
@@ -380,8 +438,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        /*
                                Add flowing liquid to mesh
                        */
-                       TileSpec tile_liquid = f.special_tiles[0];
-                       TileSpec tile_liquid_bfculled = f.special_tiles[1];
+                       TileSpec tile_liquid = getSpecialTile(f, n, 0);
+                       TileSpec tile_liquid_bfculled = getSpecialTile(f, n, 1);
 
                        bool top_is_same_liquid = false;
                        MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
@@ -404,7 +462,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        // Otherwise use the light of this node (the liquid)
                        else
                                l = getInteriorLight(n, 0, nodedef);
-                       video::SColor c = MapBlock_LightColor(f.alpha, l, f.light_source);
+                       video::SColor c1 = encode_light_and_color(l,
+                               tile_liquid.color, f.light_source);
+                       video::SColor c2 = encode_light_and_color(l,
+                               tile_liquid_bfculled.color, f.light_source);
 
                        u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8);
 
@@ -552,7 +613,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                        is liquid, don't draw side face
                                */
                                if (top_is_same_liquid &&
-                                               neighbor_data.flags & neighborflag_top_is_same_liquid)
+                                               (neighbor_data.flags & neighborflag_top_is_same_liquid))
                                        continue;
 
                                content_t neighbor_content = neighbor_data.content;
@@ -574,15 +635,18 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                // Use backface culled material if neighbor doesn't have a
                                // solidness of 0
                                const TileSpec *current_tile = &tile_liquid;
-                               if(n_feat.solidness != 0 || n_feat.visual_solidness != 0)
+                               video::SColor *c = &c1;
+                               if(n_feat.solidness != 0 || n_feat.visual_solidness != 0) {
                                        current_tile = &tile_liquid_bfculled;
+                                       c = &c2;
+                               }
 
                                video::S3DVertex vertices[4] =
                                {
-                                       video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,1),
-                                       video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,1),
-                                       video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,0),
-                                       video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,0),
+                                       video::S3DVertex(-BS/2,0,BS/2, 0,0,0, *c, 0,1),
+                                       video::S3DVertex(BS/2,0,BS/2, 0,0,0, *c, 1,1),
+                                       video::S3DVertex(BS/2,0,BS/2, 0,0,0, *c, 1,0),
+                                       video::S3DVertex(-BS/2,0,BS/2, 0,0,0, *c, 0,0),
                                };
 
                                /*
@@ -656,10 +720,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        {
                                video::S3DVertex vertices[4] =
                                {
-                                       video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,1),
-                                       video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,1),
-                                       video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c, 1,0),
-                                       video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c, 0,0),
+                                       video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c1, 0,1),
+                                       video::S3DVertex(BS/2,0,BS/2, 0,0,0, c1, 1,1),
+                                       video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c1, 1,0),
+                                       video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c1, 0,0),
                                };
 
                                // To get backface culling right, the vertices need to go
@@ -720,8 +784,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        TileSpec tile = getNodeTile(n, p, v3s16(0,0,0), data);
 
                        u16 l = getInteriorLight(n, 1, nodedef);
-                       video::SColor c = MapBlock_LightColor(255, l, f.light_source);
-
+                       video::SColor c = encode_light_and_color(l, tile.color,
+                               f.light_source);
                        for(u32 j=0; j<6; j++)
                        {
                                // Check this neighbor
@@ -731,13 +795,17 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                // Don't make face if neighbor is of same type
                                if(n2.getContent() == n.getContent())
                                        continue;
+                               video::SColor c2=c;
+                               if(!f.light_source)
+                                       applyFacesShading(c2, v3f(dir.X, dir.Y, dir.Z));
+
 
                                // The face at Z+
                                video::S3DVertex vertices[4] = {
-                                       video::S3DVertex(-BS/2,-BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 1,1),
-                                       video::S3DVertex(BS/2,-BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 0,1),
-                                       video::S3DVertex(BS/2,BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 0,0),
-                                       video::S3DVertex(-BS/2,BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 1,0),
+                                       video::S3DVertex(-BS/2,-BS/2,BS/2, dir.X,dir.Y,dir.Z, c2, 1,1),
+                                       video::S3DVertex(BS/2,-BS/2,BS/2, dir.X,dir.Y,dir.Z, c2, 0,1),
+                                       video::S3DVertex(BS/2,BS/2,BS/2, dir.X,dir.Y,dir.Z, c2, 0,0),
+                                       video::S3DVertex(-BS/2,BS/2,BS/2, dir.X,dir.Y,dir.Z, c2, 1,0),
                                };
 
                                // Rotations in the g_6dirs format
@@ -784,12 +852,20 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                v3s16( 0, 0,-1)
                        };
 
+                       u16 l = getInteriorLight(n, 1, nodedef);
                        u8 i;
                        TileSpec tiles[6];
                        for (i = 0; i < 6; i++)
                                tiles[i] = getNodeTile(n, p, dirs[i], data);
 
+                       video::SColor tile0color = encode_light_and_color(l,
+                               tiles[0].color, f.light_source);
+                       video::SColor tile0colors[6];
+                       for (i = 0; i < 6; i++)
+                               tile0colors[i] = tile0color;
+
                        TileSpec glass_tiles[6];
+                       video::SColor glasscolor[6];
                        if (tiles[1].texture && tiles[2].texture && tiles[3].texture) {
                                glass_tiles[0] = tiles[2];
                                glass_tiles[1] = tiles[3];
@@ -801,14 +877,15 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                for (i = 0; i < 6; i++)
                                        glass_tiles[i] = tiles[1];
                        }
+                       for (i = 0; i < 6; i++)
+                               glasscolor[i] = encode_light_and_color(l, glass_tiles[i].color,
+                                       f.light_source);
 
                        u8 param2 = n.getParam2();
                        bool H_merge = ! bool(param2 & 128);
                        bool V_merge = ! bool(param2 & 64);
                        param2  = param2 & 63;
 
-                       u16 l = getInteriorLight(n, 1, nodedef);
-                       video::SColor c = MapBlock_LightColor(255, l, f.light_source);
                        v3f pos = intToFloat(p, BS);
                        static const float a = BS / 2;
                        static const float g = a - 0.003;
@@ -947,7 +1024,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                        1-tx2, 1-ty2, 1-tx1, 1-ty1,
                                        tx1,   1-ty2,   tx2, 1-ty1,
                                };
-                               makeCuboid(&collector, box, &tiles[0], 1, c, txc1);
+                               makeCuboid(&collector, box, &tiles[0], 1, tile0colors,
+                                       txc1, f.light_source);
                        }
 
                        for(i = 0; i < 6; i++)
@@ -971,16 +1049,21 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                        1-tx2, 1-ty2, 1-tx1, 1-ty1,
                                        tx1,   1-ty2,   tx2, 1-ty1,
                                };
-                               makeCuboid(&collector, box, &glass_tiles[i], 1, c, txc2);
+                               makeCuboid(&collector, box, &glass_tiles[i], 1, glasscolor,
+                                       txc2, f.light_source);
                        }
 
                        if (param2 > 0 && f.special_tiles[0].texture) {
                                // Interior volume level is in range 0 .. 63,
                                // convert it to -0.5 .. 0.5
                                float vlev = (((float)param2 / 63.0 ) * 2.0 - 1.0);
+                               TileSpec tile=getSpecialTile(f, n, 0);
+                               video::SColor special_color = encode_light_and_color(l,
+                                       tile.color, f.light_source);
                                TileSpec interior_tiles[6];
                                for (i = 0; i < 6; i++)
-                                       interior_tiles[i] = f.special_tiles[0];
+                                       interior_tiles[i] = tile;
+
                                float offset = 0.003;
                                box = aabb3f(visible_faces[3] ? -b : -a + offset,
                                                         visible_faces[1] ? -b : -a + offset,
@@ -1004,22 +1087,24 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                        1-tx2, 1-ty2, 1-tx1, 1-ty1,
                                        tx1,   1-ty2,   tx2, 1-ty1,
                                };
-                               makeCuboid(&collector, box, interior_tiles, 6, c,  txc3);
+                               makeCuboid(&collector, box, interior_tiles, 6, special_color,
+                                       txc3, f.light_source);
                        }
                break;}
                case NDT_ALLFACES:
                {
                        TileSpec tile_leaves = getNodeTile(n, p,
                                        v3s16(0,0,0), data);
-
                        u16 l = getInteriorLight(n, 1, nodedef);
-                       video::SColor c = MapBlock_LightColor(255, l, f.light_source);
+                       video::SColor c = encode_light_and_color(l,
+                               tile_leaves.color, f.light_source);
 
                        v3f pos = intToFloat(p, BS);
                        aabb3f box(-BS/2,-BS/2,-BS/2,BS/2,BS/2,BS/2);
                        box.MinEdge += pos;
                        box.MaxEdge += pos;
-                       makeCuboid(&collector, box, &tile_leaves, 1, c, NULL);
+                       makeCuboid(&collector, box, &tile_leaves, 1, c, NULL,
+                               f.light_source);
                break;}
                case NDT_ALLFACES_OPTIONAL:
                        // This is always pre-converted to something else
@@ -1046,7 +1131,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
 
                        u16 l = getInteriorLight(n, 1, nodedef);
-                       video::SColor c = MapBlock_LightColor(255, l, f.light_source);
+                       video::SColor c = encode_light_and_color(l, tile.color,
+                               f.light_source);
 
                        float s = BS/2*f.visual_scale;
                        // Wall at X+ of node
@@ -1087,7 +1173,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
 
                        u16 l = getInteriorLight(n, 0, nodedef);
-                       video::SColor c = MapBlock_LightColor(255, l, f.light_source);
+                       video::SColor c = encode_light_and_color(l, tile.color,
+                               f.light_source);
 
                        float d = (float)BS/16;
                        float s = BS/2*f.visual_scale;
@@ -1132,7 +1219,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
 
                        u16 l = getInteriorLight(n, 1, nodedef);
-                       video::SColor c = MapBlock_LightColor(255, l, f.light_source);
+                       video::SColor c = encode_light_and_color(l, tile.color,
+                               f.light_source);
 
                        float s = BS / 2 * f.visual_scale;
                        // add sqrt(2) visual scale
@@ -1302,7 +1390,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
 
                        u16 l = getInteriorLight(n, 1, nodedef);
-                       video::SColor c = MapBlock_LightColor(255, l, f.light_source);
+                       video::SColor c = encode_light_and_color(l, tile.color,
+                               f.light_source);
 
                        float s = BS / 2 * f.visual_scale;
 
@@ -1437,7 +1526,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        tile_rot.rotation = 1;
 
                        u16 l = getInteriorLight(n, 1, nodedef);
-                       video::SColor c = MapBlock_LightColor(255, l, f.light_source);
+                       video::SColor c = encode_light_and_color(l, tile.color,
+                               f.light_source);
 
                        const f32 post_rad=(f32)BS/8;
                        const f32 bar_rad=(f32)BS/16;
@@ -1456,7 +1546,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                        4/16.,0,8/16.,1,
                                        8/16.,0,12/16.,1,
                                        12/16.,0,16/16.,1};
-                       makeCuboid(&collector, post, &tile_rot, 1, c, postuv);
+                       makeCuboid(&collector, post, &tile_rot, 1, c, postuv,
+                               f.light_source);
 
                        // Now a section of fence, +X, if there's a post there
                        v3s16 p2 = p;
@@ -1477,11 +1568,11 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                        0/16.,8/16.,16/16.,10/16.,
                                        0/16.,14/16.,16/16.,16/16.};
                                makeCuboid(&collector, bar, &tile_nocrack, 1,
-                                               c, xrailuv);
+                                               c, xrailuv, f.light_source);
                                bar.MinEdge.Y -= BS/2;
                                bar.MaxEdge.Y -= BS/2;
                                makeCuboid(&collector, bar, &tile_nocrack, 1,
-                                               c, xrailuv);
+                                               c, xrailuv, f.light_source);
                        }
 
                        // Now a section of fence, +Z, if there's a post there
@@ -1503,11 +1594,11 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                        6/16.,6/16.,8/16.,8/16.,
                                        10/16.,10/16.,12/16.,12/16.};
                                makeCuboid(&collector, bar, &tile_nocrack, 1,
-                                               c, zrailuv);
+                                               c, zrailuv, f.light_source);
                                bar.MinEdge.Y -= BS/2;
                                bar.MaxEdge.Y -= BS/2;
                                makeCuboid(&collector, bar, &tile_nocrack, 1,
-                                               c, zrailuv);
+                                               c, zrailuv, f.light_source);
                        }
                break;}
                case NDT_RAILLIKE:
@@ -1616,7 +1707,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
 
                        u16 l = getInteriorLight(n, 0, nodedef);
-                       video::SColor c = MapBlock_LightColor(255, l, f.light_source);
+                       video::SColor c = encode_light_and_color(l, tile.color,
+                               f.light_source);
 
                        float d = (float)BS/64;
                        float s = BS/2;
@@ -1627,10 +1719,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 
                        video::S3DVertex vertices[4] =
                        {
-                                       video::S3DVertex(-s,  -s+d,-s,  0,0,0,  c,0,1),
-                                       video::S3DVertex( s,  -s+d,-s,  0,0,0,  c,1,1),
-                                       video::S3DVertex( s, g*s+d, s,  0,0,0,  c,1,0),
-                                       video::S3DVertex(-s, g*s+d, s,  0,0,0,  c,0,0),
+                                       video::S3DVertex(-s,  -s+d, -s, 0, 0, 0, c, 0, 1),
+                                       video::S3DVertex( s,  -s+d, -s, 0, 0, 0, c, 1, 1),
+                                       video::S3DVertex( s, g*s+d,  s, 0, 0, 0, c, 1, 0),
+                                       video::S3DVertex(-s, g*s+d,  s, 0, 0, 0, c, 0, 0),
                        };
 
                        for(s32 i=0; i<4; i++)
@@ -1653,10 +1745,16 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                v3s16(0, 0, 1),
                                v3s16(0, 0, -1)
                        };
-                       TileSpec tiles[6];
 
                        u16 l = getInteriorLight(n, 1, nodedef);
-                       video::SColor c = MapBlock_LightColor(255, l, f.light_source);
+                       TileSpec tiles[6];
+                       video::SColor colors[6];
+                       for(int j = 0; j < 6; j++) {
+                               // Handles facedir rotation for textures
+                               tiles[j] = getNodeTile(n, p, tile_dirs[j], data);
+                               colors[j]= encode_light_and_color(l, tiles[j].color,
+                                       f.light_source);
+                       }
 
                        v3f pos = intToFloat(p, BS);
 
@@ -1696,11 +1794,6 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                        i = boxes.begin();
                                        i != boxes.end(); ++i)
                        {
-                       for(int j = 0; j < 6; j++)
-                               {
-                               // Handles facedir rotation for textures
-                               tiles[j] = getNodeTile(n, p, tile_dirs[j], data);
-                               }
                                aabb3f box = *i;
                                box.MinEdge += pos;
                                box.MaxEdge += pos;
@@ -1747,18 +1840,19 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                        // front
                                        tx1, 1-ty2, tx2, 1-ty1,
                                };
-                               makeCuboid(&collector, box, tiles, 6, c, txc);
+                               makeCuboid(&collector, box, tiles, 6, colors, txc, f.light_source);
                        }
                break;}
                case NDT_MESH:
                {
                        v3f pos = intToFloat(p, BS);
-                       video::SColor c = MapBlock_LightColor(255, getInteriorLight(n, 1, nodedef), f.light_source);
-
+                       u16 l = getInteriorLight(n, 1, nodedef);
                        u8 facedir = 0;
-                       if (f.param_type_2 == CPT2_FACEDIR) {
+                       if (f.param_type_2 == CPT2_FACEDIR ||
+                                       f.param_type_2 == CPT2_COLORED_FACEDIR) {
                                facedir = n.getFaceDir(nodedef);
-                       } else if (f.param_type_2 == CPT2_WALLMOUNTED) {
+                       } else if (f.param_type_2 == CPT2_WALLMOUNTED ||
+                                       f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
                                //convert wallmounted to 6dfacedir.
                                //when cache enabled, it is already converted
                                facedir = n.getWallMounted(nodedef);
@@ -1771,10 +1865,13 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                        if (f.mesh_ptr[facedir]) {
                                // use cached meshes
                                for(u16 j = 0; j < f.mesh_ptr[0]->getMeshBufferCount(); j++) {
+                                       const TileSpec &tile = getNodeTileN(n, p, j, data);
                                        scene::IMeshBuffer *buf = f.mesh_ptr[facedir]->getMeshBuffer(j);
-                                       collector.append(getNodeTileN(n, p, j, data),
-                                               (video::S3DVertex *)buf->getVertices(), buf->getVertexCount(),
-                                               buf->getIndices(), buf->getIndexCount(), pos, c);
+                                       collector.append(tile, (video::S3DVertex *)
+                                               buf->getVertices(), buf->getVertexCount(),
+                                               buf->getIndices(), buf->getIndexCount(), pos,
+                                               encode_light_and_color(l, tile.color, f.light_source),
+                                               f.light_source);
                                }
                        } else if (f.mesh_ptr[0]) {
                                // no cache, clone and rotate mesh
@@ -1783,10 +1880,13 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
                                recalculateBoundingBox(mesh);
                                meshmanip->recalculateNormals(mesh, true, false);
                                for(u16 j = 0; j < mesh->getMeshBufferCount(); j++) {
+                                       const TileSpec &tile = getNodeTileN(n, p, j, data);
                                        scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-                                       collector.append(getNodeTileN(n, p, j, data),
-                                               (video::S3DVertex *)buf->getVertices(), buf->getVertexCount(),
-                                               buf->getIndices(), buf->getIndexCount(), pos, c);
+                                       collector.append(tile, (video::S3DVertex *)
+                                               buf->getVertices(), buf->getVertexCount(),
+                                               buf->getIndices(), buf->getIndexCount(), pos,
+                                               encode_light_and_color(l, tile.color, f.light_source),
+                                               f.light_source);
                                }
                                mesh->drop();
                        }
index 1070cb1b25715deb431d7f2eeddb8d38d42ff377..07d429c6b974e09d5fe123a2f4a7c4370c5926f9 100644 (file)
@@ -642,7 +642,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
        CachedPixelShaderSetting<float> m_fog_distance;
        CachedVertexShaderSetting<float> m_animation_timer_vertex;
        CachedPixelShaderSetting<float> m_animation_timer_pixel;
-       CachedPixelShaderSetting<float> m_day_night_ratio;
+       CachedPixelShaderSetting<float, 3> m_day_light;
        CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
        CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
        CachedPixelShaderSetting<float, 3> m_minimap_yaw;
@@ -674,7 +674,7 @@ public:
                m_fog_distance("fogDistance"),
                m_animation_timer_vertex("animationTimer"),
                m_animation_timer_pixel("animationTimer"),
-               m_day_night_ratio("dayNightRatio"),
+               m_day_light("dayLight"),
                m_eye_position_pixel("eyePosition"),
                m_eye_position_vertex("eyePosition"),
                m_minimap_yaw("yawVec"),
@@ -717,8 +717,14 @@ public:
 
                m_fog_distance.set(&fog_distance, services);
 
-               float daynight_ratio = (float)m_client->getEnv().getDayNightRatio() / 1000.f;
-               m_day_night_ratio.set(&daynight_ratio, services);
+               u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
+               video::SColorf sunlight;
+               get_sunlight_color(&sunlight, daynight_ratio);
+               float dnc[3] = {
+                       sunlight.r,
+                       sunlight.g,
+                       sunlight.b };
+               m_day_light.set(dnc, services);
 
                u32 animation_timer = porting::getTimeMs() % 100000;
                float animation_timer_f = (float)animation_timer / 100000.f;
@@ -840,7 +846,8 @@ bool nodePlacementPrediction(Client &client,
                // Predict param2 for facedir and wallmounted nodes
                u8 param2 = 0;
 
-               if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED) {
+               if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED ||
+                               nodedef->get(id).param_type_2 == CPT2_COLORED_WALLMOUNTED) {
                        v3s16 dir = nodepos - neighbourpos;
 
                        if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
@@ -852,7 +859,8 @@ bool nodePlacementPrediction(Client &client,
                        }
                }
 
-               if (nodedef->get(id).param_type_2 == CPT2_FACEDIR) {
+               if (nodedef->get(id).param_type_2 == CPT2_FACEDIR ||
+                               nodedef->get(id).param_type_2 == CPT2_COLORED_FACEDIR) {
                        v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS);
 
                        if (abs(dir.X) > abs(dir.Z)) {
@@ -3749,11 +3757,9 @@ PointedThing Game::updatePointedThing(
                                light_level = node_light;
                }
 
-               video::SColor c = MapBlock_LightColor(255, light_level, 0);
-               u8 day = c.getRed();
-               u8 night = c.getGreen();
                u32 daynight_ratio = client->getEnv().getDayNightRatio();
-               finalColorBlend(c, day, night, daynight_ratio);
+               video::SColor c;
+               final_color_blend(&c, light_level, daynight_ratio);
 
                // Modify final color a bit with time
                u32 timer = porting::getTimeMs() % 5000;
@@ -3964,7 +3970,7 @@ void Game::handleDigging(GameRunData *runData,
                        const ContentFeatures &features =
                                        client->getNodeDefManager()->get(n);
                        client->getParticleManager()->addPunchingParticles(client, smgr,
-                                       player, nodepos, features.tiles);
+                                       player, nodepos, n, features);
                }
        }
 
@@ -4011,7 +4017,7 @@ void Game::handleDigging(GameRunData *runData,
                        const ContentFeatures &features =
                                client->getNodeDefManager()->get(wasnode);
                        client->getParticleManager()->addDiggingParticles(client, smgr,
-                                       player, nodepos, features.tiles);
+                                       player, nodepos, wasnode, features);
                }
 
                runData->dig_time = 0;
index 143adb410f006bee1fc10bd6c52f6b5f047ecaec..dfd6f55a57420cc5779b46fc7406a18f77b57702 100644 (file)
@@ -32,12 +32,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/directiontables.h"
 #include <IMeshManipulator.h>
 
-static void applyFacesShading(video::SColor &color, const float factor)
-{
-       color.setRed(core::clamp(core::round32(color.getRed() * factor), 0, 255));
-       color.setGreen(core::clamp(core::round32(color.getGreen() * factor), 0, 255));
-}
-
 /*
        MeshMakeData
 */
@@ -321,19 +315,34 @@ u16 getSmoothLight(v3s16 p, v3s16 corner, MeshMakeData *data)
        return getSmoothLightCombined(p, data);
 }
 
-/*
-       Converts from day + night color values (0..255)
-       and a given daynight_ratio to the final SColor shown on screen.
-*/
-void finalColorBlend(video::SColor& result,
-               u8 day, u8 night, u32 daynight_ratio)
+void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){
+       f32 rg = daynight_ratio / 1000.0f - 0.04f;
+       f32 b = (0.98f * daynight_ratio) / 1000.0f + 0.078f;
+       sunlight->r = rg;
+       sunlight->g = rg;
+       sunlight->b = b;
+}
+
+void final_color_blend(video::SColor *result,
+               u16 light, u32 daynight_ratio)
 {
-       s32 rg = (day * daynight_ratio + night * (1000-daynight_ratio)) / 1000;
-       s32 b = rg;
+       video::SColorf dayLight;
+       get_sunlight_color(&dayLight, daynight_ratio);
+       final_color_blend(result,
+               encode_light_and_color(light, video::SColor(0xFFFFFFFF), 0), dayLight);
+}
 
-       // Moonlight is blue
-       b += (day - night) / 13;
-       rg -= (day - night) / 23;
+void final_color_blend(video::SColor *result,
+               const video::SColor &data, const video::SColorf &dayLight)
+{
+       static const video::SColorf artificialColor(1.04f, 1.04f, 1.04f);
+
+       video::SColorf c(data);
+       f32 n = 1 - c.a;
+
+       f32 r = c.r * (c.a * dayLight.r + n * artificialColor.r) * 2.0f;
+       f32 g = c.g * (c.a * dayLight.g + n * artificialColor.g) * 2.0f;
+       f32 b = c.b * (c.a * dayLight.b + n * artificialColor.b) * 2.0f;
 
        // Emphase blue a bit in darker places
        // Each entry of this array represents a range of 8 blue levels
@@ -341,19 +350,13 @@ void finalColorBlend(video::SColor& result,
                1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        };
-       b += emphase_blue_when_dark[irr::core::clamp(b, 0, 255) / 8];
-       b = irr::core::clamp(b, 0, 255);
 
-       // Artificial light is yellow-ish
-       static const u8 emphase_yellow_when_artificial[16] = {
-               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 15, 15, 15
-       };
-       rg += emphase_yellow_when_artificial[night/16];
-       rg = irr::core::clamp(rg, 0, 255);
+       b += emphase_blue_when_dark[irr::core::clamp((s32) ((r + g + b) / 3 * 255),
+               0, 255) / 8] / 255.0f;
 
-       result.setRed(rg);
-       result.setGreen(rg);
-       result.setBlue(b);
+       result->setRed(core::clamp((s32) (r * 255.0f), 0, 255));
+       result->setGreen(core::clamp((s32) (g * 255.0f), 0, 255));
+       result->setBlue(core::clamp((s32) (b * 255.0f), 0, 255));
 }
 
 /*
@@ -430,7 +433,7 @@ struct FastFace
 };
 
 static void makeFastFace(TileSpec tile, u16 li0, u16 li1, u16 li2, u16 li3,
-               v3f p, v3s16 dir, v3f scale, u8 light_source, std::vector<FastFace> &dest)
+               v3f p, v3s16 dir, v3f scale, std::vector<FastFace> &dest)
 {
        // Position is at the center of the cube.
        v3f pos = p * BS;
@@ -580,24 +583,25 @@ static void makeFastFace(TileSpec tile, u16 li0, u16 li1, u16 li2, u16 li3,
 
        v3f normal(dir.X, dir.Y, dir.Z);
 
-       u8 alpha = tile.alpha;
-
        dest.push_back(FastFace());
 
        FastFace& face = *dest.rbegin();
 
-       face.vertices[0] = video::S3DVertex(vertex_pos[0], normal,
-                       MapBlock_LightColor(alpha, li0, light_source),
-                       core::vector2d<f32>(x0+w*abs_scale, y0+h));
-       face.vertices[1] = video::S3DVertex(vertex_pos[1], normal,
-                       MapBlock_LightColor(alpha, li1, light_source),
-                       core::vector2d<f32>(x0, y0+h));
-       face.vertices[2] = video::S3DVertex(vertex_pos[2], normal,
-                       MapBlock_LightColor(alpha, li2, light_source),
-                       core::vector2d<f32>(x0, y0));
-       face.vertices[3] = video::S3DVertex(vertex_pos[3], normal,
-                       MapBlock_LightColor(alpha, li3, light_source),
-                       core::vector2d<f32>(x0+w*abs_scale, y0));
+       u16 li[4] = { li0, li1, li2, li3 };
+       v2f32 f[4] = {
+               core::vector2d<f32>(x0 + w * abs_scale, y0 + h),
+               core::vector2d<f32>(x0, y0 + h),
+               core::vector2d<f32>(x0, y0),
+               core::vector2d<f32>(x0 + w * abs_scale, y0) };
+
+       for (u8 i = 0; i < 4; i++) {
+               video::SColor c = encode_light_and_color(li[i], tile.color,
+                       tile.emissive_light);
+               if (!tile.emissive_light)
+                       applyFacesShading(c, normal);
+
+               face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]);
+       }
 
        face.tile = tile;
 }
@@ -664,7 +668,10 @@ static u8 face_contents(content_t m1, content_t m2, bool *equivalent,
 TileSpec getNodeTileN(MapNode mn, v3s16 p, u8 tileindex, MeshMakeData *data)
 {
        INodeDefManager *ndef = data->m_client->ndef();
-       TileSpec spec = ndef->get(mn).tiles[tileindex];
+       const ContentFeatures &f = ndef->get(mn);
+       TileSpec spec = f.tiles[tileindex];
+       if (!spec.has_color)
+               mn.getColor(f, &spec.color);
        // Apply temporary crack
        if (p == data->m_crack_pos_relative)
                spec.material_flags |= MATERIAL_FLAG_CRACK;
@@ -747,8 +754,7 @@ static void getTileInfo(
                v3s16 &p_corrected,
                v3s16 &face_dir_corrected,
                u16 *lights,
-               TileSpec &tile,
-               u8 &light_source
+               TileSpec &tile
        )
 {
        VoxelManipulator &vmanip = data->m_vmanip;
@@ -763,7 +769,8 @@ static void getTileInfo(
                return;
        }
 
-       const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir);
+       const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(
+               blockpos_nodes + p + face_dir);
 
        if (n1.getContent() == CONTENT_IGNORE) {
                makes_face = false;
@@ -783,26 +790,25 @@ static void getTileInfo(
 
        makes_face = true;
 
-       if(mf == 1)
-       {
-               tile = getNodeTile(n0, p, face_dir, data);
+       MapNode n = n0;
+
+       if (mf == 1) {
                p_corrected = p;
                face_dir_corrected = face_dir;
-               light_source = ndef->get(n0).light_source;
-       }
-       else
-       {
-               tile = getNodeTile(n1, p + face_dir, -face_dir, data);
+       } else {
+               n = n1;
                p_corrected = p + face_dir;
                face_dir_corrected = -face_dir;
-               light_source = ndef->get(n1).light_source;
        }
+       tile = getNodeTile(n, p_corrected, face_dir_corrected, data);
+       const ContentFeatures &f = ndef->get(n);
+       tile.emissive_light = f.light_source;
 
        // eg. water and glass
-       if(equivalent)
+       if (equivalent)
                tile.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
 
-       if(data->m_smooth_lighting == false)
+       if (data->m_smooth_lighting == false)
        {
                lights[0] = lights[1] = lights[2] = lights[3] =
                                getFaceLight(n0, n1, face_dir, ndef);
@@ -845,10 +851,9 @@ static void updateFastFaceRow(
        v3s16 face_dir_corrected;
        u16 lights[4] = {0,0,0,0};
        TileSpec tile;
-       u8 light_source = 0;
        getTileInfo(data, p, face_dir,
                        makes_face, p_corrected, face_dir_corrected,
-                       lights, tile, light_source);
+                       lights, tile);
 
        for(u16 j=0; j<MAP_BLOCKSIZE; j++)
        {
@@ -862,7 +867,6 @@ static void updateFastFaceRow(
                v3s16 next_face_dir_corrected;
                u16 next_lights[4] = {0,0,0,0};
                TileSpec next_tile;
-               u8 next_light_source = 0;
 
                // If at last position, there is nothing to compare to and
                // the face must be drawn anyway
@@ -873,7 +877,7 @@ static void updateFastFaceRow(
                        getTileInfo(data, p_next, face_dir,
                                        next_makes_face, next_p_corrected,
                                        next_face_dir_corrected, next_lights,
-                                       next_tile, next_light_source);
+                                       next_tile);
 
                        if(next_makes_face == makes_face
                                        && next_p_corrected == p_corrected + translate_dir
@@ -884,9 +888,10 @@ static void updateFastFaceRow(
                                        && next_lights[3] == lights[3]
                                        && next_tile == tile
                                        && tile.rotation == 0
-                                       && next_light_source == light_source
                                        && (tile.material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL)
-                                       && (tile.material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL)) {
+                                       && (tile.material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL)
+                                       && tile.color == next_tile.color
+                                       && tile.emissive_light == next_tile.emissive_light) {
                                next_is_different = false;
                                continuous_tiles_count++;
                        } else {
@@ -938,8 +943,7 @@ static void updateFastFaceRow(
                                }
 
                                makeFastFace(tile, lights[0], lights[1], lights[2], lights[3],
-                                               sp, face_dir_corrected, scale, light_source,
-                                               dest);
+                                               sp, face_dir_corrected, scale, dest);
 
                                g_profiler->avg("Meshgen: faces drawn by tiling", 0);
                                for(int i = 1; i < continuous_tiles_count; i++){
@@ -958,7 +962,6 @@ static void updateFastFaceRow(
                lights[2] = next_lights[2];
                lights[3] = next_lights[3];
                tile = next_tile;
-               light_source = next_light_source;
                p = p_next;
        }
 }
@@ -1083,12 +1086,14 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
                        const u16 *indices_p = indices;
 
                        /*
-                               Revert triangles for nicer looking gradient if vertices
-                               1 and 3 have same color or 0 and 2 have different color.
-                               getRed() is the day color.
+                               Revert triangles for nicer looking gradient if the
+                               brightness of vertices 1 and 3 differ less than
+                               the brightness of vertices 0 and 2.
                        */
-                       if(f.vertices[0].Color.getRed() != f.vertices[2].Color.getRed()
-                                       || f.vertices[1].Color.getRed() == f.vertices[3].Color.getRed())
+                       if (abs(f.vertices[0].Color.getAverage()
+                                       - f.vertices[2].Color.getAverage())
+                                       > abs(f.vertices[1].Color.getAverage()
+                                       - f.vertices[3].Color.getAverage()))
                                indices_p = indices_alternate;
 
                        collector.append(f.tile, f.vertices, 4, indices_p, 6);
@@ -1148,43 +1153,30 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
                        p.tile.texture = animation_frame.texture;
                }
 
-               u32 vertex_count = m_use_tangent_vertices ?
-                       p.tangent_vertices.size() : p.vertices.size();
-               for (u32 j = 0; j < vertex_count; j++) {
-                       v3f *Normal;
-                       video::SColor *vc;
-                       if (m_use_tangent_vertices) {
-                               vc = &p.tangent_vertices[j].Color;
-                               Normal = &p.tangent_vertices[j].Normal;
-                       } else {
-                               vc = &p.vertices[j].Color;
-                               Normal = &p.vertices[j].Normal;
-                       }
-                       // Note applyFacesShading second parameter is precalculated sqrt
-                       // value for speed improvement
-                       // Skip it for lightsources and top faces.
-                       if (!vc->getBlue()) {
-                               if (Normal->Y < -0.5) {
-                                       applyFacesShading(*vc, 0.447213);
-                               } else if (Normal->X > 0.5) {
-                                       applyFacesShading(*vc, 0.670820);
-                               } else if (Normal->X < -0.5) {
-                                       applyFacesShading(*vc, 0.670820);
-                               } else if (Normal->Z > 0.5) {
-                                       applyFacesShading(*vc, 0.836660);
-                               } else if (Normal->Z < -0.5) {
-                                       applyFacesShading(*vc, 0.836660);
-                               }
-                       }
-                       if (!m_enable_shaders) {
-                               // - Classic lighting (shaders handle this by themselves)
-                               // Set initial real color and store for later updates
-                               u8 day = vc->getRed();
-                               u8 night = vc->getGreen();
-                               finalColorBlend(*vc, day, night, 1000);
-                               if (day != night) {
-                                       m_daynight_diffs[i][j] = std::make_pair(day, night);
+               if (!m_enable_shaders) {
+                       // Extract colors for day-night animation
+                       // Dummy sunlight to handle non-sunlit areas
+                       video::SColorf sunlight;
+                       get_sunlight_color(&sunlight, 0);
+                       u32 vertex_count =
+                               m_use_tangent_vertices ?
+                                       p.tangent_vertices.size() : p.vertices.size();
+                       for (u32 j = 0; j < vertex_count; j++) {
+                               video::SColor *vc;
+                               if (m_use_tangent_vertices) {
+                                       vc = &p.tangent_vertices[j].Color;
+                               } else {
+                                       vc = &p.vertices[j].Color;
                                }
+                               video::SColor copy(*vc);
+                               if (vc->getAlpha() == 0) // No sunlight - no need to animate
+                                       final_color_blend(vc, copy, sunlight); // Finalize color
+                               else // Record color to animate
+                                       m_daynight_diffs[i][j] = copy;
+
+                               // The sunlight ratio has been stored,
+                               // delete alpha (for the final rendering).
+                               vc->setAlpha(255);
                        }
                }
 
@@ -1358,19 +1350,19 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat
                if (m_enable_vbo) {
                        m_mesh->setDirty();
                }
-               for(std::map<u32, std::map<u32, std::pair<u8, u8> > >::iterator
+               video::SColorf day_color;
+               get_sunlight_color(&day_color, daynight_ratio);
+               for(std::map<u32, std::map<u32, video::SColor > >::iterator
                                i = m_daynight_diffs.begin();
                                i != m_daynight_diffs.end(); ++i)
                {
                        scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i->first);
                        video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
-                       for(std::map<u32, std::pair<u8, u8 > >::iterator
+                       for(std::map<u32, video::SColor >::iterator
                                        j = i->second.begin();
                                        j != i->second.end(); ++j)
                        {
-                               u8 day = j->second.first;
-                               u8 night = j->second.second;
-                               finalColorBlend(vertices[j->first].Color, day, night, daynight_ratio);
+                               final_color_blend(&(vertices[j->first].Color), j->second, day_color);
                        }
                }
                m_last_daynight_ratio = daynight_ratio;
@@ -1452,7 +1444,7 @@ void MeshCollector::append(const TileSpec &tile,
 void MeshCollector::append(const TileSpec &tile,
                const video::S3DVertex *vertices, u32 numVertices,
                const u16 *indices, u32 numIndices,
-               v3f pos, video::SColor c)
+               v3f pos, video::SColor c, u8 light_source)
 {
        if (numIndices > 65535) {
                dstream<<"FIXME: MeshCollector::append() called with numIndices="<<numIndices<<" (limit 65535)"<<std::endl;
@@ -1478,10 +1470,15 @@ void MeshCollector::append(const TileSpec &tile,
                p = &prebuffers[prebuffers.size() - 1];
        }
 
+       video::SColor original_c = c;
        u32 vertex_count;
        if (m_use_tangent_vertices) {
                vertex_count = p->tangent_vertices.size();
                for (u32 i = 0; i < numVertices; i++) {
+                       if (!light_source) {
+                               c = original_c;
+                               applyFacesShading(c, vertices[i].Normal);
+                       }
                        video::S3DVertexTangents vert(vertices[i].Pos + pos,
                                vertices[i].Normal, c, vertices[i].TCoords);
                        p->tangent_vertices.push_back(vert);
@@ -1489,8 +1486,12 @@ void MeshCollector::append(const TileSpec &tile,
        } else {
                vertex_count = p->vertices.size();
                for (u32 i = 0; i < numVertices; i++) {
-                       video::S3DVertex vert(vertices[i].Pos + pos,
-                               vertices[i].Normal, c, vertices[i].TCoords);
+                       if (!light_source) {
+                               c = original_c;
+                               applyFacesShading(c, vertices[i].Normal);
+                       }
+                       video::S3DVertex vert(vertices[i].Pos + pos, vertices[i].Normal, c,
+                               vertices[i].TCoords);
                        p->vertices.push_back(vert);
                }
        }
@@ -1500,3 +1501,33 @@ void MeshCollector::append(const TileSpec &tile,
                p->indices.push_back(j);
        }
 }
+
+video::SColor encode_light_and_color(u16 light, const video::SColor &color,
+       u8 emissive_light)
+{
+       // Get components
+       f32 day = (light & 0xff) / 255.0f;
+       f32 night = (light >> 8) / 255.0f;
+       // Add emissive light
+       night += emissive_light * 0.01f;
+       if (night > 255)
+               night = 255;
+       // Since we don't know if the day light is sunlight or
+       // artificial light, assume it is artificial when the night
+       // light bank is also lit.
+       if (day < night)
+               day = 0;
+       else
+               day = day - night;
+       f32 sum = day + night;
+       // Ratio of sunlight:
+       float r;
+       if (sum > 0)
+               r = day / sum;
+       else
+               r = 0;
+       // Average light:
+       float b = (day + night) / 2;
+       return video::SColor(r * 255, b * color.getRed(), b * color.getGreen(),
+               b * color.getBlue());
+}
index 5adb7df3f6343919b70bc49f282465e217151b1c..916703f3ea97ec49326e96714d966e24f81cf6cd 100644 (file)
@@ -156,8 +156,8 @@ private:
        // Animation info: day/night transitions
        // Last daynight_ratio value passed to animate()
        u32 m_last_daynight_ratio;
-       // For each meshbuffer, maps vertex indices to (day,night) pairs
-       std::map<u32, std::map<u32, std::pair<u8, u8> > > m_daynight_diffs;
+       // For each meshbuffer, stores pre-baked colors of sunlit vertices
+       std::map<u32, std::map<u32, video::SColor > > m_daynight_diffs;
 
        // Camera offset info -> do we have to translate the mesh?
        v3s16 m_camera_offset;
@@ -192,28 +192,53 @@ struct MeshCollector
        void append(const TileSpec &material,
                        const video::S3DVertex *vertices, u32 numVertices,
                        const u16 *indices, u32 numIndices,
-                       v3f pos, video::SColor c);
+                       v3f pos, video::SColor c, u8 light_source);
 };
 
-// This encodes
-//   alpha in the A channel of the returned SColor
-//   day light (0-255) in the R channel of the returned SColor
-//   night light (0-255) in the G channel of the returned SColor
-//   light source (0-255) in the B channel of the returned SColor
-inline video::SColor MapBlock_LightColor(u8 alpha, u16 light, u8 light_source=0)
-{
-       return video::SColor(alpha, (light & 0xff), (light >> 8), light_source);
-}
+/*!
+ * Encodes light and color of a node.
+ * The result is not the final color, but a
+ * half-baked vertex color.
+ *
+ * \param light the first 8 bits are day light,
+ * the last 8 bits are night light
+ * \param color the node's color
+ * \param emissive_light amount of light the surface emits,
+ * from 0 to LIGHT_SUN.
+ */
+video::SColor encode_light_and_color(u16 light, const video::SColor &color,
+       u8 emissive_light);
 
 // Compute light at node
 u16 getInteriorLight(MapNode n, s32 increment, INodeDefManager *ndef);
 u16 getFaceLight(MapNode n, MapNode n2, v3s16 face_dir, INodeDefManager *ndef);
 u16 getSmoothLight(v3s16 p, v3s16 corner, MeshMakeData *data);
 
-// Converts from day + night color values (0..255)
-// and a given daynight_ratio to the final SColor shown on screen.
-void finalColorBlend(video::SColor& result,
-               u8 day, u8 night, u32 daynight_ratio);
+/*!
+ * Returns the sunlight's color from the current
+ * day-night ratio.
+ */
+void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio);
+
+/*!
+ * Gives the final  SColor shown on screen.
+ *
+ * \param result output color
+ * \param light first 8 bits are day light, second 8 bits are
+ * night light
+ */
+void final_color_blend(video::SColor *result,
+               u16 light, u32 daynight_ratio);
+
+/*!
+ * Gives the final  SColor shown on screen.
+ *
+ * \param result output color
+ * \param data the half-baked vertex color
+ * \param dayLight color of the sunlight
+ */
+void final_color_blend(video::SColor *result,
+               const video::SColor &data, const video::SColorf &dayLight);
 
 // Retrieves the TileSpec of a face of a node
 // Adds MATERIAL_FLAG_CRACK if the node is cracked
index f1a7f3e6173cbb31d26b9a57168e5007ca0780bc..d835daba27f461f0629fe33958dec3d69854254f 100644 (file)
@@ -55,6 +55,15 @@ MapNode::MapNode(INodeDefManager *ndef, const std::string &name,
        param2 = a_param2;
 }
 
+void MapNode::getColor(const ContentFeatures &f, video::SColor *color) const
+{
+       if (f.palette) {
+               *color = (*f.palette)[param2];
+               return;
+       }
+       *color = f.color;
+}
+
 void MapNode::setLight(enum LightBank bank, u8 a_light, const ContentFeatures &f)
 {
        // If node doesn't contain light data, ignore this
@@ -146,7 +155,8 @@ bool MapNode::getLightBanks(u8 &lightday, u8 &lightnight, INodeDefManager *nodem
 u8 MapNode::getFaceDir(INodeDefManager *nodemgr) const
 {
        const ContentFeatures &f = nodemgr->get(*this);
-       if(f.param_type_2 == CPT2_FACEDIR)
+       if (f.param_type_2 == CPT2_FACEDIR ||
+                       f.param_type_2 == CPT2_COLORED_FACEDIR)
                return (getParam2() & 0x1F) % 24;
        return 0;
 }
@@ -154,7 +164,8 @@ u8 MapNode::getFaceDir(INodeDefManager *nodemgr) const
 u8 MapNode::getWallMounted(INodeDefManager *nodemgr) const
 {
        const ContentFeatures &f = nodemgr->get(*this);
-       if(f.param_type_2 == CPT2_WALLMOUNTED)
+       if (f.param_type_2 == CPT2_WALLMOUNTED ||
+                       f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
                return getParam2() & 0x07;
        return 0;
 }
@@ -176,7 +187,7 @@ void MapNode::rotateAlongYAxis(INodeDefManager *nodemgr, Rotation rot)
 {
        ContentParamType2 cpt2 = nodemgr->get(*this).param_type_2;
 
-       if (cpt2 == CPT2_FACEDIR) {
+       if (cpt2 == CPT2_FACEDIR || cpt2 == CPT2_COLORED_FACEDIR) {
                static const u8 rotate_facedir[24 * 4] = {
                        // Table value = rotated facedir
                        // Columns: 0, 90, 180, 270 degrees rotation around vertical axis
@@ -216,7 +227,8 @@ void MapNode::rotateAlongYAxis(INodeDefManager *nodemgr, Rotation rot)
                u8 index = facedir * 4 + rot;
                param2 &= ~31;
                param2 |= rotate_facedir[index];
-       } else if (cpt2 == CPT2_WALLMOUNTED) {
+       } else if (cpt2 == CPT2_WALLMOUNTED ||
+                       cpt2 == CPT2_COLORED_WALLMOUNTED) {
                u8 wmountface = (param2 & 7);
                if (wmountface <= 1)
                        return;
index ae0245cfe3ee707f162bce7b265fb915d755fd46..9c56a7e170c9b4b73d8b5217041356e27ee328d1 100644 (file)
@@ -20,9 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #ifndef MAPNODE_HEADER
 #define MAPNODE_HEADER
 
-#include "irrlichttypes.h"
-#include "irr_v3d.h"
-#include "irr_aabb3d.h"
+#include "irrlichttypes_bloated.h"
 #include "light.h"
 #include <string>
 #include <vector>
@@ -187,6 +185,14 @@ struct MapNode
                param2 = p;
        }
 
+       /*!
+        * Returns the color of the node.
+        *
+        * \param f content features of this node
+        * \param color output, contains the node's color.
+        */
+       void getColor(const ContentFeatures &f, video::SColor *color) const;
+
        void setLight(enum LightBank bank, u8 a_light, const ContentFeatures &f);
 
        void setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr);
index 50465748cabec9739b4f1fe28eadb334c8e6247b..84689b6319f1f39e875301850913ebb4527c9f94 100644 (file)
@@ -33,13 +33,25 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY
 #endif
 
-static void applyFacesShading(video::SColor& color, float factor)
+inline static void applyShadeFactor(video::SColor& color, float factor)
 {
        color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255));
        color.setGreen(core::clamp(core::round32(color.getGreen()*factor), 0, 255));
        color.setBlue(core::clamp(core::round32(color.getBlue()*factor), 0, 255));
 }
 
+void applyFacesShading(video::SColor &color, const v3f &normal)
+{
+       // Many special drawtypes have normals set to 0,0,0 and this
+       // must result in maximum brightness (no face shadng).
+       if (normal.Y < -0.5f)
+               applyShadeFactor (color, 0.447213f);
+       else if (normal.X > 0.5f || normal.X < -0.5f)
+               applyShadeFactor (color, 0.670820f);
+       else if (normal.Z > 0.5f || normal.Z < -0.5f)
+               applyShadeFactor (color, 0.836660f);
+}
+
 scene::IAnimatedMesh* createCubeMesh(v3f scale)
 {
        video::SColor c(255,255,255,255);
@@ -172,29 +184,18 @@ void setMeshColor(scene::IMesh *mesh, const video::SColor &color)
        }
 }
 
-void shadeMeshFaces(scene::IMesh *mesh)
+void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor)
 {
-       if (mesh == NULL)
-               return;
-
-       u32 mc = mesh->getMeshBufferCount();
-       for (u32 j = 0; j < mc; j++) {
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-               const u32 stride = getVertexPitchFromType(buf->getVertexType());
-               u32 vertex_count = buf->getVertexCount();
-               u8 *vertices = (u8 *)buf->getVertices();
-               for (u32 i = 0; i < vertex_count; i++) {
-                       video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
-                       video::SColor &vc = vertex->Color;
-                       // Many special drawtypes have normals set to 0,0,0 and this
-                       // must result in maximum brightness (no face shadng).
-                       if (vertex->Normal.Y < -0.5f)
-                               applyFacesShading (vc, 0.447213f);
-                       else if (vertex->Normal.X > 0.5f || vertex->Normal.X < -0.5f)
-                               applyFacesShading (vc, 0.670820f);
-                       else if (vertex->Normal.Z > 0.5f || vertex->Normal.Z < -0.5f)
-                               applyFacesShading (vc, 0.836660f);
-               }
+       const u32 stride = getVertexPitchFromType(buf->getVertexType());
+       u32 vertex_count = buf->getVertexCount();
+       u8 *vertices = (u8 *) buf->getVertices();
+       for (u32 i = 0; i < vertex_count; i++) {
+               video::S3DVertex *vertex = (video::S3DVertex *) (vertices + i * stride);
+               video::SColor *vc = &(vertex->Color);
+               // Reset color
+               *vc = *buffercolor;
+               // Apply shading
+               applyFacesShading(*vc, vertex->Normal);
        }
 }
 
index 10df97015e52064051db6dc32bca50cf975ff589..bcf0d771c508e4ccce5050db0dffeefda8cc4f0f 100644 (file)
@@ -23,6 +23,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "irrlichttypes_extrabloated.h"
 #include "nodedef.h"
 
+/*!
+ * Applies shading to a color based on the surface's
+ * normal vector.
+ */
+void applyFacesShading(video::SColor &color, const v3f &normal);
+
 /*
        Create a new cube mesh.
        Vertices are at (+-scale.X/2, +-scale.Y/2, +-scale.Z/2).
@@ -48,11 +54,7 @@ void translateMesh(scene::IMesh *mesh, v3f vec);
 */
 void setMeshColor(scene::IMesh *mesh, const video::SColor &color);
 
-/*
-       Shade mesh faces according to their normals
-*/
-
-void shadeMeshFaces(scene::IMesh *mesh);
+void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor);
 
 /*
        Set the color of all vertices in the mesh.
@@ -87,7 +89,7 @@ void rotateMeshYZby (scene::IMesh *mesh, f64 degrees);
 scene::IMesh* cloneMesh(scene::IMesh *src_mesh);
 
 /*
-       Convert nodeboxes to mesh.
+       Convert nodeboxes to mesh. Each tile goes into a different buffer.
        boxes - set of nodeboxes to be converted into cuboids
        uv_coords[24] - table of texture uv coords for each cuboid face
        expand - factor by which cuboids will be resized
index f49adb5170dc3a048dcb8e0c716086e4c1e888bb..a2e50175171459eee110cd6c8d46343141592aa5 100644 (file)
@@ -144,7 +144,7 @@ MinimapPixel *MinimapUpdateThread::getMinimapPixel(v3s16 pos,
                if (it != m_blocks_cache.end()) {
                        MinimapMapblock *mmblock = it->second;
                        MinimapPixel *pixel = &mmblock->data[relpos.Z * MAP_BLOCKSIZE + relpos.X];
-                       if (pixel->id != CONTENT_AIR) {
+                       if (pixel->n.param0 != CONTENT_AIR) {
                                *pixel_height = height + pixel->height;
                                return pixel;
                        }
@@ -187,7 +187,7 @@ void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height, bool is_radar)
 
        for (s16 x = 0; x < size; x++)
        for (s16 z = 0; z < size; z++) {
-               u16 id = CONTENT_AIR;
+               MapNode n(CONTENT_AIR);
                MinimapPixel *mmpixel = &data->minimap_scan[x + z * size];
 
                if (!is_radar) {
@@ -195,14 +195,14 @@ void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height, bool is_radar)
                        MinimapPixel *cached_pixel =
                                getMinimapPixel(v3s16(p.X + x, p.Y, p.Z + z), height, &pixel_height);
                        if (cached_pixel) {
-                               id = cached_pixel->id;
+                               n = cached_pixel->n;
                                mmpixel->height = pixel_height;
                        }
                } else {
                        mmpixel->air_count = getAirCount(v3s16(p.X + x, p.Y, p.Z + z), height);
                }
 
-               mmpixel->id = id;
+               mmpixel->n = n;
        }
 }
 
@@ -372,10 +372,21 @@ void Mapper::blitMinimapPixelsToImageSurface(
        for (s16 z = 0; z < data->map_size; z++) {
                MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size];
 
-               video::SColor c = m_ndef->get(mmpixel->id).minimap_color;
-               c.setAlpha(240);
-
-               map_image->setPixel(x, data->map_size - z - 1, c);
+               const ContentFeatures &f = m_ndef->get(mmpixel->n);
+               const TileDef *tile = &f.tiledef[0];
+               // Color of the 0th tile (mostly this is the topmost)
+               video::SColor tilecolor;
+               if(tile->has_color)
+                       tilecolor = tile->color;
+               else
+                       mmpixel->n.getColor(f, &tilecolor);
+               tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255);
+               tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen()
+                       / 255);
+               tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255);
+               tilecolor.setAlpha(240);
+
+               map_image->setPixel(x, data->map_size - z - 1, tilecolor);
 
                u32 h = mmpixel->height;
                heightmap_image->setPixel(x,data->map_size - z - 1,
@@ -617,7 +628,7 @@ void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, v3s16 pos)
                        MapNode n = vmanip->getNodeNoEx(pos + p);
                        if (!surface_found && n.getContent() != CONTENT_AIR) {
                                mmpixel->height = y;
-                               mmpixel->id = n.getContent();
+                               mmpixel->n = n;
                                surface_found = true;
                        } else if (n.getContent() == CONTENT_AIR) {
                                air_count++;
@@ -625,7 +636,7 @@ void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, v3s16 pos)
                }
 
                if (!surface_found)
-                       mmpixel->id = CONTENT_AIR;
+                       mmpixel->n = MapNode(CONTENT_AIR);
 
                mmpixel->air_count = air_count;
        }
index 743b2bff2fcec4562d2652d7f209a9a05405af25..81ed0e49f202543aa3daa19d1557a31ee7d02c82 100644 (file)
@@ -52,10 +52,10 @@ struct MinimapModeDef {
 };
 
 struct MinimapPixel {
-       u16 id;
+       //! The topmost node that the minimap displays.
+       MapNode n;
        u16 height;
        u16 air_count;
-       u16 light;
 };
 
 struct MinimapMapblock {
index 23c8a665b10133e40747c63eb6572f70c0456cfb..a511d169b7e686ae684676279441b354534bef83 100644 (file)
@@ -143,9 +143,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
                serialization of TileAnimation params changed
                TAT_SHEET_2D
                Removed client-sided chat perdiction
+       PROTOCOL VERSION 30:
+               New ContentFeatures serialization version
+               Add node and tile color and palette
 */
 
-#define LATEST_PROTOCOL_VERSION 29
+#define LATEST_PROTOCOL_VERSION 30
 
 // Server's supported network protocol range
 #define SERVER_PROTOCOL_VERSION_MIN 13
index a4af26e87ade414818d20e3f14dd7127fa3092cb..98f795c7a79c9129ba9abe4687f7fc3b75fb9dc2 100644 (file)
@@ -189,7 +189,9 @@ void NodeBox::deSerialize(std::istream &is)
 
 void TileDef::serialize(std::ostream &os, u16 protocol_version) const
 {
-       if (protocol_version >= 29)
+       if (protocol_version >= 30)
+               writeU8(os, 4);
+       else if (protocol_version >= 29)
                writeU8(os, 3);
        else if (protocol_version >= 26)
                writeU8(os, 2);
@@ -205,6 +207,14 @@ void TileDef::serialize(std::ostream &os, u16 protocol_version) const
                writeU8(os, tileable_horizontal);
                writeU8(os, tileable_vertical);
        }
+       if (protocol_version >= 30) {
+               writeU8(os, has_color);
+               if (has_color) {
+                       writeU8(os, color.getRed());
+                       writeU8(os, color.getGreen());
+                       writeU8(os, color.getBlue());
+               }
+       }
 }
 
 void TileDef::deSerialize(std::istream &is, const u8 contenfeatures_version, const NodeDrawType drawtype)
@@ -218,6 +228,14 @@ void TileDef::deSerialize(std::istream &is, const u8 contenfeatures_version, con
                tileable_horizontal = readU8(is);
                tileable_vertical = readU8(is);
        }
+       if (version >= 4) {
+               has_color = readU8(is);
+               if (has_color) {
+                       color.setRed(readU8(is));
+                       color.setGreen(readU8(is));
+                       color.setBlue(readU8(is));
+               }
+       }
 
        if ((contenfeatures_version < 8) &&
                ((drawtype == NDT_MESH) ||
@@ -351,172 +369,222 @@ void ContentFeatures::reset()
        connects_to.clear();
        connects_to_ids.clear();
        connect_sides = 0;
+       color = video::SColor(0xFFFFFFFF);
+       palette_name = "";
+       palette = NULL;
 }
 
 void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
 {
-       if(protocol_version < 24){
+       if (protocol_version < 30) {
                serializeOld(os, protocol_version);
                return;
        }
 
-       writeU8(os, protocol_version < 27 ? 7 : 8);
+       // version
+       writeU8(os, 9);
 
-       os<<serializeString(name);
+       // general
+       os << serializeString(name);
        writeU16(os, groups.size());
-       for(ItemGroupList::const_iterator
-                       i = groups.begin(); i != groups.end(); ++i){
-               os<<serializeString(i->first);
+       for (ItemGroupList::const_iterator i = groups.begin(); i != groups.end();
+               ++i) {
+               os << serializeString(i->first);
                writeS16(os, i->second);
        }
+       writeU8(os, param_type);
+       writeU8(os, param_type_2);
+
+       // visual
        writeU8(os, drawtype);
+       os << serializeString(mesh);
        writeF1000(os, visual_scale);
        writeU8(os, 6);
-       for(u32 i = 0; i < 6; i++)
+       for (u32 i = 0; i < 6; i++)
                tiledef[i].serialize(os, protocol_version);
        writeU8(os, CF_SPECIAL_COUNT);
-       for(u32 i = 0; i < CF_SPECIAL_COUNT; i++){
+       for (u32 i = 0; i < CF_SPECIAL_COUNT; i++) {
                tiledef_special[i].serialize(os, protocol_version);
        }
        writeU8(os, alpha);
+       writeU8(os, color.getRed());
+       writeU8(os, color.getGreen());
+       writeU8(os, color.getBlue());
+       os << serializeString(palette_name);
+       writeU8(os, waving);
+       writeU8(os, connect_sides);
+       writeU16(os, connects_to_ids.size());
+       for (std::set<content_t>::const_iterator i = connects_to_ids.begin();
+               i != connects_to_ids.end(); ++i)
+               writeU16(os, *i);
        writeU8(os, post_effect_color.getAlpha());
        writeU8(os, post_effect_color.getRed());
        writeU8(os, post_effect_color.getGreen());
        writeU8(os, post_effect_color.getBlue());
-       writeU8(os, param_type);
-       if ((protocol_version < 28) && (param_type_2 == CPT2_MESHOPTIONS))
-               writeU8(os, CPT2_NONE);
-       else
-               writeU8(os, param_type_2);
-       writeU8(os, is_ground_content);
+       writeU8(os, leveled);
+
+       // lighting
        writeU8(os, light_propagates);
        writeU8(os, sunlight_propagates);
+       writeU8(os, light_source);
+
+       // map generation
+       writeU8(os, is_ground_content);
+
+       // interaction
        writeU8(os, walkable);
        writeU8(os, pointable);
        writeU8(os, diggable);
        writeU8(os, climbable);
        writeU8(os, buildable_to);
-       os<<serializeString(""); // legacy: used to be metadata_name
+       writeU8(os, rightclickable);
+       writeU32(os, damage_per_second);
+
+       // liquid
        writeU8(os, liquid_type);
-       os<<serializeString(liquid_alternative_flowing);
-       os<<serializeString(liquid_alternative_source);
+       os << serializeString(liquid_alternative_flowing);
+       os << serializeString(liquid_alternative_source);
        writeU8(os, liquid_viscosity);
        writeU8(os, liquid_renewable);
-       writeU8(os, light_source);
-       writeU32(os, damage_per_second);
+       writeU8(os, liquid_range);
+       writeU8(os, drowning);
+       writeU8(os, floodable);
+
+       // node boxes
        node_box.serialize(os, protocol_version);
        selection_box.serialize(os, protocol_version);
-       writeU8(os, legacy_facedir_simple);
-       writeU8(os, legacy_wallmounted);
+       collision_box.serialize(os, protocol_version);
+
+       // sound
        serializeSimpleSoundSpec(sound_footstep, os);
        serializeSimpleSoundSpec(sound_dig, os);
        serializeSimpleSoundSpec(sound_dug, os);
-       writeU8(os, rightclickable);
-       writeU8(os, drowning);
-       writeU8(os, leveled);
-       writeU8(os, liquid_range);
-       writeU8(os, waving);
-       // Stuff below should be moved to correct place in a version that otherwise changes
-       // the protocol version
-       os<<serializeString(mesh);
-       collision_box.serialize(os, protocol_version);
-       writeU8(os, floodable);
-       writeU16(os, connects_to_ids.size());
-       for (std::set<content_t>::const_iterator i = connects_to_ids.begin();
-                       i != connects_to_ids.end(); ++i)
-               writeU16(os, *i);
-       writeU8(os, connect_sides);
+
+       // legacy
+       writeU8(os, legacy_facedir_simple);
+       writeU8(os, legacy_wallmounted);
+}
+
+void ContentFeatures::correctAlpha()
+{
+       if (alpha == 0 || alpha == 255)
+               return;
+
+       for (u32 i = 0; i < 6; i++) {
+               std::stringstream s;
+               s << tiledef[i].name << "^[noalpha^[opacity:" << ((int)alpha);
+               tiledef[i].name = s.str();
+       }
+
+       for (u32 i = 0; i < CF_SPECIAL_COUNT; i++) {
+               std::stringstream s;
+               s << tiledef_special[i].name << "^[noalpha^[opacity:" << ((int)alpha);
+               tiledef_special[i].name = s.str();
+       }
 }
 
 void ContentFeatures::deSerialize(std::istream &is)
 {
+       // version detection
        int version = readU8(is);
-       if (version < 7) {
+       if (version < 9) {
                deSerializeOld(is, version);
                return;
-       } else if (version > 8) {
+       } else if (version > 9) {
                throw SerializationError("unsupported ContentFeatures version");
        }
 
+       // general
        name = deSerializeString(is);
        groups.clear();
        u32 groups_size = readU16(is);
-       for(u32 i = 0; i < groups_size; i++){
+       for (u32 i = 0; i < groups_size; i++) {
                std::string name = deSerializeString(is);
                int value = readS16(is);
                groups[name] = value;
        }
-       drawtype = (enum NodeDrawType)readU8(is);
+       param_type = (enum ContentParamType) readU8(is);
+       param_type_2 = (enum ContentParamType2) readU8(is);
 
+       // visual
+       drawtype = (enum NodeDrawType) readU8(is);
+       mesh = deSerializeString(is);
        visual_scale = readF1000(is);
-       if(readU8(is) != 6)
+       if (readU8(is) != 6)
                throw SerializationError("unsupported tile count");
-       for(u32 i = 0; i < 6; i++)
+       for (u32 i = 0; i < 6; i++)
                tiledef[i].deSerialize(is, version, drawtype);
-       if(readU8(is) != CF_SPECIAL_COUNT)
+       if (readU8(is) != CF_SPECIAL_COUNT)
                throw SerializationError("unsupported CF_SPECIAL_COUNT");
-       for(u32 i = 0; i < CF_SPECIAL_COUNT; i++)
+       for (u32 i = 0; i < CF_SPECIAL_COUNT; i++)
                tiledef_special[i].deSerialize(is, version, drawtype);
        alpha = readU8(is);
+       color.setRed(readU8(is));
+       color.setGreen(readU8(is));
+       color.setBlue(readU8(is));
+       palette_name = deSerializeString(is);
+       waving = readU8(is);
+       connect_sides = readU8(is);
+       u16 connects_to_size = readU16(is);
+       connects_to_ids.clear();
+       for (u16 i = 0; i < connects_to_size; i++)
+               connects_to_ids.insert(readU16(is));
        post_effect_color.setAlpha(readU8(is));
        post_effect_color.setRed(readU8(is));
        post_effect_color.setGreen(readU8(is));
        post_effect_color.setBlue(readU8(is));
-       param_type = (enum ContentParamType)readU8(is);
-       param_type_2 = (enum ContentParamType2)readU8(is);
-       is_ground_content = readU8(is);
+       leveled = readU8(is);
+
+       // lighting-related
        light_propagates = readU8(is);
        sunlight_propagates = readU8(is);
+       light_source = readU8(is);
+       light_source = MYMIN(light_source, LIGHT_MAX);
+
+       // map generation
+       is_ground_content = readU8(is);
+
+       // interaction
        walkable = readU8(is);
        pointable = readU8(is);
        diggable = readU8(is);
        climbable = readU8(is);
        buildable_to = readU8(is);
-       deSerializeString(is); // legacy: used to be metadata_name
-       liquid_type = (enum LiquidType)readU8(is);
+       rightclickable = readU8(is);
+       damage_per_second = readU32(is);
+
+       // liquid
+       liquid_type = (enum LiquidType) readU8(is);
        liquid_alternative_flowing = deSerializeString(is);
        liquid_alternative_source = deSerializeString(is);
        liquid_viscosity = readU8(is);
        liquid_renewable = readU8(is);
-       light_source = readU8(is);
-       light_source = MYMIN(light_source, LIGHT_MAX);
-       damage_per_second = readU32(is);
+       liquid_range = readU8(is);
+       drowning = readU8(is);
+       floodable = readU8(is);
+
+       // node boxes
        node_box.deSerialize(is);
        selection_box.deSerialize(is);
-       legacy_facedir_simple = readU8(is);
-       legacy_wallmounted = readU8(is);
+       collision_box.deSerialize(is);
+
+       // sounds
        deSerializeSimpleSoundSpec(sound_footstep, is);
        deSerializeSimpleSoundSpec(sound_dig, is);
        deSerializeSimpleSoundSpec(sound_dug, is);
-       rightclickable = readU8(is);
-       drowning = readU8(is);
-       leveled = readU8(is);
-       liquid_range = readU8(is);
-       waving = readU8(is);
-       // If you add anything here, insert it primarily inside the try-catch
-       // block to not need to increase the version.
-       try{
-               // Stuff below should be moved to correct place in a version that
-               // otherwise changes the protocol version
-       mesh = deSerializeString(is);
-       collision_box.deSerialize(is);
-       floodable = readU8(is);
-       u16 connects_to_size = readU16(is);
-       connects_to_ids.clear();
-       for (u16 i = 0; i < connects_to_size; i++)
-               connects_to_ids.insert(readU16(is));
-       connect_sides = readU8(is);
-       }catch(SerializationError &e) {};
+
+       // read legacy properties
+       legacy_facedir_simple = readU8(is);
+       legacy_wallmounted = readU8(is);
 }
 
 #ifndef SERVER
 void ContentFeatures::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile,
                TileDef *tiledef, u32 shader_id, bool use_normal_texture,
-               bool backface_culling, u8 alpha, u8 material_type)
+               bool backface_culling, u8 material_type)
 {
        tile->shader_id     = shader_id;
        tile->texture       = tsrc->getTextureForMesh(tiledef->name, &tile->texture_id);
-       tile->alpha         = alpha;
        tile->material_type = material_type;
 
        // Normal texture and shader flags texture
@@ -536,6 +604,13 @@ void ContentFeatures::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile,
        if (tiledef->tileable_vertical)
                tile->material_flags |= MATERIAL_FLAG_TILEABLE_VERTICAL;
 
+       // Color
+       tile->has_color = tiledef->has_color;
+       if (tiledef->has_color)
+               tile->color = tiledef->color;
+       else
+               tile->color = color;
+
        // Animation parameters
        int frame_count = 1;
        if (tile->material_flags & MATERIAL_FLAG_ANIMATION) {
@@ -681,6 +756,9 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
                        is_water_surface = true;
        }
 
+       // Vertex alpha is no longer supported, correct if necessary.
+       correctAlpha();
+
        u32 tile_shader[6];
        for (u16 j = 0; j < 6; j++) {
                tile_shader[j] = shdsrc->getShader("nodes_shader",
@@ -696,14 +774,14 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
        for (u16 j = 0; j < 6; j++) {
                fillTileAttribs(tsrc, &tiles[j], &tdef[j], tile_shader[j],
                        tsettings.use_normal_texture,
-                       tiledef[j].backface_culling, alpha, material_type);
+                       tiledef[j].backface_culling, material_type);
        }
 
        // Special tiles (fill in f->special_tiles[])
        for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) {
                fillTileAttribs(tsrc, &special_tiles[j], &tiledef_special[j],
                        tile_shader[j], tsettings.use_normal_texture,
-                       tiledef_special[j].backface_culling, alpha, material_type);
+                       tiledef_special[j].backface_culling, material_type);
        }
 
        if ((drawtype == NDT_MESH) && (mesh != "")) {
@@ -731,15 +809,19 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
        }
 
        //Cache 6dfacedir and wallmounted rotated clones of meshes
-       if (tsettings.enable_mesh_cache && mesh_ptr[0] && (param_type_2 == CPT2_FACEDIR)) {
+       if (tsettings.enable_mesh_cache && mesh_ptr[0] &&
+                       (param_type_2 == CPT2_FACEDIR
+                       || param_type_2 == CPT2_COLORED_FACEDIR)) {
                for (u16 j = 1; j < 24; j++) {
                        mesh_ptr[j] = cloneMesh(mesh_ptr[0]);
                        rotateMeshBy6dFacedir(mesh_ptr[j], j);
                        recalculateBoundingBox(mesh_ptr[j]);
                        meshmanip->recalculateNormals(mesh_ptr[j], true, false);
                }
-       } else if (tsettings.enable_mesh_cache && mesh_ptr[0] && (param_type_2 == CPT2_WALLMOUNTED)) {
-               static const u8 wm_to_6d[6] = {20, 0, 16+1, 12+3, 8, 4+2};
+       } else if (tsettings.enable_mesh_cache && mesh_ptr[0]
+                       && (param_type_2 == CPT2_WALLMOUNTED ||
+                       param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
+               static const u8 wm_to_6d[6] = { 20, 0, 16 + 1, 12 + 3, 8, 4 + 2 };
                for (u16 j = 1; j < 6; j++) {
                        mesh_ptr[j] = cloneMesh(mesh_ptr[0]);
                        rotateMeshBy6dFacedir(mesh_ptr[j], wm_to_6d[j]);
@@ -775,6 +857,9 @@ public:
        virtual void removeNode(const std::string &name);
        virtual void updateAliases(IItemDefManager *idef);
        virtual void applyTextureOverrides(const std::string &override_filepath);
+       //! Returns a palette or NULL if not found. Only on client.
+       std::vector<video::SColor> *getPalette(const ContentFeatures &f,
+               const IGameDef *gamedef);
        virtual void updateTextures(IGameDef *gamedef,
                void (*progress_cbk)(void *progress_args, u32 progress, u32 max_progress),
                void *progress_cbk_args);
@@ -823,6 +908,9 @@ private:
        // Next possibly free id
        content_t m_next_id;
 
+       // Maps image file names to loaded palettes.
+       UNORDERED_MAP<std::string, std::vector<video::SColor> > m_palettes;
+
        // NodeResolvers to callback once node registration has ended
        std::vector<NodeResolver *> m_pending_resolve_callbacks;
 
@@ -1062,7 +1150,8 @@ void getNodeBoxUnion(const NodeBox &nodebox, const ContentFeatures &features,
                        if (nodebox.type == NODEBOX_LEVELED) {
                                half_processed.MaxEdge.Y = +BS / 2;
                        }
-                       if (features.param_type_2 == CPT2_FACEDIR) {
+                       if (features.param_type_2 == CPT2_FACEDIR ||
+                                       features.param_type_2 == CPT2_COLORED_FACEDIR) {
                                // Get maximal coordinate
                                f32 coords[] = {
                                        fabsf(half_processed.MinEdge.X),
@@ -1309,6 +1398,78 @@ void CNodeDefManager::applyTextureOverrides(const std::string &override_filepath
        }
 }
 
+std::vector<video::SColor> *CNodeDefManager::getPalette(
+       const ContentFeatures &f, const IGameDef *gamedef)
+{
+#ifndef SERVER
+       // This works because colors always use the most significant bits
+       // of param2. If you add a new colored type which uses param2
+       // in a more advanced way, you should change this code, too.
+       u32 palette_pixels = 0;
+       switch (f.param_type_2) {
+               case CPT2_COLOR:
+                       palette_pixels = 256;
+                       break;
+               case CPT2_COLORED_FACEDIR:
+                       palette_pixels = 8;
+                       break;
+               case CPT2_COLORED_WALLMOUNTED:
+                       palette_pixels = 32;
+                       break;
+               default:
+                       return NULL;
+       }
+       // This many param2 values will have the same color
+       u32 step = 256 / palette_pixels;
+       const std::string &name = f.palette_name;
+       if (name == "")
+               return NULL;
+       Client *client = (Client *) gamedef;
+       ITextureSource *tsrc = client->tsrc();
+
+       UNORDERED_MAP<std::string, std::vector<video::SColor> >::iterator it =
+       m_palettes.find(name);
+       if (it == m_palettes.end()) {
+               // Create palette
+               if (!tsrc->isKnownSourceImage(name)) {
+                       warningstream << "CNodeDefManager::getPalette(): palette \"" << name
+                               << "\" could not be loaded." << std::endl;
+                       return NULL;
+               }
+               video::IImage *img = tsrc->generateImage(name);
+               std::vector<video::SColor> new_palette;
+               u32 w = img->getDimension().Width;
+               u32 h = img->getDimension().Height;
+               // Real area of the image
+               u32 area = h * w;
+               if (area != palette_pixels)
+                       warningstream << "CNodeDefManager::getPalette(): the "
+                               << "specified palette image \"" << name << "\" does not "
+                               << "contain exactly " << palette_pixels
+                               << " pixels." << std::endl;
+               if (area > palette_pixels)
+                       area = palette_pixels;
+               // 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);
+
+#endif
+       return NULL;
+}
+
 void CNodeDefManager::updateTextures(IGameDef *gamedef,
        void (*progress_callback)(void *progress_args, u32 progress, u32 max_progress),
        void *progress_callback_args)
@@ -1325,10 +1486,13 @@ void CNodeDefManager::updateTextures(IGameDef *gamedef,
        TextureSettings tsettings;
        tsettings.readSettings();
 
+       m_palettes.clear();
        u32 size = m_content_features.size();
 
        for (u32 i = 0; i < size; i++) {
-               m_content_features[i].updateTextures(tsrc, shdsrc, meshmanip, client, tsettings);
+               ContentFeatures *f = &(m_content_features[i]);
+               f->palette = getPalette(*f, gamedef);
+               f->updateTextures(tsrc, shdsrc, meshmanip, client, tsettings);
                progress_callback(progress_callback_args, i, size);
        }
 #endif
@@ -1429,6 +1593,19 @@ IWritableNodeDefManager *createNodeDefManager()
 //// Serialization of old ContentFeatures formats
 void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const
 {
+       u8 compatible_param_type_2 = param_type_2;
+       if ((protocol_version < 28)
+                       && (compatible_param_type_2 == CPT2_MESHOPTIONS))
+               compatible_param_type_2 = CPT2_NONE;
+       else if (protocol_version < 30) {
+               if (compatible_param_type_2 == CPT2_COLOR)
+                       compatible_param_type_2 = CPT2_NONE;
+               else if (compatible_param_type_2 == CPT2_COLORED_FACEDIR)
+                       compatible_param_type_2 = CPT2_FACEDIR;
+               else if (compatible_param_type_2 == CPT2_COLORED_WALLMOUNTED)
+                       compatible_param_type_2 = CPT2_WALLMOUNTED;
+       }
+
        if (protocol_version == 13)
        {
                writeU8(os, 5); // version
@@ -1454,7 +1631,7 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const
                writeU8(os, post_effect_color.getGreen());
                writeU8(os, post_effect_color.getBlue());
                writeU8(os, param_type);
-               writeU8(os, param_type_2);
+               writeU8(os, compatible_param_type_2);
                writeU8(os, is_ground_content);
                writeU8(os, light_propagates);
                writeU8(os, sunlight_propagates);
@@ -1483,9 +1660,9 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const
                os<<serializeString(name);
                writeU16(os, groups.size());
                for (ItemGroupList::const_iterator
-                       i = groups.begin(); i != groups.end(); ++i) {
-                               os<<serializeString(i->first);
-                               writeS16(os, i->second);
+                               i = groups.begin(); i != groups.end(); ++i) {
+                       os<<serializeString(i->first);
+                       writeS16(os, i->second);
                }
                writeU8(os, drawtype);
                writeF1000(os, visual_scale);
@@ -1502,7 +1679,7 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const
                writeU8(os, post_effect_color.getGreen());
                writeU8(os, post_effect_color.getBlue());
                writeU8(os, param_type);
-               writeU8(os, param_type_2);
+               writeU8(os, compatible_param_type_2);
                writeU8(os, is_ground_content);
                writeU8(os, light_propagates);
                writeU8(os, sunlight_propagates);
@@ -1530,6 +1707,68 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const
                writeU8(os, drowning);
                writeU8(os, leveled);
                writeU8(os, liquid_range);
+       }
+       else if(protocol_version >= 24 && protocol_version < 30) {
+               writeU8(os, protocol_version < 27 ? 7 : 8);
+
+               os << serializeString(name);
+               writeU16(os, groups.size());
+               for (ItemGroupList::const_iterator i = groups.begin();
+                               i != groups.end(); ++i) {
+                       os << serializeString(i->first);
+                       writeS16(os, i->second);
+               }
+               writeU8(os, drawtype);
+               writeF1000(os, visual_scale);
+               writeU8(os, 6);
+               for (u32 i = 0; i < 6; i++)
+                       tiledef[i].serialize(os, protocol_version);
+               writeU8(os, CF_SPECIAL_COUNT);
+               for (u32 i = 0; i < CF_SPECIAL_COUNT; i++)
+                       tiledef_special[i].serialize(os, protocol_version);
+               writeU8(os, alpha);
+               writeU8(os, post_effect_color.getAlpha());
+               writeU8(os, post_effect_color.getRed());
+               writeU8(os, post_effect_color.getGreen());
+               writeU8(os, post_effect_color.getBlue());
+               writeU8(os, param_type);
+               writeU8(os, compatible_param_type_2);
+               writeU8(os, is_ground_content);
+               writeU8(os, light_propagates);
+               writeU8(os, sunlight_propagates);
+               writeU8(os, walkable);
+               writeU8(os, pointable);
+               writeU8(os, diggable);
+               writeU8(os, climbable);
+               writeU8(os, buildable_to);
+               os << serializeString(""); // legacy: used to be metadata_name
+               writeU8(os, liquid_type);
+               os << serializeString(liquid_alternative_flowing);
+               os << serializeString(liquid_alternative_source);
+               writeU8(os, liquid_viscosity);
+               writeU8(os, liquid_renewable);
+               writeU8(os, light_source);
+               writeU32(os, damage_per_second);
+               node_box.serialize(os, protocol_version);
+               selection_box.serialize(os, protocol_version);
+               writeU8(os, legacy_facedir_simple);
+               writeU8(os, legacy_wallmounted);
+               serializeSimpleSoundSpec(sound_footstep, os);
+               serializeSimpleSoundSpec(sound_dig, os);
+               serializeSimpleSoundSpec(sound_dug, os);
+               writeU8(os, rightclickable);
+               writeU8(os, drowning);
+               writeU8(os, leveled);
+               writeU8(os, liquid_range);
+               writeU8(os, waving);
+               os << serializeString(mesh);
+               collision_box.serialize(os, protocol_version);
+               writeU8(os, floodable);
+               writeU16(os, connects_to_ids.size());
+               for (std::set<content_t>::const_iterator i = connects_to_ids.begin();
+                               i != connects_to_ids.end(); ++i)
+                       writeU16(os, *i);
+               writeU8(os, connect_sides);
        } else
                throw SerializationError("ContentFeatures::serialize(): "
                        "Unsupported version requested");
@@ -1642,7 +1881,73 @@ void ContentFeatures::deSerializeOld(std::istream &is, int version)
                drowning = readU8(is);
                leveled = readU8(is);
                liquid_range = readU8(is);
-       } else {
+       } else if (version == 7 || version == 8){
+               name = deSerializeString(is);
+               groups.clear();
+               u32 groups_size = readU16(is);
+               for (u32 i = 0; i < groups_size; i++) {
+                       std::string name = deSerializeString(is);
+                       int value = readS16(is);
+                       groups[name] = value;
+               }
+               drawtype = (enum NodeDrawType) readU8(is);
+
+               visual_scale = readF1000(is);
+               if (readU8(is) != 6)
+                       throw SerializationError("unsupported tile count");
+               for (u32 i = 0; i < 6; i++)
+                       tiledef[i].deSerialize(is, version, drawtype);
+               if (readU8(is) != CF_SPECIAL_COUNT)
+                       throw SerializationError("unsupported CF_SPECIAL_COUNT");
+               for (u32 i = 0; i < CF_SPECIAL_COUNT; i++)
+                       tiledef_special[i].deSerialize(is, version, drawtype);
+               alpha = readU8(is);
+               post_effect_color.setAlpha(readU8(is));
+               post_effect_color.setRed(readU8(is));
+               post_effect_color.setGreen(readU8(is));
+               post_effect_color.setBlue(readU8(is));
+               param_type = (enum ContentParamType) readU8(is);
+               param_type_2 = (enum ContentParamType2) readU8(is);
+               is_ground_content = readU8(is);
+               light_propagates = readU8(is);
+               sunlight_propagates = readU8(is);
+               walkable = readU8(is);
+               pointable = readU8(is);
+               diggable = readU8(is);
+               climbable = readU8(is);
+               buildable_to = readU8(is);
+               deSerializeString(is); // legacy: used to be metadata_name
+               liquid_type = (enum LiquidType) readU8(is);
+               liquid_alternative_flowing = deSerializeString(is);
+               liquid_alternative_source = deSerializeString(is);
+               liquid_viscosity = readU8(is);
+               liquid_renewable = readU8(is);
+               light_source = readU8(is);
+               light_source = MYMIN(light_source, LIGHT_MAX);
+               damage_per_second = readU32(is);
+               node_box.deSerialize(is);
+               selection_box.deSerialize(is);
+               legacy_facedir_simple = readU8(is);
+               legacy_wallmounted = readU8(is);
+               deSerializeSimpleSoundSpec(sound_footstep, is);
+               deSerializeSimpleSoundSpec(sound_dig, is);
+               deSerializeSimpleSoundSpec(sound_dug, is);
+               rightclickable = readU8(is);
+               drowning = readU8(is);
+               leveled = readU8(is);
+               liquid_range = readU8(is);
+               waving = readU8(is);
+               try {
+                       mesh = deSerializeString(is);
+                       collision_box.deSerialize(is);
+                       floodable = readU8(is);
+                       u16 connects_to_size = readU16(is);
+                       connects_to_ids.clear();
+                       for (u16 i = 0; i < connects_to_size; i++)
+                               connects_to_ids.insert(readU16(is));
+                       connect_sides = readU8(is);
+               } catch (SerializationError &e) {};
+       }else{
                throw SerializationError("unsupported ContentFeatures version");
        }
 }
@@ -1736,19 +2041,23 @@ bool CNodeDefManager::nodeboxConnects(MapNode from, MapNode to, u8 connect_face)
 
        // does to node declare usable faces?
        if (f2.connect_sides > 0) {
-               if ((f2.param_type_2 == CPT2_FACEDIR) && (connect_face >= 4)) {
-                       static const u8 rot[33 * 4] = {
-                               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                               4, 32, 16, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4 - back
-                               8, 4, 32, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 - right
-                               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                               16, 8, 4, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - front
-                               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                               32, 16, 8, 4 // 32 - left
-                       };
-                       return (f2.connect_sides & rot[(connect_face * 4) + to.param2]);
+               if ((f2.param_type_2 == CPT2_FACEDIR ||
+                               f2.param_type_2 == CPT2_COLORED_FACEDIR)
+                               && (connect_face >= 4)) {
+                       static const u8 rot[33 * 4] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                               0, 0, 0, 0, 4, 32, 16, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                               0, // 4 - back
+                               8, 4, 32, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                               0, // 8 - right
+                               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 8, 4, 32, 0,
+                               0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                               0, // 16 - front
+                               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                               0, 0, 0, 0, 0, 0, 32, 16, 8, 4 // 32 - left
+                               };
+                       return (f2.connect_sides
+                               & rot[(connect_face * 4) + (to.param2 & 0x1F)]);
                }
                return (f2.connect_sides & connect_face);
        }
index 183b95d87f52844166c45f8fa97faa62d80107dc..6275b41cebb1ed3132be6e52aa269d32d594ad4d 100644 (file)
@@ -68,7 +68,13 @@ enum ContentParamType2
        // 2D rotation for things like plants
        CPT2_DEGROTATE,
        // Mesh options for plants
-       CPT2_MESHOPTIONS
+       CPT2_MESHOPTIONS,
+       // Index for palette
+       CPT2_COLOR,
+       // 3 bits of palette index, then facedir
+       CPT2_COLORED_FACEDIR,
+       // 5 bits of palette index, then wallmounted
+       CPT2_COLORED_WALLMOUNTED
 };
 
 enum LiquidType
@@ -170,6 +176,11 @@ struct TileDef
        bool backface_culling; // Takes effect only in special cases
        bool tileable_horizontal;
        bool tileable_vertical;
+       //! If true, the tile has its own color.
+       bool has_color;
+       //! The color of the tile.
+       video::SColor color;
+
        struct TileAnimationParams animation;
 
        TileDef()
@@ -178,6 +189,8 @@ struct TileDef
                backface_culling = true;
                tileable_horizontal = true;
                tileable_vertical = true;
+               has_color = false;
+               color = video::SColor(0xFFFFFFFF);
                animation.type = TAT_NONE;
        }
 
@@ -191,7 +204,7 @@ struct ContentFeatures
 {
        /*
                Cached stuff
-       */
+        */
 #ifndef SERVER
        // 0     1     2     3     4     5
        // up    down  right left  back  front
@@ -211,12 +224,19 @@ struct ContentFeatures
 
        /*
                Actual data
-       */
+        */
+
+       // --- GENERAL PROPERTIES ---
 
        std::string name; // "" = undefined node
        ItemGroupList groups; // Same as in itemdef
+       // Type of MapNode::param1
+       ContentParamType param_type;
+       // Type of MapNode::param2
+       ContentParamType2 param_type_2;
+
+       // --- VISUAL PROPERTIES ---
 
-       // Visual definition
        enum NodeDrawType drawtype;
        std::string mesh;
 #ifndef SERVER
@@ -226,19 +246,38 @@ struct ContentFeatures
        float visual_scale; // Misc. scale parameter
        TileDef tiledef[6];
        TileDef tiledef_special[CF_SPECIAL_COUNT]; // eg. flowing liquid
+       // If 255, the node is opaque.
+       // Otherwise it uses texture alpha.
        u8 alpha;
-
+       // The color of the node.
+       video::SColor color;
+       std::string palette_name;
+       std::vector<video::SColor> *palette;
+       // Used for waving leaves/plants
+       u8 waving;
+       // for NDT_CONNECTED pairing
+       u8 connect_sides;
+       std::vector<std::string> connects_to;
+       std::set<content_t> connects_to_ids;
        // Post effect color, drawn when the camera is inside the node.
        video::SColor post_effect_color;
+       // Flowing liquid or snow, value = default level
+       u8 leveled;
+
+       // --- LIGHTING-RELATED ---
 
-       // Type of MapNode::param1
-       ContentParamType param_type;
-       // Type of MapNode::param2
-       ContentParamType2 param_type_2;
-       // True for all ground-like things like stone and mud, false for eg. trees
-       bool is_ground_content;
        bool light_propagates;
        bool sunlight_propagates;
+       // Amount of light the node emits
+       u8 light_source;
+
+       // --- MAP GENERATION ---
+
+       // True for all ground-like things like stone and mud, false for eg. trees
+       bool is_ground_content;
+
+       // --- INTERACTION PROPERTIES ---
+
        // This is used for collision detection.
        // Also for general solidness queries.
        bool walkable;
@@ -250,12 +289,12 @@ struct ContentFeatures
        bool climbable;
        // Player can build on these
        bool buildable_to;
-       // Liquids flow into and replace node
-       bool floodable;
        // Player cannot build to these (placement prediction disabled)
        bool rightclickable;
-       // Flowing liquid or snow, value = default level
-       u8 leveled;
+       u32 damage_per_second;
+
+       // --- LIQUID PROPERTIES ---
+
        // Whether the node is non-liquid, source liquid or flowing liquid
        enum LiquidType liquid_type;
        // If the content is liquid, this is the flowing version of the liquid.
@@ -271,29 +310,28 @@ struct ContentFeatures
        // Number of flowing liquids surrounding source
        u8 liquid_range;
        u8 drowning;
-       // Amount of light the node emits
-       u8 light_source;
-       u32 damage_per_second;
+       // Liquids flow into and replace node
+       bool floodable;
+
+       // --- NODEBOXES ---
+
        NodeBox node_box;
        NodeBox selection_box;
        NodeBox collision_box;
-       // Used for waving leaves/plants
-       u8 waving;
-       // Compatibility with old maps
-       // Set to true if paramtype used to be 'facedir_simple'
-       bool legacy_facedir_simple;
-       // Set to true if wall_mounted used to be set to true
-       bool legacy_wallmounted;
-       // for NDT_CONNECTED pairing
-       u8 connect_sides;
 
-       // Sound properties
+       // --- SOUND PROPERTIES ---
+
        SimpleSoundSpec sound_footstep;
        SimpleSoundSpec sound_dig;
        SimpleSoundSpec sound_dug;
 
-       std::vector<std::string> connects_to;
-       std::set<content_t> connects_to_ids;
+       // --- LEGACY ---
+
+       // Compatibility with old maps
+       // Set to true if paramtype used to be 'facedir_simple'
+       bool legacy_facedir_simple;
+       // Set to true if wall_mounted used to be set to true
+       bool legacy_wallmounted;
 
        /*
                Methods
@@ -306,6 +344,14 @@ struct ContentFeatures
        void deSerialize(std::istream &is);
        void serializeOld(std::ostream &os, u16 protocol_version) const;
        void deSerializeOld(std::istream &is, int version);
+       /*!
+        * Since vertex alpha is no lnger supported, this method
+        * adds instructions to the texture names to blend alpha there.
+        *
+        * tiledef, tiledef_special and alpha must be initialized
+        * before calling this.
+        */
+       void correctAlpha();
 
        /*
                Some handy methods
@@ -321,7 +367,7 @@ struct ContentFeatures
 #ifndef SERVER
        void fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, TileDef *tiledef,
                u32 shader_id, bool use_normal_texture, bool backface_culling,
-               u8 alpha, u8 material_type);
+               u8 material_type);
        void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc,
                scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings);
 #endif
index 5f17763e013373cf2ef4423facbb1122635496fe..e1f292fb60cf567cb7270a90d8a576fd160cf8c3 100644 (file)
@@ -56,7 +56,8 @@ Particle::Particle(
        v2f texpos,
        v2f texsize,
        const struct TileAnimationParams &anim,
-       u8 glow
+       u8 glow,
+       video::SColor color
 ):
        scene::ISceneNode(smgr->getRootSceneNode(), smgr)
 {
@@ -77,6 +78,10 @@ Particle::Particle(
        m_animation_frame = 0;
        m_animation_time = 0.0;
 
+       // Color
+       m_base_color = color;
+       m_color = color;
+
        // Particle related
        m_pos = pos;
        m_velocity = velocity;
@@ -183,12 +188,15 @@ void Particle::updateLight()
        else
                light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
 
-       m_light = decode_light(light + m_glow);
+       u8 m_light = decode_light(light + m_glow);
+       m_color.set(255,
+               m_light * m_base_color.getRed() / 255,
+               m_light * m_base_color.getGreen() / 255,
+               m_light * m_base_color.getBlue() / 255);
 }
 
 void Particle::updateVertices()
 {
-       video::SColor c(255, m_light, m_light, m_light);
        f32 tx0, tx1, ty0, ty1;
 
        if (m_animation.type != TAT_NONE) {
@@ -210,14 +218,14 @@ void Particle::updateVertices()
                ty1 = m_texpos.Y + m_texsize.Y;
        }
 
-       m_vertices[0] = video::S3DVertex(-m_size/2,-m_size/2,0, 0,0,0,
-                       c, tx0, ty1);
-       m_vertices[1] = video::S3DVertex(m_size/2,-m_size/2,0, 0,0,0,
-                       c, tx1, ty1);
-       m_vertices[2] = video::S3DVertex(m_size/2,m_size/2,0, 0,0,0,
-                       c, tx1, ty0);
-       m_vertices[3] = video::S3DVertex(-m_size/2,m_size/2,0, 0,0,0,
-                       c, tx0, ty0);
+       m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
+               0, 0, 0, 0, m_color, tx0, ty1);
+       m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
+               0, 0, 0, 0, m_color, tx1, ty1);
+       m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
+               0, 0, 0, 0, m_color, tx1, ty0);
+       m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
+               0, 0, 0, 0, m_color, tx0, ty0);
 
        v3s16 camera_offset = m_env->getCameraOffset();
        for(u16 i=0; i<4; i++)
@@ -589,35 +597,39 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
        }
 }
 
-void ParticleManager::addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
-               LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
+void ParticleManager::addDiggingParticles(IGameDef* gamedef,
+       scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
+       const MapNode &n, const ContentFeatures &f)
 {
        for (u16 j = 0; j < 32; j++) // set the amount of particles here
        {
-               addNodeParticle(gamedef, smgr, player, pos, tiles);
+               addNodeParticle(gamedef, smgr, player, pos, n, f);
        }
 }
 
-void ParticleManager::addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
-               LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
+void ParticleManager::addPunchingParticles(IGameDef* gamedef,
+       scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
+       const MapNode &n, const ContentFeatures &f)
 {
-       addNodeParticle(gamedef, smgr, player, pos, tiles);
+       addNodeParticle(gamedef, smgr, player, pos, n, f);
 }
 
-void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
-               LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
+void ParticleManager::addNodeParticle(IGameDef* gamedef,
+       scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
+       const MapNode &n, const ContentFeatures &f)
 {
        // Texture
        u8 texid = myrand_range(0, 5);
+       const TileSpec &tile = f.tiles[texid];
        video::ITexture *texture;
        struct TileAnimationParams anim;
        anim.type = TAT_NONE;
 
        // Only use first frame of animated texture
-       if (tiles[texid].material_flags & MATERIAL_FLAG_ANIMATION)
-               texture = tiles[texid].frames[0].texture;
+       if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
+               texture = tile.frames[0].texture;
        else
-               texture = tiles[texid].texture;
+               texture = tile.texture;
 
        float size = rand() % 64 / 512.;
        float visual_size = BS * size;
@@ -638,6 +650,12 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* s
                (f32) pos.Z + rand() %100 /200. - 0.25
        );
 
+       video::SColor color;
+       if (tile.has_color)
+               color = tile.color;
+       else
+               n.getColor(f, &color);
+
        Particle* toadd = new Particle(
                gamedef,
                smgr,
@@ -655,7 +673,8 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* s
                texpos,
                texsize,
                anim,
-               0);
+               0,
+               color);
 
        addParticle(toadd);
 }
index 5464e66722c9a41dded0e62af1aa472958041fe4..3177f2cfd2e87e07ccaabe93535d0ec6de342c11 100644 (file)
@@ -32,6 +32,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 struct ClientEvent;
 class ParticleManager;
 class ClientEnvironment;
+class MapNode;
+class ContentFeatures;
 
 class Particle : public scene::ISceneNode
 {
@@ -53,7 +55,8 @@ class Particle : public scene::ISceneNode
                v2f texpos,
                v2f texsize,
                const struct TileAnimationParams &anim,
-               u8 glow
+               u8 glow,
+               video::SColor color = video::SColor(0xFFFFFFFF)
        );
        ~Particle();
 
@@ -100,7 +103,10 @@ private:
        v3f m_acceleration;
        LocalPlayer *m_player;
        float m_size;
-       u8 m_light;
+       //! Color without lighting
+       video::SColor m_base_color;
+       //! Final rendered color
+       video::SColor m_color;
        bool m_collisiondetection;
        bool m_collision_removal;
        bool m_vertical;
@@ -184,13 +190,16 @@ public:
                        scene::ISceneManager* smgr, LocalPlayer *player);
 
        void addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
-               LocalPlayer *player, v3s16 pos, const TileSpec tiles[]);
+               LocalPlayer *player, v3s16 pos, const MapNode &n,
+               const ContentFeatures &f);
 
        void addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
-               LocalPlayer *player, v3s16 pos, const TileSpec tiles[]);
+               LocalPlayer *player, v3s16 pos, const MapNode &n,
+               const ContentFeatures &f);
 
        void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
-               LocalPlayer *player, v3s16 pos, const TileSpec tiles[]);
+               LocalPlayer *player, v3s16 pos, const MapNode &n,
+               const ContentFeatures &f);
 
 protected:
        void addParticle(Particle* toadd);
index 84af4583b9de7d2962d1b2d18eccfc88f18a6302..ebc951295a6bfbd29cb0edaadba30edfd8782e6f 100644 (file)
@@ -332,6 +332,10 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype)
                        L, index, "tileable_horizontal", default_tiling);
                tiledef.tileable_vertical = getboolfield_default(
                        L, index, "tileable_vertical", default_tiling);
+               // color = ...
+               lua_getfield(L, index, "color");
+               tiledef.has_color = read_color(L, -1, &tiledef.color);
+               lua_pop(L, 1);
                // animation = {}
                lua_getfield(L, index, "animation");
                tiledef.animation = read_animation_definition(L, -1);
@@ -450,6 +454,13 @@ ContentFeatures read_content_features(lua_State *L, int index)
        if (usealpha)
                f.alpha = 0;
 
+       // Read node color.
+       lua_getfield(L, index, "color");
+       read_color(L, -1, &f.color);
+       lua_pop(L, 1);
+
+       getstringfield(L, index, "palette", f.palette_name);
+
        /* Other stuff */
 
        lua_getfield(L, index, "post_effect_color");
@@ -461,6 +472,13 @@ ContentFeatures read_content_features(lua_State *L, int index)
        f.param_type_2 = (ContentParamType2)getenumfield(L, index, "paramtype2",
                        ScriptApiNode::es_ContentParamType2, CPT2_NONE);
 
+       if (f.palette_name != "" &&
+                       !(f.param_type_2 == CPT2_COLOR ||
+                       f.param_type_2 == CPT2_COLORED_FACEDIR ||
+                       f.param_type_2 == CPT2_COLORED_WALLMOUNTED))
+               warningstream << "Node " << f.name.c_str()
+                       << " has a palette, but not a suitable paramtype2." << std::endl;
+
        // Warn about some deprecated fields
        warn_if_field_exists(L, index, "wall_mounted",
                        "Deprecated; use paramtype2 = 'wallmounted'");
index 379ed773f3b69c692d2cddf086ffd73da8b4d021..23c8f43b97b908080a8b61076ebc4b3fa6327c0e 100644 (file)
@@ -59,6 +59,9 @@ struct EnumString ScriptApiNode::es_ContentParamType2[] =
                {CPT2_LEVELED, "leveled"},
                {CPT2_DEGROTATE, "degrotate"},
                {CPT2_MESHOPTIONS, "meshoptions"},
+               {CPT2_COLOR, "color"},
+               {CPT2_COLORED_FACEDIR, "colorfacedir"},
+               {CPT2_COLORED_WALLMOUNTED, "colorwallmounted"},
                {0, NULL},
        };
 
index c0ecf738dd545474d932e2739ddab660e4c01fb5..79485025b30675169d4fc034aecf1b21c1f6cb50 100644 (file)
@@ -543,7 +543,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype,
                        shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
                        break;
                case TILE_MATERIAL_LIQUID_TRANSPARENT:
-                       shaderinfo.base_material = video::EMT_TRANSPARENT_VERTEX_ALPHA;
+                       shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
                        break;
                case TILE_MATERIAL_LIQUID_OPAQUE:
                        shaderinfo.base_material = video::EMT_SOLID;
index c305238feee409dfcedf25ccd4b6dee4f9644cf9..089a67f338a8fad3b920d41229e5db9c0b4201d3 100644 (file)
@@ -318,6 +318,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client)
                u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL);
                m_material_type = shdrsrc->getShaderInfo(shader_id).material;
        }
+       m_colors.clear();
 
        // If wield_image is defined, it overrides everything else
        if (def.wield_image != "") {
@@ -358,28 +359,30 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client)
                        material_count = 6;
                }
                for (u32 i = 0; i < material_count; ++i) {
+                       const TileSpec *tile = &(f.tiles[i]);
                        video::SMaterial &material = m_meshnode->getMaterial(i);
                        material.setFlag(video::EMF_BACK_FACE_CULLING, true);
                        material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
                        material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
-                       bool animated = (f.tiles[i].animation_frame_count > 1);
+                       bool animated = (tile->animation_frame_count > 1);
                        if (animated) {
-                               FrameSpec animation_frame = f.tiles[i].frames[0];
+                               FrameSpec animation_frame = tile->frames[0];
                                material.setTexture(0, animation_frame.texture);
                        } else {
-                               material.setTexture(0, f.tiles[i].texture);
+                               material.setTexture(0, tile->texture);
                        }
+                       m_colors.push_back(tile->color);
                        material.MaterialType = m_material_type;
                        if (m_enable_shaders) {
-                               if (f.tiles[i].normal_texture) {
+                               if (tile->normal_texture) {
                                        if (animated) {
-                                               FrameSpec animation_frame = f.tiles[i].frames[0];
+                                               FrameSpec animation_frame = tile->frames[0];
                                                material.setTexture(1, animation_frame.normal_texture);
                                        } else {
-                                               material.setTexture(1, f.tiles[i].normal_texture);
+                                               material.setTexture(1, tile->normal_texture);
                                        }
                                }
-                               material.setTexture(2, f.tiles[i].flags_texture);
+                               material.setTexture(2, tile->flags_texture);
                        }
                }
                return;
@@ -393,11 +396,28 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client)
        changeToMesh(NULL);
 }
 
-void WieldMeshSceneNode::setColor(video::SColor color)
+void WieldMeshSceneNode::setColor(video::SColor c)
 {
        assert(!m_lighting);
-       setMeshColor(m_meshnode->getMesh(), color);
-       shadeMeshFaces(m_meshnode->getMesh());
+       scene::IMesh *mesh=m_meshnode->getMesh();
+       if (mesh == NULL)
+               return;
+
+       u8 red = c.getRed();
+       u8 green = c.getGreen();
+       u8 blue = c.getBlue();
+       u32 mc = mesh->getMeshBufferCount();
+       for (u32 j = 0; j < mc; j++) {
+               video::SColor bc(0xFFFFFFFF);
+               if (m_colors.size() > j)
+                       bc = m_colors[j];
+               video::SColor buffercolor(255,
+                       bc.getRed() * red / 255,
+                       bc.getGreen() * green / 255,
+                       bc.getBlue() * blue / 255);
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               colorizeMeshBuffer(buf, &buffercolor);
+       }
 }
 
 void WieldMeshSceneNode::render()
@@ -464,7 +484,6 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item)
                } else if (f.drawtype == NDT_PLANTLIKE) {
                        mesh = getExtrudedMesh(tsrc,
                                tsrc->getTextureName(f.tiles[0].texture_id));
-                       return mesh;
                } else if (f.drawtype == NDT_NORMAL || f.drawtype == NDT_ALLFACES
                        || f.drawtype == NDT_LIQUID || f.drawtype == NDT_FLOWINGLIQUID) {
                        mesh = cloneMesh(g_extrusion_mesh_cache->createCube());
@@ -477,8 +496,6 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item)
                        mesh = cloneMesh(mapblock_mesh.getMesh());
                        translateMesh(mesh, v3f(-BS, -BS, -BS));
                        scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
-                       rotateMeshXZby(mesh, -45);
-                       rotateMeshYZby(mesh, -30);
 
                        u32 mc = mesh->getMeshBufferCount();
                        for (u32 i = 0; i < mc; ++i) {
@@ -492,28 +509,29 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item)
                                material1.setTexture(3, material2.getTexture(3));
                                material1.MaterialType = material2.MaterialType;
                        }
-                       return mesh;
                }
 
-               shadeMeshFaces(mesh);
-               rotateMeshXZby(mesh, -45);
-               rotateMeshYZby(mesh, -30);
-
                u32 mc = mesh->getMeshBufferCount();
                for (u32 i = 0; i < mc; ++i) {
-                       video::SMaterial &material = mesh->getMeshBuffer(i)->getMaterial();
+                       const TileSpec *tile = &(f.tiles[i]);
+                       scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
+                       colorizeMeshBuffer(buf, &tile->color);
+                       video::SMaterial &material = buf->getMaterial();
                        material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
                        material.setFlag(video::EMF_BILINEAR_FILTER, false);
                        material.setFlag(video::EMF_TRILINEAR_FILTER, false);
                        material.setFlag(video::EMF_BACK_FACE_CULLING, true);
                        material.setFlag(video::EMF_LIGHTING, false);
-                       if (f.tiles[i].animation_frame_count > 1) {
-                               FrameSpec animation_frame = f.tiles[i].frames[0];
+                       if (tile->animation_frame_count > 1) {
+                               FrameSpec animation_frame = tile->frames[0];
                                material.setTexture(0, animation_frame.texture);
                        } else {
-                               material.setTexture(0, f.tiles[i].texture);
+                               material.setTexture(0, tile->texture);
                        }
                }
+
+               rotateMeshXZby(mesh, -45);
+               rotateMeshYZby(mesh, -30);
                return mesh;
        }
        return NULL;
index 0162c5e5ae487385350e7e0d85efe6898e9d4400..2e78232ae68daa322bf10e770ea455b3ae80b83f 100644 (file)
@@ -70,6 +70,11 @@ private:
        bool m_anisotropic_filter;
        bool m_bilinear_filter;
        bool m_trilinear_filter;
+       /*!
+        * Stores the colors of the mesh's mesh buffers.
+        * This does not include lighting.
+        */
+       std::vector<video::SColor> m_colors;
 
        // Bounding box culling is disabled for this type of scene node,
        // so this variable is just required so we can implement