Skip to content

Commit

Permalink
add mesh loading
Browse files Browse the repository at this point in the history
  • Loading branch information
yorkie committed Dec 1, 2023
1 parent 486b5b3 commit 665c0f4
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 22 deletions.
1 change: 1 addition & 0 deletions fixtures/spatial-externalmesh-glb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// TODO
4 changes: 1 addition & 3 deletions fixtures/spatial-externalmesh-glb.xsml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,5 @@
<space>
<mesh ref="my" />
</space>
<script type="module">
console.log('hello world', new URL('https://example.com'));
</script>
<script src="./spatial-externalmesh-glb.ts"></script>
</xsml>
8 changes: 5 additions & 3 deletions src/impl-headless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ export class HeadlessNativeDocument extends EventTarget implements NativeDocumen
closed: boolean = false;

private _scene: BABYLON.Scene;
private _preloadMeshes: Map<string, Array<BABYLON.AbstractMesh | BABYLON.TransformNode>> = new Map();
private _preloadAnimationGroups: Map<string, BABYLON.AnimationGroup[]> = new Map();

constructor() {
super();
Expand All @@ -136,11 +138,11 @@ export class HeadlessNativeDocument extends EventTarget implements NativeDocumen
getContainerPose(): XRPose {
throw new Error('Method not implemented.');
}
getPreloadedMeshes(): Map<string, BABYLON.AbstractMesh[]> {
throw new Error('Method not implemented.');
getPreloadedMeshes(): Map<string, Array<BABYLON.AbstractMesh | BABYLON.TransformNode>> {
return this._preloadMeshes;
}
getPreloadedAnimationGroups(): Map<string, BABYLON.AnimationGroup[]> {
throw new Error('Method not implemented.');
return this._preloadAnimationGroups;
}
observeInputEvent(name?: string): void {
throw new Error('Method not implemented.');
Expand Down
2 changes: 1 addition & 1 deletion src/impl-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export interface NativeDocument extends EventTarget {
*
* When loading a XSML document, the implementation should preload the specific meshes.
*/
getPreloadedMeshes(): Map<string, BABYLON.AbstractMesh[]>;
getPreloadedMeshes(): Map<string, Array<BABYLON.AbstractMesh | BABYLON.TransformNode>>;
/**
* It returns a map of preloaded animation groups.
*/
Expand Down
61 changes: 48 additions & 13 deletions src/living/nodes/HTMLLinkElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,21 +176,56 @@ export default class HTMLLinkElementImpl extends HTMLElementImpl implements HTML
}

private async _loadStylesheet() {
// TODO
throw new DOMException('Not implemented yet.', 'NOT_SUPPORTED_ERR');
}

private async _loadSpatialModel() {
const nativeDocument = this._hostObject;
const url = new URL(this.href, this._ownerDocument._URL);
const resArrayBuffer = await nativeDocument.userAgent.resourceLoader.fetch(url.href, {}, 'arraybuffer');
const extname = path.extname(url.pathname);
const importedResult = await BABYLON.SceneLoader.ImportMeshAsync(
'',
'',
new Uint8Array(resArrayBuffer),
nativeDocument.getNativeScene(),
null,
extname
);
const id = this.getAttribute('id');
if (id == null) {
throw new DOMException('The id attribute is required for spatial-model.', 'INVALID_STATE_ERR');
}

try {
const nativeDocument = this._hostObject;
const url = new URL(this.href, this._ownerDocument._URL);
const resArrayBuffer = await nativeDocument.userAgent.resourceLoader.fetch(url.href, {}, 'arraybuffer');
const extname = path.extname(url.pathname);
const importedResult = await BABYLON.SceneLoader.ImportMeshAsync(
'',
'',
new Uint8Array(resArrayBuffer),
nativeDocument.getNativeScene(),
null,
extname
);

const transformNodesAndMeshes = importedResult.transformNodes.concat(importedResult.meshes);
for (const node of transformNodesAndMeshes) {
node.setEnabled(false);
/**
* If the extension is .glb or .gltf, we assume the nodes are in right-handed system.
*
* Babylon.js doesn't convert the mesh to left-handed system even though the loader and scene are in left-handed system, it
* works with root node's rotation and scaling correction and the renderer, thus it doesn't work for our case, that requires
* the right data in the left-handed system for mesh itself.
*
* Thus we add a tag to the node to indicate that the mesh is in right-handed system, and we will convert it to left-handed
* at serializer.
*/
if (extname === '.glb' || extname === '.gltf') {
/**
* The tag will be copied when this node is cloned, so this is a exact match for our use case and don't need to handle the
* same tagging on the cloned nodes.
*/
BABYLON.Tags.AddTagsTo(node, 'right-handed-system');
}
}
this._hostObject.getPreloadedMeshes().set(id, transformNodesAndMeshes);
if (importedResult.animationGroups?.length > 0) {
this._hostObject.getPreloadedAnimationGroups().set(id, importedResult.animationGroups);
}
} catch (err) {
throw new DOMException(`Failed to load spatial model(${this.href}): ${err.message}`, 'INVALID_STATE_ERR');
}
}
}
137 changes: 135 additions & 2 deletions src/living/nodes/SpatialMeshElement.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import BABYLON from 'babylonjs';
import { NativeDocument } from '../../impl-interfaces';
import DOMExceptionImpl from '../domexception';
import { SpatialElement } from './SpatialElement';

export function cloneWithOriginalRefs(
source: BABYLON.Node,
name: string,
newParent: BABYLON.Node,
onClonedObversaval?: (origin: BABYLON.Node, cloned: BABYLON.Node) => void
) {
const result = source.clone(name, newParent, true);
result.setEnabled(true);

if (typeof onClonedObversaval === 'function') {
onClonedObversaval(source, result);
}
const directDescendantsOfSource = source.getDescendants(true);
for (let child of directDescendantsOfSource) {
cloneWithOriginalRefs(child, child.name, result, onClonedObversaval);
}
return result;
}

export default class SpatialMeshElement extends SpatialElement {
constructor(
hostObject: NativeDocument,
Expand All @@ -12,8 +33,120 @@ export default class SpatialMeshElement extends SpatialElement {
});
}

get ref(): string {
return this.getAttribute('ref');
}
set ref(value: string) {
this.setAttribute('ref', value);
}

get selector(): string {
return this.getAttribute('selector') || '__root__';
}
set selector(value: string) {
this.setAttribute('selector', value);
}

_attach(): void {
super._attach();
// TODO
super._attach(this._instantiate());
}

_instantiate(): BABYLON.Node {
const { ref, selector } = this;

if (!ref) {
throw new DOMExceptionImpl('ref is required in <mesh>', 'INVALID_STATE_ERR');
}
if (!this._hostObject.getPreloadedMeshes().has(ref)) {
throw new DOMExceptionImpl(`No mesh with ref(${ref}) is preloaded`, 'INVALID_STATE_ERR');
}

const transformNodesAndMeshes = this._hostObject.getPreloadedMeshes().get(ref);
if (!transformNodesAndMeshes || !transformNodesAndMeshes.length) {
throw new DOMExceptionImpl(`No mesh or transforms with ref(${ref}) is found from preloaded resource`, 'INVALID_STATE_ERR');
}

const targetMesh = transformNodesAndMeshes.find((node) => node.name === selector);
if (!targetMesh) {
throw new DOMExceptionImpl(`No mesh or transform with selector(${selector}) is found from preloaded resource`, 'INVALID_STATE_ERR');
}

const rootName = this.id || this.getAttribute('name') || this.localName;
const originUidToClonedMap: { [key: number]: BABYLON.Node } = {};
const clonedMesh = cloneWithOriginalRefs(targetMesh, rootName, null, (origin, cloned) => {
originUidToClonedMap[origin.uniqueId] = cloned;
});

/** Set the cloned skeleton, it set the new bone's linking transforms to new. */
clonedMesh.getChildMeshes().forEach((childMesh) => {
if (childMesh.skeleton) {
const clonedSkeleton = childMesh.skeleton.clone(`${childMesh.skeleton.name}-cloned`);
clonedSkeleton.bones.forEach(bone => {
const linkedTransformNode = bone.getTransformNode();
if (!originUidToClonedMap[linkedTransformNode.uniqueId]) {
throw new TypeError(`Could not find the linked transform node(${linkedTransformNode.name}) for bone(${bone.name})`);
} else {
const newTransform = originUidToClonedMap[linkedTransformNode.uniqueId];
if (newTransform.getClassName() === 'TransformNode') {
bone.linkTransformNode(newTransform as BABYLON.TransformNode);
} else {
throw new TypeError(`The linked transform node(${linkedTransformNode.name}) for bone(${bone.name}) is not a transform node, actual type is "${newTransform.getClassName()}".`);
}
}
});
childMesh.skeleton = clonedSkeleton;
}
});

/**
* The Babylonjs won't clone the animation group when the related meshes are cloned.
*
* In this case, we need to clone the animation groups manually, and set the new targets
* to the cloned meshes and its related animation groups.
*/
const animationGroups = this._hostObject.getPreloadedAnimationGroups().get(ref);
if (animationGroups) {
for (let i = 0; i < animationGroups.length; i++) {
const animationGroup = animationGroups[i];
if (animationGroup.targetedAnimations.length <= 0) {
continue;
}
let id = `${i}`;
if (this.id) {
id = `#${this.id}`;
}
const newAnimationGroup = animationGroup.clone(
`${animationGroup.name}/${id}`,
(oldTarget: BABYLON.Node) => originUidToClonedMap[oldTarget.uniqueId],
false
);

/**
* When an animation group is cloned into space, we added the following listeners to mark
* the animation group as dirty.
*/
function markDirty(animationGroup: BABYLON.AnimationGroup) {
/** FIXME(Yorkie): we use the metadata._isDirty as the dirty-update flag for animation group */
if (!animationGroup.metadata) {
animationGroup.metadata = {};
}
animationGroup.metadata._isDirty = true;
}
newAnimationGroup.onAnimationGroupPlayObservable.add(markDirty);
newAnimationGroup.onAnimationGroupEndObservable.add(markDirty);
newAnimationGroup.onAnimationGroupPauseObservable.add(markDirty);

// If the animation group is playing, stop it and start the new one.
if (animationGroup.isPlaying === true) {
try {
newAnimationGroup.start(animationGroup.loopAnimation);
} catch (err) {
console.warn(`Failed to start the animation group(${newAnimationGroup.name}): ${err.message}`);
}
animationGroup.stop();
}
}
}
return clonedMesh;
}
}

0 comments on commit 665c0f4

Please sign in to comment.