Carbon Goggles
LSL Portal | Functions | Events | Types | Operators | Constants | Flow Control | Script Library | Categorized Library | Tutorials |
<lsl> // Carbon Goggles 0.7 // // An Augmented Virtual Reality HUD for Second Life // by Babbage Linden // // Available under the Creative Commons Attribution-ShareAlike 2.5 license // http://creativecommons.org/licenses/by-sa/2.5/
float gSelectionTimeout = 120.0; float gScanDelay = 5.0; float gRateDelay = 0.0;
integer gAnnotateDialogChannel = 8888; integer gAnnotateDialogChannelHandle = 0;
integer gCommandChannel = 5; integer gCommandChannelHandle = 0;
integer gConfigDialogChannel = 9999; integer gConfigDialogChannelHandle = 0;
float gSensorRange = 10.0; integer gMaxOverlays = 8; float gRefreshPeriod = 4.0; float gFOV; list gSkipNames = ["Object", "Tree", ""];
list gDetectedNames; list gDetectedKeys; list gDetectedPositions; list gDetectedEmissions;
integer gAnnotate = FALSE; string gAnnotationType; string gAnnotationUrl = "http://wiki.amee.com/andrew/secondlife/get.php?path=/data"; string gAnnotationPath; string gAnnotationParams; key gAnnotationRequest;
key gEmissionsRequest; integer gEmissionsRequestIndex;
// Emission parameters integer gDistanceKmPerMonth = 1207; // cars, motorcycles integer gJourneysPerYear = 2; // flights integer gHoursPerMonth = 90; // tv integer gCyclesPerMonth = 12; // washing machine, dryer, dishwasher integer gNumberOwned = 1; // entertainment devices, e.g. computers, dvd, freeview box, game console etc.
key gUpdateId; string gRegionName; vector gCameraPos; rotation gCameraRot; integer gHidingOverlays; integer gSelectedIndex;
key gNotecardId; integer gNotecardLine;
string gURL = "http://carbongoggles.org/";
float gLastRateTime; float gLastScanTime; float gLastSelectedTime;
integer DISPLAY_STRING = 204000; integer SET_COLOR = 204007; integer SET_POSITION = 204008; integer SET_SCALE = 204009; integer SET_TEXT_COLOUR = 204010; integer SET_ALPHA = 204011; integer OVERLAY_LINK_OFFSET = 2; integer OVERLAY_COUNT = 8;
key httpRequest(string url, list parameters, string body) {
//llOwnerSay("llHTTPRequest " + url + " " + body); return llHTTPRequest(url, parameters, body);
}
requestEmissions() {
if(gEmissionsRequestIndex < llGetListLength(gDetectedEmissions) && llList2Float(gDetectedEmissions, gEmissionsRequestIndex) < 0.0f) { string url = gURL + "object/" + (string)llList2Key(gDetectedKeys, gEmissionsRequestIndex) + "/emissions?"; url += "distanceKmPerMonth=" + (string) gDistanceKmPerMonth; url += "%26journeysPerYear=" + (string) gJourneysPerYear; url += "%26hoursPerMonth=" + (string) gHoursPerMonth; url += "%26cyclesPerMonth=" + (string) gCyclesPerMonth; url += "%26numberOwned=" + (string) gNumberOwned; //llOwnerSay(url); gEmissionsRequest = httpRequest(url, [HTTP_METHOD, "GET"], ""); }
}
rescan() {
float time = llGetTime(); if(time > gLastScanTime + gScanDelay && gSelectedIndex == -1) { llSensor("", NULL_KEY, ACTIVE | PASSIVE, gSensorRange, PI / 2.0); gLastScanTime = time; }
}
hideOverlay(integer index) {
float offset = 0.5 + ((2 / 16.0) * (float)index); //string s = (string)index; //float offset = 1.1; string s = ""; llMessageLinked(index + OVERLAY_LINK_OFFSET, SET_ALPHA, (string)0.0f, NULL_KEY); llMessageLinked(index + OVERLAY_LINK_OFFSET, DISPLAY_STRING, s, NULL_KEY); llMessageLinked(index + OVERLAY_LINK_OFFSET, SET_SCALE, (string)<0.1,0.1,0.1>, NULL_KEY); llMessageLinked(index + OVERLAY_LINK_OFFSET, SET_POSITION, (string)<0,0,offset>, NULL_KEY);
}
hideOverlays() {
integer i = 0; while(i < OVERLAY_COUNT) { hideOverlay(i); ++i; } gHidingOverlays = TRUE;
}
setOverlayColour(vector colour) {
integer i; for(i = 0; i < OVERLAY_COUNT; ++i) { llMessageLinked(i + OVERLAY_LINK_OFFSET, SET_COLOR, (string)<0.8,0.8,0.8>, NULL_KEY); }
}
init(key id) {
gFOV = 60.0 * DEG_TO_RAD; gLastRateTime = 0.0; gLastScanTime = 0.0; llResetTime(); llRequestPermissions(llGetOwner(), PERMISSION_TRACK_CAMERA); gCommandChannelHandle = llListen(gCommandChannel, "", id, ""); gAnnotateDialogChannelHandle = llListen(gAnnotateDialogChannel, "", id, ""); gConfigDialogChannelHandle = llListen(gConfigDialogChannel, "", id, ""); llOwnerSay("Loading settings"); gNotecardLine = 0; gNotecardId = llGetNotecardLine("Settings", gNotecardLine); hideOverlays(); gSelectedIndex = -1; setOverlayColour(<0,1,0>);
}
integer overlay(integer index) {
vector cameraPos = llGetCameraPos(); rotation cameraRot = llGetCameraRot(); integer overlayLinkNum = index + OVERLAY_LINK_OFFSET; float emissions = llList2Float(gDetectedEmissions, index); vector objectPos = llList2Vector(gDetectedPositions, index); if(emissions >= 0.0f || gAnnotate) { // Translate object in to camera space. objectPos = objectPos - cameraPos; // Rotate object in camera space. rotation invCameraRot = <0.0,0.0,0.0,1.0> / cameraRot; objectPos = objectPos * invCameraRot; // Switch axes from Z = up to RHS (X = left, Y = up, Z = forward) objectPos = <-objectPos.y, objectPos.z, objectPos.x>; // Apply perspective distortion. float xHUD = (objectPos.x * (1.0 / llTan(gFOV / 2.0))) / objectPos.z; float yHUD = (objectPos.y * (1.0 / llTan(gFOV / 2.0))) / objectPos.z; // Set front clipping plane to 1m and back clipping plane to infinity. float zHUD = (objectPos.z - 2) / objectPos.z; // Clip object to HUD. if( xHUD > -1.0 && xHUD < 1.0 && yHUD > -1.0 && yHUD < 1.0 && zHUD > -1.0 && zHUD < 1.0) { string label = "Click to annotate"; if(llList2Float(gDetectedEmissions, index) >= 0.0f) { label = (string)llList2String(gDetectedNames, index) + "\n"; label += (string)llList2Float(gDetectedEmissions, index) + " kg co2/month"; } else { emissions = 1.0f; } vector posHUD = <0, -xHUD / 2, yHUD / 2> - llGetLocalPos(); llMessageLinked(overlayLinkNum, SET_POSITION, (string)posHUD, NULL_KEY); float radius = llPow((3.0f * emissions) / (4.0f * PI), (1.0 / 3.0)); float scale = (float)radius / 10.0f; if(scale > 1.0) { scale /= 10.0; llMessageLinked(overlayLinkNum, SET_COLOR, (string)<0.5,0.05,0.05>, NULL_KEY); } else { llMessageLinked(overlayLinkNum, SET_COLOR, (string)<0.8,0.8,0.8>, NULL_KEY); } llMessageLinked(overlayLinkNum, SET_SCALE, (string)<scale,scale,scale>, NULL_KEY); llMessageLinked(overlayLinkNum, DISPLAY_STRING, label, NULL_KEY); llMessageLinked(overlayLinkNum, SET_ALPHA, (string)1.0f, NULL_KEY); return TRUE; } } hideOverlay(index); return FALSE;
}
integer setOption(string option) {
list pair = llParseString2List(option, [":", " "], []); string name = llList2String(pair, 0); name = llToLower(name); if(name == "sensorrange") { gSensorRange = llList2Float(pair, 1); } else if(name == "refreshperiod") { gRefreshPeriod = llList2Float(pair, 1); llSetTimerEvent(gRefreshPeriod); } else if(name == "selectiontimeout") { gSelectionTimeout = llList2Float(pair, 1); } else if(name == "annotate") { gAnnotate = llList2Integer(pair, 1); } else if(name == "fieldofview") { gFOV = llList2Float(pair, 1) * DEG_TO_RAD; } else if(name == "skipnames") { gSkipNames = llCSV2List(llList2String(pair, 1)); } else if(name == "overlayrgb") { vector colour; list rgb = llCSV2List(llList2String(pair, 1)); colour.x = llList2Float(rgb, 0); colour.y = llList2Float(rgb, 1); colour.z = llList2Float(rgb, 2); setOverlayColour(colour); } else if(name == "distancekmpermonth") { gDistanceKmPerMonth = llList2Integer(pair, 1); } else if(name == "journeysperyear") { gJourneysPerYear = llList2Integer(pair, 1); } else if(name == "hourspermonth") { gHoursPerMonth = llList2Integer(pair, 1); } else if(name == "cyclespermonth") { gCyclesPerMonth = llList2Integer(pair, 1); } else if(name == "numberowned") { gNumberOwned = llList2Integer(pair, 1); } else { return FALSE; } //llOwnerSay(llList2String(pair, 0) + ":" + llList2String(pair, 1)); return TRUE;
}
annotateObject(integer index, string ameeurl) {
key id = llList2Key(gDetectedKeys, index); vector position = llList2Vector(gDetectedPositions, index); string body = (string)((integer)position.x); body += "," + (string)((integer)position.y); body += "," + (string)((integer)position.z); body += "," + llEscapeURL(ameeurl); body += "," + llList2String(gDetectedNames, index); string url = gURL + "object/" + (string)id; gUpdateId = httpRequest(url, [HTTP_METHOD, "POST"], body); llOwnerSay("Annotated " + llList2String(gDetectedNames, gSelectedIndex)); gSelectedIndex = -1;
}
saySettings() {
llOwnerSay("To change settings, chat \"/5 Setting:Value\" or change values in Settings notecard then re-attach HUD\nSensorRange:N (scan objects up to N meters away)\nRefreshTime:N (update overlay positions every N seconds)\nShowUnknown:N (augment objects with unknown emissions if N is 1)\nFieldOfView:N (field of view in degrees, must match viewer)\nSkipNames:X,Y,...,Z (ignore objects with names in list X,Y,...,Z)\nOverlayRGB:R,G,B (set overlay colour to <R,G,B>)\nSelectionTimeout:N (time out object selection after N seconds)\n");
}
default {
state_entry() { init(llGetOwner()); } attach(key id) { if(id != NULL_KEY) { init(id); } } listen(integer channel, string name, key id, string message) { if(channel == gCommandChannel) { list pair = llParseString2List(message, ["", " "], []); string command = llList2String(pair, 0); command = llToLower(command); if(setOption(message) == TRUE) { return; } else if(command == "annotate") { annotateObject(gSelectedIndex, llList2String(pair, 1)); } else { llOwnerSay("ERROR: Unrecognised command \"" + message + "\" chat \"/5 Help\" for help"); } } else if(channel == gAnnotateDialogChannel) { if(gAnnotationType == "path") { gAnnotationPath += "/" + llEscapeURL(message); } else { if(gAnnotationParams == "") { gAnnotationParams = "/drill%3f"; } else { gAnnotationParams += "%26"; } gAnnotationParams += gAnnotationType; gAnnotationParams += "="; gAnnotationParams += llEscapeURL(message); } string url = gAnnotationUrl + gAnnotationPath + gAnnotationParams; gAnnotationRequest = httpRequest(url, [], ""); //llOwnerSay(url); } else if(channel == gConfigDialogChannel) { if(message == "Visualise") { llOwnerSay("Hiding annotation interface..."); gAnnotate = FALSE; } else if(message == "Annotate") { llOwnerSay("Showing annotation interface..."); gAnnotate = TRUE; } else if(message == "Web") { llOwnerSay("Loading web interface..."); llLoadURL(llGetOwner(), "", gURL); } else if(message == "Settings") { saySettings(); } else if(message == "Off") { llOwnerSay("Turning off..."); state off; } } } timer() { if((gSelectedIndex != -1) && (gLastSelectedTime + gSelectionTimeout < llGetTime())) { gSelectedIndex = -1; llOwnerSay("Selection timeout"); } vector cameraPos = llGetCameraPos(); rotation cameraRot = llGetCameraRot(); string regionName = llGetRegionName(); integer hide = (regionName != gRegionName) || (llVecDist(cameraPos, gCameraPos) / gRefreshPeriod > 0.1) || (llAngleBetween(cameraRot, gCameraRot) * RAD_TO_DEG > 10); gRegionName = regionName; gCameraPos = cameraPos; gCameraRot = cameraRot; if(hide == TRUE) { hideOverlays(); return; } gHidingOverlays = FALSE; integer count = llGetListLength(gDetectedNames); integer i = 0; while(i < count) { overlay(i); ++i; } while(i < OVERLAY_COUNT) { hideOverlay(i); ++i; } rescan(); } sensor(integer num) { //llOwnerSay("sensor"); list oldKeys = gDetectedKeys; list oldEmissions = gDetectedEmissions; gDetectedNames = []; gDetectedKeys = []; gDetectedPositions = []; gDetectedEmissions = []; integer i; for(i = 0; i < num && i < gMaxOverlays; ++i) { string name = llDetectedName(i); //llOwnerSay("scanned " + name); integer skip = FALSE; integer skipCount = llGetListLength(gSkipNames); integer skipIndex; for(skipIndex = 0; (skipIndex < skipCount) && (skip == FALSE); ++skipIndex) { if(name == llList2String(gSkipNames, skipIndex)) { skip = TRUE; } } if(skip == FALSE) { key newKey = llDetectedKey(i); integer oldCount = llGetListLength(oldKeys); integer oldIndex; integer found = FALSE; for(oldIndex = 0; (oldIndex < oldCount) && (found == FALSE); ++oldIndex) { key oldKey = llList2Key(oldKeys, oldIndex); if(oldKey == newKey) { gDetectedEmissions += llList2Float(oldEmissions, oldIndex); found = TRUE; } } if(found == FALSE) { gDetectedEmissions += -1.0f; } gDetectedNames += name; gDetectedKeys += newKey; gDetectedPositions += llDetectedPos(i); } } gRegionName = llGetRegionName(); gCameraPos = llGetCameraPos(); gEmissionsRequest = NULL_KEY; gEmissionsRequestIndex = 0; gSelectedIndex = -1; requestEmissions(); } http_response(key id, integer status, list meta, string body) { if(id == gEmissionsRequest) { //llOwnerSay("request http_response " + (string)status + " length " + (string)llStringLength(body) + " " + body); if(status == 200) { list result = llParseString2List(body, [","], []); //llOwnerSay(llList2CSV(result)); float emissions = llList2Float(result, 1); //llOwnerSay((string) emissions + " " + (string) gEmissionsRequestIndex + " " + llList2CSV(gDetectedEmissions)); gDetectedEmissions = llListReplaceList(gDetectedEmissions, [emissions], gEmissionsRequestIndex, gEmissionsRequestIndex); } overlay(gEmissionsRequestIndex); gEmissionsRequest = NULL_KEY; ++gEmissionsRequestIndex; requestEmissions(); } else if(id == gAnnotationRequest) { //llOwnerSay((string)status); //llOwnerSay(body); //list result = llParseString2List(body, [" ",",","\n"], []); //llOwnerSay(llList2CSV(result)); //string type = llList2String(result, 0); string type = llGetSubString(body, 0, llSubStringIndex(body, ",") - 1); list result = llParseString2List(body, [type + ",","\n"], []); //llOwnerSay(body); //llOwnerSay(llList2CSV(result)); if(type == "uid") { // AMEE data item UID found, build and store AMEE URL. string uid = llList2String(result, 0); string url = gAnnotationUrl + gAnnotationPath + "/" + uid; annotateObject(gSelectedIndex, url); llOwnerSay("(To annotate another object with the same data, select it, then say \"/5 annotate " + url + "\""); } else { gAnnotationType = type; list options = []; integer length = llGetListLength(result); integer i; for(i = 0; i < length; i += 1) { options += llList2String(result, i); } if(type == "path") { type = "catagory"; } options = llList2List(options, 0, 11); // TODO: Paginate instead of truncating options. //llOwnerSay(llList2CSV(options)); llDialog(llGetOwner(), "Select " + type, options, gAnnotateDialogChannel); } } if(status != 200 && status != 404) { llOwnerSay("ERROR:" + (string)status + ":" + body); } //llOwnerSay("http_response " + (string)status + " length " + (string)llStringLength(body) + " " + body); } dataserver(key id, string data) { if(id == gNotecardId) { if(data != EOF) { if(setOption(data) == FALSE) { llOwnerSay("ERROR: Unknown setting " + data); } ++gNotecardLine; gNotecardId = llGetNotecardLine("Settings", gNotecardLine); } else { llOwnerSay("Settings loaded\nEmissions data will be overlayed on objects when avatar is stationary\nClick logo for settings"); llSetTimerEvent(gRefreshPeriod); llSensor("", NULL_KEY, ACTIVE | PASSIVE, gSensorRange, PI / 2.0); } } } touch_start(integer num) { integer linkNum = llDetectedLinkNumber(0); //llOwnerSay((string)linkNum); linkNum -= OVERLAY_LINK_OFFSET; if(linkNum >= 0) { gSelectedIndex = linkNum; gLastSelectedTime = llGetTime(); llOwnerSay("Selected " + llList2String(gDetectedNames, gSelectedIndex) + "(" + llList2String(gDetectedKeys, gSelectedIndex) + ")"); gAnnotationPath = ""; gAnnotationParams = ""; gAnnotationRequest = httpRequest(gAnnotationUrl, [], ""); } else { llDialog(llGetOwner(), "Select Interface", ["Web", "Settings", "Off", "Visualise", "Annotate"], gConfigDialogChannel); } }
}
state off {
state_entry() { hideOverlays(); llSetTimerEvent(0); } touch_start(integer num) { llOwnerSay("Turning on..."); state default; }
} </lsl>