mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 08:29:33 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into black
This commit is contained in:
commit
2e0fa1df81
16 changed files with 147 additions and 75 deletions
|
@ -15,6 +15,11 @@ import "controls" as Controls
|
||||||
|
|
||||||
Controls.WebView {
|
Controls.WebView {
|
||||||
|
|
||||||
|
// This is for JS/QML communication, which is unused in a Web3DOverlay,
|
||||||
|
// but not having this here results in spurious warnings about a
|
||||||
|
// missing signal
|
||||||
|
signal sendToScript(var message);
|
||||||
|
|
||||||
function onWebEventReceived(event) {
|
function onWebEventReceived(event) {
|
||||||
if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
|
if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
|
||||||
ApplicationInterface.addAssetToWorldFromURL(event.slice(18));
|
ApplicationInterface.addAssetToWorldFromURL(event.slice(18));
|
||||||
|
|
|
@ -41,6 +41,11 @@ FocusScope {
|
||||||
// when they're opened.
|
// when they're opened.
|
||||||
signal showDesktop();
|
signal showDesktop();
|
||||||
|
|
||||||
|
// This is for JS/QML communication, which is unused in the Desktop,
|
||||||
|
// but not having this here results in spurious warnings about a
|
||||||
|
// missing signal
|
||||||
|
signal sendToScript(var message);
|
||||||
|
|
||||||
// Allows QML/JS to find the desktop through the parent chain
|
// Allows QML/JS to find the desktop through the parent chain
|
||||||
property bool desktopRoot: true
|
property bool desktopRoot: true
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,11 @@ ScrollingWindow {
|
||||||
|
|
||||||
HifiConstants { id: hifi }
|
HifiConstants { id: hifi }
|
||||||
|
|
||||||
|
// This is for JS/QML communication, which is unused in the AttachmentsDialog,
|
||||||
|
// but not having this here results in spurious warnings about a
|
||||||
|
// missing signal
|
||||||
|
signal sendToScript(var message);
|
||||||
|
|
||||||
property var settings: Settings {
|
property var settings: Settings {
|
||||||
category: "AttachmentsDialog"
|
category: "AttachmentsDialog"
|
||||||
property alias x: root.x
|
property alias x: root.x
|
||||||
|
|
|
@ -6911,6 +6911,12 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::takeSecondaryCameraSnapshot() {
|
||||||
|
postLambdaEvent([this] {
|
||||||
|
Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Application::shareSnapshot(const QString& path, const QUrl& href) {
|
void Application::shareSnapshot(const QString& path, const QUrl& href) {
|
||||||
postLambdaEvent([path, href] {
|
postLambdaEvent([path, href] {
|
||||||
// not much to do here, everything is done in snapshot code...
|
// not much to do here, everything is done in snapshot code...
|
||||||
|
|
|
@ -274,6 +274,7 @@ public:
|
||||||
float getAverageSimsPerSecond() const { return _simCounter.rate(); }
|
float getAverageSimsPerSecond() const { return _simCounter.rate(); }
|
||||||
|
|
||||||
void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f);
|
void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f);
|
||||||
|
void takeSecondaryCameraSnapshot();
|
||||||
void shareSnapshot(const QString& filename, const QUrl& href = QUrl(""));
|
void shareSnapshot(const QString& filename, const QUrl& href = QUrl(""));
|
||||||
|
|
||||||
model::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; }
|
model::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; }
|
||||||
|
|
|
@ -294,6 +294,10 @@ void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, f
|
||||||
qApp->takeSnapshot(notify, includeAnimated, aspectRatio);
|
qApp->takeSnapshot(notify, includeAnimated, aspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WindowScriptingInterface::takeSecondaryCameraSnapshot() {
|
||||||
|
qApp->takeSecondaryCameraSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& href) {
|
void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& href) {
|
||||||
qApp->shareSnapshot(path, href);
|
qApp->shareSnapshot(path, href);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ public slots:
|
||||||
void showAssetServer(const QString& upload = "");
|
void showAssetServer(const QString& upload = "");
|
||||||
void copyToClipboard(const QString& text);
|
void copyToClipboard(const QString& text);
|
||||||
void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f);
|
void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f);
|
||||||
|
void takeSecondaryCameraSnapshot();
|
||||||
void makeConnection(bool success, const QString& userNameOrError);
|
void makeConnection(bool success, const QString& userNameOrError);
|
||||||
void displayAnnouncement(const QString& message);
|
void displayAnnouncement(const QString& message);
|
||||||
void shareSnapshot(const QString& path, const QUrl& href = QUrl(""));
|
void shareSnapshot(const QString& path, const QUrl& href = QUrl(""));
|
||||||
|
|
|
@ -34,3 +34,7 @@ void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) {
|
||||||
QImage NullDisplayPlugin::getScreenshot(float aspectRatio) const {
|
QImage NullDisplayPlugin::getScreenshot(float aspectRatio) const {
|
||||||
return QImage();
|
return QImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QImage NullDisplayPlugin::getSecondaryCameraScreenshot() const {
|
||||||
|
return QImage();
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ public:
|
||||||
bool hasFocus() const override;
|
bool hasFocus() const override;
|
||||||
void submitFrame(const gpu::FramePointer& newFrame) override;
|
void submitFrame(const gpu::FramePointer& newFrame) override;
|
||||||
QImage getScreenshot(float aspectRatio = 0.0f) const override;
|
QImage getScreenshot(float aspectRatio = 0.0f) const override;
|
||||||
|
QImage getSecondaryCameraScreenshot() const override;
|
||||||
void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {};
|
void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {};
|
||||||
private:
|
private:
|
||||||
static const QString NAME;
|
static const QString NAME;
|
||||||
|
|
|
@ -775,6 +775,19 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
|
||||||
return screenshot.mirrored(false, true);
|
return screenshot.mirrored(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() const {
|
||||||
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
auto secondaryCameraFramebuffer = textureCache->getSpectatorCameraFramebuffer();
|
||||||
|
gpu::Vec4i region(0, 0, secondaryCameraFramebuffer->getWidth(), secondaryCameraFramebuffer->getHeight());
|
||||||
|
|
||||||
|
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
|
||||||
|
QImage screenshot(region.z, region.w, QImage::Format_ARGB32);
|
||||||
|
withMainThreadContext([&] {
|
||||||
|
glBackend->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot);
|
||||||
|
});
|
||||||
|
return screenshot.mirrored(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
glm::uvec2 OpenGLDisplayPlugin::getSurfacePixels() const {
|
glm::uvec2 OpenGLDisplayPlugin::getSurfacePixels() const {
|
||||||
uvec2 result;
|
uvec2 result;
|
||||||
auto window = _container->getPrimaryWidget();
|
auto window = _container->getPrimaryWidget();
|
||||||
|
|
|
@ -60,6 +60,7 @@ public:
|
||||||
virtual bool setDisplayTexture(const QString& name) override;
|
virtual bool setDisplayTexture(const QString& name) override;
|
||||||
virtual bool onDisplayTextureReset() { return false; };
|
virtual bool onDisplayTextureReset() { return false; };
|
||||||
QImage getScreenshot(float aspectRatio = 0.0f) const override;
|
QImage getScreenshot(float aspectRatio = 0.0f) const override;
|
||||||
|
QImage getSecondaryCameraScreenshot() const override;
|
||||||
|
|
||||||
float presentRate() const override;
|
float presentRate() const override;
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,7 @@ public:
|
||||||
|
|
||||||
// Fetch the most recently displayed image as a QImage
|
// Fetch the most recently displayed image as a QImage
|
||||||
virtual QImage getScreenshot(float aspectRatio = 0.0f) const = 0;
|
virtual QImage getScreenshot(float aspectRatio = 0.0f) const = 0;
|
||||||
|
virtual QImage getSecondaryCameraScreenshot() const = 0;
|
||||||
|
|
||||||
// will query the underlying hmd api to compute the most recent head pose
|
// will query the underlying hmd api to compute the most recent head pose
|
||||||
virtual bool beginFrameRender(uint32_t frameIndex) { return true; }
|
virtual bool beginFrameRender(uint32_t frameIndex) { return true; }
|
||||||
|
|
|
@ -1228,7 +1228,7 @@ Script.scriptEnding.connect(function () {
|
||||||
Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered);
|
Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered);
|
||||||
Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent);
|
Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent);
|
||||||
|
|
||||||
Messages.messageReceived.disconnect(handleOverlaySelectionToolUpdates);
|
Messages.messageReceived.disconnect(handleMessagesReceived);
|
||||||
Messages.unsubscribe("entityToolUpdates");
|
Messages.unsubscribe("entityToolUpdates");
|
||||||
Messages.unsubscribe("Toolbar-DomainChanged");
|
Messages.unsubscribe("Toolbar-DomainChanged");
|
||||||
createButton = null;
|
createButton = null;
|
||||||
|
|
|
@ -61,6 +61,7 @@
|
||||||
tablet.gotoHomeScreen();
|
tablet.gotoHomeScreen();
|
||||||
}
|
}
|
||||||
button.clicked.disconnect(onClicked);
|
button.clicked.disconnect(onClicked);
|
||||||
|
tablet.screenChanged.disconnect(onScreenChanged);
|
||||||
Script.clearInterval(interval);
|
Script.clearInterval(interval);
|
||||||
if (tablet) {
|
if (tablet) {
|
||||||
tablet.removeButton(button);
|
tablet.removeButton(button);
|
||||||
|
|
|
@ -30,11 +30,13 @@ function objectTranslationPlanePoint(position, dimensions) {
|
||||||
SelectionManager = (function() {
|
SelectionManager = (function() {
|
||||||
var that = {};
|
var that = {};
|
||||||
|
|
||||||
|
// FUNCTION: SUBSCRIBE TO UPDATE MESSAGES
|
||||||
function subscribeToUpdateMessages() {
|
function subscribeToUpdateMessages() {
|
||||||
Messages.subscribe("entityToolUpdates");
|
Messages.subscribe("entityToolUpdates");
|
||||||
Messages.messageReceived.connect(handleEntitySelectionToolUpdates);
|
Messages.messageReceived.connect(handleEntitySelectionToolUpdates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FUNCTION: HANDLE ENTITY SELECTION TOOL UDPATES
|
||||||
function handleEntitySelectionToolUpdates(channel, message, sender) {
|
function handleEntitySelectionToolUpdates(channel, message, sender) {
|
||||||
if (channel !== 'entityToolUpdates') {
|
if (channel !== 'entityToolUpdates') {
|
||||||
return;
|
return;
|
||||||
|
@ -238,6 +240,7 @@ function normalizeDegrees(degrees) {
|
||||||
return degrees;
|
return degrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FUNCTION: getRelativeCenterPosition
|
||||||
// Return the enter position of an entity relative to it's registrationPoint
|
// Return the enter position of an entity relative to it's registrationPoint
|
||||||
// A registration point of (0.5, 0.5, 0.5) will have an offset of (0, 0, 0)
|
// A registration point of (0.5, 0.5, 0.5) will have an offset of (0, 0, 0)
|
||||||
// A registration point of (1.0, 1.0, 1.0) will have an offset of (-dimensions.x / 2, -dimensions.y / 2, -dimensions.z / 2)
|
// A registration point of (1.0, 1.0, 1.0) will have an offset of (-dimensions.x / 2, -dimensions.y / 2, -dimensions.z / 2)
|
||||||
|
@ -249,6 +252,7 @@ function getRelativeCenterPosition(dimensions, registrationPoint) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SELECTION DISPLAY DEFINITION
|
||||||
SelectionDisplay = (function() {
|
SelectionDisplay = (function() {
|
||||||
var that = {};
|
var that = {};
|
||||||
|
|
||||||
|
@ -1152,6 +1156,7 @@ SelectionDisplay = (function() {
|
||||||
that.updateHandles();
|
that.updateHandles();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FUNCTION: UPDATE ROTATION HANDLES
|
||||||
that.updateRotationHandles = function() {
|
that.updateRotationHandles = function() {
|
||||||
var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1;
|
var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1;
|
||||||
var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5);
|
var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5);
|
||||||
|
@ -1545,11 +1550,6 @@ SelectionDisplay = (function() {
|
||||||
translateHandlesVisible = false;
|
translateHandlesVisible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rotation = selectionManager.worldRotation;
|
|
||||||
var dimensions = selectionManager.worldDimensions;
|
|
||||||
var position = selectionManager.worldPosition;
|
|
||||||
|
|
||||||
|
|
||||||
Overlays.editOverlay(rotateOverlayTarget, {
|
Overlays.editOverlay(rotateOverlayTarget, {
|
||||||
visible: rotationOverlaysVisible
|
visible: rotationOverlaysVisible
|
||||||
});
|
});
|
||||||
|
@ -1577,6 +1577,7 @@ SelectionDisplay = (function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FUNCTION: SET SPACE MODE
|
||||||
that.setSpaceMode = function(newSpaceMode) {
|
that.setSpaceMode = function(newSpaceMode) {
|
||||||
if (spaceMode != newSpaceMode) {
|
if (spaceMode != newSpaceMode) {
|
||||||
spaceMode = newSpaceMode;
|
spaceMode = newSpaceMode;
|
||||||
|
@ -1584,6 +1585,7 @@ SelectionDisplay = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FUNCTION: TOGGLE SPACE MODE
|
||||||
that.toggleSpaceMode = function() {
|
that.toggleSpaceMode = function() {
|
||||||
if (spaceMode == SPACE_WORLD && SelectionManager.selections.length > 1) {
|
if (spaceMode == SPACE_WORLD && SelectionManager.selections.length > 1) {
|
||||||
print("Local space editing is not available with multiple selections");
|
print("Local space editing is not available with multiple selections");
|
||||||
|
@ -1593,8 +1595,11 @@ SelectionDisplay = (function() {
|
||||||
that.updateHandles();
|
that.updateHandles();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FUNCTION: UNSELECT ALL
|
||||||
|
// TODO?: Needs implementation
|
||||||
that.unselectAll = function() {};
|
that.unselectAll = function() {};
|
||||||
|
|
||||||
|
// FUNCTION: UPDATE HANDLES
|
||||||
that.updateHandles = function() {
|
that.updateHandles = function() {
|
||||||
if (SelectionManager.selections.length === 0) {
|
if (SelectionManager.selections.length === 0) {
|
||||||
that.setOverlaysVisible(false);
|
that.setOverlaysVisible(false);
|
||||||
|
@ -2168,10 +2173,10 @@ SelectionDisplay = (function() {
|
||||||
position: EdgeTR
|
position: EdgeTR
|
||||||
});
|
});
|
||||||
|
|
||||||
var boxPosition = Vec3.multiplyQbyV(rotation, center);
|
var selectionBoxPosition = Vec3.multiplyQbyV(rotation, center);
|
||||||
boxPosition = Vec3.sum(position, boxPosition);
|
selectionBoxPosition = Vec3.sum(position, selectionBoxPosition);
|
||||||
Overlays.editOverlay(selectionBox, {
|
Overlays.editOverlay(selectionBox, {
|
||||||
position: boxPosition,
|
position: selectionBoxPosition,
|
||||||
dimensions: dimensions,
|
dimensions: dimensions,
|
||||||
rotation: rotation,
|
rotation: rotation,
|
||||||
visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"),
|
visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"),
|
||||||
|
@ -2218,13 +2223,13 @@ SelectionDisplay = (function() {
|
||||||
var offset = vec3Mult(props.dimensions, centeredRP);
|
var offset = vec3Mult(props.dimensions, centeredRP);
|
||||||
offset = Vec3.multiply(-1, offset);
|
offset = Vec3.multiply(-1, offset);
|
||||||
offset = Vec3.multiplyQbyV(props.rotation, offset);
|
offset = Vec3.multiplyQbyV(props.rotation, offset);
|
||||||
var boxPosition = Vec3.sum(props.position, offset);
|
var curBoxPosition = Vec3.sum(props.position, offset);
|
||||||
|
|
||||||
var color = {red: 255, green: 128, blue: 0};
|
var color = {red: 255, green: 128, blue: 0};
|
||||||
if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64};
|
if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64};
|
||||||
|
|
||||||
Overlays.editOverlay(selectionBoxes[i], {
|
Overlays.editOverlay(selectionBoxes[i], {
|
||||||
position: boxPosition,
|
position: curBoxPosition,
|
||||||
color: color,
|
color: color,
|
||||||
rotation: props.rotation,
|
rotation: props.rotation,
|
||||||
dimensions: props.dimensions,
|
dimensions: props.dimensions,
|
||||||
|
@ -2305,9 +2310,9 @@ SelectionDisplay = (function() {
|
||||||
x: position.x,
|
x: position.x,
|
||||||
y: position.y + worldTop + grabberMoveUpOffset,
|
y: position.y + worldTop + grabberMoveUpOffset,
|
||||||
z: position.z
|
z: position.z
|
||||||
}
|
};
|
||||||
Overlays.editOverlay(grabberMoveUp, {
|
Overlays.editOverlay(grabberMoveUp, {
|
||||||
visible: activeTool == null || mode == "TRANSLATE_UP_DOWN"
|
visible: (activeTool === null) || (mode == "TRANSLATE_UP_DOWN")
|
||||||
});
|
});
|
||||||
|
|
||||||
Overlays.editOverlay(baseOfEntityProjectionOverlay, {
|
Overlays.editOverlay(baseOfEntityProjectionOverlay, {
|
||||||
|
@ -2327,21 +2332,24 @@ SelectionDisplay = (function() {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FUNCTION: SET OVERLAYS VISIBLE
|
||||||
that.setOverlaysVisible = function(isVisible) {
|
that.setOverlaysVisible = function(isVisible) {
|
||||||
var length = allOverlays.length;
|
var length = allOverlays.length;
|
||||||
for (var i = 0; i < length; i++) {
|
for (var overlayIndex = 0; overlayIndex < length; overlayIndex++) {
|
||||||
Overlays.editOverlay(allOverlays[i], {
|
Overlays.editOverlay(allOverlays[overlayIndex], {
|
||||||
visible: isVisible
|
visible: isVisible
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
length = selectionBoxes.length;
|
length = selectionBoxes.length;
|
||||||
for (var i = 0; i < length; i++) {
|
for (var boxIndex = 0; boxIndex < length; boxIndex++) {
|
||||||
Overlays.editOverlay(selectionBoxes[i], {
|
Overlays.editOverlay(selectionBoxes[boxIndex], {
|
||||||
visible: isVisible
|
visible: isVisible
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FUNCTION: UNSELECT
|
||||||
|
// TODO?: Needs implementation
|
||||||
that.unselect = function(entityID) {};
|
that.unselect = function(entityID) {};
|
||||||
|
|
||||||
var initialXZPick = null;
|
var initialXZPick = null;
|
||||||
|
@ -2350,6 +2358,7 @@ SelectionDisplay = (function() {
|
||||||
var startPosition = null;
|
var startPosition = null;
|
||||||
var duplicatedEntityIDs = null;
|
var duplicatedEntityIDs = null;
|
||||||
|
|
||||||
|
// TOOL DEFINITION: TRANSLATE XZ TOOL
|
||||||
var translateXZTool = {
|
var translateXZTool = {
|
||||||
mode: 'TRANSLATE_XZ',
|
mode: 'TRANSLATE_XZ',
|
||||||
pickPlanePosition: { x: 0, y: 0, z: 0 },
|
pickPlanePosition: { x: 0, y: 0, z: 0 },
|
||||||
|
@ -2538,7 +2547,8 @@ SelectionDisplay = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var lastXYPick = null
|
// GRABBER TOOL: GRABBER MOVE UP
|
||||||
|
var lastXYPick = null;
|
||||||
var upDownPickNormal = null;
|
var upDownPickNormal = null;
|
||||||
addGrabberTool(grabberMoveUp, {
|
addGrabberTool(grabberMoveUp, {
|
||||||
mode: "TRANSLATE_UP_DOWN",
|
mode: "TRANSLATE_UP_DOWN",
|
||||||
|
@ -2594,7 +2604,7 @@ SelectionDisplay = (function() {
|
||||||
print(" event.y:" + event.y);
|
print(" event.y:" + event.y);
|
||||||
Vec3.print(" newIntersection:", newIntersection);
|
Vec3.print(" newIntersection:", newIntersection);
|
||||||
Vec3.print(" vector:", vector);
|
Vec3.print(" vector:", vector);
|
||||||
Vec3.print(" newPosition:", newPosition);
|
//Vec3.print(" newPosition:", newPosition);
|
||||||
}
|
}
|
||||||
for (var i = 0; i < SelectionManager.selections.length; i++) {
|
for (var i = 0; i < SelectionManager.selections.length; i++) {
|
||||||
var id = SelectionManager.selections[i];
|
var id = SelectionManager.selections[i];
|
||||||
|
@ -2612,6 +2622,7 @@ SelectionDisplay = (function() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GRABBER TOOL: GRABBER CLONER
|
||||||
addGrabberTool(grabberCloner, {
|
addGrabberTool(grabberCloner, {
|
||||||
mode: "CLONE",
|
mode: "CLONE",
|
||||||
onBegin: function(event) {
|
onBegin: function(event) {
|
||||||
|
@ -2639,7 +2650,7 @@ SelectionDisplay = (function() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// FUNCTION: VEC 3 MULT
|
||||||
var vec3Mult = function(v1, v2) {
|
var vec3Mult = function(v1, v2) {
|
||||||
return {
|
return {
|
||||||
x: v1.x * v2.x,
|
x: v1.x * v2.x,
|
||||||
|
@ -2647,6 +2658,8 @@ SelectionDisplay = (function() {
|
||||||
z: v1.z * v2.z
|
z: v1.z * v2.z
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FUNCTION: MAKE STRETCH TOOL
|
||||||
// stretchMode - name of mode
|
// stretchMode - name of mode
|
||||||
// direction - direction to stretch in
|
// direction - direction to stretch in
|
||||||
// pivot - point to use as a pivot
|
// pivot - point to use as a pivot
|
||||||
|
@ -2898,13 +2911,14 @@ SelectionDisplay = (function() {
|
||||||
|
|
||||||
// Are we using handControllers or Mouse - only relevant for 3D tools
|
// Are we using handControllers or Mouse - only relevant for 3D tools
|
||||||
var controllerPose = getControllerWorldLocation(activeHand, true);
|
var controllerPose = getControllerWorldLocation(activeHand, true);
|
||||||
if (HMD.isHMDAvailable()
|
var vector = null;
|
||||||
&& HMD.isHandControllerAvailable() && controllerPose.valid && that.triggered && directionFor3DStretch) {
|
if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() &&
|
||||||
|
controllerPose.valid && that.triggered && directionFor3DStretch) {
|
||||||
localDeltaPivot = deltaPivot3D;
|
localDeltaPivot = deltaPivot3D;
|
||||||
|
|
||||||
newPick = pickRay.origin;
|
newPick = pickRay.origin;
|
||||||
|
|
||||||
var vector = Vec3.subtract(newPick, lastPick3D);
|
vector = Vec3.subtract(newPick, lastPick3D);
|
||||||
|
|
||||||
vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector);
|
vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector);
|
||||||
|
|
||||||
|
@ -2919,7 +2933,7 @@ SelectionDisplay = (function() {
|
||||||
newPick = rayPlaneIntersection(pickRay,
|
newPick = rayPlaneIntersection(pickRay,
|
||||||
pickRayPosition,
|
pickRayPosition,
|
||||||
planeNormal);
|
planeNormal);
|
||||||
var vector = Vec3.subtract(newPick, lastPick);
|
vector = Vec3.subtract(newPick, lastPick);
|
||||||
|
|
||||||
vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector);
|
vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector);
|
||||||
|
|
||||||
|
@ -2955,41 +2969,39 @@ SelectionDisplay = (function() {
|
||||||
} else {
|
} else {
|
||||||
newDimensions = Vec3.sum(initialDimensions, changeInDimensions);
|
newDimensions = Vec3.sum(initialDimensions, changeInDimensions);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION);
|
|
||||||
newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION);
|
|
||||||
newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION);
|
|
||||||
|
|
||||||
var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions));
|
newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION);
|
||||||
var newPosition = Vec3.sum(initialPosition, changeInPosition);
|
newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION);
|
||||||
|
newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION);
|
||||||
|
|
||||||
for (var i = 0; i < SelectionManager.selections.length; i++) {
|
var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions));
|
||||||
Entities.editEntity(SelectionManager.selections[i], {
|
var newPosition = Vec3.sum(initialPosition, changeInPosition);
|
||||||
position: newPosition,
|
|
||||||
dimensions: newDimensions,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var wantDebug = false;
|
for (var i = 0; i < SelectionManager.selections.length; i++) {
|
||||||
if (wantDebug) {
|
Entities.editEntity(SelectionManager.selections[i], {
|
||||||
print(stretchMode);
|
position: newPosition,
|
||||||
//Vec3.print(" newIntersection:", newIntersection);
|
dimensions: newDimensions,
|
||||||
Vec3.print(" vector:", vector);
|
});
|
||||||
//Vec3.print(" oldPOS:", oldPOS);
|
}
|
||||||
//Vec3.print(" newPOS:", newPOS);
|
|
||||||
Vec3.print(" changeInDimensions:", changeInDimensions);
|
|
||||||
Vec3.print(" newDimensions:", newDimensions);
|
|
||||||
|
|
||||||
Vec3.print(" changeInPosition:", changeInPosition);
|
var wantDebug = false;
|
||||||
Vec3.print(" newPosition:", newPosition);
|
if (wantDebug) {
|
||||||
|
print(stretchMode);
|
||||||
|
//Vec3.print(" newIntersection:", newIntersection);
|
||||||
|
Vec3.print(" vector:", vector);
|
||||||
|
//Vec3.print(" oldPOS:", oldPOS);
|
||||||
|
//Vec3.print(" newPOS:", newPOS);
|
||||||
|
Vec3.print(" changeInDimensions:", changeInDimensions);
|
||||||
|
Vec3.print(" newDimensions:", newDimensions);
|
||||||
|
|
||||||
|
Vec3.print(" changeInPosition:", changeInPosition);
|
||||||
|
Vec3.print(" newPosition:", newPosition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectionManager._update();
|
SelectionManager._update();
|
||||||
|
};//--End of onMove def
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mode: stretchMode,
|
mode: stretchMode,
|
||||||
|
@ -3042,7 +3054,8 @@ SelectionDisplay = (function() {
|
||||||
z: -1
|
z: -1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FUNCTION: GET DIRECTION FOR 3D STRETCH
|
||||||
// Returns a vector with directions for the stretch tool in 3D using hand controllers
|
// Returns a vector with directions for the stretch tool in 3D using hand controllers
|
||||||
function getDirectionsFor3DStretch(mode) {
|
function getDirectionsFor3DStretch(mode) {
|
||||||
if (mode === "STRETCH_LBN") {
|
if (mode === "STRETCH_LBN") {
|
||||||
|
@ -3067,7 +3080,7 @@ SelectionDisplay = (function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FUNCTION: ADD STRETCH TOOL
|
||||||
function addStretchTool(overlay, mode, pivot, direction, offset, handleMove) {
|
function addStretchTool(overlay, mode, pivot, direction, offset, handleMove) {
|
||||||
if (!pivot) {
|
if (!pivot) {
|
||||||
pivot = direction;
|
pivot = direction;
|
||||||
|
@ -3077,6 +3090,7 @@ SelectionDisplay = (function() {
|
||||||
addGrabberTool(overlay, tool);
|
addGrabberTool(overlay, tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FUNCTION: CUTOFF STRETCH FUNC
|
||||||
function cutoffStretchFunc(vector, change) {
|
function cutoffStretchFunc(vector, change) {
|
||||||
vector = change;
|
vector = change;
|
||||||
Vec3.print("Radius stretch: ", vector);
|
Vec3.print("Radius stretch: ", vector);
|
||||||
|
@ -3097,6 +3111,7 @@ SelectionDisplay = (function() {
|
||||||
SelectionManager._update();
|
SelectionManager._update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FUNCTION: RADIUS STRETCH FUNC
|
||||||
function radiusStretchFunc(vector, change) {
|
function radiusStretchFunc(vector, change) {
|
||||||
var props = selectionManager.savedProperties[selectionManager.selections[0]];
|
var props = selectionManager.savedProperties[selectionManager.selections[0]];
|
||||||
|
|
||||||
|
@ -3123,6 +3138,7 @@ SelectionDisplay = (function() {
|
||||||
SelectionManager._update();
|
SelectionManager._update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// STRETCH TOOL DEF SECTION
|
||||||
addStretchTool(grabberNEAR, "STRETCH_NEAR", {
|
addStretchTool(grabberNEAR, "STRETCH_NEAR", {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -3529,6 +3545,7 @@ SelectionDisplay = (function() {
|
||||||
z: 1
|
z: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// FUNCTION: UPDATE ROTATION DEGREES OVERLAY
|
||||||
function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) {
|
function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) {
|
||||||
var angle = angleFromZero * (Math.PI / 180);
|
var angle = angleFromZero * (Math.PI / 180);
|
||||||
var position = {
|
var position = {
|
||||||
|
@ -3549,6 +3566,7 @@ SelectionDisplay = (function() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// YAW GRABBER TOOL DEFINITION
|
||||||
var initialPosition = SelectionManager.worldPosition;
|
var initialPosition = SelectionManager.worldPosition;
|
||||||
addGrabberTool(yawHandle, {
|
addGrabberTool(yawHandle, {
|
||||||
mode: "ROTATE_YAW",
|
mode: "ROTATE_YAW",
|
||||||
|
@ -3625,10 +3643,10 @@ SelectionDisplay = (function() {
|
||||||
if (result.intersects) {
|
if (result.intersects) {
|
||||||
var center = yawCenter;
|
var center = yawCenter;
|
||||||
var zero = yawZero;
|
var zero = yawZero;
|
||||||
// TODO: these vectors are backwards to their names, doesn't matter for this use case (inverted vectors still give same angle)
|
var centerToZero = Vec3.subtract(zero, center);
|
||||||
var centerToZero = Vec3.subtract(center, zero);
|
var centerToIntersect = Vec3.subtract(result.intersection, center);
|
||||||
var centerToIntersect = Vec3.subtract(center, result.intersection);
|
// Note: orientedAngle which wants normalized centerToZero and centerToIntersect
|
||||||
// TODO: orientedAngle wants normalized centerToZero and centerToIntersect
|
// handles that internally, so it's to pass unnormalized vectors here.
|
||||||
var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
|
var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
|
||||||
var distanceFromCenter = Vec3.distance(center, result.intersection);
|
var distanceFromCenter = Vec3.distance(center, result.intersection);
|
||||||
var snapToInner = distanceFromCenter < innerRadius;
|
var snapToInner = distanceFromCenter < innerRadius;
|
||||||
|
@ -3717,6 +3735,7 @@ SelectionDisplay = (function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// PITCH GRABBER TOOL DEFINITION
|
||||||
addGrabberTool(pitchHandle, {
|
addGrabberTool(pitchHandle, {
|
||||||
mode: "ROTATE_PITCH",
|
mode: "ROTATE_PITCH",
|
||||||
onBegin: function(event) {
|
onBegin: function(event) {
|
||||||
|
@ -3789,11 +3808,12 @@ SelectionDisplay = (function() {
|
||||||
var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]);
|
var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]);
|
||||||
|
|
||||||
if (result.intersects) {
|
if (result.intersects) {
|
||||||
var properties = Entities.getEntityProperties(selectionManager.selections[0]);
|
|
||||||
var center = pitchCenter;
|
var center = pitchCenter;
|
||||||
var zero = pitchZero;
|
var zero = pitchZero;
|
||||||
var centerToZero = Vec3.subtract(center, zero);
|
var centerToZero = Vec3.subtract(zero, center);
|
||||||
var centerToIntersect = Vec3.subtract(center, result.intersection);
|
var centerToIntersect = Vec3.subtract(result.intersection, center);
|
||||||
|
// Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles
|
||||||
|
// this internally, so it's fine to pass non-normalized versions here.
|
||||||
var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
|
var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
|
||||||
|
|
||||||
var distanceFromCenter = Vec3.distance(center, result.intersection);
|
var distanceFromCenter = Vec3.distance(center, result.intersection);
|
||||||
|
@ -3809,7 +3829,6 @@ SelectionDisplay = (function() {
|
||||||
|
|
||||||
for (var i = 0; i < SelectionManager.selections.length; i++) {
|
for (var i = 0; i < SelectionManager.selections.length; i++) {
|
||||||
var entityID = SelectionManager.selections[i];
|
var entityID = SelectionManager.selections[i];
|
||||||
var properties = Entities.getEntityProperties(entityID);
|
|
||||||
var initialProperties = SelectionManager.savedProperties[entityID];
|
var initialProperties = SelectionManager.savedProperties[entityID];
|
||||||
var dPos = Vec3.subtract(initialProperties.position, initialPosition);
|
var dPos = Vec3.subtract(initialProperties.position, initialPosition);
|
||||||
dPos = Vec3.multiplyQbyV(pitchChange, dPos);
|
dPos = Vec3.multiplyQbyV(pitchChange, dPos);
|
||||||
|
@ -3874,6 +3893,7 @@ SelectionDisplay = (function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ROLL GRABBER TOOL DEFINITION
|
||||||
addGrabberTool(rollHandle, {
|
addGrabberTool(rollHandle, {
|
||||||
mode: "ROTATE_ROLL",
|
mode: "ROTATE_ROLL",
|
||||||
onBegin: function(event) {
|
onBegin: function(event) {
|
||||||
|
@ -3946,11 +3966,12 @@ SelectionDisplay = (function() {
|
||||||
var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]);
|
var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]);
|
||||||
|
|
||||||
if (result.intersects) {
|
if (result.intersects) {
|
||||||
var properties = Entities.getEntityProperties(selectionManager.selections[0]);
|
|
||||||
var center = rollCenter;
|
var center = rollCenter;
|
||||||
var zero = rollZero;
|
var zero = rollZero;
|
||||||
var centerToZero = Vec3.subtract(center, zero);
|
var centerToZero = Vec3.subtract(zero, center);
|
||||||
var centerToIntersect = Vec3.subtract(center, result.intersection);
|
var centerToIntersect = Vec3.subtract(result.intersection, center);
|
||||||
|
// Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles
|
||||||
|
// this internally, so it's fine to pass non-normalized versions here.
|
||||||
var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
|
var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
|
||||||
|
|
||||||
var distanceFromCenter = Vec3.distance(center, result.intersection);
|
var distanceFromCenter = Vec3.distance(center, result.intersection);
|
||||||
|
@ -3965,7 +3986,6 @@ SelectionDisplay = (function() {
|
||||||
});
|
});
|
||||||
for (var i = 0; i < SelectionManager.selections.length; i++) {
|
for (var i = 0; i < SelectionManager.selections.length; i++) {
|
||||||
var entityID = SelectionManager.selections[i];
|
var entityID = SelectionManager.selections[i];
|
||||||
var properties = Entities.getEntityProperties(entityID);
|
|
||||||
var initialProperties = SelectionManager.savedProperties[entityID];
|
var initialProperties = SelectionManager.savedProperties[entityID];
|
||||||
var dPos = Vec3.subtract(initialProperties.position, initialPosition);
|
var dPos = Vec3.subtract(initialProperties.position, initialPosition);
|
||||||
dPos = Vec3.multiplyQbyV(rollChange, dPos);
|
dPos = Vec3.multiplyQbyV(rollChange, dPos);
|
||||||
|
@ -4030,6 +4050,7 @@ SelectionDisplay = (function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// FUNCTION: CHECK MOVE
|
||||||
that.checkMove = function() {
|
that.checkMove = function() {
|
||||||
if (SelectionManager.hasSelection()) {
|
if (SelectionManager.hasSelection()) {
|
||||||
|
|
||||||
|
@ -4044,6 +4065,7 @@ SelectionDisplay = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FUNCTION: MOUSE PRESS EVENT
|
||||||
that.mousePressEvent = function(event) {
|
that.mousePressEvent = function(event) {
|
||||||
var wantDebug = false;
|
var wantDebug = false;
|
||||||
if (!event.isLeftButton && !that.triggered) {
|
if (!event.isLeftButton && !that.triggered) {
|
||||||
|
@ -4167,7 +4189,7 @@ SelectionDisplay = (function() {
|
||||||
|
|
||||||
|
|
||||||
// Only intersect versus yaw/pitch/roll.
|
// Only intersect versus yaw/pitch/roll.
|
||||||
var result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] );
|
result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] );
|
||||||
|
|
||||||
var overlayOrientation;
|
var overlayOrientation;
|
||||||
var overlayCenter;
|
var overlayCenter;
|
||||||
|
@ -4184,10 +4206,10 @@ SelectionDisplay = (function() {
|
||||||
originalRoll = roll;
|
originalRoll = roll;
|
||||||
|
|
||||||
if (result.intersects) {
|
if (result.intersects) {
|
||||||
var tool = grabberTools[result.overlayID];
|
var resultTool = grabberTools[result.overlayID];
|
||||||
if (tool) {
|
if (resultTool) {
|
||||||
activeTool = tool;
|
activeTool = resultTool;
|
||||||
mode = tool.mode;
|
mode = resultTool.mode;
|
||||||
somethingClicked = 'tool';
|
somethingClicked = 'tool';
|
||||||
if (activeTool && activeTool.onBegin) {
|
if (activeTool && activeTool.onBegin) {
|
||||||
activeTool.onBegin(event);
|
activeTool.onBegin(event);
|
||||||
|
@ -4370,7 +4392,7 @@ SelectionDisplay = (function() {
|
||||||
|
|
||||||
if (!somethingClicked) {
|
if (!somethingClicked) {
|
||||||
// Only intersect versus selectionBox.
|
// Only intersect versus selectionBox.
|
||||||
var result = Overlays.findRayIntersection(pickRay, true, [selectionBox]);
|
result = Overlays.findRayIntersection(pickRay, true, [selectionBox]);
|
||||||
if (result.intersects) {
|
if (result.intersects) {
|
||||||
switch (result.overlayID) {
|
switch (result.overlayID) {
|
||||||
case selectionBox:
|
case selectionBox:
|
||||||
|
@ -4425,6 +4447,7 @@ SelectionDisplay = (function() {
|
||||||
return somethingClicked;
|
return somethingClicked;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FUNCTION: MOUSE MOVE EVENT
|
||||||
that.mouseMoveEvent = function(event) {
|
that.mouseMoveEvent = function(event) {
|
||||||
if (activeTool) {
|
if (activeTool) {
|
||||||
activeTool.onMove(event);
|
activeTool.onMove(event);
|
||||||
|
@ -4554,7 +4577,7 @@ SelectionDisplay = (function() {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FUNCTION: UPDATE HANDLE SIZES
|
||||||
that.updateHandleSizes = function() {
|
that.updateHandleSizes = function() {
|
||||||
if (selectionManager.hasSelection()) {
|
if (selectionManager.hasSelection()) {
|
||||||
var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition());
|
var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition());
|
||||||
|
@ -4593,6 +4616,7 @@ SelectionDisplay = (function() {
|
||||||
};
|
};
|
||||||
Script.update.connect(that.updateHandleSizes);
|
Script.update.connect(that.updateHandleSizes);
|
||||||
|
|
||||||
|
// FUNCTION: MOUSE RELEASE EVENT
|
||||||
that.mouseReleaseEvent = function(event) {
|
that.mouseReleaseEvent = function(event) {
|
||||||
var showHandles = false;
|
var showHandles = false;
|
||||||
if (activeTool && activeTool.onEnd) {
|
if (activeTool && activeTool.onEnd) {
|
||||||
|
|
|
@ -764,8 +764,8 @@ Script.scriptEnding.connect(function () {
|
||||||
}
|
}
|
||||||
Window.snapshotShared.disconnect(snapshotUploaded);
|
Window.snapshotShared.disconnect(snapshotUploaded);
|
||||||
Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet);
|
Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet);
|
||||||
Entities.canRezChanged.disconnect(processRezPermissionChange);
|
Entities.canRezChanged.disconnect(updatePrintPermissions);
|
||||||
Entities.canRezTmpChanged.disconnect(processRezPermissionChange);
|
Entities.canRezTmpChanged.disconnect(updatePrintPermissions);
|
||||||
});
|
});
|
||||||
|
|
||||||
}()); // END LOCAL_SCOPE
|
}()); // END LOCAL_SCOPE
|
||||||
|
|
Loading…
Reference in a new issue