Skip to content

[BETA] ESM Orbit Camera #7082

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions examples/src/examples/camera/orbit.controls.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* @param {import('../../app/components/Example.mjs').ControlOptions} options - The options.
* @returns {JSX.Element} The returned JSX Element.
*/
export function controls({ observer, ReactPCUI, React, jsx, fragment }) {
const { BindingTwoWay, LabelGroup, Panel, BooleanInput, SliderInput } = ReactPCUI;

return fragment(
jsx(
LabelGroup,
{ text: 'Zoom reset' },
jsx(BooleanInput, {
type: 'toggle',
binding: new BindingTwoWay(),
link: { observer, path: 'example.zoomReset' }
})
),
jsx(
Panel,
{ headerText: 'Attributes' },
jsx(
LabelGroup,
{ text: 'Focus FOV' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'attr.focusFov' },
min: 30,
max: 120
})
),
jsx(
LabelGroup,
{ text: 'Look sensitivity' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'attr.lookSensitivity' },
min: 0.1,
max: 1,
step: 0.01
})
),
jsx(
LabelGroup,
{ text: 'Look damping' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'attr.lookDamping' },
min: 0,
max: 0.99,
step: 0.01
})
),
jsx(
LabelGroup,
{ text: 'Move damping' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'attr.moveDamping' },
min: 0,
max: 0.99,
step: 0.01
})
),
jsx(
LabelGroup,
{ text: 'Pinch speed' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'attr.pinchSpeed' },
min: 1,
max: 10
})
),
jsx(
LabelGroup,
{ text: 'Wheel speed' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'attr.wheelSpeed' },
min: 0.001,
max: 0.01,
step: 0.001
})
),
jsx(
LabelGroup,
{ text: 'Zoom min' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'attr.zoomMin' },
min: 0.001,
max: 0.01,
step: 0.001
})
),
jsx(
LabelGroup,
{ text: 'Zoom max' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'attr.zoomMax' },
min: 1,
max: 10
})
),
jsx(
LabelGroup,
{ text: 'Zoom scale min' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'attr.zoomScaleMin' },
min: 0.001,
max: 0.01,
step: 0.001
})
)
)
);
}
163 changes: 120 additions & 43 deletions examples/src/examples/camera/orbit.example.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
// @config DESCRIPTION <div style='text-align:center'><div>(<b>LMB</b>) Orbit</div><div>(<b>Scroll Wheel</b>) zoom</div><div>(<b>RMB</b>) Pan</div><div>(<b>F</b>) Focus</div></div>
// @config HIDDEN
import * as pc from 'playcanvas';
import { deviceType, rootPath } from 'examples/utils';
import { data } from 'examples/observer';
import { deviceType, rootPath, fileImport } from 'examples/utils';

const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas'));
const { OrbitCamera } = await fileImport(rootPath + '/static/scripts/camera/orbit-camera.js');

const canvas = document.getElementById('application-canvas');
if (!(canvas instanceof HTMLCanvasElement)) {
throw new Error('No canvas found');
}
window.focus();

const gfxOptions = {
Expand All @@ -10,13 +18,21 @@ const gfxOptions = {
twgslUrl: rootPath + '/static/lib/twgsl/twgsl.js'
};

const assets = {
helipad: new pc.Asset(
'helipad-env-atlas',
'texture',
{ url: rootPath + '/static/assets/cubemaps/helipad-env-atlas.png' },
{ type: pc.TEXTURETYPE_RGBP, mipmaps: false }
),
statue: new pc.Asset('statue', 'container', { url: rootPath + '/static/assets/models/statue.glb' })
};

const device = await pc.createGraphicsDevice(canvas, gfxOptions);
device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);

const createOptions = new pc.AppOptions();
createOptions.graphicsDevice = device;
createOptions.mouse = new pc.Mouse(document.body);
createOptions.touch = new pc.TouchDevice(document.body);

