fix(viewer): guard texture.repeat against non-array values (Sentry MONOREPO-EDITOR-EK) (#504)
* fix(viewer): guard texture.repeat against non-array values (Sentry MONOREPO-EDITOR-EK) * fix(viewer): normalize legacy texture repeats --------- Co-authored-by: openclaw-agent <agent@pascal.app> Co-authored-by: Aymeric Rabot <aymeric.rabot@gmail.com>
This commit is contained in:
co-authored by
openclaw-agent
Aymeric Rabot
parent
d0ac9fb739
commit
16ea186f77
@@ -0,0 +1,32 @@
|
||||
// @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
|
||||
// depend on @types/bun so the import type is unresolved at compile time.
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import type { MaterialSchema } from '@pascal-app/core'
|
||||
import { getTextureKey, resolveTextureRepeat } from './materials'
|
||||
|
||||
function materialWithRepeat(repeat: unknown): MaterialSchema {
|
||||
return {
|
||||
texture: {
|
||||
url: 'https://example.com/texture.png',
|
||||
repeat,
|
||||
},
|
||||
} as unknown as MaterialSchema
|
||||
}
|
||||
|
||||
describe('legacy texture repeat values', () => {
|
||||
test('normalizes tuple, scalar, and Vector2-shaped repeats', () => {
|
||||
expect(resolveTextureRepeat([2, 3], undefined)).toEqual([2, 3])
|
||||
expect(resolveTextureRepeat(2, undefined)).toEqual([2, 2])
|
||||
expect(resolveTextureRepeat({ x: 2, y: 3 }, undefined)).toEqual([2, 3])
|
||||
})
|
||||
|
||||
test('falls back to scale for malformed repeats', () => {
|
||||
expect(resolveTextureRepeat({ width: 2 }, 4)).toEqual([4, 4])
|
||||
})
|
||||
|
||||
test('keeps distinct Vector2-shaped repeats in distinct cache entries', () => {
|
||||
expect(getTextureKey(materialWithRepeat({ x: 2, y: 3 }))).not.toBe(
|
||||
getTextureKey(materialWithRepeat({ x: 4, y: 5 })),
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -180,12 +180,28 @@ function getCacheKey(props: MaterialProperties, shading: RenderShading): string
|
||||
return `${shading}-${props.color}-${props.roughness}-${props.metalness}-${props.opacity}-${props.transparent}-${props.side}`
|
||||
}
|
||||
|
||||
function getTextureKey(material?: MaterialSchema): string {
|
||||
function isFiniteNumber(value: unknown): value is number {
|
||||
return typeof value === 'number' && Number.isFinite(value)
|
||||
}
|
||||
|
||||
export function resolveTextureRepeat(repeat: unknown, scale: unknown): [number, number] {
|
||||
const fallback = isFiniteNumber(scale) ? scale : 1
|
||||
if (Array.isArray(repeat) && isFiniteNumber(repeat[0]) && isFiniteNumber(repeat[1])) {
|
||||
return [repeat[0], repeat[1]]
|
||||
}
|
||||
if (isFiniteNumber(repeat)) return [repeat, repeat]
|
||||
if (repeat && typeof repeat === 'object' && 'x' in repeat && 'y' in repeat) {
|
||||
const { x, y } = repeat
|
||||
if (isFiniteNumber(x) && isFiniteNumber(y)) return [x, y]
|
||||
}
|
||||
return [fallback, fallback]
|
||||
}
|
||||
|
||||
export function getTextureKey(material?: MaterialSchema): string {
|
||||
const texture = material?.texture
|
||||
if (!texture) return 'none'
|
||||
const repeat = texture.repeat?.join('x') ?? 'default'
|
||||
const scale = texture.scale ?? 'default'
|
||||
return `${texture.url}-${repeat}-${scale}`
|
||||
const [repeatX, repeatY] = resolveTextureRepeat(texture.repeat, texture.scale)
|
||||
return `${texture.url}-${repeatX}x${repeatY}`
|
||||
}
|
||||
|
||||
function getTexture(material?: MaterialSchema): THREE.Texture | undefined {
|
||||
@@ -200,8 +216,7 @@ function getTexture(material?: MaterialSchema): THREE.Texture | undefined {
|
||||
texture.wrapS = THREE.RepeatWrapping
|
||||
texture.wrapT = THREE.RepeatWrapping
|
||||
|
||||
const repeatX = textureConfig.repeat?.[0] ?? textureConfig.scale ?? 1
|
||||
const repeatY = textureConfig.repeat?.[1] ?? textureConfig.scale ?? 1
|
||||
const [repeatX, repeatY] = resolveTextureRepeat(textureConfig.repeat, textureConfig.scale)
|
||||
texture.repeat.set(repeatX, repeatY)
|
||||
texture.updateMatrix()
|
||||
texture.colorSpace = THREE.SRGBColorSpace
|
||||
|
||||
Reference in New Issue
Block a user