Merge dev-asset beanch (#891)

* feat: merge dev-asset beanch
This commit is contained in:
Hu Song
2022-07-27 15:58:09 +08:00
committed by GitHub
parent 2ca650889c
commit 7db6dddc44
69 changed files with 1804 additions and 2861 deletions

View File

@@ -115,7 +115,7 @@ export class RenderQueue {
} else if (switchProgram) {
program.uploadTextures(program.cameraUniformBlock, cameraData);
}
if (program._uploadRenderer !== renderer) {
program.uploadAll(program.rendererUniformBlock, rendererData);
program._uploadRenderer = renderer;

View File

@@ -33,10 +33,14 @@ export enum AssetType {
KTX = "ktx",
/** Cube Compress Texture. */
KTXCube = "ktx-cube",
/** Sprite. */
Sprite = "sprite",
/** Sprite Atlas. */
SpriteAtlas = "sprite-atlas",
/** ambient light */
Env = "environment",
/** scene */
Scene = "scene",
/** HDR to cube */
HDR = "HDR"
}

View File

@@ -6,6 +6,25 @@ import { ResourceManager } from "./ResourceManager";
* Loader abstract class.
*/
export abstract class Loader<T> {
/**
* Register a class with a string name for serialization and deserialization.
* @param key - class name
* @param obj - class object
*/
public static registerClass(className: string, classDefine: { new (): Object }) {
this._engineObjects[className] = classDefine;
}
/**
* Get the class object by class name.
* @param key - class name
* @returns class object
*/
public static getClass(className: string): { new (): Object } {
return this._engineObjects[className];
}
private static _engineObjects: { [key: string]: any } = {};
request: <U>(url: string, config: RequestConfig) => AssetPromise<U> = request;
abstract load(item: LoadItem, resourceManager: ResourceManager): AssetPromise<T>;
constructor(public readonly useCache: boolean) {}

View File

@@ -5,6 +5,8 @@ import { Loader } from "./Loader";
import { LoadItem } from "./LoadItem";
import { RefObject } from "./RefObject";
type EditorResourceItem = { virtualPath: string; path: string; type: string; id: string };
type EditorResourceConfig = Record<string, EditorResourceItem>;
/**
* ResourceManager
*/
@@ -34,6 +36,12 @@ export class ResourceManager {
retryInterval: number = 0;
/** The default timeout period for loading assets, in milliseconds. */
timeout: number = 20000;
/** @internal */
_objectPool: { [key: string]: any } = Object.create(null);
/** @internal */
_editorResourceConfig: EditorResourceConfig = Object.create(null);
/** @internal */
_virtualPathMap: Record<string, string> = Object.create(null);
/** Asset path pool, key is asset ID, value is asset path */
private _assetPool: { [key: number]: string } = Object.create(null);
@@ -136,6 +144,29 @@ export class ResourceManager {
return this._assetPool[instanceId];
}
/**
* @beta Just for internal editor, not recommended for developers.
*/
getResourceByRef<T>(ref: { refId: string; key?: string; isClone?: boolean }): Promise<T> {
const { refId, key, isClone } = ref;
const obj = this._objectPool[refId];
const promise = obj
? Promise.resolve(obj)
: this.load<any>({ type: this._editorResourceConfig[refId].type, url: this._editorResourceConfig[refId].path });
return promise.then((res) => (key ? res[key] : res)).then((item) => (isClone ? item.clone() : item));
}
/**
* @internal
* @beta Just for internal editor, not recommended for developers.
*/
initVirtualResources(config: EditorResourceItem[]): void {
config.forEach((element) => {
this._virtualPathMap[element.virtualPath] = element.path;
this._editorResourceConfig[element.id] = element;
});
}
/**
* @internal
*/
@@ -196,7 +227,9 @@ export class ResourceManager {
private _loadSingleItem<T>(item: LoadItem | string): AssetPromise<T> {
const info = this._assignDefaultOptions(typeof item === "string" ? { url: item } : item);
const url = info.url;
const infoUrl = info.url;
// check url mapping
const url = this._virtualPathMap[infoUrl] ? this._virtualPathMap[infoUrl] : infoUrl;
// has cache
if (this._assetUrlPool[url]) {
return new AssetPromise((resolve) => {
@@ -208,6 +241,10 @@ export class ResourceManager {
return this._loadingPromises[info.url];
}
const loader = ResourceManager._loaders[info.type];
if (!loader) {
throw `loader not found: ${info.type}`;
}
info.url = url;
const promise = loader.load(info, this);
this._loadingPromises[url] = promise;
promise
@@ -216,7 +253,7 @@ export class ResourceManager {
delete this._loadingPromises[url];
})
.catch((err: Error) => {
Promise.reject(err)
Promise.reject(err);
delete this._loadingPromises[url];
});
return promise;

View File

@@ -10,4 +10,4 @@ export { IndexBufferBinding } from "./IndexBufferBinding";
export { Mesh } from "./Mesh";
export { SubMesh } from "./SubMesh";
export { VertexBufferBinding } from "./VertexBufferBinding";
export { VertexElement } from "./VertexElement";
export { VertexElement } from "./VertexElement";

View File

@@ -21,6 +21,7 @@
"@oasis-engine/core": "0.8.0-alpha.5",
"@oasis-engine/draco": "0.8.0-alpha.5",
"@oasis-engine/math": "0.8.0-alpha.5",
"@oasis-engine/resource-process": "0.8.0-alpha.5",
"@oasis-engine/rhi-webgl": "0.8.0-alpha.5"
}
}

View File

@@ -0,0 +1,89 @@
import {
AssetPromise,
AssetType,
BlinnPhongMaterial,
Loader,
LoadItem,
PBRMaterial,
PBRSpecularMaterial,
resourceLoader,
ResourceManager,
ShaderData,
Texture2D,
UnlitMaterial
} from "@oasis-engine/core";
import { Color, Vector2, Vector3, Vector4 } from "@oasis-engine/math";
@resourceLoader(AssetType.Material, ["json"])
class MaterialLoader extends Loader<string> {
load(item: LoadItem, resourceManager: ResourceManager): AssetPromise<string> {
return new AssetPromise((resolve, reject) => {
this.request(item.url, {
...item,
type: "json"
}).then((json: { [key: string]: any }) => {
const engine = resourceManager.engine;
const { shader, shaderData, macros, renderState } = json;
let material;
switch (shader) {
case "pbr":
material = new PBRMaterial(engine);
break;
case "pbr-specular":
material = new PBRSpecularMaterial(engine);
break;
case "unlit":
material = new UnlitMaterial(engine);
break;
case "blinn-phong":
material = new BlinnPhongMaterial(engine);
break;
}
const materialShaderData: ShaderData = material.shaderData;
for (let key in shaderData) {
const { type, value } = shaderData[key];
switch (type) {
case "Vector2":
materialShaderData.setVector2(key, new Vector2(value.x, value.y));
break;
case "Vector3":
materialShaderData.setVector3(key, new Vector3(value.x, value.y, value.z));
break;
case "Vector4":
materialShaderData.setVector4(key, new Vector4(value.x, value.y, value.z, value.w));
break;
case "Color":
materialShaderData.setColor(key, new Color(value.r, value.g, value.b, value.a));
break;
case "Float":
materialShaderData.setFloat(key, value);
break;
case "Texture":
resourceManager.getResourceByRef<Texture2D>(value).then((texture) => {
materialShaderData.setTexture(key, texture);
});
break;
}
}
for (let i = 0, length = macros.length; i < length; i++) {
const { name, value } = macros[i];
if (value == undefined) {
materialShaderData.enableMacro(name);
} else {
materialShaderData.enableMacro(name, value);
}
}
for (let key in renderState) {
materialShaderData[key] = renderState[key];
}
resolve(material);
});
});
}
}

View File

@@ -0,0 +1,65 @@
import {
AssetPromise,
Loader,
LoadItem,
resourceLoader,
ResourceManager,
AssetType,
Scene,
BackgroundMode,
SkyBoxMaterial,
PrimitiveMesh
} from "@oasis-engine/core";
import { SceneParser } from "@oasis-engine/resource-process";
@resourceLoader(AssetType.Scene, ["prefab"], true)
class SceneLoader extends Loader<Scene> {
load(item: LoadItem, resourceManager: ResourceManager): AssetPromise<Scene> {
const { engine } = resourceManager;
return new AssetPromise((resolve, reject) => {
return this.request<any>(item.url, { type: "json" }).then((data) => {
// @ts-ignore
engine.resourceManager.initVirtualResources(data.files);
return SceneParser.parse(engine, data).then((scene) => {
const ambient = data.scene.ambient;
if (ambient.ambientLight) {
resourceManager.getResourceByRef<any>(data.scene.ambient.ambientLight).then((light) => {
scene.ambientLight = light;
});
}
scene.ambientLight.diffuseIntensity = ambient.diffuseIntensity;
scene.ambientLight.specularIntensity = ambient.specularIntensity;
const background = data.scene.background;
scene.background.mode = background.mode;
switch (scene.background.mode) {
case BackgroundMode.SolidColor:
scene.background.solidColor.copyFrom(background.color);
break;
case BackgroundMode.Sky:
if (background.sky) {
resourceManager.getResourceByRef<any>(background.sky).then((light) => {
const sky = scene.background.sky;
const skyMaterial = new SkyBoxMaterial(engine);
skyMaterial.textureCubeMap = light.specularTexture;
skyMaterial.textureDecodeRGBM = true;
sky.material = skyMaterial;
sky.mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1);
});
}
break;
case BackgroundMode.Texture:
if (background.texture) {
resourceManager.getResourceByRef<any>(background.texture).then((texture) => {
scene.background.texture = texture;
});
}
break;
}
resolve(scene);
});
});
//
});
}
}

View File

@@ -0,0 +1,29 @@
import {
resourceLoader,
Loader,
AssetPromise,
AssetType,
LoadItem,
Sprite,
Texture2D,
ResourceManager
} from "@oasis-engine/core";
@resourceLoader(AssetType.Sprite, ["sprite"], false)
class SpriteLoader extends Loader<Sprite> {
load(item: LoadItem, resourceManager: ResourceManager): AssetPromise<Sprite> {
return new AssetPromise((resolve, reject) => {
this.request<any>(item.url, {
...item,
type: "json"
}).then((data) => {
resourceManager.getResourceByRef<Texture2D>(data.texture).then((texture) => {
const sprite = new Sprite(resourceManager.engine, texture);
sprite.region = data.region;
sprite.pivot = data.pivot;
resolve(sprite);
});
});
});
}
}

View File

@@ -5,13 +5,15 @@ import "./KTXCubeLoader";
import "./KTXLoader";
import "./Texture2DLoader";
import "./TextureCubeLoader";
import "./SpriteLoader";
import "./SpriteAtlasLoader";
import "./EnvLoader";
import "./HDRLoader";
import "./gltf/extensions/index";
import "./MaterialLoader"
export * from "@oasis-engine/resource-process";
export { GLTFResource } from "./gltf/GLTFResource";
export { GLTFModel } from "./scene-loader/GLTFModel";
export { Model } from "./scene-loader/Model";
export * from "./scene-loader/index";
export * from "./SceneLoader";
export { parseSingleKTX } from "./compressed-texture";
export type { IScene } from "@oasis-engine/resource-process";

View File

@@ -1,129 +0,0 @@
import { Component, Logger } from "@oasis-engine/core";
import { Model } from "./Model";
import { Oasis } from "./Oasis";
import { Parser } from "./Parser";
import { pluginHook } from "./plugins/PluginManager";
import { scriptAbility } from "./resources";
import { ComponentConfig, Props } from "./types";
import { switchElementsIndex } from "./utils";
import { colliderConfigure } from "./ColliderConfigure";
export class AbilityManager {
private abilityMap: { [id: string]: Component } = {};
constructor(private oasis: Oasis) {}
@pluginHook({ after: "abilityAdded", before: "beforeAbilityAdded" })
public add(abilityConfig: ComponentConfig) {
const { type, node: nodeId, props, id, index } = abilityConfig;
const node = this.oasis.nodeManager.get(nodeId);
const AbilityConstructor = this.getCompConstructor(type);
if (!AbilityConstructor) {
Logger.error(`${type} ability is not defined`);
return;
}
const abilityProps = this.mixPropsToExplicitProps(props);
const ability = node.addComponent(AbilityConstructor);
const { enabled } = abilityProps;
if (enabled !== undefined) {
ability.enabled = enabled;
}
if (type === "GLTFModel") {
// TODO
(ability as any).init(abilityProps);
} else if (type === "Model") {
// TODO
(ability as any).setProps(abilityProps);
if (abilityProps.material) {
(ability as any).material = abilityProps.material;
}
} else if (type === "StaticCollider" || type === "DynamicCollider") {
colliderConfigure(ability as any, abilityProps);
} else {
for (let k in abilityProps) {
if (abilityProps[k] !== null) {
ability[k] = abilityProps[k];
}
}
}
//@ts-ignore
const abilityArray = node._components;
const currentIndex = abilityArray.length - 1;
switchElementsIndex(abilityArray, currentIndex, index);
(ability as any).id = id;
this.abilityMap[id] = ability;
return ability;
}
@pluginHook({ before: "beforeAbilityUpdated", after: "abilityUpdated" })
public update(id: string, key: string, value: any) {
if (value && this.checkIsAsset(value)) {
this.get(id)[key] = this.oasis.resourceManager.get(value.id).resource;
} else {
if (this.get(id).constructor === Model) {
(this.get(id) as any).updateProp(key, value);
} else {
this.get(id)[key] = value;
}
}
return { id, key, value };
}
public addRuntimeComponent(componentId: string, component: Component) {
(component as any).id = componentId;
this.abilityMap[componentId] = component;
return component;
}
public get(id: string): Component {
return this.abilityMap[id];
}
@pluginHook({ after: "abilityDeleted", before: "beforeAbilityDeleted" })
public delete(id: string) {
const ability = this.abilityMap[id];
ability.destroy();
delete this.abilityMap[id];
return id;
}
private getCompConstructor(type: string) {
const splits = type.split(".");
// script
if (splits[0] === "script") {
return scriptAbility[splits[1]];
}
const constructor = Parser._components["o3"][type];
if (!constructor) {
console.warn(`${type} is not defined`);
}
return constructor;
}
private mixPropsToExplicitProps(props: Props) {
const explicitProps = { ...props };
for (let k in props) {
const prop = props[k];
if (prop && this.checkIsAsset(prop)) {
const res = this.oasis.resourceManager.get(prop.id);
if (res) {
explicitProps[k] = res.resource;
} else {
explicitProps[k] = null;
Logger.warn(`AbilityManager: can't get asset "${k}", which id is ${prop.id}`);
}
}
}
return explicitProps;
}
private checkIsAsset(prop: any): boolean {
return prop.type === "asset";
}
}

View File

@@ -1,80 +0,0 @@
import {
BoxColliderShape,
CapsuleColliderShape,
Collider,
ColliderShapeUpAxis,
DynamicCollider,
PlaneColliderShape,
SphereColliderShape
} from "@oasis-engine/core";
import { Vector3 } from "@oasis-engine/math";
// 根据Schema构造Component
export function colliderConfigure(collider: Collider, props: any) {
(<any>collider).isShowCollider = props.isShowCollider;
const shapes = props.colliderShapes;
for (let i = 0; i < shapes.length; i++) {
const shape = shapes[i];
switch (shape._shapes) {
case "BoxColliderShape": {
const box = new BoxColliderShape();
shape.size && box.setSize(shape.size[0], shape.size[1], shape.size[2]);
shape.position && box.setPosition(shape.position[0], shape.position[1], shape.position[2]);
shape.isTrigger && (box.isTrigger = shape.isTrigger);
collider.addShape(box);
break;
}
case "CapsuleColliderShape": {
const capsule = new CapsuleColliderShape();
shape.radius && (capsule.radius = shape.radius);
shape.height && (capsule.height = shape.height);
if (shape.upAxis) {
switch (shape.upAxis) {
case "X":
capsule.upAxis = ColliderShapeUpAxis.X;
break;
case "Y":
capsule.upAxis = ColliderShapeUpAxis.Y;
break;
case "Z":
capsule.upAxis = ColliderShapeUpAxis.Z;
break;
}
}
shape.position && capsule.setPosition(shape.position[0], shape.position[1], shape.position[2]);
shape.isTrigger && (capsule.isTrigger = shape.isTrigger);
collider.addShape(capsule);
break;
}
case "PlaneColliderShape": {
const plane = new PlaneColliderShape();
shape.rotation && plane.setRotation(shape.rotation[0], shape.rotation[1], shape.rotation[2]);
shape.position && plane.setPosition(shape.position[0], shape.position[1], shape.position[2]);
shape.isTrigger && (plane.isTrigger = shape.isTrigger);
collider.addShape(plane);
break;
}
case "SphereColliderShape": {
const sphere = new SphereColliderShape();
shape.radius && (sphere.radius = shape.radius);
shape.position && sphere.setPosition(shape.position[0], shape.position[1], shape.position[2]);
shape.isTrigger && (sphere.isTrigger = shape.isTrigger);
collider.addShape(sphere);
break;
}
}
}
if (collider instanceof DynamicCollider) {
const force = props.force;
if (force) {
(<DynamicCollider>collider).applyForce(new Vector3(force[0], force[1], force[2]));
}
const torque = props.torque;
if (torque) {
(<DynamicCollider>collider).applyTorque(new Vector3(torque[0], torque[1], torque[2]));
}
}
}

View File

@@ -1,197 +0,0 @@
import { Animator, AnimatorController, Component, Entity } from "@oasis-engine/core";
import { BoolUpdateFlag } from "@oasis-engine/core/types/BoolUpdateFlag";
import { GLTFResource } from "../gltf/GLTFResource";
/**
* @deprecated
* Temporarily only for editor use.
* Remove when editor finish change from gltf to prefab.
*/
export class GLTFModel extends Component {
private _animatorController: AnimatorController;
private _speed: number = 1.0;
private _animator: Animator;
private _asset: GLTFResource;
private _glTFEntity: Entity;
private _clipPreview: string;
private _hasBuiltNode: boolean = false;
private _controllerUpdateFlag: BoolUpdateFlag;
get asset() {
return this._asset;
}
set asset(value: GLTFResource) {
const { _animatorController: animatorController, _speed: speed } = this;
const entity = this._glTFEntity;
if (value && value.defaultSceneRoot === this._glTFEntity) {
return;
}
if (!this._hasBuiltNode) {
entity.clearChildren();
if (value !== null) {
entity?.destroy();
const gltfEntity = value.defaultSceneRoot.clone();
this._animator = gltfEntity.getComponent(Animator);
this.entity.addChild(gltfEntity);
gltfEntity.isActive = this.enabled;
this._glTFEntity = gltfEntity;
}
}
if (animatorController) {
this._animator.animatorController = animatorController;
this._animator.speed = speed;
this._playState();
}
this._asset = value;
}
get animatorController(): AnimatorController {
return this._animatorController;
}
set animatorController(animatorController: AnimatorController) {
const { _animator: animator } = this;
if (animatorController !== this._animatorController) {
this._controllerUpdateFlag && this._controllerUpdateFlag.clearFromManagers();
// @ts-ignore
this._controllerUpdateFlag = animatorController && animatorController._registerChangeFlag();
this._animatorController = animatorController;
if (animator) {
animator.animatorController = animatorController;
this._playState();
}
}
}
get speed(): number {
return this._speed;
}
set speed(speed: number) {
const { _animator: animator } = this;
this._speed = speed;
if (animator) {
animator.speed = speed;
this._playState();
}
}
get animator() {
return this._animator;
}
get clipPreview() {
return this._clipPreview;
}
set clipPreview(value: string) {
if (this._animator) {
if (value) {
if (value === "_default") {
this._playDefaultState();
} else {
this._animator.play(value, 0);
}
} else {
// @ts-ignore
this._animator._reset();
}
}
this._clipPreview = value;
}
constructor(entity) {
super(entity);
}
/**
* Init.
* @param props - Init props
*/
init(props): void {
const { asset = null, speed, animatorController, clipPreview, isClone } = props;
if (isClone) {
const rootName = (props as any).gltfRootName;
if (rootName) {
this._glTFEntity = this.entity.findByName(rootName);
}
}
if (!this._glTFEntity) {
const rootName = `GLTF-${Date.now()}`;
(props as any).gltfRootName = rootName;
this._glTFEntity = this.entity.createChild(rootName);
this._hasBuiltNode = false;
} else {
this._hasBuiltNode = true;
}
this.asset = asset;
this.animatorController = animatorController;
this.speed = speed;
this.clipPreview = clipPreview;
}
update() {
if (this._animator) {
if (this._controllerUpdateFlag?.flag) {
this._playState();
}
}
}
/**
* @override
*/
_onEnable(): void {
this._glTFEntity && (this._glTFEntity.isActive = true);
this.engine._componentsManager.addOnUpdateAnimations(this);
}
/**
* @override
*/
_onDisable(): void {
this._glTFEntity && (this._glTFEntity.isActive = false);
this.engine._componentsManager.removeOnUpdateAnimations(this);
}
_playState() {
const playStateName = this._clipPreview;
if (playStateName) {
if (playStateName === "_default") {
this._playDefaultState();
} else {
this._animator.play(playStateName, 0);
}
} else {
// @ts-ignore
this._animator._reset();
}
if (this._controllerUpdateFlag?.flag) {
this._controllerUpdateFlag.flag = false;
}
}
_playDefaultState() {
const { _animatorController: animatorController, _animator: animator } = this;
if (!animator) return;
if (animatorController) {
const { layers } = animatorController;
for (let i = 0, length = layers.length; i < length; ++i) {
//@ts-ignore
const defaultState = layers[i]?.stateMachine?._defaultState;
const defaultStateName = defaultState?.name;
if (defaultStateName) {
animator.play(defaultStateName, i);
} else {
// @ts-ignore
animator._reset();
}
if (this._controllerUpdateFlag?.flag) {
this._controllerUpdateFlag.flag = false;
}
}
}
}
}

View File

@@ -1,62 +0,0 @@
import { BlinnPhongMaterial, Entity, MeshRenderer, PrimitiveMesh } from "@oasis-engine/core";
// Only for editor
export class Model extends MeshRenderer {
private _props: Object = null;
constructor(entity: Entity) {
super(entity);
this.setMaterial(new BlinnPhongMaterial(this.engine));
}
get material(): any {
return this.getMaterial();
}
set material(mtl: any) {
this.setMaterial(mtl);
}
setProps(props: any = {}) {
if (this._props !== props) {
this._props = props;
}
switch (props.geometryType) {
case "Sphere":
this.mesh = PrimitiveMesh.createSphere(this._engine, props.sphereRadius, props.sphereSegments);
break;
case "Cylinder":
this.mesh = PrimitiveMesh.createCylinder(
this._engine,
props.cylinderRadiusTop,
props.cylinderRadiusBottom,
props.cylinderHeight,
props.cylinderRadialSegments,
props.cylinderHeightSegments
);
break;
case "Plane":
this.mesh = PrimitiveMesh.createPlane(
this._engine,
props.planeWidth,
props.planeHeight,
props.planeHorizontalSegments,
props.planeVerticalSegments
);
break;
case "Box":
this.mesh = PrimitiveMesh.createCuboid(this._engine, props.boxWidth, props.boxHeight, props.boxDepth);
break;
}
}
updateProp(key: string, value: string | number) {
const props = this._props;
props[key] = value;
this.setProps(props);
}
}

View File

@@ -1,68 +0,0 @@
import { Entity } from "@oasis-engine/core";
import { Vector3 } from "@oasis-engine/math";
import { Oasis } from "./Oasis";
import { pluginHook } from "./plugins/PluginManager";
import { NodeConfig } from "./types";
import { switchElementsIndex } from "./utils";
export class NodeManager {
private nodeMap: { [id: string]: Entity } = {};
private readonly root: Entity;
constructor(private oasis: Oasis) {
this.root = new Entity(this.oasis.engine, "root");
}
public addRootEntity() {
this.oasis.engine.sceneManager.activeScene.addRootEntity(this.root);
}
@pluginHook({ after: "nodeAdded" })
public add(nodeConfig: NodeConfig) {
this.create(nodeConfig);
this.append(nodeConfig.id, nodeConfig.parent, nodeConfig.index);
return this.get(nodeConfig.id);
}
@pluginHook({ before: "beforeNodeUpdated", after: "nodeUpdated" })
public update(id: string, key: string, value: any) {
this.get(id)[key] = value;
return { id, key, value };
}
public get(id: string): Entity {
return this.nodeMap[id];
}
public reset() {
this.nodeMap = {};
}
@pluginHook({ before: "beforeNodeDeleted" })
public delete(id: string) {
this.nodeMap[id].destroy();
delete this.nodeMap[id];
}
private create(nodeConfig: NodeConfig): Entity {
const { isActive, position, rotation, scale, id, name } = nodeConfig;
const entity = new Entity(this.oasis.engine, name);
entity.isActive = isActive;
entity.transform.position = new Vector3(position[0], position[1], position[2]);
entity.transform.rotation = new Vector3(rotation[0], rotation[1], rotation[2]);
entity.transform.scale = new Vector3(scale[0], scale[1], scale[2]);
(entity as any).id = id;
this.nodeMap[id] = entity;
return entity;
}
private append(childId: string, parentId: string, index: number) {
const child = this.nodeMap[childId];
const parent = this.nodeMap[parentId] || this.root;
parent.addChild(child);
//@ts-ignore
const children = parent._children;
const currentIndex = children.length - 1;
switchElementsIndex(children, currentIndex, index);
}
}

View File

@@ -1,131 +0,0 @@
import { Engine, EventDispatcher, ObjectValues } from "@oasis-engine/core";
import { AbilityManager } from "./AbilityManager";
import { NodeManager } from "./NodeManager";
import { SceneManager } from "./SceneManager";
import { pluginHook, PluginManager } from "./plugins/PluginManager";
import { RESOURCE_CLASS, SchemaResourceManager } from "./ResourceManager";
import { Options, Schema } from "./types";
export class Oasis extends EventDispatcher {
public readonly nodeManager: NodeManager;
public readonly abilityManager: AbilityManager;
public readonly sceneManager: SceneManager;
public resourceManager: SchemaResourceManager;
public _canvas: HTMLCanvasElement;
private schema: Schema;
public timeout: number;
private oasis = this;
public engine: Engine;
private constructor(private _options: Options, public readonly pluginManager: PluginManager) {
super();
this.engine = _options.engine;
this.schema = _options.config;
this.timeout = _options.timeout;
_options.scripts = _options.scripts ?? {};
this.nodeManager = new NodeManager(this);
this.abilityManager = new AbilityManager(this);
this.nodeManager.add = this.nodeManager.add.bind(this.nodeManager);
this.abilityManager.add = this.abilityManager.add.bind(this.abilityManager);
this.resourceManager = new SchemaResourceManager(this);
this.sceneManager = new SceneManager(this);
if (_options.fps) {
this.engine.targetFrameRate = _options.fps;
this.engine.vSyncCount = 0;
}
}
public get canvas(): HTMLCanvasElement {
return this._options.canvas;
}
public get options(): Readonly<Options> {
return this._options;
}
public updateConfig(config: Schema): void {
this.schema = config;
this.init();
}
@pluginHook({ after: "schemaParsed" })
private init(): Promise<any> {
return this.loadResources().then(() => {
this.bindResources();
this.parseEntities();
this.attach();
this.nodeManager.addRootEntity();
this.sceneManager.init();
this.parseNodeAbilities();
this.pluginManager.boot(this);
});
}
private loadResources(): Promise<any> {
const { assets = {} } = this.schema;
const loadingPromises = ObjectValues(assets)
.filter((asset) => {
if (RESOURCE_CLASS[asset.type]) {
return true;
}
console.warn(`${asset.type} loader is not defined. the ${asset.type} type will be ignored.`);
return false;
})
.map((asset) => this.resourceManager.load(asset));
return Promise.all(loadingPromises);
}
private bindResources() {
this.resourceManager.getAll().forEach((resource) => {
resource.bind();
});
}
private parseEntities(): void {
const { nodes } = this.schema;
const indices = this.bfsNodes();
indices.map((index) => nodes[index]).forEach(this.nodeManager.add);
}
private parseNodeAbilities(): void {
const { abilities } = this.schema;
Object.keys(abilities)
.map((id) => ({ id, ...abilities[id] }))
.forEach(this.abilityManager.add);
}
private bfsNodes(): number[] {
const { nodes } = this.schema;
const roots = ObjectValues(nodes)
.filter((node) => !nodes[node.parent])
.map((node) => node.id);
let result = [];
const traverseChildren = (roots: string[]) => {
result = result.concat(roots);
roots.forEach((id) => {
const children = nodes[id].children;
children && traverseChildren(children);
});
};
traverseChildren(roots);
return result;
}
private attach() {
this.resourceManager.getAll().forEach((resource) => {
resource.attach();
});
}
static create(options: Options, pluginManager: PluginManager): Promise<Oasis> {
const oasis = new Oasis(options, pluginManager);
return oasis.init().then(() => {
options.autoPlay && oasis.engine.run();
return oasis;
});
}
}

View File

@@ -1,56 +0,0 @@
import { Component } from "@oasis-engine/core";
import { Oasis } from "./Oasis";
import { Plugin } from "./plugins/Plugin";
import { PluginManager } from "./plugins/PluginManager";
import { Options } from "./types";
import { compatibleToV2 } from "./temp.compatible";
const CURRENT_SCHEMA_VERSION = 3;
export class Parser {
private pluginManager: PluginManager = new PluginManager();
/**
* Parse a scene config.
* @param options - Options of scene
*/
public parse(options: Options): Promise<Oasis> {
if (options?.config?.version !== CURRENT_SCHEMA_VERSION) {
console.warn(
`schema-parser: schema version "${options?.config?.version}" is out of date, please re-pull the latest version (version ${CURRENT_SCHEMA_VERSION}) of the schema`
);
}
compatibleToV2(options.config);
return Oasis.create(options, this.pluginManager);
}
register(plugin: Plugin) {
this.pluginManager.register(plugin);
}
resetPlugins() {
this.pluginManager.reset();
}
private constructor() {}
static create(): Parser {
const parser = new Parser();
return parser;
}
/** @internal */
public static _components: { [namespace: string]: { [compName: string]: Component } } = {};
/**
* Register parsing component
* @param namespace - Namespace
* @param components - Components
*/
static registerComponents(namespace: string, components: { [key: string]: any }) {
if (!this._components[namespace]) {
this._components[namespace] = {};
}
Object.assign(this._components[namespace], components);
}
}
export const parser = Parser.create();

View File

@@ -1,181 +0,0 @@
import { ObjectValues, ResourceManager } from "@oasis-engine/core";
import { Oasis } from "./Oasis";
import { pluginHook } from "./plugins/PluginManager";
import {
BaseResource,
BlinnPhongMaterialResource,
GLTFResource,
PBRMaterialResource,
PBRSpecularMaterialResource,
SchemaResource,
ScriptResource,
SpriteResource,
TextureCubeMapResource,
TextureResource,
UnlitMaterialResource,
AnimatorControllerResource,
AnimationClipResource,
AmbientLightResource
} from "./resources";
import { SpriteAtlasResource } from "./resources/SpriteAtlasResource";
import { AssetConfig } from "./types";
export const RESOURCE_CLASS = {
script: ScriptResource,
gltf: GLTFResource,
texture: TextureResource,
// 'image': TextureResource,
cubeTexture: TextureCubeMapResource,
PBRMaterial: PBRMaterialResource,
PBRSpecularMaterial: PBRSpecularMaterialResource,
UnlitMaterial: UnlitMaterialResource,
BlinnPhongMaterial: BlinnPhongMaterialResource,
base: BaseResource,
sprite: SpriteResource,
SpriteAtlas: SpriteAtlasResource,
animatorController: AnimatorControllerResource,
animationClip: AnimationClipResource,
environment: AmbientLightResource
};
const RESOURCE_TYPE: Map<SchemaResource, string> = new Map();
for (const key in RESOURCE_CLASS) {
if (RESOURCE_CLASS.hasOwnProperty(key)) {
const element = RESOURCE_CLASS[key];
RESOURCE_TYPE.set(element, key);
}
}
const resourceFactory = {
createResource(resourceManager: SchemaResourceManager, type: string): SchemaResource {
return new RESOURCE_CLASS[type](resourceManager);
}
};
export function registerResource(type: string, resource: any) {
if (!RESOURCE_CLASS.hasOwnProperty(type)) {
RESOURCE_CLASS[type] = resource;
RESOURCE_TYPE.set(resource, type);
}
}
export class SchemaResourceManager {
private resourceMap: { [id: string]: SchemaResource } = {};
private resourceIdMap: WeakMap<SchemaResource, string> = new WeakMap();
private maxId = 0;
private readonly engineResourceManager: ResourceManager;
constructor(private oasis: Oasis) {
this.engineResourceManager = this.oasis.engine.resourceManager;
}
load(asset: AssetConfig): Promise<SchemaResource> {
const resource = resourceFactory.createResource(this, asset.type);
const loadPromise = resource.load(this.oasis.engine.resourceManager, asset, this.oasis);
this.maxId = Math.max(+asset.id, this.maxId);
loadPromise.then(() => {
this.resourceMap[asset.id] = resource;
this.resourceIdMap.set(resource, asset.id);
});
return loadPromise;
}
add(asset: AssetConfig): Promise<any> {
const resource = resourceFactory.createResource(this, asset.type);
return new Promise((resolve) => {
resource.loadWithAttachedResources(this.oasis.engine.resourceManager, asset, this.oasis).then((result) => {
resolve(this.getAddResourceResult(result.resources, result.structure));
});
});
}
@pluginHook({ before: "beforeResourceRemove" })
remove(id: string): Promise<Array<string>> {
return new Promise((resolve) => {
const resource = this.resourceMap[id];
const result = [id];
let hasAttachedResource = false;
delete this.resourceMap[id];
if (resource) {
const attached = resource.attachedResources;
for (let index = 0; index < attached.length; index++) {
const attachedResource = attached[index];
const attachedResourceId = this.resourceIdMap.get(attachedResource);
if (attachedResourceId) {
hasAttachedResource = true;
this.remove(attachedResourceId).then((attachedResourceRemoveResult) => {
result.push(...attachedResourceRemoveResult);
resolve(result);
});
}
}
}
if (!hasAttachedResource) {
resolve(result);
}
});
}
@pluginHook({ after: "resourceUpdated", before: "beforeResourceUpdate" })
update(id: string, key: string, value: any) {
const resource = this.get(id);
if (resource) {
resource.update(key, value);
}
return {
resource,
id,
key,
value
};
}
updateMeta(id: string, key: string, value: any) {
const resource = this.get(id);
if (resource) {
resource.updateMeta(key, value);
}
}
get(id: string): SchemaResource {
return this.resourceMap[id];
}
getAll(): Array<SchemaResource> {
return ObjectValues(this.resourceMap);
}
private getAddResourceResult(resources, structure) {
const addResourceResult: any = {};
const resource = resources[structure.index];
const id = `${++this.maxId}`;
this.resourceMap[id] = resource;
this.resourceIdMap.set(resource, id);
addResourceResult.id = this.maxId;
addResourceResult.type = RESOURCE_TYPE.get(resource.constructor);
addResourceResult.meta = resource.meta;
addResourceResult.props = {};
for (const key in structure.props) {
if (structure.props.hasOwnProperty(key)) {
const element = structure.props[key];
if (element) {
if (Array.isArray(element)) {
addResourceResult.props[key] = element.map((child) => this.getAddResourceResult(resources, child));
} else {
addResourceResult.props[key] = this.getAddResourceResult(resources, element);
}
}
}
}
return addResourceResult;
}
get isLocal(): boolean {
return this.oasis.options.local;
}
get useCompressedTexture(): boolean {
return this.oasis.options.useCompressedTexture ?? true;
}
}

View File

@@ -1,59 +0,0 @@
import { AmbientLight, DiffuseMode, PrimitiveMesh, SkyBoxMaterial } from "@oasis-engine/core";
import { Oasis } from "./Oasis";
import { pluginHook } from "./plugins/PluginManager";
export class SceneManager {
constructor(private oasis: Oasis) {}
init() {
const { scene } = this.oasis.options.config;
if (scene) {
Object.keys(scene).forEach((field) => {
const fieldConfig = scene[field];
Object.keys(fieldConfig.props).forEach((key) => {
const prop = fieldConfig.props[key];
this.setProp(field, key, prop);
});
});
}
}
@pluginHook({ before: "beforeSceneUpdated", after: "sceneUpdated" })
public update(field: string, key: string, value: any) {
this.setProp(field, key, value);
return { field, key, value };
}
private setProp(field, key, prop) {
const scene = this.oasis.engine.sceneManager.activeScene;
if (field === "background" && key === "skyboxTexture") {
const sky = scene.background.sky;
if (prop) {
sky.mesh = PrimitiveMesh.createCuboid(scene.engine, 2, 2, 2);
const skyMaterial = new SkyBoxMaterial(scene.engine);
skyMaterial.textureCubeMap = this.oasis.resourceManager.get(prop.id).resource;
sky.material = skyMaterial;
} else {
sky.mesh = null;
sky.material = null;
}
} else if (scene[field] && field === "ambientLight" && key === "specularTexture") {
if (prop && prop.type === "asset") {
const ambientLight: AmbientLight = this.oasis.resourceManager.get(prop.id).resource;
scene.ambientLight.specularTexture = ambientLight.specularTexture;
scene.ambientLight.diffuseSphericalHarmonics = ambientLight.diffuseSphericalHarmonics;
scene.ambientLight.diffuseMode = DiffuseMode.SphericalHarmonics;
scene.ambientLight.specularTextureDecodeRGBM = true;
} else {
scene.ambientLight.specularTexture = null;
scene.ambientLight.diffuseMode = DiffuseMode.SolidColor;
}
} else if (scene[field]) {
if (prop && prop.type === "asset") {
scene[field][key] = this.oasis.resourceManager.get(prop.id).resource;
} else {
scene[field][key] = prop;
}
}
}
}

View File

@@ -1,7 +0,0 @@
export { GLTFModel } from "./GLTFModel";
export { Model } from "./Model";
export { Oasis } from "./Oasis";
export { parser, Parser } from "./Parser";
export { registerResource } from "./ResourceManager";
export { SchemaResource, script, SpritePivotType } from "./resources";
export * from "./types";

View File

@@ -1,4 +0,0 @@
import { PluginHook } from "./PluginManager";
import { Oasis } from "../Oasis";
export type Plugin = ((oasis: Oasis) => PluginHook) | PluginHook;

View File

@@ -1,71 +0,0 @@
import { Component, Entity } from "@oasis-engine/core";
import { Oasis } from "../Oasis";
import { SchemaResource } from "../resources";
import { Plugin } from "./Plugin";
export class PluginManager implements PluginHook {
private registeredPlugins: Set<Plugin> = new Set();
private plugins: PluginHook[] = [];
register(plugin: Plugin) {
this.registeredPlugins.add(plugin);
}
boot(oasis: Oasis) {
for (let plugin of this.registeredPlugins.values()) {
if (typeof plugin === "function") {
plugin = plugin(oasis);
}
this.plugins.push(plugin);
}
}
reset() {
this.registeredPlugins.clear();
this.plugins = [];
}
nodeAdded(entity: Entity) {
this.delegateMethod("nodeAdded", entity);
}
private delegateMethod(name: keyof PluginHook, ...args) {
this.plugins.forEach((plugin) => plugin[name] && (plugin[name] as any)(...args));
}
}
export interface PluginHook {
oasis?: Oasis;
nodeAdded?(entity: Entity): any;
beforeNodeUpdated?(id: string, key: string, value: any): any;
nodeUpdated?(updateConfig?: { id: string; key: string; value: any }): any;
abilityAdded?(ability: Component): any;
beforeAbilityAdded?(config: any): any;
beforeAbilityUpdated?(id: string, key: string, value: any): any;
abilityUpdated?(updateConfig?: { id: string; key: string; value: any }): any;
schemaParsed?(): any;
abilityDeleted?(id: string): any;
beforeAbilityDeleted?(id: string): any;
beforeNodeDeleted?(config: any): any;
beforeResourceRemove?(id: string): any;
resourceUpdated?(info: { resource: SchemaResource; id: string; key: string; value: any }): any;
beforeResourceUpdate?(id: string, key: string, value: any): any;
// todo type
beforeResourceAdd?(resource: any): any;
resourceAdded?(resource: any): any;
beforeSceneUpdated?(field: string, key: string, value: any): any;
sceneUpdated?(updateConfig?: { type: string; key: string; value: any }): any;
}
export function pluginHook(options: Partial<{ before: keyof PluginHook; after: keyof PluginHook }>): MethodDecorator {
return function (target: any, propertyName: string, descriptor: TypedPropertyDescriptor<any>) {
const method = descriptor.value;
descriptor.value = function (...args: any[]) {
options.before && this.oasis.pluginManager.delegateMethod(options.before, ...args);
return Promise.resolve(method.apply(this, arguments)).then((returnObj) => {
options.after && this.oasis.pluginManager.delegateMethod(options.after, returnObj);
return returnObj;
});
};
};
}

View File

@@ -1,27 +0,0 @@
import { AssetType, ResourceManager } from "@oasis-engine/core";
import { Oasis } from "../Oasis";
import { AssetConfig } from "../types";
import { SchemaResource } from "./SchemaResource";
export class AmbientLightResource extends SchemaResource {
load(resourceManager: ResourceManager, assetConfig: AssetConfig, oasis: Oasis): Promise<AmbientLightResource> {
return new Promise((resolve, reject) => {
const { url } = assetConfig;
resourceManager
.load({ url, type: AssetType.Env })
.then((res) => {
this._resource = res;
resolve(this);
})
.catch((e) => {
reject(e);
});
});
}
setMeta() {
if (this.resource) {
this._meta.name = this.resource.name;
}
}
}

View File

@@ -1,51 +0,0 @@
import {
ResourceManager,
} from "@oasis-engine/core";
import { AssetConfig, LoadAttachedResourceResult } from "../types";
import { SchemaResource } from "./SchemaResource";
export class AnimationClipResource extends SchemaResource {
load(resourceManager: ResourceManager, assetConfig: AssetConfig): Promise<any> {
return new Promise((resolve) => {
this._resource = assetConfig.props || {};
this.setMeta();
resolve(this);
});
}
loadWithAttachedResources(
resourceManager: ResourceManager,
assetConfig: AssetConfig
): Promise<LoadAttachedResourceResult> {
return new Promise((resolve, reject) => {
let loadPromise;
if (assetConfig.props) {
loadPromise = this.load(resourceManager, assetConfig);
} else {
reject("Load AnimationClip Error");
}
if (loadPromise) {
loadPromise.then(() => {
const result: any = {
resources: [this],
structure: {
index: 0,
props: {}
}
};
resolve(result);
});
}
});
}
setMeta() {
if (this.resource) {
this.meta.name = this.resource.name;
}
}
getProps() {
return this._resource;
}
}

View File

@@ -1,214 +0,0 @@
import { AnimationClipResource } from "./AnimationClipResource";
import {
AnimatorController,
AnimatorControllerLayer,
AnimatorStateMachine,
AnimatorStateTransition
} from "@oasis-engine/core";
import { ResourceManager } from "@oasis-engine/core";
import { SchemaResource } from "./SchemaResource";
import { AssetConfig, LoadAttachedResourceResult } from "../types";
export class AnimatorControllerResource extends SchemaResource {
public gltf;
private animatorControllerData;
private animationClipAssets: any[];
private animationsIndices: {
name: string;
index: number;
}[];
load(resourceManager: ResourceManager, assetConfig: AssetConfig): Promise<any> {
return new Promise((resolve) => {
const { animatorController, animationClips: animationClipAssets, animationsIndices, gltf } =
assetConfig.props || {};
this._resource = new AnimatorController();
this.animatorControllerData = animatorController;
this.animationsIndices = animationsIndices || [];
this.animationClipAssets = animationClipAssets || [];
this.gltf = gltf;
!animatorController && this._setDefaultDataByAnimationClip();
this.setMetaData("name", assetConfig.name);
resolve(this);
});
}
loadWithAttachedResources(
resourceManager: ResourceManager,
assetConfig: AssetConfig
): Promise<LoadAttachedResourceResult> {
return new Promise((resolve, reject) => {
const clipLoadPromises = [];
this.load(resourceManager, assetConfig).then(() => {
const result: any = {
resources: [this],
structure: {
index: 0,
props: {
animationClips: []
}
}
};
const animations = this.animationsIndices;
for (let i = 0, length = animations.length; i < length; ++i) {
const clip = animations[i];
const clipResourse = new AnimationClipResource(this.resourceManager);
this.attachedResources.push(clipResourse);
clipLoadPromises.push(
clipResourse.loadWithAttachedResources(resourceManager, {
type: "animationClip",
name: clip.name,
props: clip
})
);
}
Promise.all(clipLoadPromises).then((res) => {
const { animationClips } = result.structure.props;
res.forEach((clip) => {
const clipStructure = clip.structure;
const clipResource = clip.resources[clipStructure.index];
result.resources.push(clipResource);
clipStructure.index = result.resources.length - 1;
animationClips.push(clipStructure);
});
resolve(result);
});
});
});
}
setMetaData(key, value) {
this._meta[key] = value;
}
update(key: string, value: any) {
this._initAnimatorController(value);
}
bind() {
const { animatorControllerData, animationClipAssets } = this;
this._bindClips(animationClipAssets);
if (animatorControllerData) {
this._initAnimatorController(animatorControllerData);
} else {
this._setDefaultDataByAnimationClipAsset();
}
}
_initAnimatorController(animatorControllerData) {
const { animations } = this.gltf || {};
const { layers } = animatorControllerData;
if (!animations || !layers) return;
this._resource.clearLayers();
for (let i = 0, length = layers.length; i < length; ++i) {
const { name, blending, weight, stateMachine: stateMachineData } = layers[i];
if (!stateMachineData) continue;
const layer = new AnimatorControllerLayer(name);
layer.blendingMode = blending;
layer.weight = weight;
const { states } = stateMachineData;
const stateMachine = new AnimatorStateMachine();
let stateMachineTransitions = [];
for (let j = 0, length = states.length; j < length; ++j) {
const stateData = states[j];
const {
name,
transitions,
clip,
speed,
wrapMode,
clipStartNormalizedTime,
clipEndNormalizedTime,
isDefaultState
} = stateData;
const { id: clipAssetId } = clip || {};
if (!clipAssetId) continue;
const uniqueName = stateMachine.makeUniqueStateName(name);
if (uniqueName !== name) {
console.warn(`AnimatorState name is existed, name: ${name} reset to ${uniqueName}`);
}
const state = stateMachine.addState(uniqueName);
state.speed = speed;
state.wrapMode = wrapMode;
const animationIndex = this.resourceManager.get(clipAssetId).resource;
const animationClip = animations[animationIndex.index];
if (!animationClip) continue;
state.clip = animationClip;
state.clipStartTime = clipStartNormalizedTime;
state.clipEndTime = clipEndNormalizedTime;
for (let j = 0, length = transitions.length; j < length; ++j) {
const transition = transitions[j];
transitions[j].srcState = state;
stateMachineTransitions.push(transition);
}
if (isDefaultState) {
//@ts-ignore
stateMachine._defaultState = state;
}
}
for (let j = 0, length = stateMachineTransitions.length; j < length; ++j) {
const transitionData = stateMachineTransitions[j];
const transition = new AnimatorStateTransition();
transition.duration = transitionData.duration;
transition.offset = transitionData.offset;
transition.exitTime = transitionData.exitTime;
transition.destinationState = stateMachine.findStateByName(transitionData.targetStateName);
transitionData.srcState.addTransition(transition);
delete transitionData.srcState;
}
layer.stateMachine = stateMachine;
this._resource.addLayer(layer);
}
}
_bindClips(animationClips) {
for (let i = 0, length = animationClips.length; i < length; i++) {
const clipAsset = animationClips[i];
const clipResource = this.resourceManager.get(clipAsset.id);
if (clipResource) {
this._attachedResources.push(clipResource);
} else {
`AnimatorResource: ${this.meta.name} can't find asset "animationClip", which id is: ${clipAsset.id}`;
}
}
}
_setDefaultDataByAnimationClipAsset() {
const { animationClipAssets } = this;
if (!animationClipAssets.length) {
return;
}
let clips = [];
for (let i = 0, length = animationClipAssets.length; i < length; i++) {
const clipAsset = this.resourceManager.get(animationClipAssets[i].id);
clips.push(clipAsset.resource);
}
this.animationsIndices = clips;
this._setDefaultDataByAnimationClip();
}
_setDefaultDataByAnimationClip() {
const { animationsIndices, _resource: animatorController, gltf } = this;
if (!animationsIndices.length || !gltf) {
return;
}
const { animations } = gltf
const layer = new AnimatorControllerLayer("layer");
const animatorStateMachine = new AnimatorStateMachine();
animatorController.addLayer(layer);
layer.stateMachine = animatorStateMachine;
for (let i = 0, length = animationsIndices.length; i < length; i++) {
const animationIndex = animationsIndices[i];
const { name, index} = animationIndex
const uniqueName = animatorStateMachine.makeUniqueStateName(name);
if (uniqueName !== name) {
console.warn(`AnimatorState name is existed, name: ${name} reset to ${uniqueName}`);
}
const animatorState = animatorStateMachine.addState(uniqueName);
animatorState.clip = animations[index];
}
}
}

View File

@@ -1,17 +0,0 @@
import { SchemaResource } from "./SchemaResource";
import { AssetConfig } from "../types";
export class BaseResource extends SchemaResource {
load(resourceLoader, assetConfig: AssetConfig): Promise<BaseResource> {
return new Promise((resolve) => {
this._resource = assetConfig;
this.setMetaData("name", this.resource.name);
this.setMetaData("url", this.resource.url);
resolve(this);
});
}
setMetaData(key, value) {
this._meta[key] = value;
}
}

View File

@@ -1,95 +0,0 @@
import { BlinnPhongMaterial, Logger, ResourceManager, Texture } from "@oasis-engine/core";
import { AssetConfig, LoadAttachedResourceResult } from "../types";
import { getAllGetters, isAsset } from "../utils";
import { SchemaResource } from "./SchemaResource";
import { TextureResource } from "./TextureResource";
export class BlinnPhongMaterialResource extends SchemaResource {
private configProps;
load(resourceManager: ResourceManager, assetConfig: AssetConfig): Promise<BlinnPhongMaterialResource> {
return new Promise((resolve) => {
const assetObj = new BlinnPhongMaterial(resourceManager.engine);
this.configProps = assetConfig.props;
this._resource = assetObj;
for (let k in this.configProps) {
if (!isAsset(this.configProps[k])) {
assetObj[k] = this.configProps[k];
}
}
this.setMeta();
resolve(this);
});
}
loadWithAttachedResources(
resourceManager: ResourceManager,
assetConfig: AssetConfig
): Promise<LoadAttachedResourceResult> {
return new Promise((resolve, reject) => {
let loadPromise;
if (assetConfig.resource instanceof BlinnPhongMaterial) {
loadPromise = new Promise((resolve) => {
this._resource = assetConfig.resource;
this.setMeta();
resolve(this);
});
} else if (assetConfig.props) {
loadPromise = this.load(resourceManager, assetConfig);
} else {
reject("Load BlinnPhongMaterial Error");
}
if (loadPromise) {
loadPromise.then(() => {
const result: any = {
resources: [this],
structure: {
index: 0,
props: {}
}
};
const material = this._resource;
getAllGetters(this._resource).forEach((attr) => {
if (!(material[attr] instanceof Texture)) return;
const textureResource = new TextureResource(this.resourceManager, material[attr]);
this.attachedResources.push(textureResource);
result.resources.push(textureResource);
result.structure.props[attr] = {
index: result.resources.length - 1
};
});
resolve(result);
});
}
});
}
setMeta() {
if (this.resource) {
this.meta.name = this.resource.name;
}
}
bind() {
const resource = this._resource;
Object.keys(this.configProps).forEach((attr) => {
const value = this.configProps[attr];
if (isAsset(value)) {
const textureResource = this.resourceManager.get(value.id);
if (textureResource && textureResource instanceof TextureResource) {
resource[attr] = textureResource.resource;
this._attachedResources.push(textureResource);
} else {
resource[attr] = null;
Logger.warn(
`BlinnPhongMaterialResource: ${this.meta.name} can't find asset "${attr}", which id is: ${value.id}`
);
}
} else {
resource[attr] = value;
}
});
}
}

View File

@@ -1,216 +0,0 @@
import { AnimatorControllerResource } from "./AnimatorControllerResource";
import {
AssetType,
Entity,
Logger,
MeshRenderer,
PBRMaterial,
PBRSpecularMaterial,
ResourceManager,
UnlitMaterial
} from "@oasis-engine/core";
import { Oasis } from "../Oasis";
import { AssetConfig, LoadAttachedResourceResult } from "../types";
import { BlinnPhongMaterialResource } from "./BlinnPhongMaterialResource";
import { PBRMaterialResource } from "./PBRMaterialResource";
import { PBRSpecularMaterialResource } from "./PBRSpecularMaterialResource";
import { SchemaResource } from "./SchemaResource";
import { UnlitMaterialResource } from "./UnlitMaterialResource";
export class GLTFResource extends SchemaResource {
load(resourceManager: ResourceManager, assetConfig: AssetConfig, oasis: Oasis): Promise<any> {
return resourceManager.load<any>({ url: assetConfig.url, type: AssetType.Prefab }).then((res) => {
const gltf = res;
if (assetConfig.props) {
gltf.newMaterial = (assetConfig.props as any).newMaterial;
gltf.animatorControllers = (assetConfig.props as any).animatorControllers;
}
this._resource = gltf;
});
}
loadWithAttachedResources(
resourceManager: ResourceManager,
assetConfig: AssetConfig,
oasis: Oasis
): Promise<LoadAttachedResourceResult> {
return new Promise((resolve) => {
this.load(resourceManager, assetConfig, oasis).then(() => {
const gltf = this.resource;
const { materials = [], _animationsIndices = [] } = gltf;
const materialLoadPromises = [];
const clipLoadPromises = [];
let animatorControllerLoadPromise: Promise<any>;
const result = {
resources: [this],
structure: {
index: 0,
props: {
newMaterial: [],
animatorControllers: []
}
}
};
if (materials?.length) {
for (let i = 0; i < materials.length; i++) {
const material = materials[i];
let materialResource = null;
let type = "";
if (material instanceof PBRMaterial) {
materialResource = new PBRMaterialResource(this.resourceManager);
type = "PBRMaterial";
} else if (material instanceof UnlitMaterial) {
materialResource = new UnlitMaterialResource(this.resourceManager);
type = "UnlitMaterial";
} else if (material instanceof PBRSpecularMaterial) {
materialResource = new PBRSpecularMaterialResource(this.resourceManager);
type = "PBRSpecularMaterial";
} else {
materialResource = new BlinnPhongMaterialResource(this.resourceManager);
type = "BlinnPhongMaterial";
}
this._attachedResources.push(materialResource);
materialLoadPromises.push(
materialResource.loadWithAttachedResources(resourceManager, {
type,
name: material.name,
resource: material
})
);
}
}
if (_animationsIndices.length) {
const animatorControllerResource = new AnimatorControllerResource(this.resourceManager);
this._attachedResources.push(animatorControllerResource);
animatorControllerLoadPromise = animatorControllerResource.loadWithAttachedResources(resourceManager, {
type: "animatorController",
name: "AnimatorController",
props: {
animationsIndices: _animationsIndices,
gltf: this._resource
}
});
}
const loadAttachedMaterial = Promise.all(materialLoadPromises).then((res) => {
const newMaterial = result.structure.props.newMaterial;
res.forEach((mat) => {
const matStructure = mat.structure;
const matResource = mat.resources[matStructure.index];
result.resources.push(matResource);
matStructure.index = result.resources.length - 1;
for (const key in matStructure.props) {
if (matStructure.props.hasOwnProperty(key)) {
const textureStructure = matStructure.props[key];
const textureResource = mat.resources[textureStructure.index];
result.resources.push(textureResource);
textureStructure.index = result.resources.length - 1;
}
}
newMaterial.push(matStructure);
});
});
const loadAttachedController = animatorControllerLoadPromise
? animatorControllerLoadPromise.then((res) => {
const { animatorControllers } = result.structure.props;
const controllerStructure = res.structure;
const controllerResource = res.resources[controllerStructure.index];
result.resources.push(controllerResource as any);
controllerStructure.index = result.resources.length - 1;
const { animationClips } = controllerStructure.props;
if (animationClips) {
for (let i = 0, length = animationClips.length; i < length; ++i) {
const clipStructure = animationClips[i];
const clipResource = res.resources[clipStructure.index];
result.resources.push(clipResource);
clipStructure.index = result.resources.length - 1;
}
}
animatorControllers.push(controllerStructure);
})
: Promise.resolve();
Promise.all([loadAttachedMaterial, loadAttachedController]).then(() => {
resolve(result);
});
});
});
}
setMeta(assetConfig?: AssetConfig) {
if (assetConfig) {
this.meta.name = assetConfig.name;
}
}
bind() {
const resource = this._resource;
this.bindMaterials(resource.newMaterial);
this.bindAnimatorControllers(resource.animatorControllers);
}
update(key: string, value: any) {
if (key === "newMaterial") {
this.bindMaterials(value);
} else {
this._resource[key] = value;
}
}
private bindMaterials(newMaterialsConfig) {
const newMaterialCount = newMaterialsConfig.length;
if (!newMaterialsConfig || !newMaterialsConfig.length) {
return;
}
const gltf = this._resource;
const newMaterials = new Array(newMaterialCount);
gltf.newMaterial = newMaterials;
for (let i = 0; i < newMaterialsConfig.length; i++) {
const mtlResource = this.resourceManager.get(newMaterialsConfig[i].id);
if (mtlResource) {
this._attachedResources.push(mtlResource);
newMaterials[i] = mtlResource.resource;
} else {
Logger.warn(
`GLTFResource: ${this.meta.name} can't find asset "material", which id is: ${newMaterialsConfig[i].id}`
);
}
}
const gltfRoot = gltf.defaultSceneRoot as Entity;
const originMaterials = gltf.materials;
const meshRenderers: MeshRenderer[] = gltfRoot.getComponentsIncludeChildren(MeshRenderer, []);
for (let i = 0; i < newMaterialCount; i++) {
const newMaterial = newMaterials[i];
const originMaterial = originMaterials[i];
for (let j = 0; j < meshRenderers.length; j++) {
const meshRenderer = meshRenderers[j];
const meshMaterials = meshRenderer.getMaterials();
for (let k = 0; k < meshMaterials.length; k++) {
if (originMaterial === meshMaterials[k]) {
meshRenderer.setMaterial(k, newMaterial);
}
}
}
}
}
private bindAnimatorControllers(animatorControllers) {
for (let i = 0, length = animatorControllers.length; i < length; i++) {
const animatorControllerAsset = animatorControllers[i];
const controllerResource = <AnimatorControllerResource>this.resourceManager.get(animatorControllerAsset.id);
controllerResource.gltf = this._resource;
if (controllerResource) {
this._attachedResources.push(controllerResource);
} else {
`GLTFResource: ${this.meta.name} can't find asset "animatorController", which id is: ${animatorControllerAsset.id}`;
}
}
}
}

View File

@@ -1,121 +0,0 @@
import { Logger, PBRMaterial, ResourceManager, Texture } from "@oasis-engine/core";
import { AssetConfig, LoadAttachedResourceResult } from "../types";
import { isAsset } from "../utils";
import { SchemaResource } from "./SchemaResource";
import { TextureResource } from "./TextureResource";
const attrs = [
"metallic",
"roughness",
"roughnessMetallicTexture",
// pbr base
"tilingOffset",
"baseColor",
"normalTextureIntensity",
"emissiveColor",
"occlusionTextureIntensity",
"baseTexture",
"normalTexture",
"emissiveTexture",
"occlusionTexture",
// base material
"isTransparent",
"alphaCutoff",
"renderFace",
"blendMode"
];
export class PBRMaterialResource extends SchemaResource {
private configProps;
load(resourceManager: ResourceManager, assetConfig: AssetConfig): Promise<PBRMaterialResource> {
return new Promise((resolve) => {
const assetObj = new PBRMaterial(resourceManager.engine);
this.configProps = assetConfig.props;
for (let k in this.configProps) {
if (!isAsset(this.configProps[k])) {
assetObj[k] = this.configProps[k];
}
}
this._resource = assetObj;
this.setMeta();
resolve(this);
});
}
loadWithAttachedResources(
resourceManager: ResourceManager,
assetConfig: AssetConfig
): Promise<LoadAttachedResourceResult> {
return new Promise((resolve, reject) => {
let loadPromise;
if (assetConfig.resource instanceof PBRMaterial) {
loadPromise = new Promise((resolve) => {
this._resource = assetConfig.resource;
this.setMeta();
resolve(this);
});
} else if (assetConfig.props) {
loadPromise = this.load(resourceManager, assetConfig);
} else {
reject("Load PBRMaterial Error");
}
if (loadPromise) {
loadPromise.then(() => {
const result: any = {
resources: [this],
structure: {
index: 0,
props: {}
}
};
const material = this._resource;
attrs.forEach((attr) => {
if (!(material[attr] instanceof Texture)) return;
const textureResource = new TextureResource(this.resourceManager, material[attr]);
this.attachedResources.push(textureResource);
result.resources.push(textureResource);
result.structure.props[attr] = {
index: result.resources.length - 1
};
});
resolve(result);
});
}
});
}
setMeta() {
if (this.resource) {
this.meta.name = this.resource.name;
}
}
getProps() {
const result = {};
attrs.forEach((prop) => (result[prop] = this.resource[prop]));
return result;
}
bind() {
const resource = this._resource;
Object.keys(this.configProps).forEach((attr) => {
const value = this.configProps[attr];
if (isAsset(value)) {
const textureResource = this.resourceManager.get(value.id);
if (textureResource && textureResource instanceof TextureResource) {
resource[attr] = textureResource.resource;
this._attachedResources.push(textureResource);
} else {
resource[attr] = null;
Logger.warn(`PBRMaterialResource: ${this.meta.name} can't find asset "${attr}", which id is: ${value.id}`);
}
} else {
resource[attr] = value;
}
});
}
}

View File

@@ -1,124 +0,0 @@
import { Logger, PBRSpecularMaterial, ResourceManager, Texture } from "@oasis-engine/core";
import { AssetConfig, LoadAttachedResourceResult } from "../types";
import { isAsset } from "../utils";
import { SchemaResource } from "./SchemaResource";
import { TextureResource } from "./TextureResource";
const attrs = [
"specularColor",
"glossiness",
"specularGlossinessTexture",
// pbr base
"tilingOffset",
"baseColor",
"normalTextureIntensity",
"emissiveColor",
"occlusionTextureIntensity",
"baseTexture",
"normalTexture",
"emissiveTexture",
"occlusionTexture",
// base material
"isTransparent",
"alphaCutoff",
"renderFace",
"blendMode"
];
export class PBRSpecularMaterialResource extends SchemaResource {
private configProps;
load(resourceManager: ResourceManager, assetConfig: AssetConfig): Promise<PBRSpecularMaterialResource> {
return new Promise((resolve) => {
const assetObj = new PBRSpecularMaterial(resourceManager.engine);
this.configProps = assetConfig.props;
this._resource = assetObj;
for (let k in this.configProps) {
if (!isAsset(this.configProps[k])) {
assetObj[k] = this.configProps[k];
}
}
this.setMeta();
resolve(this);
});
}
loadWithAttachedResources(
resourceManager: ResourceManager,
assetConfig: AssetConfig
): Promise<LoadAttachedResourceResult> {
return new Promise((resolve, reject) => {
let loadPromise;
if (assetConfig.resource instanceof PBRSpecularMaterial) {
loadPromise = new Promise((resolve) => {
this._resource = assetConfig.resource;
this.setMeta();
resolve(this);
});
} else if (assetConfig.props) {
loadPromise = this.load(resourceManager, assetConfig);
} else {
reject("Load PBRSpecularMaterial Error");
}
if (loadPromise) {
loadPromise.then(() => {
const result: any = {
resources: [this],
structure: {
index: 0,
props: {}
}
};
const material = this._resource;
Object.keys(this._resource).forEach((attr) => {
if (!(material[attr] instanceof Texture)) return;
const textureResource = new TextureResource(this.resourceManager, material[attr]);
this.attachedResources.push(textureResource);
result.resources.push(textureResource);
result.structure.props[attr] = {
index: result.resources.length - 1
};
});
resolve(result);
});
}
});
}
setMeta() {
if (this.resource) {
this.meta.name = this.resource.name;
}
}
getProps() {
const result = {};
attrs.forEach((prop) => (result[prop] = this.resource[prop]));
return result;
}
bind() {
const resource = this._resource;
Object.keys(this.configProps).forEach((attr) => {
const value = this.configProps[attr];
if (isAsset(value)) {
const textureResource = this.resourceManager.get(value.id);
if (textureResource && textureResource instanceof TextureResource) {
resource[attr] = textureResource.resource;
this._attachedResources.push(textureResource);
} else {
resource[attr] = null;
Logger.warn(
`PBRSpecularMaterialResource: ${this.meta.name} can't find asset "${attr}", which id is: ${value.id}`
);
}
} else {
resource[attr] = value;
}
});
}
}

View File

@@ -1,87 +0,0 @@
import { Logger, ResourceManager } from "@oasis-engine/core";
import { Oasis } from "../Oasis";
import { SchemaResourceManager } from "../ResourceManager";
import { AssetConfig, LoadAttachedResourceResult } from "../types";
import { isAsset } from "../utils";
interface IResourceMeta {
name?: string;
url?: string;
size?: number;
source?: string;
}
export abstract class SchemaResource {
protected _meta: IResourceMeta = {};
protected _attachedResources: Array<SchemaResource> = [];
/**
* Resource
*/
get resource() {
return this._resource;
}
get meta(): IResourceMeta {
return this._meta;
}
get attachedResources() {
return this._attachedResources;
}
protected setMeta() {}
constructor(protected resourceManager: SchemaResourceManager, protected _resource?: any) {
this.setMeta();
}
abstract load(resourceManager: ResourceManager, assetConfig: AssetConfig, oasis: Oasis): Promise<SchemaResource>;
loadWithAttachedResources(
resourceLoader: any,
assetConfig: AssetConfig,
oasis: Oasis
): Promise<LoadAttachedResourceResult> {
return new Promise((resolve, reject) => {
this.load(resourceLoader, assetConfig, oasis)
.then(() => {
resolve({
resources: [this],
structure: {
index: 0,
props: {}
}
});
})
.catch((e) => {
reject(e);
});
});
}
getProps(): any {
return {};
}
bind(): void {}
attach(): void {}
update(key: string, value: any) {
if (isAsset(value)) {
const resource = this.resourceManager.get(value.id);
if (resource) {
this._resource[key] = resource.resource;
} else {
Logger.warn(`SchemaResource: ${this.meta.name} can't find asset, which id is: ${value.id}`);
}
} else {
this._resource[key] = value;
}
}
updateMeta(key: string, value: any) {
this._meta[key] = value;
}
onDestroy() {}
}

View File

@@ -1,68 +0,0 @@
import { SchemaResource } from "./SchemaResource";
import { AssetConfig } from "../types";
import { Oasis } from "../Oasis";
import { Parser } from "../Parser";
export const scriptAbility = {};
export function script(name: string) {
return (target: any) => {
scriptAbility[name] = target;
};
}
export class ScriptResource extends SchemaResource {
private isInit = false;
private initScriptContext() {
if (this.isInit) {
return;
}
this.isInit = true;
(window as any).__o3_script_context__ = {
o3: Parser._components["o3"],
script: (name: string) => {
return (target: any) => {
scriptAbility[name] = target;
};
}
};
}
load(resourceLoader, assetConfig: AssetConfig, oasis: Oasis): Promise<ScriptResource> {
this.initScriptContext();
return new Promise((resolve) => {
const config = assetConfig as any;
const scripts = config.props.scripts;
if (!this.resourceManager.isLocal) {
const scriptDom = document.createElement("script");
scriptDom.crossOrigin = "anonymous";
this.setMeta(assetConfig);
scriptDom.onload = () => {
const o3Scripts = (window as any).o3Scripts;
for (let i = 0; i < scripts.length; i++) {
const name = scripts[i].name;
this._resource = o3Scripts && o3Scripts[name];
scriptAbility[name] = this._resource;
}
resolve(this);
};
scriptDom.src = assetConfig.url;
document.body.appendChild(scriptDom);
} else {
for (let i = 0; i < scripts.length; i++) {
const name = scripts[i].name;
scriptAbility[name] = oasis.options?.scripts[name];
}
resolve(this);
}
});
}
setMeta(assetConfig?: AssetConfig) {
if (assetConfig) {
this._meta.name = assetConfig.name;
this._meta.url = assetConfig.url;
this._meta.source = assetConfig.source;
}
}
}

View File

@@ -1,61 +0,0 @@
import { AssetType, ResourceManager, SpriteAtlas } from "@oasis-engine/core";
import { AssetConfig } from "../types";
import { getAllGetters } from "../utils";
import { SchemaResource } from "./SchemaResource";
import { SpriteResource } from "./SpriteResource";
export class SpriteAtlasResource extends SchemaResource {
static defaultAtlas: SpriteAtlas;
load(resourceManager: ResourceManager, assetConfig: AssetConfig): Promise<SpriteAtlasResource> {
return new Promise((resolve) => {
this.setMeta();
if (assetConfig.source) {
resourceManager
.load<SpriteAtlas>({
url: assetConfig.source,
type: AssetType.SpriteAtlas
})
.then((spriteAtlas) => {
this._resource = spriteAtlas;
const { sprites } = spriteAtlas;
const schemaResourceManager = this.resourceManager;
for (let index = sprites.length - 1; index >= 0; index--) {
const sprite = sprites[index];
const spriteResource = new SpriteResource(schemaResourceManager, sprite);
// @ts-ignore
const assetID = sprite._assetID;
// @ts-ignore
schemaResourceManager.maxId = Math.max(assetID, schemaResourceManager.maxId);
// @ts-ignore
schemaResourceManager.resourceMap[assetID] = spriteResource;
// @ts-ignore
schemaResourceManager.resourceIdMap.set(spriteResource, "" + assetID);
}
resolve(this);
});
} else {
if (!SpriteAtlasResource.defaultAtlas) {
SpriteAtlasResource.defaultAtlas = new SpriteAtlas(resourceManager.engine);
}
this._resource = SpriteAtlasResource.defaultAtlas;
resolve(this);
}
});
}
setMeta() {
if (this.resource) {
this.meta.name = this.resource.name;
}
}
getProps() {
const result = {};
const props = getAllGetters(this.resource);
props.forEach((prop) => (result[prop] = this.resource[prop]));
return result;
}
update() {}
}

View File

@@ -1,160 +0,0 @@
import { Logger, ResourceManager, Sprite, Texture } from "@oasis-engine/core";
import { AssetConfig, LoadAttachedResourceResult } from "../types";
import { getAllGetters, isAsset } from "../utils";
import { SchemaResource } from "./SchemaResource";
import { TextureResource } from "./TextureResource";
export class SpriteResource extends SchemaResource {
private configProps;
load(resourceManager: ResourceManager, assetConfig: AssetConfig): Promise<SpriteResource> {
return new Promise((resolve) => {
const assetObj = new Sprite(resourceManager.engine);
this.configProps = assetConfig.props;
const { configProps } = this;
const { pivotType, pivot } = configProps;
if (typeof pivot !== "undefined" && typeof pivotType !== "undefined" && pivotType !== SpritePivotType.Custom) {
switch (pivotType) {
case SpritePivotType.Center:
pivot.x = 0.5;
pivot.y = 0.5;
break;
case SpritePivotType.TopLeft:
pivot.x = 0;
pivot.y = 1;
break;
case SpritePivotType.Top:
pivot.x = 0.5;
pivot.y = 1;
break;
case SpritePivotType.TopRight:
pivot.x = 1;
pivot.y = 1;
break;
case SpritePivotType.Left:
pivot.x = 0;
pivot.y = 0.5;
break;
case SpritePivotType.Right:
pivot.x = 1;
pivot.y = 0.5;
break;
case SpritePivotType.BottomLeft:
pivot.x = 0;
pivot.y = 0;
break;
case SpritePivotType.Bottom:
pivot.x = 0.5;
pivot.y = 0;
break;
case SpritePivotType.BottomRight:
pivot.x = 1;
pivot.y = 0;
break;
default:
break;
}
}
for (let k in configProps) {
if (!isAsset(configProps[k]) && typeof configProps[k] !== "undefined") {
assetObj[k] = configProps[k];
}
}
this._resource = assetObj;
this.setMeta();
resolve(this);
});
}
loadWithAttachedResources(
resourceManager: ResourceManager,
assetConfig: AssetConfig
): Promise<LoadAttachedResourceResult> {
return new Promise((resolve, reject) => {
let loadPromise;
if (assetConfig.resource instanceof SpriteResource) {
loadPromise = new Promise((resolve) => {
this._resource = assetConfig.resource;
this.setMeta();
resolve(this);
});
} else if (assetConfig.props) {
loadPromise = this.load(resourceManager, assetConfig);
} else {
reject("Load Sprite Error");
}
if (loadPromise) {
loadPromise.then(() => {
const result: any = {
resources: [this],
structure: {
index: 0,
props: {}
}
};
const sprite = this._resource;
getAllGetters(this._resource).forEach((attr) => {
if (!(sprite[attr] instanceof Texture)) return;
const textureResource = new TextureResource(this.resourceManager, sprite[attr]);
this.attachedResources.push(textureResource);
result.resources.push(textureResource);
result.structure.props[attr] = {
index: result.resources.length - 1
};
});
resolve(result);
});
}
});
}
setMeta() {
if (this.resource) {
this.meta.name = this.resource.name;
}
}
getProps() {
const result = {};
const props = getAllGetters(this.resource);
props.forEach((prop) => (result[prop] = this.resource[prop]));
return result;
}
bind() {
const resource = this._resource;
this.configProps &&
Object.keys(this.configProps).forEach((attr) => {
const value = this.configProps[attr];
if (isAsset(value)) {
const textureResource = this.resourceManager.get(value.id);
if (textureResource && textureResource instanceof TextureResource) {
resource[attr] = textureResource.resource;
this._attachedResources.push(textureResource);
} else {
resource[attr] = null;
Logger.warn(`SpriteResource: ${this.meta.name} can't find asset "${attr}", which id is: ${value.id}`);
}
} else {
resource[attr] = value;
}
});
}
}
export enum SpritePivotType {
Center = 0,
TopLeft = 1,
Top = 2,
TopRight = 3,
Left = 4,
Right = 5,
BottomLeft = 6,
Bottom = 7,
BottomRight = 8,
Custom = 9
}

View File

@@ -1,68 +0,0 @@
import { AssetType, GLCapabilityType, ResourceManager } from "@oasis-engine/core";
import { Oasis } from "../Oasis";
import { AssetConfig } from "../types";
import { SchemaResource } from "./SchemaResource";
const imageOrderMap = {
px: 0,
nx: 1,
py: 2,
ny: 3,
pz: 4,
nz: 5
};
export class TextureCubeMapResource extends SchemaResource {
load(resourceManager: ResourceManager, assetConfig: AssetConfig, oasis: Oasis): Promise<TextureCubeMapResource> {
return new Promise((resolve, reject) => {
const imageUrls = [];
let type = AssetType.TextureCube;
if (this.resourceManager.useCompressedTexture && assetConfig?.props?.compression?.compressions.length) {
const rhi = oasis.engine._hardwareRenderer;
const compressions = assetConfig.props.compression.compressions;
for (let i = 0; i < compressions.length; i++) {
const compression = compressions[i];
if (compression.container === "ktx" && rhi.canIUse(GLCapabilityType[compression.type])) {
for (const key in compression.files) {
if (compression.files.hasOwnProperty(key)) {
const image = compression.files[key];
imageUrls[imageOrderMap[key]] = image.url;
}
}
console.warn(compression.type);
type = AssetType.KTXCube;
break;
}
}
}
if (type === AssetType.TextureCube) {
for (const key in assetConfig.props.images) {
if (assetConfig.props.images.hasOwnProperty(key)) {
const image = assetConfig.props.images[key];
imageUrls[imageOrderMap[key]] = image.url;
}
}
}
resourceManager
.load({
urls: imageUrls,
type: type
})
.then((res) => {
this._resource = res;
resolve(this);
})
.catch((e) => {
reject(e);
});
});
}
setMeta() {
if (this.resource) {
this.meta.name = this.resource.name;
}
}
}

View File

@@ -1,43 +0,0 @@
import { AssetType, GLCapabilityType, ResourceManager } from "@oasis-engine/core";
import { Oasis } from "../Oasis";
import { AssetConfig } from "../types";
import { SchemaResource } from "./SchemaResource";
export class TextureResource extends SchemaResource {
load(resourceManager: ResourceManager, assetConfig: AssetConfig, oasis: Oasis): Promise<TextureResource> {
return new Promise((resolve, reject) => {
let url: string;
let assetType = AssetType.Texture2D;
if (this.resourceManager.useCompressedTexture && assetConfig?.props?.compression?.compressions.length) {
const rhi = oasis.engine._hardwareRenderer;
const compressions = assetConfig.props.compression.compressions;
for (let i = 0; i < compressions.length; i++) {
const compression = compressions[i];
if (compression.container === "ktx" && rhi.canIUse(GLCapabilityType[compression.type])) {
url = compression.url;
assetType = AssetType.KTX;
break;
}
}
}
url = url ?? assetConfig.url;
resourceManager
.load({ url, type: assetType })
.then((res) => {
this._resource = res;
resolve(this);
})
.catch((e) => {
reject(e);
});
});
}
setMeta() {
if (this.resource) {
this._meta.name = this.resource.name;
}
}
}

View File

@@ -1,100 +0,0 @@
import { Logger, UnlitMaterial, ResourceManager, Texture } from "@oasis-engine/core";
import { AssetConfig, LoadAttachedResourceResult } from "../types";
import { getAllGetters, isAsset } from "../utils";
import { SchemaResource } from "./SchemaResource";
import { TextureResource } from "./TextureResource";
export class UnlitMaterialResource extends SchemaResource {
private configProps;
load(resourceManager: ResourceManager, assetConfig: AssetConfig): Promise<UnlitMaterialResource> {
return new Promise((resolve) => {
const assetObj = new UnlitMaterial(resourceManager.engine);
this.configProps = assetConfig.props;
for (let k in this.configProps) {
if (!isAsset(this.configProps[k])) {
assetObj[k] = this.configProps[k];
}
}
this._resource = assetObj;
this.setMeta();
resolve(this);
});
}
loadWithAttachedResources(
resourceManager: ResourceManager,
assetConfig: AssetConfig
): Promise<LoadAttachedResourceResult> {
return new Promise((resolve, reject) => {
let loadPromise;
if (assetConfig.resource instanceof UnlitMaterial) {
loadPromise = new Promise((resolve) => {
this._resource = assetConfig.resource;
this.setMeta();
resolve(this);
});
} else if (assetConfig.props) {
loadPromise = this.load(resourceManager, assetConfig);
} else {
reject("Load PBRMaterial Error");
}
if (loadPromise) {
loadPromise.then(() => {
const result: any = {
resources: [this],
structure: {
index: 0,
props: {}
}
};
const material = this._resource;
getAllGetters(this._resource).forEach((attr) => {
if (!(material[attr] instanceof Texture)) return;
const textureResource = new TextureResource(this.resourceManager, material[attr]);
this.attachedResources.push(textureResource);
result.resources.push(textureResource);
result.structure.props[attr] = {
index: result.resources.length - 1
};
});
resolve(result);
});
}
});
}
setMeta() {
if (this.resource) {
this.meta.name = this.resource.name;
}
}
getProps() {
const result = {};
const props = getAllGetters(this.resource);
props.forEach((prop) => (result[prop] = this.resource[prop]));
return result;
}
bind() {
const resource = this._resource;
Object.keys(this.configProps).forEach((attr) => {
const value = this.configProps[attr];
if (isAsset(value)) {
const textureResource = this.resourceManager.get(value.id);
if (textureResource && textureResource instanceof TextureResource) {
resource[attr] = textureResource.resource;
this._attachedResources.push(textureResource);
} else {
resource[attr] = null;
Logger.warn(`PBRMaterialResource: ${this.meta.name} can't find asset "${attr}", which id is: ${value.id}`);
}
} else {
resource[attr] = value;
}
});
}
}

View File

@@ -1,15 +0,0 @@
export { SchemaResource } from "./SchemaResource";
export { GLTFResource } from "./GLTFResource";
export { PBRMaterialResource } from "./PBRMaterialResource";
export { PBRSpecularMaterialResource } from "./PBRSpecularMaterialResource";
export { UnlitMaterialResource } from "./UnlitMaterialResource";
export { TextureResource } from "./TextureResource";
export { ScriptResource } from "./ScriptResource";
export { SpriteResource, SpritePivotType } from "./SpriteResource";
export { BlinnPhongMaterialResource } from "./BlinnPhongMaterialResource";
export { TextureCubeMapResource } from "./TextureCubeMapResource";
export * from "./ScriptResource";
export { BaseResource } from "./BaseResource";
export { AnimatorControllerResource } from "./AnimatorControllerResource";
export { AnimationClipResource } from "./AnimationClipResource";
export { AmbientLightResource } from "./AmbientLightResource";

View File

@@ -1,94 +0,0 @@
import { Color, Vector2, Vector3, Vector4 } from "@oasis-engine/math";
/**
* temp compa
* @param config
*/
export function compatibleToV2(config) {
const { abilities = {}, assets = {}, scene = {} } = config;
const ids = Object.keys(abilities);
const assetKeys = Object.keys(assets);
const sceneKeys = Object.keys(scene || {});
for (let i = 0, l = ids.length; i < l; ++i) {
handleComponents(abilities[ids[i]].props);
}
for (let i = 0, l = assetKeys.length; i < l; ++i) {
handleAssets(assets[assetKeys[i]].props);
}
for (let i = 0, l = sceneKeys.length; i < l; ++i) {
handleSceneProps(scene[sceneKeys[i]].props);
}
return config;
}
// TODO temp
function handleComponents(props) {
const keys = Object.keys(props);
for (let i = 0, l = keys.length; i < l; ++i) {
const k = keys[i];
const v = props[k];
if (Array.isArray(v) && typeof v[0] !== "object") {
if (["color", "diffuseColor", "specularColor"].indexOf(k) !== -1) {
props[k] = new Color(v[0], v[1], v[2], v[3]);
} else if (v.length === 4) {
props[k] = new Vector4(v[0], v[1], v[2], v[3]);
} else if (v.length === 3) {
props[k] = new Vector3(v[0], v[1], v[2]);
} else if (v.length === 2) {
props[k] = new Vector2(v[0], v[1]);
}
}
}
}
function handleSceneProps(props) {
const keys = Object.keys(props);
for (let i = 0, l = keys.length; i < l; ++i) {
const k = keys[i];
const v = props[k];
if (Array.isArray(v) && typeof v[0] !== "object") {
if (/color/i.test(k)) {
props[k] = new Color(v[0], v[1], v[2], v[3]);
} else if (v.length === 4) {
props[k] = new Vector4(v[0], v[1], v[2], v[3]);
} else if (v.length === 3) {
props[k] = new Vector3(v[0], v[1], v[2]);
} else if (v.length === 2) {
props[k] = new Vector2(v[0], v[1]);
}
}
}
}
function handleAssets(props: any = {}) {
if (!props) {
return;
}
const keys = Object.keys(props);
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i];
const value = props[key];
if (key === "newMaterial" || key === "scripts") {
continue;
}
if (Array.isArray(value) && typeof value[0] !== "object") {
if (["emissiveColor", "diffuseColor", "specularColor", "baseColor"].indexOf(key) !== -1) {
props[key] = new Color(value[0], value[1], value[2], value[3]);
} else if (value.length === 4) {
props[key] = new Vector4(value[0], value[1], value[2], value[3]);
} else if (value.length === 3) {
props[key] = new Vector3(value[0], value[1], value[2]);
} else if (value.length === 2) {
props[key] = new Vector2(value[0], value[1]);
}
}
}
}

View File

@@ -1,164 +0,0 @@
import { Engine } from "@oasis-engine/core";
import { SchemaResource } from "./resources";
export interface NodeConfig {
/**
* Node id
*/
id: string;
/**
* Index of parent's children
*/
index: number;
/**
* Name of node
*/
name: string;
/**
* Position of node
*/
position: [number, number, number];
/**
* Euler rotation of node
*/
rotation: [number, number, number];
/**
* Scale of node
*/
scale: [number, number, number];
/**
* Parent node
*/
parent: string | undefined;
/**
* Children of node
*/
children: string[];
/**
* Components of node
*/
abilities?: string[];
/**
* Is active of
*/
isActive?: boolean;
}
export interface Props {
[key: string]: any | AssetProp;
}
export interface AssetProp {
type: "asset";
/**
* asset id
*/
id: string;
}
export interface ComponentConfig {
/**
* ComponentId id
*/
id: string;
/**
* Node id of component
*/
node: string;
/**
* Ability type
*/
type: string;
/**
* Ability props
*/
props: Props;
/**
* Index of components
*/
index: number;
}
// todo
export interface AssetConfig {
/**
* Asset id
*/
id?: string;
/**
* Asset name
*/
name: string;
/**
* Asset type
*/
type: string;
/**
* Asset props
*/
props?: any;
/**
* Asset url
*/
url?: string;
/**
* Asset source
*/
source?: string;
/**
* Asset resource
*/
resource?: any;
}
export interface Schema {
nodes: {
[nodeId: string]: NodeConfig;
};
abilities: {
[abiliId: string]: ComponentConfig;
};
assets: {
[assetId: string]: AssetConfig;
};
version: number;
[name: string]: any;
}
export interface ClassType<T> extends Function {
new (...args: any[]): T;
}
export interface Options {
/** Engine of scene */
engine: Engine;
/** HTMLCanvasElement */
canvas?: HTMLCanvasElement;
/** Scene data */
config?: Schema;
/** Call engine.run() automatically */
autoPlay?: boolean;
onProgress?: () => {};
local?: boolean;
scripts?: { [name: string]: any };
/** Attributes of GLContext */
rhiAttr?: WebGLContextAttributes & { enableCollect?: boolean };
/** Timeout of loading */
timeout?: number;
/** Fps of engine run */
fps?: number;
/** Whether to use compress texture */
useCompressedTexture?: boolean;
/** Engine */
// engine?: Engine;
}
export interface LoadAttachedResourceResult {
resources: Array<SchemaResource>;
structure: LoadAttachedResourceResultStructure;
}
interface LoadAttachedResourceResultStructure {
index: number;
props?: {
[propName: string]: LoadAttachedResourceResultStructure | Array<LoadAttachedResourceResultStructure>;
};
}

View File

@@ -1,40 +0,0 @@
export function switchElementsIndex(elements: any[], currentIndex: number, targetIndex: number) {
if (currentIndex === targetIndex || targetIndex === null || targetIndex === undefined) {
return;
}
[elements[currentIndex], elements[targetIndex]] = [elements[targetIndex], elements[currentIndex]];
}
export function isAsset(config: any): boolean {
return config && config.type === "asset";
}
export function getAllGetters(obj: any): Array<string> {
const result = [];
const prototype = Object.getPrototypeOf(obj);
const prototype_property_descriptors = Object.getOwnPropertyDescriptors(prototype);
for (const [property, descriptor] of Object.entries(prototype_property_descriptors)) {
if (typeof descriptor.get === "function") {
result.push(property);
}
}
return result;
}
export function union(arr1: Array<any>, arr2: Array<any>): Array<any> {
return arr1.concat(arr2.filter((v) => !(arr1.indexOf(v) > -1)));
}
// https://github.com/BabylonJS/Babylon.js/blob/d780145531ac1b1cee85cbfba4d836dcc24ab58e/src/Engines/Extensions/engine.textureSelector.ts#L70
// Intelligently add supported compressed formats in order to check for.
// Check for ASTC support first as it is most powerful and to be very cross platform.
// Next PVRTC & DXT, which are probably superior to ETC1/2.
// Likely no hardware which supports both PVR & DXT, so order matters little.
// ETC2 is newer and handles ETC1 (no alpha capability), so check for first.
export const compressedTextureLoadOrder = {
astc: 1,
s3tc: 2,
pvrtc: 3,
etc: 4,
etc1: 5
};

View File

@@ -1,41 +1,16 @@
export * from "@oasis-engine/core";
export * from "@oasis-engine/loader";
export * from "@oasis-engine/math";
export * from "@oasis-engine/rhi-webgl";
import {
AmbientLight,
Camera,
Component,
DirectLight,
ParticleRenderer,
PointLight,
SpriteRenderer,
SpriteMask,
TextRenderer,
Animator,
StaticCollider,
DynamicCollider
} from "@oasis-engine/core";
import { GLTFModel, Parser, Model } from "@oasis-engine/loader";
Parser.registerComponents("o3", {
GLTFModel,
SpriteRenderer,
SpriteMask,
TextRenderer,
PointLight,
AmbientLight,
DirectLight,
ParticleRenderer,
Camera,
Model,
Component,
StaticCollider,
DynamicCollider,
Animator
});
import * as CoreObjects from "@oasis-engine/core";
import { Loader } from "@oasis-engine/core";
//@ts-ignore
export const version = `__buildVersion`;
console.log(`oasis engine version: ${version}`);
export * from "@oasis-engine/core";
export * from "@oasis-engine/loader";
export * from "@oasis-engine/math";
export * from "@oasis-engine/rhi-webgl";
for (let key in CoreObjects) {
Loader.registerClass(key, CoreObjects[key]);
}

View File

@@ -0,0 +1,21 @@
{
"name": "@oasis-engine/resource-process",
"license": "MIT",
"version": "0.8.0-alpha.5",
"scripts": {
"b:types": "tsc"
},
"types": "types/index.d.ts",
"debug": "src/index.ts",
"main": "dist/browser.js",
"module": "dist/module.js",
"browser": "dist/browser.js",
"files": [
"dist/**/*",
"types/**/*"
],
"dependencies": {
"@oasis-engine/core": "0.8.0-alpha.5",
"@oasis-engine/math": "0.8.0-alpha.5"
}
}

View File

@@ -0,0 +1,32 @@
import { Engine } from "@oasis-engine/core";
import { BufferReader } from "./utils/BufferReader";
import { decoderMap, decoder } from "./utils/Decorator";
import { FileHeader } from "./utils/FileHeader";
export { MeshDecoder } from "./resources/mesh/MeshDecoder";
export { Texture2DDecoder } from "./resources/texture2D/TextureDecoder";
export { ReflectionParser } from "./resources/prefab/ReflectionParser";
export { PrefabParser } from "./resources/prefab/PrefabParser";
export type { IModelMesh } from "./resources/mesh/IModelMesh";
export type { IAnimationClipAsset } from "./resources/animationClip/type";
export type { IAnimatorControllerAsset } from "./resources/animatorController/type";
/**
* Decode engine binary resource.
* @param arrayBuffer - array buffer of decode binary file
* @param engine - engine
* @returns
*/
export function decode<T>(arrayBuffer: ArrayBuffer, engine: Engine): Promise<T> {
const header = FileHeader.decode(arrayBuffer);
const bufferReader = new BufferReader(arrayBuffer, header.headerLength, header.dataLength);
return decoderMap[header.type].decode(engine, bufferReader).then((object) => {
object.name = header.name;
return object;
});
}
export * from "./resources/prefab/PrefabDesign";
export * from "./resources/scene/SceneParser";
export * from "./resources/scene/MeshLoader";
export * from "./resources/scene/EditorTextureLoader";

View File

@@ -0,0 +1,162 @@
import { Quaternion } from "@oasis-engine/math";
import {
AnimationClip,
AnimationCurve,
AnimationEvent,
Component,
Engine,
Entity,
InterpolableKeyframe,
InterpolableValueType,
SkinnedMeshRenderer,
Transform
} from "@oasis-engine/core";
import { Vector2, Vector3, Vector4 } from "@oasis-engine/math";
import type { BufferReader } from "../../utils/BufferReader";
import { decoder } from "../../utils/Decorator";
import { ComponentClass, PropertyNameMap } from "./type";
@decoder("AnimationClip")
export class AnimationClipDecoder {
public static decode(engine: Engine, bufferReader: BufferReader): Promise<AnimationClip> {
return new Promise((resolve) => {
const objectId = bufferReader.nextStr();
const name = bufferReader.nextStr();
const clip = new AnimationClip(name);
const eventsLen = bufferReader.nextUint16();
for (let i = 0; i < eventsLen; ++i) {
const event = new AnimationEvent();
event.time = bufferReader.nextFloat32();
event.functionName = bufferReader.nextStr();
event.parameter = JSON.parse(bufferReader.nextStr()).val;
clip.addEvent(event);
}
const curveBindingsLen = bufferReader.nextUint16();
for (let i = 0; i < curveBindingsLen; ++i) {
const relativePath = bufferReader.nextStr();
const componentClass: ComponentClass = bufferReader.nextUint8();
let compType: new (entity: Entity) => Component;
switch (componentClass) {
case ComponentClass.Transform:
compType = Transform;
break;
case ComponentClass.SkinnedMeshRenderer:
compType = SkinnedMeshRenderer;
break;
}
const property = bufferReader.nextUint8();
const curve = new AnimationCurve();
curve.interpolation = bufferReader.nextUint8();
const keysLen = bufferReader.nextUint16();
for (let j = 0; j < keysLen; ++j) {
const type = bufferReader.nextUint8();
switch (type) {
case InterpolableValueType.Float: {
const keyframe = new InterpolableKeyframe<number, number>();
keyframe.time = bufferReader.nextFloat32();
keyframe.value = bufferReader.nextFloat32();
keyframe.inTangent = bufferReader.nextFloat32();
keyframe.outTangent = bufferReader.nextFloat32();
curve.addKey(keyframe);
break;
}
case InterpolableValueType.FloatArray: {
const keyframe = new InterpolableKeyframe<Float32Array, Float32Array>();
keyframe.time = bufferReader.nextFloat32();
const len = bufferReader.nextUint16();
keyframe.value = bufferReader.nextFloat32Array(len);
keyframe.inTangent = bufferReader.nextFloat32Array(len);
keyframe.outTangent = bufferReader.nextFloat32Array(len);
curve.addKey(keyframe);
break;
}
case InterpolableValueType.Vector2: {
const keyframe = new InterpolableKeyframe<Vector2, Vector2>();
keyframe.time = bufferReader.nextFloat32();
keyframe.value = new Vector2(bufferReader.nextFloat32(), bufferReader.nextFloat32());
keyframe.inTangent = new Vector2(bufferReader.nextFloat32(), bufferReader.nextFloat32());
keyframe.outTangent = new Vector2(bufferReader.nextFloat32(), bufferReader.nextFloat32());
curve.addKey(keyframe);
break;
}
case InterpolableValueType.Vector3: {
const keyframe = new InterpolableKeyframe<Vector3, Vector3>();
keyframe.time = bufferReader.nextFloat32();
keyframe.value = new Vector3(
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32()
);
keyframe.inTangent = new Vector3(
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32()
);
keyframe.outTangent = new Vector3(
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32()
);
curve.addKey(keyframe);
break;
}
case InterpolableValueType.Vector4: {
const keyframe = new InterpolableKeyframe<Vector4, Vector4>();
keyframe.time = bufferReader.nextFloat32();
keyframe.value = new Vector4(
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32()
);
keyframe.inTangent = new Vector4(
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32()
);
keyframe.outTangent = new Vector4(
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32()
);
curve.addKey(keyframe);
break;
}
case InterpolableValueType.Quaternion: {
const keyframe = new InterpolableKeyframe<Vector4, Quaternion>();
keyframe.time = bufferReader.nextFloat32();
keyframe.value = new Quaternion(
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32()
);
keyframe.inTangent = new Vector4(
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32()
);
keyframe.outTangent = new Vector4(
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32(),
bufferReader.nextFloat32()
);
curve.addKey(keyframe);
break;
}
}
}
clip.addCurveBinding(relativePath, compType, PropertyNameMap[property], curve);
}
// @ts-ignore
engine.resourceManager._objectPool[objectId] = clip;
resolve(clip);
});
}
}

View File

@@ -0,0 +1,33 @@
import { AnimationProperty, InterpolableValueType } from "@oasis-engine/core";
export enum ComponentClass {
Transform,
SkinnedMeshRenderer,
Other
}
export const PropertyNameMap = ['position', 'rotation', 'scale', 'blendShapeWeights'];
export interface IAnimationClipAsset {
objectId: string;
name: string;
events: Array<{
time: number;
functionName: string;
parameter: string;
}>;
curveBindings: Array<{
relativePath: string;
property: AnimationProperty;
curve: {
interpolation: number;
keys: Array<{
time: number;
value: any; // 详细查看 KeyframeValueType的映射
inTangent: any; // 详细查看 KeyframeValueType的映射
outTangent: any; // 详细查看 KeyframeValueType的映射
}>;
valueType: InterpolableValueType;
};
}>;
}

View File

@@ -0,0 +1,89 @@
import {
Engine,
AnimatorController,
AnimatorControllerLayer,
AnimatorStateMachine,
AnimationClip,
AnimatorStateTransition,
AssetType
} from "@oasis-engine/core";
import type { BufferReader } from "../../utils/BufferReader";
import { decoder } from "../../utils/Decorator";
@decoder("AnimatorController")
export class AnimatorControllerDecoder {
public static decode(engine: Engine, bufferReader: BufferReader): Promise<AnimatorController> {
return new Promise(async (resolve) => {
const animatorController = new AnimatorController();
const objectId = bufferReader.nextStr();
const layersLen = bufferReader.nextUint16();
const clipLoadPromises = [];
for (let i = 0; i < layersLen; ++i) {
const name = bufferReader.nextStr();
const layer = new AnimatorControllerLayer(name);
layer.blendingMode = bufferReader.nextUint8();
layer.weight = bufferReader.nextFloat32();
const stateMachine = new AnimatorStateMachine();
const statesLen = bufferReader.nextUint16();
for (let j = 0; j < statesLen; ++j) {
const stateName = bufferReader.nextStr();
const state = stateMachine.addState(stateName);
state.speed = bufferReader.nextFloat32();
state.wrapMode = bufferReader.nextUint8();
const isDefaultState = bufferReader.nextUint8() ? true : false;
const clipStartNormalizedTime = bufferReader.nextFloat32();
const clipEndNormalizedTime = bufferReader.nextFloat32();
const clipPath = bufferReader.nextStr();
const clipObjectId = bufferReader.nextStr();
clipLoadPromises.push(
AnimatorControllerDecoder.loadAndSetClip(engine, clipPath, clipObjectId).then((clip) => {
state.clip = clip;
state.clipStartTime = clip.length * clipStartNormalizedTime;
state.clipEndTime = clip.length * clipEndNormalizedTime;
})
);
// @ts-ignore
isDefaultState && (stateMachine._defaultState = state);
const transitionsLen = bufferReader.nextUint16();
for (let k = 0; k < transitionsLen; ++k) {
const transition = new AnimatorStateTransition();
transition.duration = bufferReader.nextFloat32();
transition.offset = bufferReader.nextFloat32();
transition.exitTime = bufferReader.nextFloat32();
transition.exitTime = bufferReader.nextFloat32();
transition.destinationState = stateMachine.findStateByName(bufferReader.nextStr());
state.addTransition(transition);
}
}
layer.stateMachine = stateMachine;
animatorController.addLayer(layer);
}
Promise.all(clipLoadPromises).then(() => {
// @ts-ignore
engine.resourceManager._objectPool[objectId] = animatorController;
resolve(animatorController);
});
});
}
public static loadAndSetClip(engine: Engine, path: string, objectId: string): Promise<AnimationClip> {
// @ts-ignore
return Promise.resolve(engine.resourceManager._objectPool[objectId]);
// return new Promise((resolve) => {
// engine.resourceManager
// .load({
// url: path,
// // @ts-ignore
// type: AssetType.Oasis
// })
// .then(() => {
// // 从缓存池获取对象
// // @ts-ignore
// resolve(engine.resourceManager._objectPool[objectId]);
// });
// });
}
}

View File

@@ -0,0 +1,27 @@
import { AnimatorState, AnimatorLayerBlendingMode, WrapMode } from "@oasis-engine/core";
export interface IAnimatorControllerAsset {
objectId: string;
layers: Array<{
name: string;
blending: AnimatorLayerBlendingMode;
weight: number;
stateMachine: {
states: Array<{
name: string;
speed: number;
wrapMode: WrapMode;
isDefaultState: boolean;
clipStartNormalizedTime: number;
clipEndNormalizedTime: number;
clip: { path: string; objectId: string };
transitions: Array<{
duration: number;
offset: number;
exitTime: number;
targetStateName: string;
}>;
}>;
};
}>;
}

View File

@@ -0,0 +1,141 @@
import { MeshTopology } from "@oasis-engine/core";
export interface IVector3 {
x: number;
y: number;
z: number;
}
export interface IVector2 {
x: number;
y: number;
}
export interface IVector4 {
x: number;
y: number;
z: number;
w: number;
}
export interface IColor {
r: number;
g: number;
b: number;
a: number;
}
export interface IEncodedModelMesh {
positions: {
start: number;
end: number;
};
normals?: {
start: number;
end: number;
};
uvs?: {
start: number;
end: number;
};
uv1?: {
start: number;
end: number;
};
uv2?: {
start: number;
end: number;
};
uv3?: {
start: number;
end: number;
};
uv4?: {
start: number;
end: number;
};
uv5?: {
start: number;
end: number;
};
uv6?: {
start: number;
end: number;
};
uv7?: {
start: number;
end: number;
};
colors?: {
start: number;
end: number;
};
tangents?: {
start: number;
end: number;
};
boneWeights?: {
start: number;
end: number;
};
boneIndices?: {
start: number;
end: number;
};
blendShapes?: {
name: string;
frames: {
weight: number;
deltaPosition: { start: number; end: number };
deltaNormals?: {
start: number;
end: number;
};
deltaTangents?: {
start: number;
end: number;
};
}[];
}[];
indices?: {
type: number;
start: number;
end: number;
};
subMeshes: {
start: number;
topology: MeshTopology;
count: number;
}[];
}
export interface IModelMesh {
positions: IVector3[];
normals?: IVector3[];
uvs?: IVector2[];
uv1?: IVector2[];
uv2?: IVector2[];
uv3?: IVector2[];
uv4?: IVector2[];
uv5?: IVector2[];
uv6?: IVector2[];
uv7?: IVector2[];
colors?: IColor[];
tangents?: IVector4[];
boneWeights?: IVector4[];
boneIndices?: IVector4[];
blendShapes?: {
name: string;
frames: {
weight: number;
deltaPositions: IVector3[];
deltaNormals: IVector3[];
deltaTangents: IVector4[];
}[];
}[];
indices?: number[];
subMeshes: {
start: number;
topology: MeshTopology;
count: number;
}[];
}

View File

@@ -0,0 +1,225 @@
import {
ModelMesh,
BlendShape} from "@oasis-engine/core";
import { decoder } from "../../utils/Decorator";
import type { Engine } from "@oasis-engine/core";
import type { BufferReader } from "../../utils/BufferReader";
import { IEncodedModelMesh } from "./IModelMesh";
import { Color, Vector2, Vector3, Vector4 } from "@oasis-engine/math";
@decoder("Mesh")
export class MeshDecoder {
public static decode(engine: Engine, bufferReader: BufferReader): Promise<ModelMesh> {
return new Promise((resolve) => {
const modelMesh = new ModelMesh(engine);
const jsonDataString = bufferReader.nextStr();
const encodedMeshData: IEncodedModelMesh = JSON.parse(jsonDataString);
const offset = Math.ceil(bufferReader.offset / 4) * 4;
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.positions.start + offset,
(encodedMeshData.positions.end - encodedMeshData.positions.start) / 4
);
const vertexCount = float32Array.length / 3;
const positions = float32ArrayToVector3(float32Array, vertexCount);
modelMesh.setPositions(positions);
if (encodedMeshData.normals) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.normals.start + offset,
(encodedMeshData.normals.end - encodedMeshData.normals.start) / 4
);
const normals = float32ArrayToVector3(float32Array, vertexCount);
modelMesh.setNormals(normals);
}
if (encodedMeshData.uvs) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.uvs.start + offset,
(encodedMeshData.uvs.end - encodedMeshData.uvs.start) / 4
);
modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount));
}
if (encodedMeshData.uv1) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.uv1.start + offset,
(encodedMeshData.uv1.end - encodedMeshData.uv1.start) / 4
);
modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 1);
}
if (encodedMeshData.uv2) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.uv2.start + offset,
(encodedMeshData.uv2.end - encodedMeshData.uv2.start) / 4
);
modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 2);
}
if (encodedMeshData.uv3) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.uv3.start + offset,
(encodedMeshData.uv3.end - encodedMeshData.uv3.start) / 4
);
modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 3);
}
if (encodedMeshData.uv4) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.uv4.start + offset,
(encodedMeshData.uv4.end - encodedMeshData.uv4.start) / 4
);
modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 4);
}
if (encodedMeshData.uv5) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.uv5.start + offset,
(encodedMeshData.uv5.end - encodedMeshData.uv5.start) / 4
);
modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 5);
}
if (encodedMeshData.uv6) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.uv6.start + offset,
(encodedMeshData.uv6.end - encodedMeshData.uv6.start) / 4
);
modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 6);
}
if (encodedMeshData.uv7) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.uv7.start + offset,
(encodedMeshData.uv7.end - encodedMeshData.uv7.start) / 4
);
modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 7);
}
if (encodedMeshData.colors) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.colors.start + offset,
(encodedMeshData.colors.end - encodedMeshData.colors.start) / 4
);
modelMesh.setColors(float32ArrayToVColor(float32Array, vertexCount));
}
if (encodedMeshData.boneWeights) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.boneWeights.start + offset,
(encodedMeshData.boneWeights.end - encodedMeshData.boneWeights.start) / 4
);
modelMesh.setBoneWeights(float32ArrayToVector4(float32Array, vertexCount));
}
if (encodedMeshData.boneIndices) {
const float32Array = new Float32Array(
bufferReader.buffer,
encodedMeshData.boneIndices.start + offset,
(encodedMeshData.boneIndices.end - encodedMeshData.boneIndices.start) / 4
);
modelMesh.setBoneIndices(float32ArrayToVector4(float32Array, vertexCount));
}
if (encodedMeshData.blendShapes) {
encodedMeshData.blendShapes.forEach((blendShapeData) => {
const blendShape = new BlendShape(blendShapeData.name);
blendShapeData.frames.forEach((frameData) => {
const positionArray = new Float32Array(
bufferReader.buffer,
frameData.deltaPosition.start + offset,
(frameData.deltaPosition.end - frameData.deltaPosition.start) / 4
);
const count = positionArray.length / 3;
const deltaPosition = float32ArrayToVector3(positionArray, count);
let deltaNormals: Vector3[] | null = null;
if (frameData.deltaNormals) {
const normalsArray = new Float32Array(
bufferReader.buffer,
frameData.deltaNormals.start + offset,
(frameData.deltaNormals.end - frameData.deltaNormals.start) / 4
);
deltaNormals = float32ArrayToVector3(normalsArray, count);
}
let deltaTangents: Vector4[] | null = null;
if (frameData.deltaTangents) {
const tangentsArray = new Float32Array(
bufferReader.buffer,
frameData.deltaTangents.start + offset,
(frameData.deltaTangents.end - frameData.deltaTangents.start) / 4
);
deltaTangents = float32ArrayToVector4(tangentsArray, count);
}
blendShape.addFrame(frameData.weight, deltaPosition);
});
modelMesh.addBlendShape(blendShape);
});
}
if (encodedMeshData.indices) {
let indices: Uint16Array | Uint32Array = null;
if (encodedMeshData.indices.type === 0) {
indices = new Uint16Array(
bufferReader.buffer,
encodedMeshData.indices.start + offset,
(encodedMeshData.indices.end - encodedMeshData.indices.start) / 2
);
} else {
indices = new Uint32Array(
bufferReader.buffer,
encodedMeshData.indices.start + offset,
(encodedMeshData.indices.end - encodedMeshData.indices.start) / 4
);
}
modelMesh.setIndices(indices);
}
encodedMeshData.subMeshes.forEach((subMesh) => {
modelMesh.addSubMesh(subMesh);
});
modelMesh.uploadData(false);
resolve(modelMesh);
});
}
}
function float32ArrayToVColor(float32Array: Float32Array, vertexCount: number) {
const array = new Array(vertexCount);
for (let i = 0; i < vertexCount; i++) {
array[i] = new Color(
float32Array[i * 4],
float32Array[i * 4 + 1],
float32Array[i * 4 + 2],
float32Array[i * 4 + 3]
);
}
return array;
}
function float32ArrayToVector4(float32Array: Float32Array, vertexCount: number) {
const array = new Array(vertexCount);
for (let i = 0; i < vertexCount; i++) {
array[i] = new Vector4(
float32Array[i * 4],
float32Array[i * 4 + 1],
float32Array[i * 4 + 2],
float32Array[i * 4 + 3]
);
}
return array;
}
function float32ArrayToVector3(float32Array: Float32Array, vertexCount: number) {
const array = new Array(vertexCount);
for (let i = 0; i < vertexCount; i++) {
array[i] = new Vector3(float32Array[i * 3], float32Array[i * 3 + 1], float32Array[i * 3 + 2]);
}
return array;
}
function float32ArrayToVector2(float32Array: Float32Array, vertexCount: number) {
const array = new Array(vertexCount);
for (let i = 0; i < vertexCount; i++) {
array[i] = new Vector2(float32Array[i * 2], float32Array[i * 2 + 1]);
}
return array;
}

