import { getScene, getSceneElement, getCategory, getCookie } from "./api"; import { populateDestinationDropdown } from "./editor/teleport"; import registerClickDrag from "@kumitterer/aframe-click-drag-component"; registerClickDrag(window.aframe); import "../css/editor.css"; let clickTimestamp = 0; // Find parent quackscape-scene for ID function findParentScene(element) { var parent = element.parentElement; while (parent.tagName != "QUACKSCAPE-SCENE") { parent = parent.parentElement; } return parent; } // Distinguishing clicks from drags based on duration. // TODO: Find a better way to distinguish these. function addEventListeners(element) { element.addEventListener("mousedown", function (event) { clickTimestamp = event.timeStamp; }); element.addEventListener("mouseup", function (event) { if (event.timeStamp - clickTimestamp > 200) { // Ignoring this, we only handle regular short clicks. return; } else { handleClick(event); } // Right-clicks are definitely intentional. element.addEventListener("contextmenu", function (event) { handleClick(event); }); }); } // Open a modal for creating a new Element function startCreateElement(event) { var propertiesTitle = document.getElementById("propertiesTitle"); propertiesTitle.textContent = "Create Element"; var propertiesContent = document.getElementById("propertiesContent"); var thetaStart = cartesianToTheta( event.detail.intersection.point.x, event.detail.intersection.point.z ); propertiesContent.innerHTML = `
Creating element at:
X: ${event.detail.intersection.point.x}
Y: ${event.detail.intersection.point.y}
Z: ${event.detail.intersection.point.z}
Calculated Theta: ${thetaStart}
Parent Element Type: ${event.srcElement.tagName}

`; var scene = findParentScene(event.target); var scene_data_request = getScene(scene.getAttribute("id")); document.getElementById("resetButton").classList.remove("hide"); document.getElementById("buttons").classList.remove("hide"); document .getElementById("resourcetype") .addEventListener("change", function () { var selectedType = document.getElementById("resourcetype").value; createElementPropertiesForm(selectedType, event, thetaStart); scene_data_request.then((scene_data) => { var category_data_request = getCategory(scene_data.obj.category); category_data_request.then((category_data) => { populateDestinationDropdown(null, category_data); }); }); }); } function createElementPropertiesForm(elementType) { const elementProperties = document.getElementById("elementProperties"); let inputFields = ""; switch (elementType) { case "MarkerElement": case "ImageElement": case "TeleportElement": inputFields = `
`; if (elementType !== "MarkerElement") { inputFields += `
`; } if (elementType === "TeleportElement") { inputFields += `
`; } break; default: // TODO: Handle unknown element type } elementProperties.innerHTML = inputFields; } function startModifyElement(event) { // Make the element draggable for positioning event.target.setAttribute("click-drag", "enabled: true;"); // Listen for "dragend" event to update the element's position event.target.addEventListener("dragend", function (event) { updateElementLocation(event); }); var propertiesTitle = document.getElementById("propertiesTitle"); propertiesTitle.textContent = "Modify Element"; // Get element from API var scene = findParentScene(event.target); var element_data_request = getSceneElement( scene.getAttribute("id"), event.target.getAttribute("id") ); element_data_request.then((element_data) => { var propertiesContent = document.getElementById("propertiesContent"); propertiesContent.innerHTML = `
Modifying element:
Element Type: ${event.srcElement.tagName}
Element ID: ${event.target.getAttribute("id")}
Element data: ${JSON.stringify(element_data.obj)}

`; createElementPropertiesForm(element_data.obj.resourcetype); var scene_data_request = getScene(scene.getAttribute("id")); scene_data_request.then((scene_data) => { var category_data_request = getCategory(scene_data.obj.category); category_data_request.then((category_data) => { populateDestinationDropdown( element_data.obj.destination, category_data ); }); }); document .getElementById("elementUpload") .addEventListener("change", function () { if (this.files && this.files[0]) { var reader = new FileReader(); reader.onload = function (e) { // Set the image to the file contents if (event.target.getAttribute("data-original-src") == undefined) { event.target.setAttribute( "data-original-src", event.target.getAttribute("src") ); } event.target.setAttribute("src", e.target.result); }; reader.readAsDataURL(this.files[0]); } }); if (element_data.obj.destination_x != -1) { document.getElementById("destination_x").value = element_data.obj.destination_x; } if (element_data.obj.destination_y != -1) { document.getElementById("destination_y").value = element_data.obj.destination_y; } if (element_data.obj.destination_z != -1) { document.getElementById("destination_z").value = element_data.obj.destination_z; } document.getElementById("resetButton").classList.remove("hide"); document.getElementById("buttons").classList.remove("hide"); }); } function cartesianToTheta(x, z) { // Calculate the angle in radians let angleRadians = Math.atan2(z, x); // Convert to degrees let angleDegrees = angleRadians * (180 / Math.PI); // A-Frame's thetaStart is measured from the positive X-axis ("right" direction) // and goes counter-clockwise, so this should directly give us the thetaStart value. let thetaStart = 90 - angleDegrees; // Since atan2 returns values from -180 to 180, let's normalize this to 0 - 360 thetaStart = thetaStart < 0 ? thetaStart + 360 : thetaStart; return thetaStart; } function thetaToCartesian(thetaStart, radius = 1) { // Convert thetaStart back to the angle in degrees as used in standard Cartesian coordinates. let angleDegrees = 90 - thetaStart; // Normalize the angle to be within the range of 0 to 360 degrees angleDegrees = angleDegrees % 360; if (angleDegrees < 0) { angleDegrees += 360; } // Convert the angle back to radians let angleRadians = angleDegrees * (Math.PI / 180); // Calculate the Cartesian coordinates using the angle and radius // x = r * cos(θ) let x = radius * Math.cos(angleRadians); // z = r * sin(θ) let z = radius * Math.sin(angleRadians); return { x, z }; } function latLonToXYZ(lat, lon, radius = 5) { // Convert lat/lon to X/Y/Z coordinates on the sphere const phi = (90 - lat) * (Math.PI / 180); const theta = (lon + 180) * (Math.PI / 180); const x = -(radius * Math.sin(phi) * Math.cos(theta)); const y = radius * Math.cos(phi); const z = radius * Math.sin(phi) * Math.sin(theta); return { x, y, z }; } function handleClick(event) { // If clicked on the sky, start creating a new element if (event.target.tagName == "A-SKY") { startCreateElement(event); } else { // Else we are modifying an existing element startModifyElement(event); } } document.addEventListener("loadedQuackscapeScene", function (event) { // Get the scene var scene = document.querySelector("a-scene"); // Get all children var children = scene.children; for (var i = 0; i < children.length; i++) { var child = children[i]; if (child.tagName.startsWith("A-")) { // Remove original onclick events if (child.hasAttribute("onclick")) { child.removeAttribute("onclick"); } // Add new event listeners addEventListeners(child); } } }); function updateElementLocation(event) { const theta = cartesianToTheta(event.detail.offset.x, event.detail.offset.z); event.target.setAttribute("theta-start", theta); } function toggleDebugVisibility() { const debugElement = document.getElementById("propertiesDebug"); debugElement.classList.toggle("hide"); } document.addEventListener("keydown", function (event) { if (event.ctrlKey && event.shiftKey && event.key === "Q") { toggleDebugVisibility(); event.preventDefault(); } });