Carbon Goggles
Revision as of 09:56, 25 January 2015 by ObviousAltIsObvious Resident (talk | contribs) (<lsl> tag to <source>)
LSL Portal | Functions | Events | Types | Operators | Constants | Flow Control | Script Library | Categorized Library | Tutorials |
// 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;
}
}