View File

@@ -0,0 +1,31 @@
type uint8 = number;
type uint16 = number;
type uint32 = number;
export interface IMeshData {
objectId: string;
name: string;
vertexElements: Array<{
semantic: string;
offset: uint32;
format: uint8;
bindingIndex: uint8;
instanceStepRate: uint8;
}>;
subMeshes: Array<{
start: uint32;
count: uint32;
topology: uint8;
}>;
vertexBuffer: {
bufferUsage: uint8;
buffer: ArrayBuffer;
stride: uint16;
};
hasIndexBuffer: boolean;
indexBuffer?: {
bufferUsage: uint8;
buffer: ArrayBuffer;
format: uint8;
};
}

View File

@@ -0,0 +1,44 @@
export interface IPrefabFile {
entities: Array<IEntity>;
}
export interface IScene extends IPrefabFile {}
export interface IVector3 {
x: number;
y: number;
z: number;
}
export interface IBasicEntity {
name?: string;
id?: string;
components?: Array<IComponent>;
isActive?: boolean;
position?: IVector3;
rotation?: IVector3;
scale?: IVector3;
children?: Array<string>;
parent?: string;
}
export type IEntity = IBasicEntity | IRefEntity;
export interface IRefEntity extends IBasicEntity {
assetRefId: string;
key?: string;
}
export type IComponent = { id: string; refId?: string } & IClassObject;
export type IMethodParams = Array<IBasicType>;
export type IClassObject = {
class: string;
constructParams?: IMethodParams;
methods?: { [methodName: string]: Array<IMethodParams> };
props?: { [key: string]: IBasicType | IMethodParams };
};
export type IBasicType = string | number | boolean | null | undefined | IReferenceType | IClassObject | IMethodParams;
export type IReferenceType = { key?: string; refId: string };

