From 0c3c20c210ed3191aafc29c897a0762196451835 Mon Sep 17 00:00:00 2001 From: Steven Vergenz Date: Wed, 30 Jan 2019 16:06:36 -0800 Subject: [PATCH 1/5] Add texture APIs --- packages/sdk/src/math/vector2.ts | 18 +++++- packages/sdk/src/types/runtime/actor.ts | 4 +- .../sdk/src/types/runtime/assets/material.ts | 51 ++++++++++++++-- .../sdk/src/types/runtime/assets/texture.ts | 59 +++++++++++++++++-- 4 files changed, 119 insertions(+), 13 deletions(-) diff --git a/packages/sdk/src/math/vector2.ts b/packages/sdk/src/math/vector2.ts index d7948fff8..7aa3f3a56 100644 --- a/packages/sdk/src/math/vector2.ts +++ b/packages/sdk/src/math/vector2.ts @@ -7,10 +7,15 @@ import { Epsilon, Matrix, Scalar, Vector3 } from '.'; // tslint:disable:member-ordering variable-name one-variable-per-declaration trailing-comma no-bitwise curly max-line-length +export interface Vector2Like { + x: number; + y: number; +} + /** * Class representing a vector containing 2 coordinates */ -export class Vector2 { +export class Vector2 implements Vector2Like { // Statics @@ -656,4 +661,15 @@ export class Vector2 { public clone(): Vector2 { return new Vector2(this.x, this.y); } + + /** + * Updates the Vector2 from the sparsely populated value. + * @param from The sparsely populated value to read from. + */ + public copy(from: Partial): this { + if (!from) { return this; } + if (from.x !== undefined) { this.x = from.x; } + if (from.y !== undefined) { this.y = from.y; } + return this; + } } diff --git a/packages/sdk/src/types/runtime/actor.ts b/packages/sdk/src/types/runtime/actor.ts index fed712a34..9b0799de8 100644 --- a/packages/sdk/src/types/runtime/actor.ts +++ b/packages/sdk/src/types/runtime/actor.ts @@ -85,7 +85,7 @@ export class Actor implements ActorLike, Patchable { private _materialId?: string; // tslint:enable:variable-name - /** + /* * PUBLIC ACCESSORS */ @@ -113,6 +113,8 @@ export class Actor implements ActorLike, Patchable { this._parentId = value; this.actorChanged('parentId'); } + + /** @returns A shared reference to this actor's material, or null if this actor has no material */ public get material() { return this._context.assetManager.assets[this._materialId] as Material; } public set material(value) { this.materialId = value && value.id || null; diff --git a/packages/sdk/src/types/runtime/assets/material.ts b/packages/sdk/src/types/runtime/assets/material.ts index c5a11e444..805776d17 100644 --- a/packages/sdk/src/types/runtime/assets/material.ts +++ b/packages/sdk/src/types/runtime/assets/material.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. */ -import { Asset, AssetLike, AssetManager } from '.'; -import { Color3, Color4 } from '../../../math'; +import { Asset, AssetLike, AssetManager, Texture } from '.'; +import { Color3, Color4, Vector2Like, Vector2 } from '../../../math'; import observe from '../../../utils/observe'; import readPath from '../../../utils/readPath'; import { InternalAsset } from '../../internal/asset'; @@ -16,6 +16,12 @@ import { Patchable } from '../../patchable'; export interface MaterialLike { /** The base color of this material. */ color: Partial; + /** The main (albedo) texture asset ID */ + mainTextureId: string; + /** The main texture's offset from default */ + mainTextureOffset: Vector2Like; + /** The main texture's scale from default */ + mainTextureScale: Vector2Like; } /** @@ -41,6 +47,9 @@ export enum AlphaMode { export class Material extends Asset implements MaterialLike, Patchable { // tslint:disable:variable-name private _color = Color4.FromColor3(Color3.White(), 1.0); + private _mainTextureId: string = null; + private _mainTextureOffset = Vector2.Zero(); + private _mainTextureScale = Vector2.One(); private _internal = new InternalAsset(this); // tslint:enable:variable-name @@ -50,6 +59,31 @@ export class Material extends Asset implements MaterialLike, Patchable this.materialChanged(path)); + this._mainTextureId = def.material.mainTextureId; + this._mainTextureOffset.copy(def.material.mainTextureOffset); + this._mainTextureScale.copy(def.material.mainTextureScale); + + // material patching: observe the nested material properties + // for changed values, and write them to a patch + observe(this._color, 'color', (...path: string[]) => this.materialChanged(...path)); + observe(this._mainTextureOffset, 'mainTextureOffset', (...path: string[]) => this.materialChanged(...path)); + observe(this._mainTextureScale, 'mainTextureScale', (...path: string[]) => this.materialChanged(...path)); } public copy(from: Partial): this { @@ -94,7 +135,7 @@ export class Material extends Asset implements MaterialLike, Patchable { // tslint:disable:variable-name + private _resolution = Vector2.One(); private _wrapU = TextureWrapMode.Repeat; private _wrapV = TextureWrapMode.Repeat; + private _internal = new InternalAsset(this); // tslint:enable:variable-name + /** @hidden */ + public get internal() { return this._internal; } + + /** The pixel dimensions of the loaded texture */ + public get resolution() { return this._resolution; } + /** How overflowing UVs are handled horizontally. */ public get wrapU() { return this._wrapU; } - public set wrapU(val) { this._wrapU = val; } + public set wrapU(val) { this._wrapU = val; this.textureChanged('wrapU'); } /** How overflowing UVs are handled vertically. */ public get wrapV() { return this._wrapV; } - public set wrapV(val) { this._wrapV = val; } + public set wrapV(val) { this._wrapV = val; this.textureChanged('wrapV'); } /** @inheritdoc */ public get texture(): TextureLike { return this; } @@ -44,18 +58,51 @@ export class Texture extends Asset implements TextureLike { throw new Error("Cannot construct texture from non-texture definition"); } + this._resolution = new Vector2(def.texture.resolution.x, def.texture.resolution.y); this._wrapU = def.texture.wrapU; this._wrapV = def.texture.wrapV; } + public copy(from: Partial): this { + if (!from) { + return this; + } + + // Pause change detection while we copy the values into the actor. + const wasObserving = this.internal.observing; + this.internal.observing = false; + + // tslint:disable:curly + super.copy(from); + if (from.texture && from.texture.resolution) + this._resolution = new Vector2(from.texture.resolution.x, from.texture.resolution.y); + if (from.texture && from.texture.wrapU) + this._wrapU = from.texture.wrapU; + if (from.texture && from.texture.wrapV) + this._wrapV = from.texture.wrapV; + // tslint:enable:curly + + this.internal.observing = wasObserving; + return this; + } + /** @hidden */ public toJSON(): AssetLike { return { ...super.toJSON(), texture: { + resolution: this.resolution.toJSON(), wrapU: this.wrapU, wrapV: this.wrapV } }; } + + private textureChanged(...path: string[]): void { + if (this.internal.observing) { + this.manager.context.internal.incrementGeneration(); + this.internal.patch = this.internal.patch || { texture: {} } as AssetLike; + readPath(this, this.internal.patch.texture, ...path); + } + } } From 1f5f865162d6ed9ac74fc3c2aa27f8e5b27ca37c Mon Sep 17 00:00:00 2001 From: Steven Vergenz Date: Wed, 30 Jan 2019 16:45:17 -0800 Subject: [PATCH 2/5] Update material with correct patching props --- .../sdk/src/types/runtime/assets/material.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/sdk/src/types/runtime/assets/material.ts b/packages/sdk/src/types/runtime/assets/material.ts index 805776d17..5d04f3e22 100644 --- a/packages/sdk/src/types/runtime/assets/material.ts +++ b/packages/sdk/src/types/runtime/assets/material.ts @@ -4,7 +4,7 @@ */ import { Asset, AssetLike, AssetManager, Texture } from '.'; -import { Color3, Color4, Vector2Like, Vector2 } from '../../../math'; +import { Color3, Color4, Vector2, Vector2Like } from '../../../math'; import observe from '../../../utils/observe'; import readPath from '../../../utils/readPath'; import { InternalAsset } from '../../internal/asset'; @@ -115,11 +115,19 @@ export class Material extends Asset implements MaterialLike, Patchable Date: Fri, 1 Feb 2019 13:28:39 -0800 Subject: [PATCH 3/5] Fix patching ambiguity --- .../src/tests/asset-preload-test.ts | 33 +++++++-- .../src/tests/mutable-asset-test.ts | 69 +++++++++++-------- packages/gltf-gen/src/geometry/sphere.ts | 6 +- packages/sdk/src/types/runtime/actor.ts | 11 +-- .../sdk/src/types/runtime/assets/material.ts | 11 +-- packages/sdk/src/utils/deterministicGuids.ts | 4 ++ 6 files changed, 85 insertions(+), 49 deletions(-) diff --git a/packages/functional-tests/src/tests/asset-preload-test.ts b/packages/functional-tests/src/tests/asset-preload-test.ts index a60cd0081..be4b869b1 100644 --- a/packages/functional-tests/src/tests/asset-preload-test.ts +++ b/packages/functional-tests/src/tests/asset-preload-test.ts @@ -37,17 +37,24 @@ export default class AssetPreloadTest extends Test { await delay(1000); label.text.contents = 'Preloading assets'; - const [prefabs, mats] = await Promise.all([ + const [monkey, uvgrid] = await Promise.all([ this.app.context.assetManager.loadGltf('monkey', this.baseUrl + '/monkey.glb'), this.app.context.assetManager.loadGltf('uvgrid', this.generateMaterial()) ]); - label.text.contents = `Assets preloaded: -${prefabs.prefabs.count + mats.prefabs.count} prefabs, ${prefabs.materials.count + mats.materials.count} materials`; + label.text.contents = "Assets preloaded:" + + `${monkey.prefabs.count + uvgrid.prefabs.count} prefabs, ` + + `${monkey.materials.count + uvgrid.materials.count} materials, ` + + `${monkey.textures.count + uvgrid.textures.count} textures`; await delay(1000); + const monkeyPrefab = monkey.prefabs.byIndex(0); + const monkeyMat = monkey.materials.byIndex(0); + const uvgridMat = uvgrid.materials.byIndex(0); + const uvgridTex = uvgrid.textures.byIndex(0); + label.text.contents = 'Instantiating prefabs'; const head = await MRESDK.Actor.CreateFromPrefab(this.app.context, { - prefabId: prefabs.prefabs.byIndex(0).id, + prefabId: monkeyPrefab.id, actor: { transform: { position: { x: -1, y: 1, z: 0 } @@ -60,7 +67,7 @@ ${prefabs.prefabs.count + mats.prefabs.count} prefabs, ${prefabs.materials.count radius: 1 }, actor: { - materialId: mats.materials.byIndex(0).id, + materialId: uvgridMat.id, transform: { position: { x: 1, y: 1, z: 0 } } @@ -75,12 +82,24 @@ ${prefabs.prefabs.count + mats.prefabs.count} prefabs, ${prefabs.materials.count actor.children.forEach(c => assignMat(c, mat)); } - assignMat(head, mats.materials.byIndex(0)); - assignMat(sphere, prefabs.materials.byIndex(0)); + assignMat(head, uvgridMat); + assignMat(sphere, monkeyMat); label.text.contents = 'Materials swapped'; await delay(3000); + monkeyMat.mainTexture = uvgridTex; + uvgridMat.mainTexture = null; + label.text.contents = 'Textures swapped'; + + await delay(3000); + + monkeyMat.mainTexture = null; + uvgridMat.mainTexture = null; + label.text.contents = 'Textures cleared'; + + await delay(3000); + assignMat(head, null); assignMat(sphere, null); label.text.contents = 'Materials cleared'; diff --git a/packages/functional-tests/src/tests/mutable-asset-test.ts b/packages/functional-tests/src/tests/mutable-asset-test.ts index 30baab30a..cfa100523 100644 --- a/packages/functional-tests/src/tests/mutable-asset-test.ts +++ b/packages/functional-tests/src/tests/mutable-asset-test.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. */ +import * as GltfGen from '@microsoft/gltf-gen'; import * as MRESDK from '@microsoft/mixed-reality-extension-sdk'; import App from '../app'; +import Server from '../server'; import delay from '../utils/delay'; import destroyActors from '../utils/destroyActors'; import Test from './test'; @@ -18,46 +20,55 @@ export default class MutableAssetTest extends Test { public async run(): Promise { const assets = await this.app.context.assetManager.loadGltf( - 'assets', `${this.baseUrl}/monkey.glb`); + 'assets', this.generateMaterial() + ); - const monkey = MRESDK.Actor.CreateFromPrefab(this.app.context, { - prefabId: assets.prefabs.byIndex(0).id, + const mat = assets.materials.byIndex(0); + const box = await MRESDK.Actor.CreatePrimitive(this.app.context, { + definition: { + shape: MRESDK.PrimitiveShape.Box, + dimensions: {x: 1, y: 1, z: 1} + }, actor: { - name: 'monkey', + name: 'box', + materialId: mat.id, transform: { position: { x: 0, y: 1, z: 0 } } } - }).value; + }); - const label = MRESDK.Actor.CreateEmpty(this.app.context, { - actor: { - name: 'label', - transform: { - position: { x: 0, y: 2, z: 0 } - }, - text: { - contents: 'Original (pink)' - } - } - }).value; - label.lookAt(this.user, MRESDK.LookAtMode.TargetY); - - await delay(3000); + for (let i = 0; i < 64; i++) { + mat.color.copyFrom( this.fromHSV(i / 32, 1, 1) ); + mat.mainTextureOffset.set(i / 32, i / 32); + mat.mainTextureScale.set(1 - i / 32, 1 - i / 32); - const material = assets.materials.byIndex(0); - const origColor = material.color.clone(); - material.color.set(0, 1, 0, 1); - label.text.contents = 'Green'; + await delay(100); + } - await delay(3000); + destroyActors([box]); + return true; + } - material.color.copyFrom(origColor); - label.text.contents = 'Back to original'; + private generateMaterial(): string { + const material = new GltfGen.Material({ + metallicFactor: 0, + baseColorTexture: new GltfGen.Texture({ + source: new GltfGen.Image({ + uri: `${this.baseUrl}/uv-grid.png` // alternate form (don't embed) + }) + }) + }); + const gltfFactory = new GltfGen.GltfFactory(null, null, [material]); - await delay(3000); + return Server.registerStaticBuffer('assets.glb', gltfFactory.generateGLTF()); + } - destroyActors([monkey, label]); - return true; + private fromHSV(h: number, s: number, v: number): MRESDK.Color4 { + // from wikipedia: https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV + function f(n: number, k = (n + h * 6) % 6) { + return v - v * s * Math.max(Math.min(k, 4 - k, 1), 0); + } + return new MRESDK.Color4(f(5), f(3), f(1), 1); } } diff --git a/packages/gltf-gen/src/geometry/sphere.ts b/packages/gltf-gen/src/geometry/sphere.ts index caecf9da8..1c62e5f2e 100644 --- a/packages/gltf-gen/src/geometry/sphere.ts +++ b/packages/gltf-gen/src/geometry/sphere.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { MeshPrimitive, Vertex } from '..'; +import { Material, MeshPrimitive, Vertex } from '..'; import { Vector2, Vector3 } from '@microsoft/mixed-reality-extension-sdk'; /** @@ -17,8 +17,8 @@ export class Sphere extends MeshPrimitive { * @param longLines The number of polar vertex rings * @param latLines The number of equatorial vertex rings (not counting poles) */ - public constructor(radius: number, longLines = 12, latLines = 8) { - super(); + public constructor(radius: number, longLines = 12, latLines = 8, material: Material = null) { + super({material}); // generate north pole const north = new Vertex({ diff --git a/packages/sdk/src/types/runtime/actor.ts b/packages/sdk/src/types/runtime/actor.ts index 9b0799de8..c8500234f 100644 --- a/packages/sdk/src/types/runtime/actor.ts +++ b/packages/sdk/src/types/runtime/actor.ts @@ -27,6 +27,7 @@ import { SetAnimationStateOptions } from '../..'; import { log } from '../../log'; +import { ZeroGuid } from '../../utils/deterministicGuids'; import observe from '../../utils/observe'; import readPath from '../../utils/readPath'; import { createForwardPromise, ForwardPromise } from '../forwardPromise'; @@ -82,7 +83,7 @@ export class Actor implements ActorLike, Patchable { private _rigidBody?: RigidBody; private _collider?: Collider; private _text?: Text; - private _materialId?: string; + private _materialId = ZeroGuid; // tslint:enable:variable-name /* @@ -117,15 +118,15 @@ export class Actor implements ActorLike, Patchable { /** @returns A shared reference to this actor's material, or null if this actor has no material */ public get material() { return this._context.assetManager.assets[this._materialId] as Material; } public set material(value) { - this.materialId = value && value.id || null; + this.materialId = value && value.id || ZeroGuid; } public get materialId() { return this._materialId; } public set materialId(value) { - if (value && value.startsWith('0000')) { - value = null; + if (!value || value.startsWith('0000')) { + value = ZeroGuid; } if (!this.context.assetManager.assets[value]) { - value = null; // throw? + value = ZeroGuid; // throw? } this._materialId = value; this.actorChanged('materialId'); diff --git a/packages/sdk/src/types/runtime/assets/material.ts b/packages/sdk/src/types/runtime/assets/material.ts index 5d04f3e22..8e32b0708 100644 --- a/packages/sdk/src/types/runtime/assets/material.ts +++ b/packages/sdk/src/types/runtime/assets/material.ts @@ -5,6 +5,7 @@ import { Asset, AssetLike, AssetManager, Texture } from '.'; import { Color3, Color4, Vector2, Vector2Like } from '../../../math'; +import { ZeroGuid } from '../../../utils/deterministicGuids'; import observe from '../../../utils/observe'; import readPath from '../../../utils/readPath'; import { InternalAsset } from '../../internal/asset'; @@ -47,7 +48,7 @@ export enum AlphaMode { export class Material extends Asset implements MaterialLike, Patchable { // tslint:disable:variable-name private _color = Color4.FromColor3(Color3.White(), 1.0); - private _mainTextureId: string = null; + private _mainTextureId: string = ZeroGuid; private _mainTextureOffset = Vector2.Zero(); private _mainTextureScale = Vector2.One(); private _internal = new InternalAsset(this); @@ -62,17 +63,17 @@ export class Material extends Asset implements MaterialLike, Patchable Date: Mon, 4 Feb 2019 09:27:50 -0800 Subject: [PATCH 4/5] Move ZeroGuid definition --- packages/sdk/src/constants.ts | 2 ++ packages/sdk/src/math/vector2.ts | 2 +- packages/sdk/src/types/runtime/actor.ts | 2 +- packages/sdk/src/types/runtime/assets/material.ts | 2 +- packages/sdk/src/utils/deterministicGuids.ts | 4 ---- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index 4f01560ac..c96dc5ca7 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -5,6 +5,8 @@ // tslint:disable:variable-name +export const ZeroGuid = '00000000-0000-0000-0000-000000000000'; + /** @hidden */ export const HTTPHeaders = { SessionID: 'x-ms-mixed-reality-extension-sessionid', diff --git a/packages/sdk/src/math/vector2.ts b/packages/sdk/src/math/vector2.ts index 7aa3f3a56..491be8521 100644 --- a/packages/sdk/src/math/vector2.ts +++ b/packages/sdk/src/math/vector2.ts @@ -305,7 +305,7 @@ export class Vector2 implements Vector2Like { return { x: this.x, y: this.y, - }; + } as Vector2Like; } /** diff --git a/packages/sdk/src/types/runtime/actor.ts b/packages/sdk/src/types/runtime/actor.ts index c8500234f..8b67a3430 100644 --- a/packages/sdk/src/types/runtime/actor.ts +++ b/packages/sdk/src/types/runtime/actor.ts @@ -26,8 +26,8 @@ import { PrimitiveDefinition, SetAnimationStateOptions } from '../..'; +import { ZeroGuid } from '../../constants'; import { log } from '../../log'; -import { ZeroGuid } from '../../utils/deterministicGuids'; import observe from '../../utils/observe'; import readPath from '../../utils/readPath'; import { createForwardPromise, ForwardPromise } from '../forwardPromise'; diff --git a/packages/sdk/src/types/runtime/assets/material.ts b/packages/sdk/src/types/runtime/assets/material.ts index 8e32b0708..a15fb1fbf 100644 --- a/packages/sdk/src/types/runtime/assets/material.ts +++ b/packages/sdk/src/types/runtime/assets/material.ts @@ -4,8 +4,8 @@ */ import { Asset, AssetLike, AssetManager, Texture } from '.'; +import { ZeroGuid } from '../../../constants'; import { Color3, Color4, Vector2, Vector2Like } from '../../../math'; -import { ZeroGuid } from '../../../utils/deterministicGuids'; import observe from '../../../utils/observe'; import readPath from '../../../utils/readPath'; import { InternalAsset } from '../../internal/asset'; diff --git a/packages/sdk/src/utils/deterministicGuids.ts b/packages/sdk/src/utils/deterministicGuids.ts index 0dca9a7f3..b1ed5565f 100644 --- a/packages/sdk/src/utils/deterministicGuids.ts +++ b/packages/sdk/src/utils/deterministicGuids.ts @@ -5,10 +5,6 @@ import * as crypto from 'crypto'; -// tslint:disable-next-line:variable-name -const ZeroGuid = '00000000-0000-0000-0000-000000000000'; -export { ZeroGuid }; - /** * @hidden * Class for generating a sequence of deterministic GUID values. From 2e6d7cd820b42464bb5e09d0fc72a918377566da Mon Sep 17 00:00:00 2001 From: Steven Vergenz Date: Mon, 4 Feb 2019 09:39:54 -0800 Subject: [PATCH 5/5] Add material color setter --- packages/sdk/src/types/runtime/assets/material.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sdk/src/types/runtime/assets/material.ts b/packages/sdk/src/types/runtime/assets/material.ts index a15fb1fbf..9d3aa03fd 100644 --- a/packages/sdk/src/types/runtime/assets/material.ts +++ b/packages/sdk/src/types/runtime/assets/material.ts @@ -59,6 +59,7 @@ export class Material extends Asset implements MaterialLike, Patchable