Pirate fort example issue when downloaded

Hi, I’ve trying to learn how does the examples work, but I get that the pirate fort example don’t run the ball animation when downloaded.

The particles, the sound and the cannon recoil works, the only issue is on ball that freeze itself on the frame it should be thrown.

Is this expected or its a real bug?

Let s check with @PirateJC

Hey @Alecell

First of all, welcome to the Babylon family! We’re genuinely happy to have you here!

Thanks for flagging this potential issue to us!

Can you give me just a bit more information? What platform, device, browser are you using?

When I click on the link, running on a windows PC through Chrome and on an iPhone through Safari, both experiences work correctly.

I wonder if there’s something specific about the device or browser you’re using that’s causing the issue?

Thank you @PirateJC!
I’ve tried on Brave browser both on Windows and Ubuntu, I also just tried on firefox and don’t worked either.

I get some errors when load the scripts:
BJS - [15:51:05]: AmmoJS is not ready. Please make sure you await Ammo() before using the plugin.
BJS - [15:51:05]: Cannot read property ‘setValue’ of undefined

And this error when I click on the cannon:
[15:52:23]: Physics not enabled. Please use scene.enablePhysics(…) before creating impostors.

Brave Version 1.26.74 Chromium: 91.0.4472.124 (Official Build) (64-bit)
Windows 10
Ubuntu 20
Firefox for Ubuntu 90.0 64 bits

This definitely sounds like a browser compatibility issue with the ammo.js plugin.

Adding @Cedric for some advice.

It seems like the Ammo promise isn’t resolving for one reason or another. @Alecell, did you happen to check your network tab and see if there were any issues loading anything? Perhaps Brave’s CORS/Adblock settings are swatting something?

HTH

Neither Brave nor Firefox generate request errors. (I’ve tried to add the prints here, but I get that I can’t as a new user)

One point is that when I try the playground, on both browsers, just works fine and, when I run locally, I’m running the code with the .html file generated by the download button, maybe this error occurs because I’m not running this on a server?

I can’t try on a local server right now, but as soon as I can I’ll try.

1 Like

That’s a likely scenario – because you’re running the .html page off your local filesystem, I’d guess there’s something (a browser or OS security setting perhaps) that causes silent failures. If you’re using VSCode, the LiveServer extension is super useful for these types of things :slight_smile:

I was not able to repro on the latest versions of Brave or Firefox for Win10. Do you have any extensions running?

@jelster I’ve tried and still not working :confused:

@DarraghBurke yes, but I just tested on ubuntu and tried on incognito but the balls still not being throwed :confused:

I just get that the .zip has another 2 empty folders. These folder should have something inside it?

I’ve tried to add the .zip here but I can’t, thats the sample I downloaded:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

        <title>Babylon.js sample code</title>

        <!-- Babylon.js -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
        <script src="https://preview.babylonjs.com/ammo.js"></script>
        <script src="https://preview.babylonjs.com/cannon.js"></script>
        <script src="https://preview.babylonjs.com/Oimo.js"></script>
        <script src="https://preview.babylonjs.com/earcut.min.js"></script>
        <script src="https://preview.babylonjs.com/babylon.js"></script>
        <script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
        <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script>
        <script src="https://preview.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js"></script>
        <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.js"></script>
        <script src="https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js"></script>
        <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script>
        <script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>

        <style>
            html, body {
                overflow: hidden;
                width: 100%;
                height: 100%;
                margin: 0;
                padding: 0;
            }

            #renderCanvas {
                width: 100%;
                height: 100%;
                touch-action: none;
            }
        </style>
    </head>
