diff options
19 files changed, 645 insertions, 191 deletions
diff --git a/doc/testplans/pbr_terrain_appearance.md b/doc/testplans/pbr_terrain_appearance.md index f6d54029b5..11b501be3a 100644 --- a/doc/testplans/pbr_terrain_appearance.md +++ b/doc/testplans/pbr_terrain_appearance.md @@ -1,13 +1,14 @@ # PBR Terrain Appearance -## Tiling +## Tiling Without Texture Transforms + +This section assumes the PBR terrain of the current region and adjacent regions have the default texture transforms. The southwest corner of a region with PBR materials should exactly match up with the bottom left corner of the material texture(s). -If two adjacent regions have the same PBR terrain settings, then: +If two adjacent regions have the same PBR terrain settings, then there should not be seams between the two regions at their shared border. -- There should not be seams between the two regions at their shared border -- The ground should not suddenly slide beneath the avatar when moving between regions (except due to movement of the avatar, which is not covered by this test plan) +The ground should not suddenly slide beneath the avatar when moving between two PBR terrain regions (except due to movement of the avatar, which is not covered by this test plan) ## Feature Gating @@ -35,3 +36,17 @@ Availability of PBR textures varies by machine and graphics setting: ### PBR Alpha PBR terrain does not support materials with alpha blend or double-sided. In addition, the viewer does not make any guarantees about what will render behind the terrain if alpha is used. + +## PBR Terrain Texture Transforms + +Like PBR materials on prims, PBR terrain repeats are based on the [KHR\_texture\_transform](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform) spec, and thus should be expected to behave the same way. + +The southwest corner of a region, at z=0, is the UV origin for all texture coordinates of the whole region. Unless an offset is also applied, scale and rotation of the terrain texture transforms are relative to that point. + +When an avatar faces north and looks down at flat ground, the textures of the materials should appear to face upright, unless a rotation is applied. + +If triplanar mapping is enabled, and an avatar faces an axially-aligned wall, the textures of the materials should appear to face upright, unless a rotation is applied. + +Textures of materials should not appear mirrored. + +When triplanar mapping is enabled, rotations on the axially aligned walls should apply in the same direction as they would on flat ground. diff --git a/doc/testplans/pbr_terrain_composition.md b/doc/testplans/pbr_terrain_composition.md index aadd97a94b..731da90aba 100644 --- a/doc/testplans/pbr_terrain_composition.md +++ b/doc/testplans/pbr_terrain_composition.md @@ -12,6 +12,10 @@ All tests in this section assume the PBR terrain feature flag is enabled, and th ### Feature Availability +These features are related to UI, where the Region/Estate floater is opened to the terrain Tab. + +#### Feature: PBR Terrain + On the client, the advanced setting `RenderTerrainPBREnabled` is the PBR terrain feature flag. The PBR terrain feature flag should be set automatically when logging in/teleporting to a new region. @@ -29,6 +33,24 @@ When the PBR terrain feature flag is enabled: - The "PBR Metallic Roughness" checkbox should be visible - The user should be able to apply PBR terrain or textures to the region, depending on if the "PBR Metallic Roughness" checkbox is checked. +#### Feature: PBR Terrain Texture Transforms + +On the client, the advanced setting, `RenderTerrainPBRTransformsEnabled` is the PBR terrain texture transform flag. Generally, this feature should not be expected to work correctly unless the PBR terrain feature is also enabled. + +The PBR terrain texture transform flag should be set automatically when logging in/teleporting to a new region. + +- The flag should be enabled on regions where the PBR terrain texture transform feature is enabled +- Otherwise the flag should be disabled + +When the PBR terrain texture transform feature is enabled, the UI of the Terrain tab should be overhauled. Availability of features depends on the type of terrain. + +When "PBR Metallic Roughness" is checked: + +- There should be a way for the user to change the texture transforms for the terrain in the current region +- For each of the four swatches, the user can change the scale, offset, and rotation of that swatch. Nonuniform scale is allowed + +When "PBR Metallic Roughness" is unchecked, the controls for texture transforms should be hidden. + ### Current Composition Type When the Region/Estate floater is opened to the terrain Tab, the current terrain should be shown in the four swatches, and the "PBR Metallic Roughness" checkbox should be checked or unchecked accordingly. diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index 3b4224cbfd..9339e7998d 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -328,14 +328,16 @@ public: // bit 0 = alpha mode blend (1) or opaque (0) // bit 1 = rigged (1) or static (0) // bit 2 = unlit (1) or lit (0) + // bit 3 = single (0) or multi (1) uv coordinates struct GLTFVariant { constexpr static U8 ALPHA_BLEND = 1; constexpr static U8 RIGGED = 2; constexpr static U8 UNLIT = 4; + constexpr static U8 MULTI_UV = 8; }; - constexpr static U8 NUM_GLTF_VARIANTS = 8; + constexpr static U8 NUM_GLTF_VARIANTS = 16; std::vector<LLGLSLShader> mGLTFVariants; diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp index 51028e5667..cfefde3acc 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -1686,7 +1686,7 @@ void LLRender::flush() if (attribute_mask & LLVertexBuffer::MAP_TEXCOORD0) { - vb->setTexCoordData(mTexcoordsp.get()); + vb->setTexCoord0Data(mTexcoordsp.get()); } if (attribute_mask & LLVertexBuffer::MAP_COLOR) diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp index c3cb015556..634be615e6 100644 --- a/indra/llrender/llshadermgr.cpp +++ b/indra/llrender/llshadermgr.cpp @@ -1178,7 +1178,13 @@ void LLShaderMgr::initAttribsAndUniforms() mReservedUniforms.push_back("texture_base_color_transform"); // (GLTF) mReservedUniforms.push_back("texture_normal_transform"); // (GLTF) mReservedUniforms.push_back("texture_metallic_roughness_transform"); // (GLTF) + mReservedUniforms.push_back("texture_occlusion_transform"); // (GLTF) mReservedUniforms.push_back("texture_emissive_transform"); // (GLTF) + mReservedUniforms.push_back("base_color_texcoord"); // (GLTF) + mReservedUniforms.push_back("emissive_texcoord"); // (GLTF) + mReservedUniforms.push_back("normal_texcoord"); // (GLTF) + mReservedUniforms.push_back("metallic_roughness_texcoord"); // (GLTF) + mReservedUniforms.push_back("occlusion_texcoord"); // (GLTF) mReservedUniforms.push_back("terrain_texture_transforms"); // (GLTF) diff --git a/indra/llrender/llshadermgr.h b/indra/llrender/llshadermgr.h index 53e3d010db..c00aff3a9a 100644 --- a/indra/llrender/llshadermgr.h +++ b/indra/llrender/llshadermgr.h @@ -56,7 +56,13 @@ public: TEXTURE_BASE_COLOR_TRANSFORM, // "texture_base_color_transform" (GLTF) TEXTURE_NORMAL_TRANSFORM, // "texture_normal_transform" (GLTF) TEXTURE_METALLIC_ROUGHNESS_TRANSFORM, // "texture_metallic_roughness_transform" (GLTF) + TEXTURE_OCCLUSION_TRANSFORM, // "texture_occlusion_transform" (GLTF) TEXTURE_EMISSIVE_TRANSFORM, // "texture_emissive_transform" (GLTF) + BASE_COLOR_TEXCOORD, // "base_color_texcoord" (GLTF) + EMISSIVE_TEXCOORD, // "emissive_texcoord" (GLTF) + NORMAL_TEXCOORD, // "normal_texcoord" (GLTF) + METALLIC_ROUGHNESS_TEXCOORD, // "metallic_roughness_texcoord" (GLTF) + OCCLUSION_TEXCOORD, // "occlusion_texcoord" (GLTF) TERRAIN_TEXTURE_TRANSFORMS, // "terrain_texture_transforms" (GLTF) diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp index 5bace72ffd..f82ec30242 100644 --- a/indra/llrender/llvertexbuffer.cpp +++ b/indra/llrender/llvertexbuffer.cpp @@ -1633,7 +1633,7 @@ void LLVertexBuffer::setPositionData(const LLVector4a* data) flush_vbo(GL_ARRAY_BUFFER, 0, sizeof(LLVector4a) * getNumVerts()-1, (U8*) data, mMappedData); } -void LLVertexBuffer::setTexCoordData(const LLVector2* data) +void LLVertexBuffer::setTexCoord0Data(const LLVector2* data) { #if !LL_DARWIN llassert(sGLRenderBuffer == mGLBuffer); @@ -1641,6 +1641,14 @@ void LLVertexBuffer::setTexCoordData(const LLVector2* data) flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_TEXCOORD0], mOffsets[TYPE_TEXCOORD0] + sTypeSize[TYPE_TEXCOORD0] * getNumVerts() - 1, (U8*)data, mMappedData); } +void LLVertexBuffer::setTexCoord1Data(const LLVector2* data) +{ +#if !LL_DARWIN + llassert(sGLRenderBuffer == mGLBuffer); +#endif + flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_TEXCOORD1], mOffsets[TYPE_TEXCOORD1] + sTypeSize[TYPE_TEXCOORD1] * getNumVerts() - 1, (U8*)data, mMappedData); +} + void LLVertexBuffer::setColorData(const LLColor4U* data) { #if !LL_DARWIN diff --git a/indra/llrender/llvertexbuffer.h b/indra/llrender/llvertexbuffer.h index 94339191a4..66a7f2bf26 100644 --- a/indra/llrender/llvertexbuffer.h +++ b/indra/llrender/llvertexbuffer.h @@ -196,7 +196,8 @@ public: void setTangentData(const LLVector4a* data); void setWeight4Data(const LLVector4a* data); void setJointData(const U64* data); - void setTexCoordData(const LLVector2* data); + void setTexCoord0Data(const LLVector2* data); + void setTexCoord1Data(const LLVector2* data); void setColorData(const LLColor4U* data); void setIndexData(const U16* data); void setIndexData(const U32* data); diff --git a/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl index d71a3fad99..789c00259b 100644 --- a/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl +++ b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl @@ -37,8 +37,8 @@ uniform sampler2D emissiveMap; uniform vec3 emissiveColor; in vec3 vary_position; in vec4 vertex_color; -in vec2 base_color_texcoord; -in vec2 emissive_texcoord; +in vec2 base_color_uv; +in vec2 emissive_uv; uniform float minimum_alpha; void mirrorClip(vec3 pos); @@ -59,8 +59,9 @@ uniform float roughnessFactor; in vec3 vary_normal; in vec3 vary_tangent; flat in float vary_sign; -in vec2 normal_texcoord; -in vec2 metallic_roughness_texcoord; +in vec2 normal_uv; +in vec2 metallic_roughness_uv; +in vec2 occlusion_uv; #endif // ================================== @@ -164,7 +165,7 @@ void main() vec3 pos = vary_position; mirrorClip(pos); - vec4 basecolor = texture(diffuseMap, base_color_texcoord.xy).rgba; + vec4 basecolor = texture(diffuseMap, base_color_uv.xy).rgba; basecolor.rgb = srgb_to_linear(basecolor.rgb); basecolor *= vertex_color; @@ -174,7 +175,7 @@ void main() } vec3 emissive = emissiveColor; - emissive *= srgb_to_linear(texture(emissiveMap, emissive_texcoord.xy).rgb); + emissive *= srgb_to_linear(texture(emissiveMap, emissive_uv.xy).rgb); // ================================== // ================================== @@ -184,7 +185,7 @@ void main() // ================================== #ifndef UNLIT // from mikktspace.com - vec3 vNt = texture(normalMap, normal_texcoord.xy).xyz*2.0-1.0; + vec3 vNt = texture(normalMap, normal_uv.xy).xyz*2.0-1.0; float sign = vary_sign; vec3 vN = vary_normal; vec3 vT = vary_tangent.xyz; @@ -198,8 +199,8 @@ void main() // occlusion 1.0 // roughness 0.0 // metal 0.0 - vec3 orm = texture(metallicRoughnessMap, metallic_roughness_texcoord.xy).rgb; - orm.r = texture(occlusionMap, metallic_roughness_texcoord.xy).r; + vec3 orm = texture(metallicRoughnessMap, metallic_roughness_uv.xy).rgb; + orm.r = texture(occlusionMap, occlusion_uv.xy).r; orm.g *= roughnessFactor; orm.b *= metallicFactor; #endif diff --git a/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl index f123c29101..aac3dc917f 100644 --- a/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl +++ b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl @@ -33,33 +33,97 @@ uniform mat4 projection_matrix; uniform mat3 normal_matrix; uniform mat4 modelview_projection_matrix; #endif -uniform mat4 texture_matrix0; uniform vec4[2] texture_base_color_transform; uniform vec4[2] texture_normal_transform; uniform vec4[2] texture_metallic_roughness_transform; uniform vec4[2] texture_emissive_transform; +uniform vec4[2] texture_occlusion_transform; in vec3 position; in vec4 diffuse_color; in vec2 texcoord0; -out vec2 base_color_texcoord; -out vec2 emissive_texcoord; +out vec2 base_color_uv; +out vec2 emissive_uv; out vec4 vertex_color; out vec3 vary_position; #ifndef UNLIT in vec3 normal; in vec4 tangent; -out vec2 normal_texcoord; -out vec2 metallic_roughness_texcoord; +out vec2 normal_uv; +out vec2 metallic_roughness_uv; +out vec2 occlusion_uv; out vec3 vary_tangent; flat out float vary_sign; out vec3 vary_normal; -vec3 tangent_space_transform(vec4 vertex_tangent, vec3 vertex_normal, vec4[2] khr_gltf_transform, mat4 sl_animation_transform); #endif -vec2 texture_transform(vec2 vertex_texcoord, vec4[2] khr_gltf_transform, mat4 sl_animation_transform); +#ifdef MULTI_UV +in vec2 texcoord1; +uniform int base_color_texcoord; +uniform int emissive_texcoord; +#ifndef UNLIT +uniform int normal_texcoord; +uniform int metallic_roughness_texcoord; +uniform int occlusion_texcoord; +#endif +#endif + +vec2 gltf_texture_transform(vec2 texcoord, vec4[2] p) +{ + texcoord.y = 1.0 - texcoord.y; + + vec2 Scale = p[0].xy; + float Rotation = -p[0].z; + vec2 Offset = vec2(p[0].w, p[1].x); + + mat3 translation = mat3(1,0,0, 0,1,0, Offset.x, Offset.y, 1); + mat3 rotation = mat3( + cos(Rotation), sin(Rotation), 0, + -sin(Rotation), cos(Rotation), 0, + 0, 0, 1); + + mat3 scale = mat3(Scale.x,0,0, 0,Scale.y,0, 0,0,1); + + mat3 matrix = translation * rotation * scale; + + vec2 uvTransformed = ( matrix * vec3(texcoord.xy, 1) ).xy; + + uvTransformed.y = 1.0 - uvTransformed.y; + + return uvTransformed; +} + +#ifndef UNLIT +vec3 gltf_tangent_space_transform(vec4 vertex_tangent, vec3 vertex_normal, vec4[2] khr_gltf_transform) +{ //derived from tangent_space_transform in textureUtilV.glsl + vec2 weights = vec2(0, 1); + + // Convert to left-handed coordinate system + weights.y = -weights.y; + + // Apply KHR_texture_transform (rotation only) + float khr_rotation = khr_gltf_transform[0].z; + mat2 khr_rotation_mat = mat2( + cos(khr_rotation),-sin(khr_rotation), + sin(khr_rotation), cos(khr_rotation) + ); + weights = khr_rotation_mat * weights; + + // Convert back to right-handed coordinate system + weights.y = -weights.y; + + // Similar to the MikkTSpace-compatible method of extracting the binormal + // from the normal and tangent, as seen in the fragment shader + vec3 vertex_binormal = vertex_tangent.w * cross(vertex_normal, vertex_tangent.xyz); + + return (weights.x * vertex_binormal.xyz) + (weights.y * vertex_tangent.xyz); + + return vertex_tangent.xyz; +} +#endif + #ifdef ALPHA_BLEND @@ -136,14 +200,41 @@ void main() gl_Position = vert; #endif - base_color_texcoord = texture_transform(texcoord0, texture_base_color_transform, texture_matrix0); - emissive_texcoord = texture_transform(texcoord0, texture_emissive_transform, texture_matrix0); + vec2 bcuv; + vec2 emuv; + +#ifdef MULTI_UV + vec2 uv[2]; + uv[0] = texcoord0; + uv[1] = texcoord1; + + bcuv = uv[base_color_texcoord]; + emuv = uv[emissive_texcoord]; +#else + bcuv = texcoord0; + emuv = texcoord0; +#endif + + base_color_uv = gltf_texture_transform(bcuv, texture_base_color_transform); + emissive_uv = gltf_texture_transform(emuv, texture_emissive_transform); #ifndef UNLIT - normal_texcoord = texture_transform(texcoord0, texture_normal_transform, texture_matrix0); - metallic_roughness_texcoord = texture_transform(texcoord0, texture_metallic_roughness_transform, texture_matrix0); + vec2 normuv; + vec2 rmuv; + vec2 ouv; +#ifdef MULTI_UV + normuv = uv[normal_texcoord]; + rmuv = uv[metallic_roughness_texcoord]; + ouv = uv[occlusion_texcoord]; +#else + normuv = texcoord0; + rmuv = texcoord0; + ouv = texcoord0; +#endif + normal_uv = gltf_texture_transform(normuv, texture_normal_transform); + metallic_roughness_uv = gltf_texture_transform(rmuv, texture_metallic_roughness_transform); + occlusion_uv = gltf_texture_transform(ouv, texture_occlusion_transform); #endif - #ifndef UNLIT #ifdef HAS_SKIN @@ -155,7 +246,7 @@ void main() #endif n = normalize(n); - vary_tangent = normalize(tangent_space_transform(vec4(t, tangent.w), n, texture_normal_transform, texture_matrix0)); + vary_tangent = normalize(gltf_tangent_space_transform(vec4(t, tangent.w), n, texture_normal_transform)); vary_sign = tangent.w; vary_normal = n; #endif diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index a4efb25860..21be69aae2 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -45,7 +45,8 @@ namespace LL namespace GLTF { static std::unordered_set<std::string> ExtensionsSupported = { - "KHR_materials_unlit" + "KHR_materials_unlit", + "KHR_texture_transform" }; Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode) @@ -906,6 +907,25 @@ void Material::TextureInfo::serialize(object& dst) const { write(mIndex, "index", dst, INVALID_INDEX); write(mTexCoord, "texCoord", dst, 0); + write_extensions(dst, &mTextureTransform, "KHR_texture_transform"); +} + +S32 Material::TextureInfo::getTexCoord() const +{ + if (mTextureTransform.mPresent && mTextureTransform.mTexCoord != INVALID_INDEX) + { + return mTextureTransform.mTexCoord; + } + return mTexCoord; +} + +bool Material::isMultiUV() const +{ + return mPbrMetallicRoughness.mBaseColorTexture.getTexCoord() != 0 || + mPbrMetallicRoughness.mMetallicRoughnessTexture.getTexCoord() != 0 || + mNormalTexture.getTexCoord() != 0 || + mOcclusionTexture.getTexCoord() != 0 || + mEmissiveTexture.getTexCoord() != 0; } const Material::TextureInfo& Material::TextureInfo::operator=(const Value& src) @@ -914,6 +934,7 @@ const Material::TextureInfo& Material::TextureInfo::operator=(const Value& src) { copy(src, "index", mIndex); copy(src, "texCoord", mTexCoord); + copy_extensions(src, "KHR_texture_transform", &mTextureTransform); } return *this; @@ -931,17 +952,16 @@ bool Material::TextureInfo::operator!=(const Material::TextureInfo& rhs) const void Material::OcclusionTextureInfo::serialize(object& dst) const { - write(mIndex, "index", dst, INVALID_INDEX); - write(mTexCoord, "texCoord", dst, 0); + TextureInfo::serialize(dst); write(mStrength, "strength", dst, 1.f); } const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(const Value& src) { + TextureInfo::operator=(src); + if (src.is_object()) { - copy(src, "index", mIndex); - copy(src, "texCoord", mTexCoord); copy(src, "strength", mStrength); } @@ -950,13 +970,13 @@ const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=( void Material::NormalTextureInfo::serialize(object& dst) const { - write(mIndex, "index", dst, INVALID_INDEX); - write(mTexCoord, "texCoord", dst, 0); + TextureInfo::serialize(dst); write(mScale, "scale", dst, 1.f); } const Material::NormalTextureInfo& Material::NormalTextureInfo::operator=(const Value& src) { + TextureInfo::operator=(src); if (src.is_object()) { copy(src, "index", mIndex); @@ -1015,6 +1035,41 @@ void Material::Unlit::serialize(object& dst) const // no members and object has already been created, nothing to do } +void TextureTransform::getPacked(F32* packed) const +{ + packed[0] = mScale.x; + packed[1] = mScale.y; + packed[2] = mRotation; + packed[3] = mOffset.x; + packed[4] = mOffset.y; + + packed[5] = packed[6] = packed[7] = 0.f; +} + + +const TextureTransform& TextureTransform::operator=(const Value& src) +{ + mPresent = true; + if (src.is_object()) + { + copy(src, "offset", mOffset); + copy(src, "rotation", mRotation); + copy(src, "scale", mScale); + copy(src, "texCoord", mTexCoord); + } + + return *this; +} + +void TextureTransform::serialize(object& dst) const +{ + write(mOffset, "offset", dst, vec2(0.f, 0.f)); + write(mRotation, "rotation", dst, 0.f); + write(mScale, "scale", dst, vec2(1.f, 1.f)); + write(mTexCoord, "texCoord", dst, -1); +} + + void Material::serialize(object& dst) const { write(mName, "name", dst); diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 8f28e5905f..ea3f7d480a 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -57,6 +57,21 @@ namespace LL bool mPresent = false; }; + class TextureTransform : public Extension // KHR_texture_transform implementation + { + public: + vec2 mOffset = vec2(0.f, 0.f); + F32 mRotation = 0.f; + vec2 mScale = vec2(1.f, 1.f); + S32 mTexCoord = INVALID_INDEX; + + // get the texture transform as a packed array of floats + // dst MUST point to at least 8 floats + void getPacked(F32* dst) const; + + const TextureTransform& operator=(const Value& src); + void serialize(boost::json::object& dst) const; + }; class Material { @@ -82,9 +97,15 @@ namespace LL S32 mIndex = INVALID_INDEX; S32 mTexCoord = 0; + TextureTransform mTextureTransform; + bool operator==(const TextureInfo& rhs) const; bool operator!=(const TextureInfo& rhs) const; + // get the UV channel that should be used for sampling this texture + // returns mTextureTransform.mTexCoord if present and valid, otherwise mTexCoord + S32 getTexCoord() const; + const TextureInfo& operator=(const Value& src); void serialize(boost::json::object& dst) const; }; @@ -135,6 +156,8 @@ namespace LL bool mDoubleSided = false; Unlit mUnlit; + bool isMultiUV() const; + const Material& operator=(const Value& src); void serialize(boost::json::object& dst) const; }; diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index c26752a6b6..c1101818b7 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -590,8 +590,8 @@ namespace LL // Write all extensions to dst.extensions // Usage: // write_extensions(dst, - // "KHR_materials_unlit", mUnlit, - // "KHR_materials_pbrSpecularGlossiness", mPbrSpecularGlossiness); + // mUnlit, "KHR_materials_unlit", + // mPbrSpecularGlossiness, "KHR_materials_pbrSpecularGlossiness"); // returns true if any of the extensions are written template<class... Types> inline bool write_extensions(boost::json::object& dst, Types... args) @@ -816,6 +816,39 @@ namespace LL return true; } + // vec2 + template<> + inline bool copy(const Value& src, vec2& dst) + { + if (src.is_array()) + { + const boost::json::array& arr = src.as_array(); + if (arr.size() == 2) + { + std::error_code ec; + vec2 t; + t.x = arr[0].to_number<F32>(ec); if (ec) return false; + t.y = arr[1].to_number<F32>(ec); if (ec) return false; + + dst = t; + return true; + } + } + return false; + } + + template<> + inline bool write(const vec2& src, Value& dst) + { + dst = boost::json::array(); + boost::json::array& arr = dst.as_array(); + arr.resize(2); + arr[0] = src.x; + arr[1] = src.y; + + return true; + } + // bool template<> inline bool copy(const Value& src, bool& dst) diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index 197ffb68e8..dd37b5b4d0 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -42,13 +42,14 @@ using namespace boost::json; // Mesh data useful for Mikktspace tangent generation (and flat normal generation) struct MikktMesh { - std::vector<LLVector3> p; - std::vector<LLVector3> n; - std::vector<LLVector2> tc; - std::vector<LLVector4> w; - std::vector<LLVector4> t; - std::vector<LLColor4U> c; - std::vector<U64> j; + std::vector<LLVector3> p; //positions + std::vector<LLVector3> n; //normals + std::vector<LLVector4> t; //tangents + std::vector<LLVector2> tc0; //texcoords 0 + std::vector<LLVector2> tc1; //texcoords 1 + std::vector<LLColor4U> c; //colors + std::vector<LLVector4> w; //weights + std::vector<U64> j; //joints // initialize from src primitive and make an unrolled triangle list // returns false if the Primitive cannot be converted to a triangle list @@ -57,15 +58,28 @@ struct MikktMesh bool indexed = !prim->mIndexArray.empty(); auto vert_count = indexed ? prim->mIndexArray.size() : prim->mPositions.size(); - if (prim->mMode != Primitive::Mode::TRIANGLES) + U32 triangle_count = 0; + + if (prim->mMode == Primitive::Mode::TRIANGLE_STRIP || + prim->mMode == Primitive::Mode::TRIANGLE_FAN) + { + triangle_count = vert_count - 2; + } + else if (prim->mMode == Primitive::Mode::TRIANGLES) { - LL_WARNS("GLTF") << "Unsupported primitive mode for conversion to triangles: " << (S32) prim->mMode << LL_ENDL; + triangle_count = vert_count / 3; + } + else + { + LL_WARNS("GLTF") << "Unsupported primitive mode for conversion to triangles: " << (S32)prim->mMode << LL_ENDL; return false; } + vert_count = triangle_count * 3; + p.resize(vert_count); n.resize(vert_count); - tc.resize(vert_count); + tc0.resize(vert_count); c.resize(vert_count); bool has_normals = !prim->mNormals.empty(); @@ -78,6 +92,7 @@ struct MikktMesh { t.resize(vert_count); } + bool rigged = !prim->mWeights.empty(); if (rigged) { @@ -85,23 +100,69 @@ struct MikktMesh j.resize(vert_count); } - for (U32 i = 0; i < vert_count; ++i) + bool multi_uv = !prim->mTexCoords1.empty(); + if (multi_uv) { - U32 idx = indexed ? prim->mIndexArray[i] : i; + tc1.resize(vert_count); + } - p[i].set(prim->mPositions[idx].getF32ptr()); - tc[i].set(prim->mTexCoords[idx]); - c[i] = prim->mColors[idx]; + for (int tri_idx = 0; tri_idx < triangle_count; ++tri_idx) + { + U32 idx[3]; + + if (prim->mMode == Primitive::Mode::TRIANGLES) + { + idx[0] = tri_idx * 3; + idx[1] = tri_idx * 3 + 1; + idx[2] = tri_idx * 3 + 2; + } + else if (prim->mMode == Primitive::Mode::TRIANGLE_STRIP) + { + idx[0] = tri_idx; + idx[1] = tri_idx + 1; + idx[2] = tri_idx + 2; + + if (tri_idx % 2 != 0) + { + std::swap(idx[1], idx[2]); + } + } + else if (prim->mMode == Primitive::Mode::TRIANGLE_FAN) + { + idx[0] = 0; + idx[1] = tri_idx + 1; + idx[2] = tri_idx + 2; + } - if (has_normals) + if (indexed) { - n[i].set(prim->mNormals[idx].getF32ptr()); + idx[0] = prim->mIndexArray[idx[0]]; + idx[1] = prim->mIndexArray[idx[1]]; + idx[2] = prim->mIndexArray[idx[2]]; } - if (rigged) + for (U32 v = 0; v < 3; ++v) { - w[i].set(prim->mWeights[idx].getF32ptr()); - j[i] = prim->mJoints[idx]; + U32 i = tri_idx * 3 + v; + p[i].set(prim->mPositions[idx[v]].getF32ptr()); + tc0[i].set(prim->mTexCoords0[idx[v]]); + c[i] = prim->mColors[idx[v]]; + + if (multi_uv) + { + tc1[i].set(prim->mTexCoords1[idx[v]]); + } + + if (has_normals) + { + n[i].set(prim->mNormals[idx[v]].getF32ptr()); + } + + if (rigged) + { + w[i].set(prim->mWeights[idx[v]].getF32ptr()); + j[i] = prim->mJoints[idx[v]]; + } } } @@ -138,25 +199,34 @@ struct MikktMesh void write(Primitive* prim) const { //re-weld - meshopt_Stream mos[] = + std::vector<meshopt_Stream> mos = { { &p[0], sizeof(LLVector3), sizeof(LLVector3) }, { &n[0], sizeof(LLVector3), sizeof(LLVector3) }, { &t[0], sizeof(LLVector4), sizeof(LLVector4) }, - { &tc[0], sizeof(LLVector2), sizeof(LLVector2) }, - { &c[0], sizeof(LLColor4U), sizeof(LLColor4U) }, - { w.empty() ? nullptr : &w[0], sizeof(LLVector4), sizeof(LLVector4) }, - { j.empty() ? nullptr : &j[0], sizeof(U64), sizeof(U64) } + { &tc0[0], sizeof(LLVector2), sizeof(LLVector2) }, + { &c[0], sizeof(LLColor4U), sizeof(LLColor4U) } }; + if (!w.empty()) + { + mos.push_back({ &w[0], sizeof(LLVector4), sizeof(LLVector4) }); + mos.push_back({ &j[0], sizeof(U64), sizeof(U64) }); + } + + if (!tc1.empty()) + { + mos.push_back({ &tc1[0], sizeof(LLVector2), sizeof(LLVector2) }); + } + std::vector<U32> remap; remap.resize(p.size()); - U32 stream_count = w.empty() ? 5 : 7; + U32 stream_count = mos.size(); - size_t vert_count = meshopt_generateVertexRemapMulti(&remap[0], nullptr, p.size(), p.size(), mos, stream_count); + size_t vert_count = meshopt_generateVertexRemapMulti(&remap[0], nullptr, p.size(), p.size(), mos.data(), stream_count); - prim->mTexCoords.resize(vert_count); + prim->mTexCoords0.resize(vert_count); prim->mNormals.resize(vert_count); prim->mTangents.resize(vert_count); prim->mPositions.resize(vert_count); @@ -166,6 +236,10 @@ struct MikktMesh prim->mWeights.resize(vert_count); prim->mJoints.resize(vert_count); } + if (!tc1.empty()) + { + prim->mTexCoords1.resize(vert_count); + } prim->mIndexArray.resize(remap.size()); @@ -178,7 +252,7 @@ struct MikktMesh prim->mPositions[dst_idx].load3(p[src_idx].mV); prim->mNormals[dst_idx].load3(n[src_idx].mV); - prim->mTexCoords[dst_idx] = tc[src_idx]; + prim->mTexCoords0[dst_idx] = tc0[src_idx]; prim->mTangents[dst_idx].loadua(t[src_idx].mV); prim->mColors[dst_idx] = c[src_idx]; @@ -187,6 +261,11 @@ struct MikktMesh prim->mWeights[dst_idx].loadua(w[src_idx].mV); prim->mJoints[dst_idx] = j[src_idx]; } + + if (!tc1.empty()) + { + prim->mTexCoords1[dst_idx] = tc1[src_idx]; + } } prim->mGLMode = LLRender::TRIANGLES; @@ -210,8 +289,8 @@ struct MikktMesh mikk::float3 GetTexCoord(const uint32_t face_num, const uint32_t vert_num) { - F32* uv = tc[face_num * 3 + vert_num].mV; - return mikk::float3(uv[0], uv[1], 1.0f); + F32* uv = tc0[face_num * 3 + vert_num].mV; + return mikk::float3(uv[0], 1.f-uv[1], 1.0f); } mikk::float3 GetNormal(const uint32_t face_num, const uint32_t vert_num) @@ -228,6 +307,14 @@ struct MikktMesh }; +static void vertical_flip(std::vector<LLVector2>& texcoords) +{ + for (auto& tc : texcoords) + { + tc[1] = 1.f - tc[1]; + } +} + bool Primitive::prep(Asset& asset) { // allocate vertex buffer @@ -261,7 +348,11 @@ bool Primitive::prep(Asset& asset) } else if (attribName == "TEXCOORD_0") { - copy(asset, accessor, mTexCoords); + copy(asset, accessor, mTexCoords0); + } + else if (attribName == "TEXCOORD_1") + { + copy(asset, accessor, mTexCoords1); } else if (attribName == "JOINTS_0") { @@ -297,24 +388,28 @@ bool Primitive::prep(Asset& asset) mask |= LLVertexBuffer::MAP_JOINT; } - if (mTexCoords.empty()) + if (mTexCoords0.empty()) { - mTexCoords.resize(mPositions.size()); + mTexCoords0.resize(mPositions.size()); } - // TODO: support more than one texcoord set (or no texcoords) mask |= LLVertexBuffer::MAP_TEXCOORD0; + if (!mTexCoords1.empty()) + { + mask |= LLVertexBuffer::MAP_TEXCOORD1; + } + if (mColors.empty()) { mColors.resize(mPositions.size(), LLColor4U::white); } + mShaderVariant = 0; + // TODO: support colorless vertex buffers mask |= LLVertexBuffer::MAP_COLOR; - mShaderVariant = 0; - bool unlit = false; // bake material basecolor into color array @@ -332,6 +427,11 @@ bool Primitive::prep(Asset& asset) mShaderVariant |= LLGLSLShader::GLTFVariant::UNLIT; unlit = true; } + + if (material.isMultiUV()) + { + mShaderVariant |= LLGLSLShader::GLTFVariant::MULTI_UV; + } } if (mNormals.empty() && !unlit) @@ -437,16 +537,18 @@ bool Primitive::prep(Asset& asset) } // flip texcoord y, upload, then flip back (keep the off-spec data in vram only) - for (auto& tc : mTexCoords) - { - tc[1] = 1.f - tc[1]; - } - mVertexBuffer->setTexCoordData(mTexCoords.data()); - for (auto& tc : mTexCoords) + vertical_flip(mTexCoords0); + mVertexBuffer->setTexCoord0Data(mTexCoords0.data()); + vertical_flip(mTexCoords0); + + if (!mTexCoords1.empty()) { - tc[1] = 1.f - tc[1]; + vertical_flip(mTexCoords1); + mVertexBuffer->setTexCoord1Data(mTexCoords1.data()); + vertical_flip(mTexCoords1); } + if (!mIndexArray.empty()) { mVertexBuffer->setIndexData(mIndexArray.data()); @@ -456,10 +558,13 @@ bool Primitive::prep(Asset& asset) mVertexBuffer->unbind(); - Material& material = asset.mMaterials[mMaterial]; - if (material.mAlphaMode == Material::AlphaMode::BLEND) + if (mMaterial != INVALID_INDEX) { - mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND; + Material& material = asset.mMaterials[mMaterial]; + if (material.mAlphaMode == Material::AlphaMode::BLEND) + { + mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND; + } } return true; @@ -617,7 +722,7 @@ const LLVolumeTriangle* Primitive::lineSegmentIntersect(const LLVector4a& start, //create a proxy LLVolumeFace for the raycast LLVolumeFace face; face.mPositions = mPositions.data(); - face.mTexCoords = mTexCoords.data(); + face.mTexCoords = mTexCoords0.data(); face.mNormals = mNormals.data(); face.mTangents = mTangents.data(); face.mIndices = nullptr; // unreferenced diff --git a/indra/newview/gltf/primitive.h b/indra/newview/gltf/primitive.h index f9d7c63c65..7cc05cf831 100644 --- a/indra/newview/gltf/primitive.h +++ b/indra/newview/gltf/primitive.h @@ -58,7 +58,8 @@ namespace LL LLPointer<LLVertexBuffer> mVertexBuffer; // CPU copy of mesh data, keep these as LLVector types for compatibility with raycasting code - std::vector<LLVector2> mTexCoords; + std::vector<LLVector2> mTexCoords0; + std::vector<LLVector2> mTexCoords1; std::vector<LLVector4a> mNormals; std::vector<LLVector4a> mTangents; std::vector<LLVector4a> mPositions; diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index b948b2e2d6..e01aec0497 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -552,10 +552,16 @@ void GLTFSceneManager::render(bool opaque, bool rigged, bool unlit) void GLTFSceneManager::render(U8 variant) { - // for debugging, just render the whole scene as opaque - // by traversing the whole scenegraph - // Assumes camera transform is already set and - // appropriate shader is already boundd + // just render the whole scene by traversing the whole scenegraph + // Assumes camera transform is already set and appropriate shader is already bound. + // Eventually we'll want a smarter render pipe that has pre-sorted the scene graph + // into buckets by material and shader. + + // HACK -- implicitly render multi-uv variant + if (!(variant & LLGLSLShader::GLTFVariant::MULTI_UV)) + { + render((U8) (variant | LLGLSLShader::GLTFVariant::MULTI_UV)); + } gGL.matrixMode(LLRender::MM_MODELVIEW); @@ -666,11 +672,30 @@ static void bindTexture(Asset& asset, S32 uniform, Material::TextureInfo& info, { if (info.mIndex != INVALID_INDEX) { - LLViewerTexture* tex = asset.mImages[asset.mTextures[info.mIndex].mSource].mTexture; + Texture& texture = asset.mTextures[info.mIndex]; + + LLViewerTexture* tex = asset.mImages[texture.mSource].mTexture; if (tex) { tex->addTextureStats(2048.f * 2048.f); - LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, tex); + S32 channel = LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, tex); + + if (channel != -1 && texture.mSampler != -1) + { // set sampler state + Sampler& sampler = asset.mSamplers[texture.mSampler]; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sampler.mWrapS); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, sampler.mWrapT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, sampler.mMagFilter); + + // NOTE: do not set min filter. Always respect client preference for min filter + } + else + { + // set default sampler state + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } } else { @@ -713,10 +738,10 @@ void GLTFSceneManager::bind(Asset& asset, Material& material) bindTexture(asset, LLShaderMgr::DIFFUSE_MAP, material.mPbrMetallicRoughness.mBaseColorTexture, LLViewerFetchedTexture::sWhiteImagep); - F32 base_color_packed[8]; - //mTextureTransform[GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(base_color_packed); - LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(base_color_packed); - shader->uniform4fv(LLShaderMgr::TEXTURE_BASE_COLOR_TRANSFORM, 2, (F32*)base_color_packed); + F32 tf[8]; + material.mPbrMetallicRoughness.mBaseColorTexture.mTextureTransform.getPacked(tf); + shader->uniform4fv(LLShaderMgr::TEXTURE_BASE_COLOR_TRANSFORM, 2, tf); + shader->uniform1i(LLShaderMgr::BASE_COLOR_TEXCOORD, material.mPbrMetallicRoughness.mBaseColorTexture.getTexCoord()); if (!LLPipeline::sShadowRender) { @@ -731,20 +756,21 @@ void GLTFSceneManager::bind(Asset& asset, Material& material) shader->uniform1f(LLShaderMgr::METALLIC_FACTOR, material.mPbrMetallicRoughness.mMetallicFactor); shader->uniform3fv(LLShaderMgr::EMISSIVE_COLOR, 1, glm::value_ptr(material.mEmissiveFactor)); - F32 normal_packed[8]; - //mTextureTransform[GLTF_TEXTURE_INFO_NORMAL].getPacked(normal_packed); - LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL].getPacked(normal_packed); - shader->uniform4fv(LLShaderMgr::TEXTURE_NORMAL_TRANSFORM, 2, (F32*)normal_packed); + material.mNormalTexture.mTextureTransform.getPacked(tf); + shader->uniform4fv(LLShaderMgr::TEXTURE_NORMAL_TRANSFORM, 2, tf); + shader->uniform1i(LLShaderMgr::NORMAL_TEXCOORD, material.mNormalTexture.getTexCoord()); + + material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mTextureTransform.getPacked(tf); + shader->uniform4fv(LLShaderMgr::TEXTURE_METALLIC_ROUGHNESS_TRANSFORM, 2, tf); + shader->uniform1i(LLShaderMgr::METALLIC_ROUGHNESS_TEXCOORD, material.mPbrMetallicRoughness.mMetallicRoughnessTexture.getTexCoord()); - F32 metallic_roughness_packed[8]; - //mTextureTransform[GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].getPacked(metallic_roughness_packed); - LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].getPacked(metallic_roughness_packed); - shader->uniform4fv(LLShaderMgr::TEXTURE_METALLIC_ROUGHNESS_TRANSFORM, 2, (F32*)metallic_roughness_packed); + material.mOcclusionTexture.mTextureTransform.getPacked(tf); + shader->uniform4fv(LLShaderMgr::TEXTURE_OCCLUSION_TRANSFORM, 2, tf); + shader->uniform1i(LLShaderMgr::OCCLUSION_TEXCOORD, material.mOcclusionTexture.getTexCoord()); - F32 emissive_packed[8]; - //mTextureTransform[GLTF_TEXTURE_INFO_EMISSIVE].getPacked(emissive_packed); - LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE].getPacked(emissive_packed); - shader->uniform4fv(LLShaderMgr::TEXTURE_EMISSIVE_TRANSFORM, 2, (F32*)emissive_packed); + material.mEmissiveTexture.mTextureTransform.getPacked(tf); + shader->uniform4fv(LLShaderMgr::TEXTURE_EMISSIVE_TRANSFORM, 2, tf); + shader->uniform1i(LLShaderMgr::EMISSIVE_TEXCOORD, material.mEmissiveTexture.getTexCoord()); } } diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index d43d6fea37..5913e7ba6f 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -249,7 +249,7 @@ static bool make_rigged_variant(LLGLSLShader& shader, LLGLSLShader& riggedShader } -static bool make_gltf_variant(LLGLSLShader& shader, LLGLSLShader& variant, bool alpha_blend, bool rigged, bool unlit, bool use_sun_shadow) +static bool make_gltf_variant(LLGLSLShader& shader, LLGLSLShader& variant, bool alpha_blend, bool rigged, bool unlit, bool multi_uv, bool use_sun_shadow) { variant.mName = shader.mName.c_str(); variant.mFeatures = shader.mFeatures; @@ -271,6 +271,11 @@ static bool make_gltf_variant(LLGLSLShader& shader, LLGLSLShader& variant, bool variant.addPermutation("UNLIT", "1"); } + if (multi_uv) + { + variant.addPermutation("MULTI_UV", "1"); + } + if (alpha_blend) { variant.addPermutation("ALPHA_BLEND", "1"); @@ -317,8 +322,9 @@ static bool make_gltf_variants(LLGLSLShader& shader, bool use_sun_shadow) bool alpha_blend = i & LLGLSLShader::GLTFVariant::ALPHA_BLEND; bool rigged = i & LLGLSLShader::GLTFVariant::RIGGED; bool unlit = i & LLGLSLShader::GLTFVariant::UNLIT; + bool multi_uv = i & LLGLSLShader::GLTFVariant::MULTI_UV; - if (!make_gltf_variant(shader, shader.mGLTFVariants[i], alpha_blend, rigged, unlit, use_sun_shadow)) + if (!make_gltf_variant(shader, shader.mGLTFVariants[i], alpha_blend, rigged, unlit, multi_uv, use_sun_shadow)) { return false; } diff --git a/indra/newview/skins/default/xui/en/panel_region_terrain_texture_transform.xml b/indra/newview/skins/default/xui/en/panel_region_terrain_texture_transform.xml index cbcbe418cd..9a90700056 100644 --- a/indra/newview/skins/default/xui/en/panel_region_terrain_texture_transform.xml +++ b/indra/newview/skins/default/xui/en/panel_region_terrain_texture_transform.xml @@ -3,7 +3,6 @@ border="true" follows="top|left" height="460" - help_topic="panel_region_terrain_tab" label="Terrain" layout="topleft" left="0" @@ -225,13 +224,12 @@ left="0" name="terrain_tabs" tab_position="top" - tab_width="100" + tab_width="110" tab_padding_right="3" top_pad="0" width="700"> <panel border="true" - class="panel_settings_terrain_elevation" filename="panel_settings_terrain_elevation.xml" label="Elevation" layout="topleft" @@ -240,7 +238,6 @@ name="terrain_elevation_panel" /> <panel border="true" - class="panel_settings_terrain_transform" filename="panel_settings_terrain_transform.xml" label="Transforms" layout="topleft" @@ -256,8 +253,8 @@ height="20" label="Apply" layout="topleft" - left="353" + left="351" name="apply_btn" - top_delta="290" + top_delta="310" width="100" /> </panel> diff --git a/indra/newview/skins/default/xui/en/panel_settings_terrain_transform.xml b/indra/newview/skins/default/xui/en/panel_settings_terrain_transform.xml index 7052622813..0bf0d8cffc 100644 --- a/indra/newview/skins/default/xui/en/panel_settings_terrain_transform.xml +++ b/indra/newview/skins/default/xui/en/panel_settings_terrain_transform.xml @@ -10,6 +10,62 @@ <text type="string" length="1" + halign="center" + valign="center" + follows="left|top" + height="20" + layout="topleft" + name="terrain0_label" + left="48" + top_delta="0" + width="62"> + 1 + </text> + <text + type="string" + length="1" + halign="center" + valign="center" + follows="left|top" + height="20" + layout="topleft" + name="terrain1_label" + left_delta="110" + top_delta="0" + width="62"> + 2 + </text> + <text + type="string" + length="1" + halign="center" + valign="center" + follows="left|top" + height="20" + layout="topleft" + name="terrain2_label" + left_delta="110" + top_delta="0" + width="62"> + 3 + </text> + <text + type="string" + length="1" + halign="center" + valign="center" + follows="left|top" + height="20" + layout="topleft" + name="terrain3_label" + left_delta="110" + top_delta="0" + width="62"> + 4 + </text> + <text + type="string" + length="1" halign="left" valign="center" follows="left|top" @@ -17,19 +73,10 @@ layout="topleft" name="terrain0ScaleU_label" left="10" - top_pad="3" + top_pad="0" width="170"> Scale u </text> - <view_border - bevel_style="none" - follows="top|left" - height="0" - layout="topleft" - left="8" - top_pad="-2" - name="terrain0ScaleU_horizontal" - width="430" /> <spinner follows="left|top" height="19" @@ -37,7 +84,7 @@ label="Scale u" label_width="0" layout="topleft" - left="10" + left="46" min_val="-100" max_val="100" name="terrain0ScaleU" @@ -78,6 +125,15 @@ max_val="100" name="terrain3ScaleU" width="64" /> + <view_border + bevel_style="none" + follows="top|left" + height="0" + layout="topleft" + left="8" + top_pad="8" + name="terrainScaleU_horizontal" + width="432" /> <text type="string" length="1" @@ -88,19 +144,10 @@ layout="topleft" name="terrain0ScaleV_label" left="10" - top_pad="3" + top_pad="0" width="170"> Scale v </text> - <view_border - bevel_style="none" - follows="top|left" - height="0" - layout="topleft" - left="8" - top_pad="-2" - name="terrain0ScaleV_horizontal" - width="430" /> <spinner follows="left|top" height="19" @@ -108,7 +155,7 @@ label="Scale v" label_width="0" layout="topleft" - left="10" + left="46" min_val="-100" max_val="100" name="terrain0ScaleV" @@ -149,6 +196,15 @@ max_val="100" name="terrain3ScaleV" width="64" /> + <view_border + bevel_style="none" + follows="top|left" + height="0" + layout="topleft" + left="8" + top_pad="8" + name="terrainScaleV_horizontal" + width="432" /> <text type="string" length="1" @@ -159,19 +215,10 @@ layout="topleft" name="terrain0Rotation_label" left="10" - top_pad="3" + top_pad="0" width="170"> Rotation </text> - <view_border - bevel_style="none" - follows="top|left" - height="0" - layout="topleft" - left="8" - top_pad="-2" - name="terrain0Rotation_horizontal" - width="430" /> <spinner follows="left|top" height="19" @@ -179,7 +226,7 @@ label="Rotation" label_width="0" layout="topleft" - left="10" + left="46" min_val="-360" max_val="360" name="terrain0Rotation" @@ -192,8 +239,8 @@ label_width="0" layout="topleft" left_delta="110" - min_val="-100" - max_val="100" + min_val="-360" + max_val="360" name="terrain1Rotation" width="64" /> <spinner @@ -204,8 +251,8 @@ label_width="0" layout="topleft" left_delta="110" - min_val="-100" - max_val="100" + min_val="-360" + max_val="360" name="terrain2Rotation" width="64" /> <spinner @@ -216,10 +263,19 @@ label_width="0" layout="topleft" left_delta="110" - min_val="-100" - max_val="100" + min_val="-360" + max_val="360" name="terrain3Rotation" width="64" /> + <view_border + bevel_style="none" + follows="top|left" + height="0" + layout="topleft" + left="8" + top_pad="8" + name="terrainRotation_horizontal" + width="432" /> <text type="string" length="1" @@ -230,19 +286,10 @@ layout="topleft" name="terrain0OffsetU_label" left="10" - top_pad="3" + top_pad="0" width="170"> Offset y </text> - <view_border - bevel_style="none" - follows="top|left" - height="0" - layout="topleft" - left="8" - top_pad="-2" - name="terrain0OffsetU_horizontal" - width="430" /> <spinner follows="left|top" height="19" @@ -250,7 +297,7 @@ label="Offset u" label_width="0" layout="topleft" - left="10" + left="46" min_val="-999" max_val="999" name="terrain0OffsetU" @@ -263,8 +310,8 @@ label_width="0" layout="topleft" left_delta="110" - min_val="-100" - max_val="100" + min_val="-999" + max_val="999" name="terrain1OffsetU" width="64" /> <spinner @@ -275,8 +322,8 @@ label_width="0" layout="topleft" left_delta="110" - min_val="-100" - max_val="100" + min_val="-999" + max_val="999" name="terrain2OffsetU" width="64" /> <spinner @@ -287,10 +334,19 @@ label_width="0" layout="topleft" left_delta="110" - min_val="-100" - max_val="100" + min_val="-999" + max_val="999" name="terrain3OffsetU" width="64" /> + <view_border + bevel_style="none" + follows="top|left" + height="0" + layout="topleft" + left="8" + top_pad="8" + name="terrainOffsetU_horizontal" + width="432" /> <text type="string" length="1" @@ -301,19 +357,10 @@ layout="topleft" name="terrain0OffsetV_label" left="10" - top_pad="3" + top_pad="0" width="170"> Offset v </text> - <view_border - bevel_style="none" - follows="top|left" - height="0" - layout="topleft" - left="8" - top_pad="-2" - name="terrain0OffsetV_horizontal" - width="430" /> <spinner follows="left|top" height="19" @@ -321,7 +368,7 @@ label="Offset v" label_width="0" layout="topleft" - left="10" + left="46" min_val="-999" max_val="999" name="terrain0OffsetV" @@ -334,8 +381,8 @@ label_width="0" layout="topleft" left_delta="110" - min_val="-100" - max_val="100" + min_val="-999" + max_val="999" name="terrain1OffsetV" width="64" /> <spinner @@ -346,8 +393,8 @@ label_width="0" layout="topleft" left_delta="110" - min_val="-100" - max_val="100" + min_val="-999" + max_val="999" name="terrain2OffsetV" width="64" /> <spinner @@ -358,8 +405,17 @@ label_width="0" layout="topleft" left_delta="110" - min_val="-100" - max_val="100" + min_val="-999" + max_val="999" name="terrain3OffsetV" width="64" /> + <view_border + bevel_style="none" + follows="top|left" + height="0" + layout="topleft" + left="8" + top_pad="8" + name="terrainOffsetV_horizontal" + width="432" /> </panel> |