View File

@@ -0,0 +1,44 @@
import { Engine, Entity } from "@oasis-engine/core";
import type { IEntity, IPrefabFile } from "./PrefabDesign";
import { ReflectionParser } from "./ReflectionParser";
export class PrefabParser {
constructor(private _engine: Engine) {}
parse(data: IPrefabFile): Promise<Entity> {
const entitiesMap: Record<string, Entity> = {};
const entitiesConfigMap: Record<string, IEntity> = {};
const promises: Promise<Entity>[] = [];
const entitiesConfig = data.entities;
for (const entity of entitiesConfig) {
entitiesConfigMap[entity.id] = entity;
promises.push(ReflectionParser.parseEntity(entity, this._engine));
}
return Promise.all(promises).then((entities) => {
const rootId = entitiesConfig[0].id;
entities.forEach((entity, index) => {
entitiesMap[entitiesConfig[index].id] = entity;
});
PrefabParser.parseChildren(entitiesConfigMap, entitiesMap, rootId);
return entitiesMap[rootId];
});
}
static parseChildren(
entitiesConfig: { [key: string]: IEntity },
entities: { [key: string]: Entity },
parentId: string
) {
const children = entitiesConfig[parentId].children;
if (children && children.length > 0) {
const parent = entities[parentId];
for (let i = 0; i < children.length; i++) {
const childId = children[i];
const entity = entities[childId];
parent.addChild(entity);
this.parseChildren(entitiesConfig, entities, childId);
}
}
}
}

