Update framework
parent
9a6ce1f832
commit
ae057940af
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,12 @@
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import { Queue } from './queue';
|
||||||
|
import { AssetStorage } from './storage';
|
||||||
|
export declare class Downloader extends EventEmitter {
|
||||||
|
private storage;
|
||||||
|
private queue;
|
||||||
|
private basePath;
|
||||||
|
constructor(storage: AssetStorage, queue: Queue, basePath?: string);
|
||||||
|
setBasePath(path: string): void;
|
||||||
|
download(): Promise<any>;
|
||||||
|
downloadItem(path: string): Promise<any>;
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import { buildPath } from './utils';
|
||||||
|
export class Downloader extends EventEmitter {
|
||||||
|
constructor(storage, queue, basePath = '') {
|
||||||
|
super();
|
||||||
|
this.storage = storage;
|
||||||
|
this.queue = queue;
|
||||||
|
this.basePath = basePath;
|
||||||
|
}
|
||||||
|
setBasePath(path) {
|
||||||
|
this.basePath = this.basePath;
|
||||||
|
}
|
||||||
|
download() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const downloaded = new Map();
|
||||||
|
let numDownloaded = 0;
|
||||||
|
while (this.queue.length() > 0) {
|
||||||
|
const path = this.queue.pop();
|
||||||
|
const item = yield this.downloadItem(buildPath(this.basePath, path));
|
||||||
|
downloaded.set(path, item);
|
||||||
|
numDownloaded++;
|
||||||
|
this.emit('download.progress', {
|
||||||
|
downloaded: numDownloaded,
|
||||||
|
remaining: this.queue.length()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return downloaded;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
downloadItem(path) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const inCache = yield this.storage.get(path);
|
||||||
|
if (inCache) {
|
||||||
|
return inCache;
|
||||||
|
}
|
||||||
|
const response = yield fetch(path);
|
||||||
|
this.storage.add(path, response);
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
export declare class AssetManager extends EventEmitter {
|
||||||
|
private name;
|
||||||
|
private basePath;
|
||||||
|
private downloader;
|
||||||
|
private queue;
|
||||||
|
private storage;
|
||||||
|
private manifest;
|
||||||
|
constructor(name: string, basePath: string);
|
||||||
|
init(): Promise<boolean>;
|
||||||
|
setManifest(path: string): Promise<any>;
|
||||||
|
enqueue(path: string): void;
|
||||||
|
download(): Promise<any>;
|
||||||
|
downloadFromManifest(key: string): Promise<any>;
|
||||||
|
downloadFile(path: string): Promise<any>;
|
||||||
|
setBasePath(path: string): void;
|
||||||
|
clearCache(): void;
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import { Downloader } from './downloader';
|
||||||
|
import { Queue } from './queue';
|
||||||
|
import { Manifest } from './manifest';
|
||||||
|
import { AssetStorage } from './storage';
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import { buildPath } from './utils';
|
||||||
|
export class AssetManager extends EventEmitter {
|
||||||
|
constructor(name, basePath) {
|
||||||
|
super();
|
||||||
|
this.name = name;
|
||||||
|
this.basePath = basePath;
|
||||||
|
this.queue = new Queue();
|
||||||
|
this.storage = new AssetStorage(name);
|
||||||
|
this.downloader = new Downloader(this.storage, this.queue, this.basePath);
|
||||||
|
console.log(`Asset manager initialized`);
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield this.storage.init();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setManifest(path) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
this.manifest = yield Manifest(`${this.basePath}/${path}`);
|
||||||
|
this.storage.setManifest(this.manifest);
|
||||||
|
return this.manifest;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
enqueue(path) {
|
||||||
|
this.queue.add(path);
|
||||||
|
}
|
||||||
|
download() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield this.downloader.download();
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
downloadFromManifest(key) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const paths = this.manifest[key];
|
||||||
|
paths.forEach((path) => this.enqueue(path));
|
||||||
|
this.downloader.on('download.progress', (info) => this.emit('progress', info));
|
||||||
|
const files = yield this.downloader.download();
|
||||||
|
this.downloader.off('download.progress');
|
||||||
|
return files;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
downloadFile(path) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield this.downloader.downloadItem(buildPath(this.basePath, path));
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setBasePath(path) {
|
||||||
|
this.basePath = this.basePath;
|
||||||
|
this.downloader.setBasePath(this.basePath);
|
||||||
|
}
|
||||||
|
clearCache() {
|
||||||
|
this.storage.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export declare function Manifest(manifestPath: string): Promise<any>;
|
||||||
|
export declare function CheckManifest(manifest: any): boolean;
|
|
@ -0,0 +1,40 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import * as yaml from 'yaml';
|
||||||
|
export function Manifest(manifestPath) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
const response = yield fetch(manifestPath);
|
||||||
|
console.log(response);
|
||||||
|
const data = yield response.text();
|
||||||
|
console.log(`Parsing: `, data);
|
||||||
|
const manifest = yaml.parse(data);
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
alert(`Error occured: ${error.toString()}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function CheckManifest(manifest) {
|
||||||
|
const prevManifestStr = localStorage.getItem('manifest');
|
||||||
|
if (!prevManifestStr) {
|
||||||
|
localStorage.setItem('manifest', JSON.stringify(manifest));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const prevManifest = JSON.parse(prevManifestStr);
|
||||||
|
if (prevManifest.version === manifest.version) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
localStorage.setItem('manifest', manifest);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
export declare class Queue {
|
||||||
|
private items;
|
||||||
|
constructor();
|
||||||
|
add(file: string): string[];
|
||||||
|
remove(file: string): string[];
|
||||||
|
pop(): string;
|
||||||
|
length(): number;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
export class Queue {
|
||||||
|
constructor() {
|
||||||
|
this.items = [];
|
||||||
|
}
|
||||||
|
add(file) {
|
||||||
|
this.items.push(file);
|
||||||
|
return this.items;
|
||||||
|
}
|
||||||
|
remove(file) {
|
||||||
|
this.items = this.items.filter((item) => item !== file);
|
||||||
|
return this.items;
|
||||||
|
}
|
||||||
|
pop() {
|
||||||
|
return this.items.pop();
|
||||||
|
}
|
||||||
|
length() {
|
||||||
|
return this.items.length;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
export declare class AssetStorage {
|
||||||
|
private id;
|
||||||
|
private cache;
|
||||||
|
private manifest;
|
||||||
|
constructor(id: string);
|
||||||
|
init(): Promise<void>;
|
||||||
|
add(request: RequestInfo, response: Response): Promise<Boolean>;
|
||||||
|
get(request: RequestInfo): Promise<Response>;
|
||||||
|
setManifest(manifest: any): Promise<void>;
|
||||||
|
clear(): Promise<void>;
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import { CheckManifest } from './manifest';
|
||||||
|
export class AssetStorage {
|
||||||
|
constructor(id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
this.cache = yield caches.open(this.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
add(request, response) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield this.cache.put(request, response);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
get(request) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield this.cache.match(request);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setManifest(manifest) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
this.manifest = manifest;
|
||||||
|
if (!CheckManifest(this.manifest)) {
|
||||||
|
yield this.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
clear() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const keys = yield this.cache.keys();
|
||||||
|
keys.forEach((key) => __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield this.cache.delete(key);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export {};
|
|
@ -0,0 +1,5 @@
|
||||||
|
const yaml = require('yaml');
|
||||||
|
const fs = require('fs');
|
||||||
|
const data = fs.readFileSync('manifest.yaml');
|
||||||
|
const parsed = yaml.parse(data.toString());
|
||||||
|
console.log(parsed);
|
|
@ -0,0 +1 @@
|
||||||
|
export declare function buildPath(basePath: string, path: string): string;
|
|
@ -0,0 +1,8 @@
|
||||||
|
export function buildPath(basePath, path) {
|
||||||
|
if (!basePath) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return `${basePath}/${path}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
export declare class BaseComponent {
|
||||||
|
id: number;
|
||||||
|
properties: any;
|
||||||
|
constructor();
|
||||||
|
clone(): BaseComponent;
|
||||||
|
}
|
||||||
|
export interface Component {
|
||||||
|
id: number;
|
||||||
|
properties: any;
|
||||||
|
clone(): BaseComponent;
|
||||||
|
new (): BaseComponent;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
export class BaseComponent {
|
||||||
|
constructor() {
|
||||||
|
this.id = 0;
|
||||||
|
this.properties = {};
|
||||||
|
}
|
||||||
|
clone() {
|
||||||
|
const comp = new BaseComponent();
|
||||||
|
comp.properties = this.properties;
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { BaseComponent, Component } from './component';
|
||||||
|
export declare class BaseEntity {
|
||||||
|
id: number;
|
||||||
|
components: Map<number, BaseComponent>;
|
||||||
|
constructor();
|
||||||
|
addComponent(component: Component): void;
|
||||||
|
removeComponent(component: BaseComponent): void;
|
||||||
|
getComponentIDs(): number[];
|
||||||
|
getComponent(component: BaseComponent): BaseComponent;
|
||||||
|
getComponentByID(id: number): BaseComponent;
|
||||||
|
}
|
||||||
|
export interface Entity {
|
||||||
|
new (): BaseComponent;
|
||||||
|
addComponent(component: Component): any;
|
||||||
|
removeComponent(component: BaseComponent): any;
|
||||||
|
getComponentIDs(): number[];
|
||||||
|
getComponent(component: BaseComponent): any;
|
||||||
|
getComponentByID(id: number): BaseComponent;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
export class BaseEntity {
|
||||||
|
constructor() {
|
||||||
|
this.components = new Map();
|
||||||
|
this.id = 0;
|
||||||
|
}
|
||||||
|
addComponent(component) {
|
||||||
|
let comp = new component();
|
||||||
|
comp.id = component.id;
|
||||||
|
this.components.set(component.id, comp);
|
||||||
|
}
|
||||||
|
removeComponent(component) {
|
||||||
|
this.components.delete(component.id);
|
||||||
|
}
|
||||||
|
getComponentIDs() {
|
||||||
|
return [...this.components.keys()];
|
||||||
|
}
|
||||||
|
getComponent(component) {
|
||||||
|
return this.components.get(component.id);
|
||||||
|
}
|
||||||
|
getComponentByID(id) {
|
||||||
|
return this.components.get(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Component } from './component';
|
||||||
|
import { BaseEntity, Entity } from './entity';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
import { Query } from './query';
|
||||||
|
import { System } from './system';
|
||||||
|
export declare class World {
|
||||||
|
entities: Array<BaseEntity>;
|
||||||
|
components: Map<number, Component>;
|
||||||
|
componentNamesToIDs: Map<string, number>;
|
||||||
|
systems: Set<System>;
|
||||||
|
nextEntityID: number;
|
||||||
|
nextComponentID: number;
|
||||||
|
nextQueryID: number;
|
||||||
|
queryCache: Array<Query>;
|
||||||
|
eventBus: EventBus;
|
||||||
|
constructor();
|
||||||
|
run(): void;
|
||||||
|
createSystem(systemExecutor: Function): void;
|
||||||
|
addSystem(system: System): void;
|
||||||
|
addEntity(entity: BaseEntity): void;
|
||||||
|
removeEntity(entityToRemove: BaseEntity): void;
|
||||||
|
createEntity(components: Array<Component>): BaseEntity;
|
||||||
|
extendEntity(entity: Entity, components: Array<Component>): BaseEntity;
|
||||||
|
createComponent(component: Component): Component;
|
||||||
|
query(include: Array<Component>, exclude: Array<Component>): Array<BaseEntity>;
|
||||||
|
createQuery(include: Array<Component>, exclude: Array<Component>): Query;
|
||||||
|
markQueriesDirty(): void;
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { BaseEntity } from './entity';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
import { Query } from './query';
|
||||||
|
import { System } from './system';
|
||||||
|
export class World {
|
||||||
|
constructor() {
|
||||||
|
this.nextEntityID = 0;
|
||||||
|
this.nextComponentID = 0;
|
||||||
|
this.nextQueryID = 0;
|
||||||
|
this.entities = new Array();
|
||||||
|
this.systems = new Set();
|
||||||
|
this.components = new Map();
|
||||||
|
this.componentNamesToIDs = new Map();
|
||||||
|
this.queryCache = new Array();
|
||||||
|
this.eventBus = new EventBus();
|
||||||
|
}
|
||||||
|
run() {
|
||||||
|
this.systems.forEach((system) => {
|
||||||
|
system.execute(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
createSystem(systemExecutor) {
|
||||||
|
const newSystem = new System(systemExecutor);
|
||||||
|
this.systems.add(newSystem);
|
||||||
|
}
|
||||||
|
addSystem(system) {
|
||||||
|
this.systems.add(system);
|
||||||
|
}
|
||||||
|
addEntity(entity) {
|
||||||
|
this.entities.push(entity);
|
||||||
|
this.markQueriesDirty();
|
||||||
|
}
|
||||||
|
removeEntity(entityToRemove) {
|
||||||
|
this.entities = this.entities.filter((entity) => entity !== entityToRemove);
|
||||||
|
this.markQueriesDirty();
|
||||||
|
}
|
||||||
|
createEntity(components) {
|
||||||
|
const newEntity = new BaseEntity();
|
||||||
|
newEntity.id = this.nextEntityID;
|
||||||
|
this.nextEntityID++;
|
||||||
|
components.forEach((component) => {
|
||||||
|
if (this.componentNamesToIDs.has(component.name)) {
|
||||||
|
component.id = this.componentNamesToIDs.get(component.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.componentNamesToIDs.set(component.name, this.nextComponentID);
|
||||||
|
component.id = this.nextComponentID;
|
||||||
|
this.nextComponentID++;
|
||||||
|
}
|
||||||
|
newEntity.addComponent(component);
|
||||||
|
});
|
||||||
|
this.entities.push(newEntity);
|
||||||
|
this.markQueriesDirty();
|
||||||
|
return newEntity;
|
||||||
|
}
|
||||||
|
extendEntity(entity, components) {
|
||||||
|
const toClone = this.entities.find((found) => entity.name === found.constructor.name);
|
||||||
|
const cloned = new BaseEntity();
|
||||||
|
cloned.id = this.nextEntityID;
|
||||||
|
this.nextEntityID++;
|
||||||
|
toClone.components.forEach((component) => {
|
||||||
|
cloned.addComponent(this.components.get(component.id));
|
||||||
|
});
|
||||||
|
components.forEach((component) => {
|
||||||
|
if (this.componentNamesToIDs.has(component.name)) {
|
||||||
|
component.id = this.componentNamesToIDs.get(component.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.componentNamesToIDs.set(component.name, this.nextComponentID);
|
||||||
|
component.id = this.nextComponentID;
|
||||||
|
this.nextComponentID++;
|
||||||
|
}
|
||||||
|
cloned.addComponent(component);
|
||||||
|
});
|
||||||
|
this.entities.push(cloned);
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
|
createComponent(component) {
|
||||||
|
const newComponent = component;
|
||||||
|
newComponent.id = this.nextComponentID;
|
||||||
|
this.nextComponentID++;
|
||||||
|
this.components.set(newComponent.id, newComponent);
|
||||||
|
this.componentNamesToIDs.set(component.name, component.id);
|
||||||
|
return newComponent;
|
||||||
|
}
|
||||||
|
query(include, exclude) {
|
||||||
|
const query = new Query(include, exclude, this);
|
||||||
|
const cache = this.queryCache.find((item) => item.include == include && item.exclude == exclude);
|
||||||
|
if (cache) {
|
||||||
|
return cache.execute();
|
||||||
|
}
|
||||||
|
this.queryCache.push(query);
|
||||||
|
return query.execute();
|
||||||
|
}
|
||||||
|
createQuery(include, exclude) {
|
||||||
|
const newQuery = new Query(include, exclude, this);
|
||||||
|
newQuery.id = this.nextQueryID;
|
||||||
|
this.nextQueryID++;
|
||||||
|
this.queryCache.push(newQuery);
|
||||||
|
return newQuery;
|
||||||
|
}
|
||||||
|
markQueriesDirty() {
|
||||||
|
this.queryCache.forEach((query) => (query.isDirty = true));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { World } from '.';
|
||||||
|
import { Component } from './component';
|
||||||
|
import { BaseEntity } from './entity';
|
||||||
|
export declare class Query {
|
||||||
|
include: Array<Component>;
|
||||||
|
exclude: Array<Component>;
|
||||||
|
world: World;
|
||||||
|
id: number;
|
||||||
|
private results;
|
||||||
|
isDirty: boolean;
|
||||||
|
includeComponentIds: number[];
|
||||||
|
excludeComponentIds: number[];
|
||||||
|
constructor(include: Array<Component>, exclude: Array<Component>, world: World);
|
||||||
|
execute(): Array<BaseEntity>;
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
export class Query {
|
||||||
|
constructor(include, exclude, world) {
|
||||||
|
this.include = include;
|
||||||
|
this.exclude = exclude;
|
||||||
|
this.world = world;
|
||||||
|
this.isDirty = true;
|
||||||
|
this.results = new Array();
|
||||||
|
this.includeComponentIds = include.map((component) => component.id);
|
||||||
|
this.excludeComponentIds = exclude.map((component) => component.id);
|
||||||
|
this.id = 0;
|
||||||
|
}
|
||||||
|
execute() {
|
||||||
|
if (!this.isDirty && this.results) {
|
||||||
|
return this.results;
|
||||||
|
}
|
||||||
|
let filtered;
|
||||||
|
this.includeComponentIds = this.include.map((component) => this.world.componentNamesToIDs.get(component.name));
|
||||||
|
this.excludeComponentIds = this.exclude.map((component) => this.world.componentNamesToIDs.get(component.name));
|
||||||
|
const entities = this.world.entities.filter((entity) => {
|
||||||
|
let ids = entity.getComponentIDs();
|
||||||
|
// let includes = ids.map(id => this.includeComponentIds.includes(id)).includes(true);
|
||||||
|
let excludes = ids
|
||||||
|
.map((id) => this.excludeComponentIds.includes(id))
|
||||||
|
.includes(true);
|
||||||
|
let includes = ids.filter((id) => this.includeComponentIds.includes(id));
|
||||||
|
return includes.length === this.includeComponentIds.length && !excludes;
|
||||||
|
});
|
||||||
|
if (entities.length > 0) {
|
||||||
|
this.isDirty = false;
|
||||||
|
this.results = entities;
|
||||||
|
}
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { World } from '.';
|
||||||
|
export declare class System {
|
||||||
|
executor: Function;
|
||||||
|
constructor(executor: Function);
|
||||||
|
execute(world: World): void;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
export class System {
|
||||||
|
constructor(executor) {
|
||||||
|
this.executor = executor;
|
||||||
|
}
|
||||||
|
execute(world) {
|
||||||
|
if (this.executor) {
|
||||||
|
this.executor(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
export declare class EventBus {
|
||||||
|
private events;
|
||||||
|
constructor();
|
||||||
|
emit(id: string, data?: any): void;
|
||||||
|
subscribe(id: string, subscriber: Function): void;
|
||||||
|
unsubscribe(id: string, subscriber: Function): void;
|
||||||
|
unsubscribeAll(id: string): void;
|
||||||
|
}
|
||||||
|
export declare class EventItem {
|
||||||
|
id: string;
|
||||||
|
subscribers: Function[];
|
||||||
|
constructor(id: string);
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
export class EventBus {
|
||||||
|
constructor() {
|
||||||
|
this.events = new Map();
|
||||||
|
}
|
||||||
|
emit(id, data = {}) {
|
||||||
|
let ev = this.events.get(id);
|
||||||
|
if (!ev) {
|
||||||
|
let ev = new EventItem(id);
|
||||||
|
this.events.set(id, ev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.subscribers.forEach((subscriber) => {
|
||||||
|
subscriber(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
subscribe(id, subscriber) {
|
||||||
|
let ev = this.events.get(id);
|
||||||
|
if (!ev) {
|
||||||
|
ev = new EventItem(id);
|
||||||
|
this.events.set(id, ev);
|
||||||
|
}
|
||||||
|
ev.subscribers.push(subscriber);
|
||||||
|
}
|
||||||
|
unsubscribe(id, subscriber) {
|
||||||
|
if (this.events.has(id)) {
|
||||||
|
let ev = this.events.get(id);
|
||||||
|
ev.subscribers = ev.subscribers.filter((fn) => fn !== subscriber);
|
||||||
|
if (ev.subscribers.length < 1) {
|
||||||
|
this.events.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsubscribeAll(id) {
|
||||||
|
if (this.events.has(id)) {
|
||||||
|
this.events.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class EventItem {
|
||||||
|
constructor(id) {
|
||||||
|
this.id = id;
|
||||||
|
this.subscribers = [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { AssetManager } from '../asset-manager';
|
||||||
|
import { Input } from '../input';
|
||||||
|
import Resonator from '../resonator';
|
||||||
|
import { Scene } from '../scene/scene';
|
||||||
|
import { SceneManager } from '../scene/manager';
|
||||||
|
import { Scheduler } from '../scheduler';
|
||||||
|
import { TTS } from '../tts';
|
||||||
|
import { HTTPLoader } from '../resonator/loaders/http-loader';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
import { World } from '../world';
|
||||||
|
export declare class Game extends EventBus {
|
||||||
|
assetLoader: HTTPLoader;
|
||||||
|
assetManager: AssetManager;
|
||||||
|
resonator: Resonator;
|
||||||
|
input: Input;
|
||||||
|
tts: TTS;
|
||||||
|
sceneManager: SceneManager;
|
||||||
|
scheduler: Scheduler;
|
||||||
|
world: World;
|
||||||
|
constructor();
|
||||||
|
init(): void;
|
||||||
|
start(): void;
|
||||||
|
addScene(scene: Scene): void;
|
||||||
|
addDefaultScene(scene: Scene): void;
|
||||||
|
setWorld(world: World): void;
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { AssetManager } from '../asset-manager';
|
||||||
|
import { Input } from '../input';
|
||||||
|
import Resonator from '../resonator';
|
||||||
|
import { SceneManager } from '../scene/manager';
|
||||||
|
import { Scheduler } from '../scheduler';
|
||||||
|
import { TTS } from '../tts';
|
||||||
|
import { AriaOutput } from '../tts/outputs/aria';
|
||||||
|
import { HTTPLoader } from '../resonator/loaders/http-loader';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
export class Game extends EventBus {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
this.assetManager = new AssetManager('game', '');
|
||||||
|
this.assetLoader = new HTTPLoader();
|
||||||
|
this.resonator = new Resonator(this.assetLoader);
|
||||||
|
this.input = new Input(['keyboard'], document.body);
|
||||||
|
this.tts = new TTS(new AriaOutput());
|
||||||
|
this.sceneManager = new SceneManager();
|
||||||
|
this.scheduler = new Scheduler(60);
|
||||||
|
this.emit('ready');
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
this.scheduler.start();
|
||||||
|
this.scheduler.subscribe('update.logic', (dt) => {
|
||||||
|
this.sceneManager.currentScene.update(dt);
|
||||||
|
this.world.update(dt);
|
||||||
|
});
|
||||||
|
this.scheduler.subscribe('update.draw', (dt) => this.sceneManager.currentScene.updateDraw());
|
||||||
|
this.sceneManager.init();
|
||||||
|
}
|
||||||
|
addScene(scene) {
|
||||||
|
this.sceneManager.addScene(scene);
|
||||||
|
}
|
||||||
|
addDefaultScene(scene) {
|
||||||
|
this.sceneManager.addScene(scene);
|
||||||
|
this.sceneManager.setDefaultScene(scene);
|
||||||
|
}
|
||||||
|
setWorld(world) {
|
||||||
|
this.world = world;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export * from './asset-manager';
|
||||||
|
export * from './ecs';
|
||||||
|
export * from './event-bus';
|
||||||
|
export * from './input';
|
||||||
|
export * from './resonator';
|
||||||
|
export * from './tts';
|
||||||
|
export * from './ui';
|
|
@ -0,0 +1,7 @@
|
||||||
|
export * from './asset-manager';
|
||||||
|
export * from './ecs';
|
||||||
|
export * from './event-bus';
|
||||||
|
export * from './input';
|
||||||
|
export * from './resonator';
|
||||||
|
export * from './tts';
|
||||||
|
export * from './ui';
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { BaseInput } from './inputs/base-input';
|
||||||
|
import { State } from './interfaces/state';
|
||||||
|
export declare class Input {
|
||||||
|
private InputIDs;
|
||||||
|
private element;
|
||||||
|
private inputs;
|
||||||
|
constructor(InputIDs: string[], element: HTMLElement);
|
||||||
|
private init;
|
||||||
|
addInput(id: string, input: BaseInput): this;
|
||||||
|
capture(preventDefault?: boolean): void;
|
||||||
|
release(): void;
|
||||||
|
getState(): State;
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { createInput } from './input-factory';
|
||||||
|
export class Input {
|
||||||
|
constructor(InputIDs, element) {
|
||||||
|
this.InputIDs = InputIDs;
|
||||||
|
this.element = element;
|
||||||
|
this.inputs = new Map();
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
this.InputIDs.forEach((inputID) => {
|
||||||
|
const thing = createInput(inputID);
|
||||||
|
const instance = new thing(this.element);
|
||||||
|
this.inputs.set(inputID, instance);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addInput(id, input) {
|
||||||
|
this.inputs.set(id, input);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
capture(preventDefault = true) {
|
||||||
|
this.inputs.forEach((input) => input.capture(preventDefault));
|
||||||
|
}
|
||||||
|
release() {
|
||||||
|
this.inputs.forEach((input) => input.release());
|
||||||
|
}
|
||||||
|
getState() {
|
||||||
|
const state = {};
|
||||||
|
this.inputs.forEach((input, inputID) => {
|
||||||
|
if (input)
|
||||||
|
state[inputID] = input.getState();
|
||||||
|
});
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export declare function createInput(key: string): any;
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Keyboard } from './inputs/keyboard';
|
||||||
|
import { Mouse } from './inputs/mouse';
|
||||||
|
export function createInput(key) {
|
||||||
|
switch (key) {
|
||||||
|
case 'keyboard':
|
||||||
|
return Keyboard;
|
||||||
|
break;
|
||||||
|
case 'mouse':
|
||||||
|
return Mouse;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
export declare class BaseInput {
|
||||||
|
protected element: HTMLElement;
|
||||||
|
protected active: boolean;
|
||||||
|
constructor(element: HTMLElement);
|
||||||
|
getState(): any;
|
||||||
|
capture(preventDefault: boolean): void;
|
||||||
|
release(): void;
|
||||||
|
}
|
||||||
|
export interface IBaseInput {
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
export class BaseInput {
|
||||||
|
constructor(element) {
|
||||||
|
this.element = element;
|
||||||
|
}
|
||||||
|
getState() { }
|
||||||
|
capture(preventDefault) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
release() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export declare class Joystick extends BaseInput {
|
||||||
|
constructor(element: HTMLElement);
|
||||||
|
}
|
||||||
|
export interface IJoystick {
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export class Joystick extends BaseInput {
|
||||||
|
constructor(element) {
|
||||||
|
super(element);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export declare class Keyboard extends BaseInput {
|
||||||
|
private keysDown;
|
||||||
|
private keysJustPressed;
|
||||||
|
private keysJustReleased;
|
||||||
|
private preventDefault;
|
||||||
|
constructor(element: HTMLElement);
|
||||||
|
capture(preventDefault: boolean): void;
|
||||||
|
release(): void;
|
||||||
|
getState(): IKeyboard;
|
||||||
|
private handleKeyDown;
|
||||||
|
private handleKeyUp;
|
||||||
|
}
|
||||||
|
export interface IKeyboard {
|
||||||
|
keysDown: Map<number, boolean>;
|
||||||
|
keysJustPressed: Map<number, boolean>;
|
||||||
|
keysJustReleased: Map<number, boolean>;
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export class Keyboard extends BaseInput {
|
||||||
|
constructor(element) {
|
||||||
|
super(element);
|
||||||
|
this.keysDown = new Map();
|
||||||
|
this.keysJustPressed = new Map();
|
||||||
|
this.keysJustReleased = new Map();
|
||||||
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||||
|
this.handleKeyUp = this.handleKeyUp.bind(this);
|
||||||
|
}
|
||||||
|
capture(preventDefault) {
|
||||||
|
this.active = true;
|
||||||
|
this.preventDefault = preventDefault;
|
||||||
|
this.element.addEventListener('keydown', this.handleKeyDown);
|
||||||
|
this.element.addEventListener('keyup', this.handleKeyUp);
|
||||||
|
}
|
||||||
|
release() {
|
||||||
|
this.active = false;
|
||||||
|
this.element.removeEventListener('keydown', this.handleKeyDown);
|
||||||
|
this.element.removeEventListener('keyup', this.handleKeyUp);
|
||||||
|
}
|
||||||
|
getState() {
|
||||||
|
const state = {
|
||||||
|
keysDown: new Map(this.keysDown),
|
||||||
|
keysJustPressed: new Map(this.keysJustPressed),
|
||||||
|
keysJustReleased: new Map(this.keysJustReleased)
|
||||||
|
};
|
||||||
|
this.keysJustPressed.clear();
|
||||||
|
this.keysJustReleased.clear();
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
handleKeyDown(event) {
|
||||||
|
if (this.active && this.preventDefault)
|
||||||
|
event.preventDefault();
|
||||||
|
if (this.keysDown.get(event.keyCode))
|
||||||
|
return;
|
||||||
|
this.keysDown.set(event.keyCode, true);
|
||||||
|
this.keysJustPressed.set(event.keyCode, true);
|
||||||
|
this.keysJustReleased.set(event.keyCode, false);
|
||||||
|
}
|
||||||
|
handleKeyUp(event) {
|
||||||
|
if (this.active && this.preventDefault)
|
||||||
|
event.preventDefault();
|
||||||
|
if (!this.keysDown.get(event.keyCode))
|
||||||
|
return;
|
||||||
|
this.keysDown.set(event.keyCode, false);
|
||||||
|
this.keysJustPressed.set(event.keyCode, false);
|
||||||
|
this.keysJustReleased.set(event.keyCode, true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export declare class Mouse extends BaseInput {
|
||||||
|
private mousePosition;
|
||||||
|
private mouseDelta;
|
||||||
|
private mouseWheel;
|
||||||
|
private mouseButtons;
|
||||||
|
constructor(element: HTMLElement);
|
||||||
|
capture(): void;
|
||||||
|
release(): void;
|
||||||
|
getState(): IMouse;
|
||||||
|
private handleMouseDown;
|
||||||
|
private handleMouseMove;
|
||||||
|
private handleMouseUp;
|
||||||
|
private handlePointerChange;
|
||||||
|
}
|
||||||
|
export declare class Position {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
export declare class MouseButtons {
|
||||||
|
keysDown: Map<number, boolean>;
|
||||||
|
keysJustPressed: Map<number, boolean>;
|
||||||
|
keysJustReleased: Map<number, boolean>;
|
||||||
|
}
|
||||||
|
export declare class Delta {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
export interface IMouse {
|
||||||
|
mouseButtons: MouseButtons;
|
||||||
|
mousePosition: Position;
|
||||||
|
mouseWheel: Delta;
|
||||||
|
mouseDelta: Delta;
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export class Mouse extends BaseInput {
|
||||||
|
constructor(element) {
|
||||||
|
super(element);
|
||||||
|
this.mousePosition = new Position();
|
||||||
|
this.mouseDelta = new Delta();
|
||||||
|
this.mouseWheel = new Delta();
|
||||||
|
this.mouseButtons = new MouseButtons();
|
||||||
|
}
|
||||||
|
capture() {
|
||||||
|
this.handleMouseDown = this.handleMouseDown.bind(this);
|
||||||
|
this.handleMouseMove = this.handleMouseMove.bind(this);
|
||||||
|
this.handleMouseUp = this.handleMouseUp.bind(this);
|
||||||
|
this.handlePointerChange = this.handlePointerChange.bind(this);
|
||||||
|
this.active = true;
|
||||||
|
this.element.addEventListener('mousedown', this.handleMouseDown);
|
||||||
|
this.element.addEventListener('mousemove', this.handleMouseMove);
|
||||||
|
this.element.addEventListener('mouseup', this.handleMouseUp);
|
||||||
|
document.addEventListener('pointerlockchange', this.handlePointerChange);
|
||||||
|
}
|
||||||
|
release() {
|
||||||
|
this.active = false;
|
||||||
|
this.element.removeEventListener('mousedown', this.handleMouseDown);
|
||||||
|
this.element.removeEventListener('mousemove', this.handleMouseMove);
|
||||||
|
this.element.removeEventListener('mouseup', this.handleMouseUp);
|
||||||
|
}
|
||||||
|
getState() {
|
||||||
|
const { mouseButtons, mouseDelta, mousePosition, mouseWheel } = this;
|
||||||
|
const state = {
|
||||||
|
mouseButtons: {
|
||||||
|
keysDown: new Map(this.mouseButtons.keysDown),
|
||||||
|
keysJustPressed: new Map(this.mouseButtons.keysJustPressed),
|
||||||
|
keysJustReleased: new Map(this.mouseButtons.keysJustReleased)
|
||||||
|
},
|
||||||
|
mouseDelta,
|
||||||
|
mousePosition,
|
||||||
|
mouseWheel
|
||||||
|
};
|
||||||
|
this.mouseButtons.keysJustPressed.clear();
|
||||||
|
this.mouseButtons.keysJustReleased.clear();
|
||||||
|
this.mouseDelta.x = 0;
|
||||||
|
this.mouseDelta.y = 0;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
handleMouseDown(event) {
|
||||||
|
if (this.active)
|
||||||
|
event.preventDefault();
|
||||||
|
this.mouseButtons.keysDown.set(event.button, true);
|
||||||
|
this.mouseButtons.keysJustPressed.set(event.button, true);
|
||||||
|
this.mouseButtons.keysJustReleased.set(event.button, false);
|
||||||
|
}
|
||||||
|
handleMouseMove(event) {
|
||||||
|
if (this.active)
|
||||||
|
event.preventDefault();
|
||||||
|
this.mousePosition.x = event.clientX;
|
||||||
|
this.mousePosition.y = event.clientY;
|
||||||
|
this.mouseDelta.x = event.movementX;
|
||||||
|
this.mouseDelta.y = event.movementY;
|
||||||
|
}
|
||||||
|
handleMouseUp(event) {
|
||||||
|
if (this.active)
|
||||||
|
event.preventDefault();
|
||||||
|
this.mouseButtons.keysJustReleased.set(event.button, true);
|
||||||
|
this.mouseButtons.keysDown.set(event.button, false);
|
||||||
|
this.mouseButtons.keysJustPressed.set(event.button, false);
|
||||||
|
}
|
||||||
|
handlePointerChange() {
|
||||||
|
if (document.pointerLockElement !== this.element) {
|
||||||
|
this.element.addEventListener('click', () => {
|
||||||
|
this.element.requestPointerLock();
|
||||||
|
}, {
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Position {
|
||||||
|
}
|
||||||
|
export class MouseButtons {
|
||||||
|
constructor() {
|
||||||
|
this.keysDown = new Map();
|
||||||
|
this.keysJustPressed = new Map();
|
||||||
|
this.keysJustReleased = new Map();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Delta {
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { IKeyboard } from '../inputs/keyboard';
|
||||||
|
export interface State {
|
||||||
|
keyboard?: IKeyboard;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export {};
|
|
@ -0,0 +1,122 @@
|
||||||
|
export declare let KeyCodes: {
|
||||||
|
CANCEL: number;
|
||||||
|
HELP: number;
|
||||||
|
BACK_SPACE: number;
|
||||||
|
TAB: number;
|
||||||
|
CLEAR: number;
|
||||||
|
RETURN: number;
|
||||||
|
ENTER: number;
|
||||||
|
SHIFT: number;
|
||||||
|
CONTROL: number;
|
||||||
|
ALT: number;
|
||||||
|
PAUSE: number;
|
||||||
|
CAPS_LOCK: number;
|
||||||
|
ESCAPE: number;
|
||||||
|
SPACE: number;
|
||||||
|
PAGE_UP: number;
|
||||||
|
PAGE_DOWN: number;
|
||||||
|
END: number;
|
||||||
|
HOME: number;
|
||||||
|
LEFT: number;
|
||||||
|
UP: number;
|
||||||
|
RIGHT: number;
|
||||||
|
DOWN: number;
|
||||||
|
PRINTSCREEN: number;
|
||||||
|
INSERT: number;
|
||||||
|
DELETE: number;
|
||||||
|
0: number;
|
||||||
|
1: number;
|
||||||
|
2: number;
|
||||||
|
3: number;
|
||||||
|
4: number;
|
||||||
|
5: number;
|
||||||
|
6: number;
|
||||||
|
7: number;
|
||||||
|
8: number;
|
||||||
|
9: number;
|
||||||
|
SEMICOLON: number;
|
||||||
|
EQUALS: number;
|
||||||
|
A: number;
|
||||||
|
B: number;
|
||||||
|
C: number;
|
||||||
|
D: number;
|
||||||
|
E: number;
|
||||||
|
F: number;
|
||||||
|
G: number;
|
||||||
|
H: number;
|
||||||
|
I: number;
|
||||||
|
J: number;
|
||||||
|
K: number;
|
||||||
|
L: number;
|
||||||
|
M: number;
|
||||||
|
N: number;
|
||||||
|
O: number;
|
||||||
|
P: number;
|
||||||
|
Q: number;
|
||||||
|
R: number;
|
||||||
|
S: number;
|
||||||
|
T: number;
|
||||||
|
U: number;
|
||||||
|
V: number;
|
||||||
|
W: number;
|
||||||
|
X: number;
|
||||||
|
Y: number;
|
||||||
|
Z: number;
|
||||||
|
CONTEXT_MENU: number;
|
||||||
|
NUMPAD0: number;
|
||||||
|
NUMPAD1: number;
|
||||||
|
NUMPAD2: number;
|
||||||
|
NUMPAD3: number;
|
||||||
|
NUMPAD4: number;
|
||||||
|
NUMPAD5: number;
|
||||||
|
NUMPAD6: number;
|
||||||
|
NUMPAD7: number;
|
||||||
|
NUMPAD8: number;
|
||||||
|
NUMPAD9: number;
|
||||||
|
MULTIPLY: number;
|
||||||
|
ADD: number;
|
||||||
|
SEPARATOR: number;
|
||||||
|
SUBTRACT: number;
|
||||||
|
DECIMAL: number;
|
||||||
|
DIVIDE: number;
|
||||||
|
F1: number;
|
||||||
|
F2: number;
|
||||||
|
F3: number;
|
||||||
|
F4: number;
|
||||||
|
F5: number;
|
||||||
|
F6: number;
|
||||||
|
F7: number;
|
||||||
|
F8: number;
|
||||||
|
F9: number;
|
||||||
|
F10: number;
|
||||||
|
F11: number;
|
||||||
|
F12: number;
|
||||||
|
F13: number;
|
||||||
|
F14: number;
|
||||||
|
F15: number;
|
||||||
|
F16: number;
|
||||||
|
F17: number;
|
||||||
|
F18: number;
|
||||||
|
F19: number;
|
||||||
|
F20: number;
|
||||||
|
F21: number;
|
||||||
|
F22: number;
|
||||||
|
F23: number;
|
||||||
|
F24: number;
|
||||||
|
NUM_LOCK: number;
|
||||||
|
SCROLL_LOCK: number;
|
||||||
|
COMMA: number;
|
||||||
|
PERIOD: number;
|
||||||
|
SLASH: number;
|
||||||
|
BACK_QUOTE: number;
|
||||||
|
OPEN_BRACKET: number;
|
||||||
|
BACK_SLASH: number;
|
||||||
|
CLOSE_BRACKET: number;
|
||||||
|
QUOTE: number;
|
||||||
|
META: number;
|
||||||
|
};
|
||||||
|
export declare let MouseCodes: {
|
||||||
|
LEFT: number;
|
||||||
|
RIGHT: number;
|
||||||
|
MIDDLE: number;
|
||||||
|
};
|
|
@ -0,0 +1,122 @@
|
||||||
|
export let KeyCodes = {
|
||||||
|
CANCEL: 3,
|
||||||
|
HELP: 6,
|
||||||
|
BACK_SPACE: 8,
|
||||||
|
TAB: 9,
|
||||||
|
CLEAR: 12,
|
||||||
|
RETURN: 13,
|
||||||
|
ENTER: 14,
|
||||||
|
SHIFT: 16,
|
||||||
|
CONTROL: 17,
|
||||||
|
ALT: 18,
|
||||||
|
PAUSE: 19,
|
||||||
|
CAPS_LOCK: 20,
|
||||||
|
ESCAPE: 27,
|
||||||
|
SPACE: 32,
|
||||||
|
PAGE_UP: 33,
|
||||||
|
PAGE_DOWN: 34,
|
||||||
|
END: 35,
|
||||||
|
HOME: 36,
|
||||||
|
LEFT: 37,
|
||||||
|
UP: 38,
|
||||||
|
RIGHT: 39,
|
||||||
|
DOWN: 40,
|
||||||
|
PRINTSCREEN: 44,
|
||||||
|
INSERT: 45,
|
||||||
|
DELETE: 46,
|
||||||
|
0: 48,
|
||||||
|
1: 49,
|
||||||
|
2: 50,
|
||||||
|
3: 51,
|
||||||
|
4: 52,
|
||||||
|
5: 53,
|
||||||
|
6: 54,
|
||||||
|
7: 55,
|
||||||
|
8: 56,
|
||||||
|
9: 57,
|
||||||
|
SEMICOLON: 59,
|
||||||
|
EQUALS: 61,
|
||||||
|
A: 65,
|
||||||
|
B: 66,
|
||||||
|
C: 67,
|
||||||
|
D: 68,
|
||||||
|
E: 69,
|
||||||
|
F: 70,
|
||||||
|
G: 71,
|
||||||
|
H: 72,
|
||||||
|
I: 73,
|
||||||
|
J: 74,
|
||||||
|
K: 75,
|
||||||
|
L: 76,
|
||||||
|
M: 77,
|
||||||
|
N: 78,
|
||||||
|
O: 79,
|
||||||
|
P: 80,
|
||||||
|
Q: 81,
|
||||||
|
R: 82,
|
||||||
|
S: 83,
|
||||||
|
T: 84,
|
||||||
|
U: 85,
|
||||||
|
V: 86,
|
||||||
|
W: 87,
|
||||||
|
X: 88,
|
||||||
|
Y: 89,
|
||||||
|
Z: 90,
|
||||||
|
CONTEXT_MENU: 93,
|
||||||
|
NUMPAD0: 96,
|
||||||
|
NUMPAD1: 97,
|
||||||
|
NUMPAD2: 98,
|
||||||
|
NUMPAD3: 99,
|
||||||
|
NUMPAD4: 100,
|
||||||
|
NUMPAD5: 101,
|
||||||
|
NUMPAD6: 102,
|
||||||
|
NUMPAD7: 103,
|
||||||
|
NUMPAD8: 104,
|
||||||
|
NUMPAD9: 105,
|
||||||
|
MULTIPLY: 106,
|
||||||
|
ADD: 107,
|
||||||
|
SEPARATOR: 108,
|
||||||
|
SUBTRACT: 109,
|
||||||
|
DECIMAL: 110,
|
||||||
|
DIVIDE: 111,
|
||||||
|
F1: 112,
|
||||||
|
F2: 113,
|
||||||
|
F3: 114,
|
||||||
|
F4: 115,
|
||||||
|
F5: 116,
|
||||||
|
F6: 117,
|
||||||
|
F7: 118,
|
||||||
|
F8: 119,
|
||||||
|
F9: 120,
|
||||||
|
F10: 121,
|
||||||
|
F11: 122,
|
||||||
|
F12: 123,
|
||||||
|
F13: 124,
|
||||||
|
F14: 125,
|
||||||
|
F15: 126,
|
||||||
|
F16: 127,
|
||||||
|
F17: 128,
|
||||||
|
F18: 129,
|
||||||
|
F19: 130,
|
||||||
|
F20: 131,
|
||||||
|
F21: 132,
|
||||||
|
F22: 133,
|
||||||
|
F23: 134,
|
||||||
|
F24: 135,
|
||||||
|
NUM_LOCK: 144,
|
||||||
|
SCROLL_LOCK: 145,
|
||||||
|
COMMA: 188,
|
||||||
|
PERIOD: 190,
|
||||||
|
SLASH: 191,
|
||||||
|
BACK_QUOTE: 192,
|
||||||
|
OPEN_BRACKET: 219,
|
||||||
|
BACK_SLASH: 220,
|
||||||
|
CLOSE_BRACKET: 221,
|
||||||
|
QUOTE: 222,
|
||||||
|
META: 224
|
||||||
|
};
|
||||||
|
export let MouseCodes = {
|
||||||
|
LEFT: 0,
|
||||||
|
RIGHT: 1,
|
||||||
|
MIDDLE: 2
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
import { PhysicsObject } from './object';
|
||||||
|
export declare function AABB(obj1: PhysicsObject, obj2: PhysicsObject): boolean;
|
|
@ -0,0 +1,17 @@
|
||||||
|
export function AABB(obj1, obj2) {
|
||||||
|
if (checkOverlap(obj1.position.x, obj1.dimensions.x, obj2.position.x, obj2.dimensions.x)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (checkOverlap(obj1.position.y, obj1.dimensions.y, obj2.position.y, obj2.dimensions.y)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (checkOverlap(obj1.position.z, obj1.dimensions.z, obj2.position.z, obj2.dimensions.z)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function checkOverlap(x, w, yx, yw) {
|
||||||
|
if (x > yx || x < yx + yw || x + w > x || x + w < yx + yw) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Vec3 } from './vec3';
|
||||||
|
export declare class PhysicsObject {
|
||||||
|
position: Vec3;
|
||||||
|
dimensions: Vec3;
|
||||||
|
velocity: Vec3;
|
||||||
|
affectedByGravity: boolean;
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export class PhysicsObject {
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { PhysicsObject } from './object';
|
||||||
|
import { Vec3 } from './vec3';
|
||||||
|
export declare class Octree {
|
||||||
|
private dimensions;
|
||||||
|
private maxObjects;
|
||||||
|
private maxLevels;
|
||||||
|
root: OcTreeNode;
|
||||||
|
constructor(dimensions: Vec3, maxObjects?: number, maxLevels?: number);
|
||||||
|
insert(obj: PhysicsObject): void;
|
||||||
|
find(position: Vec3, dimensions: Vec3): PhysicsObject[];
|
||||||
|
}
|
||||||
|
export declare class OcTreeNode {
|
||||||
|
private position;
|
||||||
|
private dimensions;
|
||||||
|
private maxLevels;
|
||||||
|
private maxObjects;
|
||||||
|
private currentLevel;
|
||||||
|
objects: PhysicsObject[];
|
||||||
|
nodes: OcTreeNode[];
|
||||||
|
constructor(position: Vec3, dimensions: Vec3, maxLevels: number, maxObjects: number, currentLevel?: number);
|
||||||
|
insert(obj: PhysicsObject): any;
|
||||||
|
find(x: number, y: number, z: number, xw: number, yh: number, zd: number): PhysicsObject[];
|
||||||
|
split(): void;
|
||||||
|
private distributeObjectsToNodes;
|
||||||
|
getIndex(x: number, y: number, z: number): Direction;
|
||||||
|
getIndeciesForRect(x: number, y: number, z: number, xw: number, yh: number, zd: number): Direction[];
|
||||||
|
}
|
||||||
|
declare enum Direction {
|
||||||
|
AboveUpperLeft = 0,
|
||||||
|
AboveUpperRight = 1,
|
||||||
|
AboveLowerRight = 2,
|
||||||
|
AboveLowerLeft = 3,
|
||||||
|
BelowUpperLeft = 4,
|
||||||
|
BelowUpperRight = 5,
|
||||||
|
BelowLowerRight = 6,
|
||||||
|
BelowLowerLeft = 7,
|
||||||
|
Here = 8
|
||||||
|
}
|
||||||
|
export {};
|
|
@ -0,0 +1,206 @@
|
||||||
|
import { Vec3 } from './vec3';
|
||||||
|
export class Octree {
|
||||||
|
constructor(dimensions, maxObjects = 10, maxLevels = 10) {
|
||||||
|
this.dimensions = dimensions;
|
||||||
|
this.maxObjects = maxObjects;
|
||||||
|
this.maxLevels = maxLevels;
|
||||||
|
this.root = new OcTreeNode(new Vec3({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
}), this.dimensions, maxLevels, maxObjects, 0);
|
||||||
|
}
|
||||||
|
insert(obj) {
|
||||||
|
this.root.insert(obj);
|
||||||
|
}
|
||||||
|
find(position, dimensions) {
|
||||||
|
return this.root.find(position.x, position.y, position.z, dimensions.x, dimensions.y, dimensions.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class OcTreeNode {
|
||||||
|
constructor(position, dimensions, maxLevels, maxObjects, currentLevel = 0) {
|
||||||
|
this.position = position;
|
||||||
|
this.dimensions = dimensions;
|
||||||
|
this.maxLevels = maxLevels;
|
||||||
|
this.maxObjects = maxObjects;
|
||||||
|
this.currentLevel = currentLevel;
|
||||||
|
this.objects = [];
|
||||||
|
this.nodes = [];
|
||||||
|
}
|
||||||
|
insert(obj) {
|
||||||
|
const index = this.getIndex(obj.position.x, obj.position.y, obj.position.z);
|
||||||
|
if (index === Direction.Here) {
|
||||||
|
this.objects.push(obj);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return this.nodes[index].insert(obj);
|
||||||
|
}
|
||||||
|
if (this.objects.length > this.maxObjects &&
|
||||||
|
this.currentLevel < this.maxLevels) {
|
||||||
|
this.split();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
find(x, y, z, xw, yh, zd) {
|
||||||
|
if (this.nodes.length < 1) {
|
||||||
|
return this.objects;
|
||||||
|
}
|
||||||
|
const indecies = this.getIndeciesForRect(x, y, z, xw, yh, zd);
|
||||||
|
let results = [];
|
||||||
|
for (let i = 0; i < indecies.length - 1; i++) {
|
||||||
|
let res = this.nodes[indecies[i]].find(x, y, z, xw, yh, zd);
|
||||||
|
results.push([...res]);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
split() {
|
||||||
|
const halfWidth = this.dimensions.x / 2;
|
||||||
|
const halfHeight = this.dimensions.y / 2;
|
||||||
|
const halfDepth = this.dimensions.z / 2;
|
||||||
|
this.nodes[Direction.AboveUpperLeft] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.AboveUpperRight] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.AboveLowerRight] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.AboveLowerLeft] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.BelowUpperLeft] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.BelowUpperRight] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.BelowLowerRight] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.BelowLowerLeft] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.distributeObjectsToNodes();
|
||||||
|
}
|
||||||
|
distributeObjectsToNodes() {
|
||||||
|
if (this.nodes.length < 8) {
|
||||||
|
this.split();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.objects.forEach((obj) => {
|
||||||
|
const direction = this.getIndex(obj.position.x, obj.position.y, obj.position.z);
|
||||||
|
this.nodes[direction].insert(obj);
|
||||||
|
});
|
||||||
|
this.objects = [];
|
||||||
|
}
|
||||||
|
getIndex(x, y, z) {
|
||||||
|
if (this.nodes.length === 0) {
|
||||||
|
return Direction.Here;
|
||||||
|
}
|
||||||
|
const halfWidth = this.dimensions.x / 2;
|
||||||
|
const halfHeight = this.dimensions.y / 2;
|
||||||
|
const halfDepth = this.dimensions.z / 2;
|
||||||
|
const isBelow = z < this.position.z + halfDepth;
|
||||||
|
const isLeft = x < this.position.x + halfWidth;
|
||||||
|
const isUpper = y > this.position.y + halfHeight;
|
||||||
|
if (isBelow) {
|
||||||
|
if (isLeft) {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.AboveUpperLeft;
|
||||||
|
else
|
||||||
|
return Direction.AboveLowerLeft;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.AboveUpperRight;
|
||||||
|
else
|
||||||
|
return Direction.AboveLowerRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isLeft) {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.BelowUpperLeft;
|
||||||
|
else
|
||||||
|
return Direction.BelowLowerLeft;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.BelowUpperRight;
|
||||||
|
else
|
||||||
|
return Direction.BelowLowerRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getIndeciesForRect(x, y, z, xw, yh, zd) {
|
||||||
|
if (!(x > this.position.x && x < this.position.x + this.dimensions.x) ||
|
||||||
|
!(y > this.position.y && y < this.position.y + this.dimensions.y) ||
|
||||||
|
!(z > this.position.z && z < this.position.z + this.dimensions.z)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let indecies = [];
|
||||||
|
indecies.push(this.getIndex(x, y, z));
|
||||||
|
indecies.push(this.getIndex(x + xw, y + yh, z + zd));
|
||||||
|
return indecies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var Direction;
|
||||||
|
(function (Direction) {
|
||||||
|
Direction[Direction["AboveUpperLeft"] = 0] = "AboveUpperLeft";
|
||||||
|
Direction[Direction["AboveUpperRight"] = 1] = "AboveUpperRight";
|
||||||
|
Direction[Direction["AboveLowerRight"] = 2] = "AboveLowerRight";
|
||||||
|
Direction[Direction["AboveLowerLeft"] = 3] = "AboveLowerLeft";
|
||||||
|
Direction[Direction["BelowUpperLeft"] = 4] = "BelowUpperLeft";
|
||||||
|
Direction[Direction["BelowUpperRight"] = 5] = "BelowUpperRight";
|
||||||
|
Direction[Direction["BelowLowerRight"] = 6] = "BelowLowerRight";
|
||||||
|
Direction[Direction["BelowLowerLeft"] = 7] = "BelowLowerLeft";
|
||||||
|
Direction[Direction["Here"] = 8] = "Here";
|
||||||
|
})(Direction || (Direction = {}));
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Octree } from './octree';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
import { PhysicsObject } from './object';
|
||||||
|
import { Vec3 } from './vec3';
|
||||||
|
export declare class World extends EventBus {
|
||||||
|
objects: PhysicsObject[];
|
||||||
|
gravity: Vec3;
|
||||||
|
dimensions: Vec3;
|
||||||
|
octreeOptions: OctreeOptions;
|
||||||
|
constructor(dimensions: Vec3, octreeOptions: OctreeOptions);
|
||||||
|
setGravity(grav: Vec3): void;
|
||||||
|
addObject(obj: PhysicsObject): void;
|
||||||
|
removeObject(obj: PhysicsObject): void;
|
||||||
|
step(dt: number): void;
|
||||||
|
checkCollisions(obj: PhysicsObject, octree: Octree): void;
|
||||||
|
}
|
||||||
|
interface OctreeOptions {
|
||||||
|
position: Vec3;
|
||||||
|
dimensions: Vec3;
|
||||||
|
maxObjects: number;
|
||||||
|
maxLevels: number;
|
||||||
|
}
|
||||||
|
export {};
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { Octree } from './octree';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
import { Vec3 } from './vec3';
|
||||||
|
import { AABB } from './aabb';
|
||||||
|
export class World extends EventBus {
|
||||||
|
constructor(dimensions, octreeOptions) {
|
||||||
|
super();
|
||||||
|
if (!octreeOptions) {
|
||||||
|
this.octreeOptions = {
|
||||||
|
position: new Vec3({ x: 0, y: 0, z: 0 }),
|
||||||
|
dimensions: new Vec3(this.dimensions),
|
||||||
|
maxLevels: 50,
|
||||||
|
maxObjects: 50
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.octreeOptions = octreeOptions;
|
||||||
|
}
|
||||||
|
this.dimensions = dimensions;
|
||||||
|
this.objects = [];
|
||||||
|
}
|
||||||
|
setGravity(grav) {
|
||||||
|
this.gravity = grav;
|
||||||
|
}
|
||||||
|
addObject(obj) {
|
||||||
|
this.objects.push(obj);
|
||||||
|
}
|
||||||
|
removeObject(obj) {
|
||||||
|
this.objects = this.objects.filter((val) => val !== obj);
|
||||||
|
}
|
||||||
|
step(dt) {
|
||||||
|
const octree = new Octree(this.octreeOptions.dimensions, this.octreeOptions.maxObjects, this.octreeOptions.maxLevels);
|
||||||
|
this.objects.forEach((obj) => octree.insert(obj));
|
||||||
|
this.objects.forEach((obj) => {
|
||||||
|
let velocity = obj.velocity.clone();
|
||||||
|
velocity.multiply(new Vec3({ x: dt, y: dt, z: dt }));
|
||||||
|
let gravity = this.gravity.clone();
|
||||||
|
gravity.multiply(new Vec3({ x: dt, y: dt, z: dt }));
|
||||||
|
obj.position.add(velocity);
|
||||||
|
if (obj.affectedByGravity) {
|
||||||
|
obj.velocity.add(gravity);
|
||||||
|
}
|
||||||
|
this.checkCollisions(obj, octree);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
checkCollisions(obj, octree) {
|
||||||
|
const potentialCandidates = octree.find(obj.position, obj.dimensions);
|
||||||
|
potentialCandidates.forEach((candidate) => {
|
||||||
|
if (AABB(obj, candidate)) {
|
||||||
|
this.emit('collision', [obj, candidate]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
export default class ResonatorAudioContext {
|
||||||
|
private context;
|
||||||
|
constructor();
|
||||||
|
getContext(): AudioContext;
|
||||||
|
createGain(): any;
|
||||||
|
getOutputDestination(): AudioNode;
|
||||||
|
createBufferSource(): AudioBufferSourceNode;
|
||||||
|
decodeAudioData(data: ArrayBuffer): Promise<AudioBuffer>;
|
||||||
|
createPanner(): any;
|
||||||
|
createMediaElementSource(element: HTMLMediaElement): MediaElementAudioSourceNode;
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// simple wrapper around the AudioContext
|
||||||
|
// eventually will be used to deal with cross browser issues
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default class ResonatorAudioContext {
|
||||||
|
constructor() {
|
||||||
|
this.context = new AudioContext();
|
||||||
|
}
|
||||||
|
getContext() {
|
||||||
|
return this.context;
|
||||||
|
}
|
||||||
|
createGain() {
|
||||||
|
return this.context.createGain();
|
||||||
|
}
|
||||||
|
getOutputDestination() {
|
||||||
|
return this.context.destination;
|
||||||
|
}
|
||||||
|
createBufferSource() {
|
||||||
|
return this.context.createBufferSource();
|
||||||
|
}
|
||||||
|
decodeAudioData(data) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return yield this.context.decodeAudioData(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
createPanner() {
|
||||||
|
return this.context.createPanner();
|
||||||
|
}
|
||||||
|
createMediaElementSource(element) {
|
||||||
|
return this.context.createMediaElementSource(element);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import ResonatorScene from './scenes/webaudio-scene';
|
||||||
|
import ResonatorAudioContext from './audio-context';
|
||||||
|
import BaseEffect from './effects/base-effect';
|
||||||
|
export default class AudioGraph {
|
||||||
|
private master;
|
||||||
|
private effectsBus;
|
||||||
|
private worldBus;
|
||||||
|
private secondaryBus;
|
||||||
|
private effects;
|
||||||
|
private scene;
|
||||||
|
private context;
|
||||||
|
private swapChannels;
|
||||||
|
private channelSplitter;
|
||||||
|
private channelMerger;
|
||||||
|
constructor(scene: ResonatorScene, context: ResonatorAudioContext, swapChannels?: boolean);
|
||||||
|
init(): void;
|
||||||
|
connectToMaster(input: any): void;
|
||||||
|
connectToUI(input: AudioNode): void;
|
||||||
|
applyEffect(effect: BaseEffect): void;
|
||||||
|
removeEffect(effect: BaseEffect): void;
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
// this is the mixer that takes all the different outputs and mixes them into the 2 busses:
|
||||||
|
// WorldBus: The directional audio
|
||||||
|
// SecondaryBus: All the UI and things that are non directional
|
||||||
|
import EffectChain from './effect-chain';
|
||||||
|
export default class AudioGraph {
|
||||||
|
constructor(scene, context, swapChannels = false) {
|
||||||
|
this.scene = scene;
|
||||||
|
this.context = context;
|
||||||
|
this.swapChannels = swapChannels;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
this.effectsBus = this.context.createGain();
|
||||||
|
this.worldBus = this.context.createGain();
|
||||||
|
this.secondaryBus = this.context.createGain();
|
||||||
|
this.master = this.context.createGain();
|
||||||
|
this.scene.getOutput().connect(this.worldBus);
|
||||||
|
// this.worldBus.connect(this.master);
|
||||||
|
this.worldBus.connect(this.effectsBus);
|
||||||
|
this.effects = new EffectChain(this.context, this, this.effectsBus, this.master);
|
||||||
|
this.secondaryBus.connect(this.master);
|
||||||
|
if (this.swapChannels) {
|
||||||
|
this.channelSplitter = this.context.getContext().createChannelSplitter(2);
|
||||||
|
this.channelMerger = this.context.getContext().createChannelMerger(2);
|
||||||
|
this.master.connect(this.channelSplitter);
|
||||||
|
this.channelSplitter.connect(this.channelMerger, 0, 1);
|
||||||
|
this.channelSplitter.connect(this.channelMerger, 1, 0);
|
||||||
|
this.channelMerger.connect(this.context.getOutputDestination());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.master.connect(this.context.getOutputDestination());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connectToMaster(input) {
|
||||||
|
input.connect(this.master);
|
||||||
|
}
|
||||||
|
connectToUI(input) {
|
||||||
|
input.connect(this.secondaryBus);
|
||||||
|
}
|
||||||
|
applyEffect(effect) {
|
||||||
|
this.effects.applyEffect(effect);
|
||||||
|
}
|
||||||
|
removeEffect(effect) {
|
||||||
|
this.effects.removeEffect(effect);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
export default class DataPoolItem {
|
||||||
|
private name;
|
||||||
|
private data;
|
||||||
|
private decodedData;
|
||||||
|
constructor(name: string, data?: any, decodedData?: any);
|
||||||
|
getData(): any;
|
||||||
|
setData(data: any): void;
|
||||||
|
getDecodedData(): any;
|
||||||
|
setDecodedData(data: any): void;
|
||||||
|
getName(): string;
|
||||||
|
setName(name: string): void;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// An item in the data pool
|
||||||
|
export default class DataPoolItem {
|
||||||
|
constructor(name, data = null, decodedData = null) {
|
||||||
|
this.name = name;
|
||||||
|
this.data = data;
|
||||||
|
this.decodedData = decodedData;
|
||||||
|
}
|
||||||
|
getData() {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
setData(data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
getDecodedData() {
|
||||||
|
return this.decodedData;
|
||||||
|
}
|
||||||
|
setDecodedData(data) {
|
||||||
|
this.decodedData = this.decodedData;
|
||||||
|
}
|
||||||
|
getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
setName(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import ResonatorAudioContext from './audio-context';
|
||||||
|
import { BaseLoader } from './loaders/base-loader';
|
||||||
|
export default class DataPool extends EventEmitter {
|
||||||
|
private loader;
|
||||||
|
private data;
|
||||||
|
private maxData;
|
||||||
|
private context;
|
||||||
|
constructor(context: ResonatorAudioContext, loader?: BaseLoader, maxData?: number);
|
||||||
|
get(path: string): Promise<AudioBuffer>;
|
||||||
|
clear(): void;
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
// a data pool holds frequently played sounds in memory together with decoded audio data to no longer have to decode them from the cache when loaded again
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import DataPoolItem from './data-pool-item';
|
||||||
|
import { HTTPLoader } from './loaders/http-loader';
|
||||||
|
export default class DataPool extends EventEmitter {
|
||||||
|
constructor(context, loader = new HTTPLoader(), maxData = 512) {
|
||||||
|
super();
|
||||||
|
this.loader = loader;
|
||||||
|
this.data = {};
|
||||||
|
this.maxData = maxData;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
get(path) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (this.data[path]) {
|
||||||
|
return this.data[path].getDecodedData();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const buffer = yield this.loader.get(path);
|
||||||
|
const decoded = yield this.context.decodeAudioData(buffer);
|
||||||
|
const item = new DataPoolItem(path, buffer, decoded);
|
||||||
|
const length = Object.keys(this.data).length;
|
||||||
|
if (length < this.maxData) {
|
||||||
|
this.data[path] = item;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: figure out a more clever solution than just removing the first loaded data. Like tracking how much certain data is needed and prioritize them.
|
||||||
|
// const paths: string[] = Object.keys(this.data);
|
||||||
|
// delete this.data[paths[0]];
|
||||||
|
this.data[path] = item;
|
||||||
|
}
|
||||||
|
return item.getDecodedData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
clear() {
|
||||||
|
this.data = {};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import ResonatorAudioContext from './audio-context';
|
||||||
|
export default class EffectBus {
|
||||||
|
private context;
|
||||||
|
private inputNode;
|
||||||
|
private channelMerger;
|
||||||
|
constructor(context: ResonatorAudioContext, input: AudioNode, output: AudioNode);
|
||||||
|
connect(node: AudioNode): void;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Currently unused, but eventually all the effect stuff will be moved from audio graph to here to make it easier to work on
|
||||||
|
export default class EffectBus {
|
||||||
|
constructor(context, input, output) {
|
||||||
|
this.context = context;
|
||||||
|
this.inputNode = input;
|
||||||
|
this.channelMerger = this.context.getContext().createChannelMerger(1);
|
||||||
|
this.inputNode.connect(this.channelMerger);
|
||||||
|
}
|
||||||
|
connect(node) {
|
||||||
|
this.channelMerger.connect(node);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import ResonatorAudioContext from './audio-context';
|
||||||
|
import AudioGraph from './audio-graph';
|
||||||
|
import BaseEffect from './effects/base-effect';
|
||||||
|
export default class EffectChain {
|
||||||
|
private context;
|
||||||
|
private graph;
|
||||||
|
private effects;
|
||||||
|
private inputNode;
|
||||||
|
private outputNode;
|
||||||
|
constructor(context: ResonatorAudioContext, graph: AudioGraph, input: AudioNode, output: AudioNode);
|
||||||
|
applyEffect(effect: BaseEffect): void;
|
||||||
|
removeEffect(effect: BaseEffect): void;
|
||||||
|
private updateConnections;
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// A chain of effects that connect to the effect bus
|
||||||
|
export default class EffectChain {
|
||||||
|
constructor(context, graph, input, output) {
|
||||||
|
this.effects = [];
|
||||||
|
this.context = context;
|
||||||
|
this.graph = graph;
|
||||||
|
this.inputNode = input;
|
||||||
|
this.outputNode = output;
|
||||||
|
this.updateConnections();
|
||||||
|
}
|
||||||
|
applyEffect(effect) {
|
||||||
|
this.effects.push(effect);
|
||||||
|
this.updateConnections();
|
||||||
|
}
|
||||||
|
removeEffect(effect) {
|
||||||
|
this.effects.forEach((currEffect) => {
|
||||||
|
if (effect === currEffect) {
|
||||||
|
currEffect.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.effects = this.effects.filter((currEffect) => effect !== currEffect);
|
||||||
|
this.updateConnections();
|
||||||
|
}
|
||||||
|
updateConnections() {
|
||||||
|
if (this.effects.length == 0) {
|
||||||
|
this.inputNode.connect(this.outputNode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let current = null;
|
||||||
|
let previous = null;
|
||||||
|
this.effects.forEach((effect) => {
|
||||||
|
current = effect;
|
||||||
|
if (previous) {
|
||||||
|
current.connectInput(previous.getOutput());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
current.connectInput(this.inputNode);
|
||||||
|
}
|
||||||
|
previous = current;
|
||||||
|
});
|
||||||
|
if (current) {
|
||||||
|
current.connectOutput(this.outputNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import ResonatorAudioContext from '../audio-context';
|
||||||
|
import AudioGraph from '../audio-graph';
|
||||||
|
export default class BaseEffect {
|
||||||
|
protected ready: boolean;
|
||||||
|
protected effectNode: any;
|
||||||
|
protected effectParams: any;
|
||||||
|
protected context: ResonatorAudioContext;
|
||||||
|
protected graph: AudioGraph;
|
||||||
|
protected inputNode: AudioNode;
|
||||||
|
constructor(context: ResonatorAudioContext, graph: AudioGraph, params: any);
|
||||||
|
connectOutput(node: AudioNode): void;
|
||||||
|
connectInput(node: AudioNode): void;
|
||||||
|
getOutput(): AudioNode;
|
||||||
|
disconnect(): void;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
export default class BaseEffect {
|
||||||
|
constructor(context, graph, params) {
|
||||||
|
this.graph = graph;
|
||||||
|
this.context = context;
|
||||||
|
this.effectParams = params;
|
||||||
|
}
|
||||||
|
connectOutput(node) {
|
||||||
|
this.effectNode.connect(node);
|
||||||
|
}
|
||||||
|
connectInput(node) {
|
||||||
|
this.inputNode = node;
|
||||||
|
if (this.effectNode) {
|
||||||
|
this.inputNode.connect(this.effectNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getOutput() {
|
||||||
|
return this.effectNode;
|
||||||
|
}
|
||||||
|
disconnect() {
|
||||||
|
this.inputNode.disconnect();
|
||||||
|
this.effectNode.disconnect();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import BaseEffect from './base-effect';
|
||||||
|
import ResonatorAudioContext from '../audio-context';
|
||||||
|
import AudioGraph from '../audio-graph';
|
||||||
|
export default class Convolver extends BaseEffect {
|
||||||
|
private buffer;
|
||||||
|
private channelSplitter;
|
||||||
|
private channelMerger;
|
||||||
|
constructor(context: ResonatorAudioContext, graph: AudioGraph, params: any);
|
||||||
|
connectInput(node: AudioNode): void;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import BaseEffect from './base-effect';
|
||||||
|
export default class Convolver extends BaseEffect {
|
||||||
|
constructor(context, graph, params) {
|
||||||
|
super(context, graph, params);
|
||||||
|
console.log(`Creating convolver`);
|
||||||
|
this.effectNode = this.context.getContext().createConvolver();
|
||||||
|
this.effectNode.buffer = this.effectParams.buffer;
|
||||||
|
}
|
||||||
|
connectInput(node) {
|
||||||
|
this.channelSplitter = this.context.getContext().createChannelSplitter(2);
|
||||||
|
this.channelMerger = this.context.getContext().createChannelMerger(2);
|
||||||
|
this.channelSplitter.connect(this.channelMerger, 0, 0);
|
||||||
|
this.channelSplitter.connect(this.channelMerger, 1, 0);
|
||||||
|
this.channelSplitter.connect(this.channelMerger, 0, 1);
|
||||||
|
this.channelSplitter.connect(this.channelMerger, 1, 1);
|
||||||
|
node.connect(this.channelSplitter);
|
||||||
|
this.channelMerger.connect(this.effectNode);
|
||||||
|
this.inputNode = node;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import AudioSource from './sources/audio-source';
|
||||||
|
import { BaseLoader } from './loaders/base-loader';
|
||||||
|
import { BaseSource } from './sources/base-source';
|
||||||
|
import { SourceType } from './sources/source-type';
|
||||||
|
import { StreamingSource } from './sources/streaming-source';
|
||||||
|
export default class Resonator {
|
||||||
|
private loader;
|
||||||
|
private context;
|
||||||
|
private scene;
|
||||||
|
private graph;
|
||||||
|
private dataPool;
|
||||||
|
private environmentImpulse;
|
||||||
|
constructor(loader?: BaseLoader);
|
||||||
|
load(path: string, type?: SourceType): Promise<BaseSource>;
|
||||||
|
loadImmediate(path: string, type?: SourceType): AudioSource;
|
||||||
|
stream(path: string, type?: SourceType): StreamingSource;
|
||||||
|
private createSource;
|
||||||
|
setEnvironmentImpulse(file: string): Promise<void>;
|
||||||
|
setListenerPosition(x: number, y: number, z: number): void;
|
||||||
|
setListenerOrientation(forward: any, up: any): void;
|
||||||
|
clearDataPool(): void;
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
// the main module for Resonator
|
||||||
|
// API, etc.
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ResonatorAudioContext from './audio-context';
|
||||||
|
import ResonatorScene from './scenes/webaudio-scene';
|
||||||
|
import AudioGraph from './audio-graph';
|
||||||
|
import AudioSource from './sources/audio-source';
|
||||||
|
import DataPool from './data-pool';
|
||||||
|
import Convolver from './effects/convolver';
|
||||||
|
import { HTTPLoader } from './loaders/http-loader';
|
||||||
|
import { SourceType } from './sources/source-type';
|
||||||
|
import { StreamingSource } from './sources/streaming-source';
|
||||||
|
export default class Resonator {
|
||||||
|
constructor(loader = new HTTPLoader()) {
|
||||||
|
this.loader = loader;
|
||||||
|
this.environmentImpulse = null;
|
||||||
|
this.context = new ResonatorAudioContext();
|
||||||
|
this.scene = new ResonatorScene(this.context);
|
||||||
|
this.graph = new AudioGraph(this.scene, this.context, false);
|
||||||
|
this.dataPool = new DataPool(this.context, this.loader);
|
||||||
|
}
|
||||||
|
load(path, type = SourceType.WorldSource) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const data = yield this.dataPool.get(path);
|
||||||
|
const source = this.createSource(type, data);
|
||||||
|
return source;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
loadImmediate(path, type = SourceType.WorldSource) {
|
||||||
|
const source = new AudioSource(this.graph, this.scene, this.context, null, type);
|
||||||
|
this.dataPool.get(path).then((data) => {
|
||||||
|
source.setBuffer(data);
|
||||||
|
});
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
stream(path, type = SourceType.MasterSource) {
|
||||||
|
const element = new Audio(path);
|
||||||
|
element.crossOrigin = 'anonymous';
|
||||||
|
element.volume = 1;
|
||||||
|
const source = new StreamingSource(this.graph, this.scene, this.context, element, type);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
createSource(type, data) {
|
||||||
|
return new AudioSource(this.graph, this.scene, this.context, data);
|
||||||
|
}
|
||||||
|
setEnvironmentImpulse(file) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (this.environmentImpulse) {
|
||||||
|
this.graph.removeEffect(this.environmentImpulse);
|
||||||
|
}
|
||||||
|
if (file === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const buffer = yield this.dataPool.get(file);
|
||||||
|
this.environmentImpulse = new Convolver(this.context, this.graph, {
|
||||||
|
buffer
|
||||||
|
});
|
||||||
|
this.graph.applyEffect(this.environmentImpulse);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setListenerPosition(x, y, z) {
|
||||||
|
this.scene.setListenerPosition(x, y, z);
|
||||||
|
}
|
||||||
|
setListenerOrientation(forward, up) {
|
||||||
|
this.scene.setListenerOrientation(forward, up);
|
||||||
|
}
|
||||||
|
clearDataPool() {
|
||||||
|
this.dataPool.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { AssetManager } from '../../asset-manager';
|
||||||
|
import { BaseLoader } from './base-loader';
|
||||||
|
export declare class AssetLoader implements BaseLoader {
|
||||||
|
private name;
|
||||||
|
private manager;
|
||||||
|
private assetManager;
|
||||||
|
constructor(name: string, manager?: AssetManager);
|
||||||
|
init(): Promise<void>;
|
||||||
|
get(path: string): Promise<ArrayBuffer>;
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// TODO fix path when actually properly linking the packages together
|
||||||
|
import { AssetManager } from '../../asset-manager';
|
||||||
|
export class AssetLoader {
|
||||||
|
constructor(name, manager = null) {
|
||||||
|
this.name = name;
|
||||||
|
this.manager = manager;
|
||||||
|
if (manager) {
|
||||||
|
this.assetManager = manager;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.assetManager = new AssetManager(name, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield this.assetManager.init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
get(path) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield this.assetManager.downloadFile(path);
|
||||||
|
console.log(result);
|
||||||
|
const buffer = yield result.arrayBuffer();
|
||||||
|
return buffer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface BaseLoader {
|
||||||
|
get(path: string): Promise<ArrayBuffer>;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export {};
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { BaseLoader } from './base-loader';
|
||||||
|
export declare class HTTPLoader implements BaseLoader {
|
||||||
|
get(path: string): Promise<ArrayBuffer>;
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export class HTTPLoader {
|
||||||
|
get(path) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield fetch(path);
|
||||||
|
const buffer = yield result.arrayBuffer();
|
||||||
|
return buffer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import ResonatorAudioContext from '../audio-context';
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
export default class Scene extends EventEmitter {
|
||||||
|
scene: any;
|
||||||
|
context: ResonatorAudioContext;
|
||||||
|
listener: AudioListener;
|
||||||
|
constructor(context: ResonatorAudioContext);
|
||||||
|
init(): void;
|
||||||
|
createSource(): any;
|
||||||
|
getOutput(): any;
|
||||||
|
getInput(): any;
|
||||||
|
setListenerPosition(x: number, y: number, z: number): void;
|
||||||
|
setListenerOrientation(forward: any, rawup: any): void;
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
// The code that deals with 3d audio
|
||||||
|
import ResonanceAudio from '../vendor/resonance-es6/main';
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import vec3 from '../../tsm/vec3';
|
||||||
|
export default class Scene extends EventEmitter {
|
||||||
|
constructor(context) {
|
||||||
|
super();
|
||||||
|
this.context = context;
|
||||||
|
this.scene = new ResonanceAudio(this.context.getContext(), {
|
||||||
|
ambisonicOrder: 3
|
||||||
|
});
|
||||||
|
this.listener = this.context.getContext().listener;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
// this.scene.output.connect(this.context.getOutputDestination());
|
||||||
|
}
|
||||||
|
createSource() {
|
||||||
|
const source = this.scene.createSource();
|
||||||
|
return Object.assign(Object.assign({}, source), { getInput: () => source.input });
|
||||||
|
}
|
||||||
|
getOutput() {
|
||||||
|
return this.scene.output;
|
||||||
|
}
|
||||||
|
getInput() {
|
||||||
|
return this.scene.input;
|
||||||
|
}
|
||||||
|
setListenerPosition(x, y, z) {
|
||||||
|
this.scene.setListenerPosition(x, y, z);
|
||||||
|
}
|
||||||
|
setListenerOrientation(forward, rawup) {
|
||||||
|
let fwd = new vec3([forward.x, forward.y, forward.z]);
|
||||||
|
let up = fwd.copy();
|
||||||
|
vec3.cross(up, new vec3([rawup.x, rawup.y, rawup.z]), up);
|
||||||
|
vec3.cross(up, fwd, up);
|
||||||
|
fwd.normalize();
|
||||||
|
up.normalize();
|
||||||
|
this.scene.setListenerOrientation(forward.x, forward.y, forward.z, rawup.x, rawup.y, rawup.z);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import ResonatorAudioContext from '../audio-context';
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
export default class ResonatorScene extends EventEmitter {
|
||||||
|
scene: GainNode;
|
||||||
|
context: ResonatorAudioContext;
|
||||||
|
listener: AudioListener;
|
||||||
|
constructor(context: ResonatorAudioContext);
|
||||||
|
init(): void;
|
||||||
|
createSource(): any;
|
||||||
|
getOutput(): any;
|
||||||
|
getInput(): any;
|
||||||
|
setListenerPosition(x: number, y: number, z: number): void;
|
||||||
|
setListenerOrientation(forward: any, rawup: any): void;
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// The code that deals with 3d audio
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import vec3 from '../../tsm/vec3';
|
||||||
|
export default class ResonatorScene extends EventEmitter {
|
||||||
|
constructor(context) {
|
||||||
|
super();
|
||||||
|
this.context = context;
|
||||||
|
this.scene = this.context.getContext().createGain();
|
||||||
|
this.listener = this.context.getContext().listener;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
// this.scene.output.connect(this.context.getOutputDestination());
|
||||||
|
}
|
||||||
|
createSource() {
|
||||||
|
const node = this.context.getContext().createPanner();
|
||||||
|
node.panningModel = 'HRTF';
|
||||||
|
node.distanceModel = 'linear';
|
||||||
|
node.maxDistance = 20;
|
||||||
|
node.refDistance = 2;
|
||||||
|
node.connect(this.scene);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
getOutput() {
|
||||||
|
return this.scene;
|
||||||
|
}
|
||||||
|
getInput() {
|
||||||
|
return this.scene;
|
||||||
|
}
|
||||||
|
setListenerPosition(x, y, z) {
|
||||||
|
this.listener.setPosition(x, y, z);
|
||||||
|
}
|
||||||
|
setListenerOrientation(forward, rawup) {
|
||||||
|
let fwd = new vec3([forward.x, forward.y, forward.z]);
|
||||||
|
let up = fwd.copy();
|
||||||
|
vec3.cross(up, new vec3([rawup.x, rawup.y, rawup.z]), up);
|
||||||
|
vec3.cross(up, fwd, up);
|
||||||
|
fwd.normalize();
|
||||||
|
up.normalize();
|
||||||
|
this.listener.setOrientation(fwd.x, fwd.y, fwd.z, up.x, up.y, up.z);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import ResonatorAudioContext from '../audio-context';
|
||||||
|
import AudioGraph from '../audio-graph';
|
||||||
|
import ResonatorScene from '../scenes/webaudio-scene';
|
||||||
|
import { BaseSource } from './base-source';
|
||||||
|
import { SourceType } from './source-type';
|
||||||
|
export default class AudioSource implements BaseSource {
|
||||||
|
playing: boolean;
|
||||||
|
looping: boolean;
|
||||||
|
private node;
|
||||||
|
private sceneNode;
|
||||||
|
private buffer;
|
||||||
|
private context;
|
||||||
|
private graph;
|
||||||
|
private scene;
|
||||||
|
private playOnLoad;
|
||||||
|
private position;
|
||||||
|
private playbackRate;
|
||||||
|
private volume;
|
||||||
|
private gain;
|
||||||
|
private type;
|
||||||
|
constructor(graph: AudioGraph, scene: ResonatorScene, context: ResonatorAudioContext, buffer?: AudioBuffer, type?: SourceType);
|
||||||
|
init(): void;
|
||||||
|
getBuffer(): AudioBuffer;
|
||||||
|
setBuffer(data: AudioBuffer): void;
|
||||||
|
play(when?: number, offset?: number, duration?: number): void;
|
||||||
|
setPosition(x: number, y: number, z: number): void;
|
||||||
|
setPlaybackRate(rate: number): void;
|
||||||
|
getPlaybackRate(): number;
|
||||||
|
setVolume(volume: number): void;
|
||||||
|
getVolume(): number;
|
||||||
|
private createConnections;
|
||||||
|
stop(): void;
|
||||||
|
destroy(): void;
|
||||||
|
loop(value: boolean): void;
|
||||||
|
fadeOut(time: number): void;
|
||||||
|
fadeIn(time: number): void;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue