FAQ  FAQ   Search  Search   Memberlist  Memberlist   Usergroups  Usergroups
Register  ::  Log in Log in to check your private messages

This forum is locked: you cannot post, reply to, or edit topics.  This topic is locked: you cannot edit posts or make replies.
 Rotation H,V Export error? « View previous topic :: View next topic » 
Author Message
PostPosted: Wed Aug 12, 2009 9:02 am    Post subject: Rotation H,V Export error? Reply with quote

Joined: 15 Jul 2009
Posts: 1


we use grome2

Mode : Tiling
U : 5.00
V : 5.00

Spin : 0.00
H : 90.00
V : 90.00

Export Txt format

color_texmap_scale = 0.001953 0.001953
color_texmap_horiz_rot = 0.000000
color_texmap_vert_rot = 1.570796
color_texmap_spin_rot = 1.570796

This value does right? Or does the error?

Back to top
View user's profile Send private message
PostPosted: Wed Aug 12, 2009 9:40 am    Post subject: Reply with quote

Joined: 12 Feb 2007
Posts: 1329

The values seem ok. When exported, the rotations are in radians (1.57 radians = approx 90 degrees). Also when you have vertical mapping (rotation of either H or V) the scale (tiling) is in world coordinates rather than in texture space.

Next is rather a technical explication for all this (if you are a programmer this information is very useful).

Here is the sTextureMapping as described in the Grome SDK:


struct sTextureMapping
   //! Texture scale.
   /*! If rotation.x and rotation.y are present (there is a custom orientation), the scaling is in world space
   (because when rotations are used the vertex coordinates positions are used to automatically generate the
   texture coordinates which are in world space). If rotation.x and rotation.y are zero (the mapping is horizontal),
   the scale represent the tiling in texture space (1 represents the entire terrain zone, 2 texture is tiled twice etc). */
   t_float3 scale;

   //! Texture rotation (X and Z are horizontal and vertical rotation, while Y is the spin).
   t_float3 rotation;

   //! Texture offset (in X and Y, Z is to be ignored).
   t_float3 offset;

   // =============================
   // Right handed precomputed values:

   //! Texture generation planes in case we are using texture mapping rotation.
   /*! This member contains 2 planes. Texture U coordinate is obtained
   by multiplying the vertex position with the first plane and V by multiplying it with the second.
   Not used when assigning the material (we are computing them from rotation).
   Used only when obtaining the texture channels. */
   t_float4 texgen_planes[2];

   //! Texture matrix (only when getting data, ignored when setting the texture mapping)
   /*! This matrix is obtained from the scaling and the spinning. */
   t_float4x4 tex_matrix;


So, basically, when you have vertical mapping, the texture coordinates will be projected on the terrain geometry.

The plane of projection is computed in this way:
- by default the plane is horizontal, on XZ plane
- if you put a rotation of 90 degrees on V you rotate the plane around OZ and bring it vertical, facing the OX axis. So the texture will be mapped on East - West axis.
- if you also rotate with 90 on U (so you have 90, 90) you also rotate around the OY world axis so the projection plane is facing OZ axis (North - South).
- So the vertical mapping functions by using 2 projected textures (one with 0, 90 on West and East direction, and the other with 90, 90 on North and South). If the two textures are the same and their are perfectly match, the entire terrain is surrounded on vertical axis by the same texture. Thus the texture stretching is avoided on high slopes. This perfect matching is created using the MaskGenerator tool as described in the Procedural Texturing video tutorial.

This is the explanation of vertical mapping. Now how does it gets internally computed (how are the texture coordinates computed based on input rotation and scales)?

When you DON'T have vertical mapping, the texture is horizontally applied, so 0..1 texture space is covering the entire terrain zone (patch). When you put 5 for scale, the texture is tiled between 0 and 5 and thus is repeated 5 times on terrain zone (as seen from above).

Now, when you have vertical mapping, you don't have a certain range to map the texture (the terrain height can be anything and doesn't correlate at all with the terrain zone edge size). So, the tiling is in world space. Basically a texture is mapped one-to-one with a height unit. So if you have a terrain that is 100 units high, the vertical texture with no scaling is tiled 100 times. If instead you put a scale of 0.01, the texture size is covering the entire height of the terrain (base of the texture is at the base of the height, while the topmost pixels are at the top of the terrain). If your terrain was 200 units, the internal tiling would be 0.02. So this is why you see a scaling so small (0.001953 in your case) so that the texture is brought from texture space to your actual height space. In the Grome UI, the scale the user introduced is automatically converted from one space to another, but in SDK you get the actual values you should use in your code.

You need to take into account this when computing the transformation matrix in your engine (in case you want to support vertical mapping on terrain). I will post shortly the correct code to compute texture matrix in your engine for Grome exported maps.

Adrian L.
Back to top
View user's profile Send private message
PostPosted: Wed Aug 12, 2009 9:51 am    Post subject: Reply with quote

Joined: 12 Feb 2007
Posts: 1329

Here is the code that Graphite is using to create the correct DirectX9 texture coordinates matrix to be used:


void cMaterialLayer::_ComputeTextureTransformation()
   // Take only spinning from the rotation (the horizontal and vertical rotations are not generated by this matrix).
   t_float3 spin_rot;
   spin_rot.x = 0.f;
   spin_rot.y = 0.f;
   spin_rot.z = _diffuse_texture_mapping.rotation.z;

   t_float3 scale;
   scale.x = _diffuse_texture_mapping.scale.x;
   // We need to invert scale since DirectX has top-down texture origin.
   scale.y = -_diffuse_texture_mapping.scale.y;
   scale.z = 1.f;

   t_float3 offset;
   offset.x = _diffuse_texture_mapping.offset.x;
   // We need to invert scale since DirectX has top-down texture origin.
   offset.y = -_diffuse_texture_mapping.offset.y;
   offset.z = 0.f;

   gCompMultMat3(spin_rot, offset, scale, _diffuse_texture_mapping.tex_matrix);

   bool left_handed = (g_root_module.GetCoordSysHandedness() == C_COORDSYS_LEFTHAND);

      // For right handed we need to move the spin origin in the opposite OZ corner by first subtracting
      // 1 from second component. Because spin is applied first (in gCompMultMat3) we just need to
      // first multiply with translation with -1 on OY matrix. This multiplication on the left of our texture
      // matrix is translated into this:
      _diffuse_texture_mapping.tex_matrix.m[3][0] -= _diffuse_texture_mapping.tex_matrix.m[1][0];
      _diffuse_texture_mapping.tex_matrix.m[3][1] -= _diffuse_texture_mapping.tex_matrix.m[1][1];
      _diffuse_texture_mapping.tex_matrix.m[3][2] -= _diffuse_texture_mapping.tex_matrix.m[1][2];
      // Then we need to put back the translation by multiplying (at the right) with +1 translation which
      // translates into a simple addition:
      _diffuse_texture_mapping.tex_matrix.m[3][1] += 1.f;

   if(_zone && (_diffuse_texture_mapping.rotation.x || _diffuse_texture_mapping.rotation.y)) // Custom vertical orientation (besides spinning).
      // Layer has custom orientation. We need to construct the texgen planes...

      t_float3 offset;
      float zone_size = _zone->GetTilesNo() * _zone->GetTileSize();

      // Bring the rotation pivot to the corner of the zone. We choose one Z direction or another depending on the handedness of the system.
         offset.x = - zone_size * 0.5f;
         offset.z = - zone_size * 0.5f;
         offset.x = - zone_size * 0.5f;
         offset.z =   zone_size * 0.5f; // Use inverse since we in right-handed system.
      offset.y = 0.f;

      t_float3 normal;
      if(gFAlmostEqual(_diffuse_texture_mapping.rotation.y, 0.f))
         // OY axis.
         normal.x = 0.f; normal.y = 1.f; normal.z = 0.f;
         if(gFAlmostEqual(_diffuse_texture_mapping.rotation.y, C_PI))
            // -OY axis.
            normal.x = 0.f; normal.y = -1.f; normal.z = 0.f;
            // Build the normal vector
            float sin_horz, cos_horz, sin_vert, cos_vert;
            gSinCosf(_diffuse_texture_mapping.rotation.x, &sin_horz, &cos_horz);
            gSinCosf(_diffuse_texture_mapping.rotation.y + C_PI / 2.f, &sin_vert, &cos_vert);

            normal.x = -sin_horz * cos_vert;
            normal.z = cos_vert * cos_horz;
            normal.y = sin_vert;

      // The right plane
      t_float3 oy_axis;
      oy_axis.x = oy_axis.z = 0.f; oy_axis.y = 1.f;
      t_float3 right_normal;   
         right_normal = gCrossProductLH(oy_axis, normal);
         right_normal = gCrossProduct(oy_axis, normal); // Do the normal cross product in right handed.
      if(gFAlmostEqual(gVectMagnitude(right_normal), 0))
         // OX axis.
         right_normal.x = 1.f; right_normal.y = 0.f; right_normal.z = 0.f;

      _diffuse_texture_mapping.texgen_planes[0].x = right_normal.x; 
      _diffuse_texture_mapping.texgen_planes[0].y = right_normal.y;
      _diffuse_texture_mapping.texgen_planes[0].z = right_normal.z;
      _diffuse_texture_mapping.texgen_planes[0].w = -gDotProduct(right_normal, offset);

      // The up plane
      t_float3 up_normal;
         up_normal = gCrossProductLH(normal, right_normal);
         // The up vector is computed with the same left-handed cross product since DirectX has a top-down image definition (compared with bottom up of Grome).
         up_normal = gCrossProductLH(normal, right_normal);
      _diffuse_texture_mapping.texgen_planes[1].x = up_normal.x; 
      _diffuse_texture_mapping.texgen_planes[1].y = up_normal.y;
      _diffuse_texture_mapping.texgen_planes[1].z = up_normal.z;
      _diffuse_texture_mapping.texgen_planes[1].w = -gDotProduct(up_normal, offset);
      _diffuse_texture_mapping.texgen_planes[0].x = 1.f;
      _diffuse_texture_mapping.texgen_planes[0].y = 0.f;
      _diffuse_texture_mapping.texgen_planes[0].z = 0.f;
      _diffuse_texture_mapping.texgen_planes[0].w = 0.f;

      _diffuse_texture_mapping.texgen_planes[1].x = 0.f;
      _diffuse_texture_mapping.texgen_planes[1].y = 1.f;
      _diffuse_texture_mapping.texgen_planes[1].z = 0.f;
      _diffuse_texture_mapping.texgen_planes[1].w = 0.f;

   if(_mask_texture_name == NULL) // Simple color layer.
      // Construct the texture matrix to bias and scale according to the border padding (used for fixed pipeline).
      // NOTE: this will override the texture matrix as computed above using scaling, offset and spinning.
         const float tex_width = (float)_diffuse_texture->Width();
            const float recip_width = 1.f / tex_width;
            _diffuse_texture_mapping.tex_matrix.m[0][0] = (tex_width - 2.f) * recip_width;
            _diffuse_texture_mapping.tex_matrix.m[1][1] = -_diffuse_texture_mapping.tex_matrix.m[0][0]; // Need to inverse the coordinate since DirectX is top-down UV oriented.
            _diffuse_texture_mapping.tex_matrix.m[2][2] = _diffuse_texture_mapping.tex_matrix.m[3][3] = 1.f;   
            _diffuse_texture_mapping.tex_matrix.m[3][0] = recip_width;

            _diffuse_texture_mapping.tex_matrix.m[3][1] = 1.f - recip_width; // (****)
            _diffuse_texture_mapping.tex_matrix.m[3][1] = 1.f; // (****)

         // (****)
         // For layers without mask we have mirror clamp. So we need to bring -1, 0 mapping
         // (since we've inverse it thru the matrix scale to be directx compliant) back to 0, 1 so the clamping
         // will not affect the texture. For the other layers (with mask) we don't need to do this
         // since we are using wrap mode (and we also have non unitary scaling, spinning etc in this matrix).

