playcanvas
Version:
PlayCanvas WebGL game engine
213 lines (194 loc) • 9 kB
JavaScript
var RenderPhysics = pc.createScript('renderPhysics');
RenderPhysics.attributes.add('drawShapes', {
type: 'boolean',
default: false,
title: 'Draw Shapes',
description: 'Draw representations of physics collision shapes'
});
RenderPhysics.attributes.add('opacity', {
type: 'number',
default: 0.5,
min: 0,
max: 1,
title: 'Opacity',
description: 'Opacity of physics collision shapes'
});
RenderPhysics.attributes.add('castShadows', {
type: 'boolean',
default: true,
title: 'Cast Shadows',
description: 'Cast shadows from physics collision shapes'
});
// initialize code called once per entity
RenderPhysics.prototype.initialize = function () {
// Handle attribute change events
this.on('attr:castShadows', function (value, prev) {
this.debugRoot.children.forEach((child) => {
child.model.castShadows = value;
});
}, this);
this.on('attr:opacity', function (value, prev) {
this.debugRoot.children.forEach((child) => {
child.model.meshInstances.forEach((meshInstance) => {
var material = meshInstance.material;
material.opacity = value;
material.update();
});
}, this);
}, this);
this.debugRoot = new pc.Entity('Physics Debug Root');
this.app.root.addChild(this.debugRoot);
// Handle script enable/disable events
this.on('enable', function () {
this.debugRoot = new pc.Entity('Physics Debug Root');
this.app.root.addChild(this.debugRoot);
});
this.on('disable', function () {
var collisionComponents = this.app.root.findComponents('collision');
collisionComponents.forEach((collision) => {
if (collision.hasOwnProperty('_debugShape')) {
delete collision._debugShape;
}
});
this.debugRoot.destroy();
});
};
RenderPhysics.prototype.createModel = function (mesh, material) {
var node = new pc.GraphNode();
var meshInstance = new pc.MeshInstance(mesh, material, node);
var model = new pc.Model();
model.graph = node;
model.meshInstances = [meshInstance];
return model;
};
RenderPhysics.prototype.postUpdate = function (dt) {
// For any existing debug shapes, mark them as not updated (yet)
this.debugRoot.children.forEach((child) => {
child.updated = false;
});
if (this.drawShapes) {
// For each collision component, update its debug shape (creating one
// if one does not exist)
var collisionComponents = this.app.root.findComponents('collision');
collisionComponents.forEach(function (collision) {
if (collision.enabled && collision.entity.enabled) {
var deleteShape = false;
// If the type or shape of the collision components has changed, recreate the visuals
if (collision._debugShape) {
if (collision._debugShape._collisionType !== collision.type) {
deleteShape = true;
} else {
switch (collision.type) {
case 'box':
if (!collision._debugShape._halfExtents.equals(collision.halfExtents)) {
deleteShape = true;
}
break;
case 'cone':
case 'cylinder':
case 'capsule':
if (collision._debugShape._height !== collision.height || collision._debugShape._radius !== collision.radius) {
deleteShape = true;
}
break;
case 'sphere':
if (collision._debugShape._radius !== collision.radius) {
deleteShape = true;
}
break;
}
}
}
if (deleteShape) {
collision._debugShape.destroy();
delete collision._debugShape;
}
// No accompanying debug render shape for this collision component so create one
if (!collision._debugShape) {
var material = new pc.StandardMaterial();
material.diffuse.set(Math.random(), Math.random(), Math.random());
material.opacity = this.opacity;
material.blendType = pc.BLEND_NORMAL;
material.update();
var debugShape = new pc.Entity();
var mesh;
switch (collision.type) {
case 'box':
mesh = pc.Mesh.fromGeometry(this.app.graphicsDevice, new pc.BoxGeometry({
halfExtents: collision.halfExtents
}));
debugShape._halfExtents = collision.halfExtents.clone();
break;
case 'cone':
mesh = pc.Mesh.fromGeometry(this.app.graphicsDevice, new pc.ConeGeometry({
height: collision.height,
radius: collision.radius
}));
debugShape._height = collision.height;
debugShape._radius = collision.radius;
debugShape._axis = collision.axis;
break;
case 'cylinder':
mesh = pc.Mesh.fromGeometry(this.app.graphicsDevice, new pc.CylinderGeometry({
height: collision.height,
radius: collision.radius
}));
debugShape._height = collision.height;
debugShape._radius = collision.radius;
debugShape._axis = collision.axis;
break;
case 'sphere':
mesh = pc.Mesh.fromGeometry(this.app.graphicsDevice, new pc.SphereGeometry({
radius: collision.radius
}));
debugShape._radius = collision.radius;
break;
case 'capsule':
mesh = pc.Mesh.fromGeometry(this.app.graphicsDevice, new pc.CapsuleGeometry({
height: collision.height,
radius: collision.radius
}));
debugShape._height = collision.height;
debugShape._radius = collision.radius;
debugShape._axis = collision.axis;
break;
}
if (mesh) {
debugShape.addComponent('model', {
castShadows: this.castShadows,
type: 'asset'
});
debugShape.model.model = this.createModel(mesh, material);
}
this.debugRoot.addChild(debugShape);
// Cache collision component
debugShape._collision = collision;
debugShape._collisionType = collision.type;
collision._debugShape = debugShape;
}
collision._debugShape.setPosition(collision.getShapePosition());
collision._debugShape.setRotation(collision.getShapeRotation());
// If the shape is a capsule, cone or cylinder, rotate it so that its axis is taken into account
if (collision.type === 'capsule' || collision.type === 'cone' || collision.type === 'cylinder') {
if (collision._debugShape._axis === 0) {
// X
collision._debugShape.rotateLocal(0, 0, -90);
} else if (collision._debugShape._axis === 2) {
// Z
collision._debugShape.rotateLocal(90, 0, 0);
}
}
collision._debugShape.updated = true;
}
}, this);
}
// If a debug shape was not updated this frame, the source collision component
// isn't around any more so we can delete it
this.debugRoot.children.forEach((child) => {
if (!child.updated) {
delete child._collision._debugShape;
delete child._collision;
child.destroy();
}
});
};