Add configurable game flow, finish punching, and audio cues
This commit is contained in:
@@ -6,6 +6,9 @@ import { TrackLayer } from '../layer/trackLayer'
|
||||
import { GpsLayer } from '../layer/gpsLayer'
|
||||
|
||||
const COURSE_COLOR: [number, number, number, number] = [0.8, 0.0, 0.42, 0.96]
|
||||
const COMPLETED_ROUTE_COLOR: [number, number, number, number] = [0.48, 0.5, 0.54, 0.82]
|
||||
const ACTIVE_CONTROL_COLOR: [number, number, number, number] = [0.22, 1, 0.95, 1]
|
||||
const ACTIVE_LEG_COLOR: [number, number, number, number] = [0.18, 1, 0.94, 0.5]
|
||||
const EARTH_CIRCUMFERENCE_METERS = 40075016.686
|
||||
const CONTROL_RING_WIDTH_RATIO = 0.2
|
||||
const FINISH_INNER_RADIUS_RATIO = 0.6
|
||||
@@ -13,16 +16,19 @@ const FINISH_RING_WIDTH_RATIO = 0.2
|
||||
const START_RING_WIDTH_RATIO = 0.2
|
||||
const LEG_WIDTH_RATIO = 0.2
|
||||
const LEG_TRIM_TO_RING_CENTER_RATIO = 1 - CONTROL_RING_WIDTH_RATIO / 2
|
||||
const ACTIVE_CONTROL_PULSE_SPEED = 0.18
|
||||
const ACTIVE_CONTROL_PULSE_MIN_SCALE = 1.12
|
||||
const ACTIVE_CONTROL_PULSE_MAX_SCALE = 1.46
|
||||
const ACTIVE_CONTROL_PULSE_WIDTH_RATIO = 0.12
|
||||
const GUIDE_FLOW_COUNT = 5
|
||||
const GUIDE_FLOW_SPEED = 0.02
|
||||
const GUIDE_FLOW_TRAIL = 0.16
|
||||
const GUIDE_FLOW_MIN_WIDTH_RATIO = 0.12
|
||||
const GUIDE_FLOW_MAX_WIDTH_RATIO = 0.22
|
||||
const GUIDE_FLOW_HEAD_RADIUS_RATIO = 0.18
|
||||
|
||||
type RgbaColor = [number, number, number, number]
|
||||
|
||||
const GUIDE_FLOW_COUNT = 6
|
||||
const GUIDE_FLOW_SPEED = 0.022
|
||||
const GUIDE_FLOW_MIN_RADIUS_RATIO = 0.14
|
||||
const GUIDE_FLOW_MAX_RADIUS_RATIO = 0.34
|
||||
const GUIDE_FLOW_OUTER_SCALE = 1.45
|
||||
const GUIDE_FLOW_INNER_SCALE = 0.56
|
||||
|
||||
function createShader(gl: any, type: number, source: string): any {
|
||||
const shader = gl.createShader(type)
|
||||
if (!shader) {
|
||||
@@ -225,20 +231,36 @@ export class WebGLVectorRenderer {
|
||||
): void {
|
||||
const controlRadiusMeters = this.getControlRadiusMeters(scene)
|
||||
|
||||
for (const leg of course.legs) {
|
||||
this.pushCourseLeg(positions, colors, leg, controlRadiusMeters, scene)
|
||||
if (scene.revealFullCourse) {
|
||||
for (let index = 0; index < course.legs.length; index += 1) {
|
||||
const leg = course.legs[index]
|
||||
this.pushCourseLeg(positions, colors, leg, controlRadiusMeters, this.getLegColor(scene, index), scene)
|
||||
if (scene.activeLegIndices.includes(index)) {
|
||||
this.pushCourseLegHighlight(positions, colors, leg, controlRadiusMeters, scene)
|
||||
}
|
||||
}
|
||||
|
||||
const guideLeg = this.getGuideLeg(course)
|
||||
const guideLeg = this.getGuideLeg(course, scene)
|
||||
if (guideLeg) {
|
||||
this.pushGuidanceFlow(positions, colors, guideLeg, controlRadiusMeters, scene, pulseFrame)
|
||||
}
|
||||
}
|
||||
|
||||
for (const start of course.starts) {
|
||||
this.pushStartTriangle(positions, colors, start.point.x, start.point.y, start.headingDeg, controlRadiusMeters, scene)
|
||||
if (scene.activeStart) {
|
||||
this.pushActiveStartPulse(positions, colors, start.point.x, start.point.y, start.headingDeg, controlRadiusMeters, scene, pulseFrame)
|
||||
}
|
||||
this.pushStartTriangle(positions, colors, start.point.x, start.point.y, start.headingDeg, controlRadiusMeters, this.getStartColor(scene), scene)
|
||||
}
|
||||
if (!scene.revealFullCourse) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const control of course.controls) {
|
||||
if (scene.activeControlSequences.includes(control.sequence)) {
|
||||
this.pushActiveControlPulse(positions, colors, control.point.x, control.point.y, controlRadiusMeters, scene, pulseFrame)
|
||||
}
|
||||
|
||||
this.pushRing(
|
||||
positions,
|
||||
colors,
|
||||
@@ -246,12 +268,17 @@ export class WebGLVectorRenderer {
|
||||
control.point.y,
|
||||
this.getMetric(scene, controlRadiusMeters),
|
||||
this.getMetric(scene, controlRadiusMeters * (1 - CONTROL_RING_WIDTH_RATIO)),
|
||||
COURSE_COLOR,
|
||||
this.getControlColor(scene, control.sequence),
|
||||
scene,
|
||||
)
|
||||
}
|
||||
|
||||
for (const finish of course.finishes) {
|
||||
if (scene.activeFinish) {
|
||||
this.pushActiveControlPulse(positions, colors, finish.point.x, finish.point.y, controlRadiusMeters, scene, pulseFrame)
|
||||
}
|
||||
|
||||
const finishColor = this.getFinishColor(scene)
|
||||
this.pushRing(
|
||||
positions,
|
||||
colors,
|
||||
@@ -259,7 +286,7 @@ export class WebGLVectorRenderer {
|
||||
finish.point.y,
|
||||
this.getMetric(scene, controlRadiusMeters),
|
||||
this.getMetric(scene, controlRadiusMeters * (1 - FINISH_RING_WIDTH_RATIO)),
|
||||
COURSE_COLOR,
|
||||
finishColor,
|
||||
scene,
|
||||
)
|
||||
this.pushRing(
|
||||
@@ -269,17 +296,46 @@ export class WebGLVectorRenderer {
|
||||
finish.point.y,
|
||||
this.getMetric(scene, controlRadiusMeters * FINISH_INNER_RADIUS_RATIO),
|
||||
this.getMetric(scene, controlRadiusMeters * FINISH_INNER_RADIUS_RATIO * (1 - FINISH_RING_WIDTH_RATIO / FINISH_INNER_RADIUS_RATIO)),
|
||||
COURSE_COLOR,
|
||||
finishColor,
|
||||
scene,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
getGuideLeg(course: ProjectedCourseLayers): ProjectedCourseLeg | null {
|
||||
return course.legs.length ? course.legs[0] : null
|
||||
getGuideLeg(course: ProjectedCourseLayers, scene: MapScene): ProjectedCourseLeg | null {
|
||||
const activeIndex = scene.activeLegIndices.length ? scene.activeLegIndices[0] : -1
|
||||
if (activeIndex >= 0 && activeIndex < course.legs.length) {
|
||||
return course.legs[activeIndex]
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
getLegColor(scene: MapScene, index: number): RgbaColor {
|
||||
return this.isCompletedLeg(scene, index) ? COMPLETED_ROUTE_COLOR : COURSE_COLOR
|
||||
}
|
||||
|
||||
isCompletedLeg(scene: MapScene, index: number): boolean {
|
||||
return scene.completedLegIndices.includes(index)
|
||||
}
|
||||
|
||||
pushCourseLeg(
|
||||
positions: number[],
|
||||
colors: number[],
|
||||
leg: ProjectedCourseLeg,
|
||||
controlRadiusMeters: number,
|
||||
color: RgbaColor,
|
||||
scene: MapScene,
|
||||
): void {
|
||||
const trimmed = this.getTrimmedCourseLeg(leg, controlRadiusMeters, scene)
|
||||
if (!trimmed) {
|
||||
return
|
||||
}
|
||||
|
||||
this.pushSegment(positions, colors, trimmed.from, trimmed.to, this.getMetric(scene, controlRadiusMeters * LEG_WIDTH_RATIO), color, scene)
|
||||
}
|
||||
|
||||
pushCourseLegHighlight(
|
||||
positions: number[],
|
||||
colors: number[],
|
||||
leg: ProjectedCourseLeg,
|
||||
@@ -291,7 +347,110 @@ export class WebGLVectorRenderer {
|
||||
return
|
||||
}
|
||||
|
||||
this.pushSegment(positions, colors, trimmed.from, trimmed.to, this.getMetric(scene, controlRadiusMeters * LEG_WIDTH_RATIO), COURSE_COLOR, scene)
|
||||
this.pushSegment(
|
||||
positions,
|
||||
colors,
|
||||
trimmed.from,
|
||||
trimmed.to,
|
||||
this.getMetric(scene, controlRadiusMeters * LEG_WIDTH_RATIO * 1.5),
|
||||
ACTIVE_LEG_COLOR,
|
||||
scene,
|
||||
)
|
||||
}
|
||||
|
||||
pushActiveControlPulse(
|
||||
positions: number[],
|
||||
colors: number[],
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
controlRadiusMeters: number,
|
||||
scene: MapScene,
|
||||
pulseFrame: number,
|
||||
): void {
|
||||
const pulse = (Math.sin(pulseFrame * ACTIVE_CONTROL_PULSE_SPEED) + 1) / 2
|
||||
const pulseScale = ACTIVE_CONTROL_PULSE_MIN_SCALE + (ACTIVE_CONTROL_PULSE_MAX_SCALE - ACTIVE_CONTROL_PULSE_MIN_SCALE) * pulse
|
||||
const pulseWidthScale = pulseScale - ACTIVE_CONTROL_PULSE_WIDTH_RATIO
|
||||
const glowAlpha = 0.24 + pulse * 0.34
|
||||
const glowColor: RgbaColor = [0.36, 1, 0.96, glowAlpha]
|
||||
|
||||
this.pushRing(
|
||||
positions,
|
||||
colors,
|
||||
centerX,
|
||||
centerY,
|
||||
this.getMetric(scene, controlRadiusMeters * pulseScale),
|
||||
this.getMetric(scene, controlRadiusMeters * Math.max(1, pulseWidthScale)),
|
||||
glowColor,
|
||||
scene,
|
||||
)
|
||||
}
|
||||
|
||||
pushActiveStartPulse(
|
||||
positions: number[],
|
||||
colors: number[],
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
headingDeg: number | null,
|
||||
controlRadiusMeters: number,
|
||||
scene: MapScene,
|
||||
pulseFrame: number,
|
||||
): void {
|
||||
const pulse = (Math.sin(pulseFrame * ACTIVE_CONTROL_PULSE_SPEED) + 1) / 2
|
||||
const pulseScale = ACTIVE_CONTROL_PULSE_MIN_SCALE + (ACTIVE_CONTROL_PULSE_MAX_SCALE - ACTIVE_CONTROL_PULSE_MIN_SCALE) * pulse
|
||||
const pulseWidthScale = pulseScale - ACTIVE_CONTROL_PULSE_WIDTH_RATIO
|
||||
const glowAlpha = 0.24 + pulse * 0.34
|
||||
const glowColor: RgbaColor = [0.36, 1, 0.96, glowAlpha]
|
||||
const headingRad = ((headingDeg === null ? 0 : headingDeg) - 90) * Math.PI / 180
|
||||
const ringCenterX = centerX + Math.cos(headingRad) * this.getMetric(scene, controlRadiusMeters * 0.04)
|
||||
const ringCenterY = centerY + Math.sin(headingRad) * this.getMetric(scene, controlRadiusMeters * 0.04)
|
||||
|
||||
this.pushRing(
|
||||
positions,
|
||||
colors,
|
||||
ringCenterX,
|
||||
ringCenterY,
|
||||
this.getMetric(scene, controlRadiusMeters * pulseScale),
|
||||
this.getMetric(scene, controlRadiusMeters * Math.max(1, pulseWidthScale)),
|
||||
glowColor,
|
||||
scene,
|
||||
)
|
||||
}
|
||||
|
||||
getStartColor(scene: MapScene): RgbaColor {
|
||||
if (scene.activeStart) {
|
||||
return ACTIVE_CONTROL_COLOR
|
||||
}
|
||||
|
||||
if (scene.completedStart) {
|
||||
return COMPLETED_ROUTE_COLOR
|
||||
}
|
||||
|
||||
return COURSE_COLOR
|
||||
}
|
||||
|
||||
getControlColor(scene: MapScene, sequence: number): RgbaColor {
|
||||
if (scene.activeControlSequences.includes(sequence)) {
|
||||
return ACTIVE_CONTROL_COLOR
|
||||
}
|
||||
|
||||
if (scene.completedControlSequences.includes(sequence)) {
|
||||
return COMPLETED_ROUTE_COLOR
|
||||
}
|
||||
|
||||
return COURSE_COLOR
|
||||
}
|
||||
|
||||
|
||||
getFinishColor(scene: MapScene): RgbaColor {
|
||||
if (scene.activeFinish) {
|
||||
return ACTIVE_CONTROL_COLOR
|
||||
}
|
||||
|
||||
if (scene.completedFinish) {
|
||||
return COMPLETED_ROUTE_COLOR
|
||||
}
|
||||
|
||||
return COURSE_COLOR
|
||||
}
|
||||
|
||||
pushGuidanceFlow(
|
||||
@@ -316,18 +475,28 @@ export class WebGLVectorRenderer {
|
||||
|
||||
for (let index = 0; index < GUIDE_FLOW_COUNT; index += 1) {
|
||||
const progress = (pulseFrame * GUIDE_FLOW_SPEED + index / GUIDE_FLOW_COUNT) % 1
|
||||
const tailProgress = Math.max(0, progress - GUIDE_FLOW_TRAIL)
|
||||
const head = {
|
||||
x: trimmed.from.x + dx * progress,
|
||||
y: trimmed.from.y + dy * progress,
|
||||
}
|
||||
const tail = {
|
||||
x: trimmed.from.x + dx * tailProgress,
|
||||
y: trimmed.from.y + dy * tailProgress,
|
||||
}
|
||||
const eased = progress * progress
|
||||
const x = trimmed.from.x + dx * progress
|
||||
const y = trimmed.from.y + dy * progress
|
||||
const radius = this.getMetric(
|
||||
const width = this.getMetric(
|
||||
scene,
|
||||
controlRadiusMeters * (GUIDE_FLOW_MIN_RADIUS_RATIO + (GUIDE_FLOW_MAX_RADIUS_RATIO - GUIDE_FLOW_MIN_RADIUS_RATIO) * eased),
|
||||
controlRadiusMeters * (GUIDE_FLOW_MIN_WIDTH_RATIO + (GUIDE_FLOW_MAX_WIDTH_RATIO - GUIDE_FLOW_MIN_WIDTH_RATIO) * eased),
|
||||
)
|
||||
const outerColor = this.getGuideFlowOuterColor(eased)
|
||||
const innerColor = this.getGuideFlowInnerColor(eased)
|
||||
const headRadius = this.getMetric(scene, controlRadiusMeters * GUIDE_FLOW_HEAD_RADIUS_RATIO * (0.72 + eased * 0.42))
|
||||
|
||||
this.pushCircle(positions, colors, x, y, radius * GUIDE_FLOW_OUTER_SCALE, outerColor, scene)
|
||||
this.pushCircle(positions, colors, x, y, radius * GUIDE_FLOW_INNER_SCALE, innerColor, scene)
|
||||
this.pushSegment(positions, colors, tail, head, width * 1.9, outerColor, scene)
|
||||
this.pushSegment(positions, colors, tail, head, width, innerColor, scene)
|
||||
this.pushCircle(positions, colors, head.x, head.y, headRadius * 1.35, outerColor, scene)
|
||||
this.pushCircle(positions, colors, head.x, head.y, headRadius, innerColor, scene)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,11 +514,11 @@ export class WebGLVectorRenderer {
|
||||
}
|
||||
|
||||
getGuideFlowOuterColor(progress: number): RgbaColor {
|
||||
return [1, 0.18, 0.6, 0.16 + progress * 0.34]
|
||||
return [0.28, 0.92, 1, 0.14 + progress * 0.22]
|
||||
}
|
||||
|
||||
getGuideFlowInnerColor(progress: number): RgbaColor {
|
||||
return [1, 0.95, 0.98, 0.3 + progress * 0.54]
|
||||
return [0.94, 0.99, 1, 0.38 + progress * 0.42]
|
||||
}
|
||||
|
||||
getLegTrim(kind: ProjectedCourseLeg['fromKind'], controlRadiusMeters: number, scene: MapScene): number {
|
||||
@@ -398,6 +567,7 @@ export class WebGLVectorRenderer {
|
||||
centerY: number,
|
||||
headingDeg: number | null,
|
||||
controlRadiusMeters: number,
|
||||
color: RgbaColor,
|
||||
scene: MapScene,
|
||||
): void {
|
||||
const startRadius = this.getMetric(scene, controlRadiusMeters)
|
||||
@@ -411,9 +581,9 @@ export class WebGLVectorRenderer {
|
||||
}
|
||||
})
|
||||
|
||||
this.pushSegment(positions, colors, vertices[0], vertices[1], startRingWidth, COURSE_COLOR, scene)
|
||||
this.pushSegment(positions, colors, vertices[1], vertices[2], startRingWidth, COURSE_COLOR, scene)
|
||||
this.pushSegment(positions, colors, vertices[2], vertices[0], startRingWidth, COURSE_COLOR, scene)
|
||||
this.pushSegment(positions, colors, vertices[0], vertices[1], startRingWidth, color, scene)
|
||||
this.pushSegment(positions, colors, vertices[1], vertices[2], startRingWidth, color, scene)
|
||||
this.pushSegment(positions, colors, vertices[2], vertices[0], startRingWidth, color, scene)
|
||||
}
|
||||
|
||||
pushRing(
|
||||
@@ -515,3 +685,5 @@ export class WebGLVectorRenderer {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user