Mathematical functions that are used:


inline void gCompMultMat3(const t_float3& r, const t_float3& d, const t_float3& s, t_float4x4& o_m)
   const float sin_X = sinf(r.x);
   const float cos_X = cosf(r.x);
   const float sin_Y = sinf(r.y);
   const float cos_Y = cosf(r.y);
   const float sin_Z = sinf(r.z);
   const float cos_Z = cosf(r.z);
   const float sin_XY = sin_X*sin_Y;
   const float cos_sin_XY = cos_X*sin_Y;

   o_m.m[0][0] = cos_Y * cos_Z * s.x;
   o_m.m[0][1] = cos_Y * sin_Z * s.y;
   o_m.m[0][2] = - sin_Y * s.z;
   o_m.m[1][0] = (sin_XY * cos_Z - cos_X * sin_Z) * s.x;
   o_m.m[1][1] = (sin_XY * sin_Z + cos_X * cos_Z) * s.y;
   o_m.m[1][2] = sin_X * cos_Y * s.z;
   o_m.m[2][0] = (cos_sin_XY * cos_Z + sin_X * sin_Z) * s.x;
   o_m.m[2][1] = (cos_sin_XY * sin_Z - sin_X * cos_Z) * s.y;
   o_m.m[2][2] = cos_X * cos_Y * s.z;
   // translation
   o_m.m[3][0] = d.x; o_m.m[3][1] = d.y; o_m.m[3][2] = d.z;
   // homogenous
   o_m.m[0][3] = 0.f; o_m.m[1][3] = 0.f; o_m.m[2][3] = 0.f; o_m.m[3][3] = 1.f;

