feat(frontend): implements stiched 3d previews

This commit is contained in:
Dimitrie Stefanescu
2022-05-26 15:53:21 +01:00
parent d98a3c5d15
commit e8bde06d7d
@@ -3,22 +3,35 @@
:style="`position: relative; height: ${height}px;`"
:class="`${$vuetify.theme.dark ? 'grey darken-4' : 'grey lighten-4'}`"
@mouseenter="hovered = true"
@touchmove="parseTouch"
@mouseleave="
hovered = false
imageIndex = 0
"
@mouseleave="hovered = false"
@mousemove="setIndex"
@touchmove="
(e) =>
setIndex({
target: e.target,
clientX: e.touches[0].clientX,
clientY: e.touches[0].clientY
})
"
>
<!--
Note: alternate style, sketchfab inspired! We are controlling image display via manipulating
bg image props position. Results in less dom elements, and no flickering!
-->
<div :style="bgStyle"></div>
<v-fade-transition>
<div
v-show="!(fullPreviewImage && hovered) || legacyMode"
ref="image_360"
:style="background"
></div>
</v-fade-transition>
<div
ref="image_360"
xxxv-show="!legacyMode && hovered && fullPreviewImage"
:style="background360"
></div>
<v-progress-linear
v-show="loading"
indeterminate
height="2"
height="4"
style="position: absolute; bottom: 0"
/>
</div>
@@ -36,7 +49,7 @@ export default {
},
height: {
type: Number,
default: 180
default: 200
},
rotate: {
type: Boolean,
@@ -45,68 +58,97 @@ export default {
},
data() {
return {
isMounted: false,
loading: false,
hovered: false,
hasStartedLoadingImages: false,
currentPreviewImg: '',
previewImages: [],
previewImage: null,
fullPreviewImage: null,
imageIndex: 0,
legacyMode: false,
angles: [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 0
]
controller: new AbortController()
}
},
computed: {
revImg() {
return this.legacyMode ? this.previewImages : [...this.previewImages].reverse()
background() {
return `
position: absolute;
top:0;
height: 100%;
width: 100%;
background-size: cover;
background-image:url(${this.previewImage});
background-position: center center;
`
},
bgStyle() {
let bgStyle = `
background360() {
let background = `
position: absolute;
opacity: ${!this.legacyMode && this.hovered && this.fullPreviewImage ? 1 : 0};
transition: opacity 0.5s;
top:0;
height: 100%;
width: 100%;
background-size: cover;
background-image:`
if (this.fullPreviewImage && (this.hovered || true)) {
background += `url(${this.fullPreviewImage});`
} else {
background += `url(${this.previewImage});`
}
if (!this.isMounted) return background
const scaleFactor = this.$refs.image_360.getBoundingClientRect().height / 400
const actualWidth = scaleFactor * 700
const widthDiff =
(this.$refs.image_360.getBoundingClientRect().width - actualWidth) * 0.5
for (let i = 0; i < this.revImg.length; i++) {
bgStyle += `url(${this.revImg[i]})${i !== this.revImg.length - 1 ? ',' : ';'}`
if (this.fullPreviewImage && (this.hovered || true)) {
background += `background-position: ${-(
actualWidth * (2 * this.imageIndex + 1) -
widthDiff
)}px 0;`
} else {
background += `background-position: center center;`
}
bgStyle += `
background-position:`
for (let i = 0; i < this.revImg.length; i++) {
bgStyle += `${i === this.imageIndex ? 'center' : '10000px'}${
i !== this.revImg.length - 1 ? ',' : ';'
}`
}
return bgStyle
return background
}
},
watch: {
hovered(val) {
if (!this.rotate) return
if (!val || this.hasStartedLoadingImages) return
this.getOtherAngles()
this.hasStartedLoadingImages = true
if (val && !this.fullPreviewImage) {
setTimeout(async () => {
if (!this.hovered) return
if (this.legacyMode) return
if (this.fullPreviewImage) return
this.loading = true
try {
this.fullPreviewImage = await this.getPreviewImage('all')
} catch (err) {
if (err.toString() === 'Failed getting preview') {
this.legacyMode = true
}
// else: we've aborted the request due to mouse moving out
}
this.loading = false
}, 500)
}
if (!val && this.loading) {
this.controller.abort()
this.controller = new AbortController()
this.loading = false
}
if (!val) {
this.imageIndex = 0
}
}
},
async mounted() {
this.previewImages.push(await this.getPreviewImage())
this.previewImage = await this.getPreviewImage()
this.isMounted = true
},
methods: {
parseTouch(e) {
if (this.loading || !this.rotate) {
this.imageIndex = 0
return
}
this.hovered = true
this.setIndex({
target: e.target,
clientX: e.touches[0].clientX,
clientY: e.touches[0].clientY
})
},
setIndex(e) {
if (this.loading || !this.rotate) {
this.imageIndex = 0
@@ -114,13 +156,14 @@ export default {
}
const rect = e.target.getBoundingClientRect()
const x = e.clientX - rect.left
const step = rect.width / this.previewImages.length
const step = rect.width / 24
let index = Math.round(x / step)
if (index >= this.previewImages.length) index = this.previewImages.length - 1
if (index >= 24) index = 24 - 1
this.imageIndex = index
},
async getPreviewImage(angle = 0) {
const res = await fetch(this.url + `/${angle}`, {
signal: this.controller.signal,
headers: localStorage.getItem('AuthToken')
? { Authorization: `Bearer ${localStorage.getItem('AuthToken')}` }
: {}
@@ -131,44 +174,6 @@ export default {
}
const blob = await res.blob()
return URL.createObjectURL(blob)
},
async getOtherAngles() {
// Note: previously, previews were generated for only 5 angles (-30deg, -15, 0, 15, 30deg), corresponding to
// labelled angles (-2, -1, 0, 1, 2). We have now switched to generating full 360deg previews in increments
// of 15 deg, going clockwise straight. Eg., 0 = 0deg, 1 = 15deg, 2 = 30 deg, etc.
this.loading = true
try {
const img = await this.getPreviewImage(this.angles[this.angles.length - 1]) // note: this throws if requesting an incorrect angle.
this.$set(this.previewImages, this.angles.length - 1, img)
const promises = []
for (let i = 1; i < this.angles.length; i++) {
promises.push(this.getPreviewImage(this.angles[i]))
}
const otherImgs = await Promise.all(promises)
for (let i = 0; i < otherImgs.length; i++) {
this.$set(this.previewImages, i + 1, otherImgs[i])
}
} catch (e) {
// legacy track
this.legacyMode = true
const otherImgs = await Promise.all([
this.getPreviewImage(-1),
this.getPreviewImage(-2),
this.getPreviewImage(1),
this.getPreviewImage(2)
])
this.previewImages.unshift(otherImgs[0])
this.previewImages.unshift(otherImgs[1])
this.previewImages.push(otherImgs[2])
this.previewImages.push(otherImgs[3])
// TODO: Weird. getPreviewImage throws, but the try block keeps going until the for loop,
// resulting in an incorrect array; we need to filter out nulls/undefineds here...
this.previewImages = this.previewImages.filter((i) => !!i)
}
this.loading = false
}
}
}
@@ -178,25 +183,4 @@ export default {
transition: all 0.3s;
filter: grayscale(100%);
}
.preview-img {
width: 100%;
opacity: 0.8;
object-fit: cover;
transition: all 0.2s ease;
}
.preview-img:hover {
opacity: 1;
}
.stream-link a {
/* color: inherit; */
text-decoration: none;
font-weight: 500;
}
.stream-link a:hover {
text-decoration: underline;
}
</style>