View File

@@ -0,0 +1,132 @@
import { AssetType, Engine, Entity, Loader } from "@oasis-engine/core";
import { IBasicType, IClassObject, IEntity, IReferenceType } from "./PrefabDesign";
export class ReflectionParser {
static parseEntity(entityConfig: IEntity, engine: Engine): Promise<Entity> {
return ReflectionParser.getEntityByConfig(entityConfig, engine).then((entity) => {
entity.isActive = entityConfig.isActive ?? true;
const { position, rotation, scale } = entityConfig;
if (position) {
entity.transform.setPosition(position.x, position.y, position.z);
}
if (rotation) {
entity.transform.setRotation(rotation.x, rotation.y, rotation.z);
}
if (scale) {
entity.transform.setScale(scale.x, scale.y, scale.z);
}
const promises = [];
for (let i = 0; i < entityConfig.components.length; i++) {
const componentConfig = entityConfig.components[i];
const key = !componentConfig.refId ? componentConfig.class : componentConfig.refId;
const component = entity.addComponent(Loader.getClass(key));
const promise = this.parsePropsAndMethods(component, componentConfig, engine);
promises.push(promise);
}
return Promise.all(promises).then(() => {
return entity;
});
});
}
private static getEntityByConfig(entityConfig: IEntity, engine: Engine): Promise<Entity> {
// @ts-ignore
const assetRefId: string = entityConfig.assetRefId;
if (assetRefId) {
// @ts-ignore
return engine.resourceManager.getResourceByRef<Entity>({ refId: assetRefId, key: entityConfig.key });
} else {
const entity = new Entity(engine, entityConfig.name);
return Promise.resolve(entity);
}
}
static parseClassObject(
item: IClassObject,
engine: Engine,
resourceManager: any = engine.resourceManager
): Promise<any> {
const Class = Loader.getClass(item.class);
const params = item.constructParams ?? [];
const instance = new Class(...params);
return this.parsePropsAndMethods(instance, item, engine, resourceManager);
}
static parseBasicType(
value: IBasicType,
engine: Engine,
resourceManager: any = engine.resourceManager
): Promise<any> {
if (Array.isArray(value)) {
return Promise.all(value.map((item) => this.parseBasicType(item, engine, resourceManager)));
} else if (typeof value === "object") {
if (this._isClass(value)) {
// 类对象
return this.parseClassObject(value, engine, resourceManager);
} else if (this._isRef(value)) {
// 引用对象
return resourceManager.getResourceByRef(value);
} else {
// 基础类型
return Promise.resolve(value);
}
} else {
return Promise.resolve(value);
}
}
static parsePropsAndMethods(
instance: any,
item: Omit<IClassObject, "class">,
engine: Engine,
resourceManager: any = engine.resourceManager
) {
const promises = [];
if (item.methods) {
for (let methodName in item.methods) {
const methodParams = item.methods[methodName];
for (let i = 0, count = methodParams.length; i < count; i++) {
const params = methodParams[i];
const promise = this.parseMethod(instance, methodName, params, engine, resourceManager);
promises.push(promise);
}
}
}
if (item.props) {
for (let key in item.props) {
const value = item.props[key];
const promise = this.parseBasicType(value, engine).then((v) => {
return (instance[key] = v);
});
promises.push(promise);
}
}
return Promise.all(promises).then(() => {
return instance;
});
}
static parseMethod(
instance: any,
methodName: string,
methodParams: Array<IBasicType>,
engine: Engine,
resourceManager: any = engine.resourceManager
) {
return Promise.all(methodParams.map((param) => this.parseBasicType(param, engine, resourceManager))).then(
(result) => {
return instance[methodName](...result);
}
);
}
private static _isClass(value: any): value is IClassObject {
return "class" in value;
}
private static _isRef(value: any): value is IReferenceType {
return "refId" in value;
}
}

