Skip to content

Commit

Permalink
fix: handle skipping of ver3 ghosts correctly and move opening explor…
Browse files Browse the repository at this point in the history
…er to a button (#367)
  • Loading branch information
wopian committed Jun 25, 2023
1 parent 7d2c733 commit 9b3c016
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 71 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ yarn test:e2e
```sh
yarn lint
```

### Exporting STLs

Ensure the coordinate system is set to "-Z Forward" and "Y Up"
Binary file modified src/assets/models/combined_soapbox.stl
Binary file not shown.
2 changes: 1 addition & 1 deletion src/components/LoadingIndicator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
&.inline {
height: unset;
top: 0.6rem;
top: 0.4rem;
div {
top: 0;
Expand Down
16 changes: 12 additions & 4 deletions src/components/PaginatedComponent.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
<script setup lang="ts">
import {
IconPlayerSkipBackFilled,
IconPlayerSkipForwardFilled,
IconPlayerTrackNextFilled,
IconPlayerTrackPrevFilled
} from '@tabler/icons-vue'
import { ref } from 'vue'
import LoadingIndicator from '~/components/LoadingIndicator.vue'
Expand All @@ -19,6 +25,8 @@
const totalPages = ref(Math.ceil(totalItems / itemsPerPage))
const iconSize = 16
const emit = defineEmits<{
(event: 'page-changed', page: number): void
}>()
Expand All @@ -34,25 +42,25 @@
:disabled="disabledPagination || currentPage == 1"
title="First"
@click="emit('page-changed', 1)">
<icon-player-skip-back-filled :size="iconSize" />
</button>
<button
:disabled="disabledPagination || currentPage === 1"
title="Previous"
@click="emit('page-changed', currentPage - 1)">
<icon-player-track-prev-filled :size="iconSize" />
</button>
<button
:disabled="disabledPagination || currentPage === totalPages"
title="Next"
@click="emit('page-changed', currentPage + 1)">
<icon-player-track-next-filled :size="iconSize" />
</button>
<button
:disabled="disabledPagination || currentPage === totalPages"
title="Last"
@click="emit('page-changed', totalPages)">
<icon-player-skip-forward-filled :size="iconSize" />
</button>
</div>
</div>
Expand Down
112 changes: 58 additions & 54 deletions src/components/RecordRow.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { IconGhostFilled } from '@tabler/icons-vue'
import { useQueryClient } from '@tanstack/vue-query'
import type { LevelRecord } from '@zeepkist/gtr-api'
import { RouterLink } from 'vue-router'
Expand Down Expand Up @@ -38,83 +39,86 @@
</script>

<template>
<ghost-modal :ghost-urls="ghosts">
<div
class="record"
:class="{
'has-no-track': hideTrackInfo,
'has-rank': rank
}">
<div v-if="rank" class="rank">{{ rank }}</div>
<div
class="record"
:class="{
'has-no-track': hideTrackInfo,
'has-rank': rank
}">
<div v-if="rank" class="rank">{{ rank }}</div>
<router-link
v-if="!hideTrackInfo"
:to="{ name: 'level', params: { id: record.level.id } }"
@click.stop>
<img
v-if="!hideTrackInfo"
:src="record.level.thumbnailUrl"
:alt="`Thumbnail of ${record.level.name}`" />
<div v-if="!hideTrackInfo" class="author">
<router-link
:to="{ name: 'level', params: { id: record.level.id } }"
@click.stop>
{{ record.level.name }}
</router-link>
<div class="subtext">
By <user-badge :username="record.level.author" />
</div>
</router-link>
<div v-if="!hideTrackInfo" class="author">
<router-link
:to="{ name: 'level', params: { id: record.level.id } }"
@click.stop>
{{ record.level.name }}
</router-link>
<div class="subtext">
By <user-badge :username="record.level.author" />
</div>
<div class="author">
<router-link
v-if="showUser"
:to="{ name: 'user', params: { steamId: record.user.steamId } }"
@click.stop>
<user-badge :username="record.user.steamName" />
</router-link>
<div class="subtext" :title="formatDate(record.dateCreated)">
{{ formatRelativeDate(record.dateCreated) }}
</div>
</div>
<div class="author">
<router-link
v-if="showUser"
:to="{ name: 'user', params: { steamId: record.user.steamId } }"
@click.stop>
<user-badge :username="record.user.steamName" />
</router-link>
<div class="subtext" :title="formatDate(record.dateCreated)">
{{ formatRelativeDate(record.dateCreated) }}
</div>
<div>
<div class="right">{{ formatResultTime(record.time) }}</div>
<div
v-if="
showBadges &&
(record.isBest || record.isWorldRecord || !record.isValid)
"
class="record-badges">
<span v-if="record.isWorldRecord" class="wr" title="World Record"
>WR</span
>
<span v-if="record.isBest" class="pb" title="Personal Best">PB</span>
<span v-if="!record.isValid" class="any" title="Any Percentage"
>any%</span
>
</div>
<div v-if="showPoints" class="right subtext">
{{ calculateRecordPoints(rank ?? 1, record.level.points) }} ➤
</div>
</div>
<div>
<div class="right">{{ formatResultTime(record.time) }}</div>
<div
v-if="
showBadges &&
(record.isBest || record.isWorldRecord || !record.isValid)
"
class="record-badges">
<span v-if="record.isWorldRecord" class="wr" title="World Record"
>WR</span
>
<span v-if="record.isBest" class="pb" title="Personal Best">PB</span>
<span v-if="!record.isValid" class="any" title="Any Percentage"
>any%</span
>
</div>
<div v-if="false" class="actions">
<button disabled>View Ghost</button>
<button disabled>Compare</button>
<div v-if="showPoints" class="right subtext">
{{ calculateRecordPoints(rank ?? 1, record.level.points) }} ➤
</div>
</div>
</ghost-modal>
<div class="actions">
<ghost-modal :ghost-urls="ghosts">
<icon-ghost-filled />
</ghost-modal>
</div>
</div>
</template>

<style scoped lang="less">
.record {
display: grid;
grid-template-columns: 80px 2fr 1fr 100px;
grid-template-columns: 80px 2fr 1fr 100px 20px;
grid-template-rows: 50px;
gap: 1rem;
align-items: center;
padding: 0.25rem 1rem;
margin: 0 -1rem;

&.has-no-track:not(.has-rank) {
grid-template-columns: 2fr 100px;
grid-template-columns: 2fr 100px 20px;
}

&.has-no-track&.has-rank {
grid-template-columns: 3ch 2fr 100px;
grid-template-columns: 3ch 2fr 100px 20px;
}

&:nth-of-type(even) {
Expand Down
7 changes: 3 additions & 4 deletions src/components/canvases/GhostCanvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,12 @@
const position = points[currentFrame]
? (points[currentFrame] as Vector3)
: (points.at(-3) as Vector3)
soapbox.position.copy(position)
const quaternion = ghost.frames[currentFrame]
let quaternion = ghost.frames[currentFrame]
? (ghost.frames[currentFrame].quaternion as Quaternion)
: (ghost.frames.at(-3)?.quaternion as Quaternion)
soapbox.position.copy(position)
soapbox.quaternion.copy(quaternion)
if (quaternion) soapbox.quaternion.copy(quaternion)
}
material.visible = true
Expand Down
11 changes: 10 additions & 1 deletion src/components/modals/GhostModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
margin: 0 -1rem;
&:hover {
background: rgb(var(--link-1)) !important;
color: rgb(var(--link-5)) !important;
}
}
Expand All @@ -58,6 +58,15 @@
border-radius: var(--border-radius-large);
padding: 1rem;
@media screen and (max-width: 1200px) {
top: 0;
left: 0;
right: 0;
bottom: 0;
width: unset;
height: unset;
}
button {
position: absolute;
top: 1rem;
Expand Down
22 changes: 15 additions & 7 deletions src/utils/three/createGhosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
LineBasicMaterial,
Mesh,
MeshStandardMaterial,
Quaternion,
Scene,
Vector3
} from 'three'
Expand All @@ -25,15 +24,21 @@ interface GhostInstance {
}

export const createGhosts = async (scene: Scene, urls: string[]) => {
const ghosts: GhostInstance[] = []
const ghosts: (GhostInstance | undefined)[] = []

let totalDuration = 0

for (const [index, url] of urls.entries()) {
const { ghost } = await getGhost(url)

// TODO: Remove this when the parsing API is updated. Positions seem off in version 3 ghosts
if (ghost.version < 4) continue
if (ghost.version === 3) {
console.warn(
`Ghost ${index} uses version ${ghost.version}. Not rendering.`
)
ghosts.push(undefined)
continue
}

totalDuration = Math.max(totalDuration, ghost.frames.at(-1)?.time ?? 0)

Expand All @@ -60,8 +65,8 @@ export const createGhosts = async (scene: Scene, urls: string[]) => {
loader.load(soapboxUrl, geometry => {
const soapboxMaterial = new MeshStandardMaterial({
color: material.color,
metalness: 0.5,
roughness: 0.5
metalness: 0.75,
roughness: 0.75
})

const soapbox = new Mesh(geometry, soapboxMaterial)
Expand All @@ -74,7 +79,8 @@ export const createGhosts = async (scene: Scene, urls: string[]) => {

scene.add(soapbox)

ghosts[index].soapbox = soapbox
const ghostInstance = ghosts[index]
if (ghostInstance) ghostInstance.soapbox = soapbox
})

scene.add(line)
Expand All @@ -90,5 +96,7 @@ export const createGhosts = async (scene: Scene, urls: string[]) => {
})
}

return { ghosts, totalDuration }
const filteredGhosts = ghosts.filter(Boolean) as GhostInstance[]

return { ghosts: filteredGhosts, totalDuration }
}

0 comments on commit 9b3c016

Please sign in to comment.