<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        var canvas = document.getElementById("renderCanvas");

        var engine = null;
        var scene = null;
        var sceneToRender = null;
        var createDefaultEngine = function() { return new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true,  disableWebGL2Support: false}); };
        
        
        var createScene = async function () {
        
            // This creates a basic Babylon Scene object (non-mesh)
            var scene = new BABYLON.Scene(engine);
        
            scene.clearColor = new BABYLON.Color3(0.31, 0.48, 0.64);
        
            //add an arcRotateCamera to the scene
            var camera = new BABYLON.ArcRotateCamera("camera", BABYLON.Tools.ToRadians(125), BABYLON.Tools.ToRadians(70), 25, new BABYLON.Vector3(0, 3, 0), scene);
            camera.lowerRadiusLimit = 10;
            //camera.upperRadiusLimit = 40;
        
            // This attaches the camera to the canvas
            camera.attachControl(canvas, true);
        
            //enable physics in the scene
            scene.enablePhysics(new BABYLON.Vector3(0,-9.8,0), new BABYLON.AmmoJSPlugin());
        
            //array for holding the cannon and "paired" animation group
            var cannonAnimationPairings = {};
        
            //array for holding readyToPlay status for the cannons
            var cannonReadyToPlay = {};
        
            //create a cannonBall template to clone from, set it's visibility to off.
            var cannonBall = BABYLON.MeshBuilder.CreateSphere("cannonBall", {diameter: 0.3}, scene);
            var 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.
            var killBox = BABYLON.MeshBuilder.CreateBox("killBox", {width:400, depth:400, height:4}, scene);
            killBox.position = new BABYLON.Vector3(0,-50,0);
            killBox.visibility = 0;
        
            //Load the tower assets
            const pirateFortImport = await BABYLON.SceneLoader.ImportMeshAsync("", "https://models.babylonjs.com/pirateFort/", "pirateFort.glb", scene);
            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.SceneLoader.ImportMeshAsync("", "https://models.babylonjs.com/pirateFort/", "cannon.glb", scene);
            //remove the top level root node
            var cannon = cannonImportResult.meshes[0].getChildren()[0];
            cannon.setParent(null);
            cannonImportResult.meshes[0].dispose();
        
            //set the metadata of each mesh to filter on later
            var cannonMeshes = cannon.getChildMeshes();
            for (var 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.
            var animations = [];
            for (var 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.
            var 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
            var 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 = await BABYLON.ParticleHelper.CreateFromSnippetAsync("LCBQ5Y#6", scene);
            smokeBlast.emitter = particleEmitter;
            smokeBlast.targetStopDuration = 0.2;
        
            //load a cannon blast sound
            var cannonBlastSound = new BABYLON.Sound("music", "https://assets.babylonjs.com/sound/cannonBlast.mp3", scene);
        
            //position and rotation data for the placement of the cannon clones
            var 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 (var i = 0; i < 10; i++) {
                var cannonClone = cannon.clone("cannonClone" + i);
                cannonClone.position = cannonPositionArray[i][0];
                cannonClone.rotation = cannonPositionArray[i][1];
                var 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();
        
            //create an array for all particle systems in the scene, loop through it and stop all systems from playing.
            var smokeBlasts = scene.particleSystems;
            for(var i = 0; i < smokeBlasts.length; i++){
                smokeBlasts[i].stop();
            }
        
            //logic of what happens on a click
            scene.onPointerDown = function (evt, pickResult) {
                //check if a mesh was picked and if that mesh has specific metadata
                if (pickResult.pickedMesh && pickResult.pickedMesh.metadata === "cannon") {
                    //find the top level parent (necessary since the cannon is an extra layer below the clone root)
                    var topParent = pickResult.pickedMesh.parent;
                    if (topParent.parent) {
                        topParent = topParent.parent;
                    }
        
                    //wrap all 'play' elements into a check to make sure the cannon can be played.
                    if(cannonReadyToPlay[topParent.name] === 1){
                        //set the readyToPlay status to 0
                        cannonReadyToPlay[topParent.name] = 0;
                        //loop through all of the animation groups in the scene and play the correct group based on the top level parent of the picked mesh.
                        var animationToPlay = cannonAnimationPairings[topParent.name];
                        for (var i = 0; i < scene.animationGroups.length; i++) {
                            if (scene.animationGroups[i].name === animationToPlay) {
                                scene.animationGroups[i].play();
                                //after the animation has finished, set the readyToPlay status for this cannon to 1;
                                scene.animationGroups[i].onAnimationGroupEndObservable.addOnce(() => {
                                    cannonReadyToPlay[topParent.name] = 1;
                                });
                            }
                        }
                        //loop through all particle systems in the scene, loop through all picked mesh submeshes. if there is a matching mesh and particle system emitter, start the particle system.
                        var childMeshes = pickResult.pickedMesh.getChildMeshes();
                        for(var i = 0; i < smokeBlasts.length; i++){
                            for (var j = 0; j < childMeshes.length; j++){
                                if(childMeshes[j] === smokeBlasts[i].emitter){
                                    smokeBlasts[i].start();
        
                                    //clone the cannonBall, make it visible, and add a physics imposter to it. Finally apply a force by scaling the up vector of the particle emitter box
                                    var cannonBallClone = cannonBall.clone("cannonBallClone")
                                    cannonBallClone.visibility = 1;
                                    cannonBallClone.position = childMeshes[j].absolutePosition;
                                    cannonBallClone.physicsImpostor = new BABYLON.PhysicsImpostor(cannonBallClone, BABYLON.PhysicsImpostor.SphereImpostor, {mass:2, friction:0.5, restitution:0}, scene);
                                    cannonBallClone.physicsImpostor.applyImpulse(childMeshes[j].up.scale(40), BABYLON.Vector3.Zero());
                                    
                                    //create an action manager for the cannonBallClone that will fire when intersecting the killbox. It will then dispose of the cannonBallClone.
                                    cannonBallClone.actionManager = new BABYLON.ActionManager(scene);
                                    cannonBallClone.actionManager.registerAction(
                                        new BABYLON.ExecuteCodeAction(
                                            {
                                            trigger:BABYLON.ActionManager.OnIntersectionEnterTrigger,
                                            parameter:killBox
                                            }, 
                                            function(){
                                                cannonBallClone.dispose();
                                            }
                                        )
                                    );
                                }
                            }
                        }
                        cannonBlastSound.play();
                    }
                }
            };
        
            return scene;
        
        };
                window.initFunction = async function() {               
                    var asyncEngineCreation = async function() {
                        try {
                        return createDefaultEngine();
                        } catch(e) {
                        console.log("the available createEngine function failed. Creating the default engine instead");
                        return createDefaultEngine();
                        }
                    }

                    window.engine = await asyncEngineCreation();
        if (!engine) throw 'engine should not be null.';
        window.scene = createScene();};
        initFunction().then(() => {scene.then(returnedScene => { sceneToRender = returnedScene; });
                
            engine.runRenderLoop(function () {
                if (sceneToRender && sceneToRender.activeCamera) {
                    sceneToRender.render();
                }
            });
        });

        // Resize
        window.addEventListener("resize", function () {
            engine.resize();
        });
    </script>
</body>
</html>

Interesting. I don’t see any place where the Ammo framework is getting initialized in the boilerplate PG code… that normally happens automatically – something else in the PG calls something along the lines of ammo = await new Ammo(); but that isn’t happening here. Try adding that line to the top of your createScene method, then modify this line from your previous post:

Maybe there’s an issue with the PG code packager that preps the ZIP files? The Ammo.js initialization thing is fairly recent to the library, so could’ve slipped through the cracks.

HTH

4 Likes

Hey @jelster with theses changes its working now!!
No errors are throwed on console either.

Really really thank you, you guys helped me a lot and I’m very glad for this! :grinning_face_with_smiling_eyes:

2 Likes

The download will get fixed in the next playground nightly