View File

@@ -0,0 +1,21 @@
import {
AssetPromise,
Loader,
LoadItem,
resourceLoader,
ResourceManager,
Texture2D} from "@oasis-engine/core";
import { decode } from "../..";
@resourceLoader("EditorTexture2D", ["prefab"], true)
export class EditorTextureLoader extends Loader<Texture2D> {
load(item: LoadItem, resourceManager: ResourceManager): AssetPromise<Texture2D> {
return new AssetPromise((resolve) => {
this.request<ArrayBuffer>(item.url, { type: "arraybuffer" }).then((data) => {
decode<Texture2D>(data, resourceManager.engine).then((texture) => {
resolve(texture);
});
});
});
}
}

View File

@@ -0,0 +1,24 @@
import {
AssetPromise,
Loader,
LoadItem,
Mesh,
ModelMesh,
resourceLoader,
ResourceManager,
UnlitMaterial
} from "@oasis-engine/core";
import { decode } from "../..";
@resourceLoader("Mesh", ["prefab"], true)
export class MeshLoader extends Loader<ModelMesh> {
load(item: LoadItem, resourceManager: ResourceManager): AssetPromise<ModelMesh> {
return new AssetPromise((resolve, reject) => {
this.request<ArrayBuffer>(item.url, { type: "arraybuffer" }).then((data) => {
decode<ModelMesh>(data, resourceManager.engine).then((mesh) => {
resolve(mesh);
});
});
});
}
}

