diff --git a/assets/shadow_residency/ShadowResidencyCasterMotionTest.gltf b/assets/shadow_residency/ShadowResidencyCasterMotionTest.gltf new file mode 100644 index 0000000..f30063e --- /dev/null +++ b/assets/shadow_residency/ShadowResidencyCasterMotionTest.gltf @@ -0,0 +1,1010 @@ +{ + "asset": { + "version": "2.0", + "generator": "fireEngine shadow_residency generate.py" + }, + "scene": 0, + "scenes": [ + { + "name": "Scene", + "nodes": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ] + } + ], + "nodes": [ + { + "name": "PointLight", + "translation": [ + 0.0, + 0.0, + 0.0 + ], + "extensions": { + "KHR_lights_punctual": { + "light": 0 + } + } + }, + { + "name": "WallPosX", + "mesh": 0, + "translation": [ + 8.2, + 0.0, + 0.0 + ] + }, + { + "name": "CasterPosX", + "mesh": 1, + "translation": [ + 3.0, + 0.0, + 0.0 + ] + }, + { + "name": "WallNegX", + "mesh": 2, + "translation": [ + -8.2, + 0.0, + 0.0 + ] + }, + { + "name": "CasterNegX", + "mesh": 3, + "translation": [ + -3.0, + 0.0, + 0.0 + ] + }, + { + "name": "WallPosY", + "mesh": 4, + "translation": [ + 0.0, + 8.2, + 0.0 + ] + }, + { + "name": "CasterPosY", + "mesh": 5, + "translation": [ + 0.0, + 3.0, + 0.0 + ] + }, + { + "name": "WallNegY", + "mesh": 6, + "translation": [ + 0.0, + -8.2, + 0.0 + ] + }, + { + "name": "CasterNegY", + "mesh": 7, + "translation": [ + 0.0, + -3.0, + 0.0 + ] + }, + { + "name": "WallPosZ", + "mesh": 8, + "translation": [ + 0.0, + 0.0, + 8.2 + ] + }, + { + "name": "CasterPosZ", + "mesh": 9, + "translation": [ + 0.0, + 0.0, + 3.0 + ] + }, + { + "name": "WallNegZ", + "mesh": 10, + "translation": [ + 0.0, + 0.0, + -8.2 + ] + }, + { + "name": "CasterNegZ", + "mesh": 11, + "translation": [ + 0.0, + 0.0, + -3.0 + ] + }, + { + "name": "Camera", + "camera": 0, + "translation": [ + 6.2, + 3.6, + 6.6 + ], + "rotation": [ + -0.17834651899579498, + 0.3599036106085298, + 0.07029763117992845, + 0.9130827746067259 + ] + } + ], + "meshes": [ + { + "name": "WallPosX", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "NORMAL": 1 + }, + "indices": 2, + "material": 0 + } + ] + }, + { + "name": "CasterPosX", + "primitives": [ + { + "attributes": { + "POSITION": 3, + "NORMAL": 4 + }, + "indices": 5, + "material": 1 + } + ] + }, + { + "name": "WallNegX", + "primitives": [ + { + "attributes": { + "POSITION": 6, + "NORMAL": 7 + }, + "indices": 8, + "material": 0 + } + ] + }, + { + "name": "CasterNegX", + "primitives": [ + { + "attributes": { + "POSITION": 9, + "NORMAL": 10 + }, + "indices": 11, + "material": 1 + } + ] + }, + { + "name": "WallPosY", + "primitives": [ + { + "attributes": { + "POSITION": 12, + "NORMAL": 13 + }, + "indices": 14, + "material": 0 + } + ] + }, + { + "name": "CasterPosY", + "primitives": [ + { + "attributes": { + "POSITION": 15, + "NORMAL": 16 + }, + "indices": 17, + "material": 1 + } + ] + }, + { + "name": "WallNegY", + "primitives": [ + { + "attributes": { + "POSITION": 18, + "NORMAL": 19 + }, + "indices": 20, + "material": 2 + } + ] + }, + { + "name": "CasterNegY", + "primitives": [ + { + "attributes": { + "POSITION": 21, + "NORMAL": 22 + }, + "indices": 23, + "material": 1 + } + ] + }, + { + "name": "WallPosZ", + "primitives": [ + { + "attributes": { + "POSITION": 24, + "NORMAL": 25 + }, + "indices": 26, + "material": 0 + } + ] + }, + { + "name": "CasterPosZ", + "primitives": [ + { + "attributes": { + "POSITION": 27, + "NORMAL": 28 + }, + "indices": 29, + "material": 1 + } + ] + }, + { + "name": "WallNegZ", + "primitives": [ + { + "attributes": { + "POSITION": 30, + "NORMAL": 31 + }, + "indices": 32, + "material": 0 + } + ] + }, + { + "name": "CasterNegZ", + "primitives": [ + { + "attributes": { + "POSITION": 33, + "NORMAL": 34 + }, + "indices": 35, + "material": 1 + } + ] + } + ], + "materials": [ + { + "name": "Mat0", + "doubleSided": true, + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.52, + 0.51, + 0.49, + 1.0 + ], + "metallicFactor": 0.0, + "roughnessFactor": 0.6 + } + }, + { + "name": "Mat1", + "doubleSided": true, + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.85, + 0.35, + 0.25, + 1.0 + ], + "metallicFactor": 0.0, + "roughnessFactor": 0.6 + } + }, + { + "name": "Mat2", + "doubleSided": true, + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.4, + 0.4, + 0.43, + 1.0 + ], + "metallicFactor": 0.0, + "roughnessFactor": 0.6 + } + } + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.2, + -8.4, + -8.4 + ], + "max": [ + 0.2, + 8.4, + 8.4 + ] + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 2, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 3, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 4, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 5, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 6, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.2, + -8.4, + -8.4 + ], + "max": [ + 0.2, + 8.4, + 8.4 + ] + }, + { + "bufferView": 7, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 8, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 9, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 10, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 11, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 12, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -8.4, + -0.2, + -8.4 + ], + "max": [ + 8.4, + 0.2, + 8.4 + ] + }, + { + "bufferView": 13, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 14, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 15, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 16, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 17, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 18, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -8.4, + -0.2, + -8.4 + ], + "max": [ + 8.4, + 0.2, + 8.4 + ] + }, + { + "bufferView": 19, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 20, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 21, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 22, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 23, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 24, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -8.4, + -8.4, + -0.2 + ], + "max": [ + 8.4, + 8.4, + 0.2 + ] + }, + { + "bufferView": 25, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 26, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 27, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 28, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 29, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 30, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -8.4, + -8.4, + -0.2 + ], + "max": [ + 8.4, + 8.4, + 0.2 + ] + }, + { + "bufferView": 31, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 32, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 33, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 34, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 35, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 36, + "componentType": 5126, + "count": 3, + "type": "SCALAR", + "min": [ + 0.0 + ], + "max": [ + 4.0 + ] + }, + { + "bufferView": 37, + "componentType": 5126, + "count": 3, + "type": "VEC3" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 288, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 576, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 648, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 936, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 1224, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 1296, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 1584, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 1872, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 1944, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 2232, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 2520, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 2592, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 2880, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 3168, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 3240, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 3528, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 3816, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 3888, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 4176, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 4464, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 4536, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 4824, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 5112, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 5184, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 5472, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 5760, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 5832, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 6120, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 6408, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 6480, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 6768, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 7056, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 7128, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 7416, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 7704, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 7776, + "byteLength": 12 + }, + { + "buffer": 0, + "byteOffset": 7788, + "byteLength": 36 + } + ], + "buffers": [ + { + "byteLength": 7824, + "uri": "data:application/octet-stream;base64,zcxMPmZmBsFmZgbBzcxMPmZmBkFmZgbBzcxMPmZmBkFmZgZBzcxMPmZmBsFmZgZBzcxMvmZmBsFmZgZBzcxMvmZmBkFmZgZBzcxMvmZmBkFmZgbBzcxMvmZmBsFmZgbBzcxMvmZmBkFmZgbBzcxMvmZmBkFmZgZBzcxMPmZmBkFmZgZBzcxMPmZmBkFmZgbBzcxMvmZmBsFmZgZBzcxMvmZmBsFmZgbBzcxMPmZmBsFmZgbBzcxMPmZmBsFmZgZBzcxMPmZmBsFmZgZBzcxMPmZmBkFmZgZBzcxMvmZmBkFmZgZBzcxMvmZmBsFmZgZBzcxMvmZmBsFmZgbBzcxMvmZmBkFmZgbBzcxMPmZmBkFmZgbBzcxMPmZmBsFmZgbBAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAzcxMPmZmBsFmZgbBzcxMPmZmBkFmZgbBzcxMPmZmBkFmZgZBzcxMPmZmBsFmZgZBzcxMvmZmBsFmZgZBzcxMvmZmBkFmZgZBzcxMvmZmBkFmZgbBzcxMvmZmBsFmZgbBzcxMvmZmBkFmZgbBzcxMvmZmBkFmZgZBzcxMPmZmBkFmZgZBzcxMPmZmBkFmZgbBzcxMvmZmBsFmZgZBzcxMvmZmBsFmZgbBzcxMPmZmBsFmZgbBzcxMPmZmBsFmZgZBzcxMPmZmBsFmZgZBzcxMPmZmBkFmZgZBzcxMvmZmBkFmZgZBzcxMvmZmBsFmZgZBzcxMvmZmBsFmZgbBzcxMvmZmBkFmZgbBzcxMPmZmBkFmZgbBzcxMPmZmBsFmZgbBAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAZmYGQc3MTL5mZgbBZmYGQc3MTD5mZgbBZmYGQc3MTD5mZgZBZmYGQc3MTL5mZgZBZmYGwc3MTL5mZgZBZmYGwc3MTD5mZgZBZmYGwc3MTD5mZgbBZmYGwc3MTL5mZgbBZmYGwc3MTD5mZgbBZmYGwc3MTD5mZgZBZmYGQc3MTD5mZgZBZmYGQc3MTD5mZgbBZmYGwc3MTL5mZgZBZmYGwc3MTL5mZgbBZmYGQc3MTL5mZgbBZmYGQc3MTL5mZgZBZmYGQc3MTL5mZgZBZmYGQc3MTD5mZgZBZmYGwc3MTD5mZgZBZmYGwc3MTL5mZgZBZmYGwc3MTL5mZgbBZmYGwc3MTD5mZgbBZmYGQc3MTD5mZgbBZmYGQc3MTL5mZgbBAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAZmYGQc3MTL5mZgbBZmYGQc3MTD5mZgbBZmYGQc3MTD5mZgZBZmYGQc3MTL5mZgZBZmYGwc3MTL5mZgZBZmYGwc3MTD5mZgZBZmYGwc3MTD5mZgbBZmYGwc3MTL5mZgbBZmYGwc3MTD5mZgbBZmYGwc3MTD5mZgZBZmYGQc3MTD5mZgZBZmYGQc3MTD5mZgbBZmYGwc3MTL5mZgZBZmYGwc3MTL5mZgbBZmYGQc3MTL5mZgbBZmYGQc3MTL5mZgZBZmYGQc3MTL5mZgZBZmYGQc3MTD5mZgZBZmYGwc3MTD5mZgZBZmYGwc3MTL5mZgZBZmYGwc3MTL5mZgbBZmYGwc3MTD5mZgbBZmYGQc3MTD5mZgbBZmYGQc3MTL5mZgbBAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAZmYGQWZmBsHNzEy+ZmYGQWZmBkHNzEy+ZmYGQWZmBkHNzEw+ZmYGQWZmBsHNzEw+ZmYGwWZmBsHNzEw+ZmYGwWZmBkHNzEw+ZmYGwWZmBkHNzEy+ZmYGwWZmBsHNzEy+ZmYGwWZmBkHNzEy+ZmYGwWZmBkHNzEw+ZmYGQWZmBkHNzEw+ZmYGQWZmBkHNzEy+ZmYGwWZmBsHNzEw+ZmYGwWZmBsHNzEy+ZmYGQWZmBsHNzEy+ZmYGQWZmBsHNzEw+ZmYGQWZmBsHNzEw+ZmYGQWZmBkHNzEw+ZmYGwWZmBkHNzEw+ZmYGwWZmBsHNzEw+ZmYGwWZmBsHNzEy+ZmYGwWZmBkHNzEy+ZmYGQWZmBkHNzEy+ZmYGQWZmBsHNzEy+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAZmYGQWZmBsHNzEy+ZmYGQWZmBkHNzEy+ZmYGQWZmBkHNzEw+ZmYGQWZmBsHNzEw+ZmYGwWZmBsHNzEw+ZmYGwWZmBkHNzEw+ZmYGwWZmBkHNzEy+ZmYGwWZmBsHNzEy+ZmYGwWZmBkHNzEy+ZmYGwWZmBkHNzEw+ZmYGQWZmBkHNzEw+ZmYGQWZmBkHNzEy+ZmYGwWZmBsHNzEw+ZmYGwWZmBsHNzEy+ZmYGQWZmBsHNzEy+ZmYGQWZmBsHNzEw+ZmYGQWZmBsHNzEw+ZmYGQWZmBkHNzEw+ZmYGwWZmBkHNzEw+ZmYGwWZmBsHNzEw+ZmYGwWZmBsHNzEy+ZmYGwWZmBkHNzEy+ZmYGQWZmBkHNzEy+ZmYGQWZmBsHNzEy+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAAAAAAEAAAIBAzcwMQAAAAAAAAAAAMzNzQAAAAAAAAAAAzcwMQAAAAAAAAAAA" + } + ], + "cameras": [ + { + "name": "Camera", + "type": "perspective", + "perspective": { + "yfov": 0.7, + "znear": 0.05, + "zfar": 500.0 + } + } + ], + "animations": [ + { + "name": "CasterInOneFace", + "samplers": [ + { + "input": 36, + "output": 37, + "interpolation": "LINEAR" + } + ], + "channels": [ + { + "sampler": 0, + "target": { + "node": 2, + "path": "translation" + } + } + ] + } + ], + "extensionsUsed": [ + "KHR_lights_punctual" + ], + "extensions": { + "KHR_lights_punctual": { + "lights": [ + { + "name": "Point", + "type": "point", + "color": [ + 1.0, + 0.96, + 0.9 + ], + "intensity": 26.0, + "range": 22.0 + } + ] + } + } +} diff --git a/assets/shadow_residency/ShadowResidencyLightMotionTest.gltf b/assets/shadow_residency/ShadowResidencyLightMotionTest.gltf new file mode 100644 index 0000000..aa8e268 --- /dev/null +++ b/assets/shadow_residency/ShadowResidencyLightMotionTest.gltf @@ -0,0 +1,1010 @@ +{ + "asset": { + "version": "2.0", + "generator": "fireEngine shadow_residency generate.py" + }, + "scene": 0, + "scenes": [ + { + "name": "Scene", + "nodes": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ] + } + ], + "nodes": [ + { + "name": "PointLight", + "translation": [ + 0.0, + 0.0, + 0.0 + ], + "extensions": { + "KHR_lights_punctual": { + "light": 0 + } + } + }, + { + "name": "WallPosX", + "mesh": 0, + "translation": [ + 8.2, + 0.0, + 0.0 + ] + }, + { + "name": "CasterPosX", + "mesh": 1, + "translation": [ + 3.0, + 0.0, + 0.0 + ] + }, + { + "name": "WallNegX", + "mesh": 2, + "translation": [ + -8.2, + 0.0, + 0.0 + ] + }, + { + "name": "CasterNegX", + "mesh": 3, + "translation": [ + -3.0, + 0.0, + 0.0 + ] + }, + { + "name": "WallPosY", + "mesh": 4, + "translation": [ + 0.0, + 8.2, + 0.0 + ] + }, + { + "name": "CasterPosY", + "mesh": 5, + "translation": [ + 0.0, + 3.0, + 0.0 + ] + }, + { + "name": "WallNegY", + "mesh": 6, + "translation": [ + 0.0, + -8.2, + 0.0 + ] + }, + { + "name": "CasterNegY", + "mesh": 7, + "translation": [ + 0.0, + -3.0, + 0.0 + ] + }, + { + "name": "WallPosZ", + "mesh": 8, + "translation": [ + 0.0, + 0.0, + 8.2 + ] + }, + { + "name": "CasterPosZ", + "mesh": 9, + "translation": [ + 0.0, + 0.0, + 3.0 + ] + }, + { + "name": "WallNegZ", + "mesh": 10, + "translation": [ + 0.0, + 0.0, + -8.2 + ] + }, + { + "name": "CasterNegZ", + "mesh": 11, + "translation": [ + 0.0, + 0.0, + -3.0 + ] + }, + { + "name": "Camera", + "camera": 0, + "translation": [ + 6.2, + 3.6, + 6.6 + ], + "rotation": [ + -0.17834651899579498, + 0.3599036106085298, + 0.07029763117992845, + 0.9130827746067259 + ] + } + ], + "meshes": [ + { + "name": "WallPosX", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "NORMAL": 1 + }, + "indices": 2, + "material": 0 + } + ] + }, + { + "name": "CasterPosX", + "primitives": [ + { + "attributes": { + "POSITION": 3, + "NORMAL": 4 + }, + "indices": 5, + "material": 1 + } + ] + }, + { + "name": "WallNegX", + "primitives": [ + { + "attributes": { + "POSITION": 6, + "NORMAL": 7 + }, + "indices": 8, + "material": 0 + } + ] + }, + { + "name": "CasterNegX", + "primitives": [ + { + "attributes": { + "POSITION": 9, + "NORMAL": 10 + }, + "indices": 11, + "material": 1 + } + ] + }, + { + "name": "WallPosY", + "primitives": [ + { + "attributes": { + "POSITION": 12, + "NORMAL": 13 + }, + "indices": 14, + "material": 0 + } + ] + }, + { + "name": "CasterPosY", + "primitives": [ + { + "attributes": { + "POSITION": 15, + "NORMAL": 16 + }, + "indices": 17, + "material": 1 + } + ] + }, + { + "name": "WallNegY", + "primitives": [ + { + "attributes": { + "POSITION": 18, + "NORMAL": 19 + }, + "indices": 20, + "material": 2 + } + ] + }, + { + "name": "CasterNegY", + "primitives": [ + { + "attributes": { + "POSITION": 21, + "NORMAL": 22 + }, + "indices": 23, + "material": 1 + } + ] + }, + { + "name": "WallPosZ", + "primitives": [ + { + "attributes": { + "POSITION": 24, + "NORMAL": 25 + }, + "indices": 26, + "material": 0 + } + ] + }, + { + "name": "CasterPosZ", + "primitives": [ + { + "attributes": { + "POSITION": 27, + "NORMAL": 28 + }, + "indices": 29, + "material": 1 + } + ] + }, + { + "name": "WallNegZ", + "primitives": [ + { + "attributes": { + "POSITION": 30, + "NORMAL": 31 + }, + "indices": 32, + "material": 0 + } + ] + }, + { + "name": "CasterNegZ", + "primitives": [ + { + "attributes": { + "POSITION": 33, + "NORMAL": 34 + }, + "indices": 35, + "material": 1 + } + ] + } + ], + "materials": [ + { + "name": "Mat0", + "doubleSided": true, + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.52, + 0.51, + 0.49, + 1.0 + ], + "metallicFactor": 0.0, + "roughnessFactor": 0.6 + } + }, + { + "name": "Mat1", + "doubleSided": true, + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.85, + 0.35, + 0.25, + 1.0 + ], + "metallicFactor": 0.0, + "roughnessFactor": 0.6 + } + }, + { + "name": "Mat2", + "doubleSided": true, + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.4, + 0.4, + 0.43, + 1.0 + ], + "metallicFactor": 0.0, + "roughnessFactor": 0.6 + } + } + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.2, + -8.4, + -8.4 + ], + "max": [ + 0.2, + 8.4, + 8.4 + ] + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 2, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 3, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 4, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 5, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 6, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.2, + -8.4, + -8.4 + ], + "max": [ + 0.2, + 8.4, + 8.4 + ] + }, + { + "bufferView": 7, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 8, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 9, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 10, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 11, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 12, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -8.4, + -0.2, + -8.4 + ], + "max": [ + 8.4, + 0.2, + 8.4 + ] + }, + { + "bufferView": 13, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 14, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 15, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 16, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 17, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 18, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -8.4, + -0.2, + -8.4 + ], + "max": [ + 8.4, + 0.2, + 8.4 + ] + }, + { + "bufferView": 19, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 20, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 21, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 22, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 23, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 24, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -8.4, + -8.4, + -0.2 + ], + "max": [ + 8.4, + 8.4, + 0.2 + ] + }, + { + "bufferView": 25, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 26, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 27, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 28, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 29, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 30, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -8.4, + -8.4, + -0.2 + ], + "max": [ + 8.4, + 8.4, + 0.2 + ] + }, + { + "bufferView": 31, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 32, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 33, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "bufferView": 34, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 35, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 36, + "componentType": 5126, + "count": 3, + "type": "SCALAR", + "min": [ + 0.0 + ], + "max": [ + 5.0 + ] + }, + { + "bufferView": 37, + "componentType": 5126, + "count": 3, + "type": "VEC3" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 288, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 576, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 648, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 936, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 1224, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 1296, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 1584, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 1872, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 1944, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 2232, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 2520, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 2592, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 2880, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 3168, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 3240, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 3528, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 3816, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 3888, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 4176, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 4464, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 4536, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 4824, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 5112, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 5184, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 5472, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 5760, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 5832, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 6120, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 6408, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 6480, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 6768, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 7056, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 7128, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 7416, + "byteLength": 288, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 7704, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 7776, + "byteLength": 12 + }, + { + "buffer": 0, + "byteOffset": 7788, + "byteLength": 36 + } + ], + "buffers": [ + { + "byteLength": 7824, + "uri": "data:application/octet-stream;base64,zcxMPmZmBsFmZgbBzcxMPmZmBkFmZgbBzcxMPmZmBkFmZgZBzcxMPmZmBsFmZgZBzcxMvmZmBsFmZgZBzcxMvmZmBkFmZgZBzcxMvmZmBkFmZgbBzcxMvmZmBsFmZgbBzcxMvmZmBkFmZgbBzcxMvmZmBkFmZgZBzcxMPmZmBkFmZgZBzcxMPmZmBkFmZgbBzcxMvmZmBsFmZgZBzcxMvmZmBsFmZgbBzcxMPmZmBsFmZgbBzcxMPmZmBsFmZgZBzcxMPmZmBsFmZgZBzcxMPmZmBkFmZgZBzcxMvmZmBkFmZgZBzcxMvmZmBsFmZgZBzcxMvmZmBsFmZgbBzcxMvmZmBkFmZgbBzcxMPmZmBkFmZgbBzcxMPmZmBsFmZgbBAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAzcxMPmZmBsFmZgbBzcxMPmZmBkFmZgbBzcxMPmZmBkFmZgZBzcxMPmZmBsFmZgZBzcxMvmZmBsFmZgZBzcxMvmZmBkFmZgZBzcxMvmZmBkFmZgbBzcxMvmZmBsFmZgbBzcxMvmZmBkFmZgbBzcxMvmZmBkFmZgZBzcxMPmZmBkFmZgZBzcxMPmZmBkFmZgbBzcxMvmZmBsFmZgZBzcxMvmZmBsFmZgbBzcxMPmZmBsFmZgbBzcxMPmZmBsFmZgZBzcxMPmZmBsFmZgZBzcxMPmZmBkFmZgZBzcxMvmZmBkFmZgZBzcxMvmZmBsFmZgZBzcxMvmZmBsFmZgbBzcxMvmZmBkFmZgbBzcxMPmZmBkFmZgbBzcxMPmZmBsFmZgbBAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAZmYGQc3MTL5mZgbBZmYGQc3MTD5mZgbBZmYGQc3MTD5mZgZBZmYGQc3MTL5mZgZBZmYGwc3MTL5mZgZBZmYGwc3MTD5mZgZBZmYGwc3MTD5mZgbBZmYGwc3MTL5mZgbBZmYGwc3MTD5mZgbBZmYGwc3MTD5mZgZBZmYGQc3MTD5mZgZBZmYGQc3MTD5mZgbBZmYGwc3MTL5mZgZBZmYGwc3MTL5mZgbBZmYGQc3MTL5mZgbBZmYGQc3MTL5mZgZBZmYGQc3MTL5mZgZBZmYGQc3MTD5mZgZBZmYGwc3MTD5mZgZBZmYGwc3MTL5mZgZBZmYGwc3MTL5mZgbBZmYGwc3MTD5mZgbBZmYGQc3MTD5mZgbBZmYGQc3MTL5mZgbBAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAZmYGQc3MTL5mZgbBZmYGQc3MTD5mZgbBZmYGQc3MTD5mZgZBZmYGQc3MTL5mZgZBZmYGwc3MTL5mZgZBZmYGwc3MTD5mZgZBZmYGwc3MTD5mZgbBZmYGwc3MTL5mZgbBZmYGwc3MTD5mZgbBZmYGwc3MTD5mZgZBZmYGQc3MTD5mZgZBZmYGQc3MTD5mZgbBZmYGwc3MTL5mZgZBZmYGwc3MTL5mZgbBZmYGQc3MTL5mZgbBZmYGQc3MTL5mZgZBZmYGQc3MTL5mZgZBZmYGQc3MTD5mZgZBZmYGwc3MTD5mZgZBZmYGwc3MTL5mZgZBZmYGwc3MTL5mZgbBZmYGwc3MTD5mZgbBZmYGQc3MTD5mZgbBZmYGQc3MTL5mZgbBAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAZmYGQWZmBsHNzEy+ZmYGQWZmBkHNzEy+ZmYGQWZmBkHNzEw+ZmYGQWZmBsHNzEw+ZmYGwWZmBsHNzEw+ZmYGwWZmBkHNzEw+ZmYGwWZmBkHNzEy+ZmYGwWZmBsHNzEy+ZmYGwWZmBkHNzEy+ZmYGwWZmBkHNzEw+ZmYGQWZmBkHNzEw+ZmYGQWZmBkHNzEy+ZmYGwWZmBsHNzEw+ZmYGwWZmBsHNzEy+ZmYGQWZmBsHNzEy+ZmYGQWZmBsHNzEw+ZmYGQWZmBsHNzEw+ZmYGQWZmBkHNzEw+ZmYGwWZmBkHNzEw+ZmYGwWZmBsHNzEw+ZmYGwWZmBsHNzEy+ZmYGwWZmBkHNzEy+ZmYGQWZmBkHNzEy+ZmYGQWZmBsHNzEy+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAZmYGQWZmBsHNzEy+ZmYGQWZmBkHNzEy+ZmYGQWZmBkHNzEw+ZmYGQWZmBsHNzEw+ZmYGwWZmBsHNzEw+ZmYGwWZmBkHNzEw+ZmYGwWZmBkHNzEy+ZmYGwWZmBsHNzEy+ZmYGwWZmBkHNzEy+ZmYGwWZmBkHNzEw+ZmYGQWZmBkHNzEw+ZmYGQWZmBkHNzEy+ZmYGwWZmBsHNzEw+ZmYGwWZmBsHNzEy+ZmYGQWZmBsHNzEy+ZmYGQWZmBsHNzEw+ZmYGQWZmBsHNzEw+ZmYGQWZmBkHNzEw+ZmYGwWZmBkHNzEw+ZmYGwWZmBsHNzEw+ZmYGwWZmBsHNzEy+ZmYGwWZmBkHNzEy+ZmYGQWZmBkHNzEy+ZmYGQWZmBsHNzEy+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAAAAAIEAAAKBAAACAvwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAA" + } + ], + "cameras": [ + { + "name": "Camera", + "type": "perspective", + "perspective": { + "yfov": 0.7, + "znear": 0.05, + "zfar": 500.0 + } + } + ], + "animations": [ + { + "name": "LightSweep", + "samplers": [ + { + "input": 36, + "output": 37, + "interpolation": "LINEAR" + } + ], + "channels": [ + { + "sampler": 0, + "target": { + "node": 0, + "path": "translation" + } + } + ] + } + ], + "extensionsUsed": [ + "KHR_lights_punctual" + ], + "extensions": { + "KHR_lights_punctual": { + "lights": [ + { + "name": "Point", + "type": "point", + "color": [ + 1.0, + 0.96, + 0.9 + ], + "intensity": 26.0, + "range": 22.0 + } + ] + } + } +} diff --git a/assets/shadow_residency/generate.py b/assets/shadow_residency/generate.py index 41c8f0c..dad647a 100644 --- a/assets/shadow_residency/generate.py +++ b/assets/shadow_residency/generate.py @@ -98,11 +98,24 @@ def scaled(axis, distance): return tuple(component * distance for component in axis) -def build(): +def build(motion=None): + """The room, the light and the six casters. + + `motion` selects which of the three scenes this is: `None` for the static gate, + `"caster"` for one caster moving inside a single cube face, `"light"` for the light + itself moving. They are separate FILES rather than phases of one animation, and + deliberately: the diagnostic evidence is a per-family count (`recorded=1 reused=5` + against `recorded=6 reused=0`), and a scene that changed regime part-way would make + every aggregate a mixture of two answers with no way to say which frame is which. + """ s = Scene(GENERATOR) + # Node indices are captured as they are created — `Scene` hands one back from every + # builder — because the animation channels below address nodes by index. + index = {} + # The light first, so it is light 0 and the scene's only one. - s.add_node( + index["PointLight"] = s.add_node( "PointLight", light=s.light( "Point", @@ -121,7 +134,7 @@ def build(): colour = FLOOR_COLOUR if name == "NegY" else WALL_COLOUR s.box(f"Wall{name}", wall_half_extent(axis), wall_centre, colour) # One occluder per face, on the axis between the light and that wall. - s.box( + index[f"Caster{name}"] = s.box( f"Caster{name}", (CASTER_HALF, CASTER_HALF, CASTER_HALF), scaled(axis, CASTER_DISTANCE), @@ -129,10 +142,41 @@ def build(): ) s.camera(CAMERA_EYE, CAMERA_TARGET) + + if motion == "caster": + # CONTINUOUS, and confined to ONE face. Radial motion along the +X axis keeps the + # caster inside the +X face's 90-degree frustum for the whole loop while changing + # what that face stores every frame — so the steady state is `recorded=1 reused=5`, + # which is the per-face granularity this item is about, stated as a number. + # + # Continuous rather than a single hop: the diagnostic sample is periodic, and a + # one-off transition can fall between two samples and read as a scene that never + # changed. + near = CASTER_DISTANCE - 0.8 + far = CASTER_DISTANCE + 0.8 + s.animation( + "CasterInOneFace", + [ + (index["CasterPosX"], "translation", [0.0, 2.0, 4.0], + [(near, 0.0, 0.0), (far, 0.0, 0.0), (near, 0.0, 0.0)], "LINEAR"), + ], + ) + elif motion == "light": + # The light itself moves, so every face's stored depth changes: its position is an + # input to the radial ratio each of the six faces writes, not merely to the matrices. + # Steady state is therefore `recorded=6 reused=0` — the honest ceiling for what + # punctual reuse can save when the light is the thing in motion. + s.animation( + "LightSweep", + [ + (index["PointLight"], "translation", [0.0, 2.5, 5.0], + [(-1.0, 0.0, 0.0), (1.0, 0.0, 0.0), (-1.0, 0.0, 0.0)], "LINEAR"), + ], + ) return s -def validate(doc): +def validate(doc, motion=None): """Structural checks, because this scene's whole value is what it does NOT contain. A later edit that adds a sun, or animates a caster to make a screenshot livelier, would @@ -148,7 +192,18 @@ def validate(doc): # Temporal or deforming content would change the content descriptor every frame, so a # reused view could never happen and the gate would fail for the wrong reason. - assert not doc.get("animations"), "the residency gate scene must not animate" + if motion is None: + assert not doc.get("animations"), "the static gate scene must not animate" + else: + # Exactly ONE animated node, and the right one. A second channel would mix two + # regimes into one family count and make `recorded=N` unreadable. + animations = doc.get("animations", []) + assert len(animations) == 1, f"expected one animation, found {len(animations)}" + channels = animations[0]["channels"] + assert len(channels) == 1, f"expected one animated node, found {len(channels)}" + animated = doc["nodes"][channels[0]["target"]["node"]]["name"] + expected = "CasterPosX" if motion == "caster" else "PointLight" + assert animated == expected, f"{motion} scene animates '{animated}', not '{expected}'" assert not doc.get("skins"), "the residency gate scene must not contain skinned casters" for mesh in doc["meshes"]: for primitive in mesh["primitives"]: @@ -189,12 +244,16 @@ def validate(doc): def main(): - scene = build() - doc = scene.to_gltf() - validate(doc) - out = Path(__file__).resolve().parent / "ShadowResidencyTest.gltf" - write_gltf(out, doc) - print(f"wrote {out}") + here = Path(__file__).resolve().parent + for motion, name in ((None, "ShadowResidencyTest.gltf"), + ("caster", "ShadowResidencyCasterMotionTest.gltf"), + ("light", "ShadowResidencyLightMotionTest.gltf")): + scene = build(motion) + doc = scene.to_gltf() + validate(doc, motion) + out = here / name + write_gltf(out, doc) + print(f"wrote {out}") if __name__ == "__main__": diff --git a/docs/acceptance-testing.md b/docs/acceptance-testing.md index 0b67873..49f3025 100644 --- a/docs/acceptance-testing.md +++ b/docs/acceptance-testing.md @@ -591,6 +591,49 @@ Look at the image as well as the hash. The two large hard-edged rectangles on th cast shadows; if a run ever produces an image with a shadow in the wrong place rather than a different hash, that is a stale map and `--no-shadow-reuse` will confirm it in one run. +### 4. Per-face granularity — the punctual payoff (arc 2 #15) + +The static gate above proves reuse happens; these two scenes prove what it is WORTH when something +is actually moving, which is the case a shipping scene is in. They are separate files rather than +phases of one animation on purpose: the evidence is a per-family count, and a scene that changed +regime part-way would make every aggregate a mixture of two answers. + +```bash +# One rigid caster moving INSIDE a single cube face; the light and the other five casters are static. +FE_LOG=render:debug ./fireEngineApp --no-taa \ + shadow_residency/ShadowResidencyCasterMotionTest.gltf nightbox.hdr +``` + +Steady state must be **`point sampleable recorded=1 reused=5 passes=1`**. One face's content changed, +so one face re-rendered; the other five are still resident and still sampleable. Then the same scene +with the cache off: + +```bash +FE_LOG=render:debug ./fireEngineApp --no-taa --no-shadow-reuse \ + shadow_residency/ShadowResidencyCasterMotionTest.gltf nightbox.hdr +``` + +`recorded=6 reused=0 passes=6`. Measured here (macOS/arm64, MoltenVK, 15 warm samples each): +**0.007 ms** median for the point family with reuse (0.004–0.022) against **0.141 ms** forced +(0.124–0.192) — the same scene, the same image, a factor of about twenty, and a direct measurement +of per-face granularity rather than of reuse in general. + +```bash +# The light itself moving — the ceiling on what punctual reuse can save. +FE_LOG=render:debug ./fireEngineApp --no-taa \ + shadow_residency/ShadowResidencyLightMotionTest.gltf nightbox.hdr +``` + +Expect **`recorded=6 reused=0`** (measured: 0.172 ms median, 9 samples) even with reuse enabled, +and that is correct rather than a failure: +a point light's position is an input to the radial depth EVERY face stores, so a light that moves +invalidates its whole cube. A run that showed anything less would mean a face had kept depth measured +against a position the light has left. + +**No reference screenshot for either motion scene.** An animated frame has no reproducible timestamp, +so a capture proves nothing; the State column, the family counters and the timing comparison are the +evidence. + ### 4. Vulkan validation stays clean Add `--require-validation` to any of the above. A reused view is skipped **entirely** — no barrier, diff --git a/docs/architecturalreview.md b/docs/architecturalreview.md index 4f2e2bd..d980768 100644 --- a/docs/architecturalreview.md +++ b/docs/architecturalreview.md @@ -387,7 +387,7 @@ exists; the tiered review inherited the filename.)* | 12 | ✅ Skip frustum extraction for inactive shadow slots *(same branch; coarse-cull pushes only active cascade/spot/point slots — also tightens the cull)* | C | XS | §5.3 | | 13 | ✅ Comment: chart-set immutability rationale *(same branch)* | C | XS | §4.2 | | 14 | ✅ Comment: hinge axis rows prepare-frozen by design *(same branch)* | C | XS | §3.4 | -| 15 | Punctual-shadow change detection (spot/point re-render every face every frame; point is 6×1024² each) — same epoch/dirty-bit mechanism as item 4 | B/C | M | §2.3 | +| 15 | ✅ Punctual-shadow change detection *(branch `shadow-punctual-change-detection`)* — verified, closed with NO engine change. Arc 2 #4's residency mechanism is family-agnostic and already covered spot and point; this item's value was the evidence. Audit: every shadow sampling input is re-uploaded from the view set each frame, every raster input is in the content descriptor, nothing is outside both. Pinned headlessly: range-only changes re-record a whole cube, a moving light re-records all six faces (its position is an input to every face's radial depth), an inherited slot re-records rather than reusing a predecessor's map, and bias metrics are sampling rather than content. Measured: a caster moving inside one face holds `recorded=1 reused=5` at 0.007 ms against 0.141 ms forced. Slot churn is parked behind runtime scene mutation. | B/C | M | §2.3 | | 16 | `hash_combine`-style mix for the mesh-triangle warm-start key (XOR admits collisions across (pair, triangle); consequence is a wrong warm-start seed, self-correcting) | C | XS | §3.3 | | 17 | Retire the TAA resolve→blit full-res 16F copy by treating `history[cur]` as the scene target — **measure on MoltenVK first**; costs per-frame or double-buffered descriptors for three consumers | C | M | §2.5 | | 18 | `vdpmDrawCounts_` / `findSelfShadowViewProj` linear scans are O(fronts²) — a watch-item, not a defect at today's front counts | C | XS | §1.6 | diff --git a/docs/roadmap.md b/docs/roadmap.md index 8977f19..74e7cee 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -81,32 +81,21 @@ The eight small/XS items landed on `review-shadow-taa-fixes` + `review-xs-cleanu the five the review prioritised, then five a later coverage audit found had no action item. In the review's priority order: -- **#15 [B/C, M] Punctual-shadow change detection** (§2.3) — **next, and now mostly verification.** - Arc 2 #4 landed the whole mechanism on `shadow-residency-reuse` and it is family-agnostic: a - static point light's six faces already reuse today, which is what the gate scene measures. What - this item still owes is the evidence and the follow-through — a scene with a light that MOVES - (proving the faces re-record on the frame the light's position or range changes, since both are in - the content descriptor), a spot equivalent, and a decision about per-face granularity: the cube is - compared per face, but slot assignment is per-light, so a light entering or leaving reshuffles - slots and invalidates its neighbours' residency by identity. Worth measuring before assuming it - matters. (Per-face frustum filtering already exists and is correct.) -- **#5 [B/L] Compute pre-skinning pass** (§1.3) — skinning/morphing re-runs in every pass's vertex - shader (~11× per skinned vertex per frame). `SoftBodySystem` already proves the compute pattern - in-engine. The one genuinely architectural piece here; it also retires SH-04's deformable - full-detail fallback by exposing pre-deformed vertices + exact deformed bounds + a deformation - revision. -- **#7 [B/S] Physics per-step scratch persistence** (§3.1) — remove the per-step heap allocation in - the solver hot path. Golden-neutral if done as pure allocation reuse. -- **#10 [B/S] Front-to-back sort of the opaque bucket** (§1.1) — improves depth-prepass rejection. -- **#6 [C/S] Batch image barriers into single `DependencyInfo`s** (§1.2) — compounds on MoltenVK - (§5.2); coordinate with SH-* so barrier grouping doesn't change per-view LOD decisions. - -**Added by a coverage audit** (2026-07-26). The review's §6 table was a *prioritised* list, not an -exhaustive one: five actionable findings in its body had no row. They are now rows 15–19 there and -items here. All five are genuinely lower-value than the above — three are conditional or watch-items -in the review's own words — and are recorded so the arc is scoped honestly, not because each is -worth doing: - +- **#15 ✅ Punctual-shadow change detection** (§2.3) — verified on `shadow-punctual-change-detection` + and closed with **no engine change**, which was the honest outcome: arc 2 #4's mechanism is + family-agnostic and already covered the punctual families. What the branch added is the evidence — + an audit showing every shadow sampling input is re-uploaded from the view set each frame while + every raster input is in the content descriptor (so nothing sits outside both), eight headless + cases pinning the punctual specifics (range-only change, moving light, slot inheritance for both + spot and cube, metrics-are-not-content on both), and two motion scenes measuring the payoff: + `recorded=1 reused=5` at 0.007 ms against 0.141 ms forced, for an identical image. Detail in + [`shadowplans.md`](shadowplans.md) § Interaction; runbook in + [`acceptance-testing.md`](acceptance-testing.md). + + **Parked, not done:** slot churn. A light leaving compacts punctual slots and every inherited slot + re-records a whole cube. Correct today (identity is compared) and unreachable in production (no + runtime light removal or enable/disable path, stable gather order), so a stable-assignment scheme + would guard against nothing. **Trigger: runtime scene mutation, or a light enable/disable toggle.** - **#16 [C, XS] `hash_combine`-style mix for the mesh-triangle warm-start key** (§3.3) — `in.key ^= subKey * 0x9E3779B97F4A7C15ULL` (`physics_world.cpp`) is a decent mix, but XOR over the pair key admits collisions across (pair, triangle) combinations. The consequence is only a wrong diff --git a/docs/shadowplans.md b/docs/shadowplans.md index 4499f29..f186810 100644 --- a/docs/shadowplans.md +++ b/docs/shadowplans.md @@ -1034,6 +1034,38 @@ contracts from this work: `--no-shadow-reuse` (overlay: "Reuse unchanged shadow views"); the runbook is [`acceptance-testing.md`](acceptance-testing.md) § Shadow-residency gate scene. + **Punctual change detection (arc 2 #15) is the same mechanism, verified rather than built.** The + audit that item owed is done, and its result is a boundary worth stating once: every shadow + SAMPLING input — `spotViewProj`, `cascadeViewProj`, `selfShadowViewProj` and all four + `*BiasMetrics` arrays — is copied wholesale from the completed view SET every frame, + unconditionally, never from the plan and never gated on whether a family recorded. Every RASTER + input is in the content descriptor. Nothing sits outside both, which is why a reused map cannot go + stale through a sampling parameter. (Shadow draws are also VDPM-free by construction — `object.cpp` + clears the indirect handle and the GPU front — so a per-frame GPU-emitted index buffer, whose + handle repeats while its contents change, can never enter the descriptor.) + + The punctual specifics are pinned headlessly: a RANGE-only change re-records all six faces (every + matrix identical, every texel different — the case a transform-only descriptor would miss); a + moving light re-records all six, because its position is an input to the radial depth each face + stores; a cube inherited by another light re-records all six rather than lighting one light with + its predecessor's shadows; and metrics-only changes reuse, on both the spot and point paths. + + **Per-face granularity is where the punctual saving actually lives, and it is caster-driven.** + A cube is compared face by face, but a light's own movement invalidates every face by the content + law, so only a CASTER can change one face and not another. Measured on + `ShadowResidencyCasterMotionTest` (one rigid caster moving inside a single face, 15 warm samples): + the point family holds `recorded=1 reused=5` at a median **0.007 ms**, against **0.141 ms** forced + on the same scene — about twenty times less for an identical image. The light-motion scene is the + ceiling: `recorded=6 reused=0`, 0.172 ms, which is what punctual reuse cannot save. + + **Slot churn is correct and deliberately un-optimised.** Punctual slots are assigned per frame in + gather order, so a light leaving compacts the ones after it and every inherited slot re-records a + whole cube it could in principle have kept. That is SAFE — identity is part of the comparison, and + the tests above pin it — and its cost is currently unreachable: the scene graph has no runtime + light removal or enable/disable path, and `gatherLights()` walks a stable node order, so churn + frequency in production is zero. Revisit only when runtime scene mutation or a light enable toggle + arrives; until then a stable-slot-assignment scheme would be machinery guarding against nothing. + **What this does NOT buy: CPU preparation.** A reused view is filtered, resolved and observed in full — the comparison cannot be made without the work that produces its operand. The saving is GPU raster only, which is also why the honest headline case is a static PUNCTUAL light rather than diff --git a/tests/graphics/test_shadow_pass_prepare.cpp b/tests/graphics/test_shadow_pass_prepare.cpp index 2bdaace..3b48e14 100644 --- a/tests/graphics/test_shadow_pass_prepare.cpp +++ b/tests/graphics/test_shadow_pass_prepare.cpp @@ -1,5 +1,7 @@ #include +#include + #include using namespace fire_engine; @@ -651,3 +653,256 @@ TEST_CASE("a view that never engaged reports no disposition at all", "[ShadowPas CHECK(out.plan.disposition(ShadowViewGroup::Spot, unusedSpot) == ShadowViewDisposition::Invalid); } + +// --- punctual change detection (arc 2 #15) ------------------------------------------------------- +// +// The mechanism is arc 2 #4's and is family-agnostic; what these pin is that it actually HOLDS for +// the punctual families, whose lights are the ones that move independently of the camera. Each case +// below is a way a stale punctual map could survive a change nobody compared. + +namespace +{ + +// The fixture's cube, rebuilt around a light that may have moved or re-ranged. Both halves are +// SHADER INPUTS for a point face — the stored depth is `length(worldPos - lightPos) / range` — so +// each must be able to force a re-record on its own. +[[nodiscard]] ShadowRenderViewSet pointCubeAt(Vec3 position, float range, float faceMark = 0.0f) +{ + ShadowRenderViewSet views = populatedViews(); + const std::array forwards{Vec3{1, 0, 0}, Vec3{-1, 0, 0}, Vec3{0, 1, 0}, + Vec3{0, -1, 0}, Vec3{0, 0, 1}, Vec3{0, 0, -1}}; + const auto face = [&](std::uint8_t f) + { return ShadowPointFace{markedMatrix(faceMark), somePerspective(forwards[f], position)}; }; + const std::array cube{face(0), face(1), face(2), + face(3), face(4), face(5)}; + REQUIRE(views.setPointLight(0, kLight, ShadowViewMetrics::pointLight(0.004f, range), range, + std::span{cube})); + return views; +} + +// A cube whose faces carry REAL projection matrices, so each face's frustum admits only what that +// face can see. The shared fixture deliberately uses one marked matrix for all six — fine for the +// accounting rules it was built for, useless here, because six identical frusta admit every caster +// to every face and "only this face re-recorded" would pass without meaning anything. +[[nodiscard]] ShadowRenderViewSet pointCubeWithRealFaces(Vec3 position, float range) +{ + ShadowRenderViewSet views = populatedViews(); + const std::array forwards{Vec3{1, 0, 0}, Vec3{-1, 0, 0}, Vec3{0, 1, 0}, + Vec3{0, -1, 0}, Vec3{0, 0, 1}, Vec3{0, 0, -1}}; + const auto face = [&](std::uint8_t f) + { + // 90 degrees, square aspect — a cube face exactly. `up` must not be parallel to forward, + // which is why the ±Y faces take a different one. + const Vec3 forward = forwards[f]; + const Vec3 up = (f == 2 || f == 3) ? Vec3{0.0f, 0.0f, 1.0f} : Vec3{0.0f, 1.0f, 0.0f}; + const Mat4 viewProj = Mat4::perspective(1.5708f, 1.0f, 0.1f, range) * + Mat4::lookAt(position, position + forward, up); + return ShadowPointFace{viewProj, somePerspective(forward, position)}; + }; + const std::array cube{face(0), face(1), face(2), + face(3), face(4), face(5)}; + REQUIRE(views.setPointLight(0, kLight, ShadowViewMetrics::pointLight(0.004f, range), range, + std::span{cube})); + return views; +} + +} // namespace + +TEST_CASE("a point light that only changes RANGE re-records every face", "[ShadowPassPrepare]") +{ + // The sharpest of the punctual cases, and the one a matrix-only descriptor would fail. Range + // does not appear in a face's projection at all: every matrix here is identical across the two + // frames, and every texel of all six faces still changes, because the stored value is a RATIO + // against that range. A cache comparing only what it could see in the transform would keep six + // maps whose depths are now measured against a different denominator. + const std::vector draws{nearCaster()}; + Prepared out{}; + prepare(out, inputsFor(draws), pointCubeAt(kPointPosition, kPointRange), allFamilies()); + submitFrame(out); + + prepare(out, inputsFor(draws), pointCubeAt(kPointPosition, kPointRange * 2.0f), allFamilies()); + CHECK(everySlotIs(out.plan, ShadowViewGroup::Point, ShadowViewDisposition::Recorded)); + // And nothing else was disturbed: the cascades and the spot saw no change and still reuse. + CHECK(everySlotIs(out.plan, ShadowViewGroup::Cascade, ShadowViewDisposition::Reused)); + CHECK(everySlotIs(out.plan, ShadowViewGroup::Spot, ShadowViewDisposition::Reused)); +} + +TEST_CASE("a point light that moves re-records every face", "[ShadowPassPrepare]") +{ + const std::vector draws{nearCaster()}; + Prepared out{}; + prepare(out, inputsFor(draws), pointCubeAt(kPointPosition, kPointRange), allFamilies()); + submitFrame(out); + + // A cube is one light's map: moving the light changes what every face stores, so a partial + // re-record would leave a light lit from two different positions depending on receiver facing. + prepare(out, inputsFor(draws), + pointCubeAt(kPointPosition + Vec3{0.75f, 0.0f, 0.0f}, kPointRange), allFamilies()); + CHECK(everySlotIs(out.plan, ShadowViewGroup::Point, ShadowViewDisposition::Recorded)); + CHECK(out.plan.pointCubesWhole()); +} + +TEST_CASE("a caster confined to one face re-records that face alone", "[ShadowPassPrepare]") +{ + // Per-FACE granularity is the saving this item is about, and CASTERS are the only thing that + // can exercise it. Not because of how a cube is installed — a light that moves invalidates all + // six faces through the CONTENT LAW, since its position is an input to the radial depth every + // face stores — but because a caster is the only input that can differ BETWEEN faces. (Atomic + // installation is a separate rule: it guarantees a cube is whole, not that it is invalid.) A + // caster only one face can see is where five sixths of the cube stays resident. + const Vec3 onAxis = kPointPosition + Vec3{9.0f, 0.0f, 0.0f}; + std::vector draws{caster(2, boundsAt(onAxis))}; + Prepared out{}; + prepare(out, inputsFor(draws), pointCubeWithRealFaces(kPointPosition, kPointRange), + allFamilies()); + + // The premise, checked rather than assumed: exactly one face offers this caster. Six identical + // frusta would make the assertions below vacuous, and that is precisely what the shared fixture + // has (one marked matrix per face) — hence the real-matrix cube. + // DRAWN, not candidate: every face is OFFERED every caster (that is what a candidate is), and + // the per-face frustum is what decides which of them actually rasterise it. Counting candidates + // here would report six faces seeing it and make the assertions below vacuous. + std::size_t facesDrawingIt = 0; + for (std::size_t slot = 0; slot < kCubeFaceCount; ++slot) + { + facesDrawingIt += out.stats.view(ShadowViewGroup::Point, slot).drawnDraws > 0 ? 1 : 0; + } + REQUIRE(facesDrawingIt == 1); + REQUIRE(out.stats.view(ShadowViewGroup::Point, 0).drawnDraws == 1); + submitFrame(out); + + // Move it along the same axis: only the +X face's content changed. + draws[0].shadowRequest.pose = + ShadowCasterPose::fromModel(Mat4::translate(onAxis + Vec3{0.4f, 0.0f, 0.0f})); + draws[0].shadowBounds = boundsAt(onAxis + Vec3{0.4f, 0.0f, 0.0f}); + prepare(out, inputsFor(draws), pointCubeWithRealFaces(kPointPosition, kPointRange), + allFamilies()); + + CHECK(out.plan.disposition(ShadowViewGroup::Point, 0) == ShadowViewDisposition::Recorded); + for (std::size_t slot = 1; slot < kCubeFaceCount; ++slot) + { + CHECK(out.plan.disposition(ShadowViewGroup::Point, slot) == ShadowViewDisposition::Reused); + } + // Five sixths reused is still a WHOLE cube to the receiver: reuse is sampleable, so the light + // is not half-shadowed while one face catches up. + CHECK(out.plan.pointCubesWhole()); + CHECK(out.plan.sampleableCount(ShadowViewGroup::Point) == kCubeFaceCount); +} + +TEST_CASE("a spot light that moves re-records its view", "[ShadowPassPrepare]") +{ + const std::vector draws{nearCaster()}; + Prepared out{}; + prepare(out, inputsFor(draws), populatedViews(), allFamilies()); + submitFrame(out); + + ShadowRenderViewSet moved = populatedViews(); + REQUIRE(moved.setSpot(0, kLight, markedMatrix(0.25f), + somePerspective(Vec3{0.0f, 0.0f, -1.0f}, Vec3{0.0f, 0.0f, 5.0f}), + ShadowViewMetrics::spot(0.002f, 0.1f, 50.0f))); + prepare(out, inputsFor(draws), moved, allFamilies()); + CHECK(out.plan.disposition(ShadowViewGroup::Spot, 0) == ShadowViewDisposition::Recorded); + CHECK(everySlotIs(out.plan, ShadowViewGroup::Point, ShadowViewDisposition::Reused)); +} + +TEST_CASE("a slot inherited by a different light records rather than reusing", + "[ShadowPassPrepare]") +{ + // Punctual slots are assigned per frame in gather order, so a light leaving the scene COMPACTS + // the ones after it. The residency in slot 0 then describes a map rendered for a light that no + // longer occupies it. Identity is what saves this — content is compared including the logical + // view id — and the failure it prevents is the worst kind: one light lit by another's shadows, + // with every counter and timing still plausible. + const std::vector draws{nearCaster()}; + constexpr auto kOtherLight = static_cast(77); + Prepared out{}; + prepare(out, inputsFor(draws), populatedViews(), allFamilies()); + submitFrame(out); + + ShadowRenderViewSet inherited = populatedViews(); + // The SAME matrix, metrics and slot — only the light differs, which is exactly the case a + // slot-keyed cache would call a hit. + REQUIRE(inherited.setSpot(0, kOtherLight, markedMatrix(0.0f), + somePerspective(Vec3{0.0f, 0.0f, -1.0f}, Vec3{0.0f, 0.0f, 5.0f}), + ShadowViewMetrics::spot(0.002f, 0.1f, 50.0f))); + prepare(out, inputsFor(draws), inherited, allFamilies()); + CHECK(out.plan.disposition(ShadowViewGroup::Spot, 0) == ShadowViewDisposition::Recorded); +} + +TEST_CASE("bias metrics are sampling inputs, not content", "[ShadowPassPrepare]") +{ + // The boundary this whole cache depends on. Metrics never reach the shadow rasteriser — they + // are uploaded to LightUBO every frame from the view SET and read by the RECEIVER — so a + // metrics change must not re-render a map whose depth is identical. If this ever starts + // failing, something has moved a sampling parameter into the raster path, and the fix is there + // rather than here. + const std::vector draws{nearCaster()}; + Prepared out{}; + prepare(out, inputsFor(draws), populatedViews(), allFamilies()); + submitFrame(out); + + ShadowRenderViewSet rebiased = populatedViews(); + REQUIRE(rebiased.setSpot(0, kLight, markedMatrix(0.0f), + somePerspective(Vec3{0.0f, 0.0f, -1.0f}, Vec3{0.0f, 0.0f, 5.0f}), + ShadowViewMetrics::spot(0.03f, 0.2f, 90.0f))); + prepare(out, inputsFor(draws), rebiased, allFamilies()); + CHECK(out.plan.disposition(ShadowViewGroup::Spot, 0) == ShadowViewDisposition::Reused); +} + +TEST_CASE("a point cube inherited by another light records all six faces", "[ShadowPassPrepare]") +{ + // The cube's version of slot inheritance, and it needs its own case rather than trusting the + // spot one: a cube is SIX residency entries, so a law that held for a single view could still + // leave five faces of a departed light's map in place while one re-rendered — a light lit + // correctly from one direction and by its predecessor from the other five. + const std::vector draws{nearCaster()}; + constexpr auto kOtherLight = static_cast(78); + Prepared out{}; + prepare(out, inputsFor(draws), pointCubeAt(kPointPosition, kPointRange), allFamilies()); + submitFrame(out); + + // Same position, same range, same matrices, same slots — only the light's identity differs, + // which is what a compaction after a light leaves the scene produces. + ShadowRenderViewSet inherited = populatedViews(); + const std::array forwards{Vec3{1, 0, 0}, Vec3{-1, 0, 0}, Vec3{0, 1, 0}, + Vec3{0, -1, 0}, Vec3{0, 0, 1}, Vec3{0, 0, -1}}; + const auto face = [&](std::uint8_t f) + { return ShadowPointFace{markedMatrix(0.0f), somePerspective(forwards[f], kPointPosition)}; }; + const std::array cube{face(0), face(1), face(2), + face(3), face(4), face(5)}; + REQUIRE(inherited.setPointLight(0, kOtherLight, + ShadowViewMetrics::pointLight(0.004f, kPointRange), kPointRange, + std::span{cube})); + + prepare(out, inputsFor(draws), inherited, allFamilies()); + CHECK(everySlotIs(out.plan, ShadowViewGroup::Point, ShadowViewDisposition::Recorded)); + CHECK(out.plan.pointCubesWhole()); +} + +TEST_CASE("point bias metrics are sampling inputs, not content", "[ShadowPassPrepare]") +{ + // The point family's version of the boundary, and the distinction it draws is finer than the + // spot one: a point light's RANGE is content (it is the denominator of the stored ratio) while + // the metrics DERIVED from that range are sampling. Changing the metrics alone must therefore + // reuse all six faces — a cube that re-rendered because the receiver's bias inputs moved would + // be doing the work this item exists to avoid, and doing it six times over. + const std::vector draws{nearCaster()}; + Prepared out{}; + prepare(out, inputsFor(draws), pointCubeAt(kPointPosition, kPointRange), allFamilies()); + submitFrame(out); + + ShadowRenderViewSet rebiased = populatedViews(); + const std::array forwards{Vec3{1, 0, 0}, Vec3{-1, 0, 0}, Vec3{0, 1, 0}, + Vec3{0, -1, 0}, Vec3{0, 0, 1}, Vec3{0, 0, -1}}; + const auto face = [&](std::uint8_t f) + { return ShadowPointFace{markedMatrix(0.0f), somePerspective(forwards[f], kPointPosition)}; }; + const std::array cube{face(0), face(1), face(2), + face(3), face(4), face(5)}; + // A different texel scale in the metrics; the RANGE the faces store against is unchanged. + REQUIRE(rebiased.setPointLight(0, kLight, ShadowViewMetrics::pointLight(0.05f, kPointRange), + kPointRange, + std::span{cube})); + + prepare(out, inputsFor(draws), rebiased, allFamilies()); + CHECK(everySlotIs(out.plan, ShadowViewGroup::Point, ShadowViewDisposition::Reused)); +}