import * as BABYLON from "@babylonjs/core"; import "@babylonjs/loaders"; import Ammo from "ammojs-typed"; export function runTest(frames = 10) { const classNames = Object.keys(BABYLON); BABYLON.Logger.LogLevels = BABYLON.Logger.NoneLogLevel; const engine = new BABYLON.NullEngine(); const scene = createScene(engine); for (let i = 0; i < frames; i++) { scene.render(); } return { classNames, cameraRotationLength: scene.cameras[0].rotation.length(), }; } function createScene(engine) { const scene = new BABYLON.Scene(engine); scene.useConstantAnimationDeltaTime = true; const camera = new BABYLON.FreeCamera( "camera1", new BABYLON.Vector3(0, 5, -10), scene ); camera.setTarget(BABYLON.Vector3.Zero()); const light = new BABYLON.HemisphericLight( "light1", new BABYLON.Vector3(0, 1, 0), scene ); const sphere = BABYLON.MeshBuilder.CreateSphere( "sphere1", { segments: 16, diameter: 2, sideOrientation: BABYLON.Mesh.FRONTSIDE }, scene ); sphere.position.y = 1; const ground = BABYLON.MeshBuilder.CreateGround( "ground1", { width: 6, height: 6, subdivisions: 2, updatable: false }, scene ); return scene; } export async function runComplexScene( fortData, cannonData, particleData, frames = 10 ) { const classNames = Object.keys(BABYLON); BABYLON.Logger.LogLevels = BABYLON.Logger.NoneLogLevel; const engine = new BABYLON.NullEngine({ deterministicLockstep: true, }); const scene = await createComplexScene( engine, fortData, cannonData, particleData ); for (let i = 0; i < frames; i++) { scene.animate(); scene.render(); } // Leak state to the outside. return { classNames, cameraRotationLength: scene.cameras[0].rotation.length(), }; } async function createComplexScene(engine, fortData, cannonData, particleData) { // Source: // - https://www.babylonjs.com/featureDemos/ // - https://playground.babylonjs.com/#C21DGD#2 const scene = new BABYLON.Scene(engine); scene.useConstantAnimationDeltaTime = true; scene.clearColor = new BABYLON.Color3(0.31, 0.48, 0.64); let camera = new BABYLON.ArcRotateCamera( "camera", BABYLON.Tools.ToRadians(125), BABYLON.Tools.ToRadians(70), 25, new BABYLON.Vector3(0, 3, 0), scene ); camera.lowerRadiusLimit = 10; const ammo = await Ammo(); scene.enablePhysics( new BABYLON.Vector3(0, -9.8, 0), new BABYLON.AmmoJSPlugin(true, ammo) ); let cannonAnimationPairings = {}; let cannonReadyToPlay = {}; //create a cannonBall template to clone from, set it's visibility to off. let cannonBall = BABYLON.MeshBuilder.CreateSphere( "cannonBall", { diameter: 0.3 }, scene ); let cannonBallMat = new BABYLON.StandardMaterial("cannonBallMaterial", scene); cannonBallMat.diffuseColor = BABYLON.Color3.Black(); cannonBallMat.specularPower = 256; cannonBall.material = cannonBallMat; cannonBall.visibility = false; //create a large box far underneath the tower, that will act as a trigger to destroy the cannonballs. let killBox = BABYLON.MeshBuilder.CreateBox( "killBox", { width: 400, depth: 400, height: 4 }, scene ); killBox.position = new BABYLON.Vector3(0, -50, 0); killBox.visibility = 0; const pirateFortImport = await BABYLON.ImportMeshAsync(fortData, scene, { pluginExtension: ".glb", }); pirateFortImport.meshes[0].name = "pirateFort"; scene.getMeshByName("sea").material.needDepthPrePass = true; scene.getLightByName("Sun").intensity = 12; //Load the cannon model and create clones const cannonImportResult = await BABYLON.ImportMeshAsync(cannonData, scene, { pluginExtension: ".glb", }); //remove the top level root node let cannon = cannonImportResult.meshes[0].getChildren()[0]; cannon.setParent(null); cannonImportResult.meshes[0].dispose(); //set the metadata of each mesh to filter on later let cannonMeshes = cannon.getChildMeshes(); for (let i = 0; i < cannonMeshes.length; i++) { cannonMeshes[i].metadata = "cannon"; } const importedAnimGroups = cannonImportResult.animationGroups; //loop through all imported animation groups and copy the animation curve data to an array. let animations = []; for (let i = 0; i < importedAnimGroups.length; i++) { importedAnimGroups[i].stop(); animations.push(importedAnimGroups[i].targetedAnimations[0].animation); importedAnimGroups[i].dispose(); } //create a new animation group and add targeted animations based on copied curve data from the "animations" array. let cannonAnimGroup = new BABYLON.AnimationGroup("cannonAnimGroup"); cannonAnimGroup.addTargetedAnimation( animations[0], cannon.getChildMeshes()[1] ); cannonAnimGroup.addTargetedAnimation( animations[1], cannon.getChildMeshes()[0] ); //create a box for particle emission, position it at the muzzle of the cannon, turn off visibility and parent it to the cannon mesh let particleEmitter = BABYLON.MeshBuilder.CreateBox( "particleEmitter", { size: 0.05 }, scene ); particleEmitter.position = new BABYLON.Vector3(0, 0.76, 1.05); particleEmitter.rotation.x = BABYLON.Tools.ToRadians(78.5); particleEmitter.isVisible = false; particleEmitter.setParent(cannon.getChildMeshes()[1]); //load particle system from the snippet server and set the emitter to the particleEmitter. Set its stopDuration. const smokeBlast = BABYLON.ParticleSystem.Parse(particleData, scene, ""); smokeBlast.emitter = particleEmitter; smokeBlast.targetStopDuration = 0.2; //position and rotation data for the placement of the cannon clones let cannonPositionArray = [ [ new BABYLON.Vector3(0.97, 5.52, 1.79), new BABYLON.Vector3( BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(180) ), ], [ new BABYLON.Vector3(1.08, 2.32, 3.05), new BABYLON.Vector3( BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(180) ), ], [ new BABYLON.Vector3(1.46, 2.35, -0.73), new BABYLON.Vector3( BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(90), BABYLON.Tools.ToRadians(180) ), ], [ new BABYLON.Vector3(1.45, 5.52, -1.66), new BABYLON.Vector3( BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(90), BABYLON.Tools.ToRadians(180) ), ], [ new BABYLON.Vector3(1.49, 8.69, -0.35), new BABYLON.Vector3( BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(90), BABYLON.Tools.ToRadians(180) ), ], [ new BABYLON.Vector3(-1.37, 8.69, -0.39), new BABYLON.Vector3( BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(-90), BABYLON.Tools.ToRadians(180) ), ], [ new BABYLON.Vector3(0.58, 4, -2.18), new BABYLON.Vector3( BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(180), BABYLON.Tools.ToRadians(180) ), ], [ new BABYLON.Vector3(1.22, 8.69, -2.5), new BABYLON.Vector3( BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(180), BABYLON.Tools.ToRadians(180) ), ], [ new BABYLON.Vector3(-1.31, 2.33, -2.45), new BABYLON.Vector3( BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(180), BABYLON.Tools.ToRadians(180) ), ], [ new BABYLON.Vector3(-3.54, 5.26, -2.12), new BABYLON.Vector3( BABYLON.Tools.ToRadians(0), BABYLON.Tools.ToRadians(-90), BABYLON.Tools.ToRadians(180) ), ], ]; //create 10 cannon clones, each with unique position/rotation data. Note that particle systems are cloned with parent meshes //also create 10 new animation groups with targeted animations applied to the newly cloned meshes for (let i = 0; i < 10; i++) { let cannonClone = cannon.clone("cannonClone" + i); cannonClone.position = cannonPositionArray[i][0]; cannonClone.rotation = cannonPositionArray[i][1]; let cannonAnimGroupClone = new BABYLON.AnimationGroup( "cannonAnimGroupClone" + i ); cannonAnimGroupClone.addTargetedAnimation( cannonAnimGroup.targetedAnimations[0].animation, cannonClone.getChildMeshes()[1] ); cannonAnimGroupClone.addTargetedAnimation( cannonAnimGroup.targetedAnimations[1].animation, cannonClone.getChildMeshes()[0] ); //store a key/value pair of each clone name and the name of the associated animation group name. cannonAnimationPairings[cannonClone.name] = cannonAnimGroupClone.name; //store key/value pair for the cannon name and it's readyToPlay status as 1; cannonReadyToPlay[cannonClone.name] = 1; } //dispose of the original cannon, animation group, and particle system cannon.dispose(); cannonAnimGroup.dispose(); smokeBlast.dispose(); return scene; }