Change indentation to tabs from spaces (#43)

This commit is contained in:
Steven Vergenz 2019-08-20 17:00:10 -07:00 коммит произвёл GitHub
Родитель ee8e57b2d1
Коммит a828cffde2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
17 изменённых файлов: 915 добавлений и 915 удалений

Просмотреть файл

@ -4,137 +4,137 @@
*/
import {
Actor,
AnimationEaseCurves,
AnimationKeyframe,
AnimationWrapMode,
ButtonBehavior,
Context,
Quaternion,
TextAnchorLocation,
Vector3
Actor,
AnimationEaseCurves,
AnimationKeyframe,
AnimationWrapMode,
ButtonBehavior,
Context,
Quaternion,
TextAnchorLocation,
Vector3
} from '@microsoft/mixed-reality-extension-sdk';
/**
* The main class of this app. All the logic goes here.
*/
export default class HelloWorld {
private text: Actor = null;
private cube: Actor = null;
private text: Actor = null;
private cube: Actor = null;
constructor(private context: Context, private baseUrl: string) {
this.context.onStarted(() => this.started());
}
constructor(private context: Context, private baseUrl: string) {
this.context.onStarted(() => this.started());
}
/**
* Once the context is "started", initialize the app.
*/
private started() {
// Create a new actor with no mesh, but some text.
this.text = Actor.CreateEmpty(this.context, {
actor: {
name: 'Text',
transform: {
app: { position: { x: 0, y: 0.5, z: 0 } }
},
text: {
contents: "Hello World!",
anchor: TextAnchorLocation.MiddleCenter,
color: { r: 30 / 255, g: 206 / 255, b: 213 / 255 },
height: 0.3
}
}
});
/**
* Once the context is "started", initialize the app.
*/
private started() {
// Create a new actor with no mesh, but some text.
this.text = Actor.CreateEmpty(this.context, {
actor: {
name: 'Text',
transform: {
app: { position: { x: 0, y: 0.5, z: 0 } }
},
text: {
contents: "Hello World!",
anchor: TextAnchorLocation.MiddleCenter,
color: { r: 30 / 255, g: 206 / 255, b: 213 / 255 },
height: 0.3
}
}
});
// Here we create an animation on our text actor. Animations have three mandatory arguments:
// a name, an array of keyframes, and an array of events.
this.text.createAnimation(
// The name is a unique identifier for this animation. We'll pass it to "startAnimation" later.
"Spin", {
// Keyframes define the timeline for the animation: where the actor should be, and when.
// We're calling the generateSpinKeyframes function to produce a simple 20-second revolution.
keyframes: this.generateSpinKeyframes(20, Vector3.Up()),
// Events are points of interest during the animation. The animating actor will emit a given
// named event at the given timestamp with a given string value as an argument.
events: [],
// Here we create an animation on our text actor. Animations have three mandatory arguments:
// a name, an array of keyframes, and an array of events.
this.text.createAnimation(
// The name is a unique identifier for this animation. We'll pass it to "startAnimation" later.
"Spin", {
// Keyframes define the timeline for the animation: where the actor should be, and when.
// We're calling the generateSpinKeyframes function to produce a simple 20-second revolution.
keyframes: this.generateSpinKeyframes(20, Vector3.Up()),
// Events are points of interest during the animation. The animating actor will emit a given
// named event at the given timestamp with a given string value as an argument.
events: [],
// Optionally, we also repeat the animation infinitely. PingPong alternately runs the animation
// foward then backward.
wrapMode: AnimationWrapMode.PingPong
});
// Optionally, we also repeat the animation infinitely. PingPong alternately runs the animation
// foward then backward.
wrapMode: AnimationWrapMode.PingPong
});
// Load a glTF model
this.cube = Actor.CreateFromGLTF(this.context, {
// at the given URL
resourceUrl: `${this.baseUrl}/altspace-cube.glb`,
// and spawn box colliders around the meshes.
colliderType: 'box',
// Also apply the following generic actor properties.
actor: {
name: 'Altspace Cube',
// Parent the glTF model to the text actor.
parentId: this.text.id,
transform: {
local: {
position: { x: 0, y: -1, z: 0 },
scale: { x: 0.4, y: 0.4, z: 0.4 }
}
}
}
});
// Load a glTF model
this.cube = Actor.CreateFromGLTF(this.context, {
// at the given URL
resourceUrl: `${this.baseUrl}/altspace-cube.glb`,
// and spawn box colliders around the meshes.
colliderType: 'box',
// Also apply the following generic actor properties.
actor: {
name: 'Altspace Cube',
// Parent the glTF model to the text actor.
parentId: this.text.id,
transform: {
local: {
position: { x: 0, y: -1, z: 0 },
scale: { x: 0.4, y: 0.4, z: 0.4 }
}
}
}
});
// Create some animations on the cube.
this.cube.createAnimation(
'DoAFlip', {
keyframes: this.generateSpinKeyframes(1.0, Vector3.Right()),
events: []
});
// Create some animations on the cube.
this.cube.createAnimation(
'DoAFlip', {
keyframes: this.generateSpinKeyframes(1.0, Vector3.Right()),
events: []
});
// Now that the text and its animation are all being set up, we can start playing
// the animation.
this.text.enableAnimation('Spin');
// Now that the text and its animation are all being set up, we can start playing
// the animation.
this.text.enableAnimation('Spin');
// Set up cursor interaction. We add the input behavior ButtonBehavior to the cube.
// Button behaviors have two pairs of events: hover start/stop, and click start/stop.
const buttonBehavior = this.cube.setBehavior(ButtonBehavior);
// Set up cursor interaction. We add the input behavior ButtonBehavior to the cube.
// Button behaviors have two pairs of events: hover start/stop, and click start/stop.
const buttonBehavior = this.cube.setBehavior(ButtonBehavior);
// Trigger the grow/shrink animations on hover.
buttonBehavior.onHover('enter', () => {
this.cube.animateTo(
{ transform: { local: { scale: { x: 0.5, y: 0.5, z: 0.5 } } } }, 0.3, AnimationEaseCurves.EaseOutSine);
});
buttonBehavior.onHover('exit', () => {
this.cube.animateTo(
{ transform: { local: { scale: { x: 0.4, y: 0.4, z: 0.4 } } } }, 0.3, AnimationEaseCurves.EaseOutSine);
});
// Trigger the grow/shrink animations on hover.
buttonBehavior.onHover('enter', () => {
this.cube.animateTo(
{ transform: { local: { scale: { x: 0.5, y: 0.5, z: 0.5 } } } }, 0.3, AnimationEaseCurves.EaseOutSine);
});
buttonBehavior.onHover('exit', () => {
this.cube.animateTo(
{ transform: { local: { scale: { x: 0.4, y: 0.4, z: 0.4 } } } }, 0.3, AnimationEaseCurves.EaseOutSine);
});
// When clicked, do a 360 sideways.
buttonBehavior.onClick(_ => {
this.cube.enableAnimation('DoAFlip');
});
}
// When clicked, do a 360 sideways.
buttonBehavior.onClick(_ => {
this.cube.enableAnimation('DoAFlip');
});
}
/**
* Generate keyframe data for a simple spin animation.
* @param duration The length of time in seconds it takes to complete a full revolution.
* @param axis The axis of rotation in local space.
*/
private generateSpinKeyframes(duration: number, axis: Vector3): AnimationKeyframe[] {
return [{
time: 0 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 0) } } }
}, {
time: 0.25 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, Math.PI / 2) } } }
}, {
time: 0.5 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, Math.PI) } } }
}, {
time: 0.75 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 3 * Math.PI / 2) } } }
}, {
time: 1 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 2 * Math.PI) } } }
}];
}
/**
* Generate keyframe data for a simple spin animation.
* @param duration The length of time in seconds it takes to complete a full revolution.
* @param axis The axis of rotation in local space.
*/
private generateSpinKeyframes(duration: number, axis: Vector3): AnimationKeyframe[] {
return [{
time: 0 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 0) } } }
}, {
time: 0.25 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, Math.PI / 2) } } }
}, {
time: 0.5 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, Math.PI) } } }
}, {
time: 0.75 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 3 * Math.PI / 2) } } }
}, {
time: 1 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 2 * Math.PI) } } }
}];
}
}

