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