inline t_float3 gCrossProduct(const t_float3& v1, const t_float3& v2)
   t_float3 result;
   result.x = v1.y * v2.z  -  v1.z * v2.y;
   result.y = v1.z * v2.x  -  v1.x * v2.z;
   result.z = v1.x * v2.y  -  v1.y * v2.x;
   return result;

inline t_float3 gCrossProductLH(const t_float3& v1, const t_float3& v2)
   t_float3 result_lh = gCrossProduct(v1, v2);
   result_lh = -result_lh;
   return result_lh;

And then you need to combine the _diffuse_texture_mapping.tex_matrix and _diffuse_texture_mapping.texgen_planes in the vertex program.

A final texture matrix can be computed this way and applied to fixed pipeline (in case you are not using shaders):


      static t_float4x4 tex_gen_matrix, temp_matrix, tex_matrix;
      const t_float4x4 *inv_mat = g_root_module.GetRender()->GetInvWorldViewMatrix();

      tex_gen_matrix.m[0][0] = mapping->texgen_planes[0].x;
      tex_gen_matrix.m[1][0] = mapping->texgen_planes[0].y;
      tex_gen_matrix.m[2][0] = mapping->texgen_planes[0].z;
      tex_gen_matrix.m[3][0] = mapping->texgen_planes[0].w;
      tex_gen_matrix.m[0][1] = mapping->texgen_planes[1].x;
      tex_gen_matrix.m[1][1] = mapping->texgen_planes[1].y;
      tex_gen_matrix.m[2][1] = mapping->texgen_planes[1].z;
      tex_gen_matrix.m[3][1] = mapping->texgen_planes[1].w;

      // Multiply with the inverse of the world-view to use the object space vertex positions.
      // Then multiply tex gen planes matrix and with the final texture matrix.
      // We need to do all these multiplications on the CPU since the DirectX9 doesn't
      // support texgen planes and object space coordinate generations.
      gMultMat(temp_matrix, *inv_mat, tex_gen_matrix);
      gMultMat(tex_matrix, temp_matrix, mapping->tex_matrix);
      ((cDX9Render*)g_root_module.GetRender())->SetTransform(_dx_dev, D3DTS_TEXTURE0, (D3DMATRIX*)&tex_matrix);

      ((cDX9Render*)g_root_module.GetRender())->SetTextureStageState(_dx_dev, 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION);

The same can be done in OpenGL, but there you have right hand coordinate system and the texgen planes can be separately indicated (DirectX doesn't support texgen planes, only texture matrices, and also doesn't support object space coordinate generations, only eye space generation).

As seen from the above code, the vertical mapping is automatically generated by the GPU using the provided texture matrix applied to the vertex positions (D3DTSS_TCI_CAMERASPACEPOSITION mode in DirectX).

This information is very helpful to a programmer trying to implement the same texture scheme as Grome has (and actually many engines out there with custom terrain texture mapping).

Adrian L.
Back to top
View user's profile Send private message
Display posts from previous:   
This forum is locked: you cannot post, reply to, or edit topics.  This topic is locked: you cannot edit posts or make replies. Page 1 of 1

Jump to:  

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

Based on a template by Dustin Baccetti
Powered by phpBB © 2001, 2005 phpBB Group