Просмотреть файл

@ -16,8 +16,8 @@ dotenv.config();
// Start listening for connections, and serve static files
const server = new WebHost({
// baseUrl: 'http://<ngrok-id>.ngrok.io',
baseDir: resolvePath(__dirname, '../public')
// baseUrl: 'http://<ngrok-id>.ngrok.io',
baseDir: resolvePath(__dirname, '../public')
});
// Handle new application sessions

Просмотреть файл

@ -63,7 +63,7 @@
"forin": true,
"indent": [
true,
"spaces"
"tabs"
],
"interface-name": [
true,

Просмотреть файл

@ -9,31 +9,31 @@ import * as MRESDK from '@microsoft/mixed-reality-extension-sdk';
* Solar system database
*/
interface Database {
[key: string]: DatabaseRecord;
[key: string]: DatabaseRecord;
}
interface DatabaseRecord {
name: string;
parent: string;
diameter: number; // km
distance: number; // 10^6 km
day: number; // hours
year: number; // days
inclination: number; // degrees
obliquity: number; // degrees
retrograde: boolean;
name: string;
parent: string;
diameter: number; // km
distance: number; // 10^6 km
day: number; // hours
year: number; // days
inclination: number; // degrees
obliquity: number; // degrees
retrograde: boolean;
}
interface CelestialBody {
inclination: MRESDK.Actor;
position: MRESDK.Actor;
obliquity0: MRESDK.Actor;
obliquity1: MRESDK.Actor;
model: MRESDK.Actor;
inclination: MRESDK.Actor;
position: MRESDK.Actor;
obliquity0: MRESDK.Actor;
obliquity1: MRESDK.Actor;
model: MRESDK.Actor;
}
interface CelestialBodySet {
[key: string]: CelestialBody;
[key: string]: CelestialBody;
}
// Data source: https://nssdc.gsfc.nasa.gov/planetary/dataheet/
@ -45,305 +45,305 @@ const database: Database = require('../public/database.json');
* Solar System Application
*/
export default class SolarSystem {
private celestialBodies: CelestialBodySet = {};
private animationsRunning = false;
private celestialBodies: CelestialBodySet = {};
private animationsRunning = false;
constructor(private context: MRESDK.Context, private baseUrl: string) {
this.context.onUserJoined(user => this.userJoined(user));
this.context.onUserLeft(user => this.userLeft(user));
this.context.onStarted(() => this.started());
this.context.onStopped(() => this.stopped());
}
constructor(private context: MRESDK.Context, private baseUrl: string) {
this.context.onUserJoined(user => this.userJoined(user));
this.context.onUserLeft(user => this.userLeft(user));
this.context.onStarted(() => this.started());
this.context.onStopped(() => this.stopped());
}
private started = () => {
console.log(`session started ${this.context.sessionId}`);
private started = () => {
console.log(`session started ${this.context.sessionId}`);
this.createSolarSystem();
this.createSolarSystem();
const sunEntity = this.celestialBodies.sol;
if (sunEntity && sunEntity.model) {
const sun = sunEntity.model;
const sunPrimitives = sun.findChildrenByName('Primitive', true);
const sunEntity = this.celestialBodies.sol;
if (sunEntity && sunEntity.model) {
const sun = sunEntity.model;
const sunPrimitives = sun.findChildrenByName('Primitive', true);
sunPrimitives.forEach((prim) => {
// Add a collider so that the behavior system will work properly on Unity host apps.
const center = { x: 0, y: 0, z: 0 } as MRESDK.Vector3Like;
const radius = 3;
prim.setCollider('sphere', false, center, radius);
sunPrimitives.forEach((prim) => {
// Add a collider so that the behavior system will work properly on Unity host apps.
const center = { x: 0, y: 0, z: 0 } as MRESDK.Vector3Like;
const radius = 3;
prim.setCollider('sphere', false, center, radius);
const buttonBehavior = prim.setBehavior(MRESDK.ButtonBehavior);
const buttonBehavior = prim.setBehavior(MRESDK.ButtonBehavior);
buttonBehavior.onClick(_ => {
if (this.animationsRunning) {
this.pauseAnimations();
this.animationsRunning = false;
} else {
this.resumeAnimations();
this.animationsRunning = true;
}
});
buttonBehavior.onClick(_ => {
if (this.animationsRunning) {
this.pauseAnimations();
this.animationsRunning = false;
} else {
this.resumeAnimations();
this.animationsRunning = true;
}
});
buttonBehavior.onHover('enter', () => {
console.log(`Hover entered on ${sunEntity.model.name}.`);
});
buttonBehavior.onHover('enter', () => {
console.log(`Hover entered on ${sunEntity.model.name}.`);
});
buttonBehavior.onHover('exit', () => {
console.log(`Hover exited on ${sunEntity.model.name}.`);
});
});
}
buttonBehavior.onHover('exit', () => {
console.log(`Hover exited on ${sunEntity.model.name}.`);
});
});
}
this.resumeAnimations();
this.animationsRunning = true;
}
this.resumeAnimations();
this.animationsRunning = true;
}
private stopped() {
console.log(`session stopped ${this.context.sessionId}`);
}
private stopped() {
console.log(`session stopped ${this.context.sessionId}`);
}
private userJoined(user: MRESDK.User) {
console.log(`user-joined: ${user.name}, ${user.id}`);
}
private userJoined(user: MRESDK.User) {
console.log(`user-joined: ${user.name}, ${user.id}`);
}
private userLeft(user: MRESDK.User) {
console.log(`user-left: ${user.name}`);
}
private userLeft(user: MRESDK.User) {
console.log(`user-left: ${user.name}`);
}
private createSolarSystem() {
const keys = Object.keys(database);
for (const bodyName of keys) {
this.createBody(bodyName);
}
}
private createSolarSystem() {
const keys = Object.keys(database);
for (const bodyName of keys) {
this.createBody(bodyName);
}
}
private resumeAnimations() {
const keys = Object.keys(database);
for (const bodyName of keys) {
const celestialBody = this.celestialBodies[bodyName];
celestialBody.model.resumeAnimation(`${bodyName}:axial`);
celestialBody.position.resumeAnimation(`${bodyName}:orbital`);
}
}
private resumeAnimations() {
const keys = Object.keys(database);
for (const bodyName of keys) {
const celestialBody = this.celestialBodies[bodyName];
celestialBody.model.resumeAnimation(`${bodyName}:axial`);
celestialBody.position.resumeAnimation(`${bodyName}:orbital`);
}
}
private pauseAnimations() {
const keys = Object.keys(database);
for (const bodyName of keys) {
const celestialBody = this.celestialBodies[bodyName];
celestialBody.model.pauseAnimation(`${bodyName}:axial`);
celestialBody.position.pauseAnimation(`${bodyName}:orbital`);
}
}
private pauseAnimations() {
const keys = Object.keys(database);
for (const bodyName of keys) {
const celestialBody = this.celestialBodies[bodyName];
celestialBody.model.pauseAnimation(`${bodyName}:axial`);
celestialBody.position.pauseAnimation(`${bodyName}:orbital`);
}
}
private createBody(bodyName: string) {
console.log(`Loading ${bodyName}`);
private createBody(bodyName: string) {
console.log(`Loading ${bodyName}`);
const facts = database[bodyName];
const facts = database[bodyName];
const distanceMultiplier = Math.pow(facts.distance, 1 / 3);
const scaleMultiplier = Math.pow(facts.diameter, 1 / 3) / 25;
const distanceMultiplier = Math.pow(facts.distance, 1 / 3);
const scaleMultiplier = Math.pow(facts.diameter, 1 / 3) / 25;
const positionValue = { x: distanceMultiplier, y: 0, z: 0 };
const scaleValue = { x: scaleMultiplier / 2, y: scaleMultiplier / 2, z: scaleMultiplier / 2 };
const obliquityValue = MRESDK.Quaternion.RotationAxis(
MRESDK.Vector3.Forward(), facts.obliquity * MRESDK.DegreesToRadians);
const inclinationValue = MRESDK.Quaternion.RotationAxis(
MRESDK.Vector3.Forward(), facts.inclination * MRESDK.DegreesToRadians);
const positionValue = { x: distanceMultiplier, y: 0, z: 0 };
const scaleValue = { x: scaleMultiplier / 2, y: scaleMultiplier / 2, z: scaleMultiplier / 2 };
const obliquityValue = MRESDK.Quaternion.RotationAxis(
MRESDK.Vector3.Forward(), facts.obliquity * MRESDK.DegreesToRadians);
const inclinationValue = MRESDK.Quaternion.RotationAxis(
MRESDK.Vector3.Forward(), facts.inclination * MRESDK.DegreesToRadians);
// Object layout for celestial body is:
// inclination -- orbital plane. centered on sol and tilted
// position -- position of center of celestial body (orbits sol)
// label -- centered above position. location of label.
// obliquity0 -- centered on position. hidden node to account
// for the fact that obliquity is a world-relative axis
// obliquity1 -- centered on position. tilt of obliquity axis
// model -- centered on position. the celestial body (rotates)
try {
const inclination = MRESDK.Actor.CreateEmpty(this.context, {
actor: {
name: `${bodyName}-inclination`,
transform: {
app: { rotation: inclinationValue }
}
}
});
const position = MRESDK.Actor.CreateEmpty(this.context, {
actor: {
name: `${bodyName}-position`,
parentId: inclination.id,
transform: {
local: { position: positionValue }
}
}
});
const label = MRESDK.Actor.CreateEmpty(this.context, {
actor: {
name: `${bodyName}-label`,
parentId: position.id,
transform: {
local: { position: { y: 0.1 + Math.pow(scaleMultiplier, 1 / 2.5) } }
}
}
});
const obliquity0 = MRESDK.Actor.CreateEmpty(this.context, {
actor: {
name: `${bodyName}-obliquity0`,
parentId: position.id
}
});
const obliquity1 = MRESDK.Actor.CreateEmpty(this.context, {
actor: {
name: `${bodyName}-obliquity1`,
parentId: obliquity0.id,
transform: {
local: { rotation: obliquityValue }
}
}
});
const model = MRESDK.Actor.CreateFromGLTF(this.context, {
resourceUrl: `${this.baseUrl}/assets/${bodyName}.gltf`,
colliderType: 'sphere',
actor: {
name: `${bodyName}-body`,
parentId: obliquity1.id,
transform: {
local: { scale: scaleValue }
}
}
// Object layout for celestial body is:
// inclination -- orbital plane. centered on sol and tilted
// position -- position of center of celestial body (orbits sol)
// label -- centered above position. location of label.
// obliquity0 -- centered on position. hidden node to account
// for the fact that obliquity is a world-relative axis
// obliquity1 -- centered on position. tilt of obliquity axis
// model -- centered on position. the celestial body (rotates)
try {
const inclination = MRESDK.Actor.CreateEmpty(this.context, {
actor: {
name: `${bodyName}-inclination`,
transform: {
app: { rotation: inclinationValue }
}
}
});
const position = MRESDK.Actor.CreateEmpty(this.context, {
actor: {
name: `${bodyName}-position`,
parentId: inclination.id,
transform: {
local: { position: positionValue }
}
}
});
const label = MRESDK.Actor.CreateEmpty(this.context, {
actor: {
name: `${bodyName}-label`,
parentId: position.id,
transform: {
local: { position: { y: 0.1 + Math.pow(scaleMultiplier, 1 / 2.5) } }
}
}
});
const obliquity0 = MRESDK.Actor.CreateEmpty(this.context, {
actor: {
name: `${bodyName}-obliquity0`,
parentId: position.id
}
});
const obliquity1 = MRESDK.Actor.CreateEmpty(this.context, {
actor: {
name: `${bodyName}-obliquity1`,
parentId: obliquity0.id,
transform: {
local: { rotation: obliquityValue }
}
}
});
const model = MRESDK.Actor.CreateFromGLTF(this.context, {
resourceUrl: `${this.baseUrl}/assets/${bodyName}.gltf`,
colliderType: 'sphere',
actor: {
name: `${bodyName}-body`,
parentId: obliquity1.id,
transform: {
local: { scale: scaleValue }
}
}
});
});
label.enableText({
contents: bodyName,
height: 0.5,
pixelsPerLine: 50,
color: MRESDK.Color3.Yellow(),
anchor: MRESDK.TextAnchorLocation.TopCenter,
justify: MRESDK.TextJustify.Center,
});
label.enableText({
contents: bodyName,
height: 0.5,
pixelsPerLine: 50,
color: MRESDK.Color3.Yellow(),
anchor: MRESDK.TextAnchorLocation.TopCenter,
justify: MRESDK.TextJustify.Center,
});
setTimeout(() => {
label.text.color = MRESDK.Color3.White();
}, 5000);
setTimeout(() => {
label.text.color = MRESDK.Color3.White();
}, 5000);
this.celestialBodies[bodyName] = {
inclination,
position,
obliquity0,
obliquity1,
model
} as CelestialBody;
this.celestialBodies[bodyName] = {
inclination,
position,
obliquity0,
obliquity1,
model
} as CelestialBody;
this.createAnimations(bodyName);
} catch (e) {
console.log("createBody failed", bodyName, e);
}
}
this.createAnimations(bodyName);
} catch (e) {
console.log("createBody failed", bodyName, e);
}
}
private createAnimations(bodyName: string) {
this.createAxialAnimation(bodyName);
this.createOrbitalAnimation(bodyName);
}
private createAnimations(bodyName: string) {
this.createAxialAnimation(bodyName);
this.createOrbitalAnimation(bodyName);
}
public readonly timeFactor = 40;
public readonly axialKeyframeCount = 90;
public readonly orbitalKeyframeCount = 90;
public readonly timeFactor = 40;
public readonly axialKeyframeCount = 90;
public readonly orbitalKeyframeCount = 90;
private createAxialAnimation(bodyName: string) {
const facts = database[bodyName];
const celestialBody = this.celestialBodies[bodyName];
private createAxialAnimation(bodyName: string) {
const facts = database[bodyName];
const celestialBody = this.celestialBodies[bodyName];
if (facts.day > 0) {
const spin = facts.retrograde ? -1 : 1;
// days = seconds (not in agreement with orbital animation)
const axisTimeInSeconds = facts.day / this.timeFactor;
const timeStep = axisTimeInSeconds / this.axialKeyframeCount;
const keyframes: MRESDK.AnimationKeyframe[] = [];
const angleStep = 360 / this.axialKeyframeCount;
const initial = celestialBody.model.transform.local.rotation.clone();
let value: Partial<MRESDK.ActorLike>;
if (facts.day > 0) {
const spin = facts.retrograde ? -1 : 1;
// days = seconds (not in agreement with orbital animation)
const axisTimeInSeconds = facts.day / this.timeFactor;
const timeStep = axisTimeInSeconds / this.axialKeyframeCount;
const keyframes: MRESDK.AnimationKeyframe[] = [];
const angleStep = 360 / this.axialKeyframeCount;
const initial = celestialBody.model.transform.local.rotation.clone();
let value: Partial<MRESDK.ActorLike>;
for (let i = 0; i < this.axialKeyframeCount; ++i) {
const rotDelta = MRESDK.Quaternion.RotationAxis(
MRESDK.Vector3.Up(), (-angleStep * i * spin) * MRESDK.DegreesToRadians);
const rotation = initial.multiply(rotDelta);
value = {
transform: {
local: { rotation }
}
};
keyframes.push({
time: timeStep * i,
value,
});
}
for (let i = 0; i < this.axialKeyframeCount; ++i) {
const rotDelta = MRESDK.Quaternion.RotationAxis(
MRESDK.Vector3.Up(), (-angleStep * i * spin) * MRESDK.DegreesToRadians);
const rotation = initial.multiply(rotDelta);
value = {
transform: {
local: { rotation }
}
};
keyframes.push({
time: timeStep * i,
value,
});
}
// Final frame
value = {
transform: {
local: { rotation: celestialBody.model.transform.local.rotation }
}
};
keyframes.push({
time: axisTimeInSeconds,
value,
});
// Final frame
value = {
transform: {
local: { rotation: celestialBody.model.transform.local.rotation }
}
};
keyframes.push({
time: axisTimeInSeconds,
value,
});
// Create the animation on the actor
celestialBody.model.createAnimation(
`${bodyName}:axial`, {
keyframes,
events: [],
wrapMode: MRESDK.AnimationWrapMode.Loop
});
}
}
// Create the animation on the actor
celestialBody.model.createAnimation(
`${bodyName}:axial`, {
keyframes,
events: [],
wrapMode: MRESDK.AnimationWrapMode.Loop
});
}
}
private createOrbitalAnimation(bodyName: string) {
const facts = database[bodyName];
const celestialBody = this.celestialBodies[bodyName];
private createOrbitalAnimation(bodyName: string) {
const facts = database[bodyName];
const celestialBody = this.celestialBodies[bodyName];
if (facts.year > 0) {
// years = seconds (not in agreement with axial animation)
const orbitTimeInSeconds = facts.year / this.timeFactor;
const timeStep = orbitTimeInSeconds / this.orbitalKeyframeCount;
const angleStep = 360 / this.orbitalKeyframeCount;
const keyframes: MRESDK.AnimationKeyframe[] = [];
const initial = celestialBody.position.transform.local.position.clone();
let value: Partial<MRESDK.ActorLike>;
if (facts.year > 0) {
// years = seconds (not in agreement with axial animation)
const orbitTimeInSeconds = facts.year / this.timeFactor;
const timeStep = orbitTimeInSeconds / this.orbitalKeyframeCount;
const angleStep = 360 / this.orbitalKeyframeCount;
const keyframes: MRESDK.AnimationKeyframe[] = [];
const initial = celestialBody.position.transform.local.position.clone();
let value: Partial<MRESDK.ActorLike>;
for (let i = 0; i < this.orbitalKeyframeCount; ++i) {
const rotDelta = MRESDK.Quaternion.RotationAxis(
MRESDK.Vector3.Up(), (-angleStep * i) * MRESDK.DegreesToRadians);
const position = initial.rotateByQuaternionToRef(rotDelta, new MRESDK.Vector3());
value = {
transform: {
local: { position }
}
};
keyframes.push({
time: timeStep * i,
value,
});
}
for (let i = 0; i < this.orbitalKeyframeCount; ++i) {
const rotDelta = MRESDK.Quaternion.RotationAxis(
MRESDK.Vector3.Up(), (-angleStep * i) * MRESDK.DegreesToRadians);
const position = initial.rotateByQuaternionToRef(rotDelta, new MRESDK.Vector3());
value = {
transform: {
local: { position }
}
};
keyframes.push({
time: timeStep * i,
value,
});
}
// Final frame
value = {
transform: {
local: { position: celestialBody.position.transform.local.position }
}
};
keyframes.push({
time: orbitTimeInSeconds,
value,
});
// Final frame
value = {
transform: {
local: { position: celestialBody.position.transform.local.position }
}
};
keyframes.push({
time: orbitTimeInSeconds,
value,
});
// Create the animation on the actor
celestialBody.position.createAnimation(
`${bodyName}:orbital`, {
keyframes,
events: [],
wrapMode: MRESDK.AnimationWrapMode.Loop
});
}
}
// Create the animation on the actor
celestialBody.position.createAnimation(
`${bodyName}:orbital`, {
keyframes,
events: [],
wrapMode: MRESDK.AnimationWrapMode.Loop
});
}
}
}

Просмотреть файл

@ -16,8 +16,8 @@ dotenv.config();
// Start listening for connections, and serve static files
const server = new WebHost({
// baseUrl: 'http://<ngrok-id>.ngrok.io',
baseDir: resolvePath(__dirname, '../public')
// baseUrl: 'http://<ngrok-id>.ngrok.io',
baseDir: resolvePath(__dirname, '../public')
});
// Handle new application sessions

Просмотреть файл

@ -63,7 +63,7 @@
"forin": true,
"indent": [
true,
"spaces"
"tabs"
],
"interface-name": [
true,

Просмотреть файл

@ -4,346 +4,346 @@
*/
import {
Actor,
AnimationKeyframe,
AnimationWrapMode,
ButtonBehavior,
Context,
DegreesToRadians,
PrimitiveShape,
Quaternion,
TextAnchorLocation,
Vector3
Actor,
AnimationKeyframe,
AnimationWrapMode,
ButtonBehavior,
Context,
DegreesToRadians,
PrimitiveShape,
Quaternion,
TextAnchorLocation,
Vector3
} from '@microsoft/mixed-reality-extension-sdk';
enum GameState {
Intro,
Play,
Celebration
Intro,
Play,
Celebration
}
enum GamePiece {
X,
O
X,
O
}
/**
* The main class of this app. All the logic goes here.
*/
export default class TicTacToe {
private text: Actor = null;
private textAnchor: Actor = null;
private light: Actor = null;
private gameState: GameState;
private text: Actor = null;
private textAnchor: Actor = null;
private light: Actor = null;
private gameState: GameState;
private currentPlayerGamePiece: GamePiece;
private nextPlayerGamePiece: GamePiece;
private currentPlayerGamePiece: GamePiece;
private nextPlayerGamePiece: GamePiece;
private boardState: GamePiece[];
private boardState: GamePiece[];
private gamePieceActors: Actor[];
private gamePieceActors: Actor[];
private victoryChecks = [
[0 * 3 + 0, 0 * 3 + 1, 0 * 3 + 2],
[1 * 3 + 0, 1 * 3 + 1, 1 * 3 + 2],
[2 * 3 + 0, 2 * 3 + 1, 2 * 3 + 2],
[0 * 3 + 0, 1 * 3 + 0, 2 * 3 + 0],
[0 * 3 + 1, 1 * 3 + 1, 2 * 3 + 1],
[0 * 3 + 2, 1 * 3 + 2, 2 * 3 + 2],
[0 * 3 + 0, 1 * 3 + 1, 2 * 3 + 2],
[2 * 3 + 0, 1 * 3 + 1, 0 * 3 + 2]
];
private victoryChecks = [
[0 * 3 + 0, 0 * 3 + 1, 0 * 3 + 2],
[1 * 3 + 0, 1 * 3 + 1, 1 * 3 + 2],
[2 * 3 + 0, 2 * 3 + 1, 2 * 3 + 2],
[0 * 3 + 0, 1 * 3 + 0, 2 * 3 + 0],
[0 * 3 + 1, 1 * 3 + 1, 2 * 3 + 1],
[0 * 3 + 2, 1 * 3 + 2, 2 * 3 + 2],
[0 * 3 + 0, 1 * 3 + 1, 2 * 3 + 2],
[2 * 3 + 0, 1 * 3 + 1, 0 * 3 + 2]
];
constructor(private context: Context, private baseUrl: string) {
this.context.onStarted(() => this.started());
}
constructor(private context: Context, private baseUrl: string) {
this.context.onStarted(() => this.started());
}
/**
* Once the context is "started", initialize the app.
*/
private async started() {
// Create a new actor with no mesh, but some text.
this.textAnchor = Actor.CreateEmpty(this.context, {
actor: {
name: 'TextAnchor',
transform: {
app: { position: { x: 0, y: 1.2, z: 0 } }
},
}
});
/**
* Once the context is "started", initialize the app.
*/
private async started() {
// Create a new actor with no mesh, but some text.
this.textAnchor = Actor.CreateEmpty(this.context, {
actor: {
name: 'TextAnchor',
transform: {
app: { position: { x: 0, y: 1.2, z: 0 } }
},
}
});
this.text = Actor.CreateEmpty(this.context, {
actor: {
parentId: this.textAnchor.id,
name: 'Text',
transform: {
local: { position: { x: 0, y: 0.0, z: -1.5 } }
},
text: {
contents: "Tic-Tac-Toe!",
anchor: TextAnchorLocation.MiddleCenter,
color: { r: 30 / 255, g: 206 / 255, b: 213 / 255 },
height: 0.3
},
}
});
this.light = Actor.CreateEmpty(this.context, {
actor: {
parentId: this.text.id,
name: 'Light',
transform: {
local: {
position: { x: 0, y: 1.0, z: -0.5 },
rotation: Quaternion.RotationAxis(Vector3.Left(), -45.0 * DegreesToRadians),
}
},
light: {
color: { r: 1, g: 0.6, b: 0.3 },
type: 'spot',
intensity: 20,
range: 6,
spotAngle: 45 * DegreesToRadians
},
this.text = Actor.CreateEmpty(this.context, {
actor: {
parentId: this.textAnchor.id,
name: 'Text',
transform: {
local: { position: { x: 0, y: 0.0, z: -1.5 } }
},
text: {
contents: "Tic-Tac-Toe!",
anchor: TextAnchorLocation.MiddleCenter,
color: { r: 30 / 255, g: 206 / 255, b: 213 / 255 },
height: 0.3
},
}
});
this.light = Actor.CreateEmpty(this.context, {
actor: {
parentId: this.text.id,
name: 'Light',
transform: {
local: {
position: { x: 0, y: 1.0, z: -0.5 },
rotation: Quaternion.RotationAxis(Vector3.Left(), -45.0 * DegreesToRadians),
}
},
light: {
color: { r: 1, g: 0.6, b: 0.3 },
type: 'spot',
intensity: 20,
range: 6,
spotAngle: 45 * DegreesToRadians
},
}
});
}
});
// Here we create an animation on our text actor. Animations have three mandatory arguments:
// a name, an array of keyframes, and an array of events.
this.textAnchor.createAnimation(
// The name is a unique identifier for this animation. We'll pass it to "startAnimation" later.
"Spin", {
// Keyframes define the timeline for the animation: where the actor should be, and when.
// We're calling the generateSpinKeyframes function to produce a simple 20-second revolution.
keyframes: this.generateSpinKeyframes(20, Vector3.Up()),
// Events are points of interest during the animation. The animating actor will emit a given
// named event at the given timestamp with a given string value as an argument.
events: [],
// Here we create an animation on our text actor. Animations have three mandatory arguments:
// a name, an array of keyframes, and an array of events.
this.textAnchor.createAnimation(
// The name is a unique identifier for this animation. We'll pass it to "startAnimation" later.
"Spin", {
// Keyframes define the timeline for the animation: where the actor should be, and when.
// We're calling the generateSpinKeyframes function to produce a simple 20-second revolution.
keyframes: this.generateSpinKeyframes(20, Vector3.Up()),
// Events are points of interest during the animation. The animating actor will emit a given
// named event at the given timestamp with a given string value as an argument.
events: [],
// Optionally, we also repeat the animation infinitely.
wrapMode: AnimationWrapMode.Loop
});
// Optionally, we also repeat the animation infinitely.
wrapMode: AnimationWrapMode.Loop
});
for (let tileIndexX = 0; tileIndexX < 3; tileIndexX++) {
for (let tileIndexZ = 0; tileIndexZ < 3; tileIndexZ++) {
// Load a glTF model
const cube = Actor.CreateFromGLTF(this.context, {
// at the given URL
resourceUrl: `${this.baseUrl}/altspace-cube.glb`,
// and spawn box colliders around the meshes.
colliderType: 'box',
// Also apply the following generic actor properties.
actor: {
name: 'Altspace Cube',
transform: {
app: {
position: { x: (tileIndexX) - 1.0, y: 0.5, z: (tileIndexZ) - 1.0 },
},
local: { scale: { x: 0.4, y: 0.4, z: 0.4 } }
}
}
});
for (let tileIndexX = 0; tileIndexX < 3; tileIndexX++) {
for (let tileIndexZ = 0; tileIndexZ < 3; tileIndexZ++) {
// Load a glTF model
const cube = Actor.CreateFromGLTF(this.context, {
// at the given URL
resourceUrl: `${this.baseUrl}/altspace-cube.glb`,
// and spawn box colliders around the meshes.
colliderType: 'box',
// Also apply the following generic actor properties.
actor: {
name: 'Altspace Cube',
transform: {
app: {
position: { x: (tileIndexX) - 1.0, y: 0.5, z: (tileIndexZ) - 1.0 },
},
local: { scale: { x: 0.4, y: 0.4, z: 0.4 } }
}
}
});
// Create some animations on the cube.
cube.createAnimation(
'GrowIn', {
keyframes: this.growAnimationData,
events: []
});
// Create some animations on the cube.
cube.createAnimation(
'GrowIn', {
keyframes: this.growAnimationData,
events: []
});
cube.createAnimation(
'ShrinkOut', {
keyframes: this.shrinkAnimationData,
events: []
});
cube.createAnimation(
'ShrinkOut', {
keyframes: this.shrinkAnimationData,
events: []
});
cube.createAnimation(
'DoAFlip', {
keyframes: this.generateSpinKeyframes(1.0, Vector3.Right()),
events: []
});
cube.createAnimation(
'DoAFlip', {
keyframes: this.generateSpinKeyframes(1.0, Vector3.Right()),
events: []
});
// Set up cursor interaction. We add the input behavior ButtonBehavior to the cube.
// Button behaviors have two pairs of events: hover start/stop, and click start/stop.
const buttonBehavior = cube.setBehavior(ButtonBehavior);
// Set up cursor interaction. We add the input behavior ButtonBehavior to the cube.
// Button behaviors have two pairs of events: hover start/stop, and click start/stop.
const buttonBehavior = cube.setBehavior(ButtonBehavior);
// Trigger the grow/shrink animations on hover.
buttonBehavior.onHover('enter', () => {
if (this.gameState === GameState.Play &&
this.boardState[tileIndexX * 3 + tileIndexZ] === undefined) {
cube.enableAnimation('GrowIn');
}
});
buttonBehavior.onHover('exit', () => {
if (this.gameState === GameState.Play &&
this.boardState[tileIndexX * 3 + tileIndexZ] === undefined) {
cube.enableAnimation('ShrinkOut');
}
});
// Trigger the grow/shrink animations on hover.
buttonBehavior.onHover('enter', () => {
if (this.gameState === GameState.Play &&
this.boardState[tileIndexX * 3 + tileIndexZ] === undefined) {
cube.enableAnimation('GrowIn');
}
});
buttonBehavior.onHover('exit', () => {
if (this.gameState === GameState.Play &&
this.boardState[tileIndexX * 3 + tileIndexZ] === undefined) {
cube.enableAnimation('ShrinkOut');
}
});
buttonBehavior.onClick(_ => {
switch (this.gameState) {
case GameState.Intro:
this.beginGameStatePlay();
cube.enableAnimation('GrowIn');
break;
case GameState.Play:
// When clicked, put down a tile, and do a victory check
if (this.boardState[tileIndexX * 3 + tileIndexZ] === undefined) {
console.log("Putting an " + GamePiece[this.currentPlayerGamePiece] +
" on: (" + tileIndexX + "," + tileIndexZ + ")");
const gamePiecePosition: Vector3 = new Vector3(
cube.transform.local.position.x,
cube.transform.local.position.y + 0.55,
cube.transform.local.position.z);
if (this.currentPlayerGamePiece === GamePiece.O) {
this.gamePieceActors.push(Actor.CreatePrimitive(this.context, {
definition: {
shape: PrimitiveShape.Cylinder,
dimensions: { x: 0, y: 0.2, z: 0 },
radius: 0.4,
uSegments: 16,
},
actor: {
name: 'O',
transform: {
local: { position: gamePiecePosition }
}
}
}));
} else {
this.gamePieceActors.push(Actor.CreatePrimitive(this.context, {
definition: {
shape: PrimitiveShape.Box,
dimensions: { x: 0.70, y: 0.2, z: 0.70 }
},
actor: {
name: 'X',
transform: {
local: { position: gamePiecePosition }
}
}
}));
}
this.boardState[tileIndexX * 3 + tileIndexZ] = this.currentPlayerGamePiece;
cube.disableAnimation('GrowIn');
cube.enableAnimation('ShrinkOut');
buttonBehavior.onClick(_ => {
switch (this.gameState) {
case GameState.Intro:
this.beginGameStatePlay();
cube.enableAnimation('GrowIn');
break;
case GameState.Play:
// When clicked, put down a tile, and do a victory check
if (this.boardState[tileIndexX * 3 + tileIndexZ] === undefined) {
console.log("Putting an " + GamePiece[this.currentPlayerGamePiece] +
" on: (" + tileIndexX + "," + tileIndexZ + ")");
const gamePiecePosition: Vector3 = new Vector3(
cube.transform.local.position.x,
cube.transform.local.position.y + 0.55,
cube.transform.local.position.z);
if (this.currentPlayerGamePiece === GamePiece.O) {
this.gamePieceActors.push(Actor.CreatePrimitive(this.context, {
definition: {
shape: PrimitiveShape.Cylinder,
dimensions: { x: 0, y: 0.2, z: 0 },
radius: 0.4,
uSegments: 16,
},
actor: {
name: 'O',
transform: {
local: { position: gamePiecePosition }
}
}
}));
} else {
this.gamePieceActors.push(Actor.CreatePrimitive(this.context, {
definition: {
shape: PrimitiveShape.Box,
dimensions: { x: 0.70, y: 0.2, z: 0.70 }
},
actor: {
name: 'X',
transform: {
local: { position: gamePiecePosition }
}
}
}));
}
this.boardState[tileIndexX * 3 + tileIndexZ] = this.currentPlayerGamePiece;
cube.disableAnimation('GrowIn');
cube.enableAnimation('ShrinkOut');
const tempGamePiece = this.currentPlayerGamePiece;
this.currentPlayerGamePiece = this.nextPlayerGamePiece;
this.nextPlayerGamePiece = tempGamePiece;
const tempGamePiece = this.currentPlayerGamePiece;
this.currentPlayerGamePiece = this.nextPlayerGamePiece;
this.nextPlayerGamePiece = tempGamePiece;
this.text.text.contents = "Next Piece: " + GamePiece[this.currentPlayerGamePiece];
this.text.text.contents = "Next Piece: " + GamePiece[this.currentPlayerGamePiece];
for (const victoryCheck of this.victoryChecks) {
if (this.boardState[victoryCheck[0]] !== undefined &&
this.boardState[victoryCheck[0]] === this.boardState[victoryCheck[1]] &&
this.boardState[victoryCheck[0]] === this.boardState[victoryCheck[2]]) {
this.beginGameStateCelebration(tempGamePiece);
break;
}
}
for (const victoryCheck of this.victoryChecks) {
if (this.boardState[victoryCheck[0]] !== undefined &&
this.boardState[victoryCheck[0]] === this.boardState[victoryCheck[1]] &&
this.boardState[victoryCheck[0]] === this.boardState[victoryCheck[2]]) {
this.beginGameStateCelebration(tempGamePiece);
break;
}
}
let hasEmptySpace = false;
for (let i = 0; i < 3 * 3; i++) {
if (this.boardState[i] === undefined) {
hasEmptySpace = true;
}
}
if (hasEmptySpace === false) {
this.beginGameStateCelebration(undefined);
}
}
break;
case GameState.Celebration:
default:
this.beginGameStateIntro();
break;
}
});
}
}
// Now that the text and its animation are all being set up, we can start playing
// the animation.
this.textAnchor.enableAnimation('Spin');
this.beginGameStateIntro();
}
let hasEmptySpace = false;
for (let i = 0; i < 3 * 3; i++) {
if (this.boardState[i] === undefined) {
hasEmptySpace = true;
}
}
if (hasEmptySpace === false) {
this.beginGameStateCelebration(undefined);
}
}
break;
case GameState.Celebration:
default:
this.beginGameStateIntro();
break;
}
});
}
}
// Now that the text and its animation are all being set up, we can start playing
// the animation.
this.textAnchor.enableAnimation('Spin');
this.beginGameStateIntro();
}
private beginGameStateCelebration(winner: GamePiece) {
console.log("BeginGameState Celebration");
this.gameState = GameState.Celebration;
this.light.light.color = { r: 0.3, g: 1.0, b: 0.3 };
private beginGameStateCelebration(winner: GamePiece) {
console.log("BeginGameState Celebration");
this.gameState = GameState.Celebration;
this.light.light.color = { r: 0.3, g: 1.0, b: 0.3 };
if (winner === undefined) {
console.log("Tie");
this.text.text.contents = "Tie";
} else {
console.log("Winner: " + GamePiece[winner]);
this.text.text.contents = "Winner: " + GamePiece[winner];
}
}
if (winner === undefined) {
console.log("Tie");
this.text.text.contents = "Tie";
} else {
console.log("Winner: " + GamePiece[winner]);
this.text.text.contents = "Winner: " + GamePiece[winner];
}
}
private beginGameStateIntro() {
console.log("BeginGameState Intro");
this.gameState = GameState.Intro;
this.text.text.contents = "Tic-Tac-Toe\nClick To Play";
private beginGameStateIntro() {
console.log("BeginGameState Intro");
this.gameState = GameState.Intro;
this.text.text.contents = "Tic-Tac-Toe\nClick To Play";
this.currentPlayerGamePiece = GamePiece.X;
this.nextPlayerGamePiece = GamePiece.O;
this.boardState = [];
this.light.light.color = { r: 1, g: 0.6, b: 0.3 };
this.currentPlayerGamePiece = GamePiece.X;
this.nextPlayerGamePiece = GamePiece.O;
this.boardState = [];
this.light.light.color = { r: 1, g: 0.6, b: 0.3 };
if (this.gamePieceActors !== undefined) {
for (const actor of this.gamePieceActors) {
actor.destroy();
}
}
this.gamePieceActors = [];
}
if (this.gamePieceActors !== undefined) {
for (const actor of this.gamePieceActors) {
actor.destroy();
}
}
this.gamePieceActors = [];
}
private beginGameStatePlay() {
console.log("BeginGameState Play");
this.gameState = GameState.Play;
this.text.text.contents = "First Piece: " + GamePiece[this.currentPlayerGamePiece];
}
private beginGameStatePlay() {
console.log("BeginGameState Play");
this.gameState = GameState.Play;
this.text.text.contents = "First Piece: " + GamePiece[this.currentPlayerGamePiece];
}
/**
* Generate keyframe data for a simple spin animation.
* @param duration The length of time in seconds it takes to complete a full revolution.
* @param axis The axis of rotation in local space.
*/
private generateSpinKeyframes(duration: number, axis: Vector3): AnimationKeyframe[] {
return [{
time: 0 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 0) } } }
}, {
time: 0.25 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, Math.PI / 2) } } }
}, {
time: 0.5 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, Math.PI) } } }
}, {
time: 0.75 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 3 * Math.PI / 2) } } }
}, {
time: 1 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 2 * Math.PI) } } }
}];
}
/**
* Generate keyframe data for a simple spin animation.
* @param duration The length of time in seconds it takes to complete a full revolution.
* @param axis The axis of rotation in local space.
*/
private generateSpinKeyframes(duration: number, axis: Vector3): AnimationKeyframe[] {
return [{
time: 0 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 0) } } }
}, {
time: 0.25 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, Math.PI / 2) } } }
}, {
time: 0.5 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, Math.PI) } } }
}, {
time: 0.75 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 3 * Math.PI / 2) } } }
}, {
time: 1 * duration,
value: { transform: { local: { rotation: Quaternion.RotationAxis(axis, 2 * Math.PI) } } }
}];
}
private growAnimationData: AnimationKeyframe[] = [{
time: 0,
value: { transform: { local: { scale: { x: 0.4, y: 0.4, z: 0.4 } } } }
}, {
time: 0.3,
value: { transform: { local: { scale: { x: 0.5, y: 0.5, z: 0.5 } } } }
}];
private growAnimationData: AnimationKeyframe[] = [{
time: 0,
value: { transform: { local: { scale: { x: 0.4, y: 0.4, z: 0.4 } } } }
}, {
time: 0.3,
value: { transform: { local: { scale: { x: 0.5, y: 0.5, z: 0.5 } } } }
}];
private shrinkAnimationData: AnimationKeyframe[] = [{
time: 0,
value: { transform: { local: { scale: { x: 0.5, y: 0.5, z: 0.5 } } } }
}, {
time: 0.3,
value: { transform: { local: { scale: { x: 0.4, y: 0.4, z: 0.4 } } } }
}];
private shrinkAnimationData: AnimationKeyframe[] = [{
time: 0,
value: { transform: { local: { scale: { x: 0.5, y: 0.5, z: 0.5 } } } }
}, {
time: 0.3,
value: { transform: { local: { scale: { x: 0.4, y: 0.4, z: 0.4 } } } }
}];
}

