Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

124 lignes
5.4 KiB

  1. import assert from 'node:assert/strict'
  2. import { readFile } from 'node:fs/promises'
  3. import { createRequire } from 'node:module'
  4. import { pathToFileURL } from 'node:url'
  5. import test from 'node:test'
  6. import ts from 'typescript'
  7. const requireWeb = createRequire(new URL('../../unreal_tran_web/package.json', import.meta.url))
  8. const threeUrl = pathToFileURL(requireWeb.resolve('three')).href
  9. const THREE = await import(threeUrl)
  10. const source = await readFile(new URL('../../unreal_tran_web/src/features/scene-legacy/modelPlacement.ts', import.meta.url), 'utf8')
  11. const output = ts.transpileModule(source, {
  12. compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 },
  13. reportDiagnostics: true,
  14. })
  15. assert.deepEqual(output.diagnostics, [])
  16. const compiled = output.outputText.replace(/from 'three'/g, `from '${threeUrl}'`)
  17. const { fitSceneModel, refreshSceneModelBounds, parseSceneScale, formatSceneScale } = await import(
  18. `data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`,
  19. )
  20. function close(actual, expected, tolerance = 1e-8) {
  21. assert.ok(Math.abs(actual - expected) <= tolerance, `${actual} != ${expected}`)
  22. }
  23. function skinFixture() {
  24. const root = new THREE.Group()
  25. const bone = new THREE.Bone()
  26. const geometry = new THREE.BoxGeometry(2, 4, 10)
  27. geometry.translate(0, 2, 0)
  28. const count = geometry.getAttribute('position').count
  29. const indices = new Uint16Array(count * 4)
  30. const weights = new Float32Array(count * 4)
  31. for (let i = 0; i < count; i += 1) weights[i * 4] = 1
  32. geometry.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(indices, 4))
  33. geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(weights, 4))
  34. const mesh = new THREE.SkinnedMesh(geometry, new THREE.MeshBasicMaterial())
  35. root.add(bone, mesh)
  36. root.updateMatrixWorld(true)
  37. mesh.bind(new THREE.Skeleton([bone]))
  38. // The published model has already been normalized and may be saved at an
  39. // animation pose. This is the same stale matrix/cache state as loaded GLTF.
  40. root.scale.setScalar(0.1)
  41. root.position.set(1, 2, 3)
  42. bone.position.y = 5
  43. return { root, bone, mesh }
  44. }
  45. test('fresh skinned placement reaches the requested size and floor after authored scale/pose replay', () => {
  46. const { root, mesh } = skinFixture()
  47. // Populate the wrong cached skin box just as the previous import path did.
  48. const stale = new THREE.Box3().setFromObject(root).getSize(new THREE.Vector3())
  49. assert.ok(stale.z < 1)
  50. const box = fitSceneModel(root, 6)
  51. const size = box.getSize(new THREE.Vector3())
  52. close(size.x, 1.2)
  53. close(size.y, 2.4)
  54. close(size.z, 6)
  55. close(root.scale.x, 0.6)
  56. close(box.min.y, 0)
  57. close(box.getCenter(new THREE.Vector3()).x, 0)
  58. close(box.getCenter(new THREE.Vector3()).z, 0)
  59. const vertex = new THREE.Vector3()
  60. for (let i = 0; i < mesh.geometry.getAttribute('position').count; i += 1) {
  61. mesh.getVertexPosition(i, vertex)
  62. assert.ok(mesh.boundingSphere.containsPoint(vertex))
  63. }
  64. })
  65. test('fresh static placement preserves authored non-uniform proportions', () => {
  66. const root = new THREE.Group()
  67. root.add(new THREE.Mesh(new THREE.BoxGeometry(2, 4, 10), new THREE.MeshBasicMaterial()))
  68. root.scale.set(2, 0.5, 3)
  69. root.position.set(-4, 8, 6)
  70. const box = fitSceneModel(root, 6)
  71. root.scale.toArray().forEach((value, i) => close(value, [0.4, 0.1, 0.6][i]))
  72. close(box.getSize(new THREE.Vector3()).z, 6)
  73. close(box.min.y, 0)
  74. })
  75. test('restoring skin caches keeps existing scene transforms and authored bone poses unchanged', () => {
  76. const { root, bone, mesh } = skinFixture()
  77. refreshSceneModelBounds(root)
  78. root.position.set(-7.5, 1.06, -0.45)
  79. root.rotation.set(0.1, 0.4, -0.2)
  80. root.scale.set(6, 4, 2)
  81. bone.position.set(1, 17, 3)
  82. const before = { position: root.position.toArray(), rotation: root.rotation.toArray(), scale: root.scale.toArray(), bone: bone.position.toArray() }
  83. const staleBox = mesh.boundingBox.clone()
  84. const box = refreshSceneModelBounds(root)
  85. assert.deepEqual({ position: root.position.toArray(), rotation: root.rotation.toArray(), scale: root.scale.toArray(), bone: bone.position.toArray() }, before)
  86. assert.notDeepEqual(mesh.boundingBox, staleBox)
  87. const vertex = new THREE.Vector3()
  88. const generousBox = box.clone().expandByScalar(1e-8)
  89. for (let i = 0; i < mesh.geometry.getAttribute('position').count; i += 1) {
  90. mesh.getVertexPosition(i, vertex)
  91. assert.ok(vertex.distanceTo(mesh.boundingSphere.center) <= mesh.boundingSphere.radius + 1e-8)
  92. assert.ok(generousBox.containsPoint(vertex.applyMatrix4(mesh.matrixWorld)))
  93. }
  94. })
  95. test('declared terrain ground datum stays on the scene floor', () => {
  96. const root = new THREE.Group()
  97. root.add(new THREE.Mesh(new THREE.BoxGeometry(8, 4, 10), new THREE.MeshBasicMaterial()))
  98. fitSceneModel(root, 20, -1.5)
  99. close(-1.5 * root.scale.y + root.position.y, 0)
  100. })
  101. test('scale input accepts real GLTF normalization factors below 0.05', () => {
  102. close(parseSceneScale('0.0361637593257', 6), 0.0361637593257)
  103. close(parseSceneScale('0.000001', 6), 0.000001)
  104. close(parseSceneScale('1e-9', 6), 0.000001)
  105. for (const value of ['', ' ', 'invalid', 'NaN', 'Infinity', '0', '-2']) {
  106. close(parseSceneScale(value, 0.036164), 0.036164)
  107. }
  108. })
  109. test('scale display preserves small factors and trims unnecessary zeros', () => {
  110. assert.equal(formatSceneScale(0.0361637593257), '0.03616376')
  111. assert.equal(formatSceneScale(0.000001), '0.000001')
  112. assert.equal(formatSceneScale(1), '1')
  113. assert.equal(formatSceneScale(0.6), '0.6')
  114. })