"use strict"; var engine; var canvas; var currentScene; var config; var justOnce; var threshold = 25; var errorRatio = 2.5; // Overload the random to make it deterministic var seed = 100000, constant = Math.pow(2, 13) + 1, prime = 37, maximum = Math.pow(2, 50); Math.random = function() { seed *= constant; seed += prime; seed %= maximum; return seed / maximum; } function compare(renderData, referenceCanvas) { var width = referenceCanvas.width; var height = referenceCanvas.height; var size = width * height * 4; var referenceContext = referenceCanvas.getContext("2d"); var referenceData = referenceContext.getImageData(0, 0, width, height); var differencesCount = 0; for (var index = 0; index < size; index += 4) { if (Math.abs(renderData[index] - referenceData.data[index]) < threshold && Math.abs(renderData[index + 1] - referenceData.data[index + 1]) < threshold && Math.abs(renderData[index + 2] - referenceData.data[index + 2]) < threshold) { continue; } referenceData.data[index] = 255; referenceData.data[index + 1] *= 0.5; referenceData.data[index + 2] *= 0.5; differencesCount++; } referenceContext.putImageData(referenceData, 0, 0); if (differencesCount) { console.log("Pixel difference: " + differencesCount + " pixels."); } return (differencesCount * 100) / (width * height) > errorRatio; } function getRenderData(canvas, engine) { var width = canvas.width; var height = canvas.height; var renderData = engine.readPixels(0, 0, width, height); var numberOfChannelsByLine = width * 4; var halfHeight = height / 2; for (var i = 0; i < halfHeight; i++) { for (var j = 0; j < numberOfChannelsByLine; j++) { var currentCell = j + i * numberOfChannelsByLine; var targetLine = height - i - 1; var targetCell = j + targetLine * numberOfChannelsByLine; var temp = renderData[currentCell]; renderData[currentCell] = renderData[targetCell]; renderData[targetCell] = temp; } } return renderData; } function saveRenderImage(data, canvas) { var width = canvas.width; var height = canvas.height; var screenshotCanvas = document.createElement('canvas'); screenshotCanvas.width = width; screenshotCanvas.height = height; var context = screenshotCanvas.getContext('2d'); var imageData = context.createImageData(width, height); var castData = imageData.data; castData.set(data); context.putImageData(imageData, 0, 0); return screenshotCanvas.toDataURL(); } function evaluate(test, resultCanvas, result, renderImage, index, waitRing, done) { seed = 100000; var renderData = getRenderData(canvas, engine); var testRes = true; // gl check var gl = engine._gl; if (gl.getError() !== 0) { result.classList.add("failed"); result.innerHTML = "×"; testRes = false; console.log('%c failed (gl error)', 'color: red'); } else { // Visual check if (!test.onlyVisual) { if (compare(renderData, resultCanvas)) { result.classList.add("failed"); result.innerHTML = "×"; testRes = false; console.log('%c failed', 'color: red'); } else { result.innerHTML = "✔"; testRes = true; console.log('%c validated', 'color: green'); } } } waitRing.classList.add("hidden"); var renderB64 = saveRenderImage(renderData, canvas); renderImage.src = renderB64; engine.applyStates(); currentScene.dispose(); currentScene = null; engine.setHardwareScalingLevel(1); done(testRes, renderB64); } function processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing, done) { currentScene.useConstantAnimationDeltaTime = true; var renderCount = test.renderCount || 1; currentScene.executeWhenReady(function() { if (currentScene.activeCamera && currentScene.activeCamera.useAutoRotationBehavior) { currentScene.activeCamera.useAutoRotationBehavior = false; } engine.runRenderLoop(function() { try { currentScene.render(); renderCount--; if (renderCount === 0) { engine.stopRenderLoop(); evaluate(test, resultCanvas, result, renderImage, index, waitRing, done); } } catch (e) { console.error(e); done(false); } }); }); } function runTest(index, done) { if (index >= config.tests.length) { done(false); } var test = config.tests[index]; var container = document.createElement("div"); container.id = "container#" + index; container.className = "container"; document.body.appendChild(container); var titleContainer = document.createElement("div"); titleContainer.className = "containerTitle"; container.appendChild(titleContainer); var title = document.createElement("div"); title.className = "title"; titleContainer.appendChild(title); var result = document.createElement("div"); result.className = "result"; titleContainer.appendChild(result); var waitRing = document.createElement("img"); waitRing.className = "waitRing"; titleContainer.appendChild(waitRing); waitRing.src = "/tests/validation/loading.gif"; var resultCanvas = document.createElement("canvas"); resultCanvas.className = "resultImage"; container.appendChild(resultCanvas); title.innerHTML = test.title; console.log("Running " + test.title); var resultContext = resultCanvas.getContext("2d"); var img = new Image(); img.onload = function() { resultCanvas.width = img.width; resultCanvas.height = img.height; resultContext.drawImage(img, 0, 0); } img.src = "/tests/validation/ReferenceImages/" + test.referenceImage; var renderImage = new Image(); renderImage.className = "renderImage"; container.appendChild(renderImage); location.href = "#" + container.id; if (test.sceneFolder) { BABYLON.SceneLoader.Load(config.root + test.sceneFolder, test.sceneFilename, engine, function(newScene) { currentScene = newScene; processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing, done); }, null, function(loadedScene, msg) { console.error(msg); done(false); }); } else if (test.playgroundId) { if (test.playgroundId[0] !== "#" || test.playgroundId.indexOf("#", 1) === -1) { console.error("Invalid playground id"); done(false); return; } var snippetUrl = "//babylonjs-api2.azurewebsites.net/snippets"; var pgRoot = "/Playground" var retryTime = 500; var maxRetry = 2; var retry = 0; var onError = function() { retry++; if (retry < maxRetry) { setTimeout(function() { loadPG(); }, retryTime); } else { // Skip the test as we can not fetch the source. done(true); } } var loadPG = function() { var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState === 4) { try { xmlHttp.onreadystatechange = null; var snippet = JSON.parse(xmlHttp.responseText)[0]; var code = JSON.parse(snippet.jsonPayload).code.toString(); code = code.replace(/\/textures\//g, pgRoot + "/textures/"); code = code.replace(/"textures\//g, "\"" + pgRoot + "/textures/"); code = code.replace(/\/scenes\//g, pgRoot + "/scenes/"); code = code.replace(/"scenes\//g, "\"" + pgRoot + "/scenes/"); currentScene = eval(code + "\r\ncreateScene(engine)"); if (currentScene.then) { // Handle if createScene returns a promise currentScene.then(function(scene) { currentScene = scene; processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing, done); }).catch(function(e) { console.error(e); onError(); }) } else { // Handle if createScene returns a scene processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing, done); } } catch (e) { console.error(e); onError(); } } } xmlHttp.onerror = function() { console.error("Network error during test load."); onError(); } xmlHttp.open("GET", snippetUrl + test.playgroundId.replace(/#/g, "/")); xmlHttp.send(); } loadPG(); } else { // Fix references if (test.specificRoot) { BABYLON.Tools.BaseUrl = config.root + test.specificRoot; } var request = new XMLHttpRequest(); request.open('GET', config.root + test.scriptToRun, true); request.onreadystatechange = function() { if (request.readyState === 4) { try { request.onreadystatechange = null; var scriptToRun = request.responseText.replace(/..\/..\/assets\//g, config.root + "/Assets/"); scriptToRun = scriptToRun.replace(/..\/..\/Assets\//g, config.root + "/Assets/"); scriptToRun = scriptToRun.replace(/\/assets\//g, config.root + "/Assets/"); scriptToRun = scriptToRun.replace(/\/Assets\//g, config.root + "/Assets/"); if (test.replace) { var split = test.replace.split(","); for (var i = 0; i < split.length; i += 2) { var source = split[i].trim(); var destination = split[i + 1].trim(); scriptToRun = scriptToRun.replace(source, destination); } } if (test.replaceUrl) { var split = test.replaceUrl.split(","); for (var i = 0; i < split.length; i++) { var source = split[i].trim(); var regex = new RegExp(source, "g"); scriptToRun = scriptToRun.replace(regex, config.root + test.rootPath + source); } } currentScene = eval(scriptToRun + test.functionToCall + "(engine)"); processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing, done); } catch (e) { console.error(e); done(false); } } }; request.onerror = function() { console.error("Network error during test load."); done(false); } request.send(null); } } function init() { BABYLON.SceneLoader.ShowLoadingScreen = false; BABYLON.SceneLoader.ForceFullSceneLoadingForIncremental = true; // Draco configuration BABYLON.DracoCompression.Configuration.decoder = { wasmUrl: "../../dist/preview%20release/draco_wasm_wrapper_gltf.js", wasmBinaryUrl: "../../dist/preview%20release/draco_decoder_gltf.wasm", fallbackUrl: "../../dist/preview%20release/draco_decoder_gltf.js" }; canvas = document.createElement("canvas"); canvas.className = "renderCanvas"; document.body.appendChild(canvas); engine = new BABYLON.Engine(canvas, false); engine.enableOfflineSupport = false; engine.setDitheringState(false); } function dispose() { engine.dispose(); currentScene = null; engine = null; document.body.removeChild(canvas); canvas = null; } init();