View File

@@ -0,0 +1,36 @@
import { Engine, Entity, Scene } from "@oasis-engine/core";
import { IEntity, IScene } from "../prefab/PrefabDesign";
import { PrefabParser } from "../prefab/PrefabParser";
import { ReflectionParser } from "../prefab/ReflectionParser";
export class SceneParser {
static parse(engine: Engine, sceneData: IScene): Promise<Scene> {
const scene = new Scene(engine);
const entitiesMap: Record<string, Entity> = {};
const entitiesConfigMap: Record<string, IEntity> = {};
const promises: Promise<Entity>[] = [];
const entitiesConfig = sceneData.entities;
for (const entity of entitiesConfig) {
entitiesConfigMap[entity.id] = entity;
promises.push(ReflectionParser.parseEntity(entity, engine));
}
return Promise.all(promises).then((entities) => {
const rootIds = [];
entities.forEach((entity, index) => {
entitiesMap[entitiesConfig[index].id] = entity;
if (!entitiesConfig[index].parent) {
rootIds.push(entitiesConfig[index].id);
}
});
for (const rootId of rootIds) {
PrefabParser.parseChildren(entitiesConfigMap, entitiesMap, rootId);
}
const rootEntities = rootIds.map((id) => entitiesMap[id]);
for (let i = 0; i < rootEntities.length; i++) {
scene.addRootEntity(rootEntities[i]);
}
return scene;
});
}
}