Просмотреть файл

@ -16,8 +16,8 @@ dotenv.config();
// Start listening for connections, and serve static files
const server = new WebHost({
// baseUrl: 'http://<ngrok-id>.ngrok.io',
baseDir: resolvePath(__dirname, '../public')
// baseUrl: 'http://<ngrok-id>.ngrok.io',
baseDir: resolvePath(__dirname, '../public')
});
// Handle new application sessions

Просмотреть файл

@ -63,7 +63,7 @@
"forin": true,
"indent": [
true,
"spaces"
"tabs"
],
"interface-name": [
true,

Просмотреть файл

@ -9,30 +9,30 @@ import * as MRESDK from '@microsoft/mixed-reality-extension-sdk';
* The structure of a hat entry in the hat database.
*/
type HatDescriptor = {
displayName: string;
resourceName: string;
scale: {
x: number;
y: number;
z: number;
};
rotation: {
x: number;
y: number;
z: number;
};
position: {
x: number;
y: number;
z: number;
};
displayName: string;
resourceName: string;
scale: {
x: number;
y: number;
z: number;
};
rotation: {
x: number;
y: number;
z: number;
};
position: {
x: number;
y: number;
z: number;
};
};
/**
* The structure of the hat database.
*/
type HatDatabase = {
[key: string]: HatDescriptor;
[key: string]: HatDescriptor;
};
// Load the database of hats.
@ -43,171 +43,171 @@ const HatDatabase: HatDatabase = require('../public/hats.json');
* WearAHat Application - Showcasing avatar attachments.
*/
export default class WearAHat {
// Container for preloaded hat prefabs.
private assets: MRESDK.AssetContainer;
private prefabs: { [key: string]: MRESDK.Prefab } = {};
// Container for instantiated hats.
private attachedHats: { [key: string]: MRESDK.Actor } = {};
// Container for preloaded hat prefabs.
private assets: MRESDK.AssetContainer;
private prefabs: { [key: string]: MRESDK.Prefab } = {};
// Container for instantiated hats.
private attachedHats: { [key: string]: MRESDK.Actor } = {};
/**
* Constructs a new instance of this class.
* @param context The MRE SDK context.
* @param baseUrl The baseUrl to this project's `./public` folder.
*/
constructor(private context: MRESDK.Context, private baseUrl: string) {
this.assets = new MRESDK.AssetContainer(context);
// Hook the context events we're interested in.
this.context.onStarted(() => this.started());
this.context.onUserLeft(user => this.userLeft(user));
}
/**
* Constructs a new instance of this class.
* @param context The MRE SDK context.
* @param baseUrl The baseUrl to this project's `./public` folder.
*/
constructor(private context: MRESDK.Context, private baseUrl: string) {
this.assets = new MRESDK.AssetContainer(context);
// Hook the context events we're interested in.
this.context.onStarted(() => this.started());
this.context.onUserLeft(user => this.userLeft(user));
}
/**
* Called when a Hats application session starts up.
*/
private async started() {
// Preload all the hat models.
await this.preloadHats();
// Show the hat menu.
this.showHatMenu();
}
/**
* Called when a Hats application session starts up.
*/
private async started() {
// Preload all the hat models.
await this.preloadHats();
// Show the hat menu.
this.showHatMenu();
}
/**
* Called when a user leaves the application (probably left the Altspace world where this app is running).
* @param user The user that left the building.
*/
private userLeft(user: MRESDK.User) {
// If the user was wearing a hat, destroy it. Otherwise it would be
// orphaned in the world.
if (this.attachedHats[user.id]) this.attachedHats[user.id].destroy();
delete this.attachedHats[user.id];
}
/**
* Called when a user leaves the application (probably left the Altspace world where this app is running).
* @param user The user that left the building.
*/
private userLeft(user: MRESDK.User) {
// If the user was wearing a hat, destroy it. Otherwise it would be
// orphaned in the world.
if (this.attachedHats[user.id]) this.attachedHats[user.id].destroy();
delete this.attachedHats[user.id];
}
/**
* Show a menu of hat selections.
*/
private showHatMenu() {
// Create a parent object for all the menu items.
const menu = MRESDK.Actor.CreateEmpty(this.context);
let y = 0.3;
/**
* Show a menu of hat selections.
*/
private showHatMenu() {
// Create a parent object for all the menu items.
const menu = MRESDK.Actor.CreateEmpty(this.context);
let y = 0.3;
// Loop over the hat database, creating a menu item for each entry.
for (const hatId of Object.keys(HatDatabase)) {
// Create a clickable button.
const button = MRESDK.Actor.CreatePrimitive(this.context, {
definition: {
shape: MRESDK.PrimitiveShape.Box,
dimensions: { x: 0.3, y: 0.3, z: 0.01 }
},
addCollider: true,
actor: {
parentId: menu.id,
name: hatId,
transform: {
local: { position: { x: 0, y, z: 0 } }
}
}
});
// Loop over the hat database, creating a menu item for each entry.
for (const hatId of Object.keys(HatDatabase)) {
// Create a clickable button.
const button = MRESDK.Actor.CreatePrimitive(this.context, {
definition: {
shape: MRESDK.PrimitiveShape.Box,
dimensions: { x: 0.3, y: 0.3, z: 0.01 }
},
addCollider: true,
actor: {
parentId: menu.id,
name: hatId,
transform: {
local: { position: { x: 0, y, z: 0 } }
}
}
});
// Set a click handler on the button.
button.setBehavior(MRESDK.ButtonBehavior)
.onClick(user => this.wearHat(hatId, user.id));
// Set a click handler on the button.
button.setBehavior(MRESDK.ButtonBehavior)
.onClick(user => this.wearHat(hatId, user.id));
// Create a label for the menu entry.
MRESDK.Actor.CreateEmpty(this.context, {
actor: {
parentId: menu.id,
name: 'label',
text: {
contents: HatDatabase[hatId].displayName,
height: 0.5,
anchor: MRESDK.TextAnchorLocation.MiddleLeft
},
transform: {
local: { position: { x: 0.5, y, z: 0 } }
}
}
});
y = y + 0.5;
}
// Create a label for the menu entry.
MRESDK.Actor.CreateEmpty(this.context, {
actor: {
parentId: menu.id,
name: 'label',
text: {
contents: HatDatabase[hatId].displayName,
height: 0.5,
anchor: MRESDK.TextAnchorLocation.MiddleLeft
},
transform: {
local: { position: { x: 0.5, y, z: 0 } }
}
}
});
y = y + 0.5;
}
// Create a label for the menu title.
MRESDK.Actor.CreateEmpty(this.context, {
actor: {
parentId: menu.id,
name: 'label',
text: {
contents: ''.padStart(8, ' ') + "Wear a Hat",
height: 0.8,
anchor: MRESDK.TextAnchorLocation.MiddleCenter,
color: MRESDK.Color3.Yellow()
},
transform: {
local: { position: { x: 0.5, y: y + 0.25, z: 0 } }
}
}
});
}
// Create a label for the menu title.
MRESDK.Actor.CreateEmpty(this.context, {
actor: {
parentId: menu.id,
name: 'label',
text: {
contents: ''.padStart(8, ' ') + "Wear a Hat",
height: 0.8,
anchor: MRESDK.TextAnchorLocation.MiddleCenter,
color: MRESDK.Color3.Yellow()
},
transform: {
local: { position: { x: 0.5, y: y + 0.25, z: 0 } }
}
}
});
}
/**
* Preload all hat resources. This makes instantiating them faster and more efficient.
*/
private preloadHats() {
// Loop over the hat database, preloading each hat resource.
// Return a promise of all the in-progress load promises. This
// allows the caller to wait until all hats are done preloading
// before continuing.
return Promise.all(
Object.keys(HatDatabase).map(hatId => {
const hatRecord = HatDatabase[hatId];
if (hatRecord.resourceName) {
return this.assets.loadGltf(
`${this.baseUrl}/${hatRecord.resourceName}`)
.then(assets => {
this.prefabs[hatId] = assets.find(a => a.prefab !== null) as MRESDK.Prefab;
})
.catch(e => console.error(e));
} else {
return Promise.resolve();
}
}));
}
/**
* Preload all hat resources. This makes instantiating them faster and more efficient.
*/
private preloadHats() {
// Loop over the hat database, preloading each hat resource.
// Return a promise of all the in-progress load promises. This
// allows the caller to wait until all hats are done preloading
// before continuing.
return Promise.all(
Object.keys(HatDatabase).map(hatId => {
const hatRecord = HatDatabase[hatId];
if (hatRecord.resourceName) {
return this.assets.loadGltf(
`${this.baseUrl}/${hatRecord.resourceName}`)
.then(assets => {
this.prefabs[hatId] = assets.find(a => a.prefab !== null) as MRESDK.Prefab;
})
.catch(e => console.error(e));
} else {
return Promise.resolve();
}
}));
}
/**
* Instantiate a hat and attach it to the avatar's head.
* @param hatId The id of the hat in the hat database.
* @param userId The id of the user we will attach the hat to.
*/
private wearHat(hatId: string, userId: string) {
// If the user is wearing a hat, destroy it.
if (this.attachedHats[userId]) this.attachedHats[userId].destroy();
delete this.attachedHats[userId];
/**
* Instantiate a hat and attach it to the avatar's head.
* @param hatId The id of the hat in the hat database.
* @param userId The id of the user we will attach the hat to.
*/
private wearHat(hatId: string, userId: string) {
// If the user is wearing a hat, destroy it.
if (this.attachedHats[userId]) this.attachedHats[userId].destroy();
delete this.attachedHats[userId];
const hatRecord = HatDatabase[hatId];
const hatRecord = HatDatabase[hatId];
// If the user selected 'none', then early out.
if (!hatRecord.resourceName) {
return;
}
// If the user selected 'none', then early out.
if (!hatRecord.resourceName) {
return;
}
// Create the hat model and attach it to the avatar's head.
this.attachedHats[userId] = MRESDK.Actor.CreateFromPrefab(this.context, {
prefabId: this.prefabs[hatId].id,
actor: {
transform: {
local: {
position: hatRecord.position,
rotation: MRESDK.Quaternion.FromEulerAngles(
hatRecord.rotation.x * MRESDK.DegreesToRadians,
hatRecord.rotation.y * MRESDK.DegreesToRadians,
hatRecord.rotation.z * MRESDK.DegreesToRadians),
scale: hatRecord.scale,
}
},
attachment: {
attachPoint: 'head',
userId
}
}
});
}
// Create the hat model and attach it to the avatar's head.
this.attachedHats[userId] = MRESDK.Actor.CreateFromPrefab(this.context, {
prefabId: this.prefabs[hatId].id,
actor: {
transform: {
local: {
position: hatRecord.position,
rotation: MRESDK.Quaternion.FromEulerAngles(
hatRecord.rotation.x * MRESDK.DegreesToRadians,
hatRecord.rotation.y * MRESDK.DegreesToRadians,
hatRecord.rotation.z * MRESDK.DegreesToRadians),
scale: hatRecord.scale,
}
},
attachment: {
attachPoint: 'head',
userId
}
}
});
}
}

Просмотреть файл

@ -16,8 +16,8 @@ dotenv.config();
// Start listening for connections, and serve static files
const server = new WebHost({
// baseUrl: 'http://<ngrok-id>.ngrok.io',
baseDir: resolvePath(__dirname, '../public')
// baseUrl: 'http://<ngrok-id>.ngrok.io',
baseDir: resolvePath(__dirname, '../public')
});
// Handle new application sessions

Просмотреть файл

@ -63,7 +63,7 @@
"forin": true,
"indent": [
true,
"spaces"
"tabs"
],
"interface-name": [
true,

Просмотреть файл

@ -2,7 +2,7 @@ var shell = require('shelljs');
var forEachSample = require('./foreach-sample');
forEachSample((sampleDir) => {
var cmd = `pushd ${sampleDir} && npm run build && popd`;
console.log(cmd);
shell.exec(cmd);
var cmd = `pushd ${sampleDir} && npm run build && popd`;
console.log(cmd);
shell.exec(cmd);
});

Просмотреть файл

@ -4,54 +4,54 @@ var path = require('path');
var forEachSample = require('./foreach-sample');
const helpText = "Usage: change-sdk-source.js <sdk_source_type>\n\n\tWhere <sdk_source_type> is either: npm or sdk-source\n\n" +
"\tEnsure that there is a 'sdk-path-config.json' containing { \"sdkPath\": \"<path_to_sdk_directory>\" }\n";
"\tEnsure that there is a 'sdk-path-config.json' containing { \"sdkPath\": \"<path_to_sdk_directory>\" }\n";
function printError(errorMessage) {
console.error(`ERROR: ${errorMessage}\n`);
console.error(helpText);
process.exit(1);
console.error(`ERROR: ${errorMessage}\n`);
console.error(helpText);
process.exit(1);
}
function printHelp() {
console.log(helpText);
process.exit();
console.log(helpText);
process.exit();
}
function updateSdkTarget(sampleDir, sdkPath) {
// update environment to be linked to the local source package, or to be the npm package.
if (args[0] === 'npm') {
console.log(`\nUpdating SDK Target: ${sampleDir} -> npm package`);
console.log(`pushd ${sampleDir} && npm unlink ${sdkPath} && npm install && popd`);
shell.exec(`pushd ${sampleDir} && npm unlink ${sdkPath} && npm install && popd`);
} else if (args[0] === 'source') {
console.log(`\nUpdating SDK Target: ${sampleDir} -> ${sdkPath}`);
console.log(`pushd ${sampleDir} && npm link ${sdkPath} && popd`);
shell.exec(`pushd ${sampleDir} && npm link ${sdkPath} && popd`);
} else {
printError("Invalid command.");
}
// update environment to be linked to the local source package, or to be the npm package.
if (args[0] === 'npm') {
console.log(`\nUpdating SDK Target: ${sampleDir} -> npm package`);
console.log(`pushd ${sampleDir} && npm unlink ${sdkPath} && npm install && popd`);
shell.exec(`pushd ${sampleDir} && npm unlink ${sdkPath} && npm install && popd`);
} else if (args[0] === 'source') {
console.log(`\nUpdating SDK Target: ${sampleDir} -> ${sdkPath}`);
console.log(`pushd ${sampleDir} && npm link ${sdkPath} && popd`);
shell.exec(`pushd ${sampleDir} && npm link ${sdkPath} && popd`);
} else {
printError("Invalid command.");
}
}
var args = process.argv.slice(2);
console.log();
if (args.find(arg => { return (arg === '-h' || arg === '--help') }) !== undefined) {
printHelp();
printHelp();
}
if (args.length === 0) {
printError("Must supply an argument of either npm or sdk-source.");
printError("Must supply an argument of either npm or sdk-source.");
}
var config = JSON.parse(fs.readFileSync(`${__dirname}/sdk-path-config.json`, 'utf8'));
if (!config || !config.sdkPath) {
printError("Must provide a valid sdk-path-config.json with a valid sdkPath.");
printError("Must provide a valid sdk-path-config.json with a valid sdkPath.");
}
else {
config.sdkPath = path.resolve(__dirname, config.sdkPath);
config.sdkPath = path.resolve(__dirname, config.sdkPath);
}
forEachSample((sampleDir) => {
updateSdkTarget(sampleDir, config.sdkPath);
updateSdkTarget(sampleDir, config.sdkPath);
});
process.exit();

Просмотреть файл

@ -5,12 +5,12 @@ const scriptPath = __dirname;
const samplesPath = path.join(scriptPath, '..', 'samples');
function forEachSample(cb) {
fs.readdirSync(samplesPath).forEach((entry) => {
const sampleDir = path.join(samplesPath, entry);
if (fs.statSync(sampleDir).isDirectory()) {
cb(sampleDir);
}
});
fs.readdirSync(samplesPath).forEach((entry) => {
const sampleDir = path.join(samplesPath, entry);
if (fs.statSync(sampleDir).isDirectory()) {
cb(sampleDir);
}
});
}
module.exports = forEachSample;

Просмотреть файл

@ -2,7 +2,7 @@ var shell = require('shelljs');
var forEachSample = require('./foreach-sample');
forEachSample((sampleDir) => {
var cmd = `pushd ${sampleDir} && npm install && popd`;
console.log(cmd);
shell.exec(cmd);
var cmd = `pushd ${sampleDir} && npm install && popd`;
console.log(cmd);
shell.exec(cmd);
});

Просмотреть файл

@ -1,3 +1,3 @@
{
"sdkPath": "../../mixed-reality-extension-sdk/packages/sdk"
"sdkPath": "../../mixed-reality-extension-sdk/packages/sdk"
}