From e342fecd45147a72634c749c52d01ef778e0a106 Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Wed, 6 Mar 2024 21:29:15 -0500 Subject: [PATCH] 'Domain Mapper' Application This adds the "Domain Mapper" application. 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.) I finally decided to add it since it happened to be useful to adjust huge zones entities (too big to efficiently view in edition directly.) --- applications/domainMapper/app-domainMapper.js | 471 ++++++++++++++++++ applications/domainMapper/icon_active.png | Bin 0 -> 1569 bytes .../domainMapper/icon_inactive_green.png | Bin 0 -> 1613 bytes .../domainMapper/icon_inactive_white.png | Bin 0 -> 1599 bytes applications/metadata.js | 9 + 5 files changed, 480 insertions(+) create mode 100644 applications/domainMapper/app-domainMapper.js create mode 100644 applications/domainMapper/icon_active.png create mode 100644 applications/domainMapper/icon_inactive_green.png create mode 100644 applications/domainMapper/icon_inactive_white.png 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 0000000000000000000000000000000000000000..18bd34db357ccd64c2264428909d54cb305417c5 GIT binary patch literal 1569 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