Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ---[=[ Name, Version, and Description management.
- _Name_ = `ZYX Euler Rotation (Gimbal) Demonstrator`
- _Version_ = "1.5"
- _Desc_ = [[
- Prototype teaching toy. Work in Progress.
- Feel Free to take a copy to play with.
- ]]
- ll.SetObjectName( `{_Name_}: {_Version_}` ); ll.SetObjectDesc( _Desc_ )
- --]=]
- function concat( ... ) -- Return stringized values as a comma separated sting. Avoid using tables so explicit nils are preserved.
- local count = select("#", ...); if count == 0 then return "" end--if
- local result = tostring(select(1, ...)) for i = 2, count do result = result .. `{", "}{tostring(select(i, ...))}` end--for
- return result
- end--concat
- local rawprint = print -- always enabled just in case some code is using rawprint to bypass the redirect below
- local function print(...) ll.Shout( DEBUG_CHANNEL, concat(...) ) end--print
- ---[[ Send DBG messages to the Debug Channel to sync with error messages and avoid spamming Nearby Chat
- local function rawDBG( msg ) ll.Shout( DEBUG_CHANNEL, msg ) end--rawDBG this function exist to allow redirecton of all DBG output to a single destination in one place
- local function DBG( ... ) local n,l = debug.info(2, "nl"); rawDBG( `{ll.GetTimestamp()}: {l} {n}: {concat(...)}` ); ll.Sleep(0.2) end--DBG (with timestamp)
- local function DBG_dump( label, val, lvl ) --dump any data structure
- lvl = lvl or 0
- local indent = string.rep( " ", lvl )
- local leader = indent .. `({typeof(label)}) {tostring(label)}: `
- if lvl == 0 then local n,l = debug.info(2, "nl"); leader = `{ll.GetTimestamp()}: {l} {n}:\n` .. leader end--if (with timestamp)
- typ = typeof( val )
- if typ == "table" then
- rawDBG( `{leader}({typ}) {tostring(val)} `.."{" )
- lvl += 1
- for k,v in pairs(val) do DBG_dump( k, v, lvl ) end--for
- rawDBG( indent .. "}" )
- else rawDBG( `{leader}({typ}) {tostring(val)}` )
- end--if
- end--DBG_dump
- function DBG_dump_list( label, list ) local n,l = debug.info(2, "nl"); rawDBG( `{ll.GetTimestamp()}: {l} {n}: {label}: {concat(unpack(list))}` ) end--dump a list (1D array) on one line.
- function DBG_err( ... )
- DBG( "Error Caught by xpcall", ... )
- rawDBG( debug.traceback( "Stack Trace", 2) )
- end--DBG_err
- --[=[]] --Switch to do-nothing dummy versions when above is disabled.
- local function rawDBG() end--rawDBG disabled
- local DBG, DBG_dump, DBG_dump_list, = rawDBG, rawDBG, rawDBG
- --]=]
- -->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Code start <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
- --Script Scope Variables
- local base = { pos = {}, rot = {} } -- base position and rotation of each link. Stored in LSD, see Setup block belo.
- local current = { pos = {}, rot = {} } -- current position and rotation of each link
- local X, Y, Z = {}, {}, {} -- Sets of links to rotate with each Euler axis rotation.
- local Xtails, Ytails, Ztails, Alltails = {}, {}, {}, {} --Sets of links to leave particle trails
- local center, Xring, Yring, Zring --Ring link numbers
- local Xmaj, Ymaj, Zmaj, Xmin, Ymin, Zmin --X, Y, and Z ring major and minor radii.
- local STlast -- last valid ST value for use if a touch moves off face. Assume a touch must always start on face to set.
- local center, autoTouch --center of rotation == center of Object, link to touch for auto on.off and reset.
- local auto, autoControl, autoTimer
- local ComChan = 999 -- Command Channel. Used both for user commands and inter-object sync/communication.
- --Utility Functions
- local function GetLinkPos( link ) return ll.GetLinkPrimitiveParams( link, {PRIM_POS_LOCAL} )[1] end--GetLinkPos
- local function SetLinkPos( link, pos) ll.SetLinkPrimitiveParamsFast( link, {PRIM_POS_LOCAL, pos} ) end--SetLinkPos
- local function GetLinkRot( link ) return ll.GetLinkPrimitiveParams( link, {PRIM_ROT_LOCAL} )[1] end--GetLinkRot
- local function SetLinkRot( link, rot) ll.SetLinkPrimitiveParamsFast( link, {PRIM_ROT_LOCAL, rot} ) end--SetLinkRot
- --init/reset group
- local init, reset
- function init()
- Xring = tonumber( ll.LinksetDataRead("X Ring") ); Yring = tonumber( ll.LinksetDataRead("Y Ring") ); Zring = tonumber( ll.LinksetDataRead("Z Ring") )
- local tx, ty, tz = ll.GetLinkPrimitiveParams( Xring, {PRIM_SIZE} )[1], ll.GetLinkPrimitiveParams( Yring, {PRIM_SIZE} )[1], ll.GetLinkPrimitiveParams( Zring, {PRIM_SIZE} )[1]
- Xmaj, Ymaj, Zmaj = tx.y/2, ty.y/2, tz.y/2 --Radii are the y (or z) dimension/2 (Setup parameters but faster to calculate than to look up in LSD.)
- Xmin, Ymin, Zmin = tx.x, ty.x, tz.x -- minor radii are the X dimension/2
- center, autoTouch = tovector( ll.LinksetDataRead( "center" ) ), tonumber( ll.LinksetDataRead( "Reference object" ) )
- local floater = tonumber( ll.LinksetDataRead( "Text Floater" ) )
- ll.SetLinkPrimitiveParamsFast( floater, {PRIM_TEXT, `{_Name_}: {_Version_}\n \n{_Desc_}`, vector(1,0.5,0.2), 1} )
- base = lljson.sldecode( ll.LinksetDataRead( "base" ) )
- X = lljson.sldecode( ll.LinksetDataRead( "X" ) )
- Y = lljson.sldecode( ll.LinksetDataRead( "Y" ) )
- Z = lljson.sldecode( ll.LinksetDataRead( "Z" ) )
- table.move( X, 1, #X, #Y+1, Y ) -- X moves with Y
- table.move( Y, 1, #Y, #Z+1, Z ) -- Y moves with Z
- XT = lljson.sldecode( ll.LinksetDataRead( "XT" ) )
- YT = lljson.sldecode( ll.LinksetDataRead( "YT" ) )
- ZT = lljson.sldecode( ll.LinksetDataRead( "ZT" ) )
- AT = lljson.sldecode( ll.LinksetDataRead( "AT" ) )
- reset()
- end--init
- function reset() --Reset positions and rotations to base values
- local b
- for link = 2, ll.GetNumberOfPrims() do
- b = base.pos[link]; SetLinkPos( link, b ); current.pos[link] = b
- b = base.rot[link]; SetLinkRot( link, b ); current.rot[link] = b
- end--for
- autoControl = false; if autoTimer then LLTimers.off(autoTimer); autoTimer = nil; end--if
- end--reset
- --Relative Euler Rotations
- local function rotX(angle) --Rotate X set about X center relative to the current position
- angle /=2
- local v = -ll.Rot2Up(current.rot[Yring]) * math.sin(angle)
- local rot = quaternion( v.x, v.y, v.z, math.cos(angle) )
- for _, link in X do
- local p = center + (current.pos[link]-center) * rot
- local r = current.rot[link] * rot
- SetLinkPos( link, p ); current.pos[link] = p
- SetLinkRot( link, r ); current.rot[link] = r
- end--for
- end--rotX
- local function rotY(angle) --Rotate Y set about Y center relative to the current position
- angle /=2
- local v = -ll.Rot2Up(current.rot[Zring]) * math.sin(angle)
- local rot = quaternion( v.x, v.y, v.z, math.cos(angle) )
- for _, link in Y do
- local p = center + (current.pos[link]-center) * rot
- local r = current.rot[link] * rot
- SetLinkPos( link, p ); current.pos[link] = p
- SetLinkRot( link, r ); current.rot[link] = r
- end--for
- end--rotY
- local function rotZ(angle) --Rotate Z set about Z center relative to the current position
- local rot = ll.Euler2Rot( vector(0,0,angle) )
- for _, link in Z do
- local p = center + (current.pos[link]-center) * rot
- local r = current.rot[link] * rot
- SetLinkPos( link, p ); current.pos[link] = p
- SetLinkRot( link, r ); current.rot[link] = r
- end--for
- end--rotZ
- --Visual Effects
- local function linksetTrail( set, onoff, color ) -- create or stop the particle trail for a given link. For use with the tails.
- if onoff then
- for _, link in set do
- ll.LinkParticleSystem( link, {
- PSYS_PART_FLAGS , bit32.bor( PSYS_PART_EMISSIVE_MASK, PSYS_PART_INTERP_COLOR_MASK, PSYS_PART_INTERP_SCALE_MASK, PSYS_PART_RIBBON_MASK ), --PSYS_PART_RIBBON_MASK
- PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_ANGLE_CONE,
- PSYS_PART_START_COLOR, color,
- PSYS_PART_END_COLOR, color/8,
- PSYS_PART_START_SCALE, vector(0.04,0.04,0),
- PSYS_PART_END_SCALE, ZERO_VECTOR,
- PSYS_SRC_TEXTURE, uuid("5e0809d7-368c-cfab-a2ba-dd5a3774eac8"), -- uuid("3553f0aa-23c6-58ed-bf2f-2713e7875b73"),
- PSYS_PART_START_GLOW, 0.0,
- PSYS_SRC_MAX_AGE, 0,
- PSYS_PART_MAX_AGE, 1,
- PSYS_SRC_BURST_RATE, 0.0,
- PSYS_SRC_BURST_PART_COUNT, 1,
- PSYS_SRC_BURST_RADIUS, 0.15,
- PSYS_SRC_ANGLE_BEGIN, 0,
- PSYS_SRC_ANGLE_END, 0,
- PSYS_SRC_BURST_SPEED_MIN, 0,
- PSYS_SRC_BURST_SPEED_MAX, 0,
- })
- end--for
- else
- for _, link in set do
- -- DBG(link)
- ll.LinkParticleSystem( link, {})
- end--for
- end--if
- end--linksetTrail
- --touch activated autoplay
- auto = coroutine.create( --auto rotate. variabale auto declared local near the top of the script.
- function( ) -- autoControl: true -> run false -> pause until resumed
- while true do
- rotX(0.3); rotY(0.2); rotZ(0.1);
- -- llRegionSay( 9999, `qua {current.rot[Xring]}` )
- if autoControl then autoTimer = LLTimers:once( 0.1, function() coroutine.resume( auto ) end ) end--if
- coroutine.yield()
- end--while
- end--function
- )--end create auto
- --Events, Touch Group
- local longTouchThreshold, longTouchTime, longTouchTimer = 1.5, 0, nil
- function LLEvents.touch_start(detected)
- local link = detected[1]:getLinkNumber()
- if link == autoTouch then
- longTouchTime = os.clock() + longTouchThreshold
- longTouchTimer = LLTimers:once( longTouchThreshold, reset )
- else
- LongTouchTime = 0 --flags touch on a link other than autotouch.
- autoControl = false
- linksetTrail( AT, false )
- end--if
- end--touch_start
- function LLEvents.touch(detected)
- local link, ST = detected[1]:getLinkNumber(), detected[1]:getTouchST()
- if ST == TOUCH_INVALID_TEXCOORD then ST = STlast else STlast = ST end--if
- local s = if ST.y < 0.5 then 1 else -1 -- side of ring factor. Perhaps replace with theta adjustment.
- -- if face == 1 then DBG( ST, s, s*(ST.y-0.5) ) end--if
- local r = ST.x -- distance to move in minor radius units.
- r = 4 * s * (
- if r < 0.5 then 0.25-r -- 0.0 -> 0.5 => 1 -> -1
- else r-0.75 -- 0.5 -> 1.0 => -1 -> 1
- )
- -- DBG( r, Zmaj, Zmin, r*Zmin/Zmaj )
- if link == Xring then rotX( r*Xmin/Xmaj/3 ) -- chord approximation of angle, guaranteed to be less than true angle. Should
- elseif link == Yring then rotY( r*Ymin/Ymaj/3 ) -- prevent overshoot, but divide by 3 as an observed fudge factor due, perhaps,
- elseif link == Zring then rotZ( r*Zmin/Zmaj/3 ) -- to an unfortunate interaction with the simulator's smoothing algorithm.
- --else ignore
- end--if
- end--touch_start
- function LLEvents.touch_end(detected)
- if longTouchTimer then LLTimers:off( longTouchTimer ); longTouchTimer = nil; end--if
- if os.clock() < longTouchTime then --short touch on autotouch link, toggle auto mode.
- autoControl = not autoControl
- if autoControl then
- --linksetTrail( {4}, true, vector(1,0,0) );
- linksetTrail( XT, true, vector(1,0,0) ); linksetTrail( YT, true, vector(0,1,0) );linksetTrail( ZT, true, vector(0,0,1) )
- coroutine.resume( auto )
- else
- if autoTimer then LLTimers:off(autoTimer); autoTimer = nil; end--if
- linksetTrail( AT, false )
- end--if
- end--if
- end--touch_end
- --Events, Communication Group
- local RESET_MESSAGE = 999
- local owner = ll.GetOwner()
- ll.Listen( ComChan, "", NULL_KEY, "" )
- local rotWarnCount, rotWarnTimer = 0, nil
- local listenCommands = {
- init = init, reset = reset, --Do I want both? Unless LSD has changed, init shouldn't do more than reset once the script is running.
- qua = function( data )
- local quat = toquaternion(data)
- if not quat then
- rotWarnCount += 1
- if not rotWarnTimer then LLTimers:once( 1, function()
- print( `Warning: Ignoring invalid argument to qua command: {data}`..
- `{if rotWarnCount>1 then " (plus "..tostring(rotWarnCount-1).." similar)" else ""}`
- )
- rotWarnCount = 0; rowWarnTimer = nil
- end) end--function, timer, if
- return
- end--if
- reset() --DBG Consider whether there is any advantage computing the move from relative postions.
- DBG( `qua {quat} ({ll.Rot2Euler(quat)})` )
- local euler = ll.Rot2Euler( quat )
- rotX(euler.x); rotY(euler.y); rotZ(euler.z)
- end,--qua
- }
- function LLEvents.listen( chan, name, id, msg ) --no need to check channel, only listening for the one.
- DBG(`chan="{chan}" name="{name}" id="{id}" msg="{msg}"`)
- if ll.GetOwnerKey(id) ~= owner then return end--if
- msg = ll.StringTrim( msg:lower(), STRING_TRIM_HEAD )
- local _, _, command, sep, data = msg:find( "^(.-)([,:< ])(.*)$" )
- if not sep then command, data = msg, ""
- elseif sep == "<" then data = sep..data
- end--if
- DBG(`command="{command}" sep="{sep}" data="{data}"`)
- command, data = ll.StringTrim( command, STRING_TRIM), ll.StringTrim( data, STRING_TRIM)
- local t = listenCommands[command]; if t then t( data ) end--if
- end--listen
- function LLEvents.link_message( _, num, _, _ ) if num == RESET_MESSAGE then init() end end--if link_message
- --Startup Group (see also link_message above.)
- function LLEvents.on_rez() init() end--on_rez
- init()
Advertisement