View File

@@ -0,0 +1,72 @@
import { Engine, Texture2D } from "@oasis-engine/core";
import { BufferReader } from "../../utils/BufferReader";
import { decoder } from "../../utils/Decorator";
@decoder("Texture2D")
export class Texture2DDecoder {
static decode(engine: Engine, bufferReader: BufferReader): Promise<Texture2D> {
return new Promise((resolve, reject) => {
const objectId = bufferReader.nextStr();
const mipmap = !!bufferReader.nextUint8();
const filterMode = bufferReader.nextUint8();
const anisoLevel = bufferReader.nextUint8();
const wrapModeU = bufferReader.nextUint8();
const wrapModeV = bufferReader.nextUint8();
const format = bufferReader.nextUint8();
const width = bufferReader.nextUint16();
const height = bufferReader.nextUint16();
const isPixelBuffer = bufferReader.nextUint8();
const mipCount = bufferReader.nextUint8();
const imagesData = bufferReader.nextImagesData(mipCount);
const texture2D = new Texture2D(engine, width, height, format, mipmap);
texture2D.filterMode = filterMode;
texture2D.anisoLevel = anisoLevel;
texture2D.wrapModeU = wrapModeU;
texture2D.wrapModeV = wrapModeV;
if (isPixelBuffer) {
const pixelBuffer = new Uint8Array(imagesData[0]);
texture2D.setPixelBuffer(pixelBuffer);
if (mipmap) {
texture2D.generateMipmaps();
for (let i = 1; i < mipCount; i++) {
const pixelBuffer = new Uint8Array(imagesData[i]);
texture2D.setPixelBuffer(pixelBuffer, i);
}
}
// @ts-ignore
engine.resourceManager._objectPool[objectId] = texture2D;
resolve(texture2D);
} else {
const blob = new window.Blob([imagesData[0]]);
const img = new Image();
img.src = URL.createObjectURL(blob);
img.onload = () => {
texture2D.setImageSource(img);
let completedCount = 0;
const onComplete = () => {
completedCount++;
if (completedCount >= mipCount) {
resolve(texture2D);
}
};
onComplete();
if (mipmap) {
texture2D.generateMipmaps();
for (let i = 1; i < mipCount; i++) {
const blob = new window.Blob([imagesData[i]]);
const img = new Image();
img.src = URL.createObjectURL(blob);
img.onload = () => {
texture2D.setImageSource(img, i);
onComplete();
};
}
}
};
}
});
}
}

