minecraft-renderer 0.1.97 → 0.1.99

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecraft-renderer",
3
- "version": "0.1.97",
3
+ "version": "0.1.99",
4
4
  "description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -145,11 +145,11 @@ function shortestYawRadians(fromYawRad: number, toYawRad: number): number {
145
145
  return norm > Math.PI ? norm - TAU_YAW : norm
146
146
  }
147
147
 
148
- /** Voice chat presence for a player, drawn as a small mic glyph before their nametag. */
148
+ /** Voice chat presence for a player, drawn as a small speaker glyph before their nametag. */
149
149
  export type PlayerVoiceState = 'speaking' | 'muted' | 'idle'
150
150
 
151
151
  /**
152
- * Draws a small mic glyph centered at the origin of the current transform.
152
+ * Draws a small speaker glyph centered at the origin of the current transform.
153
153
  * `size` is the glyph's bounding box (both width and height).
154
154
  */
155
155
  function drawVoiceStateIcon(ctx: OffscreenCanvasRenderingContext2D, state: PlayerVoiceState, cx: number, cy: number, size: number) {
@@ -157,46 +157,42 @@ function drawVoiceStateIcon(ctx: OffscreenCanvasRenderingContext2D, state: Playe
157
157
 
158
158
  ctx.save()
159
159
  ctx.translate(cx, cy)
160
-
161
- if (state === 'speaking') {
162
- ctx.beginPath()
163
- ctx.arc(0, 0, size * 0.62, 0, Math.PI * 2)
164
- ctx.strokeStyle = 'rgba(61, 220, 71, 0.55)'
165
- ctx.lineWidth = Math.max(1, size * 0.08)
166
- ctx.stroke()
167
- }
168
-
169
160
  ctx.fillStyle = color
170
161
  ctx.strokeStyle = color
171
- ctx.lineWidth = Math.max(1, size * 0.12)
162
+ ctx.lineWidth = Math.max(1.5, size * 0.09)
163
+ ctx.lineCap = 'round'
164
+
165
+ // Speaker silhouette: a small box with a cone opening to the right
166
+ const boxWidth = size * 0.22
167
+ const boxHeight = size * 0.34
168
+ const coneWidth = size * 0.24
169
+ const left = -size * 0.44
172
170
 
173
- // Mic body: a rounded capsule
174
- const bodyWidth = size * 0.34
175
- const bodyHeight = size * 0.55
176
- const bodyRadius = bodyWidth / 2
177
171
  ctx.beginPath()
178
- ctx.moveTo(-bodyRadius, -bodyHeight / 2 + bodyRadius)
179
- ctx.arcTo(-bodyRadius, -bodyHeight / 2, 0, -bodyHeight / 2, bodyRadius)
180
- ctx.arcTo(bodyRadius, -bodyHeight / 2, bodyRadius, -bodyHeight / 2 + bodyRadius, bodyRadius)
181
- ctx.lineTo(bodyRadius, bodyHeight / 2 - bodyRadius)
182
- ctx.arcTo(bodyRadius, bodyHeight / 2, 0, bodyHeight / 2, bodyRadius)
183
- ctx.arcTo(-bodyRadius, bodyHeight / 2, -bodyRadius, bodyHeight / 2 - bodyRadius, bodyRadius)
172
+ ctx.moveTo(left, -boxHeight / 2)
173
+ ctx.lineTo(left + boxWidth, -boxHeight / 2)
174
+ ctx.lineTo(left + boxWidth + coneWidth, -size * 0.5)
175
+ ctx.lineTo(left + boxWidth + coneWidth, size * 0.5)
176
+ ctx.lineTo(left + boxWidth, boxHeight / 2)
177
+ ctx.lineTo(left, boxHeight / 2)
184
178
  ctx.closePath()
185
179
  ctx.fill()
186
180
 
187
- // Stand: arc + base line
188
- ctx.beginPath()
189
- ctx.arc(0, bodyHeight / 2 - bodyRadius / 2, size * 0.32, Math.PI * 0.15, Math.PI * 0.85, false)
190
- ctx.stroke()
191
- ctx.beginPath()
192
- ctx.moveTo(0, size * 0.5)
193
- ctx.lineTo(0, size * 0.62)
194
- ctx.stroke()
181
+ const coneTip = left + boxWidth + coneWidth
182
+
183
+ if (state === 'speaking') {
184
+ ctx.beginPath()
185
+ ctx.arc(coneTip + size * 0.06, 0, size * 0.16, -Math.PI * 0.32, Math.PI * 0.32)
186
+ ctx.stroke()
187
+ ctx.beginPath()
188
+ ctx.arc(coneTip + size * 0.06, 0, size * 0.28, -Math.PI * 0.28, Math.PI * 0.28)
189
+ ctx.stroke()
190
+ }
195
191
 
196
192
  if (state === 'muted') {
197
193
  ctx.beginPath()
198
- ctx.moveTo(-size * 0.42, -size * 0.42)
199
- ctx.lineTo(size * 0.42, size * 0.42)
194
+ ctx.moveTo(-size * 0.46, -size * 0.46)
195
+ ctx.lineTo(size * 0.46, size * 0.46)
200
196
  ctx.stroke()
201
197
  }
202
198
 
@@ -227,33 +223,38 @@ function getUsernameTexture(
227
223
 
228
224
  const height = (fontSize + padding) * plainLines.length
229
225
 
230
- // Render the text on its own canvas first so the existing centered-text
231
- // layout in renderComponent() is unaffected by the icon we add alongside it.
226
+ // Render the text onto its own transparent canvas so the existing
227
+ // centered-text layout in renderComponent() is unaffected by compositing it
228
+ // alongside the icon. The background plate is painted exactly once, on the
229
+ // final canvas below — painting it here too would double-blend the alpha
230
+ // under the text relative to the icon area, producing a visible seam.
232
231
  const textCanvas = createCanvas(textWidth, height)
233
232
  const textCtx = textCanvas.getContext('2d')
234
233
  if (!textCtx) throw new Error('Could not get 2d context')
235
234
 
236
- textCtx.fillStyle = nameTagBackgroundColor
237
- textCtx.fillRect(0, 0, textCanvas.width, textCanvas.height)
238
235
  textCtx.globalAlpha = nameTagTextOpacity / 255
239
-
240
236
  const textRendered = renderComponent(username, PrismarineChat, textCanvas, fontSize, 'white', -padding + fontSize)
241
237
  if (!textRendered) return undefined
242
-
243
238
  textCtx.globalAlpha = 1
244
239
 
245
- if (!voiceState) return textCanvas
240
+ const iconSize = fontSize * 0.85
241
+ const iconAreaWidth = voiceState ? iconSize + padding * 2.5 : 0
246
242
 
247
- const iconSize = fontSize * 0.55
248
- const iconAreaWidth = iconSize + padding * 2
249
-
250
- const canvas = createCanvas(iconAreaWidth + textWidth, height)
243
+ // The nametag sprite is anchored (and thus centered above the player) at the
244
+ // midpoint of this canvas. Mirroring the icon's width as a second, unpainted
245
+ // margin on the right moves that midpoint to align with the *text's* own
246
+ // center instead of the icon+text bounding box's center — otherwise the
247
+ // icon's extra width on the left alone drags the whole sprite's anchor left,
248
+ // which visually reads as the text drifting right of the player.
249
+ const canvas = createCanvas(iconAreaWidth * 2 + textWidth, height)
251
250
  const ctx = canvas.getContext('2d')
252
251
  if (!ctx) throw new Error('Could not get 2d context')
253
252
 
254
253
  ctx.fillStyle = nameTagBackgroundColor
255
- ctx.fillRect(0, 0, canvas.width, canvas.height)
256
- drawVoiceStateIcon(ctx, voiceState, iconAreaWidth / 2, canvas.height / 2, iconSize)
254
+ ctx.fillRect(0, 0, iconAreaWidth + textWidth, canvas.height)
255
+ if (voiceState) {
256
+ drawVoiceStateIcon(ctx, voiceState, iconAreaWidth / 2, canvas.height / 2, iconSize)
257
+ }
257
258
  ctx.drawImage(textCanvas, iconAreaWidth, 0)
258
259
 
259
260
  return canvas