mirror of
https://github.com/overte-org/overte.git
synced 2025-05-08 10:29:02 +02:00
393 lines
14 KiB
JavaScript
393 lines
14 KiB
JavaScript
//
|
|
// editModes.js
|
|
//
|
|
// Created by Karol Suprynowicz on 2022.05.17.
|
|
// Copyright 2022 Overte e.V.
|
|
//
|
|
// Based on voxels.js
|
|
// Created by Seth Alves on 2015-08-25
|
|
// Copyright 2015 High Fidelity, Inc.
|
|
//
|
|
// Based on entitySelectionTool.js
|
|
// Created by Brad hefta-Gaub on 10/1/14.
|
|
// Modified by Daniela Fontes * @DanielaFifo and Tiago Andrade @TagoWill on 4/7/2017
|
|
// Modified by David Back on 1/9/2018
|
|
// Copyright 2014 High Fidelity, Inc.
|
|
// Copyright 2020 Vircadia contributors
|
|
//
|
|
// This script implements voxel edit mode
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
Script.include([
|
|
"./libraries/utils.js",
|
|
]);
|
|
|
|
EditVoxels = function() {
|
|
var self = this;
|
|
var that = {};
|
|
|
|
const NO_HAND = -1;
|
|
|
|
var controlHeld = false;
|
|
var shiftHeld = false;
|
|
|
|
var editEnabled = false;
|
|
var addingVoxels = false;
|
|
var deletingVoxels = false;
|
|
var addingSpheres = false;
|
|
var deletingSpheres = false;
|
|
var addingCubes = false;
|
|
var deletingCubes = false;
|
|
var continuousPaint = false;
|
|
var brushPointer = false;
|
|
var isActive = true;
|
|
|
|
var editSphereRadius = 0.15;
|
|
var brushLength = 0.5;
|
|
|
|
that.triggerClickMapping = Controller.newMapping(Script.resolvePath('') + '-click-voxels');
|
|
that.triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press-voxels');
|
|
that.triggeredHand = NO_HAND;
|
|
that.pressedHand = NO_HAND;
|
|
|
|
that.setActive = function(active) {
|
|
isActive = (active === true);
|
|
}
|
|
|
|
that.updateEditSettings = function(data) {
|
|
|
|
if (data.createAppMode) {
|
|
if (data.createAppMode === "voxel"){
|
|
editEnabled = true;
|
|
} else {
|
|
editEnabled = false;
|
|
}
|
|
}
|
|
|
|
if (data.voxelEditMode) {
|
|
addingVoxels = false;
|
|
deletingVoxels = false;
|
|
addingSpheres = false;
|
|
deletingSpheres = false;
|
|
addingCubes = false;
|
|
deletingCubes = false;
|
|
if (data.voxelRemove) {
|
|
if (data.voxelEditMode === "single") {
|
|
deletingVoxels = true;
|
|
} else if (data.voxelEditMode === "sphere") {
|
|
deletingSpheres = true;
|
|
} else if (data.voxelEditMode === "cube") {
|
|
deletingCubes = true;
|
|
}
|
|
} else {
|
|
if (data.voxelEditMode === "single") {
|
|
addingVoxels = true;
|
|
} else if (data.voxelEditMode === "sphere") {
|
|
addingSpheres = true;
|
|
} else if (data.voxelEditMode === "cube") {
|
|
addingCubes = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data.voxelSphereSize) {
|
|
editSphereRadius = parseFloat(data.voxelSphereSize) / 2.0;
|
|
}
|
|
|
|
if (data.voxelEditDynamics) {
|
|
if (data.voxelEditDynamics === "continuous"){
|
|
continuousPaint = true;
|
|
} else {
|
|
continuousPaint = false;
|
|
}
|
|
}
|
|
|
|
if (data.voxelPointerMode) {
|
|
if (data.voxelPointerMode === "brush") {
|
|
brushPointer = true;
|
|
} else {
|
|
brushPointer = false;
|
|
}
|
|
}
|
|
|
|
if (data.voxelBrushLength) {
|
|
voxelBrushLength = parseFloat(data.voxelBrushLength);
|
|
}
|
|
|
|
}
|
|
|
|
function floorVector(v) {
|
|
return {
|
|
x: Math.floor(v.x),
|
|
y: Math.floor(v.y),
|
|
z: Math.floor(v.z)
|
|
};
|
|
}
|
|
|
|
function attemptVoxelChangeForEntity(entityID, pickRayDir, intersectionLocation) {
|
|
var wantDebug = false;
|
|
if (wantDebug) {
|
|
print("=============== eV::attemptVoxelChangeForEntity BEG =======================");
|
|
}
|
|
|
|
var properties = Entities.getEntityProperties(entityID);
|
|
if (properties.type != "PolyVox") {
|
|
return false;
|
|
}
|
|
|
|
if (!editEnabled || !isActive) {
|
|
return false;
|
|
}
|
|
|
|
if (addingVoxels == false && deletingVoxels == false && addingSpheres == false && deletingSpheres == false) {
|
|
return false;
|
|
}
|
|
|
|
var voxelOrigin = Entities.worldCoordsToVoxelCoords(entityID, Vec3.subtract(intersectionLocation, pickRayDir));
|
|
var voxelPosition = Entities.worldCoordsToVoxelCoords(entityID, intersectionLocation);
|
|
var pickRayDirInVoxelSpace = Vec3.subtract(voxelPosition, voxelOrigin);
|
|
pickRayDirInVoxelSpace = Vec3.normalize(pickRayDirInVoxelSpace);
|
|
|
|
if (wantDebug) {
|
|
print("voxelOrigin: " + JSON.stringify(voxelOrigin));
|
|
print("voxelPosition: " + JSON.stringify(voxelPosition));
|
|
print("pickRayDirInVoxelSpace: " + JSON.stringify(pickRayDirInVoxelSpace));
|
|
}
|
|
|
|
var doAdd = addingVoxels;
|
|
var doDelete = deletingVoxels;
|
|
var doAddSphere = addingSpheres;
|
|
var doDeleteSphere = deletingSpheres;
|
|
|
|
if (controlHeld) {
|
|
if (doAdd) {
|
|
doAdd = false;
|
|
doDelete = true;
|
|
} else if (doDelete) {
|
|
doDelete = false;
|
|
doAdd = true;
|
|
} else if (doAddSphere) {
|
|
doAddSphere = false;
|
|
doDeleteSphere = true;
|
|
} else if (doDeleteSphere) {
|
|
doDeleteSphere = false;
|
|
doAddSphere = true;
|
|
}
|
|
}
|
|
|
|
if (doDelete) {
|
|
var toErasePosition = Vec3.sum(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1));
|
|
if (wantDebug) {
|
|
print("Calling setVoxel to delete");
|
|
print("entityID: " + JSON.stringify(entityID));
|
|
print("floorVector(toErasePosition): " + JSON.stringify(floorVector(toErasePosition)));
|
|
}
|
|
return Entities.setVoxel(entityID, floorVector(toErasePosition), 0);
|
|
}
|
|
if (doAdd) {
|
|
var toDrawPosition = Vec3.subtract(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1));
|
|
if (wantDebug) {
|
|
print("Calling setVoxel to add");
|
|
print("entityID: " + JSON.stringify(entityID));
|
|
print("floorVector(toDrawPosition): " + JSON.stringify(floorVector(toDrawPosition)));
|
|
}
|
|
return Entities.setVoxel(entityID, floorVector(toDrawPosition), 255);
|
|
}
|
|
if (doDeleteSphere) {
|
|
var toErasePosition = intersectionLocation;
|
|
if (wantDebug) {
|
|
print("Calling setVoxelSphere to delete");
|
|
print("entityID: " + JSON.stringify(entityID));
|
|
print("editSphereRadius: " + JSON.stringify(editSphereRadius));
|
|
print("floorVector(toErasePosition): " + JSON.stringify(floorVector(toErasePosition)));
|
|
}
|
|
return Entities.setVoxelSphere(entityID, floorVector(toErasePosition), editSphereRadius, 0);
|
|
}
|
|
if (doAddSphere) {
|
|
var toDrawPosition = intersectionLocation;
|
|
if (wantDebug) {
|
|
print("Calling setVoxelSphere to add");
|
|
print("entityID: " + JSON.stringify(entityID));
|
|
print("editSphereRadius: " + JSON.stringify(editSphereRadius));
|
|
print("floorVector(toDrawPosition): " + JSON.stringify(floorVector(toDrawPosition)));
|
|
}
|
|
return Entities.setVoxelSphere(entityID, floorVector(toDrawPosition), editSphereRadius, 255);
|
|
}
|
|
}
|
|
|
|
function attemptVoxelChange(pickRayDir, intersection) {
|
|
var wantDebug = false;
|
|
if (wantDebug) {
|
|
print("=============== eV::attemptVoxelChange BEG =======================");
|
|
}
|
|
|
|
var ids;
|
|
|
|
ids = Entities.findEntities(intersection.intersection, editSphereRadius + 1.0);
|
|
if (ids.indexOf(intersection.entityID) < 0) {
|
|
ids.push(intersection.entityID);
|
|
}
|
|
|
|
if (wantDebug) {
|
|
print("Entities: " + JSON.stringify(ids));
|
|
}
|
|
|
|
var success = false;
|
|
for (var i = 0; i < ids.length; i++) {
|
|
var entityID = ids[i];
|
|
success |= attemptVoxelChangeForEntity(entityID, pickRayDir, intersection.intersection)
|
|
}
|
|
return success;
|
|
}
|
|
|
|
function controllerComputePickRay() {
|
|
var hand = triggered() ? that.triggeredHand : that.pressedHand;
|
|
var controllerPose = getControllerWorldLocation(hand, true);
|
|
if (controllerPose.valid) {
|
|
var controllerPosition = controllerPose.translation;
|
|
// This gets point direction right, but if you want general quaternion it would be more complicated:
|
|
var controllerDirection = Quat.getUp(controllerPose.rotation);
|
|
return {origin: controllerPosition, direction: controllerDirection};
|
|
}
|
|
}
|
|
|
|
function generalComputePickRay(x, y) {
|
|
return controllerComputePickRay() || Camera.computePickRay(x, y);
|
|
}
|
|
|
|
function mousePressEvent(event) {
|
|
var wantDebug = false;
|
|
if (!editEnabled || !isActive) {
|
|
return false;
|
|
}
|
|
|
|
if (wantDebug) {
|
|
print("=============== eV::mousePressEvent BEG =======================");
|
|
}
|
|
|
|
if (!event.isLeftButton && !triggered()) {
|
|
return;
|
|
}
|
|
|
|
var pickRay = generalComputePickRay(event.x, event.y);
|
|
var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking
|
|
|
|
if (wantDebug) {
|
|
print("Pick ray: " + JSON.stringify(pickRay));
|
|
print("Intersection: " + JSON.stringify(intersection));
|
|
}
|
|
|
|
if (intersection.intersects) {
|
|
if (attemptVoxelChange(pickRay.direction, intersection)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if the PolyVox entity is empty, we can't pick against its "on" voxels. try picking against its
|
|
// bounding box, instead.
|
|
intersection = Entities.findRayIntersection(pickRay, false); // bounding box picking
|
|
if (intersection.intersects) {
|
|
attemptVoxelChange(pickRay.direction, intersection);
|
|
}
|
|
}
|
|
|
|
function mouseReleaseEvent(event) {
|
|
return;
|
|
}
|
|
|
|
function keyPressEvent(event) {
|
|
if (event.text == "CONTROL") {
|
|
controlHeld = true;
|
|
}
|
|
if (event.text == "SHIFT") {
|
|
shiftHeld = true;
|
|
}
|
|
}
|
|
|
|
function keyReleaseEvent(event) {
|
|
if (event.text == "CONTROL") {
|
|
controlHeld = false;
|
|
}
|
|
if (event.text == "SHIFT") {
|
|
shiftHeld = false;
|
|
}
|
|
}
|
|
|
|
function triggered() {
|
|
return that.triggeredHand !== NO_HAND;
|
|
};
|
|
|
|
function pointingAtDesktopWindowOrTablet(hand) {
|
|
var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand &&
|
|
SelectionManager.pointingAtDesktopWindowRight) ||
|
|
(hand === Controller.Standard.LeftHand &&
|
|
SelectionManager.pointingAtDesktopWindowLeft);
|
|
var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) ||
|
|
(hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft);
|
|
return pointingAtDesktopWindow || pointingAtTablet;
|
|
}
|
|
|
|
function makeClickHandler(hand) {
|
|
return function (clicked) {
|
|
if (!editEnabled) {
|
|
return;
|
|
}
|
|
// Don't allow both hands to trigger at the same time
|
|
if (triggered() && hand !== that.triggeredHand) {
|
|
return;
|
|
}
|
|
if (!triggered() && clicked && !pointingAtDesktopWindowOrTablet(hand)) {
|
|
that.triggeredHand = hand;
|
|
mousePressEvent({});
|
|
} else if (triggered() && !clicked) {
|
|
that.triggeredHand = NO_HAND;
|
|
mouseReleaseEvent({});
|
|
}
|
|
};
|
|
}
|
|
|
|
function makePressHandler(hand) {
|
|
return function (value) {
|
|
if (!editEnabled) {
|
|
return;
|
|
}
|
|
if (value >= TRIGGER_ON_VALUE && !triggered() && !pointingAtDesktopWindowOrTablet(hand)) {
|
|
that.pressedHand = hand;
|
|
} else {
|
|
that.pressedHand = NO_HAND;
|
|
}
|
|
}
|
|
}
|
|
|
|
function cleanup() {
|
|
Controller.mousePressEvent.disconnect(self.mousePressEvent);
|
|
Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent);
|
|
Controller.keyPressEvent.disconnect(self.keyPressEvent);
|
|
Controller.keyReleaseEvent.disconnect(self.keyReleaseEvent);
|
|
}
|
|
|
|
Controller.mousePressEvent.connect(mousePressEvent);
|
|
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
|
Controller.keyPressEvent.connect(keyPressEvent);
|
|
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
|
that.triggerClickMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
|
|
that.triggerClickMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
|
|
that.triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
|
|
that.triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
|
|
that.enableTriggerMapping = function() {
|
|
that.triggerClickMapping.enable();
|
|
that.triggerPressMapping.enable();
|
|
};
|
|
that.disableTriggerMapping = function() {
|
|
that.triggerClickMapping.disable();
|
|
that.triggerPressMapping.disable();
|
|
};
|
|
that.enableTriggerMapping();
|
|
Script.scriptEnding.connect(cleanup);
|
|
Script.scriptEnding.connect(that.disableTriggerMapping);
|
|
|
|
return that;
|
|
}
|