-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into xr-camera-controls
- Loading branch information
Showing
17 changed files
with
523 additions
and
162 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/* eslint-disable-next-line import/no-unresolved */ | ||
import { Script } from 'playcanvas'; | ||
|
||
export default class XrControllers extends Script { | ||
/** | ||
* The base URL for fetching the WebXR input profiles. | ||
* | ||
* @attribute | ||
* @type {string} | ||
*/ | ||
basePath = 'https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets/dist/profiles'; | ||
|
||
controllers = new Map(); | ||
|
||
initialize() { | ||
this.app.xr.input.on('add', async (inputSource) => { | ||
if (!inputSource.profiles?.length) { | ||
console.warn('No profiles available for input source'); | ||
return; | ||
} | ||
|
||
// Process all profiles concurrently | ||
const profilePromises = inputSource.profiles.map(async (profileId) => { | ||
const profileUrl = `${this.basePath}/${profileId}/profile.json`; | ||
|
||
try { | ||
const response = await fetch(profileUrl); | ||
if (!response.ok) { | ||
return null; | ||
} | ||
|
||
const profile = await response.json(); | ||
const layoutPath = profile.layouts[inputSource.handedness]?.assetPath || ''; | ||
const assetPath = `${this.basePath}/${profile.profileId}/${inputSource.handedness}${layoutPath.replace(/^\/?(left|right)/, '')}`; | ||
|
||
// Load the model | ||
const asset = await new Promise((resolve, reject) => { | ||
this.app.assets.loadFromUrl(assetPath, 'container', (err, asset) => { | ||
if (err) reject(err); | ||
else resolve(asset); | ||
}); | ||
}); | ||
|
||
return { profileId, asset }; | ||
} catch (error) { | ||
console.warn(`Failed to process profile ${profileId}`); | ||
return null; | ||
} | ||
}); | ||
|
||
// Wait for all profile attempts to complete | ||
const results = await Promise.all(profilePromises); | ||
const successfulResult = results.find(result => result !== null); | ||
|
||
if (successfulResult) { | ||
const { asset } = successfulResult; | ||
const container = asset.resource; | ||
const entity = container.instantiateRenderEntity(); | ||
this.app.root.addChild(entity); | ||
|
||
const jointMap = new Map(); | ||
if (inputSource.hand) { | ||
for (const joint of inputSource.hand.joints) { | ||
const jointEntity = entity.findByName(joint.id); | ||
if (jointEntity) { | ||
jointMap.set(joint, jointEntity); | ||
} | ||
} | ||
} | ||
|
||
this.controllers.set(inputSource, { entity, jointMap }); | ||
} else { | ||
console.warn('No compatible profiles found'); | ||
} | ||
}); | ||
|
||
this.app.xr.input.on('remove', (inputSource) => { | ||
const controller = this.controllers.get(inputSource); | ||
if (controller) { | ||
controller.entity.destroy(); | ||
this.controllers.delete(inputSource); | ||
} | ||
}); | ||
} | ||
|
||
update(dt) { | ||
if (this.app.xr.active) { | ||
for (const [inputSource, { entity, jointMap }] of this.controllers) { | ||
if (inputSource.hand) { | ||
for (const [joint, jointEntity] of jointMap) { | ||
jointEntity.setPosition(joint.getPosition()); | ||
jointEntity.setRotation(joint.getRotation()); | ||
} | ||
} else { | ||
entity.setPosition(inputSource.getPosition()); | ||
entity.setRotation(inputSource.getRotation()); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/** | ||
* This script allows GPU Profiling on Mac using Xcode's GPU Frame Capture. Please read the instructions | ||
* in the manual: https://developer.playcanvas.com/user-manual/optimization/gpu-profiling/ | ||
*/ | ||
var MacGPUProfiling = pc.createScript('MacGPUProfiling'); | ||
|
||
// Called once after all resources are loaded and initialized | ||
MacGPUProfiling.prototype.initialize = function () { | ||
this.isInitialized = false; | ||
this.device = null; | ||
this.context = null; | ||
|
||
// this is not needed for WebGPU | ||
if (this.app.graphicsDevice.isWebGPU) return; | ||
|
||
// only needed on Mac | ||
if (pc.platform.name !== 'osx') return; | ||
|
||
// Create a new canvas for WebGPU with a smaller size | ||
this.webgpuCanvas = document.createElement('canvas'); | ||
this.webgpuCanvas.width = 20; | ||
this.webgpuCanvas.height = 20; | ||
this.webgpuCanvas.style.position = 'absolute'; | ||
this.webgpuCanvas.style.top = '20px'; // Adjust position if needed | ||
this.webgpuCanvas.style.left = '20px'; // Adjust position if needed | ||
document.body.appendChild(this.webgpuCanvas); | ||
|
||
// Start the asynchronous WebGPU initialization | ||
this.initWebGPU(); | ||
}; | ||
|
||
// Async function for WebGPU initialization | ||
MacGPUProfiling.prototype.initWebGPU = async function () { | ||
// Check for WebGPU support | ||
if (!navigator.gpu) { | ||
console.error('WebGPU is not supported on this browser.'); | ||
return; | ||
} | ||
|
||
// Get WebGPU adapter and device | ||
const adapter = await navigator.gpu.requestAdapter(); | ||
this.device = await adapter.requestDevice(); | ||
|
||
console.log('Created WebGPU device used for profiling'); | ||
|
||
// Create a WebGPU context for the new canvas | ||
this.context = this.webgpuCanvas.getContext('webgpu'); | ||
|
||
// Configure the WebGPU context | ||
const swapChainFormat = 'bgra8unorm'; | ||
this.context.configure({ | ||
device: this.device, | ||
format: swapChainFormat | ||
}); | ||
|
||
// Mark initialization as complete | ||
this.isInitialized = true; | ||
|
||
// Hook into the 'frameend' event | ||
this.app.on('frameend', this.onFrameEnd, this); | ||
}; | ||
|
||
// Called when the 'frameend' event is triggered | ||
MacGPUProfiling.prototype.onFrameEnd = function () { | ||
// If WebGPU is not initialized yet, do nothing | ||
if (!this.isInitialized) return; | ||
|
||
// Clear the WebGPU surface to red after WebGL rendering | ||
this.clearToRed(); | ||
}; | ||
|
||
// Function to clear the WebGPU surface to red | ||
MacGPUProfiling.prototype.clearToRed = function () { | ||
// Get the current texture to render to | ||
const textureView = this.context.getCurrentTexture().createView(); | ||
|
||
// Create a command encoder | ||
const commandEncoder = this.device.createCommandEncoder(); | ||
|
||
// Create a render pass descriptor with a red background | ||
const renderPassDescriptor = { | ||
colorAttachments: [{ | ||
view: textureView, | ||
clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, // Red background | ||
loadOp: 'clear', | ||
storeOp: 'store' | ||
}] | ||
}; | ||
|
||
// render pass | ||
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); | ||
passEncoder.end(); | ||
|
||
// Submit the commands to the GPU | ||
this.device.queue.submit([commandEncoder.finish()]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.