minecraft-renderer 0.1.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.
Files changed (187) hide show
  1. package/README.md +297 -0
  2. package/dist/index.html +83 -0
  3. package/dist/static/image/arrow.6f27b59f.png +0 -0
  4. package/dist/static/image/blocksAtlasLatest.7850afa3.png +0 -0
  5. package/dist/static/image/blocksAtlasLegacy.5c76823d.png +0 -0
  6. package/dist/static/image/itemsAtlasLatest.36036f95.png +0 -0
  7. package/dist/static/image/itemsAtlasLegacy.dcb1b58d.png +0 -0
  8. package/dist/static/image/tipped_arrow.6f27b59f.png +0 -0
  9. package/dist/static/js/365.f05233ab.js +8462 -0
  10. package/dist/static/js/365.f05233ab.js.LICENSE.txt +52 -0
  11. package/dist/static/js/async/738.efa27644.js +1 -0
  12. package/dist/static/js/index.092ec5be.js +56 -0
  13. package/dist/static/js/lib-polyfill.98986ac5.js +1 -0
  14. package/dist/static/js/lib-react.5c9129e0.js +2 -0
  15. package/dist/static/js/lib-react.5c9129e0.js.LICENSE.txt +39 -0
  16. package/package.json +104 -0
  17. package/src/assets/destroy_stage_0.png +0 -0
  18. package/src/assets/destroy_stage_1.png +0 -0
  19. package/src/assets/destroy_stage_2.png +0 -0
  20. package/src/assets/destroy_stage_3.png +0 -0
  21. package/src/assets/destroy_stage_4.png +0 -0
  22. package/src/assets/destroy_stage_5.png +0 -0
  23. package/src/assets/destroy_stage_6.png +0 -0
  24. package/src/assets/destroy_stage_7.png +0 -0
  25. package/src/assets/destroy_stage_8.png +0 -0
  26. package/src/assets/destroy_stage_9.png +0 -0
  27. package/src/examples/README.md +146 -0
  28. package/src/examples/appViewerExample.ts +205 -0
  29. package/src/examples/initialMenuStart.ts +161 -0
  30. package/src/graphicsBackend/appViewer.ts +297 -0
  31. package/src/graphicsBackend/config.ts +119 -0
  32. package/src/graphicsBackend/index.ts +10 -0
  33. package/src/graphicsBackend/playerState.ts +61 -0
  34. package/src/graphicsBackend/types.ts +143 -0
  35. package/src/index.ts +97 -0
  36. package/src/lib/DebugGui.ts +190 -0
  37. package/src/lib/animationController.ts +85 -0
  38. package/src/lib/buildSharedConfig.mjs +1 -0
  39. package/src/lib/cameraBobbing.ts +94 -0
  40. package/src/lib/canvas2DOverlay.example.ts +361 -0
  41. package/src/lib/canvas2DOverlay.quickstart.ts +242 -0
  42. package/src/lib/canvas2DOverlay.ts +381 -0
  43. package/src/lib/cleanupDecorator.ts +29 -0
  44. package/src/lib/createPlayerObject.ts +55 -0
  45. package/src/lib/frameTimingCollector.ts +164 -0
  46. package/src/lib/guiRenderer.ts +283 -0
  47. package/src/lib/items.ts +140 -0
  48. package/src/lib/mesherlogReader.ts +131 -0
  49. package/src/lib/moreBlockDataGenerated.json +714 -0
  50. package/src/lib/preflatMap.json +1741 -0
  51. package/src/lib/simpleUtils.ts +40 -0
  52. package/src/lib/smoothSwitcher.ts +168 -0
  53. package/src/lib/spiral.ts +29 -0
  54. package/src/lib/ui/newStats.ts +120 -0
  55. package/src/lib/utils/proxy.ts +23 -0
  56. package/src/lib/utils/skins.ts +63 -0
  57. package/src/lib/utils.ts +76 -0
  58. package/src/lib/workerProxy.ts +342 -0
  59. package/src/lib/worldrendererCommon.ts +1088 -0
  60. package/src/mesher/mesher.ts +253 -0
  61. package/src/mesher/models.ts +769 -0
  62. package/src/mesher/modelsGeometryCommon.ts +142 -0
  63. package/src/mesher/shared.ts +80 -0
  64. package/src/mesher/standaloneRenderer.ts +270 -0
  65. package/src/mesher/test/a.ts +3 -0
  66. package/src/mesher/test/mesherTester.ts +76 -0
  67. package/src/mesher/test/playground.ts +19 -0
  68. package/src/mesher/test/test-perf.ts +74 -0
  69. package/src/mesher/test/tests.test.ts +56 -0
  70. package/src/mesher/world.ts +294 -0
  71. package/src/mesher/worldConstants.ts +1 -0
  72. package/src/modules/index.ts +11 -0
  73. package/src/modules/starfield.ts +313 -0
  74. package/src/modules/types.ts +110 -0
  75. package/src/playerState/playerState.ts +78 -0
  76. package/src/playerState/types.ts +36 -0
  77. package/src/playground/allEntitiesDebug.ts +170 -0
  78. package/src/playground/baseScene.ts +587 -0
  79. package/src/playground/mobileControls.tsx +268 -0
  80. package/src/playground/playground.html +83 -0
  81. package/src/playground/playground.ts +11 -0
  82. package/src/playground/playgroundUi.tsx +140 -0
  83. package/src/playground/reactUtils.ts +71 -0
  84. package/src/playground/scenes/allEntities.ts +13 -0
  85. package/src/playground/scenes/entities.ts +37 -0
  86. package/src/playground/scenes/floorRandom.ts +33 -0
  87. package/src/playground/scenes/frequentUpdates.ts +148 -0
  88. package/src/playground/scenes/geometryExport.ts +142 -0
  89. package/src/playground/scenes/index.ts +12 -0
  90. package/src/playground/scenes/lightingStarfield.ts +40 -0
  91. package/src/playground/scenes/main.ts +313 -0
  92. package/src/playground/scenes/railsCobweb.ts +14 -0
  93. package/src/playground/scenes/rotationIssue.ts +7 -0
  94. package/src/playground/scenes/slabsOptimization.ts +16 -0
  95. package/src/playground/scenes/transparencyIssue.ts +11 -0
  96. package/src/playground/shared.ts +79 -0
  97. package/src/resourcesManager/index.ts +5 -0
  98. package/src/resourcesManager/resourcesManager.ts +314 -0
  99. package/src/shims/minecraftData.ts +41 -0
  100. package/src/sign-renderer/index.html +21 -0
  101. package/src/sign-renderer/index.ts +216 -0
  102. package/src/sign-renderer/noop.js +1 -0
  103. package/src/sign-renderer/playground.ts +38 -0
  104. package/src/sign-renderer/tests.test.ts +69 -0
  105. package/src/sign-renderer/vite.config.ts +10 -0
  106. package/src/three/appShared.ts +75 -0
  107. package/src/three/bannerRenderer.ts +275 -0
  108. package/src/three/cameraShake.ts +120 -0
  109. package/src/three/cinimaticScript.ts +350 -0
  110. package/src/three/documentRenderer.ts +491 -0
  111. package/src/three/entities.ts +1580 -0
  112. package/src/three/entity/EntityMesh.ts +707 -0
  113. package/src/three/entity/animations.js +171 -0
  114. package/src/three/entity/armorModels.json +204 -0
  115. package/src/three/entity/armorModels.ts +36 -0
  116. package/src/three/entity/entities.json +6230 -0
  117. package/src/three/entity/exportedModels.js +38 -0
  118. package/src/three/entity/externalTextures.json +1 -0
  119. package/src/three/entity/models/allay.obj +325 -0
  120. package/src/three/entity/models/arrow.obj +60 -0
  121. package/src/three/entity/models/axolotl.obj +509 -0
  122. package/src/three/entity/models/blaze.obj +601 -0
  123. package/src/three/entity/models/boat.obj +417 -0
  124. package/src/three/entity/models/camel.obj +1061 -0
  125. package/src/three/entity/models/cat.obj +509 -0
  126. package/src/three/entity/models/chicken.obj +371 -0
  127. package/src/three/entity/models/cod.obj +371 -0
  128. package/src/three/entity/models/creeper.obj +279 -0
  129. package/src/three/entity/models/dolphin.obj +371 -0
  130. package/src/three/entity/models/ender_dragon.obj +2993 -0
  131. package/src/three/entity/models/enderman.obj +325 -0
  132. package/src/three/entity/models/endermite.obj +187 -0
  133. package/src/three/entity/models/fox.obj +463 -0
  134. package/src/three/entity/models/frog.obj +739 -0
  135. package/src/three/entity/models/ghast.obj +463 -0
  136. package/src/three/entity/models/goat.obj +601 -0
  137. package/src/three/entity/models/guardian.obj +1015 -0
  138. package/src/three/entity/models/horse.obj +1061 -0
  139. package/src/three/entity/models/llama.obj +509 -0
  140. package/src/three/entity/models/minecart.obj +233 -0
  141. package/src/three/entity/models/parrot.obj +509 -0
  142. package/src/three/entity/models/piglin.obj +739 -0
  143. package/src/three/entity/models/pillager.obj +371 -0
  144. package/src/three/entity/models/rabbit.obj +555 -0
  145. package/src/three/entity/models/sheep.obj +555 -0
  146. package/src/three/entity/models/shulker.obj +141 -0
  147. package/src/three/entity/models/sniffer.obj +693 -0
  148. package/src/three/entity/models/spider.obj +509 -0
  149. package/src/three/entity/models/tadpole.obj +95 -0
  150. package/src/three/entity/models/turtle.obj +371 -0
  151. package/src/three/entity/models/vex.obj +325 -0
  152. package/src/three/entity/models/villager.obj +509 -0
  153. package/src/three/entity/models/warden.obj +463 -0
  154. package/src/three/entity/models/witch.obj +647 -0
  155. package/src/three/entity/models/wolf.obj +509 -0
  156. package/src/three/entity/models/zombie_villager.obj +463 -0
  157. package/src/three/entity/objModels.js +1 -0
  158. package/src/three/fireworks.ts +661 -0
  159. package/src/three/fireworksRenderer.ts +434 -0
  160. package/src/three/globals.d.ts +7 -0
  161. package/src/three/graphicsBackend.ts +274 -0
  162. package/src/three/graphicsBackendOffThread.ts +107 -0
  163. package/src/three/hand.ts +89 -0
  164. package/src/three/holdingBlock.ts +926 -0
  165. package/src/three/index.ts +20 -0
  166. package/src/three/itemMesh.ts +427 -0
  167. package/src/three/modules.d.ts +14 -0
  168. package/src/three/panorama.ts +308 -0
  169. package/src/three/panoramaShared.ts +1 -0
  170. package/src/three/renderSlot.ts +82 -0
  171. package/src/three/skyboxRenderer.ts +406 -0
  172. package/src/three/starField.ts +13 -0
  173. package/src/three/threeJsMedia.ts +731 -0
  174. package/src/three/threeJsMethods.ts +15 -0
  175. package/src/three/threeJsParticles.ts +160 -0
  176. package/src/three/threeJsSound.ts +95 -0
  177. package/src/three/threeJsUtils.ts +90 -0
  178. package/src/three/waypointSprite.ts +435 -0
  179. package/src/three/waypoints.ts +163 -0
  180. package/src/three/world/cursorBlock.ts +172 -0
  181. package/src/three/world/vr.ts +257 -0
  182. package/src/three/worldGeometryExport.ts +259 -0
  183. package/src/three/worldGeometryHandler.ts +279 -0
  184. package/src/three/worldRendererThree.ts +1381 -0
  185. package/src/worldView/index.ts +6 -0
  186. package/src/worldView/types.ts +66 -0
  187. package/src/worldView/worldView.ts +424 -0
