GLTF meshes return inverted Y in llDetectedTouchST / llDetectedTouchUV
tracked
Aglaia Resident
According to the documentation, llDetectedTouchST and llDetectedTouchUV use the bottom-left corner of the surface as the origin (0,0). This behavior is correct when interacting with meshes uploaded using COLLADA.
However, when the same geometry is uploaded as a GLTF/GLB mesh, the origin appears to be the top-left corner instead. As a result, the returned Y coordinate is inverted, producing negative or flipped values compared to the expected result.
This issue is demonstrated in the attached video, which shows a single plane mesh containing a script that outputs llDetectedTouchST. The Y value becomes negative when touching the surface. The same behavior occurs with llDetectedTouchUV:
Impact
This is a significant problem because it causes these functions to behave differently depending on the mesh upload format. Scripts that rely on consistent UV/ST coordinates cannot work reliably across COLLADA and GLTF meshes.
Steps to Reproduce
- Create a simple mesh in a 3D application (for example, a single plane or a cube).
- Export the mesh as GLTF or GLB.
- Upload the mesh to Second Life.
- Add a script that outputs llDetectedTouchST(0) and/or llDetectedTouchUV(0) to local chat on touch.
- Touch the mesh and observe that the Y coordinate is inverted compared to a COLLADA-uploaded mesh.
Log In
Dan Linden
marked this post as
tracked
Dan Linden
Thank you for the report, Aglaia!
Issue tracked at https://github.com/secondlife/viewer/issues/5284. We have no estimate when it may be implemented. Please see future updates here.
Aglaia Resident
Dan Linden Thank you :-)
Maestro Linden
Here's a practical example of a script that can break from an invalid touchST range, adapted from one of the examples in https://wiki.secondlife.com/wiki/LlDetectedTouchST :
// with friendly permission of Supremius Maximus
// who made the texture used in this script
//
// click & hold the mouse while dragging across
// the face of the prim
default
{
touch(integer num_detected)
{
integer link = llDetectedLinkNumber(0);
integer face = llDetectedTouchFace(0);
vector touchST = llDetectedTouchST(0);
// ZERO_VECTOR (<0.0, 0.0, 0.0> ... the origin) is in the bottom left corner of the face
// touchST.x goes across the face from the left to the right
// touchST.y goes across the face from the bottom to the top
string uuid = "23badbe7-6d8c-639b-0131-bb321f8e9db5";
llSetLinkPrimitiveParamsFast(link, [
PRIM_TEXTURE, face, uuid, <1.0, 1.0, 0.0>, <1,1,0> - touchST, 0,
PRIM_FULLBRIGHT, ALL_SIDES, TRUE]);
}
}
If one saves the script in a basic prim box, then clicks and drags on the box, the texture's origin follows the cursor (which looks cool). However, if the same script is added to an imported
Box With Spaces.gltf
, the texture only scrolls horizontally, since the offsets vector supplied in PRIM_TEXTURE has a y-component outside the allowed [0,1] range and is apparently ignored as a result. Adapting the offset to be <1,0,0> - touchST
is a workaround for this particular mesh.Maestro Linden
marked this post as
under review
Maestro Linden
Hi Aglaia Resident, I can reproduce this issue with Second Life Release 7.2.3.19375695301 (64bit) on Second Life Server 2025-11-24.19651578074, but suspect this is actually a viewer bug. Here's what I did:
- Rez a default box and put this LSL script in it:
default
{
state_entry()
{
// this texture illustrates the object's UV mapping
llSetTexture("d06a0ebf-839d-dc2f-ddca-9760ec589429", ALL_SIDES);
}
touch_start(integer total_number)
{
integer face = llDetectedTouchFace(0);
vector touchUV = llDetectedTouchUV(0);
vector touchST = llDetectedTouchST(0);
if (face == TOUCH_INVALID_FACE)
llWhisper(PUBLIC_CHANNEL, "Sorry, your viewer doesn't support touched faces.");
else if (touchUV == TOUCH_INVALID_TEXCOORD)
llWhisper(PUBLIC_CHANNEL, "Sorry, the touch position upon the texture could not be determined.");
else
{
llSay(PUBLIC_CHANNEL, "llDetectedTouchUV(" + (string)touchUV + ")"
+ "\ntouchUV.x = " + (string)touchUV.x
+ "\ntouchUV.y = " + (string)touchUV.y);
llSay(PUBLIC_CHANNEL, "llDetectedTouchST(" + (string)touchST + ")"
+ "\ntouchST.x = " + (string)touchST.x
+ "\ntouchST.y = " + (string)touchST.y);
}
}
}
- Touch various parts of the box, and observe that the touch UV and ST values are in the [0,1] range, with the lower-left corner of the texture mapping to 0,0 and upper-right corner mapped to 1,1.
- Import a basic GLTF mesh. I used Box With Spaces.gltffrom https://github.com/KhronosGroup/glTF-Sample-Models/tree/main/2.0/Box%20With%20Spaces/glTF - it's a basic box with UV mapping
- Rez Box With Spacesin-world, and place the same LSL script in it
- Touch Box With Spacesa few times and note the range of output.
Expected results:
The range of touch UV and ST values of the GLTF-imported mesh in (5) should match what a basic prim box returned in (2) for the same rendered part of the texture.
Actual results:
The range of touch UV and ST values in the GLTF-imported mesh is a bit different:
- The horizontal 'U' and 'S' values match the basic prim cube (range [0,1], with left side being 0 and right side being 1)
- The vertical 'V' and 'T' values in the [-1,0] range - the bottom edge returns -1.0, while the top edge returns 0.0. Basically, there's a 1.0 offset in values.
Maestro Linden
The reason I think this is probably a viewer bug is that the touch data is coming from the viewer. For example, when the user touches the center of the lower-left quadrant of the texture on the GLTF mesh, the viewer sends this message:
OUT ObjectGrab [ZEROCODED]
# ID: 3560
[AgentData]
AgentID = [[AGENT_ID]]
SessionID = [[SESSION_ID]]
[ObjectData]
LocalID = 908656
GrabOffset = <0.0, 0.0, 0.0>
[SurfaceInfo]
UVCoord = <0.24838119745254517, -0.736335039138794, 0.0>
STCoord = <0.24838119745254517, -0.7363349795341492, 0.0>
FaceIndex = 0
Position = <67.54548645019531, 46.106849670410156, 23.999998092651367>
Normal = <-1.52587890625e-05, -1.52587890625e-05, 1.0>
Binormal = <0.0, 1.0, 1.52587890625e-05>
The UVCoord and STCoord values are faithfully reported by the LSL functions:
[3:23 PM] Box With Spaces: llDetectedTouchUV(<0.24838, -0.73634, 0.00000>)
touchUV.x = 0.248381
touchUV.y = -0.736335
[3:23 PM] Box With Spaces: llDetectedTouchST(<0.24838, -0.73634, 0.00000>)
touchST.x = 0.248381
touchST.y = -0.736335
Likewise, when the user touches the face of the prim cube, the
ObjectGrab
message sent by the viewer always contains UVCoord
and STCoord
vectors with values in the [0,1] range, which are reported as-is by llDetectedTouchUV and llDetectedTouchST.Another possibility is these GLTF models are actually being uploaded with invalid UV data, which happens to render properly, and that the viewer's
ObjectGrab
message is reflecting that bad UV data.Aglaia Resident
Maestro Linden Thank you :-)
WolfGang Senizen
A quick video of my reproduction, showing comparison to a regular cube, both have 0 rotation, 1,1 repeats and 0,0 offset for their texture faces.
Reproduced with my own mesh as well to check it wasn't something in Aglaia's tools.
And my script
default
{
touch_start(integer total_number)
{
vector st = llDetectedTouchST(0);
integer x = llFloor(st.x * 16);
integer y = llFloor(st.y * 16);
llOwnerSay((string)[x," - ",y]);
}
}
Shows clearly what Aglaia means, Y is going from
0 -> -1
top to bottom instead of 0 -> 1
bottom to top.