// SPDX-License-Identifier: MIT // Copyright (c) 2026 Incoprea // Track a rotary drag across the angle seam // Not compiled or tested in-world // Setup: Use a spare ordinary cube owned by you. Press away from center, drag around it, then release. Surface mapping can differ on meshes; test one face first. // Stop / restore: Disable/remove; these controllers report privately and do not alter scene properties. Reset clears gesture/session state. // Notes: Crossings use shortest per-sample angular differences; extremely fast motion beyond180 degrees per event is ambiguous. // License: see LICENSE in this distribution. // Original Incoprea. MIT. Owner input only. integer active; float lastAngle; float accumulated; float nextReport; sample() { vector uv=llDetectedTouchST(0); if(!usable(uv))return; vector p=uv-<0.5,0.5,0.0>; if(llVecMag(p)<0.08)return; float angle=llAtan2(p.y,p.x); if(!active) { lastAngle=angle; active=TRUE; return; } float delta=angle-lastAngle; while(delta>PI)delta-=TWO_PI; while(delta< -PI)delta+=TWO_PI; accumulated+=delta; lastAngle=angle; } integer ownerTouch() { return llDetectedKey(0)==llGetOwner(); } integer usable(vector uv) { return llDetectedTouchFace(0)>=0&&uv!=TOUCH_INVALID_TEXCOORD; } default { touch_start(integer n) { if(!ownerTouch())return; active=FALSE; accumulated=0.0; sample(); nextReport=llGetTime()+0.25; } touch(integer n) { if(!ownerTouch())return; sample(); if(llGetTime()>=nextReport) { nextReport=llGetTime()+0.25; llOwnerSay("Accumulated degrees="+(string)(accumulated*RAD_TO_DEG)); } } touch_end(integer n) { if(!ownerTouch())return; sample(); llOwnerSay("Final degrees="+(string)(accumulated*RAD_TO_DEG)); active=FALSE; } changed(integer flags) { if(flags&CHANGED_OWNER)llResetScript(); } }