Found the issue. There’s global data that wasn’t reset. I have a fix coming into a PR but in the meantime, it’s easy to monkey patch (see end of file)
FYI, destroying an app is not a common user path so there may be other issues like this. The bug reports and repro projects are very helpful, thank you!
// Playcanvas
import * as pc from 'playcanvas';
function demo() {
const canvas = document.getElementById('application-canvas');
// Create the application and start the update loop
const app = new pc.Application(canvas);
app.start();
// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);
window.addEventListener('resize', () => {
app.resizeCanvas(canvas.width, canvas.height);
});
app.scene.ambientLight = new pc.Color(0.2, 0.2, 0.2);
// Set the gravity for our rigid bodies
app.systems.rigidbody.gravity.set(0, -9.81, 0);
function createMaterial(color) {
const material = new pc.StandardMaterial();
material.diffuse = color;
// we need to call material.update when we change its properties
material.update();
return material;
}
// create a few materials for our objects
const red = createMaterial(new pc.Color(1, 0.3, 0.3));
const gray = createMaterial(new pc.Color(0.7, 0.7, 0.7));
// *********** Create our floor *******************
const floor = new pc.Entity();
floor.addComponent('model', {
type: 'box',
material: gray,
});
// scale it
floor.setLocalScale(10, 1, 10);
// add a rigidbody component so that other objects collide with it
floor.addComponent('rigidbody', {
type: 'static',
restitution: 0.5,
});
// add a collision component
floor.addComponent('collision', {
type: 'box',
halfExtents: new pc.Vec3(5, 0.5, 5),
});
// add the floor to the hierarchy
app.root.addChild(floor);
// *********** Create lights *******************
// make our scene prettier by adding a directional light
const light = new pc.Entity();
light.addComponent('light', {
type: 'directional',
color: new pc.Color(1, 1, 1),
castShadows: true,
shadowBias: 0.2,
shadowDistance: 16,
normalOffsetBias: 0.05,
shadowResolution: 2048,
});
// set the direction for our light
light.setLocalEulerAngles(45, 30, 0);
// Add the light to the hierarchy
app.root.addChild(light);
// *********** Create camera *******************
// Create an Entity with a camera component
const camera = new pc.Entity();
camera.addComponent('camera', {
clearColor: new pc.Color(0.5, 0.5, 0.8),
farClip: 50,
});
// add the camera to the hierarchy
app.root.addChild(camera);
// Move the camera a little further away
camera.translate(0, 10, 15);
camera.lookAt(0, 0, 0);
// *********** Create templates *******************
// Create a template for a falling box
// It will have a model component of type 'box'...
const boxTemplate = new pc.Entity();
boxTemplate.addComponent('model', {
type: 'box',
castShadows: true,
material: gray,
});
// ...a rigidbody component of type 'dynamic' so that it is simulated
// by the physics engine...
boxTemplate.addComponent('rigidbody', {
type: 'dynamic',
mass: 50,
restitution: 0.5,
});
// ... and a collision component of type 'box'
boxTemplate.addComponent('collision', {
type: 'box',
halfExtents: new pc.Vec3(0.5, 0.5, 0.5),
});
// Create other shapes too for variety...
// A sphere...
const sphereTemplate = new pc.Entity();
sphereTemplate.addComponent('model', {
type: 'sphere',
castShadows: true,
material: gray,
});
sphereTemplate.addComponent('rigidbody', {
type: 'dynamic',
mass: 50,
restitution: 0.5,
});
sphereTemplate.addComponent('collision', {
type: 'sphere',
radius: 0.5,
});
// A capsule...
const capsuleTemplate = new pc.Entity();
capsuleTemplate.addComponent('model', {
type: 'capsule',
castShadows: true,
material: gray,
});
capsuleTemplate.addComponent('rigidbody', {
type: 'dynamic',
mass: 50,
restitution: 0.5,
});
capsuleTemplate.addComponent('collision', {
type: 'capsule',
radius: 0.5,
height: 2,
});
// A cylinder...
const cylinderTemplate = new pc.Entity();
cylinderTemplate.addComponent('model', {
type: 'cylinder',
castShadows: true,
material: gray,
});
cylinderTemplate.addComponent('rigidbody', {
type: 'dynamic',
mass: 50,
restitution: 0.5,
});
cylinderTemplate.addComponent('collision', {
type: 'cylinder',
radius: 0.5,
height: 1,
});
// add all the templates to an array so that
// we can randomly spawn them
const templates = [boxTemplate, sphereTemplate, capsuleTemplate, cylinderTemplate];
// disable the templates because we don't want them to be visible
// we'll just use them to clone other Entities
templates.forEach((template) => {
template.enabled = false;
});
// *********** Update Function *******************
// initialize variables for our update function
let timer = 0;
let count = 40;
// Set an update function on the application's update event
app.on('update', (dt) => {
// create a falling box every 0.2 seconds
if (count > 0) {
timer -= dt;
if (timer <= 0) {
count--;
timer = 0.2;
// Clone a random template and position it above the floor
const template = templates[Math.floor(pc.math.random(0, templates.length))];
const clone = template.clone();
// enable the clone because the template is disabled
clone.enabled = true;
app.root.addChild(clone);
clone.rigidbody.teleport(pc.math.random(-1, 1), 10, pc.math.random(-1, 1));
}
}
// Show active bodies in red and frozen bodies in gray
app.root.findComponents('rigidbody').forEach((body) => {
body.entity.model.material = body.isActive() ? red : gray;
});
});
};
// check for wasm module support
function wasmSupported() {
try {
if (typeof WebAssembly === 'object' && typeof WebAssembly.instantiate === 'function') {
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
if (module instanceof WebAssembly.Module) return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
}
} catch (e) { }
return false;
}
// load a script
function loadScriptAsync(url, doneCallback) {
const tag = document.createElement('script');
tag.onload = function () {
doneCallback();
};
tag.onerror = function () {
throw new Error(`failed to load ${url}`);
};
tag.async = true;
tag.src = url;
document.head.appendChild(tag);
}
// load and initialize a wasm module
function loadWasmModuleAsync(moduleName, jsUrl, binaryUrl, doneCallback) {
loadScriptAsync(jsUrl, () => {
const lib = window[moduleName];
window[`${moduleName}Lib`] = lib;
lib({
locateFile() {
return binaryUrl;
},
}).then((instance) => {
window[moduleName] = instance;
doneCallback();
});
});
}
const loadAmmo = () => new Promise((resolve) => {
if (wasmSupported()) {
loadWasmModuleAsync('Ammo', './lib/ammo.wasm.js', './lib/ammo.wasm.wasm', () => { resolve(); });
} else {
loadWasmModuleAsync('Ammo', './lib/ammo.js', '', () => { resolve(); });
}
});
loadAmmo().then(() => {
console.log('Start app 1');
demo();
setTimeout(() => {
console.log('Destroy app');
pc.app.destroy();
setTimeout(() => {
console.log('Start app 2');
demo();
}, 1000);
}, 5000);
pc.ComponentSystem.destroy = function () {
pc.ComponentSystem.off('initialize');
pc.ComponentSystem.off('postInitialize');
pc.ComponentSystem.off('toolsUpdate');
pc.ComponentSystem.off('update');
pc.ComponentSystem.off('animationUpdate');
pc.ComponentSystem.off('fixedUpdate');
pc.ComponentSystem.off('postUpdate');
pc.ComponentSystem._init = [];
pc.ComponentSystem._postInit = [];
pc.ComponentSystem._toolsUpdate = [];
pc.ComponentSystem._update = [];
pc.ComponentSystem._animationUpdate = [];
pc.ComponentSystem._fixedUpdate = [];
pc.ComponentSystem._postUpdate = [];
};
});