3dtiles-inspector 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/inspector-assets/viewer/app.js +300 -40
- package/package.json +1 -1
- package/src/server/viewerHtml.js +141 -3
- package/src/viewer/app.js +12 -0
- package/src/viewer/dom/elements.js +2 -0
- package/src/viewer/dom/events.js +117 -37
- package/src/viewer/transform/rootTransformController.js +38 -0
- package/src/viewer/transform/uniformScale.js +179 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.10] - 2026-05-22
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added an infinite `Scale` drag track and editable value input for root transform edits.
|
|
14
|
+
|
|
9
15
|
## [0.2.9] - 2026-05-21
|
|
10
16
|
|
|
11
17
|
### Changed
|
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ const {
|
|
|
85
85
|
|
|
86
86
|
<img src="https://raw.githubusercontent.com/WilliamLiu-1997/3DTiles-Inspector/main/screenshot.png" alt="screenshot" />
|
|
87
87
|
|
|
88
|
-
- `Translate`, `Rotate`, and `Reset` for root transform edits
|
|
88
|
+
- `Translate`, `Rotate`, infinite `Scale` track/value input, and `Reset` for root transform edits
|
|
89
89
|
- `Move Camera` to a WGS84 latitude / longitude / height
|
|
90
90
|
- `Move Tiles` to relocate the tileset root with an ENU-aligned transform
|
|
91
91
|
- `Set Position` to click the globe, terrain, or loaded tiles and place the tileset there
|
|
@@ -45581,6 +45581,156 @@ var init_geometricError = __esm({
|
|
|
45581
45581
|
}
|
|
45582
45582
|
});
|
|
45583
45583
|
|
|
45584
|
+
// src/viewer/transform/uniformScale.js
|
|
45585
|
+
function exponentToUniformScale(exponent) {
|
|
45586
|
+
return 2 ** exponent;
|
|
45587
|
+
}
|
|
45588
|
+
function scaleToExponent(scale) {
|
|
45589
|
+
return Math.log2(scale);
|
|
45590
|
+
}
|
|
45591
|
+
function getFinitePositiveScale(value) {
|
|
45592
|
+
const number = Number(value);
|
|
45593
|
+
return Number.isFinite(number) && number > 0 ? number : null;
|
|
45594
|
+
}
|
|
45595
|
+
function normalizeUniformScale(value) {
|
|
45596
|
+
const scale = getFinitePositiveScale(value);
|
|
45597
|
+
if (scale === null) {
|
|
45598
|
+
return null;
|
|
45599
|
+
}
|
|
45600
|
+
return scale;
|
|
45601
|
+
}
|
|
45602
|
+
function formatUniformScale(scale) {
|
|
45603
|
+
const value = Number(scale);
|
|
45604
|
+
if (!Number.isFinite(value)) {
|
|
45605
|
+
return "1";
|
|
45606
|
+
}
|
|
45607
|
+
const absValue = Math.abs(value);
|
|
45608
|
+
if (absValue > 0 && (absValue < 1e-5 || absValue >= 1e6)) {
|
|
45609
|
+
return value.toExponential(3).replace(/\.?0+e/, "e");
|
|
45610
|
+
}
|
|
45611
|
+
const formatted = absValue < 0.01 ? value.toFixed(5) : absValue < 0.1 ? value.toFixed(4) : absValue < 10 ? value.toFixed(3) : absValue < 100 ? value.toFixed(2) : value.toFixed(1);
|
|
45612
|
+
return formatted.replace(/\.?0+$/, "");
|
|
45613
|
+
}
|
|
45614
|
+
function createUniformScaleController({
|
|
45615
|
+
applyScale,
|
|
45616
|
+
uniformScaleTrackEl,
|
|
45617
|
+
uniformScaleValueInput
|
|
45618
|
+
}) {
|
|
45619
|
+
let uniformScale = 1;
|
|
45620
|
+
let syncingInputs = false;
|
|
45621
|
+
let trackDragStartClientX = 0;
|
|
45622
|
+
let trackDragStartExponent = 0;
|
|
45623
|
+
function getScaleExponent() {
|
|
45624
|
+
return scaleToExponent(uniformScale);
|
|
45625
|
+
}
|
|
45626
|
+
function updateInputs() {
|
|
45627
|
+
syncingInputs = true;
|
|
45628
|
+
try {
|
|
45629
|
+
const formattedScale = formatUniformScale(uniformScale);
|
|
45630
|
+
uniformScaleTrackEl.setAttribute(
|
|
45631
|
+
"aria-label",
|
|
45632
|
+
`Scale x${formattedScale}`
|
|
45633
|
+
);
|
|
45634
|
+
uniformScaleTrackEl.title = `Scale x${formattedScale}`;
|
|
45635
|
+
uniformScaleValueInput.value = formatUniformScale(uniformScale);
|
|
45636
|
+
} finally {
|
|
45637
|
+
syncingInputs = false;
|
|
45638
|
+
}
|
|
45639
|
+
}
|
|
45640
|
+
function applyUniformScale(nextScale) {
|
|
45641
|
+
uniformScale = nextScale;
|
|
45642
|
+
applyScale(nextScale);
|
|
45643
|
+
updateInputs();
|
|
45644
|
+
}
|
|
45645
|
+
function setScale(scale) {
|
|
45646
|
+
const nextScale = normalizeUniformScale(scale);
|
|
45647
|
+
if (nextScale === null) {
|
|
45648
|
+
return false;
|
|
45649
|
+
}
|
|
45650
|
+
applyUniformScale(nextScale);
|
|
45651
|
+
return true;
|
|
45652
|
+
}
|
|
45653
|
+
function setScaleExponent(exponent) {
|
|
45654
|
+
const exponentNumber = Number(exponent);
|
|
45655
|
+
if (!Number.isFinite(exponentNumber)) {
|
|
45656
|
+
updateInputs();
|
|
45657
|
+
return false;
|
|
45658
|
+
}
|
|
45659
|
+
const nextScale = exponentToUniformScale(exponentNumber);
|
|
45660
|
+
if (!Number.isFinite(nextScale) || nextScale <= 0) {
|
|
45661
|
+
updateInputs();
|
|
45662
|
+
return false;
|
|
45663
|
+
}
|
|
45664
|
+
applyUniformScale(nextScale);
|
|
45665
|
+
return true;
|
|
45666
|
+
}
|
|
45667
|
+
function beginTrackDrag(clientX) {
|
|
45668
|
+
trackDragStartClientX = clientX;
|
|
45669
|
+
trackDragStartExponent = getScaleExponent();
|
|
45670
|
+
uniformScaleTrackEl.style.setProperty("--scale-track-offset", "0px");
|
|
45671
|
+
}
|
|
45672
|
+
function setScaleFromTrackClientX(clientX) {
|
|
45673
|
+
const deltaX = clientX - trackDragStartClientX;
|
|
45674
|
+
uniformScaleTrackEl.style.setProperty(
|
|
45675
|
+
"--scale-track-offset",
|
|
45676
|
+
`${deltaX}px`
|
|
45677
|
+
);
|
|
45678
|
+
return setScaleExponent(
|
|
45679
|
+
trackDragStartExponent + deltaX / TRACK_PIXELS_PER_SCALE_EXPONENT
|
|
45680
|
+
);
|
|
45681
|
+
}
|
|
45682
|
+
function nudgeScaleExponent(delta) {
|
|
45683
|
+
return setScaleExponent(getScaleExponent() + delta);
|
|
45684
|
+
}
|
|
45685
|
+
function setScaleValue(value, { commit = false } = {}) {
|
|
45686
|
+
if (syncingInputs) {
|
|
45687
|
+
return true;
|
|
45688
|
+
}
|
|
45689
|
+
const rawValue = typeof value === "string" ? value.trim() : value;
|
|
45690
|
+
if (rawValue === "" && !commit) {
|
|
45691
|
+
return false;
|
|
45692
|
+
}
|
|
45693
|
+
const scale = normalizeUniformScale(rawValue);
|
|
45694
|
+
if (scale === null) {
|
|
45695
|
+
if (commit) {
|
|
45696
|
+
updateInputs();
|
|
45697
|
+
}
|
|
45698
|
+
return false;
|
|
45699
|
+
}
|
|
45700
|
+
applyUniformScale(scale);
|
|
45701
|
+
return true;
|
|
45702
|
+
}
|
|
45703
|
+
function initializeInputs() {
|
|
45704
|
+
uniformScaleValueInput.removeAttribute("max");
|
|
45705
|
+
uniformScaleValueInput.removeAttribute("min");
|
|
45706
|
+
uniformScaleValueInput.step = "any";
|
|
45707
|
+
updateInputs();
|
|
45708
|
+
}
|
|
45709
|
+
function syncFromRootScale(scale) {
|
|
45710
|
+
const nextScale = normalizeUniformScale(scale);
|
|
45711
|
+
uniformScale = nextScale === null ? 1 : nextScale;
|
|
45712
|
+
updateInputs();
|
|
45713
|
+
}
|
|
45714
|
+
return {
|
|
45715
|
+
beginTrackDrag,
|
|
45716
|
+
formatScale: formatUniformScale,
|
|
45717
|
+
getScale: () => uniformScale,
|
|
45718
|
+
initializeInputs,
|
|
45719
|
+
nudgeScaleExponent,
|
|
45720
|
+
setScale,
|
|
45721
|
+
setScaleExponent,
|
|
45722
|
+
setScaleFromTrackClientX,
|
|
45723
|
+
setScaleValue,
|
|
45724
|
+
syncFromRootScale
|
|
45725
|
+
};
|
|
45726
|
+
}
|
|
45727
|
+
var TRACK_PIXELS_PER_SCALE_EXPONENT;
|
|
45728
|
+
var init_uniformScale = __esm({
|
|
45729
|
+
"src/viewer/transform/uniformScale.js"() {
|
|
45730
|
+
TRACK_PIXELS_PER_SCALE_EXPONENT = 90;
|
|
45731
|
+
}
|
|
45732
|
+
});
|
|
45733
|
+
|
|
45584
45734
|
// node_modules/@cesium/engine/Source/Core/defined.js
|
|
45585
45735
|
function defined(value) {
|
|
45586
45736
|
return value !== void 0 && value !== null;
|
|
@@ -70869,7 +71019,8 @@ function bindViewerEvents({
|
|
|
70869
71019
|
handlers,
|
|
70870
71020
|
ktx2Loader,
|
|
70871
71021
|
renderer,
|
|
70872
|
-
setStatus
|
|
71022
|
+
setStatus,
|
|
71023
|
+
uniformScale
|
|
70873
71024
|
}) {
|
|
70874
71025
|
const {
|
|
70875
71026
|
boundingVolumeButton,
|
|
@@ -70887,8 +71038,20 @@ function bindViewerEvents({
|
|
|
70887
71038
|
setPositionButton,
|
|
70888
71039
|
terrainButton,
|
|
70889
71040
|
toolbarToggleButton,
|
|
70890
|
-
translateButton
|
|
71041
|
+
translateButton,
|
|
71042
|
+
uniformScaleTrackEl,
|
|
71043
|
+
uniformScaleValueInput
|
|
70891
71044
|
} = elements;
|
|
71045
|
+
let uniformScaleTrackPointerId = null;
|
|
71046
|
+
function setUniformScaleStatus() {
|
|
71047
|
+
setStatus(
|
|
71048
|
+
`Scale set to x${uniformScale.formatScale(uniformScale.getScale())}.`
|
|
71049
|
+
);
|
|
71050
|
+
}
|
|
71051
|
+
function updateUniformScaleFromTrackPointer(event) {
|
|
71052
|
+
handlers.cancelPositionPickModes();
|
|
71053
|
+
uniformScale.setScaleFromTrackClientX(event.clientX);
|
|
71054
|
+
}
|
|
70892
71055
|
translateButton.addEventListener("click", () => {
|
|
70893
71056
|
handlers.cancelPositionPickModes();
|
|
70894
71057
|
handlers.toggleTransformMode("translate");
|
|
@@ -70903,6 +71066,70 @@ function bindViewerEvents({
|
|
|
70903
71066
|
getActiveTransformMode() === "rotate" ? "Rotate mode enabled." : "Rotate mode disabled."
|
|
70904
71067
|
);
|
|
70905
71068
|
});
|
|
71069
|
+
uniformScaleTrackEl.addEventListener("pointerdown", (event) => {
|
|
71070
|
+
if (event.button !== 0) {
|
|
71071
|
+
return;
|
|
71072
|
+
}
|
|
71073
|
+
event.preventDefault();
|
|
71074
|
+
uniformScaleTrackPointerId = event.pointerId;
|
|
71075
|
+
uniformScaleTrackEl.focus();
|
|
71076
|
+
uniformScaleTrackEl.classList.add("dragging");
|
|
71077
|
+
uniformScaleTrackEl.setPointerCapture(event.pointerId);
|
|
71078
|
+
uniformScale.beginTrackDrag(event.clientX);
|
|
71079
|
+
});
|
|
71080
|
+
uniformScaleTrackEl.addEventListener("pointermove", (event) => {
|
|
71081
|
+
if (event.pointerId !== uniformScaleTrackPointerId) {
|
|
71082
|
+
return;
|
|
71083
|
+
}
|
|
71084
|
+
updateUniformScaleFromTrackPointer(event);
|
|
71085
|
+
});
|
|
71086
|
+
uniformScaleTrackEl.addEventListener("pointerup", (event) => {
|
|
71087
|
+
if (event.pointerId !== uniformScaleTrackPointerId) {
|
|
71088
|
+
return;
|
|
71089
|
+
}
|
|
71090
|
+
uniformScaleTrackPointerId = null;
|
|
71091
|
+
uniformScaleTrackEl.classList.remove("dragging");
|
|
71092
|
+
uniformScaleTrackEl.releasePointerCapture(event.pointerId);
|
|
71093
|
+
setUniformScaleStatus();
|
|
71094
|
+
});
|
|
71095
|
+
uniformScaleTrackEl.addEventListener("pointercancel", (event) => {
|
|
71096
|
+
if (event.pointerId !== uniformScaleTrackPointerId) {
|
|
71097
|
+
return;
|
|
71098
|
+
}
|
|
71099
|
+
uniformScaleTrackPointerId = null;
|
|
71100
|
+
uniformScaleTrackEl.classList.remove("dragging");
|
|
71101
|
+
uniformScaleTrackEl.releasePointerCapture(event.pointerId);
|
|
71102
|
+
});
|
|
71103
|
+
uniformScaleTrackEl.addEventListener("keydown", (event) => {
|
|
71104
|
+
let handled = true;
|
|
71105
|
+
const step = event.shiftKey ? 1 : 0.1;
|
|
71106
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
|
|
71107
|
+
uniformScale.nudgeScaleExponent(-step);
|
|
71108
|
+
} else if (event.key === "ArrowRight" || event.key === "ArrowUp") {
|
|
71109
|
+
uniformScale.nudgeScaleExponent(step);
|
|
71110
|
+
} else {
|
|
71111
|
+
handled = false;
|
|
71112
|
+
}
|
|
71113
|
+
if (!handled) {
|
|
71114
|
+
return;
|
|
71115
|
+
}
|
|
71116
|
+
event.preventDefault();
|
|
71117
|
+
handlers.cancelPositionPickModes();
|
|
71118
|
+
setUniformScaleStatus();
|
|
71119
|
+
});
|
|
71120
|
+
uniformScaleValueInput.addEventListener("input", () => {
|
|
71121
|
+
handlers.cancelPositionPickModes();
|
|
71122
|
+
uniformScale.setScaleValue(uniformScaleValueInput.value);
|
|
71123
|
+
});
|
|
71124
|
+
uniformScaleValueInput.addEventListener("change", () => {
|
|
71125
|
+
if (!uniformScale.setScaleValue(uniformScaleValueInput.value, {
|
|
71126
|
+
commit: true
|
|
71127
|
+
})) {
|
|
71128
|
+
setStatus("Scale must be greater than 0.", true);
|
|
71129
|
+
return;
|
|
71130
|
+
}
|
|
71131
|
+
setUniformScaleStatus();
|
|
71132
|
+
});
|
|
70906
71133
|
cropScreenSelectButton.addEventListener(
|
|
70907
71134
|
"click",
|
|
70908
71135
|
handlers.toggleCropScreenSelectionMode
|
|
@@ -70915,7 +71142,10 @@ function bindViewerEvents({
|
|
|
70915
71142
|
"click",
|
|
70916
71143
|
handlers.cancelCropScreenSelection
|
|
70917
71144
|
);
|
|
70918
|
-
toolbarToggleButton.addEventListener(
|
|
71145
|
+
toolbarToggleButton.addEventListener(
|
|
71146
|
+
"click",
|
|
71147
|
+
handlers.toggleToolbarVisibility
|
|
71148
|
+
);
|
|
70919
71149
|
terrainButton.addEventListener("click", () => {
|
|
70920
71150
|
handlers.setTerrainEnabled(!getTerrainEnabled());
|
|
70921
71151
|
setStatus(
|
|
@@ -70955,42 +71185,30 @@ function bindViewerEvents({
|
|
|
70955
71185
|
setPositionButton.addEventListener("click", handlers.toggleSetPositionMode);
|
|
70956
71186
|
resetButton.addEventListener("click", handlers.resetToSaved);
|
|
70957
71187
|
saveButton.addEventListener("click", handlers.saveTransform);
|
|
70958
|
-
renderer.domElement.addEventListener(
|
|
70959
|
-
|
|
70960
|
-
|
|
70961
|
-
if (handlers.handleScreenSelectionPointerDown(event)) {
|
|
70962
|
-
return;
|
|
70963
|
-
}
|
|
70964
|
-
handlers.handleSetPositionPointerDown(event);
|
|
71188
|
+
renderer.domElement.addEventListener("pointerdown", (event) => {
|
|
71189
|
+
if (handlers.handleScreenSelectionPointerDown(event)) {
|
|
71190
|
+
return;
|
|
70965
71191
|
}
|
|
70966
|
-
|
|
70967
|
-
|
|
70968
|
-
|
|
70969
|
-
(event)
|
|
70970
|
-
|
|
70971
|
-
return;
|
|
70972
|
-
}
|
|
70973
|
-
handlers.handleSetPositionPointerMove(event);
|
|
71192
|
+
handlers.handleSetPositionPointerDown(event);
|
|
71193
|
+
});
|
|
71194
|
+
renderer.domElement.addEventListener("pointermove", (event) => {
|
|
71195
|
+
if (handlers.handleScreenSelectionPointerMove(event)) {
|
|
71196
|
+
return;
|
|
70974
71197
|
}
|
|
70975
|
-
|
|
70976
|
-
|
|
70977
|
-
|
|
70978
|
-
(event)
|
|
70979
|
-
|
|
70980
|
-
return;
|
|
70981
|
-
}
|
|
70982
|
-
handlers.handleSetPositionPointerUp(event);
|
|
71198
|
+
handlers.handleSetPositionPointerMove(event);
|
|
71199
|
+
});
|
|
71200
|
+
renderer.domElement.addEventListener("pointerup", (event) => {
|
|
71201
|
+
if (handlers.handleScreenSelectionPointerUp(event)) {
|
|
71202
|
+
return;
|
|
70983
71203
|
}
|
|
70984
|
-
|
|
70985
|
-
|
|
70986
|
-
|
|
70987
|
-
(event)
|
|
70988
|
-
|
|
70989
|
-
return;
|
|
70990
|
-
}
|
|
70991
|
-
handlers.handleSetPositionPointerCancel(event);
|
|
71204
|
+
handlers.handleSetPositionPointerUp(event);
|
|
71205
|
+
});
|
|
71206
|
+
renderer.domElement.addEventListener("pointercancel", (event) => {
|
|
71207
|
+
if (handlers.handleScreenSelectionPointerCancel(event)) {
|
|
71208
|
+
return;
|
|
70992
71209
|
}
|
|
70993
|
-
|
|
71210
|
+
handlers.handleSetPositionPointerCancel(event);
|
|
71211
|
+
});
|
|
70994
71212
|
window.addEventListener("resize", () => {
|
|
70995
71213
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
70996
71214
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
@@ -74391,7 +74609,8 @@ function createRootTransformController({
|
|
|
74391
74609
|
transformControlsHelper,
|
|
74392
74610
|
transformHandle,
|
|
74393
74611
|
onTransformsInvalidated,
|
|
74394
|
-
onCoordinateChanged
|
|
74612
|
+
onCoordinateChanged,
|
|
74613
|
+
onUniformScaleChanged
|
|
74395
74614
|
}) {
|
|
74396
74615
|
const coordinateWorldPosition = new Vector3();
|
|
74397
74616
|
const coordinateTransformMatrix = new Matrix4();
|
|
@@ -74416,6 +74635,17 @@ function createRootTransformController({
|
|
|
74416
74635
|
function getCurrentRootTransformArray() {
|
|
74417
74636
|
return getCurrentRootTransform(currentRootTransformMatrix).toArray();
|
|
74418
74637
|
}
|
|
74638
|
+
function getUniformScale() {
|
|
74639
|
+
const scale = transformHandle.scale;
|
|
74640
|
+
const scaleValues = [scale.x, scale.y, scale.z].map((value) => Math.abs(value)).filter((value) => Number.isFinite(value));
|
|
74641
|
+
if (scaleValues.length === 0) {
|
|
74642
|
+
return 1;
|
|
74643
|
+
}
|
|
74644
|
+
return scaleValues.reduce((total, value) => total + value, 0) / scaleValues.length;
|
|
74645
|
+
}
|
|
74646
|
+
function notifyUniformScaleChanged() {
|
|
74647
|
+
onUniformScaleChanged?.(getUniformScale());
|
|
74648
|
+
}
|
|
74419
74649
|
function getCurrentMatrix() {
|
|
74420
74650
|
return getObjectMatrix(editableGroup);
|
|
74421
74651
|
}
|
|
@@ -74456,6 +74686,7 @@ function createRootTransformController({
|
|
|
74456
74686
|
} finally {
|
|
74457
74687
|
syncingTransformHandle = false;
|
|
74458
74688
|
}
|
|
74689
|
+
notifyUniformScaleChanged();
|
|
74459
74690
|
}
|
|
74460
74691
|
function applySaved(matrix) {
|
|
74461
74692
|
applySavedObjectMatrix(editableGroup, matrix);
|
|
@@ -74473,6 +74704,19 @@ function createRootTransformController({
|
|
|
74473
74704
|
target: coordinateEditMatrix
|
|
74474
74705
|
});
|
|
74475
74706
|
invalidate();
|
|
74707
|
+
notifyUniformScaleChanged();
|
|
74708
|
+
}
|
|
74709
|
+
function applyUniformScale(scale) {
|
|
74710
|
+
const nextScale = Number(scale);
|
|
74711
|
+
if (!Number.isFinite(nextScale) || nextScale <= 0) {
|
|
74712
|
+
return false;
|
|
74713
|
+
}
|
|
74714
|
+
transformHandle.scale.set(nextScale, nextScale, nextScale);
|
|
74715
|
+
transformHandle.updateMatrix();
|
|
74716
|
+
transformHandle.updateMatrixWorld(true);
|
|
74717
|
+
applyFromRootTransform(transformHandle.matrix);
|
|
74718
|
+
syncCoordinateInputs();
|
|
74719
|
+
return true;
|
|
74476
74720
|
}
|
|
74477
74721
|
async function applyFromCoordinate(latitude, longitude, height) {
|
|
74478
74722
|
await savedRootMatrixPromise;
|
|
@@ -74502,6 +74746,7 @@ function createRootTransformController({
|
|
|
74502
74746
|
lastSavedMatrix.identity();
|
|
74503
74747
|
resetEditableObjectTransform(transformHandle);
|
|
74504
74748
|
tilesTransformDirty = true;
|
|
74749
|
+
notifyUniformScaleChanged();
|
|
74505
74750
|
}
|
|
74506
74751
|
function refresh(url) {
|
|
74507
74752
|
savedRootMatrix.identity();
|
|
@@ -74572,6 +74817,7 @@ function createRootTransformController({
|
|
|
74572
74817
|
applyFromCoordinate,
|
|
74573
74818
|
applyFromRootTransform,
|
|
74574
74819
|
applySaved,
|
|
74820
|
+
applyUniformScale,
|
|
74575
74821
|
flush,
|
|
74576
74822
|
getCurrentMatrix,
|
|
74577
74823
|
getCurrentRootTransform,
|
|
@@ -74579,6 +74825,7 @@ function createRootTransformController({
|
|
|
74579
74825
|
getIncrementalSinceSaved,
|
|
74580
74826
|
getLastSaved: () => lastSavedMatrix,
|
|
74581
74827
|
getLoadError: () => savedRootMatrixLoadError,
|
|
74828
|
+
getUniformScale,
|
|
74582
74829
|
isSyncingHandle: () => syncingTransformHandle,
|
|
74583
74830
|
markDirty() {
|
|
74584
74831
|
tilesTransformDirty = true;
|
|
@@ -74722,7 +74969,9 @@ function getViewerElements() {
|
|
|
74722
74969
|
toolbarDockEl: toolbarEl.parentElement,
|
|
74723
74970
|
toolbarEl,
|
|
74724
74971
|
toolbarToggleButton: document.getElementById("toolbar-toggle"),
|
|
74725
|
-
translateButton: document.getElementById("translate")
|
|
74972
|
+
translateButton: document.getElementById("translate"),
|
|
74973
|
+
uniformScaleTrackEl: document.getElementById("uniform-scale"),
|
|
74974
|
+
uniformScaleValueInput: document.getElementById("uniform-scale-value")
|
|
74726
74975
|
};
|
|
74727
74976
|
}
|
|
74728
74977
|
var init_elements = __esm({
|
|
@@ -74742,6 +74991,7 @@ var require_app = __commonJS({
|
|
|
74742
74991
|
init_viewerToggles();
|
|
74743
74992
|
init_geoCamera();
|
|
74744
74993
|
init_geometricError();
|
|
74994
|
+
init_uniformScale();
|
|
74745
74995
|
init_globeController();
|
|
74746
74996
|
init_sceneSetup();
|
|
74747
74997
|
init_transformControls();
|
|
@@ -74785,7 +75035,9 @@ var require_app = __commonJS({
|
|
|
74785
75035
|
toolbarDockEl,
|
|
74786
75036
|
toolbarEl,
|
|
74787
75037
|
toolbarToggleButton,
|
|
74788
|
-
translateButton
|
|
75038
|
+
translateButton,
|
|
75039
|
+
uniformScaleTrackEl,
|
|
75040
|
+
uniformScaleValueInput
|
|
74789
75041
|
} = viewerElements;
|
|
74790
75042
|
var MOVE_TO_TILES_POSE = {
|
|
74791
75043
|
heading: MOVE_TO_TILES_HEADING,
|
|
@@ -74932,6 +75184,11 @@ var require_app = __commonJS({
|
|
|
74932
75184
|
geometricErrorValueEl,
|
|
74933
75185
|
getTiles: () => tiles
|
|
74934
75186
|
});
|
|
75187
|
+
var uniformScale = createUniformScaleController({
|
|
75188
|
+
applyScale: (scale) => rootTransform?.applyUniformScale(scale),
|
|
75189
|
+
uniformScaleTrackEl,
|
|
75190
|
+
uniformScaleValueInput
|
|
75191
|
+
});
|
|
74935
75192
|
function getTilesetBoundingSphere(target) {
|
|
74936
75193
|
if (!tiles || !tiles.getBoundingSphere(target)) {
|
|
74937
75194
|
return false;
|
|
@@ -75020,7 +75277,8 @@ var require_app = __commonJS({
|
|
|
75020
75277
|
transformControlsHelper,
|
|
75021
75278
|
transformHandle,
|
|
75022
75279
|
onCoordinateChanged: updateCoordinateInputs,
|
|
75023
|
-
onTransformsInvalidated: () => cropController.syncWorldState()
|
|
75280
|
+
onTransformsInvalidated: () => cropController.syncWorldState(),
|
|
75281
|
+
onUniformScaleChanged: uniformScale.syncFromRootScale
|
|
75024
75282
|
});
|
|
75025
75283
|
function setGaussianSplatUiVisible(visible) {
|
|
75026
75284
|
if (splatsCountStatEl) {
|
|
@@ -75056,6 +75314,7 @@ var require_app = __commonJS({
|
|
|
75056
75314
|
transformModeController.setMode(null);
|
|
75057
75315
|
}
|
|
75058
75316
|
geometricError.initializeInputs();
|
|
75317
|
+
uniformScale.initializeInputs();
|
|
75059
75318
|
transformModeController.setMode(null);
|
|
75060
75319
|
function moveCameraToTiles() {
|
|
75061
75320
|
cancelPositionPickModes();
|
|
@@ -75264,7 +75523,8 @@ var require_app = __commonJS({
|
|
|
75264
75523
|
},
|
|
75265
75524
|
ktx2Loader,
|
|
75266
75525
|
renderer,
|
|
75267
|
-
setStatus
|
|
75526
|
+
setStatus,
|
|
75527
|
+
uniformScale
|
|
75268
75528
|
});
|
|
75269
75529
|
window.addEventListener("popstate", () => {
|
|
75270
75530
|
if (cameraUrlPose.applyFromUrl({ showStatus: true })) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "3dtiles-inspector",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "Inspect, align, and save local 3D Tiles root transforms in an interactive browser session.",
|
|
5
5
|
"author": "William Liu <lyz15972107087@gmail.com>",
|
|
6
6
|
"license": "Apache-2.0",
|
package/src/server/viewerHtml.js
CHANGED
|
@@ -279,7 +279,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
279
279
|
grid-template-rows: minmax(0, 1fr) auto;
|
|
280
280
|
gap: 0;
|
|
281
281
|
padding: 10px 14px;
|
|
282
|
-
border: 1px solid rgba(
|
|
282
|
+
border: 1px solid rgba(73, 80, 87, 0.16);
|
|
283
283
|
border-top: 0;
|
|
284
284
|
border-radius: 0 0 20px 20px;
|
|
285
285
|
background: rgba(255, 255, 255, 0.9);
|
|
@@ -502,6 +502,124 @@ function buildViewerHtml(viewerConfig) {
|
|
|
502
502
|
margin: 0;
|
|
503
503
|
}
|
|
504
504
|
|
|
505
|
+
.scale-value-input {
|
|
506
|
+
width: 82px;
|
|
507
|
+
min-width: 0;
|
|
508
|
+
padding: 4px 7px;
|
|
509
|
+
border: 1px solid rgba(22, 50, 79, 0.16);
|
|
510
|
+
border-radius: 8px;
|
|
511
|
+
font: inherit;
|
|
512
|
+
font-size: 12px;
|
|
513
|
+
font-weight: 700;
|
|
514
|
+
text-align: right;
|
|
515
|
+
color: #16324f;
|
|
516
|
+
background: rgba(255, 255, 255, 0.92);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.scale-track {
|
|
520
|
+
--scale-track-offset: 0px;
|
|
521
|
+
position: relative;
|
|
522
|
+
width: 100%;
|
|
523
|
+
height: 22px;
|
|
524
|
+
overflow: hidden;
|
|
525
|
+
border: 1px solid rgba(22, 50, 79, 0.12);
|
|
526
|
+
border-radius: 9px;
|
|
527
|
+
padding: 0;
|
|
528
|
+
background:
|
|
529
|
+
linear-gradient(180deg, rgba(255, 255, 255, 0.84), rgba(231, 235, 240, 0.92)),
|
|
530
|
+
#edf0f4;
|
|
531
|
+
box-shadow:
|
|
532
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.78),
|
|
533
|
+
inset 0 -1px 0 rgba(73, 80, 87, 0.08);
|
|
534
|
+
cursor: ew-resize;
|
|
535
|
+
touch-action: none;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.scale-track::before {
|
|
539
|
+
content: "";
|
|
540
|
+
position: absolute;
|
|
541
|
+
top: 50%;
|
|
542
|
+
right: 8px;
|
|
543
|
+
left: 8px;
|
|
544
|
+
height: 4px;
|
|
545
|
+
border-radius: 999px;
|
|
546
|
+
background:
|
|
547
|
+
linear-gradient(
|
|
548
|
+
90deg,
|
|
549
|
+
rgba(73, 80, 87, 0.08),
|
|
550
|
+
rgba(73, 80, 87, 0.42) 50%,
|
|
551
|
+
rgba(73, 80, 87, 0.08)
|
|
552
|
+
);
|
|
553
|
+
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.56);
|
|
554
|
+
transform: translateY(-50%);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.scale-track::after {
|
|
558
|
+
content: "";
|
|
559
|
+
position: absolute;
|
|
560
|
+
inset: 5px 8px 5px;
|
|
561
|
+
background-image:
|
|
562
|
+
linear-gradient(
|
|
563
|
+
90deg,
|
|
564
|
+
rgba(73, 80, 87, 0.46) 0 1px,
|
|
565
|
+
transparent 1px 100%
|
|
566
|
+
),
|
|
567
|
+
linear-gradient(
|
|
568
|
+
90deg,
|
|
569
|
+
rgba(73, 80, 87, 0.26) 0 1px,
|
|
570
|
+
transparent 1px 100%
|
|
571
|
+
);
|
|
572
|
+
background-repeat: repeat-x;
|
|
573
|
+
background-size:
|
|
574
|
+
20px 12px,
|
|
575
|
+
20px 7px;
|
|
576
|
+
background-position:
|
|
577
|
+
calc(50% + var(--scale-track-offset) - 1px) -1px,
|
|
578
|
+
calc(50% + var(--scale-track-offset) + 10px) 2px;
|
|
579
|
+
opacity: 0.72;
|
|
580
|
+
pointer-events: none;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.scale-track-center {
|
|
584
|
+
position: absolute;
|
|
585
|
+
top: 3px;
|
|
586
|
+
bottom: 3px;
|
|
587
|
+
left: 50%;
|
|
588
|
+
width: 2px;
|
|
589
|
+
border-radius: 999px;
|
|
590
|
+
background: rgba(73, 80, 87, 0.72);
|
|
591
|
+
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.72);
|
|
592
|
+
transform: translateX(-50%);
|
|
593
|
+
pointer-events: none;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.scale-track:hover::before,
|
|
597
|
+
.scale-track:focus-visible::before,
|
|
598
|
+
.scale-track.dragging::before {
|
|
599
|
+
background:
|
|
600
|
+
linear-gradient(
|
|
601
|
+
90deg,
|
|
602
|
+
rgba(73, 80, 87, 0.12),
|
|
603
|
+
rgba(73, 80, 87, 0.58) 50%,
|
|
604
|
+
rgba(73, 80, 87, 0.12)
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.scale-track.dragging {
|
|
609
|
+
border-color: rgba(73, 80, 87, 0.32);
|
|
610
|
+
box-shadow:
|
|
611
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.82),
|
|
612
|
+
inset 0 -1px 0 rgba(73, 80, 87, 0.08);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.scale-track:focus {
|
|
616
|
+
outline: none;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.scale-track:focus-visible {
|
|
620
|
+
outline: none;
|
|
621
|
+
}
|
|
622
|
+
|
|
505
623
|
.coordinate-grid {
|
|
506
624
|
display: grid;
|
|
507
625
|
grid-template-columns: 1fr;
|
|
@@ -795,8 +913,28 @@ function buildViewerHtml(viewerConfig) {
|
|
|
795
913
|
<div class="transform-actions">
|
|
796
914
|
<button id="translate" type="button">Translate</button>
|
|
797
915
|
<button id="rotate" type="button">Rotate</button>
|
|
798
|
-
<
|
|
799
|
-
|
|
916
|
+
<div class="range-field full-span">
|
|
917
|
+
<div class="range-field-header">
|
|
918
|
+
<span>Scale</span>
|
|
919
|
+
<input
|
|
920
|
+
id="uniform-scale-value"
|
|
921
|
+
class="scale-value-input"
|
|
922
|
+
type="number"
|
|
923
|
+
step="any"
|
|
924
|
+
value="1"
|
|
925
|
+
/>
|
|
926
|
+
</div>
|
|
927
|
+
<div
|
|
928
|
+
id="uniform-scale"
|
|
929
|
+
class="scale-track"
|
|
930
|
+
tabindex="0"
|
|
931
|
+
aria-label="Scale x1"
|
|
932
|
+
>
|
|
933
|
+
<span class="scale-track-center" aria-hidden="true"></span>
|
|
934
|
+
</div>
|
|
935
|
+
</div>
|
|
936
|
+
<button id="set-position" class="full-span" type="button">Set Position</button>
|
|
937
|
+
<button id="reset" class="full-span" type="button">Reset</button>
|
|
800
938
|
</div>
|
|
801
939
|
</div>
|
|
802
940
|
<div class="toolbar-section">
|
package/src/viewer/app.js
CHANGED
|
@@ -10,6 +10,7 @@ import { createStatusPanel } from './dom/statusPanel.js';
|
|
|
10
10
|
import { createViewerToggles } from './dom/viewerToggles.js';
|
|
11
11
|
import { createGeoCameraController } from './transform/geoCamera.js';
|
|
12
12
|
import { createGeometricErrorController } from './transform/geometricError.js';
|
|
13
|
+
import { createUniformScaleController } from './transform/uniformScale.js';
|
|
13
14
|
import { createGlobeController } from './scene/globeController.js';
|
|
14
15
|
import { createViewerScene } from './scene/sceneSetup.js';
|
|
15
16
|
import { createViewerTransformControls } from './scene/transformControls.js';
|
|
@@ -71,6 +72,8 @@ const {
|
|
|
71
72
|
toolbarEl,
|
|
72
73
|
toolbarToggleButton,
|
|
73
74
|
translateButton,
|
|
75
|
+
uniformScaleTrackEl,
|
|
76
|
+
uniformScaleValueInput,
|
|
74
77
|
} = viewerElements;
|
|
75
78
|
|
|
76
79
|
const MOVE_TO_TILES_POSE = {
|
|
@@ -244,6 +247,12 @@ const geometricError = createGeometricErrorController({
|
|
|
244
247
|
getTiles: () => tiles,
|
|
245
248
|
});
|
|
246
249
|
|
|
250
|
+
const uniformScale = createUniformScaleController({
|
|
251
|
+
applyScale: (scale) => rootTransform?.applyUniformScale(scale),
|
|
252
|
+
uniformScaleTrackEl,
|
|
253
|
+
uniformScaleValueInput,
|
|
254
|
+
});
|
|
255
|
+
|
|
247
256
|
function getTilesetBoundingSphere(target) {
|
|
248
257
|
if (!tiles || !tiles.getBoundingSphere(target)) {
|
|
249
258
|
return false;
|
|
@@ -342,6 +351,7 @@ rootTransform = createRootTransformController({
|
|
|
342
351
|
transformHandle,
|
|
343
352
|
onCoordinateChanged: updateCoordinateInputs,
|
|
344
353
|
onTransformsInvalidated: () => cropController.syncWorldState(),
|
|
354
|
+
onUniformScaleChanged: uniformScale.syncFromRootScale,
|
|
345
355
|
});
|
|
346
356
|
|
|
347
357
|
function setGaussianSplatUiVisible(visible) {
|
|
@@ -384,6 +394,7 @@ function exitSaveInteractionModes() {
|
|
|
384
394
|
}
|
|
385
395
|
|
|
386
396
|
geometricError.initializeInputs();
|
|
397
|
+
uniformScale.initializeInputs();
|
|
387
398
|
transformModeController.setMode(null);
|
|
388
399
|
|
|
389
400
|
function moveCameraToTiles() {
|
|
@@ -614,6 +625,7 @@ bindViewerEvents({
|
|
|
614
625
|
ktx2Loader,
|
|
615
626
|
renderer,
|
|
616
627
|
setStatus,
|
|
628
|
+
uniformScale,
|
|
617
629
|
});
|
|
618
630
|
|
|
619
631
|
window.addEventListener('popstate', () => {
|
|
@@ -49,5 +49,7 @@ export function getViewerElements() {
|
|
|
49
49
|
toolbarEl,
|
|
50
50
|
toolbarToggleButton: document.getElementById('toolbar-toggle'),
|
|
51
51
|
translateButton: document.getElementById('translate'),
|
|
52
|
+
uniformScaleTrackEl: document.getElementById('uniform-scale'),
|
|
53
|
+
uniformScaleValueInput: document.getElementById('uniform-scale-value'),
|
|
52
54
|
};
|
|
53
55
|
}
|
package/src/viewer/dom/events.js
CHANGED
|
@@ -12,6 +12,7 @@ export function bindViewerEvents({
|
|
|
12
12
|
ktx2Loader,
|
|
13
13
|
renderer,
|
|
14
14
|
setStatus,
|
|
15
|
+
uniformScale,
|
|
15
16
|
}) {
|
|
16
17
|
const {
|
|
17
18
|
boundingVolumeButton,
|
|
@@ -30,8 +31,23 @@ export function bindViewerEvents({
|
|
|
30
31
|
terrainButton,
|
|
31
32
|
toolbarToggleButton,
|
|
32
33
|
translateButton,
|
|
34
|
+
uniformScaleTrackEl,
|
|
35
|
+
uniformScaleValueInput,
|
|
33
36
|
} = elements;
|
|
34
37
|
|
|
38
|
+
let uniformScaleTrackPointerId = null;
|
|
39
|
+
|
|
40
|
+
function setUniformScaleStatus() {
|
|
41
|
+
setStatus(
|
|
42
|
+
`Scale set to x${uniformScale.formatScale(uniformScale.getScale())}.`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function updateUniformScaleFromTrackPointer(event) {
|
|
47
|
+
handlers.cancelPositionPickModes();
|
|
48
|
+
uniformScale.setScaleFromTrackClientX(event.clientX);
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
translateButton.addEventListener('click', () => {
|
|
36
52
|
handlers.cancelPositionPickModes();
|
|
37
53
|
handlers.toggleTransformMode('translate');
|
|
@@ -50,6 +66,79 @@ export function bindViewerEvents({
|
|
|
50
66
|
: 'Rotate mode disabled.',
|
|
51
67
|
);
|
|
52
68
|
});
|
|
69
|
+
uniformScaleTrackEl.addEventListener('pointerdown', (event) => {
|
|
70
|
+
if (event.button !== 0) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
uniformScaleTrackPointerId = event.pointerId;
|
|
76
|
+
uniformScaleTrackEl.focus();
|
|
77
|
+
uniformScaleTrackEl.classList.add('dragging');
|
|
78
|
+
uniformScaleTrackEl.setPointerCapture(event.pointerId);
|
|
79
|
+
uniformScale.beginTrackDrag(event.clientX);
|
|
80
|
+
});
|
|
81
|
+
uniformScaleTrackEl.addEventListener('pointermove', (event) => {
|
|
82
|
+
if (event.pointerId !== uniformScaleTrackPointerId) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
updateUniformScaleFromTrackPointer(event);
|
|
87
|
+
});
|
|
88
|
+
uniformScaleTrackEl.addEventListener('pointerup', (event) => {
|
|
89
|
+
if (event.pointerId !== uniformScaleTrackPointerId) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
uniformScaleTrackPointerId = null;
|
|
94
|
+
uniformScaleTrackEl.classList.remove('dragging');
|
|
95
|
+
uniformScaleTrackEl.releasePointerCapture(event.pointerId);
|
|
96
|
+
setUniformScaleStatus();
|
|
97
|
+
});
|
|
98
|
+
uniformScaleTrackEl.addEventListener('pointercancel', (event) => {
|
|
99
|
+
if (event.pointerId !== uniformScaleTrackPointerId) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
uniformScaleTrackPointerId = null;
|
|
104
|
+
uniformScaleTrackEl.classList.remove('dragging');
|
|
105
|
+
uniformScaleTrackEl.releasePointerCapture(event.pointerId);
|
|
106
|
+
});
|
|
107
|
+
uniformScaleTrackEl.addEventListener('keydown', (event) => {
|
|
108
|
+
let handled = true;
|
|
109
|
+
const step = event.shiftKey ? 1 : 0.1;
|
|
110
|
+
|
|
111
|
+
if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
|
|
112
|
+
uniformScale.nudgeScaleExponent(-step);
|
|
113
|
+
} else if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
|
|
114
|
+
uniformScale.nudgeScaleExponent(step);
|
|
115
|
+
} else {
|
|
116
|
+
handled = false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!handled) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
handlers.cancelPositionPickModes();
|
|
125
|
+
setUniformScaleStatus();
|
|
126
|
+
});
|
|
127
|
+
uniformScaleValueInput.addEventListener('input', () => {
|
|
128
|
+
handlers.cancelPositionPickModes();
|
|
129
|
+
uniformScale.setScaleValue(uniformScaleValueInput.value);
|
|
130
|
+
});
|
|
131
|
+
uniformScaleValueInput.addEventListener('change', () => {
|
|
132
|
+
if (
|
|
133
|
+
!uniformScale.setScaleValue(uniformScaleValueInput.value, {
|
|
134
|
+
commit: true,
|
|
135
|
+
})
|
|
136
|
+
) {
|
|
137
|
+
setStatus('Scale must be greater than 0.', true);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
setUniformScaleStatus();
|
|
141
|
+
});
|
|
53
142
|
cropScreenSelectButton.addEventListener(
|
|
54
143
|
'click',
|
|
55
144
|
handlers.toggleCropScreenSelectionMode,
|
|
@@ -62,7 +151,10 @@ export function bindViewerEvents({
|
|
|
62
151
|
'click',
|
|
63
152
|
handlers.cancelCropScreenSelection,
|
|
64
153
|
);
|
|
65
|
-
toolbarToggleButton.addEventListener(
|
|
154
|
+
toolbarToggleButton.addEventListener(
|
|
155
|
+
'click',
|
|
156
|
+
handlers.toggleToolbarVisibility,
|
|
157
|
+
);
|
|
66
158
|
terrainButton.addEventListener('click', () => {
|
|
67
159
|
handlers.setTerrainEnabled(!getTerrainEnabled());
|
|
68
160
|
setStatus(
|
|
@@ -104,42 +196,30 @@ export function bindViewerEvents({
|
|
|
104
196
|
setPositionButton.addEventListener('click', handlers.toggleSetPositionMode);
|
|
105
197
|
resetButton.addEventListener('click', handlers.resetToSaved);
|
|
106
198
|
saveButton.addEventListener('click', handlers.saveTransform);
|
|
107
|
-
renderer.domElement.addEventListener(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
);
|
|
125
|
-
renderer.domElement.addEventListener(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
handlers.handleSetPositionPointerUp(event);
|
|
132
|
-
},
|
|
133
|
-
);
|
|
134
|
-
renderer.domElement.addEventListener(
|
|
135
|
-
'pointercancel',
|
|
136
|
-
(event) => {
|
|
137
|
-
if (handlers.handleScreenSelectionPointerCancel(event)) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
handlers.handleSetPositionPointerCancel(event);
|
|
141
|
-
},
|
|
142
|
-
);
|
|
199
|
+
renderer.domElement.addEventListener('pointerdown', (event) => {
|
|
200
|
+
if (handlers.handleScreenSelectionPointerDown(event)) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
handlers.handleSetPositionPointerDown(event);
|
|
204
|
+
});
|
|
205
|
+
renderer.domElement.addEventListener('pointermove', (event) => {
|
|
206
|
+
if (handlers.handleScreenSelectionPointerMove(event)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
handlers.handleSetPositionPointerMove(event);
|
|
210
|
+
});
|
|
211
|
+
renderer.domElement.addEventListener('pointerup', (event) => {
|
|
212
|
+
if (handlers.handleScreenSelectionPointerUp(event)) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
handlers.handleSetPositionPointerUp(event);
|
|
216
|
+
});
|
|
217
|
+
renderer.domElement.addEventListener('pointercancel', (event) => {
|
|
218
|
+
if (handlers.handleScreenSelectionPointerCancel(event)) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
handlers.handleSetPositionPointerCancel(event);
|
|
222
|
+
});
|
|
143
223
|
|
|
144
224
|
window.addEventListener('resize', () => {
|
|
145
225
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
@@ -21,6 +21,7 @@ export function createRootTransformController({
|
|
|
21
21
|
transformHandle,
|
|
22
22
|
onTransformsInvalidated,
|
|
23
23
|
onCoordinateChanged,
|
|
24
|
+
onUniformScaleChanged,
|
|
24
25
|
}) {
|
|
25
26
|
const coordinateWorldPosition = new Vector3();
|
|
26
27
|
const coordinateTransformMatrix = new Matrix4();
|
|
@@ -48,6 +49,24 @@ export function createRootTransformController({
|
|
|
48
49
|
return getCurrentRootTransform(currentRootTransformMatrix).toArray();
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
function getUniformScale() {
|
|
53
|
+
const scale = transformHandle.scale;
|
|
54
|
+
const scaleValues = [scale.x, scale.y, scale.z]
|
|
55
|
+
.map((value) => Math.abs(value))
|
|
56
|
+
.filter((value) => Number.isFinite(value));
|
|
57
|
+
if (scaleValues.length === 0) {
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
return (
|
|
61
|
+
scaleValues.reduce((total, value) => total + value, 0) /
|
|
62
|
+
scaleValues.length
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function notifyUniformScaleChanged() {
|
|
67
|
+
onUniformScaleChanged?.(getUniformScale());
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
function getCurrentMatrix() {
|
|
52
71
|
return getObjectMatrix(editableGroup);
|
|
53
72
|
}
|
|
@@ -94,6 +113,7 @@ export function createRootTransformController({
|
|
|
94
113
|
} finally {
|
|
95
114
|
syncingTransformHandle = false;
|
|
96
115
|
}
|
|
116
|
+
notifyUniformScaleChanged();
|
|
97
117
|
}
|
|
98
118
|
|
|
99
119
|
function applySaved(matrix) {
|
|
@@ -113,6 +133,21 @@ export function createRootTransformController({
|
|
|
113
133
|
target: coordinateEditMatrix,
|
|
114
134
|
});
|
|
115
135
|
invalidate();
|
|
136
|
+
notifyUniformScaleChanged();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function applyUniformScale(scale) {
|
|
140
|
+
const nextScale = Number(scale);
|
|
141
|
+
if (!Number.isFinite(nextScale) || nextScale <= 0) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
transformHandle.scale.set(nextScale, nextScale, nextScale);
|
|
146
|
+
transformHandle.updateMatrix();
|
|
147
|
+
transformHandle.updateMatrixWorld(true);
|
|
148
|
+
applyFromRootTransform(transformHandle.matrix);
|
|
149
|
+
syncCoordinateInputs();
|
|
150
|
+
return true;
|
|
116
151
|
}
|
|
117
152
|
|
|
118
153
|
async function applyFromCoordinate(latitude, longitude, height) {
|
|
@@ -145,6 +180,7 @@ export function createRootTransformController({
|
|
|
145
180
|
lastSavedMatrix.identity();
|
|
146
181
|
resetEditableObjectTransform(transformHandle);
|
|
147
182
|
tilesTransformDirty = true;
|
|
183
|
+
notifyUniformScaleChanged();
|
|
148
184
|
}
|
|
149
185
|
|
|
150
186
|
function refresh(url) {
|
|
@@ -222,6 +258,7 @@ export function createRootTransformController({
|
|
|
222
258
|
applyFromCoordinate,
|
|
223
259
|
applyFromRootTransform,
|
|
224
260
|
applySaved,
|
|
261
|
+
applyUniformScale,
|
|
225
262
|
flush,
|
|
226
263
|
getCurrentMatrix,
|
|
227
264
|
getCurrentRootTransform,
|
|
@@ -229,6 +266,7 @@ export function createRootTransformController({
|
|
|
229
266
|
getIncrementalSinceSaved,
|
|
230
267
|
getLastSaved: () => lastSavedMatrix,
|
|
231
268
|
getLoadError: () => savedRootMatrixLoadError,
|
|
269
|
+
getUniformScale,
|
|
232
270
|
isSyncingHandle: () => syncingTransformHandle,
|
|
233
271
|
markDirty() {
|
|
234
272
|
tilesTransformDirty = true;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const TRACK_PIXELS_PER_SCALE_EXPONENT = 90;
|
|
2
|
+
|
|
3
|
+
function exponentToUniformScale(exponent) {
|
|
4
|
+
return 2 ** exponent;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function scaleToExponent(scale) {
|
|
8
|
+
return Math.log2(scale);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getFinitePositiveScale(value) {
|
|
12
|
+
const number = Number(value);
|
|
13
|
+
return Number.isFinite(number) && number > 0 ? number : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeUniformScale(value) {
|
|
17
|
+
const scale = getFinitePositiveScale(value);
|
|
18
|
+
if (scale === null) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return scale;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function formatUniformScale(scale) {
|
|
25
|
+
const value = Number(scale);
|
|
26
|
+
if (!Number.isFinite(value)) {
|
|
27
|
+
return '1';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const absValue = Math.abs(value);
|
|
31
|
+
if (absValue > 0 && (absValue < 0.00001 || absValue >= 1000000)) {
|
|
32
|
+
return value.toExponential(3).replace(/\.?0+e/, 'e');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const formatted =
|
|
36
|
+
absValue < 0.01
|
|
37
|
+
? value.toFixed(5)
|
|
38
|
+
: absValue < 0.1
|
|
39
|
+
? value.toFixed(4)
|
|
40
|
+
: absValue < 10
|
|
41
|
+
? value.toFixed(3)
|
|
42
|
+
: absValue < 100
|
|
43
|
+
? value.toFixed(2)
|
|
44
|
+
: value.toFixed(1);
|
|
45
|
+
|
|
46
|
+
return formatted.replace(/\.?0+$/, '');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createUniformScaleController({
|
|
50
|
+
applyScale,
|
|
51
|
+
uniformScaleTrackEl,
|
|
52
|
+
uniformScaleValueInput,
|
|
53
|
+
}) {
|
|
54
|
+
let uniformScale = 1;
|
|
55
|
+
let syncingInputs = false;
|
|
56
|
+
let trackDragStartClientX = 0;
|
|
57
|
+
let trackDragStartExponent = 0;
|
|
58
|
+
|
|
59
|
+
function getScaleExponent() {
|
|
60
|
+
return scaleToExponent(uniformScale);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function updateInputs() {
|
|
64
|
+
syncingInputs = true;
|
|
65
|
+
try {
|
|
66
|
+
const formattedScale = formatUniformScale(uniformScale);
|
|
67
|
+
uniformScaleTrackEl.setAttribute(
|
|
68
|
+
'aria-label',
|
|
69
|
+
`Scale x${formattedScale}`,
|
|
70
|
+
);
|
|
71
|
+
uniformScaleTrackEl.title = `Scale x${formattedScale}`;
|
|
72
|
+
uniformScaleValueInput.value = formatUniformScale(uniformScale);
|
|
73
|
+
} finally {
|
|
74
|
+
syncingInputs = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function applyUniformScale(nextScale) {
|
|
79
|
+
uniformScale = nextScale;
|
|
80
|
+
applyScale(nextScale);
|
|
81
|
+
updateInputs();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function setScale(scale) {
|
|
85
|
+
const nextScale = normalizeUniformScale(scale);
|
|
86
|
+
if (nextScale === null) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
applyUniformScale(nextScale);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function setScaleExponent(exponent) {
|
|
95
|
+
const exponentNumber = Number(exponent);
|
|
96
|
+
if (!Number.isFinite(exponentNumber)) {
|
|
97
|
+
updateInputs();
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const nextScale = exponentToUniformScale(exponentNumber);
|
|
102
|
+
if (!Number.isFinite(nextScale) || nextScale <= 0) {
|
|
103
|
+
updateInputs();
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
applyUniformScale(nextScale);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function beginTrackDrag(clientX) {
|
|
112
|
+
trackDragStartClientX = clientX;
|
|
113
|
+
trackDragStartExponent = getScaleExponent();
|
|
114
|
+
uniformScaleTrackEl.style.setProperty('--scale-track-offset', '0px');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function setScaleFromTrackClientX(clientX) {
|
|
118
|
+
const deltaX = clientX - trackDragStartClientX;
|
|
119
|
+
uniformScaleTrackEl.style.setProperty(
|
|
120
|
+
'--scale-track-offset',
|
|
121
|
+
`${deltaX}px`,
|
|
122
|
+
);
|
|
123
|
+
return setScaleExponent(
|
|
124
|
+
trackDragStartExponent + deltaX / TRACK_PIXELS_PER_SCALE_EXPONENT,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function nudgeScaleExponent(delta) {
|
|
129
|
+
return setScaleExponent(getScaleExponent() + delta);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function setScaleValue(value, { commit = false } = {}) {
|
|
133
|
+
if (syncingInputs) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const rawValue = typeof value === 'string' ? value.trim() : value;
|
|
138
|
+
if (rawValue === '' && !commit) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const scale = normalizeUniformScale(rawValue);
|
|
143
|
+
if (scale === null) {
|
|
144
|
+
if (commit) {
|
|
145
|
+
updateInputs();
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
applyUniformScale(scale);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function initializeInputs() {
|
|
155
|
+
uniformScaleValueInput.removeAttribute('max');
|
|
156
|
+
uniformScaleValueInput.removeAttribute('min');
|
|
157
|
+
uniformScaleValueInput.step = 'any';
|
|
158
|
+
updateInputs();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function syncFromRootScale(scale) {
|
|
162
|
+
const nextScale = normalizeUniformScale(scale);
|
|
163
|
+
uniformScale = nextScale === null ? 1 : nextScale;
|
|
164
|
+
updateInputs();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
beginTrackDrag,
|
|
169
|
+
formatScale: formatUniformScale,
|
|
170
|
+
getScale: () => uniformScale,
|
|
171
|
+
initializeInputs,
|
|
172
|
+
nudgeScaleExponent,
|
|
173
|
+
setScale,
|
|
174
|
+
setScaleExponent,
|
|
175
|
+
setScaleFromTrackClientX,
|
|
176
|
+
setScaleValue,
|
|
177
|
+
syncFromRootScale,
|
|
178
|
+
};
|
|
179
|
+
}
|