Skip to content

Commit 8aa699d

Browse files
mvaligurskyMartin Valigurskywilleastcott
authored
Support for cameras stored in the GLB file (playcanvas#3354)
* Support for cameras stored in the GLB file * Update examples/src/examples/loaders/glb.tsx Co-authored-by: Will Eastcott <will@playcanvas.com> Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com> Co-authored-by: Will Eastcott <will@playcanvas.com>
1 parent b49251b commit 8aa699d

File tree

4 files changed

+118
-27
lines changed

4 files changed

+118
-27
lines changed
4.19 KB
Binary file not shown.

examples/src/examples/loaders/glb.tsx

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,46 @@ class GLBExample extends Example {
88
// @ts-ignore: override class function
99
example(canvas: HTMLCanvasElement): void {
1010

11+
// The example demonstrates loading of glb file, which contains meshes,
12+
// lights and cameras, and switches between the cameras every 2 seconds.
13+
1114
// Create the app and start the update loop
1215
const app = new pc.Application(canvas, {});
1316

14-
app.scene.ambientLight = new pc.Color(0.2, 0.2, 0.2);
17+
// the array will store loaded cameras
18+
let camerasComponents: Array<pc.CameraComponent> = null;
1519

1620
// Load a glb file as a container
17-
const url = "static/assets/models/playcanvas-cube.glb";
21+
const url = "static/assets/models/geometry-camera-light.glb";
1822
app.assets.loadFromUrl(url, "container", function (err, asset) {
1923
app.start();
2024

2125
// create an instance using render component
22-
const entity = asset.resource.instantiateRenderEntity({
23-
castShadows: true
24-
});
26+
const entity = asset.resource.instantiateRenderEntity();
2527
app.root.addChild(entity);
2628

27-
// Create an Entity with a camera component
28-
const camera = new pc.Entity();
29-
camera.addComponent("camera", {
30-
clearColor: new pc.Color(0.2, 0.2, 0.2)
31-
});
32-
camera.translate(0, 0, 4);
33-
app.root.addChild(camera);
34-
35-
// Create an entity with a omni light component
36-
const light = new pc.Entity();
37-
light.addComponent("light", {
38-
type: "omni"
29+
// find all cameras - by default there are disabled
30+
// set their aspect ratio to automatic to work with any window size
31+
camerasComponents = entity.findComponents("camera");
32+
camerasComponents.forEach((component) => {
33+
component.aspectRatioMode = pc.ASPECT_AUTO;
3934
});
40-
light.setLocalPosition(1, 1, 5);
41-
app.root.addChild(light);
4235

36+
let time = 0;
37+
let activeCamera = 0;
4338
app.on("update", function (dt) {
44-
if (entity) {
45-
entity.rotate(4 * dt, -20 * dt, 0);
39+
time -= dt;
40+
41+
// change the camera every few seconds
42+
if (time <= 0) {
43+
time = 2;
44+
45+
// disable current camera
46+
camerasComponents[activeCamera].enabled = false;
47+
48+
// activate next camera
49+
activeCamera = (activeCamera + 1) % camerasComponents.length;
50+
camerasComponents[activeCamera].enabled = true;
4651
}
4752
});
4853
});

src/resources/container.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,22 @@ class ContainerResource {
152152
}
153153

154154
// light - clone (additional child) entity with the light component
155+
// cannot clone the component as additional entity has a rotation to handle different light direction
155156
if (glb.lights) {
156157
const lightEntity = glb.lights.get(gltfNode);
157158
if (lightEntity) {
158159
entity.addChild(lightEntity.clone());
159160
}
160161
}
162+
163+
// camera
164+
if (glb.cameras) {
165+
const cameraEntity = glb.cameras.get(gltfNode);
166+
if (cameraEntity) {
167+
// clone camera component into the entity
168+
cameraEntity.camera.system.cloneComponent(cameraEntity, entity);
169+
}
170+
}
161171
}
162172
}
163173

@@ -333,6 +343,7 @@ class ContainerResource {
333343
* | global | x | | | x |
334344
* | node | x | x | | x |
335345
* | light | x | x | | x |
346+
* | camera | x | x | | x |
336347
* | animation | x | | | x |
337348
* | material | x | x | | x |
338349
* | image | x | | x | x |

src/resources/parser/glb-parser.js

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ import { VertexBuffer } from '../../graphics/vertex-buffer.js';
2727
import { VertexFormat } from '../../graphics/vertex-format.js';
2828

2929
import {
30-
BLEND_NONE, BLEND_NORMAL, LIGHTFALLOFF_INVERSESQUARED
30+
BLEND_NONE, BLEND_NORMAL, LIGHTFALLOFF_INVERSESQUARED,
31+
PROJECTION_ORTHOGRAPHIC, PROJECTION_PERSPECTIVE,
32+
ASPECT_MANUAL, ASPECT_AUTO
3133
} from '../../scene/constants.js';
3234
import { calculateNormals } from '../../scene/procedural.js';
3335
import { GraphNode } from '../../scene/graph-node.js';
@@ -61,6 +63,7 @@ class GlbResources {
6163
this.renders = null;
6264
this.skins = null;
6365
this.lights = null;
66+
this.cameras = null;
6467
}
6568

6669
destroy() {
@@ -1398,6 +1401,42 @@ const createNode = function (gltfNode, nodeIndex) {
13981401
return entity;
13991402
};
14001403

1404+
// creates a camera component on the supplied node, and returns it
1405+
const createCamera = function (gltfCamera, node) {
1406+
1407+
const projection = gltfCamera.type === "orthographic" ? PROJECTION_ORTHOGRAPHIC : PROJECTION_PERSPECTIVE;
1408+
const gltfProperties = projection === PROJECTION_ORTHOGRAPHIC ? gltfCamera.orthographic : gltfCamera.perspective;
1409+
1410+
const componentData = {
1411+
enabled: false,
1412+
projection: projection,
1413+
nearClip: gltfProperties.znear,
1414+
aspectRatioMode: ASPECT_AUTO
1415+
};
1416+
1417+
if (gltfProperties.zfar) {
1418+
componentData.farClip = gltfProperties.zfar;
1419+
}
1420+
1421+
if (projection === PROJECTION_ORTHOGRAPHIC) {
1422+
componentData.orthoHeight = 0.5 * gltfProperties.ymag;
1423+
if (gltfProperties.ymag) {
1424+
componentData.aspectRatioMode = ASPECT_MANUAL;
1425+
componentData.aspectRatio = gltfProperties.xmag / gltfProperties.ymag;
1426+
}
1427+
} else {
1428+
componentData.fov = gltfProperties.yfov * math.RAD_TO_DEG;
1429+
if (gltfProperties.aspectRatio) {
1430+
componentData.aspectRatioMode = ASPECT_MANUAL;
1431+
componentData.aspectRatio = gltfProperties.aspectRatio;
1432+
}
1433+
}
1434+
1435+
const cameraEntity = new Entity(gltfCamera.name);
1436+
cameraEntity.addComponent("camera", componentData);
1437+
return cameraEntity;
1438+
};
1439+
14011440
// creates light component, adds it to the node and returns the created light component
14021441
const createLight = function (gltfLight, node) {
14031442

@@ -1421,13 +1460,12 @@ const createLight = function (gltfLight, node) {
14211460

14221461
// Rotate to match light orientation in glTF specification
14231462
// Note that this adds a new entity node into the hierarchy that does not exist in the gltf hierarchy
1424-
var lightNode = new Entity(node.name);
1425-
lightNode.rotateLocal(90, 0, 0);
1463+
const lightEntity = new Entity(node.name);
1464+
lightEntity.rotateLocal(90, 0, 0);
14261465

14271466
// add component
1428-
lightNode.addComponent("light", lightProps);
1429-
1430-
return lightNode;
1467+
lightEntity.addComponent("light", lightProps);
1468+
return lightEntity;
14311469
};
14321470

14331471
const createSkins = function (device, gltf, nodes, bufferViews) {
@@ -1563,6 +1601,41 @@ const createScenes = function (gltf, nodes) {
15631601
return scenes;
15641602
};
15651603

1604+
const createCameras = function (gltf, nodes, options) {
1605+
1606+
let cameras = null;
1607+
1608+
if (gltf.hasOwnProperty('nodes') && gltf.hasOwnProperty('cameras') && gltf.cameras.length > 0) {
1609+
1610+
const preprocess = options && options.camera && options.camera.preprocess;
1611+
const process = options && options.camera && options.camera.process || createCamera;
1612+
const postprocess = options && options.camera && options.camera.postprocess;
1613+
1614+
gltf.nodes.forEach(function (gltfNode, nodeIndex) {
1615+
if (gltfNode.hasOwnProperty('camera')) {
1616+
const gltfCamera = gltf.cameras[gltfNode.camera];
1617+
if (gltfCamera) {
1618+
if (preprocess) {
1619+
preprocess(gltfCamera);
1620+
}
1621+
const camera = process(gltfCamera, nodes[nodeIndex]);
1622+
if (postprocess) {
1623+
postprocess(gltfCamera, camera);
1624+
}
1625+
1626+
// add the camera to node->camera map
1627+
if (camera) {
1628+
if (!cameras) cameras = new Map();
1629+
cameras.set(gltfNode, camera);
1630+
}
1631+
}
1632+
}
1633+
});
1634+
}
1635+
1636+
return cameras;
1637+
};
1638+
15661639
const createLights = function (gltf, nodes, options) {
15671640

15681641
let lights = null;
@@ -1637,6 +1710,7 @@ const createResources = function (device, gltf, bufferViews, textureAssets, opti
16371710
const nodes = createNodes(gltf, options);
16381711
const scenes = createScenes(gltf, nodes);
16391712
const lights = createLights(gltf, nodes, options);
1713+
const cameras = createCameras(gltf, nodes, options);
16401714
const animations = createAnimations(gltf, nodes, bufferViews, options);
16411715
const materials = createMaterials(gltf, textureAssets.map(function (textureAsset) {
16421716
return textureAsset.resource;
@@ -1663,6 +1737,7 @@ const createResources = function (device, gltf, bufferViews, textureAssets, opti
16631737
result.renders = renders;
16641738
result.skins = skins;
16651739
result.lights = lights;
1740+
result.cameras = cameras;
16661741

16671742
if (postprocess) {
16681743
postprocess(gltf, result);

0 commit comments

Comments
 (0)