@geode/opengeodeweb-front 10.16.1 → 10.17.0
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/app/assets/viewer_svgs/camera-bookmark.svg +64 -0
- package/app/components/ActionButton.vue +10 -1
- package/app/components/CameraManager/List.vue +114 -0
- package/app/components/CameraManager/Saver.vue +50 -0
- package/app/components/CameraManager.vue +39 -0
- package/app/components/Recaptcha.vue +1 -1
- package/app/components/ViewToolbar.vue +14 -3
- package/app/stores/camera_manager.js +56 -0
- package/app/stores/hybrid_viewer.js +52 -67
- package/app/utils/local/microservices.js +1 -1
- package/app/utils/local/scripts.js +1 -1
- package/app/utils/server.js +1 -1
- package/internal/database/base_database.js +2 -0
- package/internal/database/database.js +1 -1
- package/internal/database/tables/camera_positions.js +4 -0
- package/internal/stores/hybrid_viewer.js +48 -70
- package/internal/stores/hybrid_viewer_camera_animation.js +108 -0
- package/package.json +1 -1
- package/server/api/app/run_cloud.js +1 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<svg
|
|
3
|
+
version="1.0"
|
|
4
|
+
width="514px"
|
|
5
|
+
height="486px"
|
|
6
|
+
viewBox="0 0 514 486"
|
|
7
|
+
preserveAspectRatio="xMidYMid meet"
|
|
8
|
+
id="svg14"
|
|
9
|
+
sodipodi:docname="camera-bookmark.svg"
|
|
10
|
+
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
12
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
15
|
+
<defs
|
|
16
|
+
id="defs18" />
|
|
17
|
+
<sodipodi:namedview
|
|
18
|
+
id="namedview16"
|
|
19
|
+
pagecolor="#505050"
|
|
20
|
+
bordercolor="#eeeeee"
|
|
21
|
+
borderopacity="1"
|
|
22
|
+
inkscape:pageshadow="0"
|
|
23
|
+
inkscape:pageopacity="0"
|
|
24
|
+
inkscape:pagecheckerboard="0"
|
|
25
|
+
showgrid="false"
|
|
26
|
+
inkscape:zoom="1.1756014"
|
|
27
|
+
inkscape:cx="202.02426"
|
|
28
|
+
inkscape:cy="238.60128"
|
|
29
|
+
inkscape:window-width="1920"
|
|
30
|
+
inkscape:window-height="964"
|
|
31
|
+
inkscape:window-x="0"
|
|
32
|
+
inkscape:window-y="0"
|
|
33
|
+
inkscape:window-maximized="1"
|
|
34
|
+
inkscape:current-layer="g12">
|
|
35
|
+
<inkscape:grid
|
|
36
|
+
type="xygrid"
|
|
37
|
+
id="grid160" />
|
|
38
|
+
</sodipodi:namedview>
|
|
39
|
+
<g
|
|
40
|
+
fill="#000000"
|
|
41
|
+
id="g12"
|
|
42
|
+
transform="matrix(1.0257024,0,0,1.0257024,-37.052727,-7.1977612)">
|
|
43
|
+
<path
|
|
44
|
+
d="m 201.51433,384.71831 c -9.66032,-3.00746 -16.58659,-10.3894 -18.77383,-19.68518 -0.45568,-1.73156 -0.82021,-34.72246 -0.82021,-73.36372 l -0.0911,-70.174 25.33553,-0.45567 c 28.16074,-0.54681 28.88983,-0.72908 35.81608,-7.0174 2.18725,-1.91384 4.9213,-5.55924 6.10606,-8.29329 1.82269,-4.19222 2.0961,-6.19718 2.0961,-16.13091 v -11.39188 h 6.83513 6.83513 v -17.6802 c 0,-9.75145 0.36455,-18.77383 0.82021,-19.95858 0.54682,-1.27589 1.82271,-2.64292 3.00746,-3.00745 2.27838,-0.82022 90.49712,-0.54682 92.77549,0.18226 0.72908,0.27341 4.37449,3.82768 7.92875,7.92875 3.6454,4.10108 9.02237,10.02486 11.84756,13.12346 l 5.28584,5.65036 7.2908,0.18227 7.29081,0.18228 0.63794,-4.19222 c 0.2734,-2.27837 0.82022,-4.64788 1.18476,-5.10356 0.82021,-1.45816 27.06711,-1.36703 28.52527,0.0911 0.63795,0.63795 1.09362,2.82518 1.09362,5.01242 0,2.46065 0.45568,4.10108 1.18475,4.28335 0.54682,0.18227 3.18973,0.82021 5.65038,1.36703 7.47307,1.8227 15.67523,9.84259 17.86248,17.6802 1.54928,5.37697 2.00496,172.97434 0.5468,180.08288 -1.18475,6.10605 -5.1947,12.21209 -10.48053,16.22203 -7.92875,6.01492 -1.36703,5.74152 -127.68022,5.65038 -89.40349,0 -115.28584,-0.27341 -118.11103,-1.18475 z m 139.0721,-41.19305 c 12.7589,-4.00995 20.77879,-9.11351 31.44159,-20.04972 20.41426,-20.96106 25.24441,-52.58492 12.02983,-79.10523 -7.0174,-13.94367 -17.58907,-24.69759 -31.62386,-31.89727 -9.75146,-5.01242 -15.67523,-6.74399 -26.33804,-7.74648 -21.69014,-2.00497 -41.92213,5.83264 -57.23281,22.05468 -15.12843,16.03978 -21.59902,35.26928 -19.32064,57.14169 2.64291,24.60646 19.77631,46.93456 43.74483,57.05055 15.03728,6.37945 32.26181,7.2908 47.2991,2.55178 z m 7.56421,-176.80201 c 2.0961,-2.00498 1.8227,-5.10357 -1.00249,-11.30076 -1.5493,-3.6454 -3.09859,-5.65037 -4.28335,-5.92377 -3.82767,-0.91136 -43.83596,-1.09363 -45.84093,-0.18227 -1.27589,0.5468 -2.82519,3.00745 -4.28335,6.65286 -2.73405,6.74399 -2.91632,8.8401 -0.91135,10.75394 1.18476,1.27589 5.01243,1.45815 28.16073,1.45815 23.14831,0 26.97598,-0.18226 28.16074,-1.45815 z"
|
|
45
|
+
id="path2"
|
|
46
|
+
style="stroke-width:0.911351" />
|
|
47
|
+
<path
|
|
48
|
+
d="m 309.14483,317.55177 c -9.75145,-2.27838 -22.32809,-12.2121 -27.34052,-21.69015 -8.8401,-16.49544 -5.92377,-37.00083 7.29081,-50.67109 22.87489,-23.60398 61.97184,-14.39934 72.36123,16.95113 2.09611,6.56172 2.46065,18.86494 0.72908,24.87986 -4.00994,13.76139 -16.22203,26.06463 -29.80116,30.07457 -5.37697,1.5493 -17.4068,1.82271 -23.23944,0.45568 z"
|
|
49
|
+
id="path4"
|
|
50
|
+
style="stroke-width:0.911351" />
|
|
51
|
+
<path
|
|
52
|
+
d="m 129.80545,209.69281 c -15.94627,-0.85865 -18.39954,-1.96262 -20.73015,-9.81309 -0.85864,-2.94393 -1.10397,-22.69277 -0.85864,-56.91591 L 108.58465,90.341125 113.1232,86.170562 117.78442,82 h 44.52689 44.64955 l 14.22898,14.351642 14.35164,14.474308 v 44.52688 c 0,49.31078 -0.12266,50.41475 -7.72781,53.48133 -4.17057,1.7173 -68.93694,2.20795 -98.00822,0.85865 z m 93.22434,-11.40772 c 1.10398,-1.10397 1.47197,-12.14369 1.47197,-41.82829 v -40.47898 l -10.67174,-10.91707 -10.79439,-10.794389 -41.09231,-0.367992 -41.09231,-0.245327 -0.36799,52.377358 -0.24532,52.50002 49.31076,0.368 c 27.23132,0.12266 50.04675,0.36799 50.78273,0.61331 0.61332,0.12267 1.83995,-0.36799 2.6986,-1.22664 z"
|
|
53
|
+
id="path6"
|
|
54
|
+
style="stroke-width:1.22663" />
|
|
55
|
+
<path
|
|
56
|
+
d="m 163.78327,186.75471 c -8.21846,-4.29322 -11.65304,-9.69042 -11.65304,-18.39953 0,-14.35164 14.22897,-24.04207 27.72197,-18.8902 7.85048,3.0666 13.73833,13.98365 12.1437,22.5701 -2.45327,12.87968 -17.17291,20.48482 -28.21263,14.71963 z"
|
|
57
|
+
id="path8"
|
|
58
|
+
style="stroke-width:1.22663" />
|
|
59
|
+
<path
|
|
60
|
+
d="m 131.64541,130.20679 c -1.10397,-0.36799 -1.59463,-4.29323 -1.59463,-13.37032 v -12.75702 h 31.27921 31.27922 v 12.87968 c 0,9.4451 -0.36799,12.87967 -1.47196,13.00234 -9.32243,0.49065 -58.38787,0.73598 -59.49184,0.24532 z"
|
|
61
|
+
id="path10"
|
|
62
|
+
style="stroke-width:1.22663" />
|
|
63
|
+
</g>
|
|
64
|
+
</svg>
|
|
@@ -25,6 +25,15 @@ const emit = defineEmits(["click"]);
|
|
|
25
25
|
icon
|
|
26
26
|
@click="emit('click', $event)"
|
|
27
27
|
>
|
|
28
|
-
<v-icon :size="iconSize">{{
|
|
28
|
+
<v-icon v-if="typeof icon === 'string' && icon.startsWith('mdi-')" :size="iconSize">{{
|
|
29
|
+
icon
|
|
30
|
+
}}</v-icon>
|
|
31
|
+
<v-img
|
|
32
|
+
v-else
|
|
33
|
+
:src="icon"
|
|
34
|
+
:height="iconSize"
|
|
35
|
+
:width="iconSize"
|
|
36
|
+
class="d-flex justify-center align-center"
|
|
37
|
+
/>
|
|
29
38
|
</v-btn>
|
|
30
39
|
</template>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useCameraManagerStore } from "@ogw_front/stores/camera_manager";
|
|
3
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
4
|
+
|
|
5
|
+
const cameraManagerStore = useCameraManagerStore();
|
|
6
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
7
|
+
|
|
8
|
+
const savedPositions = cameraManagerStore.refAllCameraPositions();
|
|
9
|
+
|
|
10
|
+
const editingId = ref(undefined);
|
|
11
|
+
const editingName = ref("");
|
|
12
|
+
|
|
13
|
+
async function restorePosition(positionId) {
|
|
14
|
+
const position = await cameraManagerStore.getCameraPosition(positionId);
|
|
15
|
+
if (position) {
|
|
16
|
+
if (hybridViewerStore.genericRenderWindow) {
|
|
17
|
+
hybridViewerStore.setCamera(position.camera_options);
|
|
18
|
+
} else {
|
|
19
|
+
await cameraManagerStore.restoreCameraPosition(positionId);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function deletePosition(positionId) {
|
|
25
|
+
await cameraManagerStore.deleteCameraPosition(positionId);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function startEditing(position) {
|
|
29
|
+
editingId.value = position.id;
|
|
30
|
+
editingName.value = position.name;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function saveRename() {
|
|
34
|
+
if (editingName.value) {
|
|
35
|
+
await cameraManagerStore.renameCameraPosition(editingId.value, editingName.value);
|
|
36
|
+
}
|
|
37
|
+
editingId.value = undefined;
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<v-list v-if="savedPositions.length > 0" class="bg-transparent pa-2">
|
|
43
|
+
<v-list-item
|
|
44
|
+
v-for="position in savedPositions"
|
|
45
|
+
:key="position.id"
|
|
46
|
+
class="rounded-lg mb-1"
|
|
47
|
+
:active="editingId === position.id"
|
|
48
|
+
active-color="primary"
|
|
49
|
+
>
|
|
50
|
+
<template #prepend>
|
|
51
|
+
<v-btn
|
|
52
|
+
icon
|
|
53
|
+
variant="tonal"
|
|
54
|
+
color="success"
|
|
55
|
+
size="small"
|
|
56
|
+
class="mr-2"
|
|
57
|
+
@click="restorePosition(position.id)"
|
|
58
|
+
>
|
|
59
|
+
<v-icon size="20">mdi-play</v-icon>
|
|
60
|
+
<v-tooltip activator="parent" location="top">Restore</v-tooltip>
|
|
61
|
+
</v-btn>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<v-list-item-title class="font-weight-bold">
|
|
65
|
+
<v-text-field
|
|
66
|
+
v-if="editingId === position.id"
|
|
67
|
+
v-model="editingName"
|
|
68
|
+
density="compact"
|
|
69
|
+
variant="underlined"
|
|
70
|
+
hide-details
|
|
71
|
+
autofocus
|
|
72
|
+
@keyup.enter="saveRename"
|
|
73
|
+
@blur="saveRename"
|
|
74
|
+
></v-text-field>
|
|
75
|
+
<span v-else>{{ position.name }}</span>
|
|
76
|
+
</v-list-item-title>
|
|
77
|
+
|
|
78
|
+
<template #append>
|
|
79
|
+
<div class="d-flex g-1">
|
|
80
|
+
<v-btn
|
|
81
|
+
icon
|
|
82
|
+
variant="text"
|
|
83
|
+
size="small"
|
|
84
|
+
color="grey-darken-1"
|
|
85
|
+
@click="startEditing(position)"
|
|
86
|
+
>
|
|
87
|
+
<v-icon size="18">mdi-pencil</v-icon>
|
|
88
|
+
<v-tooltip activator="parent" location="top">Rename</v-tooltip>
|
|
89
|
+
</v-btn>
|
|
90
|
+
<v-btn
|
|
91
|
+
icon
|
|
92
|
+
variant="text"
|
|
93
|
+
size="small"
|
|
94
|
+
color="error"
|
|
95
|
+
@click="deletePosition(position.id)"
|
|
96
|
+
>
|
|
97
|
+
<v-icon size="18">mdi-delete</v-icon>
|
|
98
|
+
<v-tooltip activator="parent" location="top">Delete</v-tooltip>
|
|
99
|
+
</v-btn>
|
|
100
|
+
</div>
|
|
101
|
+
</template>
|
|
102
|
+
</v-list-item>
|
|
103
|
+
</v-list>
|
|
104
|
+
<div v-else class="text-center text-grey-lighten-1 py-8 italic">
|
|
105
|
+
<v-icon size="48" class="mb-2 d-block mx-auto opacity-20">mdi-camera-off</v-icon>
|
|
106
|
+
No saved positions yet.
|
|
107
|
+
</div>
|
|
108
|
+
</template>
|
|
109
|
+
|
|
110
|
+
<style scoped>
|
|
111
|
+
:deep(.v-list-item__prepend) {
|
|
112
|
+
margin-inline-end: 12px !important;
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useCameraManagerStore } from "@ogw_front/stores/camera_manager";
|
|
3
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
4
|
+
|
|
5
|
+
const cameraManagerStore = useCameraManagerStore();
|
|
6
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
7
|
+
|
|
8
|
+
const newPositionName = ref("");
|
|
9
|
+
|
|
10
|
+
async function saveCurrentPosition() {
|
|
11
|
+
if (!newPositionName.value) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
await cameraManagerStore.saveCameraPosition(
|
|
15
|
+
newPositionName.value,
|
|
16
|
+
toRaw(hybridViewerStore.camera_options),
|
|
17
|
+
);
|
|
18
|
+
newPositionName.value = "";
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<v-container class="pa-5 pb-2 bg-surface-variant-lighten-5">
|
|
24
|
+
<v-row dense>
|
|
25
|
+
<v-col cols="12">
|
|
26
|
+
<v-text-field
|
|
27
|
+
v-model="newPositionName"
|
|
28
|
+
label="Position Name"
|
|
29
|
+
placeholder="e.g. Front View"
|
|
30
|
+
density="compact"
|
|
31
|
+
variant="outlined"
|
|
32
|
+
hide-details
|
|
33
|
+
class="mb-3"
|
|
34
|
+
></v-text-field>
|
|
35
|
+
</v-col>
|
|
36
|
+
<v-col cols="12" class="d-flex align-center">
|
|
37
|
+
<v-btn
|
|
38
|
+
color="primary"
|
|
39
|
+
variant="elevated"
|
|
40
|
+
block
|
|
41
|
+
:disabled="!newPositionName"
|
|
42
|
+
@click="saveCurrentPosition"
|
|
43
|
+
height="40"
|
|
44
|
+
>
|
|
45
|
+
Save
|
|
46
|
+
</v-btn>
|
|
47
|
+
</v-col>
|
|
48
|
+
</v-row>
|
|
49
|
+
</v-container>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import GlassCard from "@ogw_front/components/GlassCard";
|
|
3
|
+
import List from "@ogw_front/components/CameraManager/List";
|
|
4
|
+
import Saver from "@ogw_front/components/CameraManager/Saver";
|
|
5
|
+
|
|
6
|
+
const emit = defineEmits(["close"]);
|
|
7
|
+
|
|
8
|
+
const { show_dialog, width } = defineProps({
|
|
9
|
+
show_dialog: { type: Boolean, required: true },
|
|
10
|
+
width: { type: Number, required: false, default: 450 },
|
|
11
|
+
});
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<GlassCard
|
|
16
|
+
v-if="show_dialog"
|
|
17
|
+
@click.stop
|
|
18
|
+
title="Camera Positions"
|
|
19
|
+
:width="width"
|
|
20
|
+
:ripple="false"
|
|
21
|
+
variant="panel"
|
|
22
|
+
padding="pa-0"
|
|
23
|
+
class="position-absolute elevation-24"
|
|
24
|
+
style="z-index: 2; top: 90px; right: 55px"
|
|
25
|
+
>
|
|
26
|
+
<v-card-text class="pa-0">
|
|
27
|
+
<Saver />
|
|
28
|
+
<v-divider></v-divider>
|
|
29
|
+
<List />
|
|
30
|
+
</v-card-text>
|
|
31
|
+
|
|
32
|
+
<v-divider></v-divider>
|
|
33
|
+
|
|
34
|
+
<v-card-actions class="pa-4">
|
|
35
|
+
<v-spacer></v-spacer>
|
|
36
|
+
<v-btn variant="text" color="grey-darken-1" @click="emit('close')">Close</v-btn>
|
|
37
|
+
</v-card-actions>
|
|
38
|
+
</GlassCard>
|
|
39
|
+
</template>
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
3
|
-
|
|
4
2
|
import ActionButton from "@ogw_front/components/ActionButton.vue";
|
|
3
|
+
import CameraBookmarkIcon from "@ogw_front/assets/viewer_svgs/camera-bookmark.svg";
|
|
4
|
+
import CameraManager from "@ogw_front/components/CameraManager";
|
|
5
5
|
import CameraOrientation from "@ogw_front/components/CameraOrientation.vue";
|
|
6
6
|
import Screenshot from "@ogw_front/components/Screenshot";
|
|
7
7
|
import ZScaling from "@ogw_front/components/ZScaling";
|
|
8
|
-
|
|
8
|
+
import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
9
9
|
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
10
10
|
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
11
11
|
|
|
12
12
|
const hybridViewerStore = useHybridViewerStore();
|
|
13
13
|
const viewerStore = useViewerStore();
|
|
14
14
|
const take_screenshot = ref(false);
|
|
15
|
+
const show_camera_manager = ref(false);
|
|
15
16
|
const showCameraOrientation = ref(false);
|
|
16
17
|
const showZScaling = ref(false);
|
|
17
18
|
const grid_scale = ref(false);
|
|
@@ -51,6 +52,14 @@ const camera_options = [
|
|
|
51
52
|
showCameraOrientation.value = !showCameraOrientation.value;
|
|
52
53
|
},
|
|
53
54
|
},
|
|
55
|
+
{
|
|
56
|
+
tooltip: "Manage camera positions",
|
|
57
|
+
icon: CameraBookmarkIcon,
|
|
58
|
+
iconSize: 34,
|
|
59
|
+
action: () => {
|
|
60
|
+
show_camera_manager.value = !show_camera_manager.value;
|
|
61
|
+
},
|
|
62
|
+
},
|
|
54
63
|
{
|
|
55
64
|
tooltip: "Take a screenshot",
|
|
56
65
|
icon: "mdi-camera",
|
|
@@ -91,6 +100,7 @@ const camera_options = [
|
|
|
91
100
|
<ActionButton
|
|
92
101
|
:icon="camera_option.icon"
|
|
93
102
|
:tooltip="camera_option.tooltip"
|
|
103
|
+
:icon-size="camera_option.iconSize"
|
|
94
104
|
tooltip-location="left"
|
|
95
105
|
@click.stop="camera_option.action"
|
|
96
106
|
/>
|
|
@@ -103,6 +113,7 @@ const camera_options = [
|
|
|
103
113
|
@select="hybridViewerStore.setCameraOrientation"
|
|
104
114
|
/>
|
|
105
115
|
<Screenshot v-model="take_screenshot" />
|
|
116
|
+
<CameraManager :show_dialog="show_camera_manager" @close="show_camera_manager = false" />
|
|
106
117
|
<ZScaling
|
|
107
118
|
v-model:show="showZScaling"
|
|
108
119
|
v-model="zScale"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Third party imports
|
|
2
|
+
import { liveQuery } from "dexie";
|
|
3
|
+
import { useObservable } from "@vueuse/rxjs";
|
|
4
|
+
import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
5
|
+
|
|
6
|
+
// Local imports
|
|
7
|
+
import { database } from "@ogw_internal/database/database.js";
|
|
8
|
+
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
9
|
+
|
|
10
|
+
export const useCameraManagerStore = defineStore("camera_manager", () => {
|
|
11
|
+
const viewerStore = useViewerStore();
|
|
12
|
+
|
|
13
|
+
function refAllCameraPositions() {
|
|
14
|
+
return useObservable(
|
|
15
|
+
liveQuery(() => database.camera_positions.toArray()),
|
|
16
|
+
{ initialValue: [] },
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function getCameraPosition(id) {
|
|
21
|
+
return await database.camera_positions.get(id);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function saveCameraPosition(name, camera_options) {
|
|
25
|
+
await database.camera_positions.put({
|
|
26
|
+
name,
|
|
27
|
+
camera_options,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function restoreCameraPosition(id) {
|
|
32
|
+
const position = await database.camera_positions.get(id);
|
|
33
|
+
if (position) {
|
|
34
|
+
await viewerStore.request(viewer_schemas.opengeodeweb_viewer.viewer.update_camera, {
|
|
35
|
+
camera_options: position.camera_options,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function deleteCameraPosition(id) {
|
|
41
|
+
await database.camera_positions.delete(id);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function renameCameraPosition(id, newName) {
|
|
45
|
+
await database.camera_positions.update(id, { name: newName });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
refAllCameraPositions,
|
|
50
|
+
getCameraPosition,
|
|
51
|
+
saveCameraPosition,
|
|
52
|
+
restoreCameraPosition,
|
|
53
|
+
deleteCameraPosition,
|
|
54
|
+
renameCameraPosition,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ACTOR_COLOR,
|
|
3
|
-
ALIGNMENT_THRESHOLD,
|
|
4
3
|
BACKGROUND_COLOR,
|
|
5
|
-
BUMP_MULTIPLIER,
|
|
6
|
-
EASE_EXPONENT,
|
|
7
|
-
LONG_ANIMATION_DURATION,
|
|
8
|
-
SHORT_ANIMATION_DURATION,
|
|
9
4
|
WHEEL_TIME_OUT_MS,
|
|
10
5
|
applySnapshot,
|
|
11
6
|
computeAverageBrightness,
|
|
12
7
|
getCameraOptions,
|
|
13
8
|
performCameraOrientation,
|
|
14
9
|
performClickPicking,
|
|
10
|
+
performSetCamera,
|
|
15
11
|
} from "@ogw_internal/stores/hybrid_viewer";
|
|
16
12
|
import { newInstance as vtkActor } from "@kitware/vtk.js/Rendering/Core/Actor";
|
|
17
13
|
import { newInstance as vtkGenericRenderWindow } from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow";
|
|
@@ -26,16 +22,17 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
|
|
|
26
22
|
|
|
27
23
|
export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
28
24
|
const dataStore = useDataStore();
|
|
29
|
-
const viewerStore = useViewerStore();
|
|
30
25
|
const hybridDb = reactive({});
|
|
31
|
-
const
|
|
26
|
+
const viewerStore = useViewerStore();
|
|
32
27
|
const camera_options = reactive({});
|
|
33
28
|
const genericRenderWindow = reactive({});
|
|
29
|
+
const status = ref(Status.NOT_CREATED);
|
|
34
30
|
const is_moving = ref(false);
|
|
35
31
|
const is_picking = ref(false);
|
|
36
32
|
const zScale = ref(1);
|
|
37
|
-
let viewStream = undefined;
|
|
38
33
|
let imageStyle = undefined;
|
|
34
|
+
let viewStream = undefined;
|
|
35
|
+
let wheelEventEndTimeout = undefined;
|
|
39
36
|
const gridActor = undefined;
|
|
40
37
|
|
|
41
38
|
watch(is_picking, (value) => {
|
|
@@ -63,12 +60,9 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
63
60
|
background: BACKGROUND_COLOR,
|
|
64
61
|
listenWindowResize: false,
|
|
65
62
|
});
|
|
66
|
-
|
|
67
63
|
const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow();
|
|
68
64
|
imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style;
|
|
69
|
-
imageStyle
|
|
70
|
-
imageStyle.zIndex = 1;
|
|
71
|
-
|
|
65
|
+
Object.assign(imageStyle, { transition: "opacity 0.1s ease-in", zIndex: 1 });
|
|
72
66
|
await viewerStore.ws_connect();
|
|
73
67
|
viewStream = viewerStore.client.getImageStream().createViewStream("-1");
|
|
74
68
|
viewStream.onImageReady((event) => {
|
|
@@ -79,13 +73,11 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
79
73
|
webGLRenderWindow.setBackgroundImage(event.image);
|
|
80
74
|
imageStyle.opacity = 1;
|
|
81
75
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const camera = renderer.getActiveCamera();
|
|
76
|
+
const camera = genericRenderWindow.value.getRenderer().getActiveCamera();
|
|
77
|
+
Object.assign(camera_options, getCameraOptions(camera));
|
|
85
78
|
camera.onModified(() => {
|
|
86
79
|
Object.assign(camera_options, getCameraOptions(camera));
|
|
87
80
|
});
|
|
88
|
-
|
|
89
81
|
status.value = Status.CREATED;
|
|
90
82
|
}
|
|
91
83
|
|
|
@@ -93,14 +85,13 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
93
85
|
if (!genericRenderWindow.value) {
|
|
94
86
|
return;
|
|
95
87
|
}
|
|
96
|
-
const value = await dataStore.item(id);
|
|
97
88
|
const reader = vtkXMLPolyDataReader();
|
|
98
|
-
const
|
|
99
|
-
await reader.parseAsArrayBuffer(
|
|
100
|
-
const
|
|
89
|
+
const value = await dataStore.item(id);
|
|
90
|
+
await reader.parseAsArrayBuffer(new TextEncoder().encode(value.binary_light_viewable));
|
|
91
|
+
const actor = vtkActor();
|
|
101
92
|
const mapper = vtkMapper();
|
|
93
|
+
const polydata = reader.getOutputData(0);
|
|
102
94
|
mapper.setInputData(polydata);
|
|
103
|
-
const actor = vtkActor();
|
|
104
95
|
actor.getProperty().setColor(ACTOR_COLOR);
|
|
105
96
|
actor.setMapper(mapper);
|
|
106
97
|
const renderer = genericRenderWindow.value.getRenderer();
|
|
@@ -116,8 +107,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
116
107
|
if (!hybridDb[id]) {
|
|
117
108
|
return;
|
|
118
109
|
}
|
|
119
|
-
|
|
120
|
-
renderer.removeActor(hybridDb[id].actor);
|
|
110
|
+
genericRenderWindow.value.getRenderer().removeActor(hybridDb[id].actor);
|
|
121
111
|
genericRenderWindow.value.getRenderWindow().render();
|
|
122
112
|
delete hybridDb[id];
|
|
123
113
|
}
|
|
@@ -127,14 +117,13 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
127
117
|
return;
|
|
128
118
|
}
|
|
129
119
|
hybridDb[id].actor.setVisibility(visibility);
|
|
130
|
-
|
|
131
|
-
renderWindow.render();
|
|
120
|
+
genericRenderWindow.value.getRenderWindow().render();
|
|
132
121
|
}
|
|
122
|
+
|
|
133
123
|
async function setZScaling(z_scale) {
|
|
134
124
|
zScale.value = z_scale;
|
|
135
125
|
const renderer = genericRenderWindow.value.getRenderer();
|
|
136
|
-
const
|
|
137
|
-
for (const actor of actors) {
|
|
126
|
+
for (const actor of renderer.getActors()) {
|
|
138
127
|
if (actor !== gridActor) {
|
|
139
128
|
const scale = actor.getScale();
|
|
140
129
|
actor.setScale(scale[0], scale[1], z_scale);
|
|
@@ -143,48 +132,49 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
143
132
|
renderer.resetCamera();
|
|
144
133
|
genericRenderWindow.value.getRenderWindow().render();
|
|
145
134
|
const schema = viewer_schemas?.opengeodeweb_viewer?.viewer?.set_z_scaling;
|
|
146
|
-
if (
|
|
147
|
-
|
|
135
|
+
if (schema) {
|
|
136
|
+
await viewerStore.request(schema, { z_scale });
|
|
148
137
|
}
|
|
149
|
-
await viewerStore.request(schema, {
|
|
150
|
-
z_scale,
|
|
151
|
-
});
|
|
152
138
|
remoteRender();
|
|
153
139
|
}
|
|
154
140
|
|
|
155
141
|
function resetCamera() {
|
|
156
|
-
|
|
157
|
-
renderer.resetCamera();
|
|
142
|
+
genericRenderWindow.value.getRenderer().resetCamera();
|
|
158
143
|
genericRenderWindow.value.getRenderWindow().render();
|
|
159
144
|
syncRemoteCamera();
|
|
160
145
|
}
|
|
161
146
|
|
|
147
|
+
function setCamera(new_camera_options) {
|
|
148
|
+
performSetCamera(new_camera_options, {
|
|
149
|
+
genericRenderWindow: genericRenderWindow.value,
|
|
150
|
+
is_moving,
|
|
151
|
+
imageStyle,
|
|
152
|
+
syncRemoteCamera,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
162
156
|
function setCameraOrientation(orientation) {
|
|
163
157
|
performCameraOrientation(orientation, {
|
|
164
158
|
genericRenderWindow: genericRenderWindow.value,
|
|
165
159
|
is_moving,
|
|
166
160
|
imageStyle,
|
|
167
161
|
syncRemoteCamera,
|
|
168
|
-
constants: {
|
|
169
|
-
ALIGNMENT_THRESHOLD,
|
|
170
|
-
BUMP_MULTIPLIER,
|
|
171
|
-
EASE_EXPONENT,
|
|
172
|
-
LONG_ANIMATION_DURATION,
|
|
173
|
-
SHORT_ANIMATION_DURATION,
|
|
174
|
-
},
|
|
175
162
|
});
|
|
176
163
|
}
|
|
177
164
|
|
|
178
165
|
function syncRemoteCamera() {
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
166
|
+
const camera = genericRenderWindow.value.getRenderer().getActiveCamera();
|
|
167
|
+
const options = getCameraOptions(camera);
|
|
168
|
+
viewerStore.request(
|
|
169
|
+
viewer_schemas.opengeodeweb_viewer.viewer.update_camera,
|
|
170
|
+
{ camera_options: options },
|
|
171
|
+
{
|
|
172
|
+
response_function: () => {
|
|
173
|
+
remoteRender();
|
|
174
|
+
Object.assign(camera_options, options);
|
|
175
|
+
},
|
|
186
176
|
},
|
|
187
|
-
|
|
177
|
+
);
|
|
188
178
|
}
|
|
189
179
|
|
|
190
180
|
function remoteRender() {
|
|
@@ -199,10 +189,8 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
199
189
|
const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow();
|
|
200
190
|
webGLRenderWindow.setUseBackgroundImage(true);
|
|
201
191
|
imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style;
|
|
202
|
-
imageStyle
|
|
203
|
-
imageStyle.zIndex = 1;
|
|
192
|
+
Object.assign(imageStyle, { transition: "opacity 0.1s ease-in", zIndex: 1 });
|
|
204
193
|
resize(container.value.$el.offsetWidth, container.value.$el.offsetHeight);
|
|
205
|
-
|
|
206
194
|
useMousePressed({
|
|
207
195
|
target: container,
|
|
208
196
|
onPressed: (event) => {
|
|
@@ -225,18 +213,17 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
225
213
|
imageStyle.opacity = 0;
|
|
226
214
|
},
|
|
227
215
|
onReleased: () => {
|
|
228
|
-
if (
|
|
229
|
-
|
|
216
|
+
if (is_moving.value) {
|
|
217
|
+
is_moving.value = false;
|
|
218
|
+
syncRemoteCamera();
|
|
230
219
|
}
|
|
231
|
-
is_moving.value = false;
|
|
232
|
-
syncRemoteCamera();
|
|
233
220
|
},
|
|
234
221
|
});
|
|
235
|
-
|
|
236
|
-
let wheelEventEndTimeout = undefined;
|
|
237
222
|
useEventListener(container, "wheel", () => {
|
|
238
223
|
is_moving.value = true;
|
|
239
|
-
imageStyle
|
|
224
|
+
if (imageStyle) {
|
|
225
|
+
imageStyle.opacity = 0;
|
|
226
|
+
}
|
|
240
227
|
clearTimeout(wheelEventEndTimeout);
|
|
241
228
|
wheelEventEndTimeout = setTimeout(() => {
|
|
242
229
|
is_moving.value = false;
|
|
@@ -256,8 +243,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
256
243
|
await nextTick();
|
|
257
244
|
webGLRenderWindow.setSize(width, height);
|
|
258
245
|
viewStream.setSize(width, height);
|
|
259
|
-
|
|
260
|
-
renderWindow.render();
|
|
246
|
+
genericRenderWindow.value.getRenderWindow().render();
|
|
261
247
|
remoteRender();
|
|
262
248
|
}
|
|
263
249
|
|
|
@@ -271,10 +257,8 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
271
257
|
}
|
|
272
258
|
|
|
273
259
|
function exportStores() {
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
const cameraSnapshot = getCameraOptions(camera) || camera_options;
|
|
277
|
-
return { zScale: zScale.value, camera_options: cameraSnapshot };
|
|
260
|
+
const camera = genericRenderWindow.value.getRenderer().getActiveCamera();
|
|
261
|
+
return { zScale: zScale.value, camera_options: getCameraOptions(camera) || camera_options };
|
|
278
262
|
}
|
|
279
263
|
|
|
280
264
|
async function importStores(snapshot) {
|
|
@@ -282,13 +266,13 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
282
266
|
genericRenderWindow: genericRenderWindow.value,
|
|
283
267
|
setZScaling,
|
|
284
268
|
syncRemoteCamera,
|
|
269
|
+
setCamera,
|
|
285
270
|
});
|
|
286
271
|
}
|
|
287
272
|
|
|
288
273
|
function clear() {
|
|
289
274
|
const renderer = genericRenderWindow.value.getRenderer();
|
|
290
|
-
const
|
|
291
|
-
for (const actor of actors) {
|
|
275
|
+
for (const actor of renderer.getActors()) {
|
|
292
276
|
renderer.removeActor(actor);
|
|
293
277
|
}
|
|
294
278
|
genericRenderWindow.value.getRenderWindow().render();
|
|
@@ -305,6 +289,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
305
289
|
setVisibility,
|
|
306
290
|
setZScaling,
|
|
307
291
|
syncRemoteCamera,
|
|
292
|
+
setCamera,
|
|
308
293
|
initHybridViewer,
|
|
309
294
|
remoteRender,
|
|
310
295
|
resize,
|
|
@@ -49,7 +49,7 @@ async function runScript(
|
|
|
49
49
|
child.on("kill", () => {
|
|
50
50
|
console.log(`[${execName}] process killed`);
|
|
51
51
|
});
|
|
52
|
-
child.name = command.replace(/^.*[\\/]
|
|
52
|
+
child.name = command.replace(/^.*[\\/]/u, "");
|
|
53
53
|
|
|
54
54
|
try {
|
|
55
55
|
return await pTimeout(waitForReady(child, expectedResponse), {
|
|
@@ -27,7 +27,7 @@ async function waitNuxt(nuxtProcess) {
|
|
|
27
27
|
for await (const [data] of on(nuxtProcess.stdout, "data")) {
|
|
28
28
|
const output = data.toString();
|
|
29
29
|
console.log("Nuxt:", output);
|
|
30
|
-
const portMatch = output.match(/Listening on http:\/\/\[::\]:(\d+)/);
|
|
30
|
+
const portMatch = output.match(/Listening on http:\/\/\[::\]:(\d+)/u);
|
|
31
31
|
if (portMatch) {
|
|
32
32
|
const [, nuxtPort] = portMatch;
|
|
33
33
|
|
package/app/utils/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
// Third party imports
|
|
6
6
|
import JSZip from "jszip";
|
|
7
7
|
|
|
8
|
-
async function unzipFile(zipFilePath, outputDir = zipFilePath.replace(/\.[^/.]
|
|
8
|
+
async function unzipFile(zipFilePath, outputDir = zipFilePath.replace(/\.[^/.]+$/u, "")) {
|
|
9
9
|
console.log("Unzipping file...", zipFilePath, outputDir);
|
|
10
10
|
try {
|
|
11
11
|
const data = await fs.promises.readFile(zipFilePath);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Dexie } from "dexie";
|
|
2
|
+
import { cameraPositionsTable } from "./tables/camera_positions";
|
|
2
3
|
import { dataStyleTable } from "./tables/data_style";
|
|
3
4
|
import { dataTable } from "./tables/data";
|
|
4
5
|
import { modelComponentDataStyleTable } from "./tables/model_component_datastyle";
|
|
@@ -15,6 +16,7 @@ export class BaseDatabase extends Dexie {
|
|
|
15
16
|
[modelComponentDataStyleTable.name]: modelComponentDataStyleTable.schema,
|
|
16
17
|
[modelComponentTypeDataStyleTable.name]: modelComponentTypeDataStyleTable.schema,
|
|
17
18
|
[modelComponentsRelationTable.name]: modelComponentsRelationTable.schema,
|
|
19
|
+
[cameraPositionsTable.name]: cameraPositionsTable.schema,
|
|
18
20
|
treeview_config: "id",
|
|
19
21
|
};
|
|
20
22
|
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LONG_ANIMATION_DURATION,
|
|
3
|
+
SHORT_ANIMATION_DURATION,
|
|
4
|
+
animateCamera,
|
|
5
|
+
computeAnimationDuration,
|
|
6
|
+
} from "@ogw_internal/stores/hybrid_viewer_camera_animation";
|
|
1
7
|
import { dot } from "@kitware/vtk.js/Common/Core/Math";
|
|
2
8
|
|
|
3
9
|
const RGB_MAX = 255;
|
|
@@ -17,8 +23,6 @@ const ACTOR_COLOR = [
|
|
|
17
23
|
const WHEEL_TIME_OUT_MS = 600;
|
|
18
24
|
const BUMP_MULTIPLIER = 0.2;
|
|
19
25
|
const ALIGNMENT_THRESHOLD = 0.9;
|
|
20
|
-
const LONG_ANIMATION_DURATION = 1000;
|
|
21
|
-
const SHORT_ANIMATION_DURATION = 500;
|
|
22
26
|
const EASE_EXPONENT = 1.1;
|
|
23
27
|
|
|
24
28
|
const SAMPLE_SIZE = 10;
|
|
@@ -76,17 +80,13 @@ function computeAverageBrightness(rect, options) {
|
|
|
76
80
|
if (!latestImage || !offscreenCtx || !offscreenCanvas || !genericRenderWindow) {
|
|
77
81
|
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
78
82
|
}
|
|
79
|
-
|
|
80
83
|
const canvas = genericRenderWindow.getApiSpecificRenderWindow().getCanvas();
|
|
81
84
|
if (!canvas) {
|
|
82
85
|
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
83
86
|
}
|
|
84
|
-
|
|
85
87
|
const { relX, relY, relW, relH } = mapRect(rect, latestImage, canvas.getBoundingClientRect());
|
|
86
|
-
|
|
87
88
|
offscreenCanvas.width = SAMPLE_SIZE;
|
|
88
89
|
offscreenCanvas.height = SAMPLE_SIZE;
|
|
89
|
-
|
|
90
90
|
try {
|
|
91
91
|
offscreenCtx.drawImage(
|
|
92
92
|
latestImage,
|
|
@@ -100,7 +100,6 @@ function computeAverageBrightness(rect, options) {
|
|
|
100
100
|
SAMPLE_SIZE,
|
|
101
101
|
);
|
|
102
102
|
const { data } = offscreenCtx.getImageData(0, 0, SAMPLE_SIZE, SAMPLE_SIZE);
|
|
103
|
-
|
|
104
103
|
let minBrightness = 1;
|
|
105
104
|
for (let i = 0; i < TOTAL_CHANNELS; i += RGBA_CHANNELS) {
|
|
106
105
|
const brightness = (data[i] + data[i + 1] + data[i + 2]) / (3 * RGB_MAX);
|
|
@@ -108,7 +107,6 @@ function computeAverageBrightness(rect, options) {
|
|
|
108
107
|
minBrightness = brightness;
|
|
109
108
|
}
|
|
110
109
|
}
|
|
111
|
-
|
|
112
110
|
return minBrightness;
|
|
113
111
|
} catch {
|
|
114
112
|
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
@@ -150,54 +148,8 @@ function performClickPicking(event, options) {
|
|
|
150
148
|
},
|
|
151
149
|
);
|
|
152
150
|
}
|
|
153
|
-
|
|
154
|
-
function animateCamera(options) {
|
|
155
|
-
const {
|
|
156
|
-
camera,
|
|
157
|
-
startState,
|
|
158
|
-
targetState,
|
|
159
|
-
duration,
|
|
160
|
-
bumpMultiplier,
|
|
161
|
-
easeExponent,
|
|
162
|
-
onUpdate,
|
|
163
|
-
onEnd,
|
|
164
|
-
} = options;
|
|
165
|
-
const startTime = performance.now();
|
|
166
|
-
|
|
167
|
-
function animate(currentTime) {
|
|
168
|
-
const progress = Math.min((currentTime - startTime) / duration, 1);
|
|
169
|
-
const ease =
|
|
170
|
-
duration > SHORT_ANIMATION_DURATION
|
|
171
|
-
? 1 - (1 - progress) ** easeExponent
|
|
172
|
-
: progress * (2 - progress);
|
|
173
|
-
const bump = bumpMultiplier * Math.sin(Math.PI * progress);
|
|
174
|
-
|
|
175
|
-
camera.set({
|
|
176
|
-
position: startState.position.map(
|
|
177
|
-
(startValue, index) =>
|
|
178
|
-
startValue + (targetState.position[index] - startValue) * ease + bump,
|
|
179
|
-
),
|
|
180
|
-
viewUp: startState.view_up.map(
|
|
181
|
-
(startValue, index) => startValue + (targetState.view_up[index] - startValue) * ease,
|
|
182
|
-
),
|
|
183
|
-
focalPoint: startState.focal_point.map(
|
|
184
|
-
(startValue, index) => startValue + (targetState.focal_point[index] - startValue) * ease,
|
|
185
|
-
),
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
onUpdate();
|
|
189
|
-
|
|
190
|
-
if (progress < 1) {
|
|
191
|
-
requestAnimationFrame(animate);
|
|
192
|
-
} else {
|
|
193
|
-
onEnd();
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
requestAnimationFrame(animate);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
151
|
async function applySnapshot(snapshot, options) {
|
|
200
|
-
const { genericRenderWindow, setZScaling, syncRemoteCamera } = options;
|
|
152
|
+
const { genericRenderWindow, setZScaling, syncRemoteCamera, setCamera } = options;
|
|
201
153
|
if (!snapshot) {
|
|
202
154
|
return;
|
|
203
155
|
}
|
|
@@ -207,17 +159,47 @@ async function applySnapshot(snapshot, options) {
|
|
|
207
159
|
}
|
|
208
160
|
const { camera_options: snapshot_camera_options } = snapshot;
|
|
209
161
|
if (snapshot_camera_options) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
162
|
+
if (setCamera) {
|
|
163
|
+
setCamera(snapshot_camera_options);
|
|
164
|
+
} else {
|
|
165
|
+
applyCameraOptions(
|
|
166
|
+
genericRenderWindow.getRenderer().getActiveCamera(),
|
|
167
|
+
snapshot_camera_options,
|
|
168
|
+
);
|
|
169
|
+
genericRenderWindow.getRenderWindow().render();
|
|
170
|
+
syncRemoteCamera();
|
|
171
|
+
}
|
|
216
172
|
}
|
|
217
173
|
}
|
|
218
174
|
|
|
175
|
+
function performSetCamera(targetCameraOptions, options) {
|
|
176
|
+
const { genericRenderWindow, is_moving, imageStyle, syncRemoteCamera } = options;
|
|
177
|
+
const camera = genericRenderWindow.getRenderer().getActiveCamera();
|
|
178
|
+
const startState = getCameraOptions(camera);
|
|
179
|
+
const duration = computeAnimationDuration(startState, targetCameraOptions);
|
|
180
|
+
is_moving.value = true;
|
|
181
|
+
if (imageStyle) {
|
|
182
|
+
imageStyle.opacity = 0;
|
|
183
|
+
}
|
|
184
|
+
animateCamera({
|
|
185
|
+
camera,
|
|
186
|
+
startState,
|
|
187
|
+
targetState: targetCameraOptions,
|
|
188
|
+
duration,
|
|
189
|
+
bumpMultiplier: 0,
|
|
190
|
+
easeExponent: EASE_EXPONENT,
|
|
191
|
+
onUpdate: () => genericRenderWindow.getRenderWindow().render(),
|
|
192
|
+
onEnd: () => {
|
|
193
|
+
applyCameraOptions(camera, targetCameraOptions);
|
|
194
|
+
genericRenderWindow.getRenderWindow().render();
|
|
195
|
+
is_moving.value = false;
|
|
196
|
+
syncRemoteCamera();
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
219
201
|
function performCameraOrientation(orientation, options) {
|
|
220
|
-
const { genericRenderWindow, is_moving, imageStyle, syncRemoteCamera
|
|
202
|
+
const { genericRenderWindow, is_moving, imageStyle, syncRemoteCamera } = options;
|
|
221
203
|
const config = ORIENTATIONS[orientation.toLowerCase()];
|
|
222
204
|
const renderer = genericRenderWindow.getRenderer();
|
|
223
205
|
const camera = renderer.getActiveCamera();
|
|
@@ -234,9 +216,7 @@ function performCameraOrientation(orientation, options) {
|
|
|
234
216
|
|
|
235
217
|
const alignment = dot(camera.getDirectionOfProjection(), config.position);
|
|
236
218
|
const duration =
|
|
237
|
-
alignment >
|
|
238
|
-
? constants.LONG_ANIMATION_DURATION
|
|
239
|
-
: constants.SHORT_ANIMATION_DURATION;
|
|
219
|
+
alignment > ALIGNMENT_THRESHOLD ? LONG_ANIMATION_DURATION : SHORT_ANIMATION_DURATION;
|
|
240
220
|
is_moving.value = true;
|
|
241
221
|
imageStyle.opacity = 0;
|
|
242
222
|
|
|
@@ -245,8 +225,8 @@ function performCameraOrientation(orientation, options) {
|
|
|
245
225
|
startState,
|
|
246
226
|
targetState,
|
|
247
227
|
duration,
|
|
248
|
-
bumpMultiplier:
|
|
249
|
-
easeExponent:
|
|
228
|
+
bumpMultiplier: BUMP_MULTIPLIER,
|
|
229
|
+
easeExponent: EASE_EXPONENT,
|
|
250
230
|
onUpdate: () => genericRenderWindow.getRenderWindow().render(),
|
|
251
231
|
onEnd: () => {
|
|
252
232
|
is_moving.value = false;
|
|
@@ -261,11 +241,8 @@ export {
|
|
|
261
241
|
WHEEL_TIME_OUT_MS,
|
|
262
242
|
BUMP_MULTIPLIER,
|
|
263
243
|
ALIGNMENT_THRESHOLD,
|
|
264
|
-
LONG_ANIMATION_DURATION,
|
|
265
|
-
SHORT_ANIMATION_DURATION,
|
|
266
244
|
EASE_EXPONENT,
|
|
267
245
|
ORIENTATIONS,
|
|
268
|
-
animateCamera,
|
|
269
246
|
applyCameraOptions,
|
|
270
247
|
applySnapshot,
|
|
271
248
|
centerCameraOnPosition,
|
|
@@ -273,4 +250,5 @@ export {
|
|
|
273
250
|
getCameraOptions,
|
|
274
251
|
performCameraOrientation,
|
|
275
252
|
performClickPicking,
|
|
253
|
+
performSetCamera,
|
|
276
254
|
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { dot } from "@kitware/vtk.js/Common/Core/Math";
|
|
2
|
+
|
|
3
|
+
const NEAR_ZERO_THRESHOLD = 1e-10;
|
|
4
|
+
const SLERP_LINEAR_THRESHOLD = 0.9995;
|
|
5
|
+
const LONG_ANIMATION_DURATION = 1000;
|
|
6
|
+
const SHORT_ANIMATION_DURATION = 500;
|
|
7
|
+
|
|
8
|
+
function vecSub(vector, other) {
|
|
9
|
+
return [vector[0] - other[0], vector[1] - other[1], vector[2] - other[2]];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function vecLength(vector) {
|
|
13
|
+
return Math.hypot(vector[0], vector[1], vector[2]);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function vecNormalize(vector) {
|
|
17
|
+
const len = vecLength(vector);
|
|
18
|
+
if (len < NEAR_ZERO_THRESHOLD) {
|
|
19
|
+
return [0, 0, 1];
|
|
20
|
+
}
|
|
21
|
+
return [vector[0] / len, vector[1] / len, vector[2] / len];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function slerp(from, target, ratio) {
|
|
25
|
+
const normFrom = vecNormalize(from);
|
|
26
|
+
const normTarget = vecNormalize(target);
|
|
27
|
+
let dotProduct =
|
|
28
|
+
normFrom[0] * normTarget[0] + normFrom[1] * normTarget[1] + normFrom[2] * normTarget[2];
|
|
29
|
+
dotProduct = Math.max(-1, Math.min(1, dotProduct));
|
|
30
|
+
if (dotProduct > SLERP_LINEAR_THRESHOLD) {
|
|
31
|
+
return vecNormalize([
|
|
32
|
+
normFrom[0] + (normTarget[0] - normFrom[0]) * ratio,
|
|
33
|
+
normFrom[1] + (normTarget[1] - normFrom[1]) * ratio,
|
|
34
|
+
normFrom[2] + (normTarget[2] - normFrom[2]) * ratio,
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
const theta = Math.acos(dotProduct);
|
|
38
|
+
const sinTheta = Math.sin(theta);
|
|
39
|
+
const weightFrom = Math.sin((1 - ratio) * theta) / sinTheta;
|
|
40
|
+
const weightTarget = Math.sin(ratio * theta) / sinTheta;
|
|
41
|
+
return [
|
|
42
|
+
normFrom[0] * weightFrom + normTarget[0] * weightTarget,
|
|
43
|
+
normFrom[1] * weightFrom + normTarget[1] * weightTarget,
|
|
44
|
+
normFrom[2] * weightFrom + normTarget[2] * weightTarget,
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function computeAnimationDuration(startState, targetState) {
|
|
49
|
+
const startDir = vecNormalize(vecSub(startState.position, startState.focal_point));
|
|
50
|
+
const targetDir = vecNormalize(vecSub(targetState.position, targetState.focal_point));
|
|
51
|
+
const dotProduct = Math.max(-1, Math.min(1, dot(startDir, targetDir)));
|
|
52
|
+
const angle = Math.acos(dotProduct);
|
|
53
|
+
const angleRatio = angle / Math.PI;
|
|
54
|
+
return (
|
|
55
|
+
SHORT_ANIMATION_DURATION + (LONG_ANIMATION_DURATION - SHORT_ANIMATION_DURATION) * angleRatio
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function animateCamera(options) {
|
|
60
|
+
const {
|
|
61
|
+
camera,
|
|
62
|
+
startState,
|
|
63
|
+
targetState,
|
|
64
|
+
duration,
|
|
65
|
+
bumpMultiplier,
|
|
66
|
+
easeExponent,
|
|
67
|
+
onUpdate,
|
|
68
|
+
onEnd,
|
|
69
|
+
} = options;
|
|
70
|
+
const startDir = vecSub(startState.position, startState.focal_point);
|
|
71
|
+
const targetDir = vecSub(targetState.position, targetState.focal_point);
|
|
72
|
+
const startDist = vecLength(startDir);
|
|
73
|
+
const targetDist = vecLength(targetDir);
|
|
74
|
+
const startTime = performance.now();
|
|
75
|
+
function animate(currentTime) {
|
|
76
|
+
const progress = Math.min((currentTime - startTime) / duration, 1);
|
|
77
|
+
const ease =
|
|
78
|
+
duration > SHORT_ANIMATION_DURATION
|
|
79
|
+
? 1 - (1 - progress) ** easeExponent
|
|
80
|
+
: progress * (2 - progress);
|
|
81
|
+
const bump = bumpMultiplier * Math.sin(Math.PI * progress);
|
|
82
|
+
const dir = slerp(startDir, targetDir, ease);
|
|
83
|
+
const dist = startDist + (targetDist - startDist) * ease + bump;
|
|
84
|
+
const focalPoint = startState.focal_point.map(
|
|
85
|
+
(startValue, index) => startValue + (targetState.focal_point[index] - startValue) * ease,
|
|
86
|
+
);
|
|
87
|
+
const viewUp = slerp(startState.view_up, targetState.view_up, ease);
|
|
88
|
+
camera.set({
|
|
89
|
+
position: focalPoint.map((focalCoord, index) => focalCoord + dir[index] * dist),
|
|
90
|
+
viewUp,
|
|
91
|
+
focalPoint,
|
|
92
|
+
});
|
|
93
|
+
onUpdate();
|
|
94
|
+
if (progress < 1) {
|
|
95
|
+
requestAnimationFrame(animate);
|
|
96
|
+
} else {
|
|
97
|
+
onEnd();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
requestAnimationFrame(animate);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
LONG_ANIMATION_DURATION,
|
|
105
|
+
SHORT_ANIMATION_DURATION,
|
|
106
|
+
animateCamera,
|
|
107
|
+
computeAnimationDuration,
|
|
108
|
+
};
|
package/package.json
CHANGED
|
@@ -36,7 +36,7 @@ export default defineEventHandler(async (event) => {
|
|
|
36
36
|
console.log("Service URL created:", response.uri);
|
|
37
37
|
return {
|
|
38
38
|
statusCode: 200,
|
|
39
|
-
url: response.uri.replace(/^https?:\/\//
|
|
39
|
+
url: response.uri.replace(/^https?:\/\//iu, ""),
|
|
40
40
|
};
|
|
41
41
|
} catch (error) {
|
|
42
42
|
console.log(error);
|