@@ -0,0 +1,40 @@
1
+ export async function getBufferFromStream (stream) {
2
+ return new Promise((resolve, reject) => {
3
+ let buffer = Buffer.from([])
4
+ stream.on('data', buf => {
5
+ buffer = Buffer.concat([buffer, buf])
6
+ })
7
+ stream.on('end', () => resolve(buffer))
8
+ stream.on('error', reject)
9
+ })
10
+ }
11
+
12
+ // used for custom blocks
13
+ export function getBlockPosKey (pos: { x: number, y: number, z: number }) {
14
+ return `${pos.x},${pos.y},${pos.z}`
15
+ }
16
+
17
+ export function openURL (url, newTab = true) {
18
+ if (newTab) {
19
+ window.open(url, '_blank', 'noopener,noreferrer')
20
+ } else {
21
+ window.open(url, '_self')
22
+ }
23
+ }
24
+
25
+ export const isMobile = () => {
26
+ return window.matchMedia('(pointer: coarse)').matches || navigator.userAgent.includes('Mobile')
27
+ }
28
+
29
+ export function chunkPos (pos: { x: number, z: number }) {
30
+ const x = Math.floor(pos.x / 16)
31
+ const z = Math.floor(pos.z / 16)
32
+ return [x, z]
33
+ }
34
+
35
+ export function sectionPos (pos: { x: number, y: number, z: number }) {
36
+ const x = Math.floor(pos.x / 16)
37
+ const y = Math.floor(pos.y / 16)
38
+ const z = Math.floor(pos.z / 16)
39
+ return [x, y, z]
40
+ }
@@ -0,0 +1,168 @@
1
+ import * as tweenJs from '@tweenjs/tween.js'
2
+ import { AnimationController } from './animationController'
3
+
4
+ export type StateProperties = Record<string, number>
5
+ export type StateGetterFn = () => StateProperties
6
+ export type StateSetterFn = (property: string, value: number) => void
7
+
8
+ // Speed in units per second for each property type
9
+ const DEFAULT_SPEEDS = {
10
+ x: 3000, // pixels/units per second
11
+ y: 3000,
12
+ z: 3000,
13
+ rotation: Math.PI, // radians per second
14
+ scale: 1, // scale units per second
15
+ default: 3000 // default speed for unknown properties
16
+ }
17
+
18
+ export class SmoothSwitcher {
19
+ private readonly animationController = new AnimationController()
20
+ // private readonly currentState: StateProperties = {}
21
+ private readonly defaultState: StateProperties
22
+ private readonly speeds: Record<string, number>
23
+ public currentStateName = ''
24
+ public transitioningToStateName = ''
25
+
26
+ constructor (
27
+ public getState: StateGetterFn,
28
+ public setState: StateSetterFn,
29
+ speeds?: Partial<Record<string, number>>
30
+ ) {
31
+
32
+ // Initialize speeds with defaults and overrides
33
+ this.speeds = { ...DEFAULT_SPEEDS }
34
+ if (speeds) {
35
+ Object.assign(this.speeds, speeds)
36
+ }
37
+
38
+ // Store initial values
39
+ this.defaultState = this.getState()
40
+ }
41
+
42
+ /**
43
+ * Calculate transition duration based on the largest property change
44
+ */
45
+ private calculateDuration (newState: Partial<StateProperties>): number {
46
+ let maxDuration = 0
47
+ const currentState = this.getState()
48
+
49
+ for (const [key, targetValue] of Object.entries(newState)) {
50
+ const currentValue = currentState[key]
51
+ const diff = Math.abs(targetValue! - currentValue)
52
+ const speed = this.getPropertySpeed(key)
53
+ const duration = (diff / speed) * 1000 // Convert to milliseconds
54
+
55
+ maxDuration = Math.max(maxDuration, duration)
56
+ }
57
+
58
+ // Ensure minimum duration of 50ms and maximum of 2000ms
59
+ return Math.min(Math.max(maxDuration, 200), 2000)
60
+ }
61
+
62
+ private getPropertySpeed (property: string): number {
63
+ // Check for specific property speed
64
+ if (property in this.speeds) {
65
+ return this.speeds[property]
66
+ }
67
+
68
+ // Check for property type (rotation, scale, etc.)
69
+ if (property.toLowerCase().includes('rotation')) return this.speeds.rotation
70
+ if (property.toLowerCase().includes('scale')) return this.speeds.scale
71
+ if (property.toLowerCase() === 'x' || property.toLowerCase() === 'y' || property.toLowerCase() === 'z') {
72
+ return this.speeds[property]
73
+ }
74
+
75
+ return this.speeds.default
76
+ }
77
+
78
+ /**
79
+ * Start a transition to a new state
80
+ * @param newState Partial state - only need to specify properties that change
81
+ * @param easing Easing function to use
82
+ */
83
+ startTransition (
84
+ newState: Partial<StateProperties>,
85
+ stateName?: string,
86
+ onEnd?: () => void,
87
+ easing: (amount: number) => number = tweenJs.Easing.Linear.None,
88
+ onCancelled?: () => void
89
+ ): void {
90
+ if (this.isTransitioning) {
91
+ this.animationController.forceFinish(false)
92
+ }
93
+
94
+ this.transitioningToStateName = stateName ?? ''
95
+ const state = this.getState()
96
+
97
+ const duration = this.calculateDuration(newState)
98
+ // console.log('duration', duration, JSON.stringify(state), JSON.stringify(newState))
99
+
100
+ void this.animationController.startAnimation(() => {
101
+ const group = new tweenJs.Group()
102
+ new tweenJs.Tween(state, group)
103
+ .to(newState, duration)
104
+ .easing(easing)
105
+ .onUpdate((obj) => {
106
+ for (const key of Object.keys(obj)) {
107
+ this.setState(key, obj[key])
108
+ }
109
+ })
110
+ .onComplete(() => {
111
+ this.animationController.forceFinish()
112
+ this.currentStateName = this.transitioningToStateName
113
+ this.transitioningToStateName = ''
114
+ onEnd?.()
115
+ })
116
+ .start()
117
+ return group
118
+ }, onCancelled)
119
+ }
120
+
121
+ /**
122
+ * Reset to default state
123
+ */
124
+ reset (): void {
125
+ this.startTransition(this.defaultState)
126
+ }
127
+
128
+
129
+ /**
130
+ * Update the animation (should be called in your render/update loop)
131
+ */
132
+ update (): void {
133
+ this.animationController.update()
134
+ }
135
+
136
+ /**
137
+ * Force finish the current transition
138
+ */
139
+ forceFinish (): void {
140
+ this.animationController.forceFinish()
141
+ }
142
+
143
+ /**
144
+ * Start a new transition to the specified state
145
+ */
146
+ transitionTo (
147
+ newState: Partial<StateProperties>,
148
+ stateName?: string,
149
+ onEnd?: () => void,
150
+ onCancelled?: () => void
151
+ ): void {
152
+ this.startTransition(newState, stateName, onEnd, tweenJs.Easing.Linear.None, onCancelled)
153
+ }
154
+
155
+ /**
156
+ * Get the current value of a property
157
+ */
158
+ getCurrentValue (property: string): number {
159
+ return this.getState()[property]
160
+ }
161
+
162
+ /**
163
+ * Check if currently transitioning
164
+ */
165
+ get isTransitioning (): boolean {
166
+ return this.animationController.isActive
167
+ }
168
+ }
@@ -0,0 +1,29 @@
1
+ export function generateSpiralMatrix(distance: number): [number, number][] {
2
+ const n = distance * 2 + 1
3
+ if (n <= 0) {
4
+ return []
5
+ }
6
+
7
+ const matrix: [number, number][] = []
8
+ let x = 0
9
+ let y = 0
10
+ let dx = 0
11
+ let dy = -1
12
+
13
+ for (let i = 0; i < n * n; i++) {
14
+ if (Math.abs(x) <= n / 2 && Math.abs(y) <= n / 2) {
15
+ matrix.push([x, y])
16
+ }
17
+
18
+ if (x === y || (x < 0 && x === -y) || (x > 0 && x === 1 - y)) {
19
+ const temp = dx
20
+ dx = -dy
21
+ dy = temp
22
+ }
23
+
24
+ x += dx
25
+ y += dy
26
+ }
27
+
28
+ return matrix
29
+ }
@@ -0,0 +1,120 @@
1
+ /* eslint-disable unicorn/prefer-dom-node-text-content */
2
+ import { isWebWorker } from '../../three/documentRenderer'
3
+
4
+ const rightOffset = 0
5
+
6
+ const stats = {}
7
+
8
+ let lastY = 40
9
+ export const addNewStat = (id: string, width = 80, x = rightOffset, y = lastY) => {
10
+ if (isWebWorker) return { updateText () {}, setVisibility () {} }
11
+
12
+ const pane = document.createElement('div')
13
+ pane.style.position = 'fixed'
14
+ pane.style.top = `${y ?? lastY}px`
15
+ pane.style.right = `${x}px`
16
+ // gray bg
17
+ pane.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'
18
+ pane.style.color = 'white'
19
+ pane.style.padding = '2px'
20
+ pane.style.fontFamily = 'monospace'
21
+ pane.style.fontSize = '12px'
22
+ pane.style.zIndex = '100'
23
+ pane.style.pointerEvents = 'none'
24
+ document.body.appendChild(pane)
25
+ stats[id] = pane
26
+ if (y === undefined && x === rightOffset) { // otherwise it's a custom position
27
+ // rightOffset += width
28
+ lastY += 20
29
+ }
30
+
31
+ return {
32
+ updateText (text: string) {
33
+ if (pane.innerText === text) return
34
+ pane.innerText = text
35
+ },
36
+ setVisibility (visible: boolean) {
37
+ pane.style.display = visible ? 'block' : 'none'
38
+ }
39
+ }
40
+ }
41
+
42
+ export const addNewStat2 = (id: string, { top, bottom, right, left, displayOnlyWhenWider }: { top?: number, bottom?: number, right?: number, left?: number, displayOnlyWhenWider?: number }) => {
43
+ if (isWebWorker) return { updateText () {}, setVisibility () {} }
44
+
45
+ if (top === undefined && bottom === undefined) top = 0
46
+ const pane = document.createElement('div')
47
+ pane.style.position = 'fixed'
48
+ if (top !== undefined) {
49
+ pane.style.top = `${top}px`
50
+ }
51
+ if (bottom !== undefined) {
52
+ pane.style.bottom = `${bottom}px`
53
+ }
54
+ if (left !== undefined) {
55
+ pane.style.left = `${left}px`
56
+ }
57
+ if (right !== undefined) {
58
+ pane.style.right = `${right}px`
59
+ }
60
+ // gray bg
61
+ pane.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'
62
+ pane.style.color = 'white'
63
+ pane.style.padding = '2px'
64
+ pane.style.fontFamily = 'monospace'
65
+ pane.style.fontSize = '12px'
66
+ pane.style.zIndex = '10000'
67
+ pane.style.pointerEvents = 'none'
68
+ document.body.appendChild(pane)
69
+ stats[id] = pane
70
+
71
+ const resizeCheck = () => {
72
+ if (!displayOnlyWhenWider) return
73
+ pane.style.display = window.innerWidth > displayOnlyWhenWider ? 'block' : 'none'
74
+ }
75
+ window.addEventListener('resize', resizeCheck)
76
+ resizeCheck()
77
+
78
+ return {
79
+ updateText (text: string) {
80
+ pane.innerText = text
81
+ },
82
+ setVisibility (visible: boolean) {
83
+ pane.style.display = visible ? 'block' : 'none'
84
+ }
85
+ }
86
+ }
87
+
88
+ export const updateStatText = (id, text) => {
89
+ if (isWebWorker || !stats[id]) return
90
+ stats[id].innerText = text
91
+ }
92
+
93
+ export const updatePanesVisibility = (visible: boolean) => {
94
+ if (isWebWorker) return
95
+ // eslint-disable-next-line guard-for-in
96
+ for (const id in stats) {
97
+ stats[id].style.display = visible ? 'block' : 'none'
98
+ }
99
+ }
100
+
101
+ export const removeAllStats = () => {
102
+ if (isWebWorker) return
103
+ // eslint-disable-next-line guard-for-in
104
+ for (const id in stats) {
105
+ removeStat(id)
106
+ }
107
+ }
108
+
109
+ export const removeStat = (id) => {
110
+ if (isWebWorker || !stats[id]) return
111
+ stats[id].remove()
112
+ delete stats[id]
113
+ }
114
+
115
+ if (typeof customEvents !== 'undefined' && !isWebWorker) {
116
+ customEvents.on('gameLoaded', () => {
117
+ const chunksLoaded = addNewStat('chunks-loaded', 80, 0, 0)
118
+ const chunksTotal = addNewStat('chunks-read', 80, 0, 0)
119
+ })
120
+ }
@@ -0,0 +1,23 @@
1
+ import { subscribeKey } from 'valtio/utils'
2
+
3
+ // eslint-disable-next-line max-params
4
+ export function watchProperty<T extends Record<string, any>, K> (asyncGetter: (value: T[keyof T]) => Promise<K>, valtioProxy: T, key: keyof T, readySetter: (res: K) => void, cleanup?: (res: K) => void) {
5
+ let i = 0
6
+ let lastRes: K | undefined
7
+ const request = async () => {
8
+ const req = ++i
9
+ const res = await asyncGetter(valtioProxy[key])
10
+ if (req === i) {
11
+ if (lastRes) {
12
+ cleanup?.(lastRes)
13
+ }
14
+ readySetter(res)
15
+ lastRes = res
16
+ } else {
17
+ // rejected
18
+ cleanup?.(res)
19
+ }
20
+ }
21
+ void request()
22
+ return subscribeKey(valtioProxy, key, request)
23
+ }
@@ -0,0 +1,63 @@
1
+ import { loadSkinToCanvas } from 'skinview-utils'
2
+ import * as THREE from 'three'
3
+ import stevePng from 'mc-assets/dist/other-textures/latest/entity/player/wide/steve.png'
4
+ import { loadThreeJsTextureFromUrl } from '../../three/threeJsUtils'
5
+ import { createCanvas, loadImageFromUrl } from '../utils'
6
+
7
+ export const stevePngUrl = stevePng
8
+ export const steveTexture = loadThreeJsTextureFromUrl(stevePngUrl)
9
+
10
+ const config = {
11
+ apiEnabled: true,
12
+ }
13
+
14
+ export const setSkinsConfig = (newConfig: Partial<typeof config>) => {
15
+ Object.assign(config, newConfig)
16
+ }
17
+
18
+ export async function loadSkinFromUsername (username: string, type: 'skin' | 'cape'): Promise<string | undefined> {
19
+ if (!config.apiEnabled) return
20
+
21
+ if (type === 'cape') return
22
+ const url = `https://playerdb.co/api/player/minecraft/${username}`
23
+ const response = await fetch(url)
24
+ if (!response.ok) return
25
+
26
+ const data: {
27
+ data: {
28
+ player: {
29
+ skin_texture: string
30
+ }
31
+ }
32
+ } = await response.json()
33
+ return data.data.player.skin_texture
34
+ }
35
+
36
+ export const parseSkinTexturesValue = (value: string) => {
37
+ const decodedData: {
38
+ textures: {
39
+ SKIN: {
40
+ url: string
41
+ }
42
+ }
43
+ } = JSON.parse(Buffer.from(value, 'base64').toString())
44
+ return decodedData.textures?.SKIN?.url
45
+ }
46
+
47
+ export async function loadSkinImage (skinUrl: string): Promise<{ canvas: OffscreenCanvas, image: ImageBitmap }> {
48
+ if (!skinUrl.startsWith('data:')) {
49
+ skinUrl = await fetchAndConvertBase64Skin(skinUrl.replace('http://', 'https://'))
50
+ }
51
+
52
+ const image = await loadImageFromUrl(skinUrl)
53
+ const skinCanvas = createCanvas(64, 64)
54
+ loadSkinToCanvas(skinCanvas, image)
55
+ return { canvas: skinCanvas, image }
56
+ }
57
+
58
+ const fetchAndConvertBase64Skin = async (skinUrl: string) => {
59
+ const response = await fetch(skinUrl, { })
60
+ const arrayBuffer = await response.arrayBuffer()
61
+ const base64 = Buffer.from(arrayBuffer).toString('base64')
62
+ return `data:image/png;base64,${base64}`
63
+ }
@@ -0,0 +1,76 @@
1
+ export const loadScript = async function (scriptSrc: string, highPriority = true): Promise<HTMLScriptElement> {
2
+ const existingScript = document.querySelector<HTMLScriptElement>(`script[src="${scriptSrc}"]`)
3
+ if (existingScript) {
4
+ return existingScript
5
+ }
6
+
7
+ return new Promise((resolve, reject) => {
8
+ const scriptElement = document.createElement('script')
9
+ scriptElement.src = scriptSrc
10
+
11
+ if (highPriority) {
12
+ scriptElement.fetchPriority = 'high'
13
+ }
14
+ scriptElement.async = true
15
+
16
+ scriptElement.addEventListener('load', () => {
17
+ resolve(scriptElement)
18
+ })
19
+
20
+ scriptElement.onerror = (error) => {
21
+ reject(new Error(typeof error === 'string' ? error : (error as any).message))
22
+ scriptElement.remove()
23
+ }
24
+
25
+ document.head.appendChild(scriptElement)
26
+ })
27
+ }
28
+
29
+ const detectFullOffscreenCanvasSupport = () => {
30
+ if (typeof OffscreenCanvas === 'undefined') return false
31
+ try {
32
+ const canvas = new OffscreenCanvas(1, 1)
33
+ // Try to get a WebGL context - this will fail on iOS where only 2D is supported (iOS 16)
34
+ const gl = canvas.getContext('webgl2') || canvas.getContext('webgl')
35
+ return gl !== null
36
+ } catch (e) {
37
+ return false
38
+ }
39
+ }
40
+
41
+ const hasFullOffscreenCanvasSupport = detectFullOffscreenCanvasSupport()
42
+
43
+ export const createCanvas = (width: number, height: number): OffscreenCanvas => {
44
+ if (hasFullOffscreenCanvasSupport) {
45
+ return new OffscreenCanvas(width, height)
46
+ }
47
+ const canvas = document.createElement('canvas')
48
+ canvas.width = width
49
+ canvas.height = height
50
+ return canvas as unknown as OffscreenCanvas // todo-low
51
+ }
52
+
53
+ export async function loadImageFromUrl(imageUrl: string): Promise<ImageBitmap> {
54
+ const response = await fetch(imageUrl)
55
+ const blob = await response.blob()
56
+ return createImageBitmap(blob)
57
+ }
58
+
59
+ export const versionToNumber = (ver: string) => {
60
+ const [x, y = '0', z = '0'] = ver.split('.')
61
+ return +`${x.padStart(2, '0')}${y.padStart(2, '0')}${z.padStart(2, '0')}`
62
+ }
63
+
64
+ export const versionToMajor = (version: string) => {
65
+ const [x, y = '0'] = version.split('.')
66
+ return `${x.padStart(2, '0')}.${y.padStart(2, '0')}`
67
+ }
68
+
69
+ export const versionsMapToMajor = <T>(versionsMap: Record<string, T>) => {
70
+ const majorVersions = {} as Record<string, T>
71
+ for (const [ver, data] of Object.entries(versionsMap)) {
72
+ const major = versionToMajor(ver)
73
+ majorVersions[major] = data
74
+ }
75
+ return majorVersions
76
+ }