diff --git a/applications/domainMapper/app-domainMapper.js b/applications/domainMapper/app-domainMapper.js new file mode 100644 index 0000000..6a36cb6 --- /dev/null +++ b/applications/domainMapper/app-domainMapper.js @@ -0,0 +1,471 @@ +// +// app-domainMapper.js +// +// Created by Alezia Kurdis, March 4th 2024. +// Copyright 2024, Overte e.V. +// +// Overte Application to generate a map of the occupied area in a domain by generating a 3d representation. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +(function() { + var jsMainFileName = "app-domainMapper.js"; + var ROOT = Script.resolvePath('').split(jsMainFileName)[0]; + + var APP_ICON_INACTIVE = ROOT + "icon_inactive_white.png"; + var ICON_CAPTION_COLOR = "#FFFFFF"; + if (ROOT.substr(0, 4) !== "http") { + APP_ICON_INACTIVE = ROOT + "icon_inactive_green.png"; + ICON_CAPTION_COLOR = "#00FF00"; + } + var APP_ICON_ACTIVE = ROOT + "icon_active.png"; + var APP_NAME = "DOMAP"; + var appStatus = false; + + var UPDATE_TIMER_INTERVAL = 5000; // 5 sec + var processTimer = 0; + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + var domainMapID = Uuid.NULL; + var displayPosition = null; + var FULL_DOMAIN_SCAN_RADIUS = 27713; + var DOMAIN_SIZE = 32768; + var DOMAIN_MAP_SIZE = 4; //in meters + + var button = tablet.addButton({ + text: APP_NAME, + icon: APP_ICON_INACTIVE, + activeIcon: APP_ICON_ACTIVE, + captionColor: ICON_CAPTION_COLOR + }); + + function clicked(){ + var colorCaption; + if (appStatus === true) { + clearDomainMap(); + colorCaption = ICON_CAPTION_COLOR; + appStatus = false; + Script.update.disconnect(myTimer); + displayPosition = null; + }else{ + drawDomainMap(); + colorCaption = "#000000"; + appStatus = true; + Script.update.connect(myTimer); + } + + button.editProperties({ + isActive: appStatus, + captionColor: colorCaption + }); + } + + function myTimer(deltaTime) { + var today = new Date(); + if ((today.getTime() - processTimer) > UPDATE_TIMER_INTERVAL ) { + + drawDomainMap(); + + today = new Date(); + processTimer = today.getTime(); + } + } + + function makeUnlit(id) { + var materialData = "{\n \"materialVersion\": 1,\n \"materials\": [\n {\n \"name\": \"0\",\n \"defaultFallthrough\": true,\n \"unlit\": true,\n \"model\": \"hifi_pbr\"\n }\n ]\n}"; + var materialEntityID = Entities.addEntity({ + "type": "Material", + "parentID": id, + "localPosition": {"x": 0, "y": 0, "z": 0}, + "name": "Unlit-material", + "parentMaterialName": "0", + "materialURL": "materialData", + "priority": 1, + "materialMappingMode": "uv", + "ignorePickIntersection": true, + "materialData": materialData + }, "local"); + } + + function drawDomainMap() { + var i, id, properties; + var domainName = location.hostname; + if (domainName === "") { + domainName = "SERVERLESS"; + } + + var zones = Entities.findEntitiesByType("Zone", {"x": 0, "y": 0, "z": 0}, FULL_DOMAIN_SCAN_RADIUS); + if (displayPosition === null) { + displayPosition = Vec3.sum(MyAvatar.feetPosition, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: DOMAIN_MAP_SIZE/2, z: - DOMAIN_MAP_SIZE })); + } + clearDomainMap(); + domainMapID = Entities.addEntity({ + "name": "DOMAIN MAP - " + domainName, + "type": "Shape", + "shape": "Cube", + "grab": {"grabbable": false }, + "dimensions": {"x": DOMAIN_MAP_SIZE, "y": DOMAIN_MAP_SIZE, "z": DOMAIN_MAP_SIZE}, + "position": displayPosition, + "color": {"red": 255, "green": 255, "blue": 255}, + "alpha": 0.05, + "canCastShadow": false, + "collisionless": true, + "primitiveMode": "solid" + }, "local"); + makeUnlit(domainMapID); + + id = Entities.addEntity({ + "name": "DOMAIN MAP FRAME - " + domainName, + "type": "Shape", + "parentID": domainMapID, + "shape": "Cube", + "grab": {"grabbable": false }, + "dimensions": {"x": DOMAIN_MAP_SIZE, "y": DOMAIN_MAP_SIZE, "z": DOMAIN_MAP_SIZE}, + "localPosition": {"x": 0, "y": 0, "z": 0}, + "color": {"red": 255, "green": 255, "blue": 255}, + "alpha": 1, + "canCastShadow": false, + "collisionless": true, + "primitiveMode": "lines" + }, "local"); + makeUnlit(id); + + id = Entities.addEntity({ + "name": "DOMAINE NAME", + "type": "Text", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": 4, "y": 0.5, "z": 0.01}, + "localPosition": {"x": 0, "y": (DOMAIN_MAP_SIZE/2) + 0.7, "z": 0}, + "text": domainName, + "lineHeight": 0.25, + "textColor": {"red": 255, "green": 255, "blue": 255}, + "textAlpha": 0.8, + "backgroundAlpha": 0, + "unlit": true, + "alignment": "center", + "billboardMode": "full", + "canCastShadow": false, + "collisionless": true + }, "local"); + + id = Entities.addEntity({ + "name": "X AXIS", + "type": "Shape", + "shape": "Cylinder", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": 0.02, "y": DOMAIN_MAP_SIZE, "z": 0.02}, + "localPosition": {"x": 0, "y": 0, "z": 0}, + "localRotation": Quat.fromVec3Degrees({"x": 0, "y": 0, "z": 90}), + "color": {"red": 255, "green": 0, "blue": 0}, + "alpha": 0.8, + "canCastShadow": false, + "collisionless": true + }, "local"); + makeUnlit(id); + + id = Entities.addEntity({ + "name": "Y AXIS", + "type": "Shape", + "shape": "Cylinder", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": 0.02, "y": DOMAIN_MAP_SIZE, "z": 0.02}, + "localPosition": {"x": 0, "y": 0, "z": 0}, + "localRotation": Quat.fromVec3Degrees({"x": 0, "y": 0, "z": 0}), + "color": {"red": 0, "green": 255, "blue": 0}, + "alpha": 0.8, + "canCastShadow": false, + "collisionless": true + }, "local"); + makeUnlit(id); + + id = Entities.addEntity({ + "name": "Z AXIS", + "type": "Shape", + "shape": "Cylinder", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": 0.02, "y": DOMAIN_MAP_SIZE, "z": 0.02}, + "localPosition": {"x": 0, "y": 0, "z": 0}, + "localRotation": Quat.fromVec3Degrees({"x": 90, "y": 0, "z": 0}), + "color": {"red": 0, "green": 0, "blue": 255}, + "alpha": 0.8, + "canCastShadow": false, + "collisionless": true + }, "local"); + makeUnlit(id); + + id = Entities.addEntity({ + "name": "+X", + "type": "Text", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": 0.3, "y": 0.2, "z": 0.01}, + "localPosition": {"x": (DOMAIN_MAP_SIZE/2) + 0.3, "y": 0, "z": 0}, + "text": "+X", + "lineHeight": 0.15, + "textColor": {"red": 255, "green": 0, "blue": 0}, + "textAlpha": 0.8, + "backgroundAlpha": 0, + "unlit": true, + "alignment": "center", + "billboardMode": "full", + "canCastShadow": false, + "collisionless": true + }, "local"); + + id = Entities.addEntity({ + "name": "-X", + "type": "Text", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": 0.3, "y": 0.2, "z": 0.01}, + "localPosition": {"x": (-DOMAIN_MAP_SIZE/2) - 0.3, "y": 0, "z": 0}, + "text": "-X", + "lineHeight": 0.15, + "textColor": {"red": 255, "green": 0, "blue": 0}, + "textAlpha": 0.8, + "backgroundAlpha": 0, + "unlit": true, + "alignment": "center", + "billboardMode": "full", + "canCastShadow": false, + "collisionless": true + }, "local"); + + id = Entities.addEntity({ + "name": "+Y", + "type": "Text", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": 0.3, "y": 0.2, "z": 0.01}, + "localPosition": {"x": 0, "y": (DOMAIN_MAP_SIZE/2) + 0.3, "z": 0}, + "text": "+Y", + "lineHeight": 0.15, + "textColor": {"red": 0, "green": 255, "blue": 0}, + "textAlpha": 0.8, + "backgroundAlpha": 0, + "unlit": true, + "alignment": "center", + "billboardMode": "full", + "canCastShadow": false, + "collisionless": true + }, "local"); + + id = Entities.addEntity({ + "name": "-Y", + "type": "Text", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": 0.3, "y": 0.2, "z": 0.01}, + "localPosition": {"x": 0, "y": (-DOMAIN_MAP_SIZE/2) - 0.3, "z": 0}, + "text": "-Y", + "lineHeight": 0.15, + "textColor": {"red": 0, "green": 255, "blue": 0}, + "textAlpha": 0.8, + "backgroundAlpha": 0, + "unlit": true, + "alignment": "center", + "billboardMode": "full", + "canCastShadow": false, + "collisionless": true + }, "local"); + + id = Entities.addEntity({ + "name": "+Z", + "type": "Text", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": 0.3, "y": 0.2, "z": 0.01}, + "localPosition": {"x": 0, "y": 0, "z": (DOMAIN_MAP_SIZE/2) + 0.3}, + "text": "+Z", + "lineHeight": 0.15, + "textColor": {"red": 0, "green": 0, "blue": 255}, + "textAlpha": 0.8, + "backgroundAlpha": 0, + "unlit": true, + "alignment": "center", + "billboardMode": "full", + "canCastShadow": false, + "collisionless": true + }, "local"); + + id = Entities.addEntity({ + "name": "-Z", + "type": "Text", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": 0.3, "y": 0.2, "z": 0.01}, + "localPosition": {"x": 0, "y": 0, "z": (-DOMAIN_MAP_SIZE/2) - 0.3}, + "text": "-Z", + "lineHeight": 0.15, + "textColor": {"red": 0, "green": 0, "blue": 255}, + "textAlpha": 0.8, + "backgroundAlpha": 0, + "unlit": true, + "alignment": "center", + "billboardMode": "full", + "canCastShadow": false, + "collisionless": true + }, "local"); + + if (zones.length > 0) { + var margins = 0; + var color; + for (i = 0; i < zones.length; i++) { + properties = Entities.getEntityProperties(zones[i], ["position", "dimensions", "name", "rotation"]); + color = getColorFromID(zones[i]); + id = Entities.addEntity({ + "name": "Zone - " + properties.name, + "type": "Shape", + "shape": "Cube", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": DOMAIN_MAP_SIZE * (properties.dimensions.x/DOMAIN_SIZE), "y": DOMAIN_MAP_SIZE * (properties.dimensions.y/DOMAIN_SIZE), "z": DOMAIN_MAP_SIZE * (properties.dimensions.z/DOMAIN_SIZE)}, + "localPosition": {"x": (DOMAIN_MAP_SIZE/2) * (properties.position.x/(DOMAIN_SIZE/2)), "y": (DOMAIN_MAP_SIZE/2) * (properties.position.y/(DOMAIN_SIZE/2)), "z": (DOMAIN_MAP_SIZE/2) * (properties.position.z/(DOMAIN_SIZE/2)) }, + "localRotation": properties.rotation, + "color": color, + "alpha": 0.1, + "canCastShadow": false, + "collisionless": true + }, "local"); + makeUnlit(id); + + id = Entities.addEntity({ + "name": "Zone Frame - " + properties.name, + "type": "Shape", + "shape": "Cube", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": DOMAIN_MAP_SIZE * (properties.dimensions.x/DOMAIN_SIZE), "y": DOMAIN_MAP_SIZE * (properties.dimensions.y/DOMAIN_SIZE), "z": DOMAIN_MAP_SIZE * (properties.dimensions.z/DOMAIN_SIZE)}, + "localPosition": {"x": (DOMAIN_MAP_SIZE/2) * (properties.position.x/(DOMAIN_SIZE/2)), "y": (DOMAIN_MAP_SIZE/2) * (properties.position.y/(DOMAIN_SIZE/2)), "z": (DOMAIN_MAP_SIZE/2) * (properties.position.z/(DOMAIN_SIZE/2)) }, + "localRotation": properties.rotation, + "color": color, + "alpha": 1, + "canCastShadow": false, + "collisionless": true, + "primitiveMode": "lines" + }, "local"); + makeUnlit(id); + + var lineHight = DOMAIN_MAP_SIZE * (getTheLargestAxisDimension(properties.dimensions)/DOMAIN_SIZE) * 0.2; + if (lineHight > 0.08) { + lineHight = 0.08; + } + if (lineHight < 0.01) { + lineHight = 0.01; + } + + margins = (0.09 - lineHight)/2; + id = Entities.addEntity({ + "name": "Zone Name - " + properties.name, + "type": "Text", + "parentID": domainMapID, + "grab": {"grabbable": false }, + "dimensions": {"x": 4, "y": 0.1, "z": 0.01}, + "localPosition": { + "x": (DOMAIN_MAP_SIZE/2) * (properties.position.x/(DOMAIN_SIZE/2)), + "y": ((DOMAIN_MAP_SIZE/2) * (properties.position.y/(DOMAIN_SIZE/2))) + ((DOMAIN_MAP_SIZE * (properties.dimensions.y/DOMAIN_SIZE))/2) + lineHight, + "z": (DOMAIN_MAP_SIZE/2) * (properties.position.z/(DOMAIN_SIZE/2)) + }, + "text": properties.name, + "lineHeight": lineHight, + "textColor": color, + "textAlpha": 0.8, + "backgroundAlpha": 0, + "topMargin": margins, + "bottomMargin": margins, + "unlit": true, + "alignment": "center", + "billboardMode": "full", + "canCastShadow": false, + "collisionless": true + }, "local"); + } + } + + } + + function getTheLargestAxisDimension(dimensions) { + var largest = dimensions.x; + if (dimensions.y > largest) { largest = dimensions.y; } + if (dimensions.z > largest) { largest = dimensions.z; } + return largest; + } + + function getColorFromID(id) { + var score = getStringScore(id); + var hue = (score % 360) / 360; + var coloration = hslToRgb(hue, 1, 0.6); + return {"red": coloration[0], "green": coloration[1], "blue": coloration[2]}; + } + + function getStringScore(str) { + var score = 0; + for (var j = 0; j < str.length; j++){ + score += str.charCodeAt(j); + } + return score; + } + + /* + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param {number} h The hue + * @param {number} s The saturation + * @param {number} l The lightness + * @return {Array} The RGB representation + */ + function hslToRgb(h, s, l){ + var r, g, b; + + if(s == 0){ + r = g = b = l; // achromatic + }else{ + var hue2rgb = function hue2rgb(p, q, t){ + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + } + + function clearDomainMap() { + if (domainMapID !== Uuid.NULL) { + Entities.deleteEntity(domainMapID); + domainMapID = Uuid.NULL; + } + } + + button.clicked.connect(clicked); + + function cleanup() { + + if (appStatus) { + clearDomainMap(); + Script.update.disconnect(myTimer); + } + + tablet.removeButton(button); + } + + Script.scriptEnding.connect(cleanup); +}()); diff --git a/applications/domainMapper/icon_active.png b/applications/domainMapper/icon_active.png new file mode 100644 index 0000000..18bd34d Binary files /dev/null and b/applications/domainMapper/icon_active.png differ diff --git a/applications/domainMapper/icon_inactive_green.png b/applications/domainMapper/icon_inactive_green.png new file mode 100644 index 0000000..6475d68 Binary files /dev/null and b/applications/domainMapper/icon_inactive_green.png differ diff --git a/applications/domainMapper/icon_inactive_white.png b/applications/domainMapper/icon_inactive_white.png new file mode 100644 index 0000000..cd259ed Binary files /dev/null and b/applications/domainMapper/icon_inactive_white.png differ diff --git a/applications/metadata.js b/applications/metadata.js index 5ceac29..34015cf 100644 --- a/applications/metadata.js +++ b/applications/metadata.js @@ -323,6 +323,15 @@ var metadata = { "applications": "jsfile": "armored-chat/armored_chat.js", "icon": "armored-chat/img/icon.png", "caption": "CHAT" + }, + { + "isActive": true, + "directory": "domainMapper", + "name": "Domain Mapper", + "description": "Generate a 3D miniature representation of your domain's zones. Useful if you have many 'Places' in your domain and are trying to visualize where you can add new ones. The domain overview can also be used in parallel with the 'Create' application to help you adjust kilometers wide zones. (It refreshes every 5 seconds.)", + "jsfile": "domainMapper/app-domainMapper.js", + "icon": "domainMapper/icon_inactive_white.png", + "caption": "DOMAP" } ] }; \ No newline at end of file