@geode/opengeodeweb-front 10.14.2-rc.3 → 10.15.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/components/CameraOrientation.vue +273 -0
- package/app/components/HybridRenderingView.vue +2 -2
- package/app/components/Screenshot.vue +38 -57
- package/app/components/ToolPanel.vue +62 -0
- package/app/components/ViewToolbar.vue +50 -4
- package/app/components/ZScaling.vue +36 -54
- package/app/stores/hybrid_viewer.js +70 -52
- package/app/utils/hybrid_viewer.js +76 -28
- package/app/utils/vtk/constants.js +31 -0
- package/package.json +3 -3
- package/app/components/VeaseViewToolbar.vue +0 -95
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import ToolPanel from "@ogw_front/components/ToolPanel";
|
|
3
|
+
import { applyCameraOptions } from "@ogw_front/utils/hybrid_viewer";
|
|
4
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
5
|
+
import { newInstance as vtkAnnotatedCubeActor } from "@kitware/vtk.js/Rendering/Core/AnnotatedCubeActor";
|
|
6
|
+
import { newInstance as vtkGenericRenderWindow } from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow";
|
|
7
|
+
|
|
8
|
+
const { panel, width } = defineProps({
|
|
9
|
+
panel: { type: Boolean, default: false },
|
|
10
|
+
width: { type: Number, default: 400 },
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const show = defineModel("show", { type: Boolean, default: false });
|
|
14
|
+
const emit = defineEmits(["select"]);
|
|
15
|
+
|
|
16
|
+
const orientations = [
|
|
17
|
+
{
|
|
18
|
+
label: "Z+",
|
|
19
|
+
value: "zplus",
|
|
20
|
+
face: "top",
|
|
21
|
+
vtkKey: "ZPlus",
|
|
22
|
+
rotation: 0,
|
|
23
|
+
position: { top: "15%", left: "50%" },
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: "Z-",
|
|
27
|
+
value: "zminus",
|
|
28
|
+
face: "bottom",
|
|
29
|
+
vtkKey: "ZMinus",
|
|
30
|
+
rotation: 0,
|
|
31
|
+
position: { top: "85%", left: "50%" },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: "Y+",
|
|
35
|
+
value: "yplus",
|
|
36
|
+
face: "front",
|
|
37
|
+
vtkKey: "YPlus",
|
|
38
|
+
rotation: 180,
|
|
39
|
+
position: { top: "35%", left: "20%" },
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: "Y-",
|
|
43
|
+
value: "yminus",
|
|
44
|
+
face: "back",
|
|
45
|
+
vtkKey: "YMinus",
|
|
46
|
+
rotation: 0,
|
|
47
|
+
position: { top: "65%", left: "80%" },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: "X+",
|
|
51
|
+
value: "xplus",
|
|
52
|
+
face: "right",
|
|
53
|
+
vtkKey: "XPlus",
|
|
54
|
+
rotation: 90,
|
|
55
|
+
position: { top: "35%", left: "80%" },
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
label: "X-",
|
|
59
|
+
value: "xminus",
|
|
60
|
+
face: "left",
|
|
61
|
+
vtkKey: "XMinus",
|
|
62
|
+
rotation: -90,
|
|
63
|
+
position: { top: "65%", left: "20%" },
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const hoveredFace = ref(undefined);
|
|
68
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
69
|
+
const cubeContainer = useTemplateRef("cubeContainer");
|
|
70
|
+
|
|
71
|
+
let genericRenderWindow = undefined;
|
|
72
|
+
let cubeActor = undefined;
|
|
73
|
+
let isInteracting = false;
|
|
74
|
+
|
|
75
|
+
function initVTK() {
|
|
76
|
+
if (genericRenderWindow) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
genericRenderWindow = vtkGenericRenderWindow({
|
|
80
|
+
background: [0, 0, 0, 0],
|
|
81
|
+
listenWindowResize: false,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const interactor = genericRenderWindow.getInteractor();
|
|
85
|
+
interactor.onStartAnimation(() => {
|
|
86
|
+
isInteracting = true;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
interactor.onEndAnimation(() => {
|
|
90
|
+
isInteracting = false;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
cubeActor = vtkAnnotatedCubeActor();
|
|
94
|
+
cubeActor.setDefaultStyle({
|
|
95
|
+
fontFamily: "sans-serif",
|
|
96
|
+
fontStyle: "bold",
|
|
97
|
+
faceColor: "rgba(60, 60, 60, 1)",
|
|
98
|
+
fontColor: "white",
|
|
99
|
+
edgeColor: "rgba(255, 255, 255, 0.4)",
|
|
100
|
+
edgeThickness: 0.1,
|
|
101
|
+
resolution: 400,
|
|
102
|
+
fontSizeScale: (resolution) => resolution / 4,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
for (const orientation of orientations) {
|
|
106
|
+
cubeActor[`set${orientation.vtkKey}FaceProperty`]({
|
|
107
|
+
text: orientation.label,
|
|
108
|
+
faceRotation: orientation.rotation,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
cubeActor.getProperty().setBackfaceCulling(true);
|
|
113
|
+
|
|
114
|
+
const renderer = genericRenderWindow.getRenderer();
|
|
115
|
+
|
|
116
|
+
renderer.addActor(cubeActor);
|
|
117
|
+
renderer.resetCamera();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function syncCubeCamera() {
|
|
121
|
+
const options = hybridViewerStore.camera_options;
|
|
122
|
+
if (!genericRenderWindow || isInteracting || !options.position) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const camera = genericRenderWindow.getRenderer().getActiveCamera();
|
|
126
|
+
applyCameraOptions(camera, options);
|
|
127
|
+
genericRenderWindow.getRenderer().resetCamera();
|
|
128
|
+
genericRenderWindow.getRenderWindow().render();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
watch(cubeContainer, (newContainer) => {
|
|
132
|
+
if (!newContainer || !import.meta.client) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
initVTK();
|
|
136
|
+
genericRenderWindow.setContainer(newContainer);
|
|
137
|
+
const canvas = genericRenderWindow.getApiSpecificRenderWindow().getCanvas();
|
|
138
|
+
canvas.style.width = "100%";
|
|
139
|
+
canvas.style.height = "100%";
|
|
140
|
+
canvas.style.background = "transparent";
|
|
141
|
+
genericRenderWindow.resize();
|
|
142
|
+
syncCubeCamera();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
onBeforeUnmount(() => genericRenderWindow?.delete());
|
|
146
|
+
|
|
147
|
+
watch(() => hybridViewerStore.camera_options, syncCubeCamera, { deep: true });
|
|
148
|
+
|
|
149
|
+
watch(hoveredFace, (newFace, oldFace) => {
|
|
150
|
+
if (!cubeActor) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
function updateFace(face, active) {
|
|
154
|
+
const config = orientations.find((orientation) => orientation.face === face);
|
|
155
|
+
if (config) {
|
|
156
|
+
cubeActor[`set${config.vtkKey}FaceProperty`]({
|
|
157
|
+
faceColor: active ? "rgba(255, 255, 255, 0.95)" : "rgba(60, 60, 60, 1)",
|
|
158
|
+
fontColor: active ? "black" : "white",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
updateFace(oldFace, false);
|
|
164
|
+
updateFace(newFace, true);
|
|
165
|
+
genericRenderWindow.getRenderWindow().render();
|
|
166
|
+
});
|
|
167
|
+
</script>
|
|
168
|
+
|
|
169
|
+
<template>
|
|
170
|
+
<ToolPanel
|
|
171
|
+
v-if="panel"
|
|
172
|
+
v-model="show"
|
|
173
|
+
title="Camera Orientations"
|
|
174
|
+
:width="width"
|
|
175
|
+
style="z-index: 2; top: 90px; right: 55px"
|
|
176
|
+
>
|
|
177
|
+
<div
|
|
178
|
+
class="pa-0 overflow-hidden position-relative"
|
|
179
|
+
style="
|
|
180
|
+
height: 320px;
|
|
181
|
+
background: radial-gradient(circle at center, rgba(255, 255, 255, 0.05), transparent 70%);
|
|
182
|
+
"
|
|
183
|
+
>
|
|
184
|
+
<svg class="position-absolute fill-height w-100" style="pointer-events: none">
|
|
185
|
+
<line
|
|
186
|
+
v-for="orientation in orientations"
|
|
187
|
+
:key="orientation.value"
|
|
188
|
+
x1="50%"
|
|
189
|
+
y1="50%"
|
|
190
|
+
:x2="orientation.position.left"
|
|
191
|
+
:y2="orientation.position.top"
|
|
192
|
+
:stroke="hoveredFace === orientation.face ? 'white' : 'rgba(255,255,255,0.1)'"
|
|
193
|
+
:stroke-width="hoveredFace === orientation.face ? 2 : 1"
|
|
194
|
+
class="transition-all"
|
|
195
|
+
style="filter: drop-shadow(0 0 3px white)"
|
|
196
|
+
/>
|
|
197
|
+
</svg>
|
|
198
|
+
|
|
199
|
+
<div
|
|
200
|
+
class="position-absolute d-flex align-center justify-center"
|
|
201
|
+
style="top: 50%; left: 50%; transform: translate(-50%, -50%)"
|
|
202
|
+
>
|
|
203
|
+
<div ref="cubeContainer" style="width: 100px; height: 100px; pointer-events: none" />
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<v-btn
|
|
207
|
+
v-for="orientation in orientations"
|
|
208
|
+
:key="orientation.value"
|
|
209
|
+
icon
|
|
210
|
+
variant="tonal"
|
|
211
|
+
size="44"
|
|
212
|
+
class="satellite-node position-absolute"
|
|
213
|
+
:style="orientation.position"
|
|
214
|
+
@mouseenter="hoveredFace = orientation.face"
|
|
215
|
+
@mouseleave="hoveredFace = undefined"
|
|
216
|
+
@click.stop="emit('select', orientation.value)"
|
|
217
|
+
>
|
|
218
|
+
<v-tooltip activator="parent" location="top">{{ orientation.value }} View</v-tooltip>
|
|
219
|
+
<span class="text-caption font-weight-black">{{ orientation.label }}</span>
|
|
220
|
+
</v-btn>
|
|
221
|
+
</div>
|
|
222
|
+
</ToolPanel>
|
|
223
|
+
|
|
224
|
+
<v-list v-else density="compact" class="pa-4 orientation-menu rounded-lg" elevation="8">
|
|
225
|
+
<div class="d-flex flex-column align-center" style="gap: 16px">
|
|
226
|
+
<div
|
|
227
|
+
class="d-flex align-center justify-center"
|
|
228
|
+
style="width: 60px; height: 60px; border-radius: 8px; overflow: hidden"
|
|
229
|
+
>
|
|
230
|
+
<div ref="cubeContainer" class="w-100 h-100" style="pointer-events: none" />
|
|
231
|
+
</div>
|
|
232
|
+
<v-divider class="w-100" />
|
|
233
|
+
<div class="d-flex flex-wrap justify-center" style="max-width: 140px">
|
|
234
|
+
<v-btn
|
|
235
|
+
v-for="orientation in orientations"
|
|
236
|
+
:key="orientation.value"
|
|
237
|
+
icon
|
|
238
|
+
size="32"
|
|
239
|
+
variant="text"
|
|
240
|
+
class="ma-1"
|
|
241
|
+
@mouseenter="hoveredFace = orientation.face"
|
|
242
|
+
@mouseleave="hoveredFace = undefined"
|
|
243
|
+
@click.stop="emit('select', orientation.value)"
|
|
244
|
+
>
|
|
245
|
+
<v-tooltip activator="parent" location="top">{{ orientation.label }}</v-tooltip>
|
|
246
|
+
<span class="text-caption font-weight-black">{{ orientation.label }}</span>
|
|
247
|
+
</v-btn>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</v-list>
|
|
251
|
+
</template>
|
|
252
|
+
|
|
253
|
+
<style scoped>
|
|
254
|
+
.satellite-node {
|
|
255
|
+
transform: translate(-50%, -50%);
|
|
256
|
+
background: rgba(0, 0, 0, 0.2) !important;
|
|
257
|
+
backdrop-filter: blur(8px);
|
|
258
|
+
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
259
|
+
transition: all 0.2s ease;
|
|
260
|
+
}
|
|
261
|
+
.satellite-node:hover {
|
|
262
|
+
transform: translate(-50%, -50%) scale(1.1);
|
|
263
|
+
border-color: white !important;
|
|
264
|
+
background: rgba(255, 255, 255, 0.1) !important;
|
|
265
|
+
}
|
|
266
|
+
.transition-all {
|
|
267
|
+
transition: all 0.4s ease;
|
|
268
|
+
}
|
|
269
|
+
.orientation-menu {
|
|
270
|
+
min-width: 180px;
|
|
271
|
+
background: rgb(var(--v-theme-surface)) !important;
|
|
272
|
+
}
|
|
273
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import
|
|
2
|
+
import ViewToolbar from "@ogw_front/components/ViewToolbar";
|
|
3
3
|
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
4
4
|
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
5
5
|
|
|
@@ -47,7 +47,7 @@ function debounce(func, wait) {
|
|
|
47
47
|
<template>
|
|
48
48
|
<ClientOnly>
|
|
49
49
|
<div data-testid="hybridViewer" class="fill-height" style="position: relative; height: 100%">
|
|
50
|
-
<
|
|
50
|
+
<ViewToolbar />
|
|
51
51
|
<slot name="ui"></slot>
|
|
52
52
|
<v-col
|
|
53
53
|
class="pa-0"
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import
|
|
2
|
+
import ToolPanel from "@ogw_front/components/ToolPanel";
|
|
3
3
|
import fileDownload from "js-file-download";
|
|
4
|
-
import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
5
|
-
|
|
6
4
|
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
5
|
+
import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
7
6
|
|
|
8
|
-
const
|
|
7
|
+
const show = defineModel({ type: Boolean, default: false });
|
|
9
8
|
|
|
10
|
-
const {
|
|
11
|
-
|
|
12
|
-
width: { type: Number, required: false, default: 400 },
|
|
9
|
+
const { width } = defineProps({
|
|
10
|
+
width: { type: Number, default: 400 },
|
|
13
11
|
});
|
|
14
12
|
|
|
15
13
|
const output_extensions =
|
|
@@ -33,7 +31,7 @@ async function takeScreenshot() {
|
|
|
33
31
|
},
|
|
34
32
|
},
|
|
35
33
|
);
|
|
36
|
-
|
|
34
|
+
show.value = false;
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
watch(output_extension, (value) => {
|
|
@@ -42,58 +40,41 @@ watch(output_extension, (value) => {
|
|
|
42
40
|
}
|
|
43
41
|
});
|
|
44
42
|
</script>
|
|
43
|
+
|
|
45
44
|
<template>
|
|
46
|
-
<
|
|
47
|
-
v-
|
|
48
|
-
@click.stop
|
|
45
|
+
<ToolPanel
|
|
46
|
+
v-model="show"
|
|
49
47
|
title="Take a screenshot"
|
|
50
48
|
:width="width"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class="position-absolute elevation-24"
|
|
55
|
-
style="z-index: 2; top: 90px; right: 55px"
|
|
49
|
+
close-label="Cancel"
|
|
50
|
+
action-label="Screenshot"
|
|
51
|
+
@action="takeScreenshot"
|
|
56
52
|
>
|
|
57
|
-
<v-
|
|
58
|
-
<v-
|
|
59
|
-
<v-
|
|
60
|
-
<v-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<v-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
</v-row>
|
|
72
|
-
|
|
73
|
-
<v-row>
|
|
74
|
-
<v-col cols="12" class="py-0">
|
|
75
|
-
<v-switch
|
|
76
|
-
v-model="include_background"
|
|
77
|
-
:disabled="output_extension !== 'png'"
|
|
78
|
-
label="Include background"
|
|
79
|
-
inset
|
|
80
|
-
></v-switch>
|
|
81
|
-
</v-col>
|
|
82
|
-
</v-row>
|
|
83
|
-
</v-container>
|
|
84
|
-
</v-card-text>
|
|
53
|
+
<v-container class="pa-5">
|
|
54
|
+
<v-row>
|
|
55
|
+
<v-col cols="8" class="py-0">
|
|
56
|
+
<v-text-field v-model="filename" label="File name"></v-text-field>
|
|
57
|
+
</v-col>
|
|
58
|
+
<v-col cols="4" class="py-0">
|
|
59
|
+
<v-select
|
|
60
|
+
v-model="output_extension"
|
|
61
|
+
:items="output_extensions"
|
|
62
|
+
label="Extension"
|
|
63
|
+
required
|
|
64
|
+
/>
|
|
65
|
+
</v-col>
|
|
66
|
+
</v-row>
|
|
85
67
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
</GlassCard>
|
|
68
|
+
<v-row>
|
|
69
|
+
<v-col cols="12" class="py-0">
|
|
70
|
+
<v-switch
|
|
71
|
+
v-model="include_background"
|
|
72
|
+
:disabled="output_extension !== 'png'"
|
|
73
|
+
label="Include background"
|
|
74
|
+
inset
|
|
75
|
+
></v-switch>
|
|
76
|
+
</v-col>
|
|
77
|
+
</v-row>
|
|
78
|
+
</v-container>
|
|
79
|
+
</ToolPanel>
|
|
99
80
|
</template>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import GlassCard from "@ogw_front/components/GlassCard";
|
|
3
|
+
|
|
4
|
+
const { title, width, closeLabel, actionLabel } = defineProps({
|
|
5
|
+
title: { type: String, default: "" },
|
|
6
|
+
width: { type: Number, default: 400 },
|
|
7
|
+
closeLabel: { type: String, default: "Close" },
|
|
8
|
+
actionLabel: { type: String, default: undefined },
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const model = defineModel({ type: Boolean, default: false });
|
|
12
|
+
const emit = defineEmits(["action"]);
|
|
13
|
+
|
|
14
|
+
function close() {
|
|
15
|
+
model.value = false;
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<GlassCard
|
|
21
|
+
v-if="model"
|
|
22
|
+
v-click-outside="close"
|
|
23
|
+
:title="title"
|
|
24
|
+
:width="width"
|
|
25
|
+
:ripple="false"
|
|
26
|
+
variant="panel"
|
|
27
|
+
padding="pa-0"
|
|
28
|
+
class="position-absolute rounded-xl elevation-24 tool-panel"
|
|
29
|
+
v-bind="$attrs"
|
|
30
|
+
>
|
|
31
|
+
<v-card-text class="pa-0 overflow-hidden position-relative">
|
|
32
|
+
<slot />
|
|
33
|
+
</v-card-text>
|
|
34
|
+
|
|
35
|
+
<template #actions>
|
|
36
|
+
<slot name="actions">
|
|
37
|
+
<v-card-actions class="justify-center pb-6" style="gap: 12px">
|
|
38
|
+
<v-btn variant="text" size="small" color="white" @click="close">
|
|
39
|
+
{{ closeLabel }}
|
|
40
|
+
</v-btn>
|
|
41
|
+
<v-btn
|
|
42
|
+
v-if="actionLabel"
|
|
43
|
+
variant="outlined"
|
|
44
|
+
size="small"
|
|
45
|
+
color="white"
|
|
46
|
+
@click="emit('action')"
|
|
47
|
+
>
|
|
48
|
+
{{ actionLabel }}
|
|
49
|
+
</v-btn>
|
|
50
|
+
</v-card-actions>
|
|
51
|
+
</slot>
|
|
52
|
+
</template>
|
|
53
|
+
</GlassCard>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<style scoped>
|
|
57
|
+
.tool-panel {
|
|
58
|
+
z-index: 2;
|
|
59
|
+
top: 90px;
|
|
60
|
+
right: 55px;
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
@@ -2,19 +2,46 @@
|
|
|
2
2
|
import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
3
3
|
|
|
4
4
|
import ActionButton from "@ogw_front/components/ActionButton.vue";
|
|
5
|
+
import CameraOrientation from "@ogw_front/components/CameraOrientation.vue";
|
|
5
6
|
import Screenshot from "@ogw_front/components/Screenshot";
|
|
7
|
+
import ZScaling from "@ogw_front/components/ZScaling";
|
|
8
|
+
|
|
9
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
6
10
|
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
7
11
|
|
|
12
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
8
13
|
const viewerStore = useViewerStore();
|
|
9
14
|
const take_screenshot = ref(false);
|
|
15
|
+
const showCameraOrientation = ref(false);
|
|
16
|
+
const showZScaling = ref(false);
|
|
10
17
|
const grid_scale = ref(false);
|
|
18
|
+
const zScale = ref(hybridViewerStore.zScale);
|
|
19
|
+
|
|
20
|
+
watch(
|
|
21
|
+
() => hybridViewerStore.zScale,
|
|
22
|
+
(newVal) => {
|
|
23
|
+
zScale.value = newVal;
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
async function handleZScalingClose() {
|
|
28
|
+
await hybridViewerStore.setZScaling(zScale.value);
|
|
29
|
+
showZScaling.value = false;
|
|
30
|
+
}
|
|
11
31
|
|
|
12
32
|
const camera_options = [
|
|
13
33
|
{
|
|
14
34
|
tooltip: "Reset camera",
|
|
15
35
|
icon: "mdi-cube-scan",
|
|
16
36
|
action: () => {
|
|
17
|
-
|
|
37
|
+
hybridViewerStore.resetCamera();
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
tooltip: "Camera orientation",
|
|
42
|
+
icon: "mdi-rotate-3d",
|
|
43
|
+
action: () => {
|
|
44
|
+
showCameraOrientation.value = !showCameraOrientation.value;
|
|
18
45
|
},
|
|
19
46
|
},
|
|
20
47
|
{
|
|
@@ -34,11 +61,19 @@ const camera_options = [
|
|
|
34
61
|
{
|
|
35
62
|
response_function: () => {
|
|
36
63
|
grid_scale.value = !grid_scale.value;
|
|
64
|
+
hybridViewerStore.remoteRender();
|
|
37
65
|
},
|
|
38
66
|
},
|
|
39
67
|
);
|
|
40
68
|
},
|
|
41
69
|
},
|
|
70
|
+
{
|
|
71
|
+
tooltip: "Z Scaling Control",
|
|
72
|
+
icon: "mdi-sort",
|
|
73
|
+
action: () => {
|
|
74
|
+
showZScaling.value = !showZScaling.value;
|
|
75
|
+
},
|
|
76
|
+
},
|
|
42
77
|
];
|
|
43
78
|
</script>
|
|
44
79
|
|
|
@@ -47,15 +82,26 @@ const camera_options = [
|
|
|
47
82
|
<v-row v-for="camera_option in camera_options" :key="camera_option.icon" dense>
|
|
48
83
|
<v-col>
|
|
49
84
|
<ActionButton
|
|
50
|
-
:tooltip="camera_option.tooltip"
|
|
51
85
|
:icon="camera_option.icon"
|
|
86
|
+
:tooltip="camera_option.tooltip"
|
|
52
87
|
tooltip-location="left"
|
|
53
|
-
@click="camera_option.action"
|
|
88
|
+
@click.stop="camera_option.action"
|
|
54
89
|
/>
|
|
55
90
|
</v-col>
|
|
56
91
|
</v-row>
|
|
57
92
|
</v-container>
|
|
58
|
-
<
|
|
93
|
+
<CameraOrientation
|
|
94
|
+
v-model:show="showCameraOrientation"
|
|
95
|
+
panel
|
|
96
|
+
@select="hybridViewerStore.setCameraOrientation"
|
|
97
|
+
/>
|
|
98
|
+
<Screenshot v-model="take_screenshot" />
|
|
99
|
+
<ZScaling
|
|
100
|
+
v-model:show="showZScaling"
|
|
101
|
+
v-model="zScale"
|
|
102
|
+
:width="400"
|
|
103
|
+
@apply="handleZScalingClose"
|
|
104
|
+
/>
|
|
59
105
|
</template>
|
|
60
106
|
|
|
61
107
|
<style module>
|
|
@@ -1,67 +1,49 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import
|
|
2
|
+
import ToolPanel from "@ogw_front/components/ToolPanel";
|
|
3
3
|
|
|
4
4
|
const zScale = defineModel({ type: Number, default: 1 });
|
|
5
|
+
const show = defineModel("show", { type: Boolean, default: false });
|
|
6
|
+
|
|
5
7
|
const { width } = defineProps({
|
|
6
8
|
width: { type: Number, default: 400 },
|
|
7
9
|
});
|
|
8
10
|
|
|
9
|
-
const emit = defineEmits(["
|
|
11
|
+
const emit = defineEmits(["apply"]);
|
|
12
|
+
|
|
13
|
+
function apply() {
|
|
14
|
+
emit("apply");
|
|
15
|
+
show.value = false;
|
|
16
|
+
}
|
|
10
17
|
</script>
|
|
18
|
+
|
|
11
19
|
<template>
|
|
12
|
-
<
|
|
13
|
-
|
|
20
|
+
<ToolPanel
|
|
21
|
+
v-model="show"
|
|
14
22
|
title="Z Scaling Control"
|
|
15
23
|
:width="width"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
padding="pa-0"
|
|
19
|
-
class="position-absolute rounded-xl elevation-24"
|
|
20
|
-
style="z-index: 2; top: 90px; right: 55px"
|
|
24
|
+
action-label="Apply"
|
|
25
|
+
@action="apply"
|
|
21
26
|
>
|
|
22
|
-
<v-
|
|
23
|
-
<v-
|
|
24
|
-
<v-
|
|
25
|
-
<v-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<v-
|
|
30
|
-
<v-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</v-card-text>
|
|
45
|
-
|
|
46
|
-
<template #actions>
|
|
47
|
-
<v-card-actions class="justify-center pb-4">
|
|
48
|
-
<v-btn variant="text" color="white" @click="emit('close')">Close</v-btn>
|
|
49
|
-
<v-btn variant="outlined" color="white" @click="emit('close')">Apply</v-btn>
|
|
50
|
-
</v-card-actions>
|
|
51
|
-
</template>
|
|
52
|
-
</GlassCard>
|
|
27
|
+
<v-container class="pa-5">
|
|
28
|
+
<v-row>
|
|
29
|
+
<v-col cols="12" class="py-2">
|
|
30
|
+
<v-slider v-model="zScale" :min="1" :max="10" :step="0.2" label="Z Scale" thumb-label />
|
|
31
|
+
</v-col>
|
|
32
|
+
</v-row>
|
|
33
|
+
<v-row>
|
|
34
|
+
<v-col cols="12" class="py-2">
|
|
35
|
+
<v-text-field
|
|
36
|
+
v-model.number="zScale"
|
|
37
|
+
type="number"
|
|
38
|
+
label="Z Scale Value"
|
|
39
|
+
outlined
|
|
40
|
+
dense
|
|
41
|
+
hide-details
|
|
42
|
+
step="0.1"
|
|
43
|
+
:min="1"
|
|
44
|
+
/>
|
|
45
|
+
</v-col>
|
|
46
|
+
</v-row>
|
|
47
|
+
</v-container>
|
|
48
|
+
</ToolPanel>
|
|
53
49
|
</template>
|
|
54
|
-
|
|
55
|
-
<style scoped>
|
|
56
|
-
.z-scaling-menu {
|
|
57
|
-
position: absolute;
|
|
58
|
-
z-index: 2;
|
|
59
|
-
top: 90px;
|
|
60
|
-
right: 55px;
|
|
61
|
-
border-radius: 12px !important;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.custom-number-input :deep(.v-input__control) {
|
|
65
|
-
min-height: 48px;
|
|
66
|
-
}
|
|
67
|
-
</style>
|
|
@@ -1,34 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ORIENTATIONS,
|
|
3
|
+
animateCamera,
|
|
2
4
|
applyCameraOptions,
|
|
3
5
|
computeAverageBrightness,
|
|
4
6
|
getCameraOptions,
|
|
5
7
|
} from "@ogw_front/utils/hybrid_viewer";
|
|
8
|
+
import { dot } from "@kitware/vtk.js/Common/Core/Math";
|
|
6
9
|
import { newInstance as vtkActor } from "@kitware/vtk.js/Rendering/Core/Actor";
|
|
7
10
|
import { newInstance as vtkGenericRenderWindow } from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow";
|
|
8
11
|
import { newInstance as vtkMapper } from "@kitware/vtk.js/Rendering/Core/Mapper";
|
|
9
12
|
import { newInstance as vtkXMLPolyDataReader } from "@kitware/vtk.js/IO/XML/XMLPolyDataReader";
|
|
10
13
|
|
|
14
|
+
import {
|
|
15
|
+
ACTOR_COLOR,
|
|
16
|
+
ALIGNMENT_THRESHOLD,
|
|
17
|
+
BACKGROUND_COLOR,
|
|
18
|
+
BUMP_MULTIPLIER,
|
|
19
|
+
EASE_EXPONENT,
|
|
20
|
+
LONG_ANIMATION_DURATION,
|
|
21
|
+
SHORT_ANIMATION_DURATION,
|
|
22
|
+
WHEEL_TIME_OUT_MS,
|
|
23
|
+
} from "@ogw_front/utils/vtk/constants";
|
|
24
|
+
import { Status } from "@ogw_front/utils/status";
|
|
11
25
|
import { useDataStore } from "@ogw_front/stores/data";
|
|
12
26
|
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
13
27
|
|
|
14
|
-
import { Status } from "@ogw_front/utils/status";
|
|
15
28
|
import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
16
29
|
|
|
17
|
-
const RGB_MAX = 255;
|
|
18
|
-
const BACKGROUND_GREY_VALUE = 180;
|
|
19
|
-
const ACTOR_DARK_VALUE = 20;
|
|
20
|
-
const BACKGROUND_COLOR = [
|
|
21
|
-
BACKGROUND_GREY_VALUE / RGB_MAX,
|
|
22
|
-
BACKGROUND_GREY_VALUE / RGB_MAX,
|
|
23
|
-
BACKGROUND_GREY_VALUE / RGB_MAX,
|
|
24
|
-
];
|
|
25
|
-
const ACTOR_COLOR = [
|
|
26
|
-
ACTOR_DARK_VALUE / RGB_MAX,
|
|
27
|
-
ACTOR_DARK_VALUE / RGB_MAX,
|
|
28
|
-
ACTOR_DARK_VALUE / RGB_MAX,
|
|
29
|
-
];
|
|
30
|
-
const WHEEL_TIME_OUT_MS = 600;
|
|
31
|
-
|
|
32
30
|
export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
33
31
|
const dataStore = useDataStore();
|
|
34
32
|
const viewerStore = useViewerStore();
|
|
@@ -39,6 +37,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
39
37
|
const is_moving = ref(false);
|
|
40
38
|
const zScale = ref(1);
|
|
41
39
|
let viewStream = undefined;
|
|
40
|
+
let imageStyle = undefined;
|
|
42
41
|
const gridActor = undefined;
|
|
43
42
|
|
|
44
43
|
const latestImage = ref(undefined);
|
|
@@ -59,7 +58,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
59
58
|
});
|
|
60
59
|
|
|
61
60
|
const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow();
|
|
62
|
-
|
|
61
|
+
imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style;
|
|
63
62
|
imageStyle.transition = "opacity 0.1s ease-in";
|
|
64
63
|
imageStyle.zIndex = 1;
|
|
65
64
|
|
|
@@ -74,6 +73,12 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
74
73
|
imageStyle.opacity = 1;
|
|
75
74
|
});
|
|
76
75
|
|
|
76
|
+
const renderer = genericRenderWindow.value.getRenderer();
|
|
77
|
+
const camera = renderer.getActiveCamera();
|
|
78
|
+
camera.onModified(() => {
|
|
79
|
+
Object.assign(camera_options, getCameraOptions(camera));
|
|
80
|
+
});
|
|
81
|
+
|
|
77
82
|
status.value = Status.CREATED;
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -147,20 +152,50 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
147
152
|
syncRemoteCamera();
|
|
148
153
|
}
|
|
149
154
|
|
|
155
|
+
function setCameraOrientation(orientation) {
|
|
156
|
+
const config = ORIENTATIONS[orientation.toLowerCase()];
|
|
157
|
+
const renderer = genericRenderWindow.value.getRenderer();
|
|
158
|
+
const camera = renderer.getActiveCamera();
|
|
159
|
+
const startState = getCameraOptions(camera);
|
|
160
|
+
|
|
161
|
+
applyCameraOptions(camera, {
|
|
162
|
+
...config,
|
|
163
|
+
focal_point: [0, 0, 0],
|
|
164
|
+
});
|
|
165
|
+
renderer.resetCamera();
|
|
166
|
+
const targetState = getCameraOptions(camera);
|
|
167
|
+
|
|
168
|
+
applyCameraOptions(camera, startState);
|
|
169
|
+
|
|
170
|
+
const alignment = dot(camera.getDirectionOfProjection(), config.position);
|
|
171
|
+
const duration =
|
|
172
|
+
alignment > ALIGNMENT_THRESHOLD ? LONG_ANIMATION_DURATION : SHORT_ANIMATION_DURATION;
|
|
173
|
+
is_moving.value = true;
|
|
174
|
+
imageStyle.opacity = 0;
|
|
175
|
+
|
|
176
|
+
animateCamera({
|
|
177
|
+
camera,
|
|
178
|
+
startState,
|
|
179
|
+
targetState,
|
|
180
|
+
duration,
|
|
181
|
+
bumpMultiplier: BUMP_MULTIPLIER,
|
|
182
|
+
easeExponent: EASE_EXPONENT,
|
|
183
|
+
onUpdate: () => genericRenderWindow.value.getRenderWindow().render(),
|
|
184
|
+
onEnd: () => {
|
|
185
|
+
is_moving.value = false;
|
|
186
|
+
syncRemoteCamera();
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
150
191
|
function syncRemoteCamera() {
|
|
151
192
|
const renderer = genericRenderWindow.value.getRenderer();
|
|
152
193
|
const camera = renderer.getActiveCamera();
|
|
153
|
-
const params = {
|
|
154
|
-
camera_options: getCameraOptions(camera),
|
|
155
|
-
};
|
|
194
|
+
const params = { camera_options: getCameraOptions(camera) };
|
|
156
195
|
viewerStore.request(viewer_schemas.opengeodeweb_viewer.viewer.update_camera, params, {
|
|
157
196
|
response_function: () => {
|
|
158
197
|
remoteRender();
|
|
159
|
-
|
|
160
|
-
if (Object.hasOwn(params.camera_options, key)) {
|
|
161
|
-
camera_options[key] = params.camera_options[key];
|
|
162
|
-
}
|
|
163
|
-
}
|
|
198
|
+
Object.assign(camera_options, params.camera_options);
|
|
164
199
|
},
|
|
165
200
|
});
|
|
166
201
|
}
|
|
@@ -176,7 +211,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
176
211
|
genericRenderWindow.value.setContainer(container.value.$el);
|
|
177
212
|
const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow();
|
|
178
213
|
webGLRenderWindow.setUseBackgroundImage(true);
|
|
179
|
-
|
|
214
|
+
imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style;
|
|
180
215
|
imageStyle.transition = "opacity 0.1s ease-in";
|
|
181
216
|
imageStyle.zIndex = 1;
|
|
182
217
|
resize(container.value.$el.offsetWidth, container.value.$el.offsetHeight);
|
|
@@ -248,36 +283,17 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
248
283
|
return;
|
|
249
284
|
}
|
|
250
285
|
const z_scale = snapshot.zScale;
|
|
286
|
+
if (typeof z_scale === "number") {
|
|
287
|
+
await setZScaling(z_scale);
|
|
288
|
+
}
|
|
251
289
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (!snapshot_camera_options) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
290
|
+
const { camera_options: snapshot_camera_options } = snapshot;
|
|
291
|
+
if (snapshot_camera_options) {
|
|
258
292
|
const renderer = genericRenderWindow.value.getRenderer();
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
applyCameraOptions(camera, snapshot_camera_options);
|
|
262
|
-
|
|
293
|
+
applyCameraOptions(renderer.getActiveCamera(), snapshot_camera_options);
|
|
263
294
|
genericRenderWindow.value.getRenderWindow().render();
|
|
264
|
-
|
|
265
|
-
const payload = {
|
|
266
|
-
camera_options: getCameraOptions(snapshot_camera_options),
|
|
267
|
-
};
|
|
268
|
-
return viewerStore.request(viewer_schemas.opengeodeweb_viewer.viewer.update_camera, payload, {
|
|
269
|
-
response_function: () => {
|
|
270
|
-
remoteRender();
|
|
271
|
-
Object.assign(camera_options, payload.camera_options);
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (typeof z_scale === "number") {
|
|
277
|
-
await setZScaling(z_scale);
|
|
278
|
-
return await applyCamera();
|
|
295
|
+
syncRemoteCamera();
|
|
279
296
|
}
|
|
280
|
-
return await applyCamera();
|
|
281
297
|
}
|
|
282
298
|
|
|
283
299
|
function clear() {
|
|
@@ -304,11 +320,13 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
304
320
|
remoteRender,
|
|
305
321
|
resize,
|
|
306
322
|
resetCamera,
|
|
323
|
+
setCameraOrientation,
|
|
307
324
|
setContainer,
|
|
308
325
|
zScale,
|
|
309
326
|
clear,
|
|
310
327
|
exportStores,
|
|
311
328
|
importStores,
|
|
329
|
+
camera_options,
|
|
312
330
|
latestImage,
|
|
313
331
|
getAverageBrightness,
|
|
314
332
|
};
|
|
@@ -1,45 +1,42 @@
|
|
|
1
|
+
import { SHORT_ANIMATION_DURATION } from "@ogw_front/utils/vtk/constants";
|
|
1
2
|
const RGB_MAX = 255;
|
|
2
3
|
const BACKGROUND_GREY_VALUE = 180;
|
|
3
4
|
const SAMPLE_SIZE = 10;
|
|
4
5
|
const TOTAL_CHANNELS = 400;
|
|
5
6
|
const RGBA_CHANNELS = 4;
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
8
|
+
const ORIENTATIONS = {
|
|
9
|
+
zplus: { position: [0, 0, 1], view_up: [0, 1, 0] },
|
|
10
|
+
zminus: { position: [0, 0, -1], view_up: [0, 1, 0] },
|
|
11
|
+
yplus: { position: [0, 1, 0], view_up: [0, 0, 1] },
|
|
12
|
+
yminus: { position: [0, -1, 0], view_up: [0, 0, 1] },
|
|
13
|
+
xplus: { position: [1, 0, 0], view_up: [0, 0, 1] },
|
|
14
|
+
xminus: { position: [-1, 0, 0], view_up: [0, 0, 1] },
|
|
15
|
+
};
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
function getCameraOptions(camera) {
|
|
18
|
+
if (!camera?.getFocalPoint) {
|
|
19
|
+
return camera;
|
|
14
20
|
}
|
|
15
21
|
return {
|
|
16
|
-
focal_point: [...camera.getFocalPoint()],
|
|
17
|
-
view_up: [...camera.getViewUp()],
|
|
18
|
-
position: [...camera.getPosition()],
|
|
22
|
+
focal_point: [...(camera.getFocalPoint() ?? [])],
|
|
23
|
+
view_up: [...(camera.getViewUp() ?? [])],
|
|
24
|
+
position: [...(camera.getPosition() ?? [])],
|
|
19
25
|
view_angle: camera.getViewAngle(),
|
|
20
|
-
clipping_range: [...camera.getClippingRange()],
|
|
26
|
+
clipping_range: [...(camera.getClippingRange() ?? [])],
|
|
21
27
|
distance: camera.getDistance(),
|
|
22
28
|
};
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
function applyCameraOptions(camera, options) {
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
if (options.position) {
|
|
36
|
-
camera.setPosition(...options.position);
|
|
37
|
-
}
|
|
38
|
-
if (options.view_angle) {
|
|
39
|
-
camera.setViewAngle(options.view_angle);
|
|
40
|
-
}
|
|
41
|
-
if (options.clipping_range) {
|
|
42
|
-
camera.setClippingRange(...options.clipping_range);
|
|
32
|
+
if (camera?.set && options) {
|
|
33
|
+
camera.set({
|
|
34
|
+
focalPoint: options.focal_point,
|
|
35
|
+
viewUp: options.view_up,
|
|
36
|
+
position: options.position,
|
|
37
|
+
viewAngle: options.view_angle,
|
|
38
|
+
clippingRange: options.clipping_range,
|
|
39
|
+
});
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
42
|
|
|
@@ -98,4 +95,55 @@ function computeAverageBrightness(rect, options) {
|
|
|
98
95
|
}
|
|
99
96
|
}
|
|
100
97
|
|
|
101
|
-
|
|
98
|
+
function animateCamera(options) {
|
|
99
|
+
const {
|
|
100
|
+
camera,
|
|
101
|
+
startState,
|
|
102
|
+
targetState,
|
|
103
|
+
duration,
|
|
104
|
+
bumpMultiplier,
|
|
105
|
+
easeExponent,
|
|
106
|
+
onUpdate,
|
|
107
|
+
onEnd,
|
|
108
|
+
} = options;
|
|
109
|
+
const startTime = performance.now();
|
|
110
|
+
|
|
111
|
+
function animate(currentTime) {
|
|
112
|
+
const progress = Math.min((currentTime - startTime) / duration, 1);
|
|
113
|
+
const ease =
|
|
114
|
+
duration > SHORT_ANIMATION_DURATION
|
|
115
|
+
? 1 - (1 - progress) ** easeExponent
|
|
116
|
+
: progress * (2 - progress);
|
|
117
|
+
const bump = bumpMultiplier * Math.sin(Math.PI * progress);
|
|
118
|
+
|
|
119
|
+
camera.set({
|
|
120
|
+
position: startState.position.map(
|
|
121
|
+
(startValue, index) =>
|
|
122
|
+
startValue + (targetState.position[index] - startValue) * ease + bump,
|
|
123
|
+
),
|
|
124
|
+
viewUp: startState.view_up.map(
|
|
125
|
+
(startValue, index) => startValue + (targetState.view_up[index] - startValue) * ease,
|
|
126
|
+
),
|
|
127
|
+
focalPoint: startState.focal_point.map(
|
|
128
|
+
(startValue, index) => startValue + (targetState.focal_point[index] - startValue) * ease,
|
|
129
|
+
),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
onUpdate();
|
|
133
|
+
|
|
134
|
+
if (progress < 1) {
|
|
135
|
+
requestAnimationFrame(animate);
|
|
136
|
+
} else {
|
|
137
|
+
onEnd();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
requestAnimationFrame(animate);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
ORIENTATIONS,
|
|
145
|
+
animateCamera,
|
|
146
|
+
applyCameraOptions,
|
|
147
|
+
computeAverageBrightness,
|
|
148
|
+
getCameraOptions,
|
|
149
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const RGB_MAX = 255;
|
|
2
|
+
const BACKGROUND_GREY_VALUE = 180;
|
|
3
|
+
const ACTOR_DARK_VALUE = 20;
|
|
4
|
+
|
|
5
|
+
const BACKGROUND_COLOR = [
|
|
6
|
+
BACKGROUND_GREY_VALUE / RGB_MAX,
|
|
7
|
+
BACKGROUND_GREY_VALUE / RGB_MAX,
|
|
8
|
+
BACKGROUND_GREY_VALUE / RGB_MAX,
|
|
9
|
+
];
|
|
10
|
+
const ACTOR_COLOR = [
|
|
11
|
+
ACTOR_DARK_VALUE / RGB_MAX,
|
|
12
|
+
ACTOR_DARK_VALUE / RGB_MAX,
|
|
13
|
+
ACTOR_DARK_VALUE / RGB_MAX,
|
|
14
|
+
];
|
|
15
|
+
const WHEEL_TIME_OUT_MS = 600;
|
|
16
|
+
const BUMP_MULTIPLIER = 0.2;
|
|
17
|
+
const ALIGNMENT_THRESHOLD = 0.9;
|
|
18
|
+
const LONG_ANIMATION_DURATION = 1000;
|
|
19
|
+
const SHORT_ANIMATION_DURATION = 500;
|
|
20
|
+
const EASE_EXPONENT = 1.1;
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
BACKGROUND_COLOR,
|
|
24
|
+
ACTOR_COLOR,
|
|
25
|
+
WHEEL_TIME_OUT_MS,
|
|
26
|
+
BUMP_MULTIPLIER,
|
|
27
|
+
ALIGNMENT_THRESHOLD,
|
|
28
|
+
LONG_ANIMATION_DURATION,
|
|
29
|
+
SHORT_ANIMATION_DURATION,
|
|
30
|
+
EASE_EXPONENT,
|
|
31
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geode/opengeodeweb-front",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.15.0",
|
|
4
4
|
"description": "OpenSource Vue/Nuxt/Pinia/Vuetify framework for web applications",
|
|
5
5
|
"homepage": "https://github.com/Geode-solutions/OpenGeodeWeb-Front",
|
|
6
6
|
"bugs": {
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"build": ""
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@geode/opengeodeweb-back": "
|
|
38
|
-
"@geode/opengeodeweb-viewer": "
|
|
37
|
+
"@geode/opengeodeweb-back": "latest",
|
|
38
|
+
"@geode/opengeodeweb-viewer": "latest",
|
|
39
39
|
"@google-cloud/run": "3.2.0",
|
|
40
40
|
"@kitware/vtk.js": "33.3.0",
|
|
41
41
|
"@mdi/font": "7.4.47",
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
3
|
-
|
|
4
|
-
import ActionButton from "@ogw_front/components/ActionButton.vue";
|
|
5
|
-
import Screenshot from "@ogw_front/components/Screenshot";
|
|
6
|
-
import ZScaling from "@ogw_front/components/ZScaling";
|
|
7
|
-
|
|
8
|
-
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
9
|
-
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
10
|
-
|
|
11
|
-
const hybridViewerStore = useHybridViewerStore();
|
|
12
|
-
const viewerStore = useViewerStore();
|
|
13
|
-
const take_screenshot = ref(false);
|
|
14
|
-
const showZScaling = ref(false);
|
|
15
|
-
const grid_scale = ref(false);
|
|
16
|
-
const zScale = ref(hybridViewerStore.zScale);
|
|
17
|
-
|
|
18
|
-
watch(
|
|
19
|
-
() => hybridViewerStore.zScale,
|
|
20
|
-
(newVal) => {
|
|
21
|
-
zScale.value = newVal;
|
|
22
|
-
},
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
async function handleZScalingClose() {
|
|
26
|
-
await hybridViewerStore.setZScaling(zScale.value);
|
|
27
|
-
showZScaling.value = false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const camera_options = [
|
|
31
|
-
{
|
|
32
|
-
tooltip: "Reset camera",
|
|
33
|
-
icon: "mdi-cube-scan",
|
|
34
|
-
action: () => {
|
|
35
|
-
hybridViewerStore.resetCamera();
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
tooltip: "Take a screenshot",
|
|
40
|
-
icon: "mdi-camera",
|
|
41
|
-
action: () => {
|
|
42
|
-
take_screenshot.value = !take_screenshot.value;
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
tooltip: "Toggle grid scale",
|
|
47
|
-
icon: "mdi-ruler-square",
|
|
48
|
-
action: () => {
|
|
49
|
-
viewerStore.request(
|
|
50
|
-
schemas.opengeodeweb_viewer.viewer.grid_scale,
|
|
51
|
-
{ visibility: !grid_scale.value },
|
|
52
|
-
{
|
|
53
|
-
response_function: () => {
|
|
54
|
-
grid_scale.value = !grid_scale.value;
|
|
55
|
-
hybridViewerStore.remoteRender();
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
);
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
tooltip: "Z Scaling Control",
|
|
63
|
-
icon: "mdi-sort",
|
|
64
|
-
action: () => {
|
|
65
|
-
showZScaling.value = !showZScaling.value;
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
];
|
|
69
|
-
</script>
|
|
70
|
-
|
|
71
|
-
<template>
|
|
72
|
-
<v-container :class="[$style.floatToolbar, 'pa-0']" width="auto">
|
|
73
|
-
<v-row v-for="camera_option in camera_options" :key="camera_option.icon" dense>
|
|
74
|
-
<v-col>
|
|
75
|
-
<ActionButton
|
|
76
|
-
:icon="camera_option.icon"
|
|
77
|
-
:tooltip="camera_option.tooltip"
|
|
78
|
-
@click.stop="camera_option.action"
|
|
79
|
-
/>
|
|
80
|
-
</v-col>
|
|
81
|
-
</v-row>
|
|
82
|
-
</v-container>
|
|
83
|
-
<Screenshot :show_dialog="take_screenshot" @close="take_screenshot = false" />
|
|
84
|
-
<ZScaling v-if="showZScaling" v-model="zScale" :width="400" @close="handleZScalingClose" />
|
|
85
|
-
</template>
|
|
86
|
-
|
|
87
|
-
<style module>
|
|
88
|
-
.floatToolbar {
|
|
89
|
-
position: absolute;
|
|
90
|
-
z-index: 2;
|
|
91
|
-
right: 20px;
|
|
92
|
-
top: 20px;
|
|
93
|
-
background-color: rgba(0, 0, 0, 0);
|
|
94
|
-
}
|
|
95
|
-
</style>
|