createOptions.componentSystems = [
pc.RenderComponentSystem,
Expand All @@ -29,11 +45,6 @@ createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler, pc.Scr
const app = new pc.AppBase(canvas);
app.init(createOptions);

const assets = {
statue: new pc.Asset('statue', 'container', { url: rootPath + '/static/assets/models/statue.glb' }),
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' })
};

// 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);
Expand All @@ -45,47 +56,113 @@ app.on('destroy', () => {
window.removeEventListener('resize', resize);
});

await new Promise((resolve) => {
new pc.AssetListLoader(Object.values(assets), app.assets).load(resolve);
});

/**
* @param {pc.Asset[] | number[]} assetList - The asset list.
* @param {pc.AssetRegistry} assetRegistry - The asset registry.
* @returns {Promise<void>} The promise.
* Calculate the bounding box of an entity.
*
* @param {pc.BoundingBox} bbox - The bounding box.
* @param {pc.Entity} entity - The entity.
* @returns {pc.BoundingBox} The bounding box.
*/
function loadAssets(assetList, assetRegistry) {
return new Promise((resolve) => {
const assetListLoader = new pc.AssetListLoader(assetList, assetRegistry);
assetListLoader.load(resolve);
const calcEntityAABB = (bbox, entity) => {
bbox.center.set(0, 0, 0);
bbox.halfExtents.set(0, 0, 0);
entity.findComponents('render').forEach((render) => {
render.meshInstances.forEach((/** @type {pc.MeshInstance} */ mi) => {
bbox.add(mi.aabb);
});
});
return bbox;
};

/**
* @param {pc.Entity} focus - The entity to focus the camera on.
* @returns {OrbitCamera} The orbit-camera script.
*/
const createOrbitCamera = (focus) => {
const camera = new pc.Entity();
camera.addComponent('camera');
camera.addComponent('script');

const start = new pc.Vec3(0, 20, 30);
const bbox = calcEntityAABB(new pc.BoundingBox(), focus);
const cameraDist = start.distance(bbox.center);

/** @type {OrbitCamera} */
const script = camera.script.create(OrbitCamera, {
attributes: {
sceneSize: bbox.halfExtents.length()
}
});
}
await loadAssets(Object.values(assets), app.assets);
// Create an entity hierarchy representing the statue
const statueEntity = assets.statue.resource.instantiateRenderEntity();
statueEntity.setLocalScale(0.07, 0.07, 0.07);
statueEntity.setLocalPosition(0, -0.5, 0);
app.root.addChild(statueEntity);

// Create a camera with an orbit camera script
const camera = new pc.Entity();
camera.addComponent('camera', {
clearColor: new pc.Color(0.4, 0.45, 0.5)
});
camera.addComponent('script');
camera.script.create('orbitCamera', {
attributes: {
inertiaFactor: 0.2 // Override default of 0 (no inertia)
}
});
camera.script.create('orbitCameraInputMouse');
camera.script.create('orbitCameraInputTouch');
app.root.addChild(camera);

// focus on entity when 'f' key is pressed
const onKeyDown = (/** @type {KeyboardEvent} */ e) => {
if (e.key === 'f') {
if (data.get('example.zoomReset')) {
script.resetZoom(cameraDist);
}
script.focus(bbox.center);
}
};
window.addEventListener('keydown', onKeyDown);
app.on('destroy', () => {
window.removeEventListener('keydown', onKeyDown);
});

// wait until after canvas resized to focus on entity
const resize = new ResizeObserver(() => {
resize.disconnect();
script.focus(bbox.center, start);
});
resize.observe(canvas);

return script;
};

app.start();

app.scene.ambientLight.set(0.4, 0.4, 0.4);

app.scene.skyboxMip = 1;
app.scene.skyboxIntensity = 0.4;
app.scene.envAtlas = assets.helipad.resource;

// Create a directional light
const light = new pc.Entity();
light.addComponent('light', {
type: 'directional'
});
app.root.addChild(light);
light.addComponent('light');
light.setLocalEulerAngles(45, 30, 0);
app.root.addChild(light);

app.start();
const statue = assets.statue.resource.instantiateRenderEntity();
statue.setLocalPosition(0, -0.5, 0);
app.root.addChild(statue);

const multiCameraScript = createOrbitCamera(statue);

// Bind controls to camera attributes
data.set('example', {
zoomReset: true
});
data.set('attr', {
focusFov: 75,
lookSensitivity: 0.2,
lookDamping: 0.97,
moveDamping: 0.98,
pinchSpeed: 5,
wheelSpeed: 0.005,
zoomMin: 0.001,
zoomMax: 10,
zoomScaleMin: 0.01
});
data.on('*:set', (/** @type {string} */ path, /** @type {any} */ value) => {
const [category, key] = path.split('.');
if (category !== 'attr') {
return;
}
multiCameraScript[key] = value;
});

export { app };
2 changes: 1 addition & 1 deletion examples/src/examples/compute/particles.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic
window.focus();

const assets = {
orbit: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }),
orbit: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/legacy-orbit-camera.js' }),
helipad: new pc.Asset(
'helipad-env-atlas',
'texture',
Expand Down
2 changes: 1 addition & 1 deletion examples/src/examples/compute/vertex-update.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const assets = {
color: new pc.Asset('color', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-color.jpg' }),
normal: new pc.Asset('normal', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-normal.jpg' }),
gloss: new pc.Asset('gloss', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-gloss.jpg' }),
orbit: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }),
orbit: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/legacy-orbit-camera.js' }),
helipad: new pc.Asset(
'helipad-env-atlas',
'texture',
Expand Down
2 changes: 1 addition & 1 deletion examples/src/examples/gizmos/transform-rotate.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ app.setCanvasResolution(pc.RESOLUTION_AUTO);

// load assets
const assets = {
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }),
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/legacy-orbit-camera.js' }),
font: new pc.Asset('font', 'font', { url: rootPath + '/static/assets/fonts/courier.json' })
};
/**
Expand Down
2 changes: 1 addition & 1 deletion examples/src/examples/gizmos/transform-scale.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ app.setCanvasResolution(pc.RESOLUTION_AUTO);

// load assets
const assets = {
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }),
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/legacy-orbit-camera.js' }),
font: new pc.Asset('font', 'font', { url: rootPath + '/static/assets/fonts/courier.json' })
};
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ app.setCanvasResolution(pc.RESOLUTION_AUTO);

// load assets
const assets = {
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }),
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/legacy-orbit-camera.js' }),
font: new pc.Asset('font', 'font', { url: rootPath + '/static/assets/fonts/courier.json' })
};
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pc.WasmModule.setConfig('DracoDecoderModule', {

const assets = {
laboratory: new pc.Asset('statue', 'container', { url: rootPath + '/static/assets/models/laboratory.glb' }),
orbit: new pc.Asset('orbit', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }),
orbit: new pc.Asset('orbit', 'script', { url: rootPath + '/static/scripts/camera/legacy-orbit-camera.js' }),
ssao: new pc.Asset('ssao', 'script', { url: rootPath + '/static/scripts/posteffects/posteffect-ssao.js' }),
helipad: new pc.Asset(
'helipad-env-atlas',
Expand Down
2 changes: 1 addition & 1 deletion examples/src/examples/graphics/asset-viewer.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic
window.focus();

const assets = {
orbitCamera: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }),
orbitCamera: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/legacy-orbit-camera.js' }),
helipad: new pc.Asset(
'helipad-env-atlas',
'texture',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ data.set('settings', {
});

const assets = {
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }),
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/legacy-orbit-camera.js' }),
color: new pc.Asset('color', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-color.jpg' }),
normal: new pc.Asset('normal', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-normal.jpg' }),
gloss: new pc.Asset('gloss', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-gloss.jpg' }),
Expand Down
Loading