View File

@@ -0,0 +1,145 @@
import { ab2str } from "./Utils";
class ImageData {
type: "image/png" | "image/jpg" | "image/webp" | "ktx";
buffer: ArrayBuffer;
}
const textDecode = new TextDecoder();
export class BufferReader {
private _dataView: DataView;
private _littleEndian: boolean;
private _offset: number;
public static imageMapping = {
0: "image/png",
1: "image/jpg",
2: "image/webp",
3: "ktx"
};
constructor(public buffer: ArrayBuffer, byteOffset: number = 0, byteLength?: number, littleEndian: boolean = true) {
// byteLength = byteLength ?? _buffer.byteLength;
this._dataView = new DataView(buffer);
this._littleEndian = littleEndian;
this._offset = byteOffset;
}
get offset() {
return this._offset;
}
nextUint8() {
const value = this._dataView.getUint8(this._offset);
this._offset += 1;
return value;
}
nextUint16() {
const value = this._dataView.getUint16(this._offset, this._littleEndian);
this._offset += 2;
return value;
}
nextUint32() {
const value = this._dataView.getUint32(this._offset, this._littleEndian);
this._offset += 4;
return value;
}
nextInt32() {
const value = this._dataView.getInt32(this._offset, this._littleEndian);
this._offset += 4;
return value;
}
nextInt32Array(len: number) {
const value = new Int32Array(this.buffer, this._offset, len);
this._offset += 4 * len;
return value;
}
nextFloat32() {
const value = this._dataView.getFloat32(this._offset, this._littleEndian);
this._offset += 4;
return value;
}
nextFloat32Array(len: number) {
const value = new Float32Array(this.buffer, this._offset, len);
this._offset += 4 * len;
return value;
}
nextUint32Array(len: number) {
const value = new Uint32Array(this.buffer, this._offset, len);
this._offset += 4 * len;
return value;
}
nextUint8Array(len: number) {
const value = new Uint8Array(this.buffer, this._offset, len);
this._offset += len;
return value;
}
nextUint64() {
const left = this._dataView.getUint32(this._offset, this._littleEndian);
const right = this._dataView.getUint32(this._offset + 4, this._littleEndian);
const value = left + 2 ** 32 * right;
this._offset += 8;
return value;
}
nextStr(): string {
const strByteLength = this.nextUint16();
const uint8Array = new Uint8Array(this.buffer, this._offset, strByteLength);
this._offset += strByteLength;
return textDecode.decode(uint8Array);
}
/**
* image data 放在最后
*/
nextImageData(count: number = 0): ArrayBuffer {
return this.buffer.slice(this._offset);
}
nextImagesData(count: number): ArrayBuffer[] {
const imagesLen = new Array(count);
// Start offset of Uint32Array should be a multiple of 4. ref: https://stackoverflow.com/questions/15417310/why-typed-array-constructors-require-offset-to-be-multiple-of-underlying-type-si
for (let i = 0; i < count; i++) {
const len = this._dataView.getUint32(this._offset, this._littleEndian);
imagesLen[i] = len;
this._offset += 4;
}
const imagesData: ArrayBuffer[] = [];
for (let i = 0; i < count; i++) {
const len = imagesLen[i];
const buffer = this.buffer.slice(this._offset, this._offset + len);
this._offset += len;
imagesData.push(buffer);
}
return imagesData;
}
skip(bytes: number) {
this._offset += bytes;
return this;
}
scan(maxByteLength: number, term: number = 0x00): Uint8Array {
const byteOffset = this._offset;
let byteLength = 0;
while (this._dataView.getUint8(this._offset) !== term && byteLength < maxByteLength) {
byteLength++;
this._offset++;
}
if (byteLength < maxByteLength) this._offset++;
return new Uint8Array(this._dataView.buffer, this._dataView.byteOffset + byteOffset, byteLength);
}
}

View File

@@ -0,0 +1,20 @@
import { Engine } from "@oasis-engine/core";
import type { BufferReader } from "./BufferReader";
export const decoderMap: Record<
string,
{
decode: (engine: Engine, bufferReader: BufferReader) => Promise<any>;
}
> = {};
/**
* Decoder decorator generator.
* @param type - resource file type.
* @returns Decoder decorator
*/
export function decoder(type: string): ClassDecorator {
return (target: any) => {
decoderMap[type] = target;
};
}

View File

@@ -0,0 +1,33 @@
const textDecode = new TextDecoder();
export class FileHeader {
totalLength: number = 0;
version: number = 0;
type: string = "";
name: string = "";
headerLength: number = 0;
static decode(arrayBuffer: ArrayBuffer): FileHeader {
const dataView = new DataView(arrayBuffer);
const totalLen = dataView.getUint32(0, true);
const fileVersion = dataView.getUint8(4);
const typeLen = dataView.getUint16(5, true);
const typeUint8Array = new Uint8Array(arrayBuffer, 7, typeLen);
const nameLen = dataView.getUint16(7 + typeLen, true);
const nameUint8Array = new Uint8Array(arrayBuffer, 9 + typeLen, nameLen);
const name = textDecode.decode(nameUint8Array);
const type = textDecode.decode(typeUint8Array);
const header = new FileHeader();
header.totalLength = totalLen;
header.name = name;
header.type = type;
header.version = fileVersion;
header.headerLength = nameUint8Array.byteLength + typeUint8Array.byteLength + 9;
return header;
}
public get dataLength() {
return this.totalLength - this.headerLength;
}
}

View File

@@ -0,0 +1,7 @@
/**
* Array buffer to string.
* @param buf
*/
export function ab2str(buf: ArrayBuffer) {
return String.fromCharCode.apply(null, new Uint16Array(buf));
}

View File

@@ -0,0 +1,30 @@
{
"objectId": "0",
"layers": [
{
"name": "Base",
"weight": 1,
"blending": 0,
"stateMachine": {
"states": [
{
"name": "animation_AnimatedCube",
"clip": {
"objectId": "1",
"path": "./"
},
"wrapMode": 1,
"speed": 1,
"clipStartNormalizedTime": 0,
"clipEndNormalizedTime": 1,
"transitions": [],
"isEntryState": false,
"isExitState": false,
"isAnyState": false,
"isDefaultState": true
}
]
}
}
]
}

View File

@@ -0,0 +1,37 @@
import { AnimationClipDecoder } from './../../src/resources/animationClip/AnimationClipDecoder';
import { AnimationClipEncoder } from './../../src/resources/animationClip/AnimationClipEncoder';
import { AnimatorControllerDecoder } from './../../src/resources/animatorController/AnimatorControllerDecoder';
import { AnimatorControllerEncoder } from './../../src/resources/animatorController/AnimatorControllerEncoder';
import { BufferReader } from '../../src/utils/BufferReader';
import { WebGLEngine } from '../../../rhi-webgl/src/WebGLEngine';
import { BufferWriter } from '../../src/utils/BufferWriter';
import testClipData from '../animationClip/animationClip.json'
import testControllerData from './animatorController.json'
describe("AnimatorControllerEncoderAndDecoder Test", () => {
it("parser", async () => {
const engine = new WebGLEngine(document.createElement("canvas"));
const clipBuffer = new ArrayBuffer(10000);
const clipBufferWriter = new BufferWriter(clipBuffer);
const clipBufferReader = new BufferReader(clipBuffer);
AnimationClipEncoder.encode(clipBufferWriter, testClipData);
await AnimationClipDecoder.decode(engine, clipBufferReader)
const controllerBuffer = new ArrayBuffer(10000);
const controllerBufferWriter = new BufferWriter(controllerBuffer);
const controllerBufferReader = new BufferReader(controllerBuffer);
AnimatorControllerEncoder.encode(controllerBufferWriter, testControllerData);
const controller = await AnimatorControllerDecoder.decode(engine, controllerBufferReader)
const { clip } = controller.layers[0].stateMachine.states[0]
expect(controller.layers.length).toEqual(1);
expect(controller.layers[0].name).toEqual('Base');
expect(controller.layers[0].blendingMode).toEqual(0);
expect(controller.layers[0].weight).toEqual(1);
expect(controller.layers[0].stateMachine.states.length).toEqual(1);
expect(controller.layers[0].stateMachine.states[0].name).toEqual('animation_AnimatedCube');
expect(controller.layers[0].stateMachine.states[0].clip.name).toEqual('animation_AnimatedCube');
expect(controller.layers[0].stateMachine.states[0].wrapMode).toEqual(1);
expect(controller.layers[0].stateMachine.states[0].clipStartTime).toEqual(0);
expect(controller.layers[0].stateMachine.states[0].clipEndTime).toEqual(clip.length);
expect(controller.layers[0].stateMachine.states[0].transitions.length).toEqual(0);
});
});

View File

@@ -0,0 +1,86 @@
{
"objectId": "1",
"name": "animation_AnimatedCube",
"curveBindings": [
{
"relativePath": "",
"property": 1,
"curve": {
"keys": [
{
"time": 0,
"value": {
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"inTangent": {
"x": 0,
"y": 0,
"z": 0,
"w": 0
},
"outTangent": {
"x": 0,
"y": 0,
"z": 0,
"w": 0
}
},
{
"time": 1,
"value": {
"x": 0,
"y": 1,
"z": 0,
"w": -4.371138828673793e-8
},
"inTangent": {
"x": 0,
"y": 0,
"z": 0,
"w": 0
},
"outTangent": {
"x": 0,
"y": 0,
"z": 0,
"w": 0
}
},
{
"time": 2,
"value": {
"x": 0,
"y": -8.742277657347586e-8,
"z": 0,
"w": -1
},
"inTangent": {
"x": 0,
"y": 0,
"z": 0,
"w": 0
},
"outTangent": {
"x": 0,
"y": 0,
"z": 0,
"w": 0
}
}
],
"interpolation": 0,
"valueType": 5
}
}
],
"events": [
{
"time": 0.5,
"functionName": "test",
"parameter": "param"
}
]
}

View File

@@ -0,0 +1,25 @@
import { AnimationClipDecoder } from '../../src/resources/animationClip/AnimationClipDecoder';
import { BufferReader } from '../../src/utils/BufferReader';
import { WebGLEngine } from '../../../rhi-webgl/src/WebGLEngine';
import { BufferWriter } from '../../src/utils/BufferWriter';
import { AnimationClipEncoder } from '../../src/resources/animationClip/AnimationClipEncoder';
import testData from './animationClip.json'
describe("AnimationClipEncoderAndDecoder Test", () => {
it("parser", async () => {
const engine = new WebGLEngine(document.createElement("canvas"));
const buffer = new ArrayBuffer(10000);
const bufferWriter = new BufferWriter(buffer);
const bufferReader = new BufferReader(buffer);
AnimationClipEncoder.encode(bufferWriter, testData);
const clip = await AnimationClipDecoder.decode(engine, bufferReader)
expect(clip.name).toEqual('animation_AnimatedCube');
expect(clip.events.length).toEqual(1);
expect(clip.events[0].time).toEqual(0.5);
expect(clip.events[0].parameter).toEqual('param');
expect(clip.events[0].functionName).toEqual('test');
expect(clip.curveBindings.length).toEqual(1);
expect(clip.curveBindings[0].relativePath).toEqual('');
expect(clip.curveBindings[0].property).toEqual(1);
expect(clip.curveBindings[0].curve.keys.length).toEqual(3);
});
});

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"module": "esnext",
"target": "esnext",
"declaration": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"declarationDir": "types",
"emitDeclarationOnly": true,
"sourceMap": true,
"skipLibCheck": true,
"incremental": false
},
"include": [
"src/**/*"
]
}

View File

@@ -15,12 +15,9 @@
"types/**/*"
],
"dependencies": {
"@oasis-engine/core": "0.7.0-beta.4",
"@oasis-engine/math": "0.7.0-beta.4",
"@oasis-engine/physics-lite": "0.7.0-beta.4",
"@oasis-engine/rhi-webgl": "0.7.0-beta.4"
},
"devDependencies": {
"@oasis-engine/design": "0.7.0-beta.4"
"@oasis-engine/core": "0.8.0-alpha.5",
"@oasis-engine/math": "0.8.0-alpha.5",
"@oasis-engine/rhi-webgl": "0.8.0-alpha.5",
"@oasis-engine/design": "0.8.0-alpha.5"
}
}