mirror of
https://github.com/galacean/engine.git
synced 2026-06-21 19:02:50 +08:00
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
65
packages/loader/src/SceneLoader.ts
Normal file
65
packages/loader/src/SceneLoader.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
29
packages/loader/src/SpriteLoader.ts
Normal file
29
packages/loader/src/SpriteLoader.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -1,4 +0,0 @@
|
||||
import { PluginHook } from "./PluginManager";
|
||||
import { Oasis } from "../Oasis";
|
||||
|
||||
export type Plugin = ((oasis: Oasis) => PluginHook) | PluginHook;
|
||||
@@ -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;
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
21
packages/resource-process/package.json
Executable file
21
packages/resource-process/package.json
Executable 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"
|
||||
}
|
||||
}
|
||||
32
packages/resource-process/src/index.ts
Normal file
32
packages/resource-process/src/index.ts
Normal 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";
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
@@ -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]);
|
||||
// });
|
||||
// });
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
141
packages/resource-process/src/resources/mesh/IModelMesh.d.ts
vendored
Normal file
141
packages/resource-process/src/resources/mesh/IModelMesh.d.ts
vendored
Normal 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;
|
||||
}[];
|
||||
}
|
||||
225
packages/resource-process/src/resources/mesh/MeshDecoder.ts
Normal file
225
packages/resource-process/src/resources/mesh/MeshDecoder.ts
Normal 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;
|
||||
}
|
||||
31
packages/resource-process/src/resources/mesh/type.ts
Normal file
31
packages/resource-process/src/resources/mesh/type.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
24
packages/resource-process/src/resources/scene/MeshLoader.ts
Normal file
24
packages/resource-process/src/resources/scene/MeshLoader.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
36
packages/resource-process/src/resources/scene/SceneParser.ts
Normal file
36
packages/resource-process/src/resources/scene/SceneParser.ts
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
145
packages/resource-process/src/utils/BufferReader.ts
Normal file
145
packages/resource-process/src/utils/BufferReader.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
20
packages/resource-process/src/utils/Decorator.ts
Normal file
20
packages/resource-process/src/utils/Decorator.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
33
packages/resource-process/src/utils/FileHeader.ts
Normal file
33
packages/resource-process/src/utils/FileHeader.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
7
packages/resource-process/src/utils/Utils.ts
Normal file
7
packages/resource-process/src/utils/Utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Array buffer to string.
|
||||
* @param buf
|
||||
*/
|
||||
export function ab2str(buf: ArrayBuffer) {
|
||||
return String.fromCharCode.apply(null, new Uint16Array(buf));
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
25
packages/resource-process/tests/animationClip/index.test.ts
Normal file
25
packages/resource-process/tests/animationClip/index.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
18
packages/resource-process/tsconfig.json
Normal file
18
packages/resource-process/tsconfig.json
Normal 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/**/*"
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user