diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 50aa51e6e6..2ef22da218 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,6 +1,6 @@ name: Node.js CI -on: [push] +on: [push, pull_request] jobs: build: @@ -9,7 +9,7 @@ jobs: strategy: matrix: - node-version: [12.x] + node-version: [12.x, 15.x] steps: - uses: actions/checkout@v2 @@ -18,4 +18,8 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm install - - run: npm run ci \ No newline at end of file + - run: npm run ci + - run: npm install codecov + - name: Upload coverage to Codecov + run: ./node_modules/.bin/codecov + - run: curl -s https://codecov.io/bash \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0696d68e6b..85e47210dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,7 +49,6 @@ ### Bug Fixes - * Wrong number of mipmap in `Texture`.([#136](https://github.com/oasis-engine/engine/pull/136)) * Material blend mode bug. ([#127](https://github.com/oasis-engine/engine/pull/127)) * Fix none-indices gltf modle load error. ([#107](https://github.com/oasis-engine/engine/pull/107)) (Thanks to @BugDongDong for providing clues) diff --git a/README.md b/README.md index 06b71a12d8..02f7f35a3a 100644 --- a/README.md +++ b/README.md @@ -18,33 +18,30 @@ Oasis is a **web-first** and **mobile-first** high-performance real-time develop ## Usage ```typescript -// Create engine by passing in the HTMLCanvasElement id and get root entity. +// Create engine by passing in the HTMLCanvasElement id and adjust canvas size. const engine = new WebGLEngine("canvas-id"); -const canvas = engine.canvas; +engine.canvas.resizeByClientSize(); + +// Create root entity. const rootEntity = engine.sceneManager.activeScene.createRootEntity("Root"); -canvas.width = window.innerWidth * SystemInfo.devicePixelRatio; -canvas.height = window.innerHeight * SystemInfo.devicePixelRatio; // Create light. const lightEntity = rootEntity.createChild("Light"); -const ambient = lightEntity.addComponent(AmbientLight); const directLight = lightEntity.addComponent(DirectLight); -ambient.color = new Color(0.5, 0.5, 0.5); -directLight.color = new Color(0.3, 0.4, 0.4); +lightEntity.transform.setRotation(-45, -45, 0); +directLight.intensity = 0.4; // Create camera. const cameraEntity = rootEntity.createChild("Camera"); -cameraEntity.transform.setPosition(0, 6, 10); -cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); cameraEntity.addComponent(Camera); +cameraEntity.transform.setPosition(0, 0, 12); -// Create cube. -const cubeEntity = rootEntity.createChild("Cube"); -const cubeRenderer = cubeEntity.addComponent(MeshRenderer); +// Create sphere. +const meshEntity = rootEntity.createChild("Sphere"); +const meshRenderer = meshEntity.addComponent(MeshRenderer); const material = new BlinnPhongMaterial(engine); -cubeEntity.transform.rotate(0, 60, 0); -cubeRenderer.mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1); -cubeRenderer.setMaterial(material); +meshRenderer.setMaterial(material); +meshRenderer.mesh = PrimitiveMesh.createSphere(engine, 1); // Run engine. engine.run(); diff --git a/package.json b/package.json index d0967d42f7..f274b84fbd 100644 --- a/package.json +++ b/package.json @@ -6,19 +6,18 @@ "packages/*" ], "scripts": { - "bootstrap": "npm i && lerna bootstrap --hoist", + "bootstrap": "npm i && lerna bootstrap", "test": "jest", "test-cov": "jest --coverage", - "ci": "lerna bootstrap && npm run build && npm run b:types && npm run test-cov", + "ci": "lerna bootstrap && npm run b:module && npm run b:types && npm run test-cov", "lint": "eslint packages/*/src --ext .ts", "watch": "cross-env NODE_ENV=development BUILD_TYPE=MODULE rollup -cw -m inline", "watch:umd": "cross-env NODE_ENV=development BUILD_TYPE=UMD rollup -cw -m inline", "b:types": "lerna run b:types", - "build": "cross-env BUILD_TYPE=MODULE rollup -c", - "build-all": "cross-env BUILD_TYPE=ALL rollup -c", + "b:module": "cross-env BUILD_TYPE=MODULE rollup -c", "b:umd": "cross-env BUILD_TYPE=UMD rollup -c", "b:miniprogram": "cross-env BUILD_TYPE=MINI rollup -c", - "b:all": "npm run b:types && npm run build-all", + "b:all": "npm run b:types && cross-env BUILD_TYPE=ALL rollup -c", "clean": "lerna exec -- rm -rf dist && lerna clean", "changelog": "node tools/gen-changelog.js" }, @@ -49,7 +48,7 @@ "eslint-config-prettier": "^7.1.0", "eslint-plugin-prettier": "^3.1.1", "husky": "^4.3.7", - "jest": "^26.6.3", + "jest": "~24", "jest-electron": "^0.1.11", "lerna": "^3.22.1", "lint-staged": "^10.5.3", diff --git a/packages/controls/src/OrthoControl.ts b/packages/controls/src/OrthoControl.ts new file mode 100644 index 0000000000..cdc8e0ed70 --- /dev/null +++ b/packages/controls/src/OrthoControl.ts @@ -0,0 +1,127 @@ +import { Camera, Entity, Logger, Script, Vector2, Vector3 } from "oasis-engine"; + +/** + * The camera's 2D controller, can zoom and pan. + */ +export class OrthoControl extends Script { + cameraEntity: Entity; + camera: Camera; + + private _zoomSpeed: number = 1.0; + private _zoomScale: number = 1.0; + private _zoomScaleUnit: number = 25.0; + private _zoomMinSize: number = 0.0; + private _zoomMaxSize: number = Infinity; + private _isPanStart: boolean = false; + private _panStartPos: Vector3 = new Vector3(); + private _panStart: Vector2 = new Vector2(); + private _panEnd: Vector2 = new Vector2(); + private _panDelta: Vector2 = new Vector2(); + + /** + * The zoom speed. + */ + get zoomSpeed(): number { + return this._zoomSpeed; + } + + set zoomSpeed(value: number) { + this._zoomSpeed = value; + } + + constructor(entity: Entity) { + super(entity); + + this.cameraEntity = entity; + this.camera = entity.getComponent(Camera); + } + + onUpdate(dt: number): void { + if (this._zoomScale !== 1) { + const { camera } = this; + const sizeDiff = this._zoomScaleUnit * (this._zoomScale - 1); + const size = camera.orthographicSize + sizeDiff; + camera.orthographicSize = Math.max(this._zoomMinSize, Math.min(this._zoomMaxSize, size)); + this._zoomScale = 1; + } + + if (this._isPanStart) { + const { _panStart: panStart, _panEnd: panEnd } = this; + const panDelta = this._panDelta; + Vector2.subtract(panEnd, panStart, panDelta); + if (panDelta.x === 0 && panDelta.y === 0) { + return ; + } + this._handlePan(); + panEnd.cloneTo(panStart); + } + } + + /** + * Zoom in. + */ + zoomIn(): void { + this._zoomScale *= this._getZoomScale(); + } + + /** + * Zoom out. + */ + zoomOut(): void { + this._zoomScale /= this._getZoomScale(); + } + + /** + * Start pan. + * @param x - The x-axis coordinate (unit: pixel) + * @param y - The y-axis coordinate (unit: pixel) + */ + panStart(x: number, y: number): void { + if (!this.enabled) return; + + this.cameraEntity.transform.position.cloneTo(this._panStartPos); + this._panStart.setValue(x, y); + this._panEnd.setValue(x, y); + this._isPanStart = true; + } + + /** + * Panning. + * @param x - The x-axis coordinate (unit: pixel) + * @param y - The y-axis coordinate (unit: pixel) + * + * @remarks Make sure to call panStart before calling panMove. + */ + panMove(x: number, y: number): void { + if (!this.enabled) return; + if (!this._isPanStart) { + Logger.warn("Make sure to call panStart before calling panMove"); + } + this._panEnd.setValue(x, y); + } + + /** + * End pan. + */ + panEnd(): void { + if (!this.enabled) return; + this._isPanStart = false; + } + + private _getZoomScale(): number { + return Math.pow(0.95, this.zoomSpeed); + } + + private _handlePan(): void { + const { width, height } = this.engine.canvas; + const { x, y } = this._panDelta; + const { camera } = this; + const doubleOrthographicSize = camera.orthographicSize * 4; + const width3D = doubleOrthographicSize * camera.aspectRatio; + const height3D = doubleOrthographicSize; + const pos = this._panStartPos; + pos.x -= (x * width3D) / width; + pos.y += (y * height3D) / height; + this.cameraEntity.transform.position = pos; + } +} diff --git a/packages/controls/src/index.ts b/packages/controls/src/index.ts index fb51fff610..16f610636c 100644 --- a/packages/controls/src/index.ts +++ b/packages/controls/src/index.ts @@ -1,2 +1,3 @@ export { FreeControl } from "./FreeControl"; export { OrbitControl } from "./OrbitControl"; +export { OrthoControl } from "./OrthoControl"; diff --git a/packages/controls/tests/OrthoControl.test.ts b/packages/controls/tests/OrthoControl.test.ts new file mode 100644 index 0000000000..eed0f2486b --- /dev/null +++ b/packages/controls/tests/OrthoControl.test.ts @@ -0,0 +1,39 @@ +import { Camera, Entity, WebGLEngine } from "oasis-engine"; +import { OrthoControl } from "../src/OrthoControl"; + +const canvasDOM = document.createElement("canvas"); +canvasDOM.width = 1024; +canvasDOM.height = 1024; + +describe(" Ortho Control", () => { + let entity: Entity; + let camera: Camera; + let cameraControl: OrthoControl; + + beforeAll(() => { + const engine = new WebGLEngine(canvasDOM); + entity = engine.sceneManager.activeScene.createRootEntity(); + camera = entity.addComponent(Camera); + cameraControl = entity.addComponent(OrthoControl); + }); + + it("test zoom", () => { + cameraControl.zoomIn(); + cameraControl.onUpdate(0); + expect(camera.orthographicSize).toEqual(8.749999999999998); + cameraControl.zoomOut(); + cameraControl.onUpdate(0); + expect(camera.orthographicSize).toEqual(10.065789473684207); + }); + + it("test pan", () => { + cameraControl.panStart(0, 0); + cameraControl.panMove(2, 0); + cameraControl.onUpdate(0); + cameraControl.panEnd(); + + const pos = entity.transform.position; + expect(pos.x).toEqual(-0.07863898026315787); + expect(pos.y).toEqual(0); + }); +}); diff --git a/packages/controls/tsconfig.json b/packages/controls/tsconfig.json index d07bfc6b9a..51fad95808 100644 --- a/packages/controls/tsconfig.json +++ b/packages/controls/tsconfig.json @@ -7,6 +7,7 @@ "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "declarationDir": "types", + "skipLibCheck": true, "emitDeclarationOnly": true, "sourceMap": true }, diff --git a/packages/core/package.json b/packages/core/package.json index efd6a07d58..694913d5a6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -5,6 +5,7 @@ "main": "dist/main.js", "module": "dist/module.js", "types": "types/index.d.ts", + "debug": "src/index.ts", "scripts": { "b:types": "tsc" }, diff --git a/packages/core/src/2d/enums/SpriteMaskInteraction.ts b/packages/core/src/2d/enums/SpriteMaskInteraction.ts new file mode 100644 index 0000000000..5342a85183 --- /dev/null +++ b/packages/core/src/2d/enums/SpriteMaskInteraction.ts @@ -0,0 +1,11 @@ +/** + * Sprite mask interaction. + */ +export enum SpriteMaskInteraction { + /** The sprite will not interact with the masking system. */ + None, + /** The sprite will be visible only in areas where a mask is present. */ + VisibleInsideMask, + /** The sprite will be visible only in areas where no mask is present. */ + VisibleOutsideMask +} diff --git a/packages/core/src/2d/enums/SpriteMaskLayer.ts b/packages/core/src/2d/enums/SpriteMaskLayer.ts new file mode 100644 index 0000000000..30d89c6ea9 --- /dev/null +++ b/packages/core/src/2d/enums/SpriteMaskLayer.ts @@ -0,0 +1,71 @@ +/** + * Sprite mask layer. + */ +export enum SpriteMaskLayer { + /** Mask layer 0. */ + Layer0 = 0x1, + /** Mask layer 1. */ + Layer1 = 0x2, + /** Mask layer 2. */ + Layer2 = 0x4, + /** Mask layer 3. */ + Layer3 = 0x8, + /** Mask layer 4. */ + Layer4 = 0x10, + /** Mask layer 5. */ + Layer5 = 0x20, + /** Mask layer 6. */ + Layer6 = 0x40, + /** Mask layer 7. */ + Layer7 = 0x80, + /** Mask layer 8. */ + Layer8 = 0x100, + /** Mask layer 9. */ + Layer9 = 0x200, + /** Mask layer 10. */ + Layer10 = 0x400, + /** Mask layer 11. */ + Layer11 = 0x800, + /** Mask layer 12. */ + Layer12 = 0x1000, + /** Mask layer 13. */ + Layer13 = 0x2000, + /** Mask layer 14. */ + Layer14 = 0x4000, + /** Mask layer 15. */ + Layer15 = 0x8000, + /** Mask layer 16. */ + Layer16 = 0x10000, + /** Mask layer 17. */ + Layer17 = 0x20000, + /** Mask layer 18. */ + Layer18 = 0x40000, + /** Mask layer 19. */ + Layer19 = 0x80000, + /** Mask layer 20. */ + Layer20 = 0x100000, + /** Mask layer 21. */ + Layer21 = 0x200000, + /** Mask layer 22. */ + Layer22 = 0x400000, + /** Mask layer 23. */ + Layer23 = 0x800000, + /** Mask layer 24. */ + Layer24 = 0x1000000, + /** Mask layer 25. */ + Layer25 = 0x2000000, + /** Mask layer 26. */ + Layer26 = 0x4000000, + /** Mask layer 27. */ + Layer27 = 0x8000000, + /** Mask layer 28. */ + Layer28 = 0x10000000, + /** Mask layer 29. */ + Layer29 = 0x20000000, + /** Mask layer 30. */ + Layer30 = 0x40000000, + /** Mask layer 31. */ + Layer31 = 0x80000000, + /** All mask layers. */ + Everything = 0xffffffff +} diff --git a/packages/core/src/2d/index.ts b/packages/core/src/2d/index.ts index 4d2cecdeb1..4a704157ab 100644 --- a/packages/core/src/2d/index.ts +++ b/packages/core/src/2d/index.ts @@ -1 +1,3 @@ +export { SpriteMaskInteraction } from "./enums/SpriteMaskInteraction"; +export { SpriteMaskLayer } from "./enums/SpriteMaskLayer"; export * from "./sprite/index"; diff --git a/packages/core/src/2d/sprite/Sprite.ts b/packages/core/src/2d/sprite/Sprite.ts index 0d5e6c60e2..82d94536fb 100644 --- a/packages/core/src/2d/sprite/Sprite.ts +++ b/packages/core/src/2d/sprite/Sprite.ts @@ -1,24 +1,24 @@ -import { MathUtil, Rect, Vector2 } from "@oasis-engine/math"; +import { BoundingBox, MathUtil, Rect, Vector2 } from "@oasis-engine/math"; import { RefObject } from "../../asset/RefObject"; import { Engine } from "../../Engine"; -import { Texture2D } from "../../texture"; +import { Texture2D } from "../../texture/Texture2D"; /** * 2D sprite. */ export class Sprite extends RefObject { - private static _tempVec2: Vector2 = new Vector2(); - /** @internal */ _triangles: number[] = []; /** @internal */ _uv: Vector2[] = [new Vector2(), new Vector2(), new Vector2(), new Vector2()]; /** @internal */ _positions: Vector2[] = [new Vector2(), new Vector2(), new Vector2(), new Vector2()]; + /** @internal */ + _bounds: BoundingBox = new BoundingBox(); private _texture: Texture2D = null; - private _atlasRect: Rect = new Rect(0, 0, 1, 1); - private _rect: Rect = new Rect(0, 0, 1, 1); + private _atlasRegion: Rect = new Rect(0, 0, 1, 1); + private _region: Rect = new Rect(0, 0, 1, 1); private _pivot: Vector2 = new Vector2(0.5, 0.5); private _pixelsPerUnit: number; private _dirtyFlag: number = DirtyFlag.all; @@ -33,58 +33,65 @@ export class Sprite extends RefObject { set texture(value: Texture2D) { if (this._texture !== value) { this._texture = value; + this._setDirtyFlagTrue(DirtyFlag.positions); } } /** - * The rectangle of the original texture on its atlas texture. + * Bounding volume of the sprite. + * @remarks The returned bounds should be considered deep-read-only. */ - get atlasRect(): Rect { - return this._atlasRect; + get bounds(): Readonly { + if (this._isContainDirtyFlag(DirtyFlag.positions)) { + this._updatePositionsAndBounds(); + this._setDirtyFlagTrue(DirtyFlag.positions); + } + return this._bounds; } - set atlasRect(value: Rect) { - const { _atlasRect } = this; - if (value && _atlasRect !== value) { - _atlasRect.x = MathUtil.clamp(value.x, 0, 1); - _atlasRect.y = MathUtil.clamp(value.y, 0, 1); - _atlasRect.width = MathUtil.clamp(value.width, 0, 1.0 - _atlasRect.x); - _atlasRect.height = MathUtil.clamp(value.height, 0, 1.0 - _atlasRect.y); - } + /** + * The rectangle region of the original texture on its atlas texture. + */ + get atlasRegion(): Rect { + return this._atlasRegion; + } + + set atlasRegion(value: Rect) { + const atlasRegion = this._atlasRegion; + atlasRegion.x = MathUtil.clamp(value.x, 0, 1); + atlasRegion.y = MathUtil.clamp(value.y, 0, 1); + atlasRegion.width = MathUtil.clamp(value.width, 0, 1.0 - atlasRegion.x); + atlasRegion.height = MathUtil.clamp(value.height, 0, 1.0 - atlasRegion.y); } /** - * Location of the sprite's center point in the rect on the original texture, specified in normalized. + * Location of the sprite's center point in the rectangle region on the original texture, specified in normalized. */ get pivot(): Vector2 { return this._pivot; } set pivot(value: Vector2) { - const { _pivot } = this; - if (value && _pivot !== value) { - _pivot.x = MathUtil.clamp(value.x, 0, 1); - _pivot.y = MathUtil.clamp(value.y, 0, 1); - this._setDirtyFlagTrue(DirtyFlag.positions); - } + const pivot = this._pivot; + pivot.x = MathUtil.clamp(value.x, 0, 1); + pivot.y = MathUtil.clamp(value.y, 0, 1); + this._setDirtyFlagTrue(DirtyFlag.positions); } /** - * Location of the sprite on the original texture, specified in normalized. + * The rectangle region of the sprite on the original texture, specified in normalized. */ - get rect(): Rect { - return this._rect; - } - - set rect(value: Rect) { - const { _rect } = this; - if (value && _rect !== value) { - _rect.x = MathUtil.clamp(value.x, 0, 1); - _rect.y = MathUtil.clamp(value.y, 0, 1); - _rect.width = MathUtil.clamp(value.width, 0, 1.0 - _rect.x); - _rect.height = MathUtil.clamp(value.height, 0, 1.0 - _rect.y); - this._setDirtyFlagTrue(DirtyFlag.positions | DirtyFlag.uv); - } + get region(): Rect { + return this._region; + } + + set region(value: Rect) { + const region = this._region; + region.x = MathUtil.clamp(value.x, 0, 1); + region.y = MathUtil.clamp(value.y, 0, 1); + region.width = MathUtil.clamp(value.width, 0, 1.0 - region.x); + region.height = MathUtil.clamp(value.height, 0, 1.0 - region.y); + this._setDirtyFlagTrue(DirtyFlag.positions | DirtyFlag.uv); } /** @@ -105,16 +112,16 @@ export class Sprite extends RefObject { * Constructor a sprite. * @param engine - Engine to which the sprite belongs * @param texture - Texture from which to obtain the sprite - * @param rect - Rectangular section of the texture to use for the sprite, specified in normalized + * @param region - Rectangle region of the texture to use for the sprite, specified in normalized * @param pivot - Sprite's pivot point relative to its graphic rectangle, specified in normalized * @param pixelsPerUnit - The number of pixels in the sprite that correspond to one unit in world space */ constructor( engine: Engine, texture: Texture2D = null, - rect: Rect = null, + region: Rect = null, pivot: Vector2 = null, - pixelsPerUnit: number = 100 + pixelsPerUnit: number = 128 ) { super(engine); @@ -122,9 +129,9 @@ export class Sprite extends RefObject { this.texture = texture; } - if (rect) { - this.rect = rect; - this.atlasRect = rect; + if (region) { + this.region = region; + this.atlasRegion = region; } if (pivot) { @@ -144,58 +151,81 @@ export class Sprite extends RefObject { } /** - * Update mesh. + * Update positions and bounds. */ - private _updateMesh(): void { - if (this._isContainDirtyFlag(DirtyFlag.positions)) { - const { _pixelsPerUnit, _positions } = this; - const { width, height } = this.texture; - const { width: rWidth, height: rHeight } = this.rect; - const { x, y } = this.pivot; - const unitPivot = Sprite._tempVec2; + private _updatePositionsAndBounds(): void { + const { texture } = this; + let lx = 0; + let ty = 0; + let rx = 0; + let by = 0; + + if (texture) { + const { width, height } = texture; + const { width: rWidth, height: rHeight } = this.region; + const pixelsPerUnitReciprocal = 1.0 / this._pixelsPerUnit; - const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; // Get the width and height in 3D space. const unitWidth = rWidth * width * pixelsPerUnitReciprocal; const unitHeight = rHeight * height * pixelsPerUnitReciprocal; - // Get the pivot coordinate in 3D space. - unitPivot.x = x * unitWidth; - unitPivot.y = y * unitHeight; - // Top-left. - _positions[0].setValue(-unitPivot.x, unitHeight - unitPivot.y); - // Top-right. - _positions[1].setValue(unitWidth - unitPivot.x, unitHeight - unitPivot.y); - // Bottom-right. - _positions[2].setValue(unitWidth - unitPivot.x, -unitPivot.y); - // Bottom-left. - _positions[3].setValue(-unitPivot.x, -unitPivot.y); + // Get the distance between the anchor point and the four sides. + const { x: px, y: py } = this.pivot; + lx = -px * unitWidth; + ty = -py * unitHeight; + rx = (1 - px) * unitWidth; + by = (1 - py) * unitHeight; + } + + // Assign values ​​to _positions + const positions = this._positions; + // Top-left. + positions[0].setValue(lx, by); + // Top-right. + positions[1].setValue(rx, by); + // Bottom-right. + positions[2].setValue(rx, ty); + // Bottom-left. + positions[3].setValue(lx, ty); + + // Update bounds. + const { min, max } = this._bounds; + min.setValue(lx, ty, 0); + max.setValue(rx, by, 0); + } + + /** + * Update mesh. + */ + private _updateMesh(): void { + if (this._isContainDirtyFlag(DirtyFlag.positions)) { + this._updatePositionsAndBounds(); } if (this._isContainDirtyFlag(DirtyFlag.uv)) { - const { _uv } = this; - const { x, y, width, height } = this.rect; + const uv = this._uv; + const { x, y, width, height } = this.region; const rightX = x + width; const bottomY = y + height; // Top-left. - _uv[0].setValue(x, y); + uv[0].setValue(x, y); // Top-right. - _uv[1].setValue(rightX, y); + uv[1].setValue(rightX, y); // Bottom-right. - _uv[2].setValue(rightX, bottomY); + uv[2].setValue(rightX, bottomY); // Bottom-left. - _uv[3].setValue(x, bottomY); + uv[3].setValue(x, bottomY); } if (this._isContainDirtyFlag(DirtyFlag.triangles)) { - const { _triangles } = this; - _triangles[0] = 0; - _triangles[1] = 2; - _triangles[2] = 1; - _triangles[3] = 2; - _triangles[4] = 0; - _triangles[5] = 3; + const triangles = this._triangles; + triangles[0] = 0; + triangles[1] = 2; + triangles[2] = 1; + triangles[3] = 2; + triangles[4] = 0; + triangles[5] = 3; } } diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts new file mode 100644 index 0000000000..851a4896e6 --- /dev/null +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -0,0 +1,133 @@ +import { Vector3 } from "@oasis-engine/math"; +import { Camera } from "../../Camera"; +import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; +import { Entity } from "../../Entity"; +import { Renderer } from "../../Renderer"; +import { SpriteMaskElement } from "../../RenderPipeline/SpriteMaskElement"; +import { Shader } from "../../shader/Shader"; +import { ShaderProperty } from "../../shader/ShaderProperty"; +import { UpdateFlag } from "../../UpdateFlag"; +import { SpriteMaskLayer } from "../enums/SpriteMaskLayer"; +import { Sprite } from "./Sprite"; + +/** + * A component for masking Sprites. + */ +export class SpriteMask extends Renderer { + /** @internal */ + static _textureProperty: ShaderProperty = Shader.getPropertyByName("u_maskTexture"); + /** @internal */ + static _alphaCutoffProperty: ShaderProperty = Shader.getPropertyByName("u_alphaCutoff"); + + private static _tempVec3: Vector3 = new Vector3(); + + /** @internal */ + _maskElement: SpriteMaskElement; + + @deepClone + private _positions: Vector3[] = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]; + @ignoreClone + private _isSpriteDirty: boolean = true; + @ignoreClone + private _worldMatrixDirtyFlag: UpdateFlag; + @assignmentClone + private _sprite: Sprite = null; + @assignmentClone + private _alphaCutoff: number = 0.5; + + /** The mask layers the sprite mask influence to. */ + @assignmentClone + influenceLayers: number = SpriteMaskLayer.Everything; + + /** + * The Sprite used to define the mask. + */ + get sprite(): Sprite { + return this._sprite; + } + + set sprite(value: Sprite) { + if (this._sprite !== value) { + this._sprite = value; + this._isSpriteDirty = true; + } + } + + /** + * The minimum alpha value used by the mask to select the area of influence defined over the mask's sprite. Value between 0 and 1. + */ + get alphaCutoff(): number { + return this._alphaCutoff; + } + + set alphaCutoff(value: number) { + if (this._alphaCutoff !== value) { + this._alphaCutoff = value; + this.shaderData.setFloat(SpriteMask._alphaCutoffProperty, value); + } + } + + /** + * @internal + */ + constructor(entity: Entity) { + super(entity); + this._worldMatrixDirtyFlag = entity.transform.registerWorldChangeFlag(); + this.setMaterial(this._engine._spriteMaskDefaultMaterial); + this.shaderData.setFloat(SpriteMask._alphaCutoffProperty, this._alphaCutoff); + } + + /** + * @override + * @inheritdoc + */ + _onDestroy(): void { + this._worldMatrixDirtyFlag.destroy(); + super._onDestroy(); + } + + /** + * @override + * @inheritdoc + */ + _render(camera: Camera): void { + const sprite = this.sprite; + if (!sprite) { + return null; + } + const texture = sprite.texture; + if (!texture) { + return null; + } + + const positions = this._positions; + const transform = this.entity.transform; + + // Update sprite data. + const localDirty = sprite._updateMeshData(); + + if (this._worldMatrixDirtyFlag.flag || localDirty || this._isSpriteDirty) { + const localPositions = sprite._positions; + const localVertexPos = SpriteMask._tempVec3; + const worldMatrix = transform.worldMatrix; + + for (let i = 0, n = positions.length; i < n; i++) { + const curVertexPos = localPositions[i]; + localVertexPos.setValue(curVertexPos.x, curVertexPos.y, 0); + Vector3.transformToVec3(localVertexPos, worldMatrix, positions[i]); + } + + this._isSpriteDirty = false; + this._worldMatrixDirtyFlag.flag = false; + } + + this.shaderData.setTexture(SpriteMask._textureProperty, texture); + const spriteMaskElementPool = this._engine._spriteMaskElementPool; + const maskElement = spriteMaskElementPool.getFromPool(); + maskElement.setValue(this, positions, sprite._uv, sprite._triangles, this.getMaterial()); + maskElement.camera = camera; + + camera._renderPipeline._allSpriteMasks.add(this); + this._maskElement = maskElement; + } +} diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index 7529decb44..52c691b01c 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -2,13 +2,14 @@ import { BoundingBox, Color, Vector3 } from "@oasis-engine/math"; import { Camera } from "../../Camera"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; import { Entity } from "../../Entity"; -import { Material, RenderQueueType } from "../../material"; import { Renderer } from "../../Renderer"; -import { BlendFactor, BlendOperation, CullMode, Shader } from "../../shader"; +import { CompareFunction } from "../../shader/enums/CompareFunction"; +import { Shader } from "../../shader/Shader"; import { ShaderProperty } from "../../shader/ShaderProperty"; import { UpdateFlag } from "../../UpdateFlag"; +import { SpriteMaskInteraction } from "../enums/SpriteMaskInteraction"; +import { SpriteMaskLayer } from "../enums/SpriteMaskLayer"; import { Sprite } from "./Sprite"; -import "./SpriteMaterial"; /** * Renders a Sprite for 2D graphics. @@ -16,7 +17,6 @@ import "./SpriteMaterial"; export class SpriteRenderer extends Renderer { private static _textureProperty: ShaderProperty = Shader.getPropertyByName("u_spriteTexture"); private static _tempVec3: Vector3 = new Vector3(); - private static _defaultMaterial: Material = null; @deepClone private _positions: Vector3[] = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]; @@ -36,6 +36,10 @@ export class SpriteRenderer extends Renderer { private _dirtyFlag: number = DirtyFlag.All; @ignoreClone private _isWorldMatrixDirty: UpdateFlag; + @assignmentClone + private _maskInteraction: SpriteMaskInteraction = SpriteMaskInteraction.None; + @assignmentClone + private _maskLayer: number = SpriteMaskLayer.Layer0; /** * The Sprite to render. @@ -93,12 +97,37 @@ export class SpriteRenderer extends Renderer { } /** - * Create a sprite renderer instance. - * @param entity - Entity to which the sprite renderer belongs + * Interacts with the masks. + */ + get maskInteraction(): SpriteMaskInteraction { + return this._maskInteraction; + } + + set maskInteraction(value: SpriteMaskInteraction) { + if (this._maskInteraction !== value) { + this._maskInteraction = value; + this._setDirtyFlagTrue(DirtyFlag.MaskInteraction); + } + } + + /** + * The mask layer the sprite renderer belongs to. + */ + get maskLayer(): number { + return this._maskLayer; + } + + set maskLayer(value: number) { + this._maskLayer = value; + } + + /** + * @internal */ constructor(entity: Entity) { super(entity); this._isWorldMatrixDirty = entity.transform.registerWorldChangeFlag(); + this.setMaterial(this._engine._spriteDefaultMaterial); } /** @@ -114,26 +143,10 @@ export class SpriteRenderer extends Renderer { return; } - this._updateRenderData(); - this.shaderData.setTexture(SpriteRenderer._textureProperty, texture); - const material = this.getMaterial() || this._getDefaultMaterial(); - - const spriteElement = this._engine._spriteElementPool.getFromPool(); - spriteElement.setValue(this, this._positions, sprite._uv, sprite._triangles, this.color, material, camera); - camera._renderPipeline.pushPrimitive(spriteElement); - } - - /** - * @internal - */ - _onDestroy(): void { - this._isWorldMatrixDirty.destroy(); - super._onDestroy(); - } - - private _updateRenderData(): void { - const { sprite, _positions } = this; + const { _positions } = this; const { transform } = this.entity; + + // Update sprite data. const localDirty = sprite._updateMeshData(); if (this._isWorldMatrixDirty.flag || localDirty || this._isContainDirtyFlag(DirtyFlag.Sprite)) { @@ -177,6 +190,27 @@ export class SpriteRenderer extends Renderer { this._cacheFlipX = flipX; this._cacheFlipY = flipY; } + + if (this._isContainDirtyFlag(DirtyFlag.MaskInteraction)) { + this._updateStencilState(); + this._setDirtyFlagFalse(DirtyFlag.MaskInteraction); + } + + this.shaderData.setTexture(SpriteRenderer._textureProperty, texture); + const material = this.getMaterial(); + + const spriteElementPool = this._engine._spriteElementPool; + const spriteElement = spriteElementPool.getFromPool(); + spriteElement.setValue(this, _positions, sprite._uv, sprite._triangles, this.color, material, camera); + camera._renderPipeline.pushPrimitive(spriteElement); + } + + /** + * @internal + */ + _onDestroy(): void { + this._isWorldMatrixDirty.destroy(); + super._onDestroy(); } private _isContainDirtyFlag(type: number): boolean { @@ -191,41 +225,49 @@ export class SpriteRenderer extends Renderer { this._dirtyFlag &= ~type; } - private _getDefaultMaterial(): Material { - if (!SpriteRenderer._defaultMaterial) { - const material = (SpriteRenderer._defaultMaterial = new Material(this.scene.engine, Shader.find("Sprite"))); - const target = material.renderState.blendState.targetBlendState; - target.enabled = true; - target.sourceColorBlendFactor = BlendFactor.SourceAlpha; - target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha; - target.sourceAlphaBlendFactor = BlendFactor.One; - target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha; - target.colorBlendOperation = target.alphaBlendOperation = BlendOperation.Add; - material.renderState.depthState.writeEnabled = false; - material.renderQueueType = RenderQueueType.Transparent; - material.renderState.rasterState.cullMode = CullMode.Off; - } - - return SpriteRenderer._defaultMaterial; - } - /** * @override */ protected _updateBounds(worldBounds: BoundingBox): void { - const { sprite } = this; - if (sprite && sprite.texture) { - this._updateRenderData(); - BoundingBox.fromPoints(this._positions, worldBounds); + const sprite = this._sprite; + if (sprite) { + const localBounds = sprite.bounds; + const worldMatrix = this._entity.transform.worldMatrix; + BoundingBox.transform(localBounds, worldMatrix, worldBounds); } else { worldBounds.min.setValue(0, 0, 0); worldBounds.max.setValue(0, 0, 0); } } + + private _updateStencilState(): void { + // Update stencil. + const material = this.getInstanceMaterial(); + const stencilState = material.renderState.stencilState; + const maskInteraction = this._maskInteraction; + + if (maskInteraction === SpriteMaskInteraction.None) { + stencilState.enabled = false; + stencilState.writeMask = 0xff; + stencilState.referenceValue = 0; + stencilState.compareFunctionFront = stencilState.compareFunctionBack = CompareFunction.Always; + } else { + stencilState.enabled = true; + stencilState.writeMask = 0x00; + stencilState.referenceValue = 1; + const compare = + maskInteraction === SpriteMaskInteraction.VisibleInsideMask + ? CompareFunction.LessEqual + : CompareFunction.Greater; + stencilState.compareFunctionFront = compare; + stencilState.compareFunctionBack = compare; + } + } } enum DirtyFlag { Flip = 0x1, Sprite = 0x2, - All = 0x3 + All = 0x3, + MaskInteraction = 0x4 } diff --git a/packages/core/src/2d/sprite/index.ts b/packages/core/src/2d/sprite/index.ts index c7ba1b272a..7dd3e15d52 100644 --- a/packages/core/src/2d/sprite/index.ts +++ b/packages/core/src/2d/sprite/index.ts @@ -1,2 +1,3 @@ export { Sprite } from "./Sprite"; export { SpriteRenderer } from "./SpriteRenderer"; +export { SpriteMask } from "./SpriteMask"; diff --git a/packages/core/src/Background.ts b/packages/core/src/Background.ts new file mode 100644 index 0000000000..943eab7acf --- /dev/null +++ b/packages/core/src/Background.ts @@ -0,0 +1,28 @@ +import { Color } from "@oasis-engine/math"; +import { BackgroundMode } from "./enums/BackgroundMode"; +import { Sky } from "./sky/Sky"; + +/** + * Background of scene. + */ +export class Background { + /** + * Background mode. + * @defaultValue `BackgroundMode.SolidColor` + * @remarks If using `BackgroundMode.Sky` mode and material or mesh of the `sky` is not defined, it will downgrade to `BackgroundMode.SolidColor`. + */ + mode: BackgroundMode = BackgroundMode.SolidColor; + + /** + * Background solid color. + * @defaultValue `new Color(0.25, 0.25, 0.25, 1.0)` + * @remarks When `mode` is `BackgroundMode.SolidColor`, the property will take effects. + */ + solidColor: Color = new Color(0.25, 0.25, 0.25, 1.0); + + /** + * Background sky. + * @remarks When `mode` is `BackgroundMode.Sky`, the property will take effects. + */ + readonly sky: Sky = new Sky(); +} diff --git a/packages/core/src/Camera.ts b/packages/core/src/Camera.ts index 73bce87659..c3cef28680 100644 --- a/packages/core/src/Camera.ts +++ b/packages/core/src/Camera.ts @@ -1,9 +1,9 @@ import { BoundingFrustum, MathUtil, Matrix, Ray, Vector2, Vector3, Vector4 } from "@oasis-engine/math"; -import { ClearMode } from "./base"; import { deepClone, ignoreClone } from "./clone/CloneManager"; import { Component } from "./Component"; import { dependencies } from "./ComponentsDependencies"; import { Entity } from "./Entity"; +import { CameraClearFlags } from "./enums/CameraClearFlags"; import { Layer } from "./Layer"; import { BasicRenderPipeline } from "./RenderPipeline/BasicRenderPipeline"; import { RenderContext } from "./RenderPipeline/RenderContext"; @@ -16,11 +16,6 @@ import { RenderTarget } from "./texture/RenderTarget"; import { Transform } from "./Transform"; import { UpdateFlag } from "./UpdateFlag"; -/** - * @todo - */ -type Sky = {}; - class MathTemp { static tempMat4 = new Matrix(); static tempVec4 = new Vector4(); @@ -28,20 +23,6 @@ class MathTemp { static tempVec2 = new Vector2(); } -/** - * ClearFlag, which controls camera's background. - */ -export enum ClearFlags { - /* Clear depth and skybox. */ - DepthSky, - /* Clear depth and color. */ - DepthColor, - /* Clear depth only. */ - Depth, - /* Do nothing. */ - None -} - /** * Camera component, as the entrance to the three-dimensional world. */ @@ -54,21 +35,27 @@ export class Camera extends Component { private static _inverseProjectionMatrixProperty = Shader.getPropertyByName("u_projInvMat"); private static _cameraPositionProperty = Shader.getPropertyByName("u_cameraPos"); + /** Shader data. */ + readonly shaderData: ShaderData = new ShaderData(ShaderDataGroup.Camera); + /** Rendering priority - A Camera with higher priority will be rendererd on top of a camera with lower priority. */ priority: number = 0; /** Whether to enable frustum culling, it is enabled by default. */ enableFrustumCulling: boolean = true; + /** + * Determining what to clear when rendering by a Camera. + * @defaultValue `CameraClearFlags.DepthColor` + */ + clearFlags: CameraClearFlags = CameraClearFlags.DepthColor; + /** * Culling mask - which layers the camera renders. * @remarks Support bit manipulation, conresponding to Entity's layer. */ cullingMask: Layer = Layer.Everything; - /** Shader data. */ - readonly shaderData: ShaderData = new ShaderData(ShaderDataGroup.Camera); - /** @internal */ _globalShaderMacro: ShaderMacroCollection = new ShaderMacroCollection(); /** @internal */ @@ -80,7 +67,6 @@ export class Camera extends Component { private _isOrthographic: boolean = false; private _isProjMatSetting = false; - private _clearMode: ClearMode = ClearMode.SOLID_COLOR; private _nearClipPlane: number = 0.1; private _farClipPlane: number = 100; private _fieldOfView: number = 45; @@ -104,14 +90,10 @@ export class Camera extends Component { @deepClone private _viewMatrix: Matrix = new Matrix(); @deepClone - private _backgroundColor: Vector4 = new Vector4(); - @deepClone private _viewport: Vector4 = new Vector4(0, 0, 1, 1); @deepClone private _inverseProjectionMatrix: Matrix = new Matrix(); @deepClone - private _inverseViewMatrix: Matrix = new Matrix(); - @deepClone private _lastAspectSize: Vector2 = new Vector2(0, 0); @deepClone private _invViewProjMat: Matrix = new Matrix(); @@ -204,39 +186,6 @@ export class Camera extends Component { this._projMatChange(); } - /** - * Clear background flags. - */ - get clearFlags(): ClearFlags { - throw "not implemented"; - } - - /** - * @todo Skybox refactor - */ - set clearFlags(value: ClearFlags) { - throw "not implemented"; - } - - /** - * Clear the background color of the viewport, which takes effect when clearFlags is DepthColor. - */ - get backgroundColor(): Vector4 { - return this._backgroundColor; - } - - set backgroundColor(value: Vector4) { - this.setClearMode(this._clearMode, value); - } - - /** - * Clear the background sky of the viewport, active when clearFlags is DepthSky. - * @todo Render pipeline modification - */ - get backgroundSky(): Sky { - throw new Error("not implemented"); - } - /** * View matrix. */ @@ -325,8 +274,6 @@ export class Camera extends Component { this._frustumViewChangeFlag = transform.registerWorldChangeFlag(); this._renderPipeline = new BasicRenderPipeline(this); this.shaderData._addRefCount(1); - - this.setClearMode(); } /** @@ -542,7 +489,7 @@ export class Camera extends Component { shaderData.setMatrix(Camera._viewMatrixProperty, this.viewMatrix); shaderData.setMatrix(Camera._projectionMatrixProperty, this.projectionMatrix); shaderData.setMatrix(Camera._vpMatrixProperty, context._viewProjectMatrix); - shaderData.setMatrix(Camera._inverseViewMatrixProperty, this.inverseViewMatrix); + shaderData.setMatrix(Camera._inverseViewMatrixProperty, this._transform.worldMatrix); shaderData.setMatrix(Camera._inverseProjectionMatrixProperty, this.inverseProjectionMatrix); shaderData.setVector3(Camera._cameraPositionProperty, this._transform.worldPosition); } @@ -554,7 +501,7 @@ export class Camera extends Component { get invViewProjMat(): Matrix { if (this._isInvViewProjDirty.flag) { this._isInvViewProjDirty.flag = false; - Matrix.multiply(this.inverseViewMatrix, this.inverseProjectionMatrix, this._invViewProjMat); + Matrix.multiply(this._transform.worldMatrix, this.inverseProjectionMatrix, this._invViewProjMat); } return this._invViewProjMat; } @@ -570,31 +517,4 @@ export class Camera extends Component { } return this._inverseProjectionMatrix; } - - //-------------------------------------------------deprecated--------------------------------------------------- - - /** - * @deprecated - * View matrix inverse matrix. - */ - get inverseViewMatrix(): Readonly { - this._transform.worldMatrix.cloneTo(this._inverseViewMatrix); - return this._inverseViewMatrix; - } - - /** - * @deprecated - * @todo Involving the rendering pipeline to modify the rhi.clearRenderTarget method. - * @param clearMode - * @param backgroundColor - */ - setClearMode( - clearMode: ClearMode = ClearMode.SOLID_COLOR, - backgroundColor: Vector4 = new Vector4(0.25, 0.25, 0.25, 1) - ): void { - this._clearMode = clearMode; - this._backgroundColor = backgroundColor; - this._renderPipeline.defaultRenderPass.clearParam = backgroundColor; - this._renderPipeline.defaultRenderPass.clearMode = clearMode; - } } diff --git a/packages/core/src/Component.ts b/packages/core/src/Component.ts index 0803a5cef2..3e43f1a889 100644 --- a/packages/core/src/Component.ts +++ b/packages/core/src/Component.ts @@ -60,13 +60,6 @@ export abstract class Component extends EngineObject { return this._entity.scene; } - /** - * The engine which the component's entity belongs to. - */ - get engine(): Engine { - return this._entity.engine; - } - constructor(entity: Entity) { super(entity.engine); this._entity = entity; diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index 0a5e57d2fc..95ea67c322 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -5,13 +5,21 @@ import { ComponentsManager } from "./ComponentsManager"; import { EngineFeature } from "./EngineFeature"; import { Entity } from "./Entity"; import { FeatureManager } from "./FeatureManager"; +import { RenderQueueType } from "./material/enums/RenderQueueType"; +import { Material } from "./material/Material"; import { IHardwareRenderer } from "./renderingHardwareInterface/IHardwareRenderer"; import { ClassPool } from "./RenderPipeline/ClassPool"; import { RenderContext } from "./RenderPipeline/RenderContext"; import { RenderElement } from "./RenderPipeline/RenderElement"; import { SpriteElement } from "./RenderPipeline/SpriteElement"; +import { SpriteMaskElement } from "./RenderPipeline/SpriteMaskElement"; +import { SpriteMaskManager } from "./RenderPipeline/SpriteMaskManager"; import { Scene } from "./Scene"; import { SceneManager } from "./SceneManager"; +import { BlendFactor } from "./shader/enums/BlendFactor"; +import { BlendOperation } from "./shader/enums/BlendOperation"; +import { ColorWriteMask } from "./shader/enums/ColorWriteMask"; +import { CullMode } from "./shader/enums/CullMode"; import { Shader } from "./shader/Shader"; import { ShaderPool } from "./shader/ShaderPool"; import { ShaderProgramPool } from "./shader/ShaderProgramPool"; @@ -31,6 +39,9 @@ export class Engine extends EventDispatcher { _lastRenderState: RenderState = new RenderState(); _renderElementPool: ClassPool = new ClassPool(RenderElement); _spriteElementPool: ClassPool = new ClassPool(SpriteElement); + _spriteMaskElementPool: ClassPool = new ClassPool(SpriteMaskElement); + _spriteDefaultMaterial: Material; + _spriteMaskDefaultMaterial: Material; _renderContext: RenderContext = new RenderContext(); /* @internal */ @@ -41,6 +52,8 @@ export class Engine extends EventDispatcher { _renderCount: number = 0; /* @internal */ _shaderProgramPools: ShaderProgramPool[] = []; + /** @internal */ + _spriteMaskManager: SpriteMaskManager; protected _canvas: Canvas; private _resourceManager: ResourceManager = new ResourceManager(this); @@ -69,7 +82,6 @@ export class Engine extends EventDispatcher { /** * The canvas to use for rendering. - * @readonly */ get canvas(): Canvas { return this._canvas; @@ -77,7 +89,6 @@ export class Engine extends EventDispatcher { /** * Get the resource manager. - * @readonly */ get resourceManager(): ResourceManager { return this._resourceManager; @@ -85,7 +96,6 @@ export class Engine extends EventDispatcher { /** * Get the scene manager. - * @readonly */ get sceneManager(): SceneManager { return this._sceneManager; @@ -93,7 +103,6 @@ export class Engine extends EventDispatcher { /** * Get the Time class. - * @readonly */ get time(): Time { return this._time; @@ -101,7 +110,6 @@ export class Engine extends EventDispatcher { /** * Whether the engine is paused. - * @readonly */ get isPaused(): boolean { return this._isPaused; @@ -149,6 +157,10 @@ export class Engine extends EventDispatcher { engineFeatureManager.addObject(this); this._sceneManager.activeScene = new Scene(this, "DefaultScene"); + this._spriteMaskManager = new SpriteMaskManager(this); + this._spriteDefaultMaterial = this._createSpriteMaterial(); + this._spriteMaskDefaultMaterial = this._createSpriteMaskMaterial(); + const whitePixel = new Uint8Array([255, 255, 255, 255]); const whiteTextrue2D = new Texture2D(this, 1, 1, TextureFormat.R8G8B8A8, false); @@ -206,6 +218,7 @@ export class Engine extends EventDispatcher { time.tick(); this._renderElementPool.resetPool(); this._spriteElementPool.resetPool(); + this._spriteMaskElementPool.resetPool(); engineFeatureManager.callFeatureMethod(this, "preTick", [this, this._sceneManager._activeScene]); @@ -263,6 +276,9 @@ export class Engine extends EventDispatcher { this.features = []; this._time = null; + // delete mask manager + this._spriteMaskManager.destroy(); + // todo: delete (engineFeatureManager as any)._objects = []; this.removeAllEventListeners(); @@ -314,6 +330,34 @@ export class Engine extends EventDispatcher { } } + private _createSpriteMaterial(): Material { + const material = new Material(this, Shader.find("Sprite")); + const renderState = material.renderState; + const target = renderState.blendState.targetBlendState; + target.enabled = true; + target.sourceColorBlendFactor = BlendFactor.SourceAlpha; + target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha; + target.sourceAlphaBlendFactor = BlendFactor.One; + target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha; + target.colorBlendOperation = target.alphaBlendOperation = BlendOperation.Add; + renderState.depthState.writeEnabled = false; + renderState.rasterState.cullMode = CullMode.Off; + material.renderQueueType = RenderQueueType.Transparent; + material.isGCIgnored = true; + return material; + } + + private _createSpriteMaskMaterial(): Material { + const material = new Material(this, Shader.find("SpriteMask")); + const renderState = material.renderState; + renderState.blendState.targetBlendState.colorWriteMask = ColorWriteMask.None; + renderState.rasterState.cullMode = CullMode.Off; + renderState.stencilState.enabled = true; + renderState.depthState.enabled = false; + material.isGCIgnored = true; + return material; + } + //-----------------------------------------@deprecated----------------------------------- findFeature(Feature) { diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index 05dc1916f1..1d18782435 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -38,9 +38,11 @@ export class Entity extends EngineObject { } } + /** The name of entity. */ name: string; /** The layer the entity belongs to. */ layer: Layer = Layer.Layer0; + /** Transform component. */ readonly transform: Transform; /** @internal */ @@ -143,13 +145,6 @@ export class Entity extends EngineObject { return this._scene; } - /** - * The engine the entity belongs to. - */ - get engine(): Engine { - return this._engine; - } - /** * Create a entity. * @param engine - The engine the entity belongs to. diff --git a/packages/core/src/Layer.ts b/packages/core/src/Layer.ts index ef55ca593c..20d3893f73 100644 --- a/packages/core/src/Layer.ts +++ b/packages/core/src/Layer.ts @@ -2,38 +2,72 @@ * Layer, used for bit operations. */ export enum Layer { + /** Layer 0. */ Layer0 = 0x1, + /** Layer 1. */ Layer1 = 0x2, + /** Layer 2. */ Layer2 = 0x4, + /** Layer 3. */ Layer3 = 0x8, + /** Layer 4. */ Layer4 = 0x10, + /** Layer 5. */ Layer5 = 0x20, + /** Layer 6. */ Layer6 = 0x40, + /** Layer 7. */ Layer7 = 0x80, + /** Layer 8. */ Layer8 = 0x100, + /** Layer 9. */ Layer9 = 0x200, + /** Layer 10. */ Layer10 = 0x400, + /** Layer 11. */ Layer11 = 0x800, + /** Layer 12. */ Layer12 = 0x1000, + /** Layer 13. */ Layer13 = 0x2000, + /** Layer 14. */ Layer14 = 0x4000, + /** Layer 15. */ Layer15 = 0x8000, + /** Layer 16. */ Layer16 = 0x10000, + /** Layer 17. */ Layer17 = 0x20000, + /** Layer 18. */ Layer18 = 0x40000, + /** Layer 19. */ Layer19 = 0x80000, + /** Layer 20. */ Layer20 = 0x100000, + /** Layer 21. */ Layer21 = 0x200000, + /** Layer 22. */ Layer22 = 0x400000, + /** Layer 23. */ Layer23 = 0x800000, + /** Layer 24. */ Layer24 = 0x1000000, + /** Layer 25. */ Layer25 = 0x2000000, + /** Layer 26. */ Layer26 = 0x4000000, + /** Layer 27. */ Layer27 = 0x8000000, + /** Layer 28. */ Layer28 = 0x10000000, + /** Layer 29. */ Layer29 = 0x20000000, + /** Layer 30. */ Layer30 = 0x40000000, + /** Layer 31. */ Layer31 = 0x80000000, + /** All layers. */ Everything = 0xffffffff, + /** None layer. */ Nothing = 0x0 } diff --git a/packages/core/src/RenderPipeline/Basic2DBatcher.ts b/packages/core/src/RenderPipeline/Basic2DBatcher.ts new file mode 100644 index 0000000000..24eb763a29 --- /dev/null +++ b/packages/core/src/RenderPipeline/Basic2DBatcher.ts @@ -0,0 +1,220 @@ +import { Engine } from "../Engine"; +import { Buffer, BufferBindFlag, BufferUsage, IndexFormat, MeshTopology, SubMesh, VertexElement } from "../graphic"; +import { BufferMesh } from "../mesh"; +import { SystemInfo } from "../SystemInfo"; +import { ClassPool } from "./ClassPool"; +import { SpriteElement } from "./SpriteElement"; +import { SpriteMaskElement } from "./SpriteMaskElement"; + +type Element = SpriteElement | SpriteMaskElement; + +export abstract class Basic2DBatcher { + /** The maximum number of vertex. */ + static MAX_VERTEX_COUNT: number = 4096; + static _canUploadSameBuffer: boolean = !SystemInfo._isIos(); + + /** @internal */ + _subMeshPool: ClassPool = new ClassPool(SubMesh); + /** @internal */ + _batchedQueue: Element[] = []; + /** @internal */ + _meshes: BufferMesh[] = []; + /** @internal */ + _meshCount: number = 1; + /** @internal */ + _vertexBuffers: Buffer[] = []; + /** @internal */ + _indiceBuffers: Buffer[] = []; + /** @internal */ + _vertices: Float32Array; + /** @internal */ + _indices: Uint16Array; + /** @internal */ + _flushId: number = 0; + /** @internal */ + _vertexCount: number = 0; + /** @internal */ + _elementCount: number = 0; + + constructor(engine: Engine) { + const { MAX_VERTEX_COUNT } = Basic2DBatcher; + this._vertices = new Float32Array(MAX_VERTEX_COUNT * 9); + this._indices = new Uint16Array(MAX_VERTEX_COUNT * 3); + + const { _meshes, _meshCount } = this; + for (let i = 0; i < _meshCount; i++) { + _meshes[i] = this._createMesh(engine, i); + } + } + + drawElement(element: Element): void { + const len = element.positions.length; + if (this._vertexCount + len > Basic2DBatcher.MAX_VERTEX_COUNT) { + this.flush(element.camera.engine); + } + + this._vertexCount += len; + this._batchedQueue[this._elementCount++] = element; + } + + flush(engine: Engine): void { + const batchedQueue = this._batchedQueue; + + if (batchedQueue.length === 0) { + return; + } + + this._updateData(engine); + this.drawBatches(engine); + + if (!Basic2DBatcher._canUploadSameBuffer) { + this._flushId++; + } + + batchedQueue.length = 0; + this._subMeshPool.resetPool(); + this._vertexCount = 0; + this._elementCount = 0; + } + + clear(): void { + this._flushId = 0; + this._vertexCount = 0; + this._elementCount = 0; + this._batchedQueue.length = 0; + } + + destroy(): void { + this._batchedQueue = null; + + const { _meshes: meshes, _vertexBuffers: vertexBuffers, _indiceBuffers: indiceBuffers } = this; + + for (let i = 0, n = meshes.length; i < n; ++i) { + meshes[i].destroy(); + } + this._meshes = null; + + for (let i = 0, n = vertexBuffers.length; i < n; ++i) { + vertexBuffers[i].destroy(); + } + this._vertexBuffers = null; + + for (let i = 0, n = indiceBuffers.length; i < n; ++i) { + indiceBuffers[i].destroy(); + } + this._indiceBuffers = null; + } + + private _createMesh(engine: Engine, index: number): BufferMesh { + const { MAX_VERTEX_COUNT } = Basic2DBatcher; + const mesh = new BufferMesh(engine, `BufferMesh${index}`); + + const vertexElements: VertexElement[] = []; + const vertexStride = this.createVertexElements(vertexElements); + + // vertices + this._vertexBuffers[index] = new Buffer( + engine, + BufferBindFlag.VertexBuffer, + MAX_VERTEX_COUNT * 4 * vertexStride, + BufferUsage.Dynamic + ); + // indices + this._indiceBuffers[index] = new Buffer( + engine, + BufferBindFlag.IndexBuffer, + MAX_VERTEX_COUNT * 3, + BufferUsage.Dynamic + ); + mesh.setVertexBufferBinding(this._vertexBuffers[index], vertexStride); + mesh.setIndexBufferBinding(this._indiceBuffers[index], IndexFormat.UInt16); + mesh.setVertexElements(vertexElements); + + return mesh; + } + + private _updateData(engine: Engine): void { + const { _meshes, _flushId } = this; + + if (!Basic2DBatcher._canUploadSameBuffer && this._meshCount <= _flushId) { + this._meshCount++; + _meshes[_flushId] = this._createMesh(engine, _flushId); + } + + const { _batchedQueue: batchedQueue, _vertices: vertices, _indices: indices } = this; + const mesh = _meshes[_flushId]; + mesh.clearSubMesh(); + + let vertexIndex = 0; + let indiceIndex = 0; + let vertexStartIndex = 0; + let vertexCount = 0; + let curIndiceStartIndex = 0; + let curMeshIndex = 0; + let preElement: Element = null; + for (let i = 0, len = batchedQueue.length; i < len; i++) { + const curElement = batchedQueue[i]; + + // Batch vertex + vertexIndex = this.updateVertices(curElement, vertices, vertexIndex); + + // Batch indice + const { triangles } = curElement; + const triangleNum = triangles.length; + for (let j = 0; j < triangleNum; j++) { + indices[indiceIndex++] = triangles[j] + curIndiceStartIndex; + } + + curIndiceStartIndex += curElement.positions.length; + + if (preElement === null) { + vertexCount += triangleNum; + } else { + if (this.canBatch(preElement, curElement)) { + vertexCount += triangleNum; + } else { + mesh.addSubMesh(this._getSubMeshFromPool(vertexStartIndex, vertexCount)); + vertexStartIndex += vertexCount; + vertexCount = triangleNum; + batchedQueue[curMeshIndex++] = preElement; + } + } + + preElement = curElement; + } + + mesh.addSubMesh(this._getSubMeshFromPool(vertexStartIndex, vertexCount)); + batchedQueue[curMeshIndex] = preElement; + + this._vertexBuffers[_flushId].setData(vertices, 0, 0, vertexIndex); + this._indiceBuffers[_flushId].setData(indices, 0, 0, indiceIndex); + } + + private _getSubMeshFromPool(start: number, count: number): SubMesh { + const subMesh = this._subMeshPool.getFromPool(); + subMesh.start = start; + subMesh.count = count; + subMesh.topology = MeshTopology.Triangles; + return subMesh; + } + + /** + * @internal + */ + abstract createVertexElements(vertexElements: VertexElement[]): number; + + /** + * @internal + */ + abstract canBatch(preElement: Element, curElement: Element): boolean; + + /** + * @internal + */ + abstract updateVertices(element: Element, vertices: Float32Array, vertexIndex: number): number; + + /** + * @internal + */ + abstract drawBatches(engine: Engine): void; +} diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index b6ed733752..e8ca5278af 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -1,8 +1,17 @@ -import { Vector4 } from "@oasis-engine/math"; +import { Matrix } from "@oasis-engine/math"; +import { SpriteMask } from "../2d/sprite/SpriteMask"; +import { Logger } from "../base/Logger"; import { Camera } from "../Camera"; +import { DisorderedArray } from "../DisorderedArray"; +import { Engine } from "../Engine"; +import { BackgroundMode } from "../enums/BackgroundMode"; +import { CameraClearFlags } from "../enums/CameraClearFlags"; import { Layer } from "../Layer"; -import { RenderQueueType } from "../material"; +import { RenderQueueType } from "../material/enums/RenderQueueType"; import { Material } from "../material/Material"; +import { Shader } from "../shader/Shader"; +import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; +import { Sky } from "../sky/Sky"; import { TextureCubeFace } from "../texture/enums/TextureCubeFace"; import { RenderTarget } from "../texture/RenderTarget"; import { RenderContext } from "./RenderContext"; @@ -21,6 +30,8 @@ export class BasicRenderPipeline { _transparentQueue: RenderQueue; /** @internal */ _alphaTestQueue: RenderQueue; + /** @internal */ + _allSpriteMasks: DisorderedArray = new DisorderedArray(); private _camera: Camera; private _defaultPass: RenderPass; @@ -32,9 +43,10 @@ export class BasicRenderPipeline { */ constructor(camera: Camera) { this._camera = camera; - this._opaqueQueue = new RenderQueue(camera.engine); - this._alphaTestQueue = new RenderQueue(camera.engine); - this._transparentQueue = new RenderQueue(camera.engine); + const { engine } = camera; + this._opaqueQueue = new RenderQueue(engine); + this._alphaTestQueue = new RenderQueue(engine); + this._transparentQueue = new RenderQueue(engine); this._renderPassArray = []; this._defaultPass = new RenderPass("default", 0, null, null, 0); @@ -55,18 +67,16 @@ export class BasicRenderPipeline { * @param renderTarget - The specified Render Target * @param replaceMaterial - Replaced material * @param mask - Perform bit and operations with Entity.Layer to filter the objects that this Pass needs to render - * @param clearParam - Clear the background color of renderTarget */ addRenderPass( nameOrPass: string | RenderPass, priority: number = null, renderTarget: RenderTarget = null, replaceMaterial: Material = null, - mask: Layer = null, - clearParam = new Vector4(0, 0, 0, 0) + mask: Layer = null ) { if (typeof nameOrPass === "string") { - const renderPass = new RenderPass(nameOrPass, priority, renderTarget, replaceMaterial, mask, clearParam); + const renderPass = new RenderPass(nameOrPass, priority, renderTarget, replaceMaterial, mask); this._renderPassArray.push(renderPass); } else if (nameOrPass instanceof RenderPass) { this._renderPassArray.push(nameOrPass); @@ -111,6 +121,7 @@ export class BasicRenderPipeline { this._opaqueQueue.destroy(); this._alphaTestQueue.destroy(); this._transparentQueue.destroy(); + this._allSpriteMasks = null; this._renderPassArray = null; this._defaultPass = null; this._camera = null; @@ -127,9 +138,13 @@ export class BasicRenderPipeline { const alphaTestQueue = this._alphaTestQueue; const transparentQueue = this._transparentQueue; + camera.engine._spriteMaskManager.clear(); + opaqueQueue.clear(); alphaTestQueue.clear(); transparentQueue.clear(); + this._allSpriteMasks.length = 0; + camera.engine._componentsManager.callRender(context); opaqueQueue.sort(RenderQueue._compareFromNearToFar); alphaTestQueue.sort(RenderQueue._compareFromNearToFar); @@ -144,17 +159,26 @@ export class BasicRenderPipeline { pass.preRender(camera, this._opaqueQueue, this._alphaTestQueue, this._transparentQueue); if (pass.enabled) { - const rhi = camera.scene.engine._hardwareRenderer; + const { engine, scene } = camera; + const { background } = scene; + const rhi = engine._hardwareRenderer; const renderTarget = camera.renderTarget || pass.renderTarget; rhi.activeRenderTarget(renderTarget, camera); renderTarget?._setRenderTargetFace(cubeFace); - rhi.clearRenderTarget(camera.engine, pass.clearMode, pass.clearParam); + const clearFlags = pass.clearFlags ?? camera.clearFlags; + const color = pass.clearColor ?? background.solidColor; + if (clearFlags !== CameraClearFlags.None) { + rhi.clearRenderTarget(camera.engine, clearFlags, color); + } if (pass.renderOverride) { pass.render(camera, this._opaqueQueue, this._alphaTestQueue, this._transparentQueue); } else { this._opaqueQueue.render(camera, pass.replaceMaterial, pass.mask); this._alphaTestQueue.render(camera, pass.replaceMaterial, pass.mask); + if (background.mode === BackgroundMode.Sky) { + this._drawSky(engine, camera, background.sky); + } this._transparentQueue.render(camera, pass.replaceMaterial, pass.mask); } @@ -180,4 +204,37 @@ export class BasicRenderPipeline { this._opaqueQueue.pushPrimitive(element); } } + + private _drawSky(engine: Engine, camera: Camera, sky: Sky): void { + const { material, mesh, _matrix } = sky; + if (!material) { + Logger.warn("The material of sky is not defined."); + return; + } + if (!mesh) { + Logger.warn("The mesh of sky is not defined."); + return; + } + + const rhi = engine._hardwareRenderer; + const { shaderData, shader, renderState } = material; + + const compileMacros = Shader._compileMacros; + ShaderMacroCollection.unionCollection(camera._globalShaderMacro, shaderData._macroCollection, compileMacros); + + const { viewMatrix, projectionMatrix } = camera; + viewMatrix.cloneTo(_matrix); + const e = _matrix.elements; + e[12] = e[13] = e[14] = 0; + Matrix.multiply(projectionMatrix, _matrix, _matrix); + shaderData.setMatrix("u_mvpNoscale", _matrix); + + const program = shader._getShaderProgram(engine, compileMacros); + program.bind(); + program.uploadAll(program.materialUniformBlock, shaderData); + program.uploadUngroupTextures(); + + renderState._apply(engine); + rhi.drawPrimitive(mesh, mesh.subMesh, program); + } } diff --git a/packages/core/src/RenderPipeline/RenderPass.ts b/packages/core/src/RenderPipeline/RenderPass.ts index ea7a747149..3812843cf8 100644 --- a/packages/core/src/RenderPipeline/RenderPass.ts +++ b/packages/core/src/RenderPipeline/RenderPass.ts @@ -1,6 +1,6 @@ -import { Vector4 } from "@oasis-engine/math"; -import { ClearMode } from "../base/Constant"; +import { Color } from "@oasis-engine/math"; import { Camera } from "../Camera"; +import { CameraClearFlags } from "../enums/CameraClearFlags"; import { Layer } from "../Layer"; import { Material } from "../material/Material"; import { RenderTarget } from "../texture/RenderTarget"; @@ -19,8 +19,8 @@ class RenderPass { public replaceMaterial: Material; public mask: Layer; public renderOverride: boolean; - public clearMode; - private _clearParam; + public clearFlags: CameraClearFlags | undefined; + public clearColor: Color | undefined; /** * Create a RenderPass. @@ -29,15 +29,13 @@ class RenderPass { * @param renderTarget - The specified Render Target * @param replaceMaterial - Replaced material * @param mask - Perform bit and operations with Entity.Layer to filter the objects that this Pass needs to render - * @param clearParam - Clear the background color of renderTarget */ constructor( name = `RENDER_PASS${passNum++}`, priority = 0, renderTarget = null, replaceMaterial = null, - mask = null, - clearParam = new Vector4(0, 0, 0, 0) + mask = null ) { this.name = name; this.enabled = true; @@ -46,20 +44,6 @@ class RenderPass { this.replaceMaterial = replaceMaterial; this.mask = mask || Layer.Everything; this.renderOverride = false; // If renderOverride is set to true, you need to implement the render method - - this.clearMode = ClearMode.SOLID_COLOR; - this._clearParam = clearParam; // PASS use render target's clearParam - } - - /** - * Canvas clear parameters, the default is to use the clearColor of RenderTarget. - */ - get clearParam() { - return this._clearParam; - } - - set clearParam(v) { - this._clearParam = v; } /** diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 3623f98da9..4ee0742a31 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -143,7 +143,7 @@ export class RenderQueue { rhi.drawPrimitive(element.mesh, element.subMesh, program); } else { const spirteElement = item; - this._spriteBatcher.drawSprite(spirteElement); + this._spriteBatcher.drawElement(spirteElement); } } diff --git a/packages/core/src/RenderPipeline/SpriteBatcher.ts b/packages/core/src/RenderPipeline/SpriteBatcher.ts index 0953cbe414..453b1fb394 100644 --- a/packages/core/src/RenderPipeline/SpriteBatcher.ts +++ b/packages/core/src/RenderPipeline/SpriteBatcher.ts @@ -1,191 +1,96 @@ +import { SpriteMaskInteraction } from "../2d/enums/SpriteMaskInteraction"; +import { SpriteRenderer } from "../2d/sprite/SpriteRenderer"; import { Engine } from "../Engine"; -import { MeshTopology, SubMesh } from "../graphic"; -import { Buffer } from "../graphic/Buffer"; -import { BufferBindFlag } from "../graphic/enums/BufferBindFlag"; -import { BufferUsage } from "../graphic/enums/BufferUsage"; -import { IndexFormat } from "../graphic/enums/IndexFormat"; import { VertexElementFormat } from "../graphic/enums/VertexElementFormat"; import { VertexElement } from "../graphic/VertexElement"; -import { BufferMesh } from "../mesh/BufferMesh"; -import { Shader } from "../shader"; +import { Shader } from "../shader/Shader"; import { ShaderProperty } from "../shader/ShaderProperty"; +import { Basic2DBatcher } from "./Basic2DBatcher"; import { SpriteElement } from "./SpriteElement"; /** * @internal */ -export class SpriteBatcher { +export class SpriteBatcher extends Basic2DBatcher { private static _textureProperty: ShaderProperty = Shader.getPropertyByName("u_spriteTexture"); - /** The maximum number of vertex. */ - private static MAX_VERTEX_COUNT: number = 4096; - private static _canUploadSameBuffer: boolean = false; - private static _subMeshPool: SubMesh[] = []; - private static _subMeshPoolIndex: number = 0; - static _getSubMeshFromPool(start: number, count: number, topology: MeshTopology = MeshTopology.Triangles): SubMesh { - const { _subMeshPoolIndex: index, _subMeshPool: pool } = SpriteBatcher; - SpriteBatcher._subMeshPoolIndex++; - let subMesh: SubMesh = null; - - if (pool.length === index) { - subMesh = new SubMesh(start, count, topology); - pool.push(subMesh); - } else { - subMesh = pool[index]; - subMesh.start = start; - subMesh.count = count; - subMesh.topology = topology; - } - - return subMesh; + createVertexElements(vertexElements: VertexElement[]): number { + vertexElements[0] = new VertexElement("POSITION", 0, VertexElementFormat.Vector3, 0); + vertexElements[1] = new VertexElement("TEXCOORD_0", 12, VertexElementFormat.Vector2, 0); + vertexElements[2] = new VertexElement("COLOR_0", 20, VertexElementFormat.Vector4, 0); + return 36; } - /** - * @internal - */ - static _restPool() { - SpriteBatcher._subMeshPoolIndex = 0; - } - - private _batchedQueue: SpriteElement[] = []; - private _meshes: BufferMesh[] = []; - private _meshCount: number = 1; - private _vertexBuffers: Buffer[] = []; - private _indiceBuffers: Buffer[] = []; - private _vertices: Float32Array; - private _indices: Uint16Array; - private _vertexCount: number = 0; - private _spriteCount: number = 0; - private _flushId: number = 0; - - constructor(engine: Engine) { - const { MAX_VERTEX_COUNT } = SpriteBatcher; - this._vertices = new Float32Array(MAX_VERTEX_COUNT * 9); - this._indices = new Uint16Array(MAX_VERTEX_COUNT * 3); + canBatch(preElement: SpriteElement, curElement: SpriteElement): boolean { + const preRenderer = preElement.component; + const curRenderer = curElement.component; - const { _meshes, _meshCount } = this; - for (let i = 0; i < _meshCount; i++) { - _meshes[i] = this._createMesh(engine, i); + // Compare mask + if (!this.checkBatchWithMask(preRenderer, curRenderer)) { + return false; } - } - - private _createMesh(engine: Engine, index: number): BufferMesh { - const { MAX_VERTEX_COUNT } = SpriteBatcher; - const mesh = new BufferMesh(engine, `SpriteBatchBufferMesh${index}`); - const vertexElements = [ - new VertexElement("POSITION", 0, VertexElementFormat.Vector3, 0), - new VertexElement("TEXCOORD_0", 12, VertexElementFormat.Vector2, 0), - new VertexElement("COLOR_0", 20, VertexElementFormat.Vector4, 0) - ]; - const vertexStride = 36; - - // vertices - this._vertexBuffers[index] = new Buffer( - engine, - BufferBindFlag.VertexBuffer, - MAX_VERTEX_COUNT * 4 * vertexStride, - BufferUsage.Dynamic - ); - // indices - this._indiceBuffers[index] = new Buffer( - engine, - BufferBindFlag.IndexBuffer, - MAX_VERTEX_COUNT * 3, - BufferUsage.Dynamic - ); - mesh.setVertexBufferBinding(this._vertexBuffers[index], vertexStride); - mesh.setIndexBufferBinding(this._indiceBuffers[index], IndexFormat.UInt16); - mesh.setVertexElements(vertexElements); + // Compare renderer property + const textureProperty = SpriteBatcher._textureProperty; + if (preRenderer.shaderData.getTexture(textureProperty) !== curRenderer.shaderData.getTexture(textureProperty)) { + return false; + } - return mesh; + // Compare material + return preElement.material === curElement.material; } - private _updateData(engine: Engine): void { - const { _meshes, _flushId } = this; + checkBatchWithMask(left: SpriteRenderer, right: SpriteRenderer): boolean { + const leftMaskInteraction = left.maskInteraction; - if (!SpriteBatcher._canUploadSameBuffer && this._meshCount <= _flushId) { - this._meshCount++; - _meshes[_flushId] = this._createMesh(engine, _flushId); + if (leftMaskInteraction !== right.maskInteraction) { + return false; } - - const { _getSubMeshFromPool } = SpriteBatcher; - const { _batchedQueue, _vertices, _indices } = this; - const mesh = _meshes[_flushId]; - mesh.clearSubMesh(); - - let vertexIndex = 0; - let indiceIndex = 0; - let vertexStartIndex = 0; - let vertexCount = 0; - let curIndiceStartIndex = 0; - let curMeshIndex = 0; - let preSpriteElement: SpriteElement = null; - for (let i = 0, len = _batchedQueue.length; i < len; i++) { - const curSpriteElement = _batchedQueue[i]; - const { positions, uv, triangles, color } = curSpriteElement; - - // Batch vertex - const verticesNum = positions.length; - for (let j = 0; j < verticesNum; j++) { - const curPos = positions[j]; - const curUV = uv[j]; - - _vertices[vertexIndex++] = curPos.x; - _vertices[vertexIndex++] = curPos.y; - _vertices[vertexIndex++] = curPos.z; - _vertices[vertexIndex++] = curUV.x; - _vertices[vertexIndex++] = curUV.y; - _vertices[vertexIndex++] = color.r; - _vertices[vertexIndex++] = color.g; - _vertices[vertexIndex++] = color.b; - _vertices[vertexIndex++] = color.a; - } - - // Batch indice - const triangleNum = triangles.length; - for (let j = 0; j < triangleNum; j++) { - _indices[indiceIndex++] = triangles[j] + curIndiceStartIndex; - } - - curIndiceStartIndex += verticesNum; - - if (preSpriteElement === null) { - vertexCount += triangleNum; - } else { - if (this._canBatch(preSpriteElement, curSpriteElement)) { - vertexCount += triangleNum; - } else { - mesh.addSubMesh(_getSubMeshFromPool(vertexStartIndex, vertexCount)); - vertexStartIndex += vertexCount; - vertexCount = triangleNum; - _batchedQueue[curMeshIndex++] = preSpriteElement; - } - } - - preSpriteElement = curSpriteElement; + if (leftMaskInteraction === SpriteMaskInteraction.None) { + return true; } + return left.maskLayer === right.maskLayer; + } - mesh.addSubMesh(_getSubMeshFromPool(vertexStartIndex, vertexCount)); - _batchedQueue[curMeshIndex] = preSpriteElement; + updateVertices(element: SpriteElement, vertices: Float32Array, vertexIndex: number): number { + const { positions, uv, color } = element; + const verticesNum = positions.length; + for (let i = 0; i < verticesNum; i++) { + const curPos = positions[i]; + const curUV = uv[i]; + + vertices[vertexIndex++] = curPos.x; + vertices[vertexIndex++] = curPos.y; + vertices[vertexIndex++] = curPos.z; + vertices[vertexIndex++] = curUV.x; + vertices[vertexIndex++] = curUV.y; + vertices[vertexIndex++] = color.r; + vertices[vertexIndex++] = color.g; + vertices[vertexIndex++] = color.b; + vertices[vertexIndex++] = color.a; + } - this._vertexBuffers[_flushId].setData(_vertices, 0, 0, vertexIndex); - this._indiceBuffers[_flushId].setData(_indices, 0, 0, indiceIndex); + return vertexIndex; } - private _drawBatches(engine: Engine): void { + drawBatches(engine: Engine): void { const mesh = this._meshes[this._flushId]; const subMeshes = mesh.subMeshes; - const { _batchedQueue } = this; + const batchedQueue = this._batchedQueue; + const maskManager = engine._spriteMaskManager; for (let i = 0, len = subMeshes.length; i < len; i++) { const subMesh = subMeshes[i]; - const spriteElement = _batchedQueue[i]; + const spriteElement = batchedQueue[i]; if (!subMesh || !spriteElement) { return; } + const renderer = spriteElement.component; + const camera = spriteElement.camera; + maskManager.preRender(camera, renderer); + const compileMacros = Shader._compileMacros; compileMacros.clear(); @@ -197,70 +102,17 @@ export class SpriteBatcher { program.bind(); program.groupingOtherUniformBlock(); - const camera = spriteElement.camera; program.uploadAll(program.sceneUniformBlock, camera.scene.shaderData); program.uploadAll(program.cameraUniformBlock, camera.shaderData); - program.uploadAll(program.rendererUniformBlock, spriteElement.component.shaderData); + program.uploadAll(program.rendererUniformBlock, renderer.shaderData); program.uploadAll(program.materialUniformBlock, material.shaderData); material.renderState._apply(engine); engine._hardwareRenderer.drawPrimitive(mesh, subMesh, program); - } - } - private _canBatch(preSpriteElement: SpriteElement, curSpriteElement: SpriteElement): boolean { - // Currently only compare texture - const { _textureProperty } = SpriteBatcher; - const preTexture = preSpriteElement.component.shaderData.getTexture(_textureProperty); - const curTexture = curSpriteElement.component.shaderData.getTexture(_textureProperty); - if (preTexture !== curTexture) { - return false; + maskManager.postRender(renderer); } - - return ( - preSpriteElement.material === curSpriteElement.material && preSpriteElement.camera === curSpriteElement.camera - ); - } - - /** - * Flush all sprites. - */ - flush(engine: Engine): void { - const { _batchedQueue } = this; - - if (_batchedQueue.length === 0) { - return; - } - - this._updateData(engine); - this._drawBatches(engine); - - if (!SpriteBatcher._canUploadSameBuffer) { - this._flushId++; - } - - SpriteBatcher._restPool(); - this._batchedQueue.length = 0; - this._vertexCount = 0; - this._spriteCount = 0; - } - - drawSprite(spriteElement: SpriteElement): void { - const len = spriteElement.positions.length; - if (this._vertexCount + len > SpriteBatcher.MAX_VERTEX_COUNT) { - this.flush(spriteElement.camera.engine); - } - - this._vertexCount += len; - this._batchedQueue[this._spriteCount++] = spriteElement; - } - - clear(): void { - this._flushId = 0; - this._vertexCount = 0; - this._spriteCount = 0; - this._batchedQueue.length = 0; } destroy(): void { diff --git a/packages/core/src/RenderPipeline/SpriteElement.ts b/packages/core/src/RenderPipeline/SpriteElement.ts index 92afca4747..133770318b 100644 --- a/packages/core/src/RenderPipeline/SpriteElement.ts +++ b/packages/core/src/RenderPipeline/SpriteElement.ts @@ -1,6 +1,6 @@ import { Color, Vector2, Vector3 } from "@oasis-engine/math"; import { Camera } from "../Camera"; -import { Material } from "../material"; +import { Material } from "../material/Material"; import { Renderer } from "../Renderer"; export class SpriteElement { diff --git a/packages/core/src/RenderPipeline/SpriteMaskBatcher.ts b/packages/core/src/RenderPipeline/SpriteMaskBatcher.ts new file mode 100644 index 0000000000..dad4df0881 --- /dev/null +++ b/packages/core/src/RenderPipeline/SpriteMaskBatcher.ts @@ -0,0 +1,94 @@ +import { SpriteMask } from "../2d/sprite/SpriteMask"; +import { Engine } from "../Engine"; +import { VertexElementFormat } from "../graphic/enums/VertexElementFormat"; +import { VertexElement } from "../graphic/VertexElement"; +import { StencilOperation } from "../shader/enums/StencilOperation"; +import { Shader } from "../shader/Shader"; +import { Basic2DBatcher } from "./Basic2DBatcher"; +import { SpriteMaskElement } from "./SpriteMaskElement"; + +export class SpriteMaskBatcher extends Basic2DBatcher { + createVertexElements(vertexElements: VertexElement[]): number { + vertexElements[0] = new VertexElement("POSITION", 0, VertexElementFormat.Vector3, 0); + vertexElements[1] = new VertexElement("TEXCOORD_0", 12, VertexElementFormat.Vector2, 0); + return 20; + } + + canBatch(preElement: SpriteMaskElement, curElement: SpriteMaskElement): boolean { + if (preElement.isAdd !== curElement.isAdd) { + return false; + } + + // Compare renderer property + const preShaderData = (preElement.component).shaderData; + const curShaderData = (curElement.component).shaderData; + const textureProperty = SpriteMask._textureProperty; + const alphaCutoffProperty = SpriteMask._alphaCutoffProperty; + + return ( + preShaderData.getTexture(textureProperty) === curShaderData.getTexture(textureProperty) && + preShaderData.getTexture(alphaCutoffProperty) === curShaderData.getTexture(alphaCutoffProperty) + ); + } + + updateVertices(element: SpriteMaskElement, vertices: Float32Array, vertexIndex: number): number { + const { positions, uv } = element; + const verticesNum = positions.length; + for (let i = 0; i < verticesNum; i++) { + const curPos = positions[i]; + const curUV = uv[i]; + + vertices[vertexIndex++] = curPos.x; + vertices[vertexIndex++] = curPos.y; + vertices[vertexIndex++] = curPos.z; + vertices[vertexIndex++] = curUV.x; + vertices[vertexIndex++] = curUV.y; + } + + return vertexIndex; + } + + drawBatches(engine: Engine): void { + const mesh = this._meshes[this._flushId]; + const subMeshes = mesh.subMeshes; + const batchedQueue = this._batchedQueue; + + for (let i = 0, len = subMeshes.length; i < len; i++) { + const subMesh = subMeshes[i]; + const spriteMaskElement = batchedQueue[i]; + + if (!subMesh || !spriteMaskElement) { + return; + } + + const compileMacros = Shader._compileMacros; + compileMacros.clear(); + + const material = spriteMaskElement.material; + // Update stencil state + const stencilState = material.renderState.stencilState; + const op = spriteMaskElement.isAdd ? StencilOperation.IncrementSaturate : StencilOperation.DecrementSaturate; + stencilState.passOperationFront = op; + stencilState.passOperationBack = op; + + const program = material.shader._getShaderProgram(engine, compileMacros); + if (!program.isValid) { + return; + } + + const camera = spriteMaskElement.camera; + const renderer = spriteMaskElement.component; + + program.bind(); + program.groupingOtherUniformBlock(); + program.uploadAll(program.sceneUniformBlock, camera.scene.shaderData); + program.uploadAll(program.cameraUniformBlock, camera.shaderData); + program.uploadAll(program.rendererUniformBlock, renderer.shaderData); + program.uploadAll(program.materialUniformBlock, material.shaderData); + + material.renderState._apply(engine); + + engine._hardwareRenderer.drawPrimitive(mesh, subMesh, program); + } + } +} diff --git a/packages/core/src/RenderPipeline/SpriteMaskElement.ts b/packages/core/src/RenderPipeline/SpriteMaskElement.ts new file mode 100644 index 0000000000..4f83015466 --- /dev/null +++ b/packages/core/src/RenderPipeline/SpriteMaskElement.ts @@ -0,0 +1,22 @@ +import { Vector2, Vector3 } from "@oasis-engine/math"; +import { Camera } from "../Camera"; +import { Component } from "../Component"; +import { Material } from "../material/Material"; + +export class SpriteMaskElement { + component: Component; + positions: Vector3[]; + uv: Vector2[]; + triangles: number[]; + material: Material; + isAdd: boolean = true; + camera: Camera; + + setValue(component: Component, positions: Vector3[], uv: Vector2[], triangles: number[], material: Material): void { + this.component = component; + this.positions = positions; + this.uv = uv; + this.triangles = triangles; + this.material = material; + } +} diff --git a/packages/core/src/RenderPipeline/SpriteMaskManager.ts b/packages/core/src/RenderPipeline/SpriteMaskManager.ts new file mode 100644 index 0000000000..a1631c4101 --- /dev/null +++ b/packages/core/src/RenderPipeline/SpriteMaskManager.ts @@ -0,0 +1,80 @@ +import { SpriteMaskInteraction } from "../2d/enums/SpriteMaskInteraction"; +import { SpriteRenderer } from "../2d/sprite/SpriteRenderer"; +import { Camera } from "../Camera"; +import { Engine } from "../Engine"; +import { SpriteMaskBatcher } from "./SpriteMaskBatcher"; + +/** + * @internal + */ +export class SpriteMaskManager { + _batcher: SpriteMaskBatcher; + + private _preMaskLayer: number = 0; + + constructor(engine: Engine) { + this._batcher = new SpriteMaskBatcher(engine); + } + + clear(): void { + this._preMaskLayer = 0; + this._batcher.clear(); + } + + preRender(camera: Camera, renderer: SpriteRenderer): void { + if (renderer.maskInteraction === SpriteMaskInteraction.None) { + return; + } + + this._batcher.clear(); + this._processMasksDiff(camera, renderer); + this._batcher.flush(camera.engine); + } + + postRender(renderer: SpriteRenderer): void { + if (renderer.maskInteraction === SpriteMaskInteraction.None) { + return; + } + + this._preMaskLayer = renderer.maskLayer; + } + + destroy(): void { + this._batcher.destroy(); + this._batcher = null; + } + + private _processMasksDiff(camera: Camera, renderer: SpriteRenderer): void { + const preMaskLayer = this._preMaskLayer; + const curMaskLayer = renderer.maskLayer; + if (preMaskLayer !== curMaskLayer) { + const allMasks = camera._renderPipeline._allSpriteMasks; + const commonLayer = preMaskLayer & curMaskLayer; + const addLayer = curMaskLayer & ~preMaskLayer; + const reduceLayer = preMaskLayer & ~curMaskLayer; + + const allMaskElements = allMasks._elements; + for (let i = 0, n = allMasks.length; i < n; i++) { + const mask = allMaskElements[i]; + const influenceLayers = mask.influenceLayers; + + if (influenceLayers & commonLayer) { + continue; + } + + if (influenceLayers & addLayer) { + const maskRenderElement = mask._maskElement; + maskRenderElement.isAdd = true; + this._batcher.drawElement(maskRenderElement); + continue; + } + + if (influenceLayers & reduceLayer) { + const maskRenderElement = mask._maskElement; + maskRenderElement.isAdd = false; + this._batcher.drawElement(maskRenderElement); + } + } + } + } +} diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index d5080cbcb7..6cb822ff6f 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -300,7 +300,7 @@ export abstract class Renderer extends Component { } } - protected _updateBounds(worldBounds: any): void {} + protected _updateBounds(worldBounds: BoundingBox): void {} private _createInstanceMaterial(material: Material, index: number): Material { const insMaterial: Material = material.clone(); diff --git a/packages/core/src/Scene.ts b/packages/core/src/Scene.ts index 990109ef8a..afba979b08 100644 --- a/packages/core/src/Scene.ts +++ b/packages/core/src/Scene.ts @@ -1,10 +1,12 @@ -import { Vector2, Vector3, Vector4 } from "@oasis-engine/math"; +import { Vector2, Vector3 } from "@oasis-engine/math"; +import { Background } from "./Background"; import { EngineObject, GLCapabilityType, Logger } from "./base"; import { Camera } from "./Camera"; import { Engine } from "./Engine"; import { Entity } from "./Entity"; import { FeatureManager } from "./FeatureManager"; import { Layer } from "./Layer"; +import { AmbientLight } from "./lighting/AmbientLight"; import { LightFeature } from "./lighting/LightFeature"; import { SceneFeature } from "./SceneFeature"; import { ShaderDataGroup } from "./shader/enums/ShaderDataGroup"; @@ -15,34 +17,30 @@ import { ShaderData } from "./shader/ShaderData"; * Scene. */ export class Scene extends EngineObject { - private static _resolutionProperty = Shader.getPropertyByName("u_resolution"); - static sceneFeatureManager = new FeatureManager(); - /** scene-related shaderdata */ - readonly shaderData: ShaderData = new ShaderData(ShaderDataGroup.Scene); + private static _resolutionProperty = Shader.getPropertyByName("u_resolution"); - /** scene name */ + /** Scene name. */ name: string; + /** The background of the scene. */ + readonly background: Background = new Background(); + /** Ambient light. */ + readonly ambientLight: AmbientLight; + /** Scene-related shader data. */ + readonly shaderData: ShaderData = new ShaderData(ShaderDataGroup.Scene); + /** @internal */ _activeCameras: Camera[] = []; + /** @internal */ _isActiveInEngine: boolean = false; private _destroyed: boolean = false; private _rootEntities: Entity[] = []; private _resolution: Vector2 = new Vector2(); - /** - * Get the scene's engine. - * @readonly - */ - get engine(): Engine { - return this._engine; - } - /** * Count of root entities. - * @readonly */ get rootEntitiesCount(): number { return this._rootEntities.length; @@ -50,7 +48,6 @@ export class Scene extends EngineObject { /** * Root entity collection. - * @readonly */ get rootEntities(): Readonly { return this._rootEntities; @@ -58,7 +55,6 @@ export class Scene extends EngineObject { /** * Whether it's destroyed. - * @readonly */ get destroyed(): boolean { return this._destroyed; @@ -76,6 +72,7 @@ export class Scene extends EngineObject { const shaderData = this.shaderData; Scene.sceneFeatureManager.addObject(this); shaderData._addRefCount(1); + this.ambientLight = new AmbientLight(this); // @todo: this is deviec macro,should add when compile shader. if (this._engine._hardwareRenderer.canIUse(GLCapabilityType.shaderTextureLod)) { diff --git a/packages/core/src/animation/AnimationClip.ts b/packages/core/src/animation/AnimationClip.ts index 3ea88f3566..cfc3b05522 100644 --- a/packages/core/src/animation/AnimationClip.ts +++ b/packages/core/src/animation/AnimationClip.ts @@ -44,7 +44,7 @@ export class AnimationClip extends EngineObject { * @param _input - The index of an accessor containing keyframe input values. * @param _output - The index of an accessor containing keyframe output values. * @param _outputSize - The length of the output values. - * @param _interpolation - Interpolation algorithm. + * @param _interpolation - Interpolation algorithm. */ public addSampler( _input: List, diff --git a/packages/core/src/animation/AnimationConst.ts b/packages/core/src/animation/AnimationConst.ts index dae765f1c7..121e8a9648 100644 --- a/packages/core/src/animation/AnimationConst.ts +++ b/packages/core/src/animation/AnimationConst.ts @@ -1,6 +1,5 @@ /** * Animation wrap mode. - * @readonly */ export enum WrapMode { /** Play once */ @@ -13,7 +12,6 @@ export enum WrapMode { /** * Animation event type. - * @readonly */ export enum AnimationEventType { /** Triggered when the animation over if the wrapMode === WrapMode.ONCE */ @@ -26,7 +24,6 @@ export enum AnimationEventType { /** * Animation interpolation method. - * @readonly */ export enum InterpolationType { /** Linear interpolation */ diff --git a/packages/core/src/base/Constant.ts b/packages/core/src/base/Constant.ts index 17c29620a0..ef3a2d6853 100644 --- a/packages/core/src/base/Constant.ts +++ b/packages/core/src/base/Constant.ts @@ -1,126 +1,3 @@ -/** - * The type of resource, mainly used to deal with the recovery of GL objects associated with the resource object. - */ -export enum InternalAssetType { - /** Belonging to the current scene, GL resources will be automatically released when the scene is switched. */ - Scene = 1, - /** Cache automatically handles, if not used in a period of time, GL resources will be released. */ - Cache = 2 -} - -/** - * Camera's clear mode enumeration - * @readonly - */ -export enum ClearMode { - /** Do not perform any operations to clear the background */ - DONT_CLEAR = 0, - /** Clear the background color and depth buffer */ - SOLID_COLOR = 1, - /** Only clear the depth buffer */ - DEPTH_ONLY = 2, - /** Only clear colors */ - COLOR_ONLY = 3, - /** Only clear the template buffer */ - STENCIL_ONLY = 4, - /** Clear all buffers */ - ALL_CLEAR = 5 -} - -/** - * Material type enumeration - * @readonly - */ -export enum MaterialType { - /** Opaque */ - OPAQUE = 1000, - /** Transparent */ - TRANSPARENT = 2000 -} - -/** - * Rendering state that can be turned on or off. - * @readonly - */ -export enum RenderState { - /** Color blend calculation of fragments */ - BLEND = 3042, - /** Front and back culling */ - CULL_FACE = 2884, - /** Depth test */ - DEPTH_TEST = 2929, - /** Alpha test */ - ALPHA_TEST = 3008, - /** Offset of the depth value of the polygon fragment. */ - POLYGON_OFFSET_FILL = 32823, - /** Calculation of temporary coverage value determined by alpha value. */ - SAMPLE_ALPHA_TO_COVERAGE = 32926, - /** Clipping test, discarding the fragments outside the clipping rectangle. */ - SCISSOR_TEST = 3089 -} - -/** - * Face enumeration - * @readonly - */ -export enum FrontFace { - /** Clockwise */ - CW = 0x0900, - /** Counterclockwise */ - CCW = 0x0901 -} - -/** - * Face culling enum - * @readonly - */ -export enum CullFace { - /** Front */ - FRONT = 1028, - /** Back */ - BACK = 1029, - /** Front and back */ - FRONT_AND_BACK = 1032 -} - -/** - * Display surface enumeration - * @readonly - * */ -export enum Side { - /** Cull the back, only show the front */ - FRONT, - /** Cull the front, only show the back */ - BACK, - /** Cull out before smoothing, without showing any surface */ - NONE, - /** Turn off culling, display the front and back */ - DOUBLE -} - -/** - * Comparison function enum - * @readonly - */ -export enum CompFunc { - /** Never pass */ - NEVER = 0x0200, - /** Pass when less than the reference value */ - LESS = 0x0201, - /** Pass when equal to reference value */ - EQUAL = 0x0202, - /** Pass when less than or equal to the reference value */ - LEQUAL = 0x0203, - /** Pass when greater than the reference value */ - GREATER = 0x0204, - /** Pass when not equal to reference value */ - NOTEQUAL = 0x0205, - /** Pass when greater than or equal to the reference value */ - GEQUAL = 0x0206, - /** Always pass */ - ALWAYS = 0x0207 -} - /** * Data type enumeration */ @@ -206,112 +83,6 @@ export enum DataType { UNSIGNED_INT = 5125 // gl.UNSIGNED_INT } -/** - * Uniform Semantic and Oasis3D extension supported by glTF 1.0 - * @readonly - */ -export enum UniformSemantic { - // -- GLTF - /** Local matrix */ - LOCAL = 1, - /** Model matrix */ - MODEL = 2, - /** View matrix */ - VIEW = 3, - /** Project matrix */ - PROJECTION = 4, - /** Model View matrix */ - MODELVIEW = 5, - /** View Projection matrix */ - VIEWPROJECTION = 21, - /** Model View Project matrix */ - MODELVIEWPROJECTION = 6, - /** Model matrix's inverse matrix */ - MODELINVERSE = 7, - /** View matrix's inverse matrix */ - VIEWINVERSE = 8, - /** Projection matrix's inverse matrix */ - PROJECTIONINVERSE = 9, - /** Model View matrix's inverse matrix */ - MODELVIEWINVERSE = 10, - /** Model View Project matrix's inverse matrix */ - MODELVIEWPROJECTIONINVERSE = 11, - /** The inverse transpose matrix of Model matrix, which can be used to transform Normal */ - MODELINVERSETRANSPOSE = 12, - /** Model View matrix's inverse transpose matrix */ - MODELVIEWINVERSETRANSPOSE = 13, - /** Viewport parameter */ - VIEWPORT = 14, - /** Joint matrix array */ - JOINTMATRIX = 15, - /** MorphTarget weights */ - MORPHWEIGHTS = 16, - - // -- - /** Current camera position */ - EYEPOS = 17, - /** How long the current program is running */ - TIME = 18, - /** Joint matrix texture */ - JOINTTEXTURE = 19, - /** Joint count */ - JOINTCOUNT = 20 -} - -/** - * Color blending method enumeration - */ -export enum BlendFunc { - /** Multiply all channels by 0 */ - ZERO = 0, - /** Multiply all channels by 1 */ - ONE = 1, - /** Multiply all channels by source color */ - SRC_COLOR = 768, - /** Multiply all channels by 1 minus source color */ - ONE_MINUS_SRC_COLOR = 769, - /** Multiply all channels by source alpha */ - SRC_ALPHA = 770, - /** Multiply all channels by 1 minus source alpha */ - ONE_MINUS_SRC_ALPHA = 771, - /** Multiply all channels by destination alpha */ - DST_ALPHA = 772, - /** Multiply all channels by 1 minus destination alpha */ - ONE_MINUS_DST_ALPHA = 773, - /** Multiply all channels by destination color */ - DST_COLOR = 774, - /** Multiply all channels by 1 minus destination color */ - ONE_MINUS_DST_COLOR = 775, - /** - * Multiplies the RGB colors by the smaller of either the source alpha value or the value of 1 minus the destination alpha value. The alpha value is multiplied by 1. - */ - SRC_ALPHA_SATURATE = 776, - /** - * Multiply all channels by a color constant. - */ - enumANT_COLOR = 32769, - /** Multiply all channels by 1 minus the color constant. */ - ONE_MINUS_enumANT_COLOR = 32770, - /** - * Multiply all channels by an alpha constant. - */ - enumANT_ALPHA = 32771, - /** - * Multiply all channels by one minus the Alpha constant. - */ - ONE_MINUS_enumANT_ALPHA = 32772 -} - -/** - * Probe rendering rate. - * */ -export enum RefreshRate { - /** Only render once */ - ONCE = 1, - /** Render every frame. */ - EVERYFRAME = 2 -} - /** * GL Capabilities * Some capabilities can be smoothed out by extension, and some capabilities must use WebGL 2.0. diff --git a/packages/core/src/base/EngineObject.ts b/packages/core/src/base/EngineObject.ts index c029c3ca64..44891784fc 100644 --- a/packages/core/src/base/EngineObject.ts +++ b/packages/core/src/base/EngineObject.ts @@ -15,6 +15,13 @@ export abstract class EngineObject { @ignoreClone protected _engine: Engine; + /** + * Get the engine which the object belongs. + */ + get engine(): Engine { + return this._engine; + } + constructor(engine: Engine) { this._engine = engine; } diff --git a/packages/core/src/base/Time.ts b/packages/core/src/base/Time.ts index bc94e56847..6cf61cc357 100644 --- a/packages/core/src/base/Time.ts +++ b/packages/core/src/base/Time.ts @@ -31,7 +31,6 @@ export class Time { /** * Current Time - * @readonly */ get nowTime(): number { return this._clock.now(); @@ -39,7 +38,6 @@ export class Time { /** * Time between two ticks - * @readonly */ get deltaTime(): number { return this._deltaTime; @@ -57,7 +55,6 @@ export class Time { /** * Unscaled delta time. - * @readonly */ get unscaledDeltaTime(): number { return this._deltaTime / this._timeScale; diff --git a/packages/core/src/enums/BackgroundMode.ts b/packages/core/src/enums/BackgroundMode.ts new file mode 100644 index 0000000000..caaa9a4d9e --- /dev/null +++ b/packages/core/src/enums/BackgroundMode.ts @@ -0,0 +1,9 @@ +/** + * The Background mode enumeration. + */ +export enum BackgroundMode { + /* Solid color. */ + SolidColor, + /* Sky. */ + Sky +} diff --git a/packages/core/src/enums/CameraClearFlags.ts b/packages/core/src/enums/CameraClearFlags.ts new file mode 100644 index 0000000000..7651e1a595 --- /dev/null +++ b/packages/core/src/enums/CameraClearFlags.ts @@ -0,0 +1,11 @@ +/** + * Camera clear flags enumeration. + */ +export enum CameraClearFlags { + /* Clear depth and color from background. */ + DepthColor, + /* Clear depth only. */ + Depth, + /* Do nothing. */ + None +} diff --git a/packages/core/src/graphic/Buffer.ts b/packages/core/src/graphic/Buffer.ts index fd593cdb17..5b50386ee3 100644 --- a/packages/core/src/graphic/Buffer.ts +++ b/packages/core/src/graphic/Buffer.ts @@ -19,13 +19,6 @@ export class Buffer extends RefObject { private _byteLength: number; private _bufferUsage: BufferUsage; - /** - * Engine. - */ - get engine(): Engine { - return this._engine; - } - /** * Buffer binding flag. */ diff --git a/packages/core/src/graphic/BufferUtil.ts b/packages/core/src/graphic/BufferUtil.ts index 2fbdc086d3..99cd806066 100644 --- a/packages/core/src/graphic/BufferUtil.ts +++ b/packages/core/src/graphic/BufferUtil.ts @@ -6,6 +6,7 @@ import { IndexFormat } from "./enums/IndexFormat"; export interface ElementInfo { size: number; type: DataType; + normalized: boolean; } export class BufferUtil { @@ -51,6 +52,8 @@ export class BufferUtil { static _getElementInfo(format: VertexElementFormat): ElementInfo { let size: number; let type: DataType; + let normalized: boolean = false; + switch (format) { case VertexElementFormat.Float: size = 1; @@ -69,28 +72,62 @@ export class BufferUtil { type = DataType.FLOAT; break; case VertexElementFormat.Byte4: + size = 4; + type = DataType.BYTE; + break; + case VertexElementFormat.UByte4: size = 4; type = DataType.UNSIGNED_BYTE; break; + case VertexElementFormat.NormalizedByte4: + size = 4; + type = DataType.BYTE; + normalized = true; + break; + case VertexElementFormat.NormalizedUByte4: + size = 4; + type = DataType.UNSIGNED_BYTE; + normalized = true; + break; case VertexElementFormat.Short2: size = 2; type = DataType.SHORT; break; - case VertexElementFormat.Short4: - size = 4; + case VertexElementFormat.UShort2: + size = 2; + type = DataType.UNSIGNED_SHORT; + break; + case VertexElementFormat.NormalizedShort2: + size = 2; type = DataType.SHORT; + normalized = true; break; - case VertexElementFormat.UShort2: + case VertexElementFormat.NormalizedUShort2: size = 2; type = DataType.UNSIGNED_SHORT; + normalized = true; + break; + case VertexElementFormat.Short4: + size = 4; + type = DataType.SHORT; break; case VertexElementFormat.UShort4: size = 4; type = DataType.UNSIGNED_SHORT; break; + case VertexElementFormat.NormalizedShort4: + size = 4; + type = DataType.SHORT; + normalized = true; + break; + case VertexElementFormat.NormalizedUShort4: + size = 4; + type = DataType.UNSIGNED_SHORT; + normalized = true; + break; default: break; } - return { size, type }; + return { size, type, normalized }; } } diff --git a/packages/core/src/graphic/Mesh.ts b/packages/core/src/graphic/Mesh.ts index d35abce1b7..2c7604c9d9 100644 --- a/packages/core/src/graphic/Mesh.ts +++ b/packages/core/src/graphic/Mesh.ts @@ -20,7 +20,7 @@ export abstract class Mesh extends RefObject { /** The bounding volume of the mesh. */ readonly bounds: BoundingBox = new BoundingBox(); - _vertexElementMap: object = {}; + _vertexElementMap: Record = {}; _glIndexType: number; _glIndexByteCount: number; _platformPrimitive: IPlatformPrimitive; diff --git a/packages/core/src/graphic/VertexElement.ts b/packages/core/src/graphic/VertexElement.ts index 6718edae2b..6c117ccea0 100644 --- a/packages/core/src/graphic/VertexElement.ts +++ b/packages/core/src/graphic/VertexElement.ts @@ -5,8 +5,6 @@ import { ElementInfo, BufferUtil } from "./BufferUtil"; * Vertex element. */ export class VertexElement { - public readonly normalized = false; - _glElementInfo: ElementInfo; private _semantic: string; @@ -72,11 +70,4 @@ export class VertexElement { this._glElementInfo = BufferUtil._getElementInfo(this.format); this._instanceStepRate = Math.floor(instanceStepRate); } - - /** - * @deprecated - */ - get elementInfo(): ElementInfo { - return this._glElementInfo; - } } diff --git a/packages/core/src/graphic/index.ts b/packages/core/src/graphic/index.ts index 2fb82f24f4..6e95b20518 100644 --- a/packages/core/src/graphic/index.ts +++ b/packages/core/src/graphic/index.ts @@ -11,4 +11,3 @@ export { Mesh } from "./Mesh"; export { SubMesh } from "./SubMesh"; export { VertexBufferBinding } from "./VertexBufferBinding"; export { VertexElement } from "./VertexElement"; - diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b3c7bd6e36..a8bc8876a3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -12,7 +12,6 @@ export { Component } from "./Component"; export { Script } from "./Script"; export { Renderer } from "./Renderer"; export { dependencies } from "./ComponentsDependencies"; - export { Camera } from "./Camera"; export { Transform } from "./Transform"; export { UpdateFlag } from "./UpdateFlag"; @@ -39,14 +38,10 @@ Scene.registerFeature(LightFeature); // Quote raycast import "./raycast"; -export { LightFeature }; -export { AmbientLight } from "./lighting/AmbientLight"; -export { DirectLight } from "./lighting/DirectLight"; -export { PointLight } from "./lighting/PointLight"; -export { SpotLight } from "./lighting/SpotLight"; -export { EnvironmentMapLight } from "./lighting/EnvironmentMapLight"; -export { Light } from "./lighting/Light"; - +export { Background } from "./Background"; +export { BackgroundMode } from "./enums/BackgroundMode"; +export { CameraClearFlags } from "./enums/CameraClearFlags"; +export * from "./lighting/index"; export * from "./material/index"; export * from "./texture/index"; export * from "./graphic/index"; @@ -54,7 +49,7 @@ export * from "./2d/index"; export * from "./shaderlib/index"; export * from "./animation/index"; export * from "./mesh/index"; -export * from "./skybox/index"; +export * from "./sky/index"; export * from "./particle/index"; export * from "./trail/index"; export * from "./collider/index"; @@ -65,5 +60,4 @@ export * from "./shadow/index"; export * from "./shader/index"; export * from "./Layer"; export * from "./clone/CloneManager"; - export * from "./renderingHardwareInterface/index"; diff --git a/packages/core/src/lighting/AmbientLight.ts b/packages/core/src/lighting/AmbientLight.ts index 419b75ffe7..2d626a966c 100644 --- a/packages/core/src/lighting/AmbientLight.ts +++ b/packages/core/src/lighting/AmbientLight.ts @@ -1,57 +1,131 @@ import { Color } from "@oasis-engine/math"; -import { Entity } from "../Entity"; +import { Scene } from "../Scene"; import { Shader } from "../shader"; +import { ShaderMacro } from "../shader/ShaderMacro"; import { ShaderProperty } from "../shader/ShaderProperty"; -import { Light } from "./Light"; +import { TextureCubeMap } from "../texture"; +import { DiffuseMode } from "./enums/DiffuseMode"; /** * Ambient light. */ -export class AmbientLight extends Light { - private static _colorProperty: ShaderProperty = Shader.getPropertyByName("u_ambientLightColor"); +export class AmbientLight { + private static _diffuseMacro: ShaderMacro = Shader.getMacroByName("O3_USE_DIFFUSE_ENV"); + private static _specularMacro: ShaderMacro = Shader.getMacroByName("O3_USE_SPECULAR_ENV"); + + private static _diffuseColorProperty: ShaderProperty = Shader.getPropertyByName("u_envMapLight.diffuse"); + private static _diffuseTextureProperty: ShaderProperty = Shader.getPropertyByName("u_env_diffuseSampler"); + private static _diffuseIntensityProperty: ShaderProperty = Shader.getPropertyByName("u_envMapLight.diffuseIntensity"); + private static _specularTextureProperty: ShaderProperty = Shader.getPropertyByName("u_env_specularSampler"); + private static _specularIntensityProperty: ShaderProperty = Shader.getPropertyByName( + "u_envMapLight.specularIntensity" + ); + private static _mipLevelProperty: ShaderProperty = Shader.getPropertyByName("u_envMapLight.mipMapLevel"); + + private _scene: Scene; + private _diffuseSolidColor: Color = new Color(0.212, 0.227, 0.259); + private _diffuseIntensity: number = 1.0; + private _specularReflection: TextureCubeMap; + private _specularIntensity: number = 1.0; + private _diffuseMode: DiffuseMode = DiffuseMode.SolidColor; + + /** + * Diffuse mode of ambient light. + */ + get diffuseMode(): DiffuseMode { + return this._diffuseMode; + } + + set diffuseMode(value: DiffuseMode) { + this._diffuseMode = value; + if (value === DiffuseMode.Texture) { + this._scene.shaderData.enableMacro(AmbientLight._diffuseMacro); + } else { + this._scene.shaderData.disableMacro(AmbientLight._diffuseMacro); + } + } + + /** + * Diffuse reflection solid color. + * @remarks Effective when diffuse reflection mode is `DiffuseMode.SolidColor`. + */ + get diffuseSolidColor(): Color { + return this._diffuseSolidColor; + } + + set diffuseSolidColor(value: Color) { + if (value !== this._diffuseSolidColor) { + value.cloneTo(this._diffuseSolidColor); + } + } /** - * Ambient light color. + * Diffuse reflection intensity. */ - get color(): Color { - return this._color; + get diffuseIntensity(): number { + return this._diffuseIntensity; } - set color(value: Color) { - this._color = value; - this.scene.shaderData.setColor(AmbientLight._colorProperty, this.lightColor); + set diffuseIntensity(value: number) { + this._diffuseIntensity = value; + this._scene.shaderData.setFloat(AmbientLight._diffuseIntensityProperty, value); } /** - * Ambient light intensity. + * Specular reflection texture. */ - get intensity(): number { - return this._intensity; + get specularTexture(): TextureCubeMap { + return this._specularReflection; } - set intensity(value: number) { - this._intensity = value; - this.scene.shaderData.setColor(AmbientLight._colorProperty, this.lightColor); + set specularTexture(value: TextureCubeMap) { + this._specularReflection = value; + + const shaderData = this._scene.shaderData; + + if (value) { + shaderData.setTexture(AmbientLight._specularTextureProperty, value); + shaderData.setFloat(AmbientLight._mipLevelProperty, this._specularReflection.mipmapCount); + shaderData.enableMacro(AmbientLight._specularMacro); + } else { + shaderData.disableMacro(AmbientLight._specularMacro); + } } /** - * Get the final light color. - * @readonly + * Specular reflection intensity. */ - get lightColor(): Color { - this._lightColor.r = this._color.r * this._intensity; - this._lightColor.g = this._color.g * this._intensity; - this._lightColor.b = this._color.b * this._intensity; - this._lightColor.a = this._color.a * this._intensity; - return this._lightColor; + get specularIntensity(): number { + return this._specularIntensity; } - private _color: Color = new Color(1, 1, 1, 1); - private _intensity: number = 1; - private _lightColor: Color = new Color(1, 1, 1, 1); + set specularIntensity(value: number) { + this._specularIntensity = value; + this._scene.shaderData.setFloat(AmbientLight._specularIntensityProperty, value); + } + + constructor(scene: Scene) { + this._scene = scene; + + const { shaderData } = this._scene; + shaderData.setColor(AmbientLight._diffuseColorProperty, this._diffuseSolidColor); + shaderData.setFloat(AmbientLight._diffuseIntensityProperty, this._diffuseIntensity); + shaderData.setFloat(AmbientLight._specularIntensityProperty, this._specularIntensity); + } + + //-----------------------------deprecated--------------------------------------- + + private _diffuseTexture: TextureCubeMap; + + /** + * Diffuse cube texture. + */ + get diffuseTexture(): TextureCubeMap { + return this._diffuseTexture; + } - constructor(entity: Entity) { - super(entity); - this.color = this._color; + set diffuseTexture(value: TextureCubeMap) { + this._diffuseTexture = value; + this._scene.shaderData.setTexture(AmbientLight._diffuseTextureProperty, value); } } diff --git a/packages/core/src/lighting/DirectLight.ts b/packages/core/src/lighting/DirectLight.ts index 05555bd47a..66763f901c 100644 --- a/packages/core/src/lighting/DirectLight.ts +++ b/packages/core/src/lighting/DirectLight.ts @@ -35,7 +35,6 @@ export class DirectLight extends Light { /** * Get direction. - * @readonly */ get direction(): Vector3 { this.entity.transform.getWorldForward(this._forward); @@ -44,7 +43,6 @@ export class DirectLight extends Light { /** * Get the final light color. - * @readonly */ get lightColor(): Color { this._lightColor.r = this.color.r * this.intensity; @@ -56,7 +54,6 @@ export class DirectLight extends Light { /** * Get the opposite direction of the directional light direction. - * @readonly */ get reverseDirection(): Vector3 { Vector3.scale(this.direction, -1, this._reverseDirection); diff --git a/packages/core/src/lighting/EnvironmentMapLight.ts b/packages/core/src/lighting/EnvironmentMapLight.ts deleted file mode 100644 index 3b877a02db..0000000000 --- a/packages/core/src/lighting/EnvironmentMapLight.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { Color } from "@oasis-engine/math"; -import { Entity } from "../Entity"; -import { Shader } from "../shader"; -import { ShaderData } from "../shader/ShaderData"; -import { ShaderMacro } from "../shader/ShaderMacro"; -import { ShaderProperty } from "../shader/ShaderProperty"; -import { TextureCubeMap } from "../texture"; -import { Light } from "./Light"; - -/** - * Environment light. - */ -export class EnvironmentMapLight extends Light { - private static _diffuseMacro: ShaderMacro = Shader.getMacroByName("O3_USE_DIFFUSE_ENV"); - private static _specularMacro: ShaderMacro = Shader.getMacroByName("O3_USE_SPECULAR_ENV"); - private static _diffuseTextureProperty: ShaderProperty = Shader.getPropertyByName("u_env_diffuseSampler"); - private static _specularTextureProperty: ShaderProperty = Shader.getPropertyByName("u_env_specularSampler"); - private static _mipLevelProperty: ShaderProperty = Shader.getPropertyByName("u_envMapLight.mipMapLevel"); - private static _diffuseColorProperty: ShaderProperty = Shader.getPropertyByName("u_envMapLight.diffuse"); - private static _specularColorProperty: ShaderProperty = Shader.getPropertyByName("u_envMapLight.specular"); - private static _diffuseIntensityProperty: ShaderProperty = Shader.getPropertyByName("u_envMapLight.diffuseIntensity"); - private static _specularIntensityProperty: ShaderProperty = Shader.getPropertyByName( - "u_envMapLight.specularIntensity" - ); - private static _transformMatrixProperty: ShaderProperty = Shader.getPropertyByName("u_envMapLight.transformMatrix"); - - /** - * @internal - */ - static _updateShaderData(shaderData: ShaderData, light: EnvironmentMapLight): void { - // support rotation - const transformMatrix = light.entity.transform.worldMatrix; - shaderData.setMatrix(EnvironmentMapLight._transformMatrixProperty, transformMatrix); - } - - /** - * Diffuse cube texture. - */ - get diffuseTexture(): TextureCubeMap { - return this._diffuseTexture; - } - - set diffuseTexture(value: TextureCubeMap) { - this._diffuseTexture = value; - const shaderData = this.scene.shaderData; - - if (value) { - shaderData.setTexture(EnvironmentMapLight._diffuseTextureProperty, value); - shaderData.enableMacro(EnvironmentMapLight._diffuseMacro); - } else { - shaderData.disableMacro(EnvironmentMapLight._diffuseMacro); - } - } - - /** - * Specular cube texture. - */ - get specularTexture(): TextureCubeMap { - return this._specularTexture; - } - - set specularTexture(value: TextureCubeMap) { - this._specularTexture = value; - const shaderData = this.scene.shaderData; - - if (value) { - shaderData.setTexture(EnvironmentMapLight._specularTextureProperty, value); - shaderData.setFloat(EnvironmentMapLight._mipLevelProperty, this.specularTexture.mipmapCount); - shaderData.enableMacro(EnvironmentMapLight._specularMacro); - } else { - shaderData.disableMacro(EnvironmentMapLight._specularMacro); - } - } - - /** - * Diffuse color. - */ - get diffuseColor(): Color { - return this._diffuseColor; - } - - set diffuseColor(value: Color) { - this._diffuseColor = value; - - this.scene.shaderData.setColor(EnvironmentMapLight._diffuseColorProperty, value); - } - - /** - * Specular color. - */ - get specularColor(): Color { - return this._specularColor; - } - - set specularColor(value: Color) { - this._specularColor = value; - - this.scene.shaderData.setColor(EnvironmentMapLight._specularColorProperty, value); - } - - /** - * Diffuse intensity. - */ - get diffuseIntensity(): number { - return this._diffuseIntensity; - } - - set diffuseIntensity(value: number) { - this._diffuseIntensity = value; - - this.scene.shaderData.setFloat(EnvironmentMapLight._diffuseIntensityProperty, value); - } - - /** - * Specular intensity. - */ - get specularIntensity(): number { - return this._specularIntensity; - } - - set specularIntensity(value: number) { - this._specularIntensity = value; - - this.scene.shaderData.setFloat(EnvironmentMapLight._specularIntensityProperty, value); - } - - private _diffuseTexture: TextureCubeMap; - private _specularTexture: TextureCubeMap; - private _diffuseColor: Color = new Color(0.3, 0.3, 0.3, 1); - private _specularColor: Color = new Color(0.5, 0.5, 0.5, 1); - private _diffuseIntensity: number = 1; - private _specularIntensity: number = 1; - - constructor(entity: Entity) { - super(entity); - this.diffuseColor = this._diffuseColor; - this.specularColor = this._specularColor; - this.diffuseIntensity = this._diffuseIntensity; - this.specularIntensity = this._specularIntensity; - } -} diff --git a/packages/core/src/lighting/Light.ts b/packages/core/src/lighting/Light.ts index 4a78639ead..3cee53b854 100644 --- a/packages/core/src/lighting/Light.ts +++ b/packages/core/src/lighting/Light.ts @@ -34,7 +34,6 @@ export class Light extends Component { /** * View matrix. - * @readonly */ get viewMatrix() { if (!this._viewMat) this._viewMat = new Matrix(); @@ -44,7 +43,6 @@ export class Light extends Component { /** * Inverse view matrix. - * @readonly */ get inverseViewMatrix() { if (!this._inverseViewMat) this._inverseViewMat = new Matrix(); diff --git a/packages/core/src/lighting/LightFeature.ts b/packages/core/src/lighting/LightFeature.ts index 5da79c5c55..221632413b 100644 --- a/packages/core/src/lighting/LightFeature.ts +++ b/packages/core/src/lighting/LightFeature.ts @@ -1,11 +1,7 @@ import { Logger } from "../base/Logger"; import { SceneFeature } from "../SceneFeature"; -import { Shader } from "../shader"; import { ShaderData } from "../shader/ShaderData"; -import { ShaderMacro } from "../shader/ShaderMacro"; -import { AmbientLight } from "./AmbientLight"; import { DirectLight } from "./DirectLight"; -import { EnvironmentMapLight } from "./EnvironmentMapLight"; import { Light } from "./Light"; import { PointLight } from "./PointLight"; import { SpotLight } from "./SpotLight"; @@ -22,9 +18,6 @@ export function hasLight(): boolean { * Light plug-in. */ export class LightFeature extends SceneFeature { - private static _ambientMacro: ShaderMacro = Shader.getMacroByName("O3_HAS_AMBIENT_LIGHT"); - private static _envMacro: ShaderMacro = Shader.getMacroByName("O3_HAS_ENVMAP_LIGHT"); - visibleLights: Light[]; constructor() { @@ -63,8 +56,6 @@ export class LightFeature extends SceneFeature { /** * ambientLight and envMapLight only use the last one in the scene * */ - let ambientLightCount = 0; - let envMapLightCount = 0; let directLightCount = 0; let pointLightCount = 0; let spotLightCount = 0; @@ -72,12 +63,7 @@ export class LightFeature extends SceneFeature { let lights = this.visibleLights; for (let i = 0, len = lights.length; i < len; i++) { const light = lights[i]; - if (light instanceof AmbientLight) { - ambientLightCount++; - } else if (light instanceof EnvironmentMapLight) { - EnvironmentMapLight._updateShaderData(shaderData, light); - envMapLightCount++; - } else if (light instanceof DirectLight) { + if (light instanceof DirectLight) { light._appendData(directLightCount++); } else if (light instanceof PointLight) { light._appendData(pointLightCount++); @@ -86,18 +72,6 @@ export class LightFeature extends SceneFeature { } } - if (ambientLightCount) { - shaderData.enableMacro(LightFeature._ambientMacro); - } else { - shaderData.disableMacro(LightFeature._ambientMacro); - } - - if (envMapLightCount) { - shaderData.enableMacro(LightFeature._envMacro); - } else { - shaderData.disableMacro(LightFeature._envMacro); - } - if (directLightCount) { DirectLight._updateShaderData(shaderData); shaderData.enableMacro("O3_DIRECT_LIGHT_COUNT", directLightCount.toString()); diff --git a/packages/core/src/lighting/PointLight.ts b/packages/core/src/lighting/PointLight.ts index 149363a160..81caa2aa91 100644 --- a/packages/core/src/lighting/PointLight.ts +++ b/packages/core/src/lighting/PointLight.ts @@ -11,13 +11,11 @@ export class PointLight extends Light { private static _colorProperty: ShaderProperty = Shader.getPropertyByName("u_pointLightColor"); private static _positionProperty: ShaderProperty = Shader.getPropertyByName("u_pointLightPosition"); private static _distanceProperty: ShaderProperty = Shader.getPropertyByName("u_pointLightDistance"); - private static _decayProperty: ShaderProperty = Shader.getPropertyByName("u_pointLightDecay"); private static _combinedData = { color: new Float32Array(3 * Light._maxLight), position: new Float32Array(3 * Light._maxLight), - distance: new Float32Array(Light._maxLight), - decay: new Float32Array(Light._maxLight) + distance: new Float32Array(Light._maxLight) }; /** @@ -29,19 +27,18 @@ export class PointLight extends Light { shaderData.setFloatArray(PointLight._colorProperty, data.color); shaderData.setFloatArray(PointLight._positionProperty, data.position); shaderData.setFloatArray(PointLight._distanceProperty, data.distance); - shaderData.setFloatArray(PointLight._decayProperty, data.decay); } - + /** Light color. */ color: Color = new Color(1, 1, 1, 1); + /** Light intensity. */ intensity: number = 1.0; + /** Defines a distance cutoff at which the light's intensity must be considered zero. */ distance: number = 100; - decay: number = 0; private _lightColor: Color = new Color(1, 1, 1, 1); /** * Get light position. - * @readonly */ get position(): Vector3 { return this.entity.transform.worldPosition; @@ -49,7 +46,6 @@ export class PointLight extends Light { /** * Get the final light color. - * @readonly */ get lightColor(): Color { this._lightColor.r = this.color.r * this.intensity; @@ -66,7 +62,6 @@ export class PointLight extends Light { const colorStart = lightIndex * 3; const positionStart = lightIndex * 3; const distanceStart = lightIndex; - const decayStart = lightIndex; const lightColor = this.lightColor; const lightPosition = this.position; @@ -80,6 +75,5 @@ export class PointLight extends Light { data.position[positionStart + 1] = lightPosition.y; data.position[positionStart + 2] = lightPosition.z; data.distance[distanceStart] = this.distance; - data.decay[decayStart] = this.decay; } } diff --git a/packages/core/src/lighting/SpotLight.ts b/packages/core/src/lighting/SpotLight.ts index 713f117050..657518660e 100644 --- a/packages/core/src/lighting/SpotLight.ts +++ b/packages/core/src/lighting/SpotLight.ts @@ -12,22 +12,16 @@ export class SpotLight extends Light { private static _positionProperty: ShaderProperty = Shader.getPropertyByName("u_spotLightPosition"); private static _directionProperty: ShaderProperty = Shader.getPropertyByName("u_spotLightDirection"); private static _distanceProperty: ShaderProperty = Shader.getPropertyByName("u_spotLightDistance"); - private static _decayProperty: ShaderProperty = Shader.getPropertyByName("u_spotLightDecay"); - private static _angleProperty: ShaderProperty = Shader.getPropertyByName("u_spotLightAngle"); - private static _penumbraProperty: ShaderProperty = Shader.getPropertyByName("u_spotLightPenumbra"); + private static _angleCosProperty: ShaderProperty = Shader.getPropertyByName("u_spotLightAngleCos"); private static _penumbraCosProperty: ShaderProperty = Shader.getPropertyByName("u_spotLightPenumbraCos"); - private static _coneCosProperty: ShaderProperty = Shader.getPropertyByName("u_spotLightConeCos"); private static _combinedData = { color: new Float32Array(3 * Light._maxLight), position: new Float32Array(3 * Light._maxLight), direction: new Float32Array(3 * Light._maxLight), distance: new Float32Array(Light._maxLight), - decay: new Float32Array(Light._maxLight), - angle: new Float32Array(Light._maxLight), - penumbra: new Float32Array(Light._maxLight), - penumbraCos: new Float32Array(Light._maxLight), - coneCos: new Float32Array(Light._maxLight) + angleCos: new Float32Array(Light._maxLight), + penumbraCos: new Float32Array(Light._maxLight) }; /** @@ -40,19 +34,20 @@ export class SpotLight extends Light { shaderData.setFloatArray(SpotLight._positionProperty, data.position); shaderData.setFloatArray(SpotLight._directionProperty, data.direction); shaderData.setFloatArray(SpotLight._distanceProperty, data.distance); - shaderData.setFloatArray(SpotLight._decayProperty, data.decay); - shaderData.setFloatArray(SpotLight._angleProperty, data.angle); - shaderData.setFloatArray(SpotLight._penumbraProperty, data.penumbra); + shaderData.setFloatArray(SpotLight._angleCosProperty, data.angleCos); shaderData.setFloatArray(SpotLight._penumbraCosProperty, data.penumbraCos); - shaderData.setFloatArray(SpotLight._coneCosProperty, data.coneCos); } + /** Light color. */ color: Color = new Color(1, 1, 1, 1); - penumbra: number = 0.2; - distance: number = 100; + /** Light intensity. */ intensity: number = 1.0; - decay: number = 0; + /** Defines a distance cutoff at which the light's intensity must be considered zero. */ + distance: number = 100; + /** Angle, in radians, from centre of spotlight where falloff begins. */ angle: number = Math.PI / 6; + /** Angle, in radians, from falloff begins to ends. */ + penumbra: number = Math.PI / 12; private _forward: Vector3 = new Vector3(); private _lightColor: Color = new Color(1, 1, 1, 1); @@ -60,7 +55,6 @@ export class SpotLight extends Light { /** * Get light position. - * @readonly */ get position(): Vector3 { return this.entity.transform.worldPosition; @@ -68,7 +62,6 @@ export class SpotLight extends Light { /** * Get light direction. - * @readonly */ get direction(): Vector3 { this.entity.transform.getWorldForward(this._forward); @@ -77,7 +70,6 @@ export class SpotLight extends Light { /** * Get the opposite direction of the spotlight. - * @readonly */ get reverseDirection(): Vector3 { Vector3.scale(this.direction, -1, this._inverseDirection); @@ -86,7 +78,6 @@ export class SpotLight extends Light { /** * Get the final light color. - * @readonly */ get lightColor(): Color { this._lightColor.r = this.color.r * this.intensity; @@ -104,11 +95,8 @@ export class SpotLight extends Light { const positionStart = lightIndex * 3; const directionStart = lightIndex * 3; const distanceStart = lightIndex; - const decayStart = lightIndex; - const angleStart = lightIndex; - const penumbraStart = lightIndex; const penumbraCosStart = lightIndex; - const coneCosStart = lightIndex; + const angleCosStart = lightIndex; const color = this.lightColor; const position = this.position; @@ -126,10 +114,7 @@ export class SpotLight extends Light { data.direction[directionStart + 1] = direction.y; data.direction[directionStart + 2] = direction.z; data.distance[distanceStart] = this.distance; - data.decay[decayStart] = this.decay; - data.angle[angleStart] = this.angle; - data.penumbra[penumbraStart] = this.penumbra; - data.penumbraCos[penumbraCosStart] = Math.cos(this.angle * (1 - this.penumbra)); - data.coneCos[coneCosStart] = Math.cos(this.angle); + data.angleCos[angleCosStart] = Math.cos(this.angle); + data.penumbraCos[penumbraCosStart] = Math.cos(this.angle + this.penumbra); } } diff --git a/packages/core/src/lighting/enums/DiffuseMode.ts b/packages/core/src/lighting/enums/DiffuseMode.ts new file mode 100644 index 0000000000..b97591c8c8 --- /dev/null +++ b/packages/core/src/lighting/enums/DiffuseMode.ts @@ -0,0 +1,10 @@ +/** + * Diffuse mode. + */ +export enum DiffuseMode { + /** Solid color mode. */ + SolidColor, + /** Texture cube mode. */ + Texture + // SphericalHarmonics +} diff --git a/packages/core/src/lighting/index.ts b/packages/core/src/lighting/index.ts new file mode 100644 index 0000000000..4f3c7e17cd --- /dev/null +++ b/packages/core/src/lighting/index.ts @@ -0,0 +1,6 @@ +export { AmbientLight } from "./AmbientLight"; +export { DirectLight } from "./DirectLight"; +export { DiffuseMode } from "./enums/DiffuseMode"; +export { Light } from "./Light"; +export { PointLight } from "./PointLight"; +export { SpotLight } from "./SpotLight"; diff --git a/packages/core/src/mesh/PrimitiveMesh.ts b/packages/core/src/mesh/PrimitiveMesh.ts index 36114be639..3631b1cdb3 100644 --- a/packages/core/src/mesh/PrimitiveMesh.ts +++ b/packages/core/src/mesh/PrimitiveMesh.ts @@ -18,7 +18,7 @@ export class PrimitiveMesh { static createSphere( engine: Engine, radius: number = 0.5, - segments: number = 12, + segments: number = 18, noLongerAccessible: boolean = true ): ModelMesh { const mesh = new ModelMesh(engine); diff --git a/packages/core/src/mesh/Skin.ts b/packages/core/src/mesh/Skin.ts index fff4bac2ca..6fb94ae9d0 100644 --- a/packages/core/src/mesh/Skin.ts +++ b/packages/core/src/mesh/Skin.ts @@ -1,7 +1,6 @@ import { Matrix } from "@oasis-engine/math"; import { EngineObject } from "../base/EngineObject"; - -let skinID = 0; +import { Entity } from "../Entity"; /** * Mesh skin data, equal glTF skins define @@ -14,9 +13,8 @@ export class Skin extends EngineObject { * Contructor of skin * @param name - name */ - constructor(name: string) { + constructor(public name: string) { super(null); - this.inverseBindMatrices = []; // inverse bind matrix array, element type: gl-matrix.mat4 this.joints = []; // joints name array, element type: string this.skeleton = "none"; // root bone name diff --git a/packages/core/src/mesh/SkinnedMeshRenderer.ts b/packages/core/src/mesh/SkinnedMeshRenderer.ts index 51ace54932..60d3246667 100644 --- a/packages/core/src/mesh/SkinnedMeshRenderer.ts +++ b/packages/core/src/mesh/SkinnedMeshRenderer.ts @@ -1,6 +1,5 @@ import { Matrix } from "@oasis-engine/math"; import { Logger } from "../base/Logger"; -import { Camera } from "../Camera"; import { ignoreClone } from "../clone/CloneManager"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; @@ -122,20 +121,6 @@ export class SkinnedMeshRenderer extends MeshRenderer { return this.findByNodeName(entity.parent, nodeName); } - private _findParent(entity: Entity, nodeName: string) { - if (entity) { - const parent = entity.parent; - if (!parent) return null; - if (parent.name === nodeName) return parent; - - const brother = parent.findByName(nodeName); - if (brother) return brother; - - return this._findParent(parent, nodeName); - } - return null; - } - /** * @internal */ diff --git a/packages/core/src/shader/ShaderPool.ts b/packages/core/src/shader/ShaderPool.ts index 00c911eee5..951390f6f0 100644 --- a/packages/core/src/shader/ShaderPool.ts +++ b/packages/core/src/shader/ShaderPool.ts @@ -9,6 +9,10 @@ import shadowMapVs from "../shaderlib/extra/shadow-map.vs.glsl"; import shadowFs from "../shaderlib/extra/shadow.fs.glsl"; import skyboxFs from "../shaderlib/extra/skybox.fs.glsl"; import skyboxVs from "../shaderlib/extra/skybox.vs.glsl"; +import spriteMaskFs from "../shaderlib/extra/sprite-mask.fs.glsl"; +import spriteMaskVs from "../shaderlib/extra/sprite-mask.vs.glsl"; +import spriteFs from "../shaderlib/extra/sprite.fs.glsl"; +import spriteVs from "../shaderlib/extra/sprite.vs.glsl"; import unlitFs from "../shaderlib/extra/unlit.fs.glsl"; import unlitVs from "../shaderlib/extra/unlit.vs.glsl"; import { Shader } from "./Shader"; @@ -26,5 +30,7 @@ export class ShaderPool { Shader.create("shadow", shadowMapVs, shadowFs); Shader.create("skybox", skyboxVs, skyboxFs); Shader.create("particle-shader", particleVs, particleFs); + Shader.create("SpriteMask", spriteMaskVs, spriteMaskFs); + Shader.create("Sprite", spriteVs, spriteFs); } } diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 36b1a70da0..d08db84249 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -65,7 +65,6 @@ export class ShaderProgram { /** * Whether this shader program is valid. - * @readonly */ get isValid(): boolean { return this._isValid; diff --git a/packages/core/src/shaderlib/ShaderLib.ts b/packages/core/src/shaderlib/ShaderLib.ts index 47a4dc9886..91e276a3c1 100644 --- a/packages/core/src/shaderlib/ShaderLib.ts +++ b/packages/core/src/shaderlib/ShaderLib.ts @@ -21,7 +21,6 @@ import worldpos_vert from "./worldpos_vert.glsl"; import shadow_vert from "./shadow_vert.glsl"; import fog_vert from "./fog_vert.glsl"; -import ambient_light_frag from "./ambient_light_frag.glsl"; import direct_light_frag from "./direct_light_frag.glsl"; import point_light_frag from "./point_light_frag.glsl"; import spot_light_frag from "./spot_light_frag.glsl"; @@ -85,7 +84,6 @@ export const ShaderLib = { shadow_vert, fog_vert, - ambient_light_frag, direct_light_frag, point_light_frag, spot_light_frag, diff --git a/packages/core/src/shaderlib/ambient_light_frag.glsl b/packages/core/src/shaderlib/ambient_light_frag.glsl deleted file mode 100644 index dc6582be01..0000000000 --- a/packages/core/src/shaderlib/ambient_light_frag.glsl +++ /dev/null @@ -1,6 +0,0 @@ -#ifdef O3_HAS_AMBIENT_LIGHT - - -uniform vec3 u_ambientLightColor; - -#endif diff --git a/packages/core/src/shaderlib/begin_mobile_frag.glsl b/packages/core/src/shaderlib/begin_mobile_frag.glsl index f40a85f0ec..0ee88f4819 100644 --- a/packages/core/src/shaderlib/begin_mobile_frag.glsl +++ b/packages/core/src/shaderlib/begin_mobile_frag.glsl @@ -3,6 +3,8 @@ vec4 diffuse = u_diffuseColor; vec4 specular = u_specularColor; + + #ifdef O3_EMISSIVE_TEXTURE emission *= texture2D(u_emissiveTexture, v_uv); @@ -21,8 +23,4 @@ #endif - #ifdef O3_HAS_AMBIENT_LIGHT - - ambient = vec4(u_ambientLightColor, 1.0) * diffuse; - - #endif + ambient = vec4(u_envMapLight.diffuse * u_envMapLight.diffuseIntensity, 1.0) * diffuse; \ No newline at end of file diff --git a/packages/core/src/shaderlib/extra/blinn-phong.fs.glsl b/packages/core/src/shaderlib/extra/blinn-phong.fs.glsl index 3873840d2b..497dc2b4c7 100644 --- a/packages/core/src/shaderlib/extra/blinn-phong.fs.glsl +++ b/packages/core/src/shaderlib/extra/blinn-phong.fs.glsl @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include #include diff --git a/packages/core/src/shaderlib/extra/pbr.fs.glsl b/packages/core/src/shaderlib/extra/pbr.fs.glsl index 0f7599f363..e9060387ac 100644 --- a/packages/core/src/shaderlib/extra/pbr.fs.glsl +++ b/packages/core/src/shaderlib/extra/pbr.fs.glsl @@ -13,7 +13,6 @@ #include // light -#include #include #include #include diff --git a/packages/core/src/shaderlib/extra/sprite-mask.fs.glsl b/packages/core/src/shaderlib/extra/sprite-mask.fs.glsl new file mode 100644 index 0000000000..b18244e091 --- /dev/null +++ b/packages/core/src/shaderlib/extra/sprite-mask.fs.glsl @@ -0,0 +1,16 @@ +precision mediump float; +precision mediump int; + +uniform sampler2D u_maskTexture; +uniform float u_alphaCutoff; +varying vec2 v_uv; + +void main() +{ + vec4 color = texture2D(u_maskTexture, v_uv); + if (color.a < u_alphaCutoff) { + discard; + } + + gl_FragColor = color; +} \ No newline at end of file diff --git a/packages/core/src/shaderlib/extra/sprite-mask.vs.glsl b/packages/core/src/shaderlib/extra/sprite-mask.vs.glsl new file mode 100644 index 0000000000..2e25725a93 --- /dev/null +++ b/packages/core/src/shaderlib/extra/sprite-mask.vs.glsl @@ -0,0 +1,14 @@ +precision highp float; + +uniform mat4 u_VPMat; + +attribute vec3 POSITION; +attribute vec2 TEXCOORD_0; + +varying vec2 v_uv; + +void main() +{ + gl_Position = u_VPMat * vec4(POSITION, 1.0); + v_uv = TEXCOORD_0; +} \ No newline at end of file diff --git a/packages/core/src/shaderlib/extra/sprite.fs.glsl b/packages/core/src/shaderlib/extra/sprite.fs.glsl new file mode 100644 index 0000000000..0279a506c6 --- /dev/null +++ b/packages/core/src/shaderlib/extra/sprite.fs.glsl @@ -0,0 +1,13 @@ +precision mediump float; +precision mediump int; + +uniform sampler2D u_spriteTexture; +varying vec2 v_uv; +varying vec4 v_color; + +void main() +{ + // Only use the Alpha of the texture as a mask, so that the tint color can still be controlled to fade out. + vec4 baseColor = texture2D(u_spriteTexture, v_uv); + gl_FragColor = baseColor * v_color; +} \ No newline at end of file diff --git a/packages/core/src/shaderlib/extra/sprite.vs.glsl b/packages/core/src/shaderlib/extra/sprite.vs.glsl new file mode 100644 index 0000000000..ab0081edf7 --- /dev/null +++ b/packages/core/src/shaderlib/extra/sprite.vs.glsl @@ -0,0 +1,17 @@ +precision highp float; + +uniform mat4 u_VPMat; + +attribute vec3 POSITION; +attribute vec2 TEXCOORD_0; +attribute vec4 COLOR_0; + +varying vec2 v_uv; +varying vec4 v_color; + +void main() +{ + gl_Position = u_VPMat * vec4(POSITION, 1.0); + v_uv = TEXCOORD_0; + v_color = COLOR_0; +} \ No newline at end of file diff --git a/packages/core/src/shaderlib/mobile_blinnphong_frag.glsl b/packages/core/src/shaderlib/mobile_blinnphong_frag.glsl index df662fbdf5..3b2b08c78f 100644 --- a/packages/core/src/shaderlib/mobile_blinnphong_frag.glsl +++ b/packages/core/src/shaderlib/mobile_blinnphong_frag.glsl @@ -4,43 +4,42 @@ #ifdef O3_DIRECT_LIGHT_COUNT - DirectLight lgt; + DirectLight directionalLight; for( int i = 0; i < O3_DIRECT_LIGHT_COUNT; i++ ) { - lgt.color = u_directLightColor[i]; - lgt.direction = u_directLightDirection[i]; + directionalLight.color = u_directLightColor[i]; + directionalLight.direction = u_directLightDirection[i]; - float d = max(dot(N, -lgt.direction), 0.0); - lightDiffuse += lgt.color*d; + float d = max(dot(N, -directionalLight.direction), 0.0); + lightDiffuse += directionalLight.color * d; - vec3 halfDir = normalize( V - lgt.direction ); + vec3 halfDir = normalize( V - directionalLight.direction ); float s = pow( clamp( dot( N, halfDir ), 0.0, 1.0 ), u_shininess ); - lightSpecular += lgt.color * s; + lightSpecular += directionalLight.color * s; } #endif #ifdef O3_POINT_LIGHT_COUNT - PointLight lgt; + PointLight pointLight; for( int i = 0; i < O3_POINT_LIGHT_COUNT; i++ ) { - lgt.color = u_pointLightColor[i]; - lgt.position = u_pointLightPosition[i]; - lgt.distance = u_pointLightDistance[i]; - lgt.decay = u_pointLightDecay[i]; + pointLight.color = u_pointLightColor[i]; + pointLight.position = u_pointLightPosition[i]; + pointLight.distance = u_pointLightDistance[i]; - vec3 direction = v_pos - lgt.position; + vec3 direction = v_pos - pointLight.position; float dist = length( direction ); direction /= dist; - float decay = pow( max( 0.0, 1.0-dist / lgt.distance ), 2.0 ); + float decay = clamp(1.0 - pow(dist / pointLight.distance, 4.0), 0.0, 1.0); float d = max( dot( N, -direction ), 0.0 ) * decay; - lightDiffuse += lgt.color * d; + lightDiffuse += pointLight.color * d; vec3 halfDir = normalize( V - direction ); float s = pow( clamp( dot( N, halfDir ), 0.0, 1.0 ), u_shininess ) * decay; - lightSpecular += lgt.color * s; + lightSpecular += pointLight.color * s; } @@ -48,32 +47,29 @@ #ifdef O3_SPOT_LIGHT_COUNT - SpotLight lgt; + SpotLight spotLight; for( int i = 0; i < O3_SPOT_LIGHT_COUNT; i++) { - lgt.color = u_spotLightColor[i]; - lgt.position = u_spotLightPosition[i]; - lgt.direction = u_spotLightDirection[i]; - lgt.distance = u_spotLightDistance[i]; - lgt.decay = u_spotLightDecay[i]; - lgt.angle = u_spotLightAngle[i]; - lgt.penumbra = u_spotLightPenumbra[i]; - - vec3 direction = v_pos - lgt.position; - float angle = acos( dot( normalize( direction ), normalize( lgt.direction ) ) ); - float dist = length( direction ); - direction /= dist; - float decay = pow( max( 0.0, 1.0 - dist / lgt.distance ), 2.0 ); - - float hasLight = step( angle, lgt.angle ); - float hasPenumbra = step( lgt.angle, angle ) * step( angle, lgt.angle * ( 1.0 + lgt.penumbra ) ); - float penumbra = hasPenumbra * ( 1.0 - ( angle - lgt.angle ) / ( lgt.angle * lgt.penumbra ) ); - float d = max( dot( N, -direction ), 0.0 ) * decay * ( penumbra + hasLight ); - lightDiffuse += lgt.color * d; - - vec3 halfDir = normalize( V - direction ); - float s = pow( clamp( dot( N, halfDir ), 0.0, 1.0 ), u_shininess ) * decay * ( penumbra + hasLight ); - lightSpecular += lgt.color * s; + spotLight.color = u_spotLightColor[i]; + spotLight.position = u_spotLightPosition[i]; + spotLight.direction = u_spotLightDirection[i]; + spotLight.distance = u_spotLightDistance[i]; + spotLight.angleCos = u_spotLightAngleCos[i]; + spotLight.penumbraCos = u_spotLightPenumbraCos[i]; + + vec3 direction = spotLight.position - v_pos; + float lightDistance = length( direction ); + direction/ = lightDistance; + float angleCos = dot( direction, -spotLight.direction ); + float decay = clamp(1.0 - pow(lightDistance/spotLight.distance, 4.0), 0.0, 1.0); + float spotEffect = smoothstep( spotLight.penumbraCos, spotLight.angleCos, angleCos ); + float decayTotal = decay * spotEffect; + float d = max( dot( N, direction ), 0.0 ) * decayTotal; + lightDiffuse += spotLight.color * d; + + vec3 halfDir = normalize( V + direction ); + float s = pow( clamp( dot( N, halfDir ), 0.0, 1.0 ), u_shininess ) * decayTotal; + lightSpecular += spotLight.color * s; } diff --git a/packages/core/src/shaderlib/pbr/direct_irradiance_frag.glsl b/packages/core/src/shaderlib/pbr/direct_irradiance_frag.glsl index 8b6bcba141..2663fd72cf 100644 --- a/packages/core/src/shaderlib/pbr/direct_irradiance_frag.glsl +++ b/packages/core/src/shaderlib/pbr/direct_irradiance_frag.glsl @@ -24,7 +24,6 @@ pointLight.color = u_pointLightColor[i]; pointLight.position = u_pointLightPosition[i]; pointLight.distance = u_pointLightDistance[i]; - pointLight.decay = u_pointLightDecay[i]; getPointDirectLightIrradiance( pointLight, geometry, directLight ); @@ -44,11 +43,8 @@ spotLight.position = u_spotLightPosition[i]; spotLight.direction = u_spotLightDirection[i]; spotLight.distance = u_spotLightDistance[i]; - spotLight.decay = u_spotLightDecay[i]; - spotLight.angle = u_spotLightAngle[i]; - spotLight.penumbra = u_spotLightPenumbra[i]; + spotLight.angleCos = u_spotLightAngleCos[i]; spotLight.penumbraCos = u_spotLightPenumbraCos[i]; - spotLight.coneCos = u_spotLightConeCos[i]; getSpotDirectLightIrradiance( spotLight, geometry, directLight ); diff --git a/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl b/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl index c4afeb02a9..f84b88cd5d 100644 --- a/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl @@ -25,7 +25,6 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricC void getDirectionalDirectLightIrradiance( const in DirectLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) { directLight.color = directionalLight.color; directLight.direction = -directionalLight.direction; - directLight.visible = true; } #endif @@ -41,8 +40,7 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricC float lightDistance = length( lVector ); directLight.color = pointLight.color; - directLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay ); - directLight.visible = ( directLight.color != vec3( 0.0 ) ); + directLight.color *= clamp(1.0 - pow(lightDistance/pointLight.distance, 4.0), 0.0, 1.0); } @@ -59,20 +57,12 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricC float lightDistance = length( lVector ); float angleCos = dot( directLight.direction, -spotLight.direction ); - if ( angleCos > spotLight.coneCos ) { + float spotEffect = smoothstep( spotLight.penumbraCos, spotLight.angleCos, angleCos ); + float decayEffect = clamp(1.0 - pow(lightDistance/spotLight.distance, 4.0), 0.0, 1.0); - float spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos ); - - directLight.color = spotLight.color; - directLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay ); - directLight.visible = true; - - } else { - - directLight.color = vec3( 0.0 ); - directLight.visible = false; - - } + directLight.color = spotLight.color; + directLight.color *= spotEffect * decayEffect; + } diff --git a/packages/core/src/shaderlib/pbr/envmap_light_frag_define.glsl b/packages/core/src/shaderlib/pbr/envmap_light_frag_define.glsl index 0a32d8ad6a..d435c207e2 100644 --- a/packages/core/src/shaderlib/pbr/envmap_light_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/envmap_light_frag_define.glsl @@ -1,8 +1,5 @@ -#ifdef O3_HAS_ENVMAP_LIGHT - struct EnvMapLight { vec3 diffuse; - vec3 specular; float mipMapLevel; float diffuseIntensity; float specularIntensity; @@ -19,5 +16,3 @@ uniform EnvMapLight u_envMapLight; #ifdef O3_USE_SPECULAR_ENV uniform samplerCube u_env_specularSampler; #endif - -#endif diff --git a/packages/core/src/shaderlib/pbr/ibl_diffuse_frag.glsl b/packages/core/src/shaderlib/pbr/ibl_diffuse_frag.glsl index de2de4cab2..4acd9a0dd1 100644 --- a/packages/core/src/shaderlib/pbr/ibl_diffuse_frag.glsl +++ b/packages/core/src/shaderlib/pbr/ibl_diffuse_frag.glsl @@ -1,27 +1,19 @@ #if defined(RE_IndirectDiffuse) vec3 irradiance = vec3(0); - - #if defined(O3_HAS_AMBIENT_LIGHT) - irradiance += getAmbientLightIrradiance(u_ambientLightColor); + + #ifdef O3_USE_DIFFUSE_ENV + vec3 lightMapIrradiance = textureCube(u_env_diffuseSampler, geometry.normal).rgb * u_envMapLight.diffuseIntensity; + #else + vec3 lightMapIrradiance = u_envMapLight.diffuse * u_envMapLight.diffuseIntensity; #endif - #if defined(O3_HAS_ENVMAP_LIGHT) - - #ifdef O3_USE_DIFFUSE_ENV - vec3 lightMapIrradiance = textureCube(u_env_diffuseSampler, geometry.normal).rgb * u_envMapLight.diffuseIntensity; - #else - vec3 lightMapIrradiance = u_envMapLight.diffuse * u_envMapLight.diffuseIntensity; - #endif - - #ifndef PHYSICALLY_CORRECT_LIGHTS - lightMapIrradiance *= PI; - #endif - - irradiance += lightMapIrradiance; - + #ifndef PHYSICALLY_CORRECT_LIGHTS + lightMapIrradiance *= PI; #endif + irradiance += lightMapIrradiance; + RE_IndirectDiffuse( irradiance, geometry, material, reflectedLight ); #endif diff --git a/packages/core/src/shaderlib/pbr/ibl_diffuse_frag_define.glsl b/packages/core/src/shaderlib/pbr/ibl_diffuse_frag_define.glsl index 11c78ce726..3283996f60 100644 --- a/packages/core/src/shaderlib/pbr/ibl_diffuse_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/ibl_diffuse_frag_define.glsl @@ -3,21 +3,3 @@ void RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricCo reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor ); } - -#ifdef O3_HAS_AMBIENT_LIGHT - -vec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) { - - vec3 irradiance = ambientLightColor; - - #ifndef PHYSICALLY_CORRECT_LIGHTS - - irradiance *= PI; - - #endif - - return irradiance; - -} - -#endif diff --git a/packages/core/src/shaderlib/pbr/ibl_specular_frag.glsl b/packages/core/src/shaderlib/pbr/ibl_specular_frag.glsl index 6ea3499434..a36af51d15 100644 --- a/packages/core/src/shaderlib/pbr/ibl_specular_frag.glsl +++ b/packages/core/src/shaderlib/pbr/ibl_specular_frag.glsl @@ -6,7 +6,7 @@ -#if defined( O3_HAS_ENVMAP_LIGHT ) && defined( RE_IndirectSpecular ) +#if defined( RE_IndirectSpecular ) radiance += getLightProbeIndirectRadiance( geometry, Material_BlinnShininessExponent( material ), int(u_envMapLight.mipMapLevel) ); diff --git a/packages/core/src/shaderlib/pbr/ibl_specular_frag_define.glsl b/packages/core/src/shaderlib/pbr/ibl_specular_frag_define.glsl index ce0ec1a618..7b0b70372e 100644 --- a/packages/core/src/shaderlib/pbr/ibl_specular_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/ibl_specular_frag_define.glsl @@ -32,29 +32,25 @@ float getSpecularMIPLevel( const in float blinnShininessExponent, const in int m } -#ifdef O3_HAS_ENVMAP_LIGHT - vec3 getLightProbeIndirectRadiance( /*const in SpecularLightProbe specularLightProbe,*/ const in GeometricContext geometry, const in float blinnShininessExponent, const in int maxMIPLevel ) { #if !defined(O3_USE_SPECULAR_ENV) && !defined(HAS_REFLECTIONMAP) - return u_envMapLight.specular * u_envMapLight.specularIntensity * u_envMapIntensity; + return vec3(0.0); #else - #ifdef ENVMAPMODE_REFRACT + #ifdef ENVMAPMODE_REFRACT - vec3 reflectVec = refract( -geometry.viewDir, geometry.normal, u_refractionRatio ); + vec3 reflectVec = refract( -geometry.viewDir, geometry.normal, u_refractionRatio ); - #else + #else - vec3 reflectVec = reflect( -geometry.viewDir, geometry.normal ); + vec3 reflectVec = reflect( -geometry.viewDir, geometry.normal ); - #endif + #endif // reflectVec = inverseTransformDirection( reflectVec, u_viewMat ); - reflectVec = mat3(u_envMapLight.transformMatrix) * reflectVec; - float specularMIPLevel = getSpecularMIPLevel( blinnShininessExponent, maxMIPLevel ); #ifdef HAS_TEX_LOD @@ -79,7 +75,6 @@ vec3 getLightProbeIndirectRadiance( /*const in SpecularLightProbe specularLightP #endif } -#endif void RE_IndirectSpecular_Physical( const in vec3 radiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) { diff --git a/packages/core/src/shaderlib/pbr/runtime_frag_define.glsl b/packages/core/src/shaderlib/pbr/runtime_frag_define.glsl index ee8808c630..ec5627f545 100644 --- a/packages/core/src/shaderlib/pbr/runtime_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/runtime_frag_define.glsl @@ -1,7 +1,6 @@ struct IncidentLight { vec3 color; vec3 direction; - bool visible; }; struct ReflectedLight { vec3 directDiffuse; diff --git a/packages/core/src/shaderlib/pbr/util_frag_define.glsl b/packages/core/src/shaderlib/pbr/util_frag_define.glsl index 71cd0b1a33..e88ce3d43a 100644 --- a/packages/core/src/shaderlib/pbr/util_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/util_frag_define.glsl @@ -29,34 +29,6 @@ vec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) { return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz ); } -// todo: enhance -float punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) { - - if( decayExponent > 0.0 ) { - - #if defined ( PHYSICALLY_CORRECT_LIGHTS ) - - // based upon Frostbite 3 Moving to Physically-based Rendering - // page 32, equation 26: E[window1] - // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf - // this is intended to be used on spot and point lights who are represented as luminous intensity - // but who must be converted to luminous irradiance for surface lighting calculation - float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 ); - float maxDistanceCutoffFactor = pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) ); - return distanceFalloff * maxDistanceCutoffFactor; - - #else - - return pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent ); - - #endif - - } - - return 1.0; - -} - vec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) { return RECIPROCAL_PI * diffuseColor; diff --git a/packages/core/src/shaderlib/point_light_frag.glsl b/packages/core/src/shaderlib/point_light_frag.glsl index d1c8476a61..e360ab02b4 100644 --- a/packages/core/src/shaderlib/point_light_frag.glsl +++ b/packages/core/src/shaderlib/point_light_frag.glsl @@ -4,11 +4,9 @@ struct PointLight { vec3 color; vec3 position; float distance; - float decay; }; uniform vec3 u_pointLightColor[ O3_POINT_LIGHT_COUNT ]; uniform vec3 u_pointLightPosition[ O3_POINT_LIGHT_COUNT ]; uniform float u_pointLightDistance[ O3_POINT_LIGHT_COUNT ]; -uniform float u_pointLightDecay[ O3_POINT_LIGHT_COUNT ]; #endif diff --git a/packages/core/src/shaderlib/spot_light_frag.glsl b/packages/core/src/shaderlib/spot_light_frag.glsl index 3015eafd8f..d2ee4e222e 100644 --- a/packages/core/src/shaderlib/spot_light_frag.glsl +++ b/packages/core/src/shaderlib/spot_light_frag.glsl @@ -5,21 +5,15 @@ struct SpotLight { vec3 position; vec3 direction; float distance; - float decay; - float angle; - float penumbra; + float angleCos; float penumbraCos; - float coneCos; }; uniform vec3 u_spotLightColor[ O3_SPOT_LIGHT_COUNT ]; uniform vec3 u_spotLightPosition[ O3_SPOT_LIGHT_COUNT ]; uniform vec3 u_spotLightDirection[ O3_SPOT_LIGHT_COUNT ]; uniform float u_spotLightDistance[ O3_SPOT_LIGHT_COUNT ]; -uniform float u_spotLightDecay[ O3_SPOT_LIGHT_COUNT ]; -uniform float u_spotLightAngle[ O3_SPOT_LIGHT_COUNT ]; -uniform float u_spotLightPenumbra[ O3_SPOT_LIGHT_COUNT ]; +uniform float u_spotLightAngleCos[ O3_SPOT_LIGHT_COUNT ]; uniform float u_spotLightPenumbraCos[ O3_SPOT_LIGHT_COUNT ]; -uniform float u_spotLightConeCos[ O3_SPOT_LIGHT_COUNT ]; #endif diff --git a/packages/core/src/shadow/LightShadow.ts b/packages/core/src/shadow/LightShadow.ts index 2144a800a8..c041afcd32 100644 --- a/packages/core/src/shadow/LightShadow.ts +++ b/packages/core/src/shadow/LightShadow.ts @@ -92,7 +92,6 @@ export class LightShadow { /** * The RenderTarget corresponding to the shadow map. - * @readonly */ get renderTarget(): RenderTarget { return this._renderTarget; @@ -100,7 +99,6 @@ export class LightShadow { /** * Shadow map's color render texture. - * @readonly */ get map(): RenderColorTexture { return this._renderTarget.getColorTexture(); @@ -108,7 +106,6 @@ export class LightShadow { /** * Shadow map size. - * @readonly */ get mapSize(): Vector2 { return this._mapSize; diff --git a/packages/core/src/shadow/ShadowMapPass.ts b/packages/core/src/shadow/ShadowMapPass.ts index 60e79c4f34..732bbcacc2 100644 --- a/packages/core/src/shadow/ShadowMapPass.ts +++ b/packages/core/src/shadow/ShadowMapPass.ts @@ -1,4 +1,4 @@ -import { Vector4 } from "@oasis-engine/math"; +import { Color } from "@oasis-engine/math"; import { Camera } from "../Camera"; import { Layer } from "../Layer"; import { Light } from "../lighting/Light"; @@ -29,8 +29,9 @@ export class ShadowMapPass extends RenderPass { mask: Layer, light: Light ) { - super(name, priority, renderTarget, replaceMaterial, mask, new Vector4(1, 1, 1, 1)); + super(name, priority, renderTarget, replaceMaterial, mask); this.light = light; + this.clearColor = new Color(1, 1, 1, 1); } /** diff --git a/packages/core/src/shadow/ShadowPass.ts b/packages/core/src/shadow/ShadowPass.ts index 6a061212e4..3b7ea19272 100644 --- a/packages/core/src/shadow/ShadowPass.ts +++ b/packages/core/src/shadow/ShadowPass.ts @@ -1,5 +1,5 @@ -import { ClearMode } from "../base/Constant"; import { Camera } from "../Camera"; +import { CameraClearFlags } from "../enums/CameraClearFlags"; import { LightFeature } from "../lighting/LightFeature"; import { RenderPass } from "../RenderPipeline/RenderPass"; import { RenderQueue } from "../RenderPipeline/RenderQueue"; @@ -11,7 +11,7 @@ import { LightShadow } from "./LightShadow"; export class ShadowPass extends RenderPass { constructor(...args) { super(...args); - this.clearMode = ClearMode.DONT_CLEAR; + this.clearFlags = CameraClearFlags.None; } /** diff --git a/packages/core/src/sky/Sky.ts b/packages/core/src/sky/Sky.ts new file mode 100644 index 0000000000..c97ddefcda --- /dev/null +++ b/packages/core/src/sky/Sky.ts @@ -0,0 +1,15 @@ +import { Matrix } from "@oasis-engine/math"; +import { Mesh } from "../graphic"; +import { Material } from "../material"; + +/** + * Sky. + */ +export class Sky { + /** Material of the sky. */ + material: Material; + /** Mesh of the sky. */ + mesh: Mesh; + /** @internal */ + _matrix: Matrix = new Matrix(); +} diff --git a/packages/core/src/skybox/SkyBoxMaterial.ts b/packages/core/src/sky/SkyBoxMaterial.ts similarity index 64% rename from packages/core/src/skybox/SkyBoxMaterial.ts rename to packages/core/src/sky/SkyBoxMaterial.ts index 0f4ac04122..521579e719 100644 --- a/packages/core/src/skybox/SkyBoxMaterial.ts +++ b/packages/core/src/sky/SkyBoxMaterial.ts @@ -3,6 +3,7 @@ import { Material } from "../material/Material"; import { CompareFunction } from "../shader/enums/CompareFunction"; import { CullMode } from "../shader/enums/CullMode"; import { Shader } from "../shader/Shader"; +import { TextureCubeMap } from "../texture"; /** * SkyboxMaterial @@ -13,6 +14,14 @@ export class SkyBoxMaterial extends Material { this.renderState.rasterState.cullMode = CullMode.Off; this.renderState.depthState.compareFunction = CompareFunction.LessEqual; - this.renderQueueType = 0; + } + + /** Texture cube map of the sky box material. */ + get textureCubeMap(): TextureCubeMap { + return this.shaderData.getTexture("u_cube") as TextureCubeMap; + } + + set textureCubeMap(v: TextureCubeMap) { + this.shaderData.setTexture("u_cube", v); } } diff --git a/packages/core/src/sky/index.ts b/packages/core/src/sky/index.ts new file mode 100644 index 0000000000..69f7a93fd1 --- /dev/null +++ b/packages/core/src/sky/index.ts @@ -0,0 +1,2 @@ +export { Sky } from "./Sky"; +export { SkyBoxMaterial } from "./SkyBoxMaterial"; diff --git a/packages/core/src/skybox/SkyBox.ts b/packages/core/src/skybox/SkyBox.ts deleted file mode 100644 index 2e31e47af5..0000000000 --- a/packages/core/src/skybox/SkyBox.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { BoundingBox, Matrix } from "@oasis-engine/math"; -import { Camera } from "../Camera"; -import { Entity } from "../Entity"; -import { PrimitiveMesh } from "../mesh/PrimitiveMesh"; -import { MeshRenderer } from "../mesh/MeshRenderer"; -import { TextureCubeMap } from "../texture/TextureCubeMap"; -import { SkyBoxMaterial } from "./SkyBoxMaterial"; - -/** - * Skybox Component - */ -export class SkyBox extends MeshRenderer { - private _skyBoxMap: TextureCubeMap; - private _matrix: Matrix = new Matrix(); - private _initBounds: boolean = false; - - /** - * Contructor of skybox - * @param - Entity - */ - constructor(entity: Entity) { - super(entity); - this.mesh = PrimitiveMesh.createCuboid(this.engine, 2, 2, 2); - this.setMaterial(new SkyBoxMaterial(this.engine)); - } - - /** - * @internal - */ - _render(camera: Camera): void { - if (!this._skyBoxMap) return; - - const modelMatrix = this.entity.transform.worldMatrix; - const view = camera.viewMatrix; - const proj = camera.projectionMatrix; - const matrix = this._matrix; - - Matrix.multiply(view, modelMatrix, matrix); - const e = matrix.elements; - e[12] = e[13] = e[14] = 0; - Matrix.multiply(proj, matrix, matrix); - this.shaderData.setMatrix("u_mvpNoscale", matrix); - - super._render(camera); - } - - /** - * CubeMap of current skybox. - */ - get skyBoxMap(): TextureCubeMap { - return this._skyBoxMap; - } - - set skyBoxMap(v: TextureCubeMap) { - this._skyBoxMap = v; - v && this.getMaterial().shaderData.setTexture("u_cube", v); - } - - /** - * @override - */ - protected _updateBounds(worldBounds: BoundingBox): void { - if (!this._initBounds) { - worldBounds.min.setValue(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY); - worldBounds.max.setValue(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY); - this._initBounds = true; - } - } -} diff --git a/packages/core/src/skybox/index.ts b/packages/core/src/skybox/index.ts deleted file mode 100644 index 23bf7468a1..0000000000 --- a/packages/core/src/skybox/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SkyBox } from "./SkyBox"; diff --git a/packages/core/src/texture/RenderColorTexture.ts b/packages/core/src/texture/RenderColorTexture.ts index 55139220c7..57344822ae 100644 --- a/packages/core/src/texture/RenderColorTexture.ts +++ b/packages/core/src/texture/RenderColorTexture.ts @@ -16,7 +16,6 @@ export class RenderColorTexture extends Texture { /** * Texture format. - * @readonly */ get format(): RenderBufferColorFormat { return this._format; @@ -24,7 +23,6 @@ export class RenderColorTexture extends Texture { /** * Whether to render to a cube texture. - * @readonly */ get isCube(): boolean { return this._isCube; diff --git a/packages/core/src/texture/RenderDepthTexture.ts b/packages/core/src/texture/RenderDepthTexture.ts index 816c4335c9..112967b9fa 100644 --- a/packages/core/src/texture/RenderDepthTexture.ts +++ b/packages/core/src/texture/RenderDepthTexture.ts @@ -14,7 +14,6 @@ export class RenderDepthTexture extends Texture { /** * Texture format. - * @readonly */ get format(): RenderBufferDepthFormat { return this._format; @@ -22,7 +21,6 @@ export class RenderDepthTexture extends Texture { /** * Whether to render to a cube texture. - * @readonly */ get isCube(): boolean { return this._isCube; diff --git a/packages/core/src/texture/RenderTarget.ts b/packages/core/src/texture/RenderTarget.ts index 5f320b3532..496a8cb8f6 100644 --- a/packages/core/src/texture/RenderTarget.ts +++ b/packages/core/src/texture/RenderTarget.ts @@ -25,7 +25,6 @@ export class RenderTarget extends EngineObject { /** * Render target width. - * @readonly */ get width(): number { return this._width; @@ -33,7 +32,6 @@ export class RenderTarget extends EngineObject { /** * Render target height. - * @readonly */ get height(): number { return this._height; @@ -41,7 +39,6 @@ export class RenderTarget extends EngineObject { /** * Render color texture count. - * @readonly */ get colorTextureCount(): number { return this._colorTextures.length; @@ -49,7 +46,6 @@ export class RenderTarget extends EngineObject { /** * Depth texture. - * @readonly */ get depthTexture(): RenderDepthTexture | null { return this._depthTexture; @@ -58,7 +54,6 @@ export class RenderTarget extends EngineObject { /** * Anti-aliasing level. * @remarks If the anti-aliasing level set is greater than the maximum level supported by the hardware, the maximum level of the hardware will be used. - * @readonly */ get antiAliasing(): number { return this._antiAliasing; diff --git a/packages/core/src/texture/Texture.ts b/packages/core/src/texture/Texture.ts index 7e0fb0c76e..29dfe42499 100644 --- a/packages/core/src/texture/Texture.ts +++ b/packages/core/src/texture/Texture.ts @@ -26,7 +26,6 @@ export abstract class Texture extends RefObject { /** * The width of the texture. - * @readonly */ get width(): number { return this._width; @@ -34,7 +33,6 @@ export abstract class Texture extends RefObject { /** * The height of the texture. - * @readonly */ get height(): number { return this._height; @@ -70,7 +68,6 @@ export abstract class Texture extends RefObject { /** * Texture mipmapping count. - * @readonly */ get mipmapCount(): number { return this._mipmapCount; diff --git a/packages/core/src/texture/Texture2D.ts b/packages/core/src/texture/Texture2D.ts index 968e7af2c0..edd7c6a953 100644 --- a/packages/core/src/texture/Texture2D.ts +++ b/packages/core/src/texture/Texture2D.ts @@ -13,7 +13,6 @@ export class Texture2D extends Texture { /** * Texture format. - * @readonly */ get format(): TextureFormat { return this._format; diff --git a/packages/core/src/texture/TextureCubeMap.ts b/packages/core/src/texture/TextureCubeMap.ts index 8565e5eabf..39cde23271 100644 --- a/packages/core/src/texture/TextureCubeMap.ts +++ b/packages/core/src/texture/TextureCubeMap.ts @@ -14,7 +14,6 @@ export class TextureCubeMap extends Texture { /** * Texture format. - * @readonly */ get format(): TextureFormat { return this._format; diff --git a/packages/core/tests/Camera.test.ts b/packages/core/tests/Camera.test.ts index 8d0cf93573..90cfa0fc5b 100644 --- a/packages/core/tests/Camera.test.ts +++ b/packages/core/tests/Camera.test.ts @@ -25,8 +25,6 @@ describe("camera test", function () { expect(camera.aspectRatio).toEqual(1); expect(camera._renderPipeline).not.toBeUndefined(); expect(camera.entity.transform.worldPosition).not.toBeUndefined(); - // TODO: deprecated - expect(camera.backgroundColor).toEqual({ x: 0.25, y: 0.25, z: 0.25, w: 1 }); expect(camera.viewport).toEqual({ x: 0, y: 0, z: 1, w: 1 }); expect(camera.fieldOfView).toEqual(45); expect(camera.isOrthographic).toEqual(false); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index b5bbd2a5c3..a214f83cb0 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -9,6 +9,7 @@ "declarationDir": "types", "emitDeclarationOnly": true, "stripInternal": true, + "skipLibCheck": true, "sourceMap": true, "strict": false, "incremental": false diff --git a/packages/design/tsconfig.json b/packages/design/tsconfig.json index fe0c58d146..1dc8cad306 100644 --- a/packages/design/tsconfig.json +++ b/packages/design/tsconfig.json @@ -9,6 +9,7 @@ "declarationDir": "types", "emitDeclarationOnly": true, "sourceMap": true, + "skipLibCheck": true, "incremental": false }, "include": [ diff --git a/packages/draco/tsconfig.json b/packages/draco/tsconfig.json index fe0c58d146..1dc8cad306 100644 --- a/packages/draco/tsconfig.json +++ b/packages/draco/tsconfig.json @@ -9,6 +9,7 @@ "declarationDir": "types", "emitDeclarationOnly": true, "sourceMap": true, + "skipLibCheck": true, "incremental": false }, "include": [ diff --git a/packages/framebuffer-picker/tsconfig.json b/packages/framebuffer-picker/tsconfig.json index 19c992c3d9..757c16677a 100644 --- a/packages/framebuffer-picker/tsconfig.json +++ b/packages/framebuffer-picker/tsconfig.json @@ -9,6 +9,7 @@ "declarationDir": "types", "emitDeclarationOnly": true, "sourceMap": true, + "skipLibCheck": true, "strict": false, "incremental": false }, diff --git a/packages/loader/package.json b/packages/loader/package.json index 75e1de9b05..ee7deb32ff 100755 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -8,6 +8,7 @@ }, "main": "dist/main.js", "module": "dist/module.js", + "debug": "src/index.ts", "files": [ "dist/**/*", "types/**/*" diff --git a/packages/loader/src/GLTF.ts b/packages/loader/src/GLTF.ts deleted file mode 100644 index 9caf216de4..0000000000 --- a/packages/loader/src/GLTF.ts +++ /dev/null @@ -1,693 +0,0 @@ -import { Texture2D } from "@oasis-engine/core"; - -export type GlTfId = number; -/** - * Indices of those attributes that deviate from their initialization value. - */ -export interface AccessorSparseIndices { - /** - * The index of the bufferView with sparse indices. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target. - */ - bufferView: GlTfId; - /** - * The offset relative to the start of the bufferView in bytes. Must be aligned. - */ - byteOffset?: number; - /** - * The indices data type. - */ - componentType: 5121 | 5123 | 5125 | number; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * Array of size `accessor.sparse.count` times number of components storing the displaced accessor attributes pointed by `accessor.sparse.indices`. - */ -export interface AccessorSparseValues { - /** - * The index of the bufferView with sparse values. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target. - */ - bufferView: GlTfId; - /** - * The offset relative to the start of the bufferView in bytes. Must be aligned. - */ - byteOffset?: number; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * Sparse storage of attributes that deviate from their initialization value. - */ -export interface AccessorSparse { - /** - * Number of entries stored in the sparse array. - */ - count: number; - /** - * Index array of size `count` that points to those accessor attributes that deviate from their initialization value. Indices must strictly increase. - */ - indices: AccessorSparseIndices; - /** - * Array of size `count` times number of components, storing the displaced accessor attributes pointed by `indices`. Substituted values must have the same `componentType` and number of components as the base accessor. - */ - values: AccessorSparseValues; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * A typed view into a bufferView. A bufferView contains raw binary data. An accessor provides a typed view into a bufferView or a subset of a bufferView similar to how WebGL's `vertexAttribPointer()` defines an attribute in a buffer. - */ -export interface Accessor { - /** - * The index of the bufferView. - */ - bufferView?: GlTfId; - /** - * The offset relative to the start of the bufferView in bytes. - */ - byteOffset?: number; - /** - * The datatype of components in the attribute. - */ - componentType: 5120 | 5121 | 5122 | 5123 | 5125 | 5126 | number; - /** - * Specifies whether integer data values should be normalized. - */ - normalized?: boolean; - /** - * The number of attributes referenced by this accessor. - */ - count: number; - /** - * Specifies if the attribute is a scalar, vector, or matrix. - */ - type: "SCALAR" | "VEC2" | "VEC3" | "VEC4" | "MAT2" | "MAT3" | "MAT4" | string; - /** - * Maximum value of each component in this attribute. - */ - max?: number[]; - /** - * Minimum value of each component in this attribute. - */ - min?: number[]; - /** - * Sparse storage of attributes that deviate from their initialization value. - */ - sparse?: AccessorSparse; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * The index of the node and TRS property that an animation channel targets. - */ -export interface AnimationChannelTarget { - /** - * The index of the node to target. - */ - node?: GlTfId; - /** - * The name of the node's TRS property to modify, or the "weights" of the Morph Targets it instantiates. For the "translation" property, the values that are provided by the sampler are the translation along the x, y, and z axes. For the "rotation" property, the values are a quaternion in the order (x, y, z, w), where w is the scalar. For the "scale" property, the values are the scaling factors along the x, y, and z axes. - */ - path: "translation" | "rotation" | "scale" | "weights" | string; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * Targets an animation's sampler at a node's property. - */ -export interface AnimationChannel { - /** - * The index of a sampler in this animation used to compute the value for the target. - */ - sampler: GlTfId; - /** - * The index of the node and TRS property to target. - */ - target: AnimationChannelTarget; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * Combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target). - */ -export interface AnimationSampler { - /** - * The index of an accessor containing keyframe input values, e.g., time. - */ - input: GlTfId; - /** - * Interpolation algorithm. - */ - interpolation?: "LINEAR" | "STEP" | "CUBICSPLINE" | string; - /** - * The index of an accessor, containing keyframe output values. - */ - output: GlTfId; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * A keyframe animation. - */ -export interface Animation { - /** - * An array of channels, each of which targets an animation's sampler at a node's property. Different channels of the same animation can't have equal targets. - */ - channels: AnimationChannel[]; - /** - * An array of samplers that combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target). - */ - samplers: AnimationSampler[]; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * Metadata about the glTF asset. - */ -export interface Asset { - /** - * A copyright message suitable for display to credit the content creator. - */ - copyright?: string; - /** - * Tool that generated this glTF model. Useful for debugging. - */ - generator?: string; - /** - * The glTF version that this asset targets. - */ - version: string; - /** - * The minimum glTF version that this asset targets. - */ - minVersion?: string; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * A buffer points to binary geometry, animation, or skins. - */ -export interface Buffer { - /** - * The uri of the buffer. - */ - uri?: string; - /** - * The length of the buffer in bytes. - */ - byteLength: number; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * A view into a buffer generally representing a subset of the buffer. - */ -export interface BufferView { - /** - * The index of the buffer. - */ - buffer: GlTfId; - /** - * The offset into the buffer in bytes. - */ - byteOffset?: number; - /** - * The length of the bufferView in bytes. - */ - byteLength: number; - /** - * The stride, in bytes. - */ - byteStride?: number; - /** - * The target that the GPU buffer should be bound to. - */ - target?: 34962 | 34963 | number; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * An orthographic camera containing properties to create an orthographic projection matrix. - */ -export interface CameraOrthographic { - /** - * The floating-point horizontal magnification of the view. Must not be zero. - */ - xmag: number; - /** - * The floating-point vertical magnification of the view. Must not be zero. - */ - ymag: number; - /** - * The floating-point distance to the far clipping plane. `zfar` must be greater than `znear`. - */ - zfar: number; - /** - * The floating-point distance to the near clipping plane. - */ - znear: number; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * A perspective camera containing properties to create a perspective projection matrix. - */ -export interface CameraPerspective { - /** - * The floating-point aspect ratio of the field of view. - */ - aspectRatio?: number; - /** - * The floating-point vertical field of view in radians. - */ - yfov: number; - /** - * The floating-point distance to the far clipping plane. - */ - zfar?: number; - /** - * The floating-point distance to the near clipping plane. - */ - znear: number; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * A camera's projection. A node can reference a camera to apply a transform to place the camera in the scene. - */ -export interface Camera { - /** - * An orthographic camera containing properties to create an orthographic projection matrix. - */ - orthographic?: CameraOrthographic; - /** - * A perspective camera containing properties to create a perspective projection matrix. - */ - perspective?: CameraPerspective; - /** - * Specifies if the camera uses a perspective or orthographic projection. - */ - type: "perspective" | "orthographic" | string; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * Image data used to create a texture. Image can be referenced by URI or `bufferView` index. `mimeType` is required in the latter case. - */ -export interface Image { - /** - * The uri of the image. - */ - uri?: string; - /** - * The image's MIME type. - */ - mimeType?: "image/jpeg" | "image/png" | string; - /** - * The index of the bufferView that contains the image. Use this instead of the image's uri property. - */ - bufferView?: GlTfId; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * Reference to a texture. - */ -export interface TextureInfo { - /** - * The index of the texture. - */ - index: GlTfId; - /** - * The set index of texture's TEXCOORD attribute used for texture coordinate mapping. - */ - texCoord?: number; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. - */ -export interface MaterialPbrMetallicRoughness { - /** - * The material's base color factor. - */ - baseColorFactor?: number[]; - /** - * The base color texture. - */ - baseColorTexture?: TextureInfo; - /** - * The metalness of the material. - */ - metallicFactor?: number; - /** - * The roughness of the material. - */ - roughnessFactor?: number; - /** - * The metallic-roughness texture. - */ - metallicRoughnessTexture?: TextureInfo; - extensions?: any; - extras?: any; - [k: string]: any; -} -export interface MaterialNormalTextureInfo { - index?: any; - texCoord?: any; - /** - * The scalar multiplier applied to each normal vector of the normal texture. - */ - scale?: number; - extensions?: any; - extras?: any; - [k: string]: any; -} -export interface MaterialOcclusionTextureInfo { - index?: any; - texCoord?: any; - /** - * A scalar multiplier controlling the amount of occlusion applied. - */ - strength?: number; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * The material appearance of a primitive. - */ -export interface Material { - name?: any; - extensions?: any; - extras?: any; - /** - * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. When not specified, all the default values of `pbrMetallicRoughness` apply. - */ - pbrMetallicRoughness?: MaterialPbrMetallicRoughness; - /** - * The normal map texture. - */ - normalTexture?: MaterialNormalTextureInfo; - /** - * The occlusion map texture. - */ - occlusionTexture?: MaterialOcclusionTextureInfo; - /** - * The emissive map texture. - */ - emissiveTexture?: TextureInfo; - /** - * The emissive color of the material. - */ - emissiveFactor?: number[]; - /** - * The alpha rendering mode of the material. - */ - alphaMode?: "OPAQUE" | "MASK" | "BLEND" | string; - /** - * The alpha cutoff value of the material. - */ - alphaCutoff?: number; - /** - * Specifies whether the material is double sided. - */ - doubleSided?: boolean; - [k: string]: any; -} -/** - * Geometry to be rendered with the given material. - */ -export interface MeshPrimitive { - /** - * A dictionary object, where each key corresponds to mesh attribute semantic and each value is the index of the accessor containing attribute's data. - */ - attributes: { - [k: string]: GlTfId; - }; - /** - * The index of the accessor that contains the indices. - */ - indices?: GlTfId; - /** - * The index of the material to apply to this primitive when rendering. - */ - material?: GlTfId; - /** - * The type of primitives to render. - */ - mode?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | number; - /** - * An array of Morph Targets, each Morph Target is a dictionary mapping attributes (only `POSITION`, `NORMAL`, and `TANGENT` supported) to their deviations in the Morph Target. - */ - targets?: { - [k: string]: GlTfId; - }[]; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * A set of primitives to be rendered. A node can contain one mesh. A node's transform places the mesh in the scene. - */ -export interface Mesh { - /** - * An array of primitives, each defining geometry to be rendered with a material. - */ - primitives: MeshPrimitive[]; - /** - * Array of weights to be applied to the Morph Targets. - */ - weights?: number[]; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * A node in the node hierarchy. When the node contains `skin`, all `mesh.primitives` must contain `JOINTS_0` and `WEIGHTS_0` attributes. A node can have either a `matrix` or any combination of `translation`/`rotation`/`scale` (TRS) properties. TRS properties are converted to matrices and postmultiplied in the `T * R * S` order to compose the transformation matrix; first the scale is applied to the vertices, then the rotation, and then the translation. If none are provided, the transform is the identity. When a node is targeted for animation (referenced by an animation.channel.target), only TRS properties may be present; `matrix` will not be present. - */ -export interface Node { - /** - * The index of the camera referenced by this node. - */ - camera?: GlTfId; - /** - * The indices of this node's children. - */ - children?: GlTfId[]; - /** - * The index of the skin referenced by this node. - */ - skin?: GlTfId; - /** - * A floating-point 4x4 transformation matrix stored in column-major order. - */ - matrix?: number[]; - /** - * The index of the mesh in this node. - */ - mesh?: GlTfId; - /** - * The node's unit quaternion rotation in the order (x, y, z, w), where w is the scalar. - */ - rotation?: number[]; - /** - * The node's non-uniform scale, given as the scaling factors along the x, y, and z axes. - */ - scale?: number[]; - /** - * The node's translation along the x, y, and z axes. - */ - translation?: number[]; - /** - * The weights of the instantiated Morph Target. Number of elements must match number of Morph Targets of used mesh. - */ - weights?: number[]; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * Texture sampler properties for filtering and wrapping modes. - */ -export interface Sampler { - /** - * Magnification filter. - */ - magFilter?: 9728 | 9729 | number; - /** - * Minification filter. - */ - minFilter?: 9728 | 9729 | 9984 | 9985 | 9986 | 9987 | number; - /** - * s wrapping mode. - */ - wrapS?: 33071 | 33648 | 10497 | number; - /** - * t wrapping mode. - */ - wrapT?: 33071 | 33648 | 10497 | number; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * The root nodes of a scene. - */ -export interface Scene { - /** - * The indices of each root node. - */ - nodes?: GlTfId[]; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * Joints and matrices defining a skin. - */ -export interface Skin { - /** - * The index of the accessor containing the floating-point 4x4 inverse-bind matrices. The default is that each matrix is a 4x4 identity matrix, which implies that inverse-bind matrices were pre-applied. - */ - inverseBindMatrices?: GlTfId; - /** - * The index of the node used as a skeleton root. When undefined, joints transforms resolve to scene root. - */ - skeleton?: GlTfId; - /** - * Indices of skeleton nodes, used as joints in this skin. - */ - joints: GlTfId[]; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * A texture and its sampler. - */ -export interface Texture { - /** - * The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering should be used. - */ - sampler?: GlTfId; - /** - * The index of the image used by this texture. - */ - source?: GlTfId; - name?: any; - extensions?: any; - extras?: any; - [k: string]: any; -} -/** - * The root object for a glTF asset. - */ -export interface GlTf { - /** - * Names of glTF extensions used somewhere in this asset. - */ - extensionsUsed?: string[]; - /** - * Names of glTF extensions required to properly load this asset. - */ - extensionsRequired?: string[]; - /** - * An array of accessors. - */ - accessors?: Accessor[]; - /** - * An array of keyframe animations. - */ - animations?: Animation[]; - /** - * Metadata about the glTF asset. - */ - asset: Asset; - /** - * An array of buffers. - */ - buffers?: Buffer[]; - /** - * An array of bufferViews. - */ - bufferViews?: BufferView[]; - /** - * An array of cameras. - */ - cameras?: Camera[]; - /** - * An array of images. - */ - images?: Image[]; - /** - * An array of materials. - */ - materials?: Material[]; - /** - * An array of meshes. - */ - meshes?: Mesh[]; - /** - * An array of nodes. - */ - nodes?: Node[]; - /** - * An array of samplers. - */ - samplers?: Sampler[]; - /** - * The index of the default scene. - */ - scene?: GlTfId; - /** - * An array of scenes. - */ - scenes?: Scene[]; - /** - * An array of skins. - */ - skins?: Skin[]; - /** - * An array of textures. - */ - textures?: Texture[]; - extensions?: any; - extras?: any; - [k: string]: any; -} - -export interface LoadedGLTFResource { - buffers?: ArrayBuffer[]; - textures?: Texture2D[]; - shaders?: string[]; - gltf?: GlTf; -} diff --git a/packages/loader/src/GLTFLoader.ts b/packages/loader/src/GLTFLoader.ts index 0c0633e1e5..fa92e030c2 100644 --- a/packages/loader/src/GLTFLoader.ts +++ b/packages/loader/src/GLTFLoader.ts @@ -1,120 +1,22 @@ -import { - resourceLoader, - Loader, - AssetPromise, - AssetType, - LoadItem, - ResourceManager, - Texture2D -} from "@oasis-engine/core"; -import { GlTf, LoadedGLTFResource } from "./GLTF"; -import { parseGLTF, GLTFResource } from "./gltf/glTF"; -import { parseGLB } from "./gltf/glb"; -import { loadImageBuffer, getBufferData, parseRelativeUrl } from "./gltf/Util"; +import { AssetPromise, AssetType, Loader, LoadItem, resourceLoader, ResourceManager } from "@oasis-engine/core"; +import { GLTFParser } from "./gltf/GLTFParser"; +import { GLTFResource } from "./gltf/GLTFResource"; @resourceLoader(AssetType.Perfab, ["gltf", "glb"]) export class GLTFLoader extends Loader { - private baseUrl: string; load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { + const url = item.url; return new AssetPromise((resolve, reject) => { - const requestGLTFResource = this.isGLB(item.url) ? this.requestGLB : this.requestGLTF; - requestGLTFResource(item, resourceManager) - .then((res) => { - parseGLTF(res, resourceManager.engine).then((gltf) => { - resolve(gltf); - }); - }) + const resource = new GLTFResource(resourceManager.engine); + resource.url = url; + + GLTFParser.instance + .parse(resource) + .then(resolve) .catch((e) => { console.error(e); - reject("Error loading glTF JSON from " + item.url); + reject(`Error loading glTF model from ${url} .`); }); }); } - - private requestGLTF = (item: LoadItem, resourceManager: ResourceManager): Promise => { - return this.request(item.url, { - ...item, - type: "json" - }).then((res) => this._loadGLTFResources(item, res, resourceManager)); - }; - - private requestGLB = (item: LoadItem, resourceManager: ResourceManager): Promise => { - return this.request(item.url, { - ...item, - type: "arraybuffer" - }) - .then(parseGLB) - .then((res) => { - return { ...res, baseUrl: item.url, resourceManager }; - }) - .then(this._loadImages); - }; - - private isGLB(url: string): boolean { - return url.substring(url.lastIndexOf(".") + 1) === "glb"; - } - - /** - * Load resources in gltf. - * @param gltf - * @param resourceManager - */ - private _loadGLTFResources( - item: LoadItem, - gltf: GlTf, - resourceManager: ResourceManager - ): Promise { - // Buffer must be loaded first, then image. - return this._loadBuffers(item.url, gltf, resourceManager).then(this._loadImages); - } - - private _loadBuffers(baseUrl: string, gltf: GlTf, resourceManager: ResourceManager): Promise { - if (gltf.buffers) { - return Promise.all( - gltf.buffers.map((item) => { - if (item instanceof ArrayBuffer) { - return Promise.resolve(item); - } - return resourceManager.load({ - url: parseRelativeUrl(baseUrl, item.uri), - type: AssetType.Buffer - }); - }) - ).then((buffers) => { - return { buffers, gltf, baseUrl, resourceManager }; - }); - } - return Promise.resolve({ baseUrl, gltf, resourceManager }); - } - - private _loadImages = ({ - gltf, - buffers, - baseUrl, - resourceManager - }: LoadedGLTFResource & { baseUrl: string; resourceManager: ResourceManager }): Promise => { - if (gltf.images) { - return Promise.all( - gltf.images.map(({ uri, bufferView: bufferViewIndex, mimeType }) => { - if (uri) { - // Use base64 or url. - return resourceManager.load({ url: parseRelativeUrl(baseUrl, uri), type: AssetType.Texture2D }); - } else { - // Use bufferView. - const bufferView = gltf.bufferViews[bufferViewIndex]; - const bufferData = getBufferData(bufferView, buffers); - return loadImageBuffer(bufferData, mimeType).then((image) => { - const tex = new Texture2D(resourceManager.engine, image.width, image.height); - tex.setImageSource(image); - tex.generateMipmaps(); - return tex; - }); - } - }) - ).then((textures) => { - return { gltf, buffers, textures }; - }); - } - return Promise.resolve({ gltf, buffers }); - }; } diff --git a/packages/loader/src/gltf/GLTFParser.ts b/packages/loader/src/gltf/GLTFParser.ts new file mode 100644 index 0000000000..3f05d7c58b --- /dev/null +++ b/packages/loader/src/gltf/GLTFParser.ts @@ -0,0 +1,59 @@ +import { GLTFResource } from "./GLTFResource"; +import { AnimationParser } from "./parser/AnimationParser"; +import { BufferParser } from "./parser/BufferParser"; +import { EntityParser } from "./parser/EntityParser"; +import { MaterialParser } from "./parser/MaterialParser"; +import { MeshParser } from "./parser/MeshParser"; +import { Parser } from "./parser/Parser"; +import { SceneParser } from "./parser/SceneParser"; +import { SkinParser } from "./parser/SkinParser"; +import { TextureParser } from "./parser/TextureParser"; +import { Validator } from "./parser/Validator"; + +export class GLTFParser { + static instance = new GLTFParser([ + BufferParser, + Validator, + TextureParser, + MaterialParser, + MeshParser, + EntityParser, + SkinParser, + AnimationParser, + SceneParser + ]); + + private _pipes: Parser[] = []; + + private constructor(pipes: (new () => Parser)[]) { + pipes.forEach((pipe: new () => Parser, index: number) => { + this._pipes[index] = new pipe(); + }); + } + + parse(context: GLTFResource): Promise { + let lastPipe: void | Promise; + + return new Promise((resolve, reject) => { + this._pipes.forEach((parser: Parser) => { + if (lastPipe) { + lastPipe = lastPipe.then(() => { + return parser.parse(context); + }); + } else { + lastPipe = parser.parse(context); + } + }); + + if (lastPipe) { + lastPipe + .then(() => { + resolve(context); + }) + .catch(reject); + } else { + resolve(context); + } + }); + } +} diff --git a/packages/loader/src/gltf/GLTFResource.ts b/packages/loader/src/gltf/GLTFResource.ts new file mode 100644 index 0000000000..6a58085209 --- /dev/null +++ b/packages/loader/src/gltf/GLTFResource.ts @@ -0,0 +1,47 @@ +import { + AnimationClip, + BufferMesh, + Camera, + EngineObject, + Entity, + Light, + Material, + Renderer, + Skin, + Texture2D +} from "@oasis-engine/core"; +import { IGLTF } from "./Schema"; + +/** + * Product after GLTF parser, usually, `defaultSceneRoot` is only needed to use. + */ +export class GLTFResource extends EngineObject { + /** GLTF file url. */ + url: string; + /** GLTF file content. */ + gltf: IGLTF; + /** ArrayBuffer after BufferParser. */ + buffers: ArrayBuffer[]; + /** Oasis Texture2D after TextureParser. */ + textures?: Texture2D[]; + /** Oasis Material after MaterialParser. */ + materials?: Material[]; + /** Oasis BufferMesh after MeshParser. */ + meshes?: BufferMesh[][]; + /** Oasis Skin after SkinParser. */ + skins?: Skin[]; + /** Oasis AnimationClip after AnimationParser. */ + animations?: AnimationClip[]; + /** Oasis Entity after EntityParser. */ + entities: Entity[]; + /** Oasis Camera after SceneParser. */ + cameras?: Camera[]; + /** GLTF can export lights in extension KHR_lights_punctual */ + lights?: Light[]; + /** Oasis RootEntities after SceneParser. */ + sceneRoots: Entity[]; + /** Oasis RootEntity after SceneParser. */ + defaultSceneRoot: Entity; + /** Renderer can replace material by `renderer.setMaterial` if gltf use plugin-in KHR_materials_variants. */ + variants?: { renderer: Renderer; material: Material; variants: string[] }[]; +} diff --git a/packages/loader/src/gltf/GLTFUtil.ts b/packages/loader/src/gltf/GLTFUtil.ts new file mode 100644 index 0000000000..9518db6fdd --- /dev/null +++ b/packages/loader/src/gltf/GLTFUtil.ts @@ -0,0 +1,340 @@ +import { IndexFormat, TypedArray, VertexElement, VertexElementFormat } from "@oasis-engine/core"; +import { AccessorComponentType, AccessorType, IAccessor, IBufferView, IGLTF } from "./Schema"; + +export class GLTFUtil { + private constructor() {} + + /** + * Parse binary text for glb loader. + */ + static decodeText(array: Uint8Array): string { + if (typeof TextDecoder !== "undefined") { + return new TextDecoder().decode(array); + } + + // TextDecoder polyfill + let s = ""; + + for (let i = 0, il = array.length; i < il; i++) { + s += String.fromCharCode(array[i]); + } + + return decodeURIComponent(encodeURIComponent(s)); + } + + /** + * Get the number of bytes occupied by accessor type. + */ + static getAccessorTypeSize(accessorType: AccessorType): number { + switch (accessorType) { + case AccessorType.SCALAR: + return 1; + case AccessorType.VEC2: + return 2; + case AccessorType.VEC3: + return 3; + case AccessorType.VEC4: + return 4; + case AccessorType.MAT2: + return 4; + case AccessorType.MAT3: + return 9; + case AccessorType.MAT4: + return 16; + } + } + + /** + * Get the TypedArray corresponding to the component type. + */ + static getComponentType(componentType: AccessorComponentType) { + switch (componentType) { + case AccessorComponentType.BYTE: + return Int8Array; + case AccessorComponentType.UNSIGNED_BYTE: + return Uint8Array; + case AccessorComponentType.SHORT: + return Int16Array; + case AccessorComponentType.UNSIGNED_SHORT: + return Uint16Array; + case AccessorComponentType.UNSIGNED_INT: + return Uint32Array; + case AccessorComponentType.FLOAT: + return Float32Array; + } + } + + /** + * Get accessor data. + */ + static getAccessorData(gltf: IGLTF, accessor: IAccessor, buffers: ArrayBuffer[]): TypedArray { + const bufferViews = gltf.bufferViews; + const bufferView = bufferViews[accessor.bufferView]; + const arrayBuffer = buffers[bufferView.buffer]; + const accessorByteOffset = accessor.hasOwnProperty("byteOffset") ? accessor.byteOffset : 0; + const bufferViewByteOffset = bufferView.hasOwnProperty("byteOffset") ? bufferView.byteOffset : 0; + const byteOffset = accessorByteOffset + bufferViewByteOffset; + const accessorTypeSize = GLTFUtil.getAccessorTypeSize(accessor.type); + const length = accessorTypeSize * accessor.count; + const byteStride = bufferView.byteStride ?? 0; + + const arrayType = GLTFUtil.getComponentType(accessor.componentType); + let uint8Array; + if (byteStride) { + uint8Array = new Uint8Array(accessor.count * byteStride); + const originalBufferView = new Uint8Array(arrayBuffer, bufferViewByteOffset, bufferView.byteLength); + const accessorByteSize = accessorTypeSize * arrayType.BYTES_PER_ELEMENT; + for (let i = 0; i < accessor.count; i++) { + for (let j = 0; j < accessorByteSize; j++) { + uint8Array[i * byteStride + j] = originalBufferView[i * byteStride + accessorByteOffset + j]; + } + } + } else { + uint8Array = new Uint8Array(arrayBuffer.slice(byteOffset, byteOffset + length * arrayType.BYTES_PER_ELEMENT)); + } + + const typedArray = new arrayType(uint8Array.buffer); + + if (accessor.sparse) { + const { count, indices, values } = accessor.sparse; + const indicesBufferView = bufferViews[indices.bufferView]; + const valuesBufferView = bufferViews[values.bufferView]; + const indicesArrayBuffer = buffers[indicesBufferView.buffer]; + const valuesArrayBuffer = buffers[valuesBufferView.buffer]; + const indicesByteOffset = (indices.byteOffset ?? 0) + (indicesBufferView.byteOffset ?? 0); + const indicesByteLength = indicesBufferView.byteLength; + const valuesByteOffset = (values.byteOffset ?? 0) + (valuesBufferView.byteOffset ?? 0); + const valuesByteLength = valuesBufferView.byteLength; + + const indicesType = GLTFUtil.getComponentType(indices.componentType); + const indicesArray = new indicesType( + indicesArrayBuffer, + indicesByteOffset, + indicesByteLength / indicesType.BYTES_PER_ELEMENT + ); + const valuesArray = new arrayType( + valuesArrayBuffer, + valuesByteOffset, + valuesByteLength / arrayType.BYTES_PER_ELEMENT + ); + + for (let i = 0; i < count; i++) { + const replaceIndex = indicesArray[i]; + for (let j = 0; j < accessorTypeSize; j++) { + typedArray[replaceIndex * accessorTypeSize + j] = valuesArray[i * accessorTypeSize + j]; + } + } + } + + return typedArray; + } + + static getBufferViewData(bufferView: IBufferView, buffers: ArrayBuffer[]): ArrayBuffer { + const { buffer, byteOffset = 0, byteLength } = bufferView; + const arrayBuffer = buffers[buffer]; + + return arrayBuffer.slice(byteOffset, byteOffset + byteLength); + } + + static getVertexStride(gltf: IGLTF, accessor: IAccessor): number { + const stride = gltf.bufferViews[accessor.bufferView ?? 0].byteStride; + if (stride) { + return stride; + } + + const size = GLTFUtil.getAccessorTypeSize(accessor.type); + const componentType = GLTFUtil.getComponentType(accessor.componentType); + return size * componentType.BYTES_PER_ELEMENT; + } + + static createVertexElement(semantic: string, accessor: IAccessor, index: number): VertexElement { + const size = GLTFUtil.getAccessorTypeSize(accessor.type); + return new VertexElement( + semantic, + 0, + GLTFUtil.getElementFormat(accessor.componentType, size, accessor.normalized), + index + ); + } + + static getIndexFormat(type: AccessorComponentType): IndexFormat { + switch (type) { + case AccessorComponentType.UNSIGNED_BYTE: + return IndexFormat.UInt8; + case AccessorComponentType.UNSIGNED_SHORT: + return IndexFormat.UInt16; + case AccessorComponentType.UNSIGNED_INT: + return IndexFormat.UInt32; + } + } + + static getElementFormat(type: AccessorComponentType, size: number, normalized: boolean = false): VertexElementFormat { + if (type == AccessorComponentType.FLOAT) { + switch (size) { + case 1: + return VertexElementFormat.Float; + case 2: + return VertexElementFormat.Vector2; + case 3: + return VertexElementFormat.Vector3; + case 4: + return VertexElementFormat.Vector4; + } + } + + if (type == AccessorComponentType.SHORT) { + switch (size) { + case 2: + return normalized ? VertexElementFormat.NormalizedShort2 : VertexElementFormat.Short2; + case 3: + case 4: + return normalized ? VertexElementFormat.NormalizedShort4 : VertexElementFormat.Short4; + } + } + + if (type == AccessorComponentType.UNSIGNED_SHORT) { + switch (size) { + case 2: + return normalized ? VertexElementFormat.NormalizedUShort2 : VertexElementFormat.UShort2; + case 3: + case 4: + return normalized ? VertexElementFormat.NormalizedUShort4 : VertexElementFormat.UShort4; + } + } + + if (type == AccessorComponentType.BYTE) { + switch (size) { + case 2: + case 3: + case 4: + return normalized ? VertexElementFormat.NormalizedByte4 : VertexElementFormat.Byte4; + } + } + + if (type == AccessorComponentType.UNSIGNED_BYTE) { + switch (size) { + case 2: + case 3: + case 4: + return normalized ? VertexElementFormat.NormalizedUByte4 : VertexElementFormat.UByte4; + } + } + } + + /** + * Load image buffer + */ + static loadImageBuffer(imageBuffer: ArrayBuffer, type: string): Promise { + return new Promise((resolve, reject) => { + const blob = new window.Blob([imageBuffer], { type }); + const img = new Image(); + img.src = URL.createObjectURL(blob); + + img.crossOrigin = "anonymous"; + img.onerror = function () { + reject(new Error("Failed to load image buffer")); + }; + img.onload = function () { + // Call requestAnimationFrame to avoid iOS's bug. + requestAnimationFrame(() => { + resolve(img); + }); + }; + }); + } + + static isAbsoluteUrl(url: string): boolean { + return /^(?:http|blob|data:|\/)/.test(url); + } + + static parseRelativeUrl(baseUrl: string, relativeUrl: string): string { + if (GLTFUtil.isAbsoluteUrl(relativeUrl)) { + return relativeUrl; + } + + const char0 = relativeUrl.charAt(0); + if (char0 === ".") { + return GLTFUtil._formatRelativePath(relativeUrl + relativeUrl); + } + + return baseUrl.substring(0, baseUrl.lastIndexOf("/") + 1) + relativeUrl; + } + + /** + * Parse the glb format. + */ + static parseGLB( + glb: ArrayBuffer + ): { + gltf: IGLTF; + buffers: ArrayBuffer[]; + } { + const UINT32_LENGTH = 4; + const GLB_HEADER_MAGIC = 0x46546c67; // 'glTF' + const GLB_HEADER_LENGTH = 12; + const GLB_CHUNK_TYPES = { JSON: 0x4e4f534a, BIN: 0x004e4942 }; + + const dataView = new DataView(glb); + + // read header + const header = { + magic: dataView.getUint32(0, true), + version: dataView.getUint32(UINT32_LENGTH, true), + length: dataView.getUint32(2 * UINT32_LENGTH, true) + }; + + if (header.magic !== GLB_HEADER_MAGIC) { + console.error("Invalid glb magic number. Expected 0x46546C67, found 0x" + header.magic.toString(16)); + return null; + } + + // read main data + let chunkLength = dataView.getUint32(GLB_HEADER_LENGTH, true); + let chunkType = dataView.getUint32(GLB_HEADER_LENGTH + UINT32_LENGTH, true); + + // read glTF json + if (chunkType !== GLB_CHUNK_TYPES.JSON) { + console.error("Invalid glb chunk type. Expected 0x4E4F534A, found 0x" + chunkType.toString(16)); + return null; + } + + const glTFData = new Uint8Array(glb, GLB_HEADER_LENGTH + 2 * UINT32_LENGTH, chunkLength); + const gltf: IGLTF = JSON.parse(GLTFUtil.decodeText(glTFData)); + + // read all buffers + const buffers: ArrayBuffer[] = []; + let byteOffset = GLB_HEADER_LENGTH + 2 * UINT32_LENGTH + chunkLength; + + while (byteOffset < header.length) { + chunkLength = dataView.getUint32(byteOffset, true); + chunkType = dataView.getUint32(byteOffset + UINT32_LENGTH, true); + + if (chunkType !== GLB_CHUNK_TYPES.BIN) { + console.error("Invalid glb chunk type. Expected 0x004E4942, found 0x" + chunkType.toString(16)); + return null; + } + + const currentOffset = byteOffset + 2 * UINT32_LENGTH; + const buffer = glb.slice(currentOffset, currentOffset + chunkLength); + buffers.push(buffer); + + byteOffset += chunkLength + 2 * UINT32_LENGTH; + } + + return { + gltf, + buffers + }; + } + + private static _formatRelativePath(value: string): string { + const parts = value.split("/"); + for (let i = 0, n = parts.length; i < n; i++) { + if (parts[i] == "..") { + parts.splice(i - 1, 2); + i -= 2; + } + } + return parts.join("/"); + } +} diff --git a/packages/loader/src/gltf/Schema.ts b/packages/loader/src/gltf/Schema.ts new file mode 100644 index 0000000000..2788e699ab --- /dev/null +++ b/packages/loader/src/gltf/Schema.ts @@ -0,0 +1,855 @@ +/** + * Module for glTF 2.0 Interface + */ + +import { MeshTopology } from "@oasis-engine/core"; + +/** + * The datatype of the components in the attribute + */ +export enum AccessorComponentType { + /** + * Byte + */ + BYTE = 5120, + /** + * Unsigned Byte + */ + UNSIGNED_BYTE = 5121, + /** + * Short + */ + SHORT = 5122, + /** + * Unsigned Short + */ + UNSIGNED_SHORT = 5123, + /** + * Unsigned Int + */ + UNSIGNED_INT = 5125, + /** + * Float + */ + FLOAT = 5126 +} + +/** + * Specifies if the attirbute is a scalar, vector, or matrix + */ +export enum AccessorType { + /** + * Scalar + */ + SCALAR = "SCALAR", + /** + * Vector2 + */ + VEC2 = "VEC2", + /** + * Vector3 + */ + VEC3 = "VEC3", + /** + * Vector4 + */ + VEC4 = "VEC4", + /** + * Matrix2x2 + */ + MAT2 = "MAT2", + /** + * Matrix3x3 + */ + MAT3 = "MAT3", + /** + * Matrix4x4 + */ + MAT4 = "MAT4" +} + +/** + * The name of the node's TRS property to modify, or the weights of the Morph Targets it instantiates + */ +export enum AnimationChannelTargetPath { + /** + * Translation + */ + TRANSLATION = "translation", + /** + * Rotation + */ + ROTATION = "rotation", + /** + * Scale + */ + SCALE = "scale", + /** + * Weights + */ + WEIGHTS = "weights" +} + +/** + * Interpolation algorithm + */ +export enum AnimationSamplerInterpolation { + /** + * The animated values are linearly interpolated between keyframes + */ + LINEAR = "LINEAR", + /** + * The animated values remain constant to the output of the first keyframe, until the next keyframe + */ + STEP = "STEP", + /** + * The animation's interpolation is computed using a cubic spline with specified tangents + */ + CUBICSPLINE = "CUBICSPLINE" +} + +/** + * A camera's projection. A node can reference a camera to apply a transform to place the camera in the scene + */ +export enum CameraType { + /** + * A perspective camera containing properties to create a perspective projection matrix + */ + PERSPECTIVE = "perspective", + /** + * An orthographic camera containing properties to create an orthographic projection matrix + */ + ORTHOGRAPHIC = "orthographic" +} + +/** + * The mime-type of the image + */ +export enum ImageMimeType { + /** + * JPEG Mime-type + */ + JPEG = "image/jpeg", + /** + * PNG Mime-type + */ + PNG = "image/png" +} + +/** + * The alpha rendering mode of the material + */ +export enum MaterialAlphaMode { + /** + * The alpha value is ignored and the rendered output is fully opaque + */ + OPAQUE = "OPAQUE", + /** + * The rendered output is either fully opaque or fully transparent depending on the alpha value and the specified alpha cutoff value + */ + MASK = "MASK", + /** + * The alpha value is used to composite the source and destination areas. The rendered output is combined with the background using the normal painting operation (i.e. the Porter and Duff over operator) + */ + BLEND = "BLEND" +} + +/** + * Magnification filter. Valid values correspond to WebGL enums: 9728 (NEAREST) and 9729 (LINEAR) + */ +export enum TextureMagFilter { + /** + * Nearest + */ + NEAREST = 9728, + /** + * Linear + */ + LINEAR = 9729 +} + +/** + * Minification filter. All valid values correspond to WebGL enums + */ +export enum TextureMinFilter { + /** + * Nearest + */ + NEAREST = 9728, + /** + * Linear + */ + LINEAR = 9729, + /** + * Nearest Mip-Map Nearest + */ + NEAREST_MIPMAP_NEAREST = 9984, + /** + * Linear Mipmap Nearest + */ + LINEAR_MIPMAP_NEAREST = 9985, + /** + * Nearest Mipmap Linear + */ + NEAREST_MIPMAP_LINEAR = 9986, + /** + * Linear Mipmap Linear + */ + LINEAR_MIPMAP_LINEAR = 9987 +} + +/** + * S (U) wrapping mode. All valid values correspond to WebGL enums + */ +export enum TextureWrapMode { + /** + * Clamp to Edge + */ + CLAMP_TO_EDGE = 33071, + /** + * Mirrored Repeat + */ + MIRRORED_REPEAT = 33648, + /** + * Repeat + */ + REPEAT = 10497 +} + +/** + * glTF Property + */ +export interface IProperty { + /** + * Dictionary object with extension-specific objects + */ + extensions?: { + [key: string]: any; + }; + /** + * Application-Specific data + */ + extras?: any; +} + +/** + * glTF Child of Root Property + */ +export interface IChildRootProperty extends IProperty { + /** + * The user-defined name of this object + */ + name?: string; +} + +/** + * Indices of those attributes that deviate from their initialization value + */ +export interface IAccessorSparseIndices extends IProperty { + /** + * The index of the bufferView with sparse indices. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target + */ + bufferView: number; + /** + * The offset relative to the start of the bufferView in bytes. Must be aligned + */ + byteOffset?: number; + /** + * The indices data type. Valid values correspond to WebGL enums: 5121 (UNSIGNED_BYTE), 5123 (UNSIGNED_SHORT), 5125 (UNSIGNED_INT) + */ + componentType: AccessorComponentType; +} + +/** + * Array of size accessor.sparse.count times number of components storing the displaced accessor attributes pointed by accessor.sparse.indices + */ +export interface IAccessorSparseValues extends IProperty { + /** + * The index of the bufferView with sparse values. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target + */ + bufferView: number; + /** + * The offset relative to the start of the bufferView in bytes. Must be aligned + */ + byteOffset?: number; +} + +/** + * Sparse storage of attributes that deviate from their initialization value + */ +export interface IAccessorSparse extends IProperty { + /** + * The number of attributes encoded in this sparse accessor + */ + count: number; + /** + * Index array of size count that points to those accessor attributes that deviate from their initialization value. Indices must strictly increase + */ + indices: IAccessorSparseIndices; + /** + * Array of size count times number of components, storing the displaced accessor attributes pointed by indices. Substituted values must have the same componentType and number of components as the base accessor + */ + values: IAccessorSparseValues; +} + +/** + * A typed view into a bufferView. A bufferView contains raw binary data. An accessor provides a typed view into a bufferView or a subset of a bufferView similar to how WebGL's vertexAttribPointer() defines an attribute in a buffer + */ +export interface IAccessor extends IChildRootProperty { + /** + * The index of the bufferview + */ + bufferView?: number; + /** + * The offset relative to the start of the bufferView in bytes + */ + byteOffset?: number; + /** + * The datatype of components in the attribute + */ + componentType: AccessorComponentType; + /** + * Specifies whether integer data values should be normalized + */ + normalized?: boolean; + /** + * The number of attributes referenced by this accessor + */ + count: number; + /** + * Specifies if the attribute is a scalar, vector, or matrix + */ + type: AccessorType; + /** + * Maximum value of each component in this attribute + */ + max?: number[]; + /** + * Minimum value of each component in this attribute + */ + min?: number[]; + /** + * Sparse storage of attributes that deviate from their initialization value + */ + sparse?: IAccessorSparse; +} + +/** + * Targets an animation's sampler at a node's property + */ +export interface IAnimationChannel extends IProperty { + /** + * The index of a sampler in this animation used to compute the value for the target + */ + sampler: number; + /** + * The index of the node and TRS property to target + */ + target: IAnimationChannelTarget; +} + +/** + * The index of the node and TRS property that an animation channel targets + */ +export interface IAnimationChannelTarget extends IProperty { + /** + * The index of the node to target + */ + node: number; + /** + * The name of the node's TRS property to modify, or the weights of the Morph Targets it instantiates + */ + path: AnimationChannelTargetPath; +} + +/** + * Combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target) + */ +export interface IAnimationSampler extends IProperty { + /** + * The index of an accessor containing keyframe input values, e.g., time + */ + input: number; + /** + * Interpolation algorithm + */ + interpolation?: AnimationSamplerInterpolation; + /** + * The index of an accessor, containing keyframe output values + */ + output: number; +} + +/** + * A keyframe animation + */ +export interface IAnimation extends IChildRootProperty { + /** + * An array of channels, each of which targets an animation's sampler at a node's property + */ + channels: IAnimationChannel[]; + /** + * An array of samplers that combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target) + */ + samplers: IAnimationSampler[]; +} + +/** + * Metadata about the glTF asset + */ +export interface IAsset extends IChildRootProperty { + /** + * A copyright message suitable for display to credit the content creator + */ + copyright?: string; + /** + * Tool that generated this glTF model. Useful for debugging + */ + generator?: string; + /** + * The glTF version that this asset targets + */ + version: string; + /** + * The minimum glTF version that this asset targets + */ + minVersion?: string; +} + +/** + * A buffer points to binary geometry, animation, or skins + */ +export interface IBuffer extends IChildRootProperty { + /** + * The uri of the buffer. Relative paths are relative to the .gltf file. Instead of referencing an external file, the uri can also be a data-uri + */ + uri?: string; + /** + * The length of the buffer in bytes + */ + byteLength: number; +} + +/** + * A view into a buffer generally representing a subset of the buffer + */ +export interface IBufferView extends IChildRootProperty { + /** + * The index of the buffer + */ + buffer: number; + /** + * The offset into the buffer in bytes + */ + byteOffset?: number; + /** + * The lenth of the bufferView in bytes + */ + byteLength: number; + /** + * The stride, in bytes + */ + byteStride?: number; +} + +/** + * An orthographic camera containing properties to create an orthographic projection matrix + */ +export interface ICameraOrthographic extends IProperty { + /** + * The floating-point horizontal magnification of the view. Must not be zero + */ + xmag: number; + /** + * The floating-point vertical magnification of the view. Must not be zero + */ + ymag: number; + /** + * The floating-point distance to the far clipping plane. zfar must be greater than znear + */ + zfar: number; + /** + * The floating-point distance to the near clipping plane + */ + znear: number; +} + +/** + * A perspective camera containing properties to create a perspective projection matrix + */ +export interface ICameraPerspective extends IProperty { + /** + * The floating-point aspect ratio of the field of view + */ + aspectRatio?: number; + /** + * The floating-point vertical field of view in radians + */ + yfov: number; + /** + * The floating-point distance to the far clipping plane + */ + zfar?: number; + /** + * The floating-point distance to the near clipping plane + */ + znear: number; +} + +/** + * A camera's projection. A node can reference a camera to apply a transform to place the camera in the scene + */ +export interface ICamera extends IChildRootProperty { + /** + * An orthographic camera containing properties to create an orthographic projection matrix + */ + orthographic?: ICameraOrthographic; + /** + * A perspective camera containing properties to create a perspective projection matrix + */ + perspective?: ICameraPerspective; + /** + * Specifies if the camera uses a perspective or orthographic projection + */ + type: CameraType; +} + +/** + * Image data used to create a texture. Image can be referenced by URI or bufferView index. mimeType is required in the latter case + */ +export interface IImage extends IChildRootProperty { + /** + * The uri of the image. Relative paths are relative to the .gltf file. Instead of referencing an external file, the uri can also be a data-uri. The image format must be jpg or png + */ + uri?: string; + /** + * The image's MIME type + */ + mimeType?: ImageMimeType; + /** + * The index of the bufferView that contains the image. Use this instead of the image's uri property + */ + bufferView?: number; +} + +/** + * Material Normal Texture Info + */ +export interface IMaterialNormalTextureInfo extends ITextureInfo { + /** + * The scalar multiplier applied to each normal vector of the normal texture + */ + scale?: number; +} + +/** + * Material Occlusion Texture Info + */ +export interface IMaterialOcclusionTextureInfo extends ITextureInfo { + /** + * A scalar multiplier controlling the amount of occlusion applied + */ + strength?: number; +} + +/** + * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology + */ +export interface IMaterialPbrMetallicRoughness { + /** + * The material's base color factor + */ + baseColorFactor?: number[]; + /** + * The base color texture + */ + baseColorTexture?: ITextureInfo; + /** + * The metalness of the material + */ + metallicFactor?: number; + /** + * The roughness of the material + */ + roughnessFactor?: number; + /** + * The metallic-roughness texture + */ + metallicRoughnessTexture?: ITextureInfo; +} + +/** + * The material appearance of a primitive + */ +export interface IMaterial extends IChildRootProperty { + /** + * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. When not specified, all the default values of pbrMetallicRoughness apply + */ + pbrMetallicRoughness?: IMaterialPbrMetallicRoughness; + /** + * The normal map texture + */ + normalTexture?: IMaterialNormalTextureInfo; + /** + * The occlusion map texture + */ + occlusionTexture?: IMaterialOcclusionTextureInfo; + /** + * The emissive map texture + */ + emissiveTexture?: ITextureInfo; + /** + * The RGB components of the emissive color of the material. These values are linear. If an emissiveTexture is specified, this value is multiplied with the texel values + */ + emissiveFactor?: number[]; + /** + * The alpha rendering mode of the material + */ + alphaMode?: MaterialAlphaMode; + /** + * The alpha cutoff value of the material + */ + alphaCutoff?: number; + /** + * Specifies whether the material is double sided + */ + doubleSided?: boolean; +} + +/** + * Geometry to be rendered with the given material + */ +export interface IMeshPrimitive extends IProperty { + /** + * A dictionary object, where each key corresponds to mesh attribute semantic and each value is the index of the accessor containing attribute's data + */ + attributes: { + [name: string]: number; + }; + /** + * The index of the accessor that contains the indices + */ + indices?: number; + /** + * The index of the material to apply to this primitive when rendering + */ + material?: number; + /** + * The type of primitives to render. All valid values correspond to WebGL enums + */ + mode?: MeshTopology; + /** + * An array of Morph Targets, each Morph Target is a dictionary mapping attributes (only POSITION, NORMAL, and TANGENT supported) to their deviations in the Morph Target + */ + targets?: { + [name: string]: number; + }[]; +} + +/** + * A set of primitives to be rendered. A node can contain one mesh. A node's transform places the mesh in the scene + */ +export interface IMesh extends IChildRootProperty { + /** + * An array of primitives, each defining geometry to be rendered with a material + */ + primitives: IMeshPrimitive[]; + /** + * Array of weights to be applied to the Morph Targets + */ + weights?: number[]; +} + +/** + * A node in the node hierarchy + */ +export interface INode extends IChildRootProperty { + /** + * The index of the camera referenced by this node + */ + camera?: number; + /** + * The indices of this node's children + */ + children?: number[]; + /** + * The index of the skin referenced by this node + */ + skin?: number; + /** + * A floating-point 4x4 transformation matrix stored in column-major order + */ + matrix?: number[]; + /** + * The index of the mesh in this node + */ + mesh?: number; + /** + * The node's unit quaternion rotation in the order (x, y, z, w), where w is the scalar + */ + rotation?: number[]; + /** + * The node's non-uniform scale, given as the scaling factors along the x, y, and z axes + */ + scale?: number[]; + /** + * The node's translation along the x, y, and z axes + */ + translation?: number[]; + /** + * The weights of the instantiated Morph Target. Number of elements must match number of Morph Targets of used mesh + */ + weights?: number[]; +} + +/** + * Texture sampler properties for filtering and wrapping modes + */ +export interface ISampler extends IChildRootProperty { + /** + * Magnification filter. Valid values correspond to WebGL enums: 9728 (NEAREST) and 9729 (LINEAR) + */ + magFilter?: TextureMagFilter; + /** + * Minification filter. All valid values correspond to WebGL enums + */ + minFilter?: TextureMinFilter; + /** + * S (U) wrapping mode. All valid values correspond to WebGL enums + */ + wrapS?: TextureWrapMode; + /** + * T (V) wrapping mode. All valid values correspond to WebGL enums + */ + wrapT?: TextureWrapMode; +} + +/** + * The root nodes of a scene + */ +export interface IScene extends IChildRootProperty { + /** + * The indices of each root node + */ + nodes: number[]; +} + +/** + * Joints and matrices defining a skin + */ +export interface ISkin extends IChildRootProperty { + /** + * The index of the accessor containing the floating-point 4x4 inverse-bind matrices. The default is that each matrix is a 4x4 identity matrix, which implies that inverse-bind matrices were pre-applied + */ + inverseBindMatrices?: number; + /** + * The index of the node used as a skeleton root. When undefined, joints transforms resolve to scene root + */ + skeleton?: number; + /** + * Indices of skeleton nodes, used as joints in this skin. The array length must be the same as the count property of the inverseBindMatrices accessor (when defined) + */ + joints: number[]; +} + +/** + * A texture and its sampler + */ +export interface ITexture extends IChildRootProperty { + /** + * The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering should be used + */ + sampler?: number; + /** + * The index of the image used by this texture + */ + source: number; +} + +/** + * Reference to a texture + */ +export interface ITextureInfo extends IProperty { + /** + * The index of the texture + */ + index: number; + /** + * The set index of texture's TEXCOORD attribute used for texture coordinate mapping + */ + texCoord?: number; +} + +/** + * The root object for a glTF asset + */ +export interface IGLTF extends IProperty { + /** + * An array of accessors. An accessor is a typed view into a bufferView + */ + accessors?: IAccessor[]; + /** + * An array of keyframe animations + */ + animations?: IAnimation[]; + /** + * Metadata about the glTF asset + */ + asset: IAsset; + /** + * An array of buffers. A buffer points to binary geometry, animation, or skins + */ + buffers?: IBuffer[]; + /** + * An array of bufferViews. A bufferView is a view into a buffer generally representing a subset of the buffer + */ + bufferViews?: IBufferView[]; + /** + * An array of cameras + */ + cameras?: ICamera[]; + /** + * Names of glTF extensions used somewhere in this asset + */ + extensionsUsed?: string[]; + /** + * Names of glTF extensions required to properly load this asset + */ + extensionsRequired?: string[]; + /** + * An array of images. An image defines data used to create a texture + */ + images?: IImage[]; + /** + * An array of materials. A material defines the appearance of a primitive + */ + materials?: IMaterial[]; + /** + * An array of meshes. A mesh is a set of primitives to be rendered + */ + meshes?: IMesh[]; + /** + * An array of nodes + */ + nodes?: INode[]; + /** + * An array of samplers. A sampler contains properties for texture filtering and wrapping modes + */ + samplers?: ISampler[]; + /** + * The index of the default scene + */ + scene?: number; + /** + * An array of scenes + */ + scenes?: IScene[]; + /** + * An array of skins. A skin is defined by joints and matrices + */ + skins?: ISkin[]; + /** + * An array of textures + */ + textures?: ITexture[]; +} diff --git a/packages/loader/src/gltf/Util.ts b/packages/loader/src/gltf/Util.ts deleted file mode 100644 index cebb9bcbf1..0000000000 --- a/packages/loader/src/gltf/Util.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { DataType, IndexFormat, VertexElement, VertexElementFormat } from "@oasis-engine/core"; - -const WEBGL_COMPONENT_TYPES = { - 5120: Int8Array, - 5121: Uint8Array, - 5122: Int16Array, - 5123: Uint16Array, - 5125: Uint32Array, - 5126: Float32Array -}; - -/** - * Parse binary text for glb loader. - * @param array - * @returns String - * @private - */ -export function decodeText(array) { - if (typeof TextDecoder !== "undefined") { - return new TextDecoder().decode(array); - } - - // TextDecoder polyfill - let s = ""; - - for (let i = 0, il = array.length; i < il; i++) { - s += String.fromCharCode(array[i]); - } - - return decodeURIComponent(encodeURIComponent(s)); -} - -/** - * Find uniform object according to paramters[name] in glTF. - * @param obj - * @param key - * @param value - * @returns {object} - * @private - */ -export function findByKeyValue(obj, key, value) { - for (const name in obj) { - if (obj[name][key] === value) { - return obj[name]; - } - } - return null; -} - -/** Get the number of bytes occupied by accessor type. - * @return {number} - * @param {string} accessorType - * @private - */ -export function getAccessorTypeSize(accessorType) { - const ACCESSOR_TYPE_SIZE = { - SCALAR: 1, - VEC2: 2, - VEC3: 3, - VEC4: 4, - MAT2: 4, - MAT3: 9, - MAT4: 16 - }; - return ACCESSOR_TYPE_SIZE[accessorType]; -} - -/** Get the TypedArray corresponding to the component type. - * @return {function} - * @param {string} componentType - */ -export function getComponentType(componentType) { - return WEBGL_COMPONENT_TYPES[componentType]; -} - -/** - * Get accessor data. - * @param gltf - * @param accessor - * @param buffers - * @private - */ -export function getAccessorData(gltf, accessor, buffers) { - const bufferView = gltf.bufferViews[accessor.bufferView]; - const arrayBuffer = buffers[bufferView.buffer]; - const accessorByteOffset = accessor.hasOwnProperty("byteOffset") ? accessor.byteOffset : 0; - const bufferViewByteOffset = bufferView.hasOwnProperty("byteOffset") ? bufferView.byteOffset : 0; - const byteOffset = accessorByteOffset + bufferViewByteOffset; - const accessorTypeSize = getAccessorTypeSize(accessor.type); - const length = accessorTypeSize * accessor.count; - const byteStride = bufferView.byteStride ?? 0; - - const arrayType = getComponentType(accessor.componentType); - let uint8Array; - if (byteStride) { - uint8Array = new Uint8Array(length * arrayType.BYTES_PER_ELEMENT); - const originalBufferView = new Uint8Array(arrayBuffer, bufferViewByteOffset, bufferView.byteLength); - let viewAccessor = 0; - const accessorByteSize = accessorTypeSize * arrayType.BYTES_PER_ELEMENT; - for (let i = 0; i < accessor.count; i++) { - viewAccessor = i * byteStride + accessorByteOffset; - for (let j = 0; j < accessorByteSize; j++) { - uint8Array[i * accessorByteSize + j] = originalBufferView[viewAccessor + j]; - } - } - } else { - uint8Array = new Uint8Array(arrayBuffer, byteOffset, length * arrayType.BYTES_PER_ELEMENT); - uint8Array = new Uint8Array(uint8Array); - } - - return new arrayType(uint8Array.buffer); -} - -/** - * Get buffer data - * @param bufferView - * @param buffers - * @returns {Blob|ArrayBuffer|Array.|string} - * @private - */ -export function getBufferData(bufferView, buffers) { - // get bufferView - const arrayBuffer = buffers[bufferView.buffer]; - const byteOffset = bufferView.byteOffset || 0; - return arrayBuffer.slice(byteOffset, byteOffset + bufferView.byteLength); -} - -export function getVertexStride(accessor): number { - const size = getAccessorTypeSize(accessor.type); - const componentType = getComponentType(accessor.componentType); - return size * componentType.BYTES_PER_ELEMENT; -} - -export function createVertexElement(gltf, semantic, accessor, index: number): VertexElement { - const size = getAccessorTypeSize(accessor.type); - return new VertexElement(semantic, 0, getElementFormat(accessor.componentType, size), index); -} - -export function getIndexFormat(type: number): IndexFormat { - switch (type) { - case DataType.UNSIGNED_BYTE: - return IndexFormat.UInt8; - case DataType.UNSIGNED_SHORT: - return IndexFormat.UInt16; - case DataType.UNSIGNED_INT: - return IndexFormat.UInt32; - } -} - -export function getElementFormat(type: number, size: number): VertexElementFormat { - if (type == DataType.FLOAT) { - switch (size) { - case 1: - return VertexElementFormat.Float; - case 2: - return VertexElementFormat.Vector2; - case 3: - return VertexElementFormat.Vector3; - case 4: - return VertexElementFormat.Vector4; - } - } - if (type == DataType.UNSIGNED_SHORT) { - switch (size) { - case 2: - return VertexElementFormat.UShort2; - case 4: - return VertexElementFormat.UShort4; - } - } -} - -/** - * Load image buffer - * @param imageBuffer - * @param type - * @param callback - */ -export function loadImageBuffer(imageBuffer: ArrayBuffer, type: string): Promise { - return new Promise((resolve, reject) => { - const blob = new window.Blob([imageBuffer], { type }); - const img = new Image(); - img.src = URL.createObjectURL(blob); - - img.crossOrigin = "anonymous"; - img.onerror = function () { - reject(new Error("Failed to load image buffer")); - }; - img.onload = function () { - // Call requestAnimationFrame to avoid iOS's bug. - requestAnimationFrame(() => { - resolve(img); - }); - }; - }); -} - -function isRelativeUrl(url: string): boolean { - // 47 is / - return url.charCodeAt(0) !== 47 && url.match(/:\/\//) === null; -} - -function isAbsoluteUrl(url: string): boolean { - return /^(?:http|blob|data:|\/)/.test(url); -} - -export function parseRelativeUrl(baseUrl: string, relativeUrl: string): string { - if (isAbsoluteUrl(relativeUrl)) { - return relativeUrl; - } - // TODO: implement ../path - return baseUrl.substring(0, baseUrl.lastIndexOf("/") + 1) + relativeUrl; -} diff --git a/packages/loader/src/gltf/extensions/ExtensionParser.ts b/packages/loader/src/gltf/extensions/ExtensionParser.ts new file mode 100644 index 0000000000..3dab2fa94c --- /dev/null +++ b/packages/loader/src/gltf/extensions/ExtensionParser.ts @@ -0,0 +1,18 @@ +import { EngineObject } from "@oasis-engine/core"; +import { GLTFResource } from "../GLTFResource"; +import { ExtensionSchema } from "./Schema"; + +export abstract class ExtensionParser { + initialize(): void {} + + parseEngineResource( + schema: ExtensionSchema, + parseResource: EngineObject, + context: GLTFResource, + ...extra + ): void | Promise {} + + createEngineResource(schema: ExtensionSchema, context: GLTFResource, ...extra): EngineObject | Promise { + return null; + } +} diff --git a/packages/loader/src/gltf/extensions/KHR_draco_mesh_compression.ts b/packages/loader/src/gltf/extensions/KHR_draco_mesh_compression.ts new file mode 100644 index 0000000000..7af7fd96e4 --- /dev/null +++ b/packages/loader/src/gltf/extensions/KHR_draco_mesh_compression.ts @@ -0,0 +1,46 @@ +import { DRACODecoder } from "@oasis-engine/draco"; +import { GLTFResource } from "../GLTFResource"; +import { GLTFUtil } from "../GLTFUtil"; +import { registerExtension } from "../parser/Parser"; +import { IMeshPrimitive } from "../Schema"; +import { ExtensionParser } from "./ExtensionParser"; +import { IKHRDracoMeshCompression } from "./Schema"; + +@registerExtension("KHR_draco_mesh_compression") +class KHR_draco_mesh_compression extends ExtensionParser { + private static _decoder: DRACODecoder; + + initialize(): void { + if (!KHR_draco_mesh_compression._decoder) { + KHR_draco_mesh_compression._decoder = new DRACODecoder(); + } + } + + createEngineResource(schema: IKHRDracoMeshCompression, context: GLTFResource, gltfPrimitive: IMeshPrimitive) { + const { gltf, buffers } = context; + const { bufferViews, accessors } = gltf; + const { bufferView: bufferViewIndex, attributes: gltfAttributeMap } = schema; + + const attributeMap = {}; + const attributeTypeMap = {}; + for (let attributeName in gltfAttributeMap) { + attributeMap[attributeName] = gltfAttributeMap[attributeName]; + } + for (let attributeName in gltfPrimitive.attributes) { + if (gltfAttributeMap[attributeName] !== undefined) { + const accessorDef = accessors[gltfPrimitive.attributes[attributeName]]; + attributeTypeMap[attributeName] = GLTFUtil.getComponentType(accessorDef.componentType).name; + } + } + const indexAccessor = accessors[gltfPrimitive.indices]; + const indexType = GLTFUtil.getComponentType(indexAccessor.componentType).name; + const taskConfig = { + attributeIDs: attributeMap, + attributeTypes: attributeTypeMap, + useUniqueIDs: true, + indexType + }; + const buffer = GLTFUtil.getBufferViewData(bufferViews[bufferViewIndex], buffers); + return KHR_draco_mesh_compression._decoder.decode(buffer, taskConfig).then((parsedGeometry) => parsedGeometry); + } +} diff --git a/packages/loader/src/gltf/extensions/KHR_lights_punctual.ts b/packages/loader/src/gltf/extensions/KHR_lights_punctual.ts new file mode 100644 index 0000000000..9c35e21442 --- /dev/null +++ b/packages/loader/src/gltf/extensions/KHR_lights_punctual.ts @@ -0,0 +1,41 @@ +import { DirectLight, Entity, PointLight, SpotLight } from "@oasis-engine/core"; +import { GLTFResource } from "../GLTFResource"; +import { registerExtension } from "../parser/Parser"; +import { ExtensionParser } from "./ExtensionParser"; +import { IKHRLightsPunctual_Light } from "./Schema"; + +@registerExtension("KHR_lights_punctual") +class KHR_lights_punctual extends ExtensionParser { + parseEngineResource(schema: IKHRLightsPunctual_Light, entity: Entity, context: GLTFResource): void { + const { color, intensity = 1, type, range, spot } = schema; + let light: DirectLight | PointLight | SpotLight; + + if (type === "directional") { + light = entity.addComponent(DirectLight); + } else if (type === "point") { + light = entity.addComponent(PointLight); + } else if (type === "spot") { + light = entity.addComponent(SpotLight); + } + + if (color) { + light.color.setValue(color[0], color[1], color[2], 1); + } + + light.intensity = intensity; + + if (range && !(light instanceof DirectLight)) { + light.distance = range; + } + + if (spot && light instanceof SpotLight) { + const { innerConeAngle = 0, outerConeAngle = Math.PI / 4 } = spot; + + light.angle = innerConeAngle; + light.penumbra = outerConeAngle - innerConeAngle; + } + + if (!context.lights) context.lights = []; + context.lights.push(light); + } +} diff --git a/packages/loader/src/gltf/extensions/KHR_materials_clearcoat.ts b/packages/loader/src/gltf/extensions/KHR_materials_clearcoat.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/loader/src/gltf/extensions/KHR_materials_ior.ts b/packages/loader/src/gltf/extensions/KHR_materials_ior.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/loader/src/gltf/extensions/KHR_materials_pbrSpecularGlossiness.ts b/packages/loader/src/gltf/extensions/KHR_materials_pbrSpecularGlossiness.ts new file mode 100644 index 0000000000..7b3eb7b32d --- /dev/null +++ b/packages/loader/src/gltf/extensions/KHR_materials_pbrSpecularGlossiness.ts @@ -0,0 +1,40 @@ +import { PBRSpecularMaterial } from "@oasis-engine/core"; +import { Color } from "@oasis-engine/math"; +import { GLTFResource } from "../GLTFResource"; +import { MaterialParser } from "../parser/MaterialParser"; +import { registerExtension } from "../parser/Parser"; +import { ExtensionParser } from "./ExtensionParser"; +import { IKHRMaterialsPbrSpecularGlossiness } from "./Schema"; + +@registerExtension("KHR_materials_pbrSpecularGlossiness") +class KHR_materials_pbrSpecularGlossiness extends ExtensionParser { + createEngineResource(schema: IKHRMaterialsPbrSpecularGlossiness, context: GLTFResource): PBRSpecularMaterial { + const { engine, textures } = context; + const material = new PBRSpecularMaterial(engine); + const { diffuseFactor, diffuseTexture, specularFactor, glossinessFactor, specularGlossinessTexture } = schema; + + if (diffuseFactor) { + material.baseColor = new Color(...diffuseFactor); + } + + if (diffuseTexture) { + material.baseTexture = textures[diffuseTexture.index]; + MaterialParser._parseTextureTransform(material, diffuseTexture.extensions, context); + } + + if (specularFactor) { + material.specularColor = new Color(...specularFactor); + } + + if (glossinessFactor !== undefined) { + material.glossinessFactor = glossinessFactor; + } + + if (specularGlossinessTexture) { + material.specularGlossinessTexture = textures[specularGlossinessTexture.index]; + MaterialParser._parseTextureTransform(material, specularGlossinessTexture.extensions, context); + } + + return material; + } +} diff --git a/packages/loader/src/gltf/extensions/KHR_materials_sheen.ts b/packages/loader/src/gltf/extensions/KHR_materials_sheen.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/loader/src/gltf/extensions/KHR_materials_transmission.ts b/packages/loader/src/gltf/extensions/KHR_materials_transmission.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/loader/src/gltf/extensions/KHR_materials_unlit.ts b/packages/loader/src/gltf/extensions/KHR_materials_unlit.ts new file mode 100644 index 0000000000..54bae1f20f --- /dev/null +++ b/packages/loader/src/gltf/extensions/KHR_materials_unlit.ts @@ -0,0 +1,15 @@ +import { UnlitMaterial } from "@oasis-engine/core"; +import { GLTFResource } from "../GLTFResource"; +import { registerExtension } from "../parser/Parser"; +import { ExtensionParser } from "./ExtensionParser"; +import { IKHRMaterialsUnlit } from "./Schema"; + +@registerExtension("KHR_materials_unlit") +class KHR_materials_unlit extends ExtensionParser { + createEngineResource(schema: IKHRMaterialsUnlit, context: GLTFResource): UnlitMaterial { + const { engine } = context; + const material = new UnlitMaterial(engine); + + return material; + } +} diff --git a/packages/loader/src/gltf/extensions/KHR_materials_variants.ts b/packages/loader/src/gltf/extensions/KHR_materials_variants.ts new file mode 100644 index 0000000000..fe730e44e3 --- /dev/null +++ b/packages/loader/src/gltf/extensions/KHR_materials_variants.ts @@ -0,0 +1,30 @@ +import { Renderer } from "@oasis-engine/core"; +import { GLTFResource } from "../GLTFResource"; +import { registerExtension } from "../parser/Parser"; +import { ExtensionParser } from "./ExtensionParser"; +import { IKHRMaterialVariants_Mapping } from "./Schema"; + +@registerExtension("KHR_materials_variants") +class KHR_materials_variants extends ExtensionParser { + parseEngineResource(schema: IKHRMaterialVariants_Mapping, renderer: Renderer, context: GLTFResource): void { + const { + gltf: { + extensions: { + KHR_materials_variants: { variants: variantNames } + } + }, + materials + } = context; + const { mappings } = schema; + + for (let i = 0; i < mappings.length; i++) { + const { material, variants } = mappings[i]; + if (!context.variants) context.variants = []; + context.variants.push({ + renderer, + material: materials[material], + variants: variants.map((index) => variantNames[index].name) + }); + } + } +} diff --git a/packages/loader/src/gltf/extensions/KHR_materials_volume.ts b/packages/loader/src/gltf/extensions/KHR_materials_volume.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/loader/src/gltf/extensions/KHR_mesh_quantization.ts b/packages/loader/src/gltf/extensions/KHR_mesh_quantization.ts new file mode 100644 index 0000000000..bf5bcbab2f --- /dev/null +++ b/packages/loader/src/gltf/extensions/KHR_mesh_quantization.ts @@ -0,0 +1,5 @@ +import { registerExtension } from "../parser/Parser"; +import { ExtensionParser } from "./ExtensionParser"; + +@registerExtension("KHR_mesh_quantization") +class KHR_mesh_quantization extends ExtensionParser {} diff --git a/packages/loader/src/gltf/extensions/KHR_texture_basisu.ts b/packages/loader/src/gltf/extensions/KHR_texture_basisu.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/loader/src/gltf/extensions/KHR_texture_transform.ts b/packages/loader/src/gltf/extensions/KHR_texture_transform.ts new file mode 100644 index 0000000000..70249270c0 --- /dev/null +++ b/packages/loader/src/gltf/extensions/KHR_texture_transform.ts @@ -0,0 +1,34 @@ +import { Logger, PBRBaseMaterial, UnlitMaterial } from "@oasis-engine/core"; +import { GLTFResource } from "../GLTFResource"; +import { registerExtension } from "../parser/Parser"; +import { ExtensionParser } from "./ExtensionParser"; +import { IKHRTextureTransform } from "./Schema"; + +@registerExtension("KHR_texture_transform") +class KHR_texture_transform extends ExtensionParser { + parseEngineResource( + schema: IKHRTextureTransform, + material: PBRBaseMaterial | UnlitMaterial, + context: GLTFResource + ): void { + const { offset, rotation, scale, texCoord } = schema; + + if (offset) { + material.tilingOffset.z = offset[0]; + material.tilingOffset.w = offset[1]; + } + + if (scale) { + material.tilingOffset.x = scale[0]; + material.tilingOffset.y = scale[1]; + } + + if (rotation) { + Logger.warn("rotation in KHR_texture_transform is not supported now"); + } + + if (texCoord) { + Logger.warn("texCoord in KHR_texture_transform is not supported now"); + } + } +} diff --git a/packages/loader/src/gltf/extensions/Schema.ts b/packages/loader/src/gltf/extensions/Schema.ts new file mode 100644 index 0000000000..59d67fd649 --- /dev/null +++ b/packages/loader/src/gltf/extensions/Schema.ts @@ -0,0 +1,172 @@ +import { IMaterialNormalTextureInfo, ITextureInfo } from "../Schema"; + +/** + * Interfaces from the KHR_lights_punctual extension + */ +export interface IKHRLightsPunctual_LightNode { + light: number; +} + +export interface IKHRLightsPunctual_Light { + type: "directional" | "point" | "spot"; + color?: number[]; + intensity?: number; + range?: number; + spot?: { + innerConeAngle?: number; + outerConeAngle?: number; + }; +} + +export interface IKHRLightsPunctual { + lights: IKHRLightsPunctual_Light[]; +} + +/** + * Interfaces from the KHR_draco_mesh_compression extension + */ +export interface IKHRDracoMeshCompression { + bufferView: number; + attributes: { + [name: string]: number; + }; +} + +/** + * Interfaces from the KHR_materials_clearcoat extension + */ +export interface IKHRMaterialsClearcoat { + clearcoatFactor: number; + clearcoatTexture: ITextureInfo; + clearcoatRoughnessFactor: number; + clearcoatRoughnessTexture: ITextureInfo; + clearcoatNormalTexture: IMaterialNormalTextureInfo; +} + +/** + * Interfaces from the KHR_materials_ior extension + */ +export interface IKHRMaterialsIor { + ior: number; +} + +/** + * Interfaces from the KHR_materials_unlit extension + */ +export interface IKHRMaterialsUnlit {} + +/** + * Interfaces from the KHR_materials_pbrSpecularGlossiness extension + */ +export interface IKHRMaterialsPbrSpecularGlossiness { + diffuseFactor: number[]; + diffuseTexture: ITextureInfo; + specularFactor: number[]; + glossinessFactor: number; + specularGlossinessTexture: ITextureInfo; +} + +/** + * Interfaces from the KHR_materials_sheen extension + */ +export interface IKHRMaterialsSheen { + sheenColorFactor?: number[]; + sheenColorTexture?: ITextureInfo; + sheenRoughnessFactor?: number; + sheenRoughnessTexture?: ITextureInfo; +} + +/** + * Interfaces from the KHR_materials_specular extension + */ +export interface IKHRMaterialsSpecular { + specularFactor: number; + specularColorFactor: number[]; + specularTexture: ITextureInfo; +} + +/** + * Interfaces from the KHR_materials_transmission extension + */ +export interface IKHRMaterialsTransmission { + transmissionFactor?: number; + transmissionTexture?: ITextureInfo; +} + +/** + * Interfaces from the KHR_materials_translucency extension + */ +export interface IKHRMaterialsTranslucency { + translucencyFactor?: number; + translucencyTexture?: ITextureInfo; +} + +/** + * Interfaces from the KHR_materials_variants extension + */ +export interface IKHRMaterialVariants_Mapping { + mappings: Array<{ + variants: number[]; + material: number; + }>; + extensions?: any; + extras?: any; +} + +export interface IKHRMaterialVariants_Variant { + name: string; + extensions?: any; + extras?: any; +} + +export interface IKHRMaterialVariants_Variants { + variants: Array; +} + +/** + * Interfaces from the KHR_texture_basisu extension + */ +export interface IKHRTextureBasisU { + source: number; +} + +/** + * Interfaces from the KHR_texture_transform extension + */ +export interface IKHRTextureTransform { + offset?: number[]; + rotation?: number; + scale?: number[]; + texCoord?: number; +} + +/** + * Interfaces from the KHR_xmp extension + */ +export interface IKHRXmp { + packets: Array<{ + [key: string]: unknown; + }>; +} + +export interface IKHRXmp_Node { + packet: number; +} + +export type ExtensionSchema = + | IKHRLightsPunctual_Light + | IKHRDracoMeshCompression + | IKHRMaterialsClearcoat + | IKHRMaterialsIor + | IKHRMaterialsUnlit + | IKHRMaterialsPbrSpecularGlossiness + | IKHRMaterialsSheen + | IKHRMaterialsSpecular + | IKHRMaterialsTransmission + | IKHRMaterialsTranslucency + | IKHRMaterialVariants_Mapping + | IKHRMaterialVariants_Variants + | IKHRTextureBasisU + | IKHRTextureTransform + | IKHRXmp + | IKHRXmp_Node; diff --git a/packages/loader/src/gltf/extensions/index.ts b/packages/loader/src/gltf/extensions/index.ts new file mode 100644 index 0000000000..7ef0b8892b --- /dev/null +++ b/packages/loader/src/gltf/extensions/index.ts @@ -0,0 +1,13 @@ +import "./KHR_draco_mesh_compression"; +import "./KHR_lights_punctual"; +import "./KHR_materials_clearcoat"; +import "./KHR_materials_ior"; +import "./KHR_materials_pbrSpecularGlossiness"; +import "./KHR_materials_sheen"; +import "./KHR_materials_transmission"; +import "./KHR_materials_unlit"; +import "./KHR_materials_variants"; +import "./KHR_materials_volume"; +import "./KHR_mesh_quantization"; +import "./KHR_texture_basisu"; +import "./KHR_texture_transform"; diff --git a/packages/loader/src/gltf/glTF.ts b/packages/loader/src/gltf/glTF.ts deleted file mode 100644 index 3219f4a4be..0000000000 --- a/packages/loader/src/gltf/glTF.ts +++ /dev/null @@ -1,867 +0,0 @@ -import { - Animation, - AnimationClip, - BlinnPhongMaterial, - Buffer, - BufferBindFlag, - BufferMesh, - BufferUsage, - Camera, - Engine, - EngineObject, - Entity, - IndexBufferBinding, - IndexFormat, - InterpolationType, - Logger, - Material, - MeshRenderer, - MeshTopology, - ModelMesh, - PBRMaterial, - PBRSpecularMaterial, - RenderFace, - Scene, - Skin, - SkinnedMeshRenderer, - SubMesh, - Texture2D, - TypedArray, - UnlitMaterial, - VertexElement -} from "@oasis-engine/core"; -import { Color, Matrix, Quaternion, Vector3 } from "@oasis-engine/math"; -import { LoadedGLTFResource } from "../GLTF"; -import { glTFDracoMeshCompression } from "./glTFDracoMeshCompression"; -import { createVertexElement, getAccessorData, getAccessorTypeSize, getIndexFormat, getVertexStride } from "./Util"; - -// KHR_lights: https://github.com/MiiBond/glTF/tree/khr_lights_v1/extensions/2.0/Khronos/KHR_lights -// https://github.com/KhronosGroup/glTF/pull/1223 -// https://github.com/KhronosGroup/glTF/issues/945 -// KHR_materials_common: https://github.com/donmccurdy/glTF/tree/donmccurdy-KHR_materials_common/extensions/Khronos/KHR_materials_common_v2 -// https://github.com/KhronosGroup/glTF/pull/1150 -// https://github.com/KhronosGroup/glTF/issues/947 - -const TARGET_PATH_MAP = { - translation: "position", - rotation: "rotation", - scale: "scale", - weights: "weights" -}; - -let nodeCount = 0; - -const RegistedObjs = {}; -const RegistedCustomMaterials = {}; - -const getDefaultMaterial = (function () { - // let defaultMateril: BlinnPhongMaterial; - return (engine: Engine) => { - // if (!defaultMateril) { - let defaultMateril: BlinnPhongMaterial = new BlinnPhongMaterial(engine); - defaultMateril.emissiveColor = new Color(0.749, 0.749, 0.749, 1); - // } - return defaultMateril; - }; -})(); - -/** - * Extension dedicated registration key. - */ -export const HandledExtensions = { - PBRMaterial: "PBRMaterial", - KHR_lights: "KHR_lights", - KHR_materials_unlit: "KHR_materials_unlit", - KHR_materials_pbrSpecularGlossiness: "KHR_materials_pbrSpecularGlossiness", - KHR_techniques_webgl: "KHR_techniques_webgl", - KHR_draco_mesh_compression: "KHR_draco_mesh_compression" -}; - -let KHR_lights = null; - -const extensionParsers = { - KHR_lights: KHR_lights, - KHR_materials_unlit: UnlitMaterial, - KHR_materials_pbrSpecularGlossiness: PBRSpecularMaterial, - KHR_techniques_webgl: Material, - KHR_draco_mesh_compression: glTFDracoMeshCompression -}; - -/** - * Register extension components to glTF loader. - * @param extobj - Need to add extensions - */ -export function RegistExtension(extobj) { - Object.keys(extobj).forEach((name) => { - if (RegistedObjs[name] === undefined) { - RegistedObjs[name] = extobj[name]; - - switch (name) { - case HandledExtensions.KHR_lights: - KHR_lights = extobj[name]; - extensionParsers.KHR_lights = KHR_lights; - break; - default: - if (Material.isPrototypeOf(extobj[name]) && extobj[name].TECH_NAME) - RegistedCustomMaterials[extobj[name].TECH_NAME] = extobj[name]; - break; - } - } - }); -} - -export interface GLTFParsed extends LoadedGLTFResource { - asset: Partial; - engine?: Engine; -} - -export class GLTFResource extends EngineObject { - defaultSceneRoot: Entity; - defaultScene: Scene; - scenes: Scene[]; - textures?: Texture2D[]; - animations?: AnimationClip[]; - materials?: Material[]; - meshes?: BufferMesh[]; - skins?: Skin[]; - cameras?: Camera[]; - meta: any; -} - -/** - * Parse the glTF structure. - * @param resource - * @returns {*} - * @private - */ -export function parseGLTF(data: LoadedGLTFResource, engine: Engine): Promise { - // Start processing glTF data. - const resources: GLTFParsed = { - engine, - gltf: data.gltf, - buffers: data.buffers, - asset: new GLTFResource(engine) - }; - resources.asset.textures = data.textures; - resources.asset.meta = data.gltf; - - if (resources.gltf.asset && resources.gltf.asset.version) { - resources.gltf.version = Number(resources.gltf.asset.version); - resources.gltf.isGltf2 = resources.gltf.version >= 2 && resources.gltf.version <= 3; - } - - parseExtensions(resources); - // parse all related resources - return ( - parseResources(resources, "materials", parseMaterial) - .then(() => parseResources(resources, "meshes", parseMesh)) - // .then(() => parseResources(resources, "cameras", parseCamera)) - .then(() => parseResources(resources, "nodes", parseNode)) - .then(() => parseResources(resources, "scenes", parseScene)) - .then(() => parseResources(resources, "skins", parseSkin)) - .then(() => parseResources(resources, "animations", parseAnimation)) - .then(() => buildSceneGraph(resources)) - ); -} - -function parseExtensions(resources) { - const { gltf, asset } = resources; - const { extensions, extensionsUsed, extensionsRequired } = gltf; - if (extensionsUsed) { - Logger.info("extensionsUsed: ", extensionsUsed); - for (let i = 0; i < extensionsUsed.length; i++) { - if (Object.keys(extensionParsers).indexOf(extensionsUsed[i]) > -1) { - if (!extensionParsers[extensionsUsed[i]]) { - Logger.warn("extension " + extensionsUsed[i] + " is used, you can add this extension into gltf"); - } - } else { - Logger.warn("extensionsUsed has unsupported extension " + extensionsUsed[i]); - } - } - } - - if (extensionsRequired) { - Logger.info(`extensionsRequired: ${extensionsRequired}`); - for (let i = 0; i < extensionsRequired.length; i++) { - if ( - Object.keys(extensionParsers).indexOf(extensionsRequired[i]) < 0 || - !extensionParsers[extensionsRequired[i]] - ) { - Logger.error(`model has not supported required extension ${extensionsRequired[i]}`); - } - if (extensionsRequired[i] === HandledExtensions.KHR_draco_mesh_compression) { - extensionParsers.KHR_draco_mesh_compression.init(); - } - } - } - - if (extensions) { - if (KHR_lights && extensions.KHR_lights) { - asset.lights = KHR_lights.parseLights(extensions.KHR_lights.lights); - } - } -} - -/** - * General resource analysis method. - * @param resources - Existing resources - * @param name - Name - * @param handler - Resource resolver - * @private - */ -function parseResources(resources: GLTFParsed, name: string, handler) { - const { gltf, asset } = resources; - if (!asset[name]) { - asset[name] = []; - } - if (gltf.hasOwnProperty(name)) { - const entities = gltf[name] || []; - Logger.debug(name + ":", entities); - const promises = []; - for (let i = entities.length - 1; i >= 0; i--) { - promises.push(handler(entities[i], resources)); - } - return Promise.all(promises).then((results) => { - for (let i = 0; i < results.length; i++) { - asset[name].push(results[i]); - } - }); - } - return Promise.resolve(); -} - -/** - * Parse material. - * @param gltfMaterial - * @param resources - * @private - */ -export function parseMaterial(gltfMaterial, resources) { - const { gltf, engine } = resources; - if (gltf.isGltf2 && typeof gltfMaterial.technique === "undefined") { - const { - extensions = {}, - pbrMetallicRoughness, - normalTexture, - emissiveTexture, - emissiveFactor, - occlusionTexture, - alphaMode, - alphaCutoff, - doubleSided - } = gltfMaterial; - - const isUnlit = extensions.KHR_materials_unlit; - const isSpecular = extensions.KHR_materials_pbrSpecularGlossiness; - - let material: UnlitMaterial | PBRMaterial | PBRSpecularMaterial = null; - if (isUnlit) { - material = new UnlitMaterial(engine); - } else if (isSpecular) { - material = new PBRSpecularMaterial(engine); - } else { - material = new PBRMaterial(engine); - } - - // render states - if (doubleSided) { - material.renderFace = RenderFace.Double; - } else { - material.renderFace = RenderFace.Front; - } - - switch (alphaMode) { - case "OPAQUE": - material.isTransparent = false; - break; - case "BLEND": - material.isTransparent = true; - break; - case "MASK": - (material as PBRMaterial | PBRSpecularMaterial).alphaCutoff = alphaCutoff === undefined ? 0.5 : alphaCutoff; - break; - } - - // may be applied to unlit too. - if (pbrMetallicRoughness) { - const { - baseColorFactor, - baseColorTexture, - metallicFactor, - roughnessFactor, - metallicRoughnessTexture - } = pbrMetallicRoughness; - if (baseColorTexture) { - material.baseTexture = getItemByIdx("textures", baseColorTexture.index || 0, resources, false); - } - if (baseColorFactor) { - material.baseColor = new Color(...baseColorFactor); - } - if (!isUnlit) { - material = material as PBRMaterial; - material.metallicFactor = metallicFactor !== undefined ? metallicFactor : 1; - material.roughnessFactor = roughnessFactor !== undefined ? roughnessFactor : 1; - if (metallicRoughnessTexture) { - material.metallicRoughnessTexture = getItemByIdx( - "textures", - metallicRoughnessTexture.index || 0, - resources, - false - ); - } - } - } - - // break unlit at here, unlit don't need to process the next code - if (isUnlit) { - return Promise.resolve(material); - } - material = material as PBRMaterial | PBRSpecularMaterial; - - if (emissiveTexture) { - material.emissiveTexture = getItemByIdx("textures", emissiveTexture.index || 0, resources, false); - } - - if (emissiveFactor) { - material.emissiveColor = new Color(...emissiveFactor); - } - - if (normalTexture) { - const { index, texCoord, scale } = normalTexture; - material = material as PBRMaterial | PBRSpecularMaterial; - material.normalTexture = getItemByIdx("textures", index || 0, resources, false); - - if (scale !== undefined) { - material.normalIntensity = scale; - } - } - - if (occlusionTexture) { - material = material as PBRMaterial | PBRSpecularMaterial; - material.occlusionTexture = getItemByIdx("textures", occlusionTexture.index || 0, resources, false); - - if (occlusionTexture.strength !== undefined) { - material.occlusionStrength = occlusionTexture.strength; - } - } - - if (isSpecular) { - const { - diffuseFactor, - diffuseTexture, - specularFactor, - glossinessFactor, - specularGlossinessTexture - } = extensions.KHR_materials_pbrSpecularGlossiness; - material = material as PBRSpecularMaterial; - if (diffuseFactor) { - material.baseColor = new Color(...diffuseFactor); - } - if (diffuseTexture) { - material.baseTexture = getItemByIdx("textures", diffuseTexture.index || 0, resources, false); - } - if (specularFactor) { - material.specularColor = new Color(...specularFactor); - } - if (glossinessFactor !== undefined) { - material.glossinessFactor = glossinessFactor; - } - if (specularGlossinessTexture) { - material.specularGlossinessTexture = getItemByIdx( - "textures", - specularGlossinessTexture.index || 0, - resources, - false - ); - } - } - return Promise.resolve(material); - } else { - const techniqueName = gltfMaterial.technique; - Logger.warn("Deprecated: Please use a model that meets the glTF 2.0 specification"); - // TODO: support KHR_UNLIT_MATERIAL in the future. - if (techniqueName === "Texture") { - const material = new UnlitMaterial(engine); - const index = gltfMaterial.values._MainTex[0]; - material.baseTexture = getItemByIdx("textures", index || 0, resources, false); - return Promise.resolve(material); - } - } - return Promise.resolve(); -} - -/** - * Parse skin. - * @param gltfSkin - * @param resources - * @private - */ -export function parseSkin(gltfSkin, resources) { - const { gltf, buffers } = resources; - - const jointCount = gltfSkin.joints.length; - - // FIXME: name is null - const skin = new Skin(gltfSkin.name); - // parse IBM - const accessor = gltf.accessors[gltfSkin.inverseBindMatrices]; - const buffer = getAccessorData(gltf, accessor, buffers); - const MAT4_LENGTH = 16; - - for (let i = 0; i < jointCount; i++) { - const startIdx = MAT4_LENGTH * i; - const endIdx = startIdx + MAT4_LENGTH; - skin.inverseBindMatrices[i] = new Matrix(...buffer.subarray(startIdx, endIdx)); - } - - // get joints - for (let i = 0; i < jointCount; i++) { - const node = getItemByIdx("nodes", gltfSkin.joints[i], resources); - skin.joints[i] = node.name; - } - - // get skeleton - const node = getItemByIdx("nodes", gltfSkin.skeleton == null ? gltfSkin.joints[0] : gltfSkin.skeleton, resources); - skin.skeleton = node.name; - - return Promise.resolve(skin); -} - -function parsePrimitiveVertex( - mesh: BufferMesh, - // primitive: Primitive, - primitiveGroup: SubMesh, - gltfPrimitive, - gltf, - getVertexBufferData: (string) => TypedArray, - getIndexBufferData: () => TypedArray, - engine -) { - // load vertices - let i = 0; - const vertexElements: VertexElement[] = []; - let vertexCount: number; - for (const attributeSemantic in gltfPrimitive.attributes) { - const accessorIdx = gltfPrimitive.attributes[attributeSemantic]; - const accessor = gltf.accessors[accessorIdx]; - const stride = getVertexStride(accessor); - const vertexELement = createVertexElement(gltf, attributeSemantic, accessor, i); - - vertexElements.push(vertexELement); - const bufferData = getVertexBufferData(attributeSemantic); - const vertexBuffer = new Buffer(engine, BufferBindFlag.VertexBuffer, bufferData.byteLength, BufferUsage.Static); - vertexBuffer.setData(bufferData); - mesh.setVertexBufferBinding(vertexBuffer, stride, i++); - - // compute bounds - if (vertexELement.semantic == "POSITION") { - const position = new Vector3(); - vertexCount = bufferData.length / 3; - const { min, max } = mesh.bounds; - for (let i = 0; i < vertexCount; i++) { - const offset = i * 3; - position.setValue(bufferData[offset], bufferData[offset + 1], bufferData[offset + 2]); - Vector3.min(min, position, min); - Vector3.max(max, position, max); - } - } - } - mesh.setVertexElements(vertexElements); - - // load indices - const indices = gltfPrimitive.indices; - if (indices !== undefined) { - const indexAccessor = gltf.accessors[gltfPrimitive.indices]; - const indexData = getIndexBufferData(); - - const indexCount = indexAccessor.count; - const indexFormat = getIndexFormat(indexAccessor.componentType); - const indexByteSize = indexFormat == IndexFormat.UInt32 ? 4 : indexFormat == IndexFormat.UInt16 ? 2 : 1; - const indexBuffer = new Buffer(engine, BufferBindFlag.IndexBuffer, indexCount * indexByteSize, BufferUsage.Static); - - indexBuffer.setData(indexData); - mesh.setIndexBufferBinding(new IndexBufferBinding(indexBuffer, indexFormat)); - mesh.addSubMesh(0, indexCount); - } else { - primitiveGroup.start = 0; - primitiveGroup.count = vertexCount; - } - return Promise.resolve(mesh); -} - -function parserPrimitiveTarget(primitive, gltfPrimitive, gltf, buffers) {} - -/** - * Parse Mesh - * @param gltfMesh - * @param resources - * @private - */ -export function parseMesh(gltfMesh, resources) { - const { gltf, buffers, engine } = resources; - // mesh.type = resources.assetType; - // parse all primitives then link to mesh - // TODO: use hash cached primitives - const primitivePromises = []; - const groups = []; - for (let i = 0; i < gltfMesh.primitives.length; i++) { - primitivePromises.push( - new Promise((resolve, reject) => { - const gltfPrimitive = gltfMesh.primitives[i]; - // FIXME: use index as primitive's name - const mesh = new BufferMesh(engine, gltfPrimitive.name || gltfMesh.name || i); - const subMesh = new SubMesh(); - groups.push(subMesh); - // primitive.type = resources.assetType; - subMesh.topology = gltfPrimitive.mode == null ? MeshTopology.Triangles : gltfPrimitive.mode; - if (gltfPrimitive.hasOwnProperty("targets")) { - // primitive.targets = []; - (mesh as any).weights = gltfMesh.weights || new Array(gltfPrimitive.targets.length).fill(0); - } - let vertexPromise; - if (gltfPrimitive.extensions && gltfPrimitive.extensions[HandledExtensions.KHR_draco_mesh_compression]) { - const extensionParser = extensionParsers.KHR_draco_mesh_compression; - const extension = gltfPrimitive.extensions[HandledExtensions.KHR_draco_mesh_compression]; - vertexPromise = extensionParser.parse(extension, gltfPrimitive, gltf, buffers).then((decodedGeometry) => { - return parsePrimitiveVertex( - mesh, - // primitive, - subMesh, - gltfPrimitive, - gltf, - (attributeSemantic) => { - for (let i = 0; i < decodedGeometry.attributes.length; i++) { - if (decodedGeometry.attributes[i].name === attributeSemantic) { - return decodedGeometry.attributes[i].array; - } - } - return null; - }, - () => { - return decodedGeometry.index.array; - }, - resources.engine - ); - }); - } else { - vertexPromise = parsePrimitiveVertex( - mesh, - // primitive, - subMesh, - gltfPrimitive, - gltf, - (attributeSemantic) => { - const accessorIdx = gltfPrimitive.attributes[attributeSemantic]; - const accessor = gltf.accessors[accessorIdx]; - return getAccessorData(gltf, accessor, buffers); - }, - () => { - const indexAccessor = gltf.accessors[gltfPrimitive.indices]; - return getAccessorData(gltf, indexAccessor, buffers); - }, - resources.engine - ); - } - vertexPromise - .then((processedPrimitive) => { - parserPrimitiveTarget(processedPrimitive, gltfPrimitive, gltf, buffers); - resolve(processedPrimitive); - }) - .catch((e) => { - reject(e); - }); - }) - ); - } - - return Promise.all(primitivePromises).then((meshes: ModelMesh[]) => { - return meshes; - }); -} - -/** - * Parse Animation. - * @param gltfAnimation - * @param resources - * @returns {*} - * @private - */ -export function parseAnimation(gltfAnimation, resources) { - const { gltf, buffers } = resources; - const gltfSamplers = gltfAnimation.samplers || []; - const gltfChannels = gltfAnimation.channels || []; - - const animationIdx = gltf.animations.indexOf(gltfAnimation); - const animationClip = new AnimationClip(gltfAnimation.name || `Animation${animationIdx}`); - - let duration = -1; - let durationIndex = -1; - // parse samplers - for (let i = 0; i < gltfSamplers.length; i++) { - const gltfSampler = gltfSamplers[i]; - // input - const inputAccessor = gltf.accessors[gltfSampler.input]; - const outputAccessor = gltf.accessors[gltfSampler.output]; - const input = getAccessorData(gltf, inputAccessor, buffers); - const output = getAccessorData(gltf, outputAccessor, buffers); - let outputAccessorSize = getAccessorTypeSize(outputAccessor.type); - if (outputAccessorSize * input.length !== output.length) outputAccessorSize = output.length / input.length; - - // TODO: support - // LINEAR, STEP, CUBICSPLINE - let samplerInterpolation = InterpolationType.LINEAR; - switch (gltfSampler.interpolation) { - case "CUBICSPLINE": - samplerInterpolation = InterpolationType.CUBICSPLINE; - break; - case "STEP": - samplerInterpolation = InterpolationType.STEP; - break; - } - const maxTime = input[input.length - 1]; - if (maxTime > duration) { - duration = maxTime; - durationIndex = i; - } - animationClip.addSampler(input, output, outputAccessorSize, samplerInterpolation); - } - - animationClip.durationIndex = durationIndex; - animationClip.duration = duration; - - for (let i = 0; i < gltfChannels.length; i++) { - const gltfChannel = gltfChannels[i]; - const target = gltfChannel.target; - const samplerIndex = gltfChannel.sampler; - const targetNode = getItemByIdx("nodes", target.node, resources); - const targetPath = TARGET_PATH_MAP[target.path]; - - animationClip.addChannel(samplerIndex, targetNode.name, targetPath); - } - - return Promise.resolve(animationClip); -} - -/** - * Parse the node of glTF. - * @param gltfNode - * @param resources - * @private - */ -export function parseNode(gltfNode, resources: GLTFParsed) { - // TODO: undefined name? - const entity = new Entity(resources.engine, gltfNode.name || `GLTF_NODE_${nodeCount++}`); - - if (gltfNode.hasOwnProperty("matrix")) { - const m = gltfNode.matrix; - const mat = new Matrix(); - mat.setValueByArray(m); - const pos = new Vector3(); - const scale = new Vector3(1, 1, 1); - const rot = new Quaternion(); - mat.decompose(pos, rot, scale); - - entity.transform.position = pos; - entity.transform.rotationQuaternion = rot; - entity.transform.scale = scale; - } else { - for (const key in TARGET_PATH_MAP) { - if (gltfNode.hasOwnProperty(key)) { - const mapKey = TARGET_PATH_MAP[key]; - if (mapKey === "weights") { - entity[mapKey] = gltfNode[key]; - } else { - const arr = gltfNode[key]; - const len = arr.length; - const obj = entity[mapKey]; - if (len === 2) { - obj.setValue(arr[0], arr[1]); - } else if (len === 3) { - obj.setValue(arr[0], arr[1], arr[2]); - } else if (len === 4) { - obj.setValue(arr[0], arr[1], arr[2], arr[3]); - } - entity[mapKey] = obj; - } - } - } - } - - if (gltfNode.camera !== undefined) { - const cameraOptions = resources.gltf.cameras[gltfNode.camera]; - const camera = entity.addComponent(Camera); - if (cameraOptions.type === "orthographic") { - camera.isOrthographic = true; - let { ymag, xmag, zfar, znear } = cameraOptions.orthographic; - if (znear !== undefined) { - camera.nearClipPlane = znear; - } - if (zfar !== undefined) { - camera.farClipPlane = zfar; - } - if (ymag && xmag) { - camera.orthographicSize = Math.max(ymag, xmag) / 2; - } - if (ymag !== undefined && xmag) { - camera.orthographicSize = xmag / 2; - } - if (xmag !== undefined && ymag) { - camera.orthographicSize = ymag / 2; - } - } else { - const { aspectRatio, yfov, zfar, znear } = cameraOptions.perspective; - if (aspectRatio !== undefined) { - camera.aspectRatio = aspectRatio; - } - if (yfov !== undefined) { - camera.fieldOfView = yfov; - } - if (zfar !== undefined) { - camera.farClipPlane = zfar; - } - if (znear !== undefined) { - camera.nearClipPlane = znear; - } - } - } - - if (gltfNode.extensions) { - if (KHR_lights && gltfNode.extensions.KHR_lights) { - const lightIdx = gltfNode.extensions.KHR_lights.light; - if (lightIdx !== undefined) { - const light = getItemByIdx("lights", lightIdx, resources); - if (light) { - const lightCon = entity.addComponent(light.ability); - Object.assign(lightCon, light.props); - } - } - } - } - - return Promise.resolve(entity); -} - -/** - * parse the scene of glTF. - * @param gltfScene - * @param resources - * @returns {{nodes: Array}} - * @private - */ -export function parseScene(gltfScene, resources) { - const sceneNodes = []; - for (let i = 0; i < gltfScene.nodes.length; i++) { - const node = getItemByIdx("nodes", gltfScene.nodes[i], resources); - sceneNodes.push(node); - } - - if (gltfScene.extensions) { - if (KHR_lights && gltfScene.extensions.KHR_lights) { - const lightIdx = gltfScene.extensions.KHR_lights.light; - if (lightIdx !== undefined) { - const light = getItemByIdx("lights", lightIdx, resources); - if (light) sceneNodes[0].addComponent(light.ability, light.props); - } - } - } - - return Promise.resolve({ - nodes: sceneNodes - }); -} - -/** - * Get content through index. - * @param name - * @param idx - * @param resources - * @returns {*} - * @private - */ -export function getItemByIdx(name, idx, resources, inverse: boolean = true) { - const { asset } = resources; - - const itemIdx = inverse ? asset[name].length - idx - 1 : idx; - return asset[name][itemIdx]; -} - -/** - * Construct scene graph and create Ability according to node configuration. - * @param resources - * @private - */ -export function buildSceneGraph(resources: GLTFParsed): GLTFResource { - const { asset, gltf } = resources; - - const gltfNodes = gltf.nodes || []; - const gltfMeshes = gltf.meshes; - - asset.defaultScene = getItemByIdx("scenes", gltf.scene ?? 0, resources); - - for (let i = gltfNodes.length - 1; i >= 0; i--) { - const gltfNode = gltfNodes[i]; - const node = getItemByIdx("nodes", i, resources); - - if (gltfNode.hasOwnProperty("children")) { - const children = gltfNode.children || []; - for (let j = children.length - 1; j >= 0; j--) { - const childNode = getItemByIdx("nodes", children[j], resources); - - node.addChild(childNode); - } - } - - // link mesh - if (gltfNode.hasOwnProperty("mesh")) { - const meshIndex = gltfNode.mesh; - node.meshIndex = meshIndex; - const gltfMeshPrimitives = gltfMeshes[meshIndex].primitives; - const meshes = getItemByIdx("meshes", meshIndex, resources); - - for (let j = 0; j < meshes.length; j++) { - const mesh = meshes[j]; - let renderer: MeshRenderer; - if (gltfNode.hasOwnProperty("skin") || mesh.hasOwnProperty("weights")) { - const skin = getItemByIdx("skins", gltfNode.skin, resources); - // const weights = mesh.weights; - const skinRenderer: SkinnedMeshRenderer = node.addComponent(SkinnedMeshRenderer); - skinRenderer.mesh = mesh; - skinRenderer.skin = skin; - // skinRenderer.setWeights(weights); - renderer = skinRenderer; - } else { - renderer = node.addComponent(MeshRenderer); - renderer.mesh = mesh; - } - - const materialIndex = gltfMeshPrimitives[j].material; - const material = - materialIndex !== undefined - ? getItemByIdx("materials", materialIndex, resources) - : getDefaultMaterial(node.engine); - renderer.setMaterial(material); - } - } - } - - //@ts-ignore - const nodes = asset.defaultScene.nodes; - if (nodes.length === 1) { - asset.defaultSceneRoot = nodes[0]; - } else { - const rootNode = new Entity(resources.engine); - for (let i = 0; i < nodes.length; i++) { - rootNode.addChild(nodes[i]); - } - asset.defaultSceneRoot = rootNode; - } - - const animator = asset.defaultSceneRoot.addComponent(Animation); - const animations = asset.animations; - if (animations) { - animations.forEach((clip: AnimationClip) => { - animator.addAnimationClip(clip, clip.name); - }); - } - return resources.asset as GLTFResource; -} diff --git a/packages/loader/src/gltf/glTFDracoMeshCompression.ts b/packages/loader/src/gltf/glTFDracoMeshCompression.ts deleted file mode 100644 index 326611aa69..0000000000 --- a/packages/loader/src/gltf/glTFDracoMeshCompression.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { DRACODecoder } from "@oasis-engine/draco"; -import { getComponentType, getBufferData } from "./Util"; - -let decoder; - -export const glTFDracoMeshCompression = { - init() { - if (!decoder) { - decoder = new DRACODecoder(); - } - }, - parse(extension, gltfPrimitive, gltf, buffers) { - const { bufferViews, accessors } = gltf; - const bufferViewIndex = extension.bufferView; - const gltfAttributeMap = extension.attributes; - const attributeMap = {}; - const attributeTypeMap = {}; - - for (let attributeName in gltfAttributeMap) { - attributeMap[attributeName] = gltfAttributeMap[attributeName]; - } - - for (let attributeName in gltfPrimitive.attributes) { - if (gltfAttributeMap[attributeName] !== undefined) { - const accessorDef = accessors[gltfPrimitive.attributes[attributeName]]; - attributeTypeMap[attributeName] = getComponentType(accessorDef.componentType).name; - } - } - const indexAccessor = accessors[gltfPrimitive.indices]; - const indexType = getComponentType(indexAccessor.componentType).name; - const taskConfig = { - attributeIDs: attributeMap, - attributeTypes: attributeTypeMap, - useUniqueIDs: true, - indexType - }; - const buffer = getBufferData(bufferViews[bufferViewIndex], buffers); - - return decoder.decode(buffer, taskConfig).then((parsedGeometry) => parsedGeometry); - } -}; diff --git a/packages/loader/src/gltf/glb.ts b/packages/loader/src/gltf/glb.ts deleted file mode 100644 index c8c1623f72..0000000000 --- a/packages/loader/src/gltf/glb.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { decodeText } from "./Util"; - -/** - * Parse the glb format. - * @param glb - Binary data - * @returns GlTF information and bin information in Object glb. - * @private - */ -export function parseGLB(glb) { - const UINT32_LENGTH = 4; - const GLB_HEADER_MAGIC = 0x46546c67; // 'glTF' - const GLB_HEADER_LENGTH = 12; - const GLB_CHUNK_TYPES = { JSON: 0x4e4f534a, BIN: 0x004e4942 }; - - const dataView = new DataView(glb); - - // read header - const header = { - magic: dataView.getUint32(0, true), - version: dataView.getUint32(UINT32_LENGTH, true), - length: dataView.getUint32(2 * UINT32_LENGTH, true) - }; - - if (header.magic !== GLB_HEADER_MAGIC) { - console.error("Invalid glb magic number. Expected 0x46546C67, found 0x" + header.magic.toString(16)); - return null; - } - - // read main data - let chunkLength = dataView.getUint32(GLB_HEADER_LENGTH, true); - let chunkType = dataView.getUint32(GLB_HEADER_LENGTH + UINT32_LENGTH, true); - - // read glTF json - if (chunkType !== GLB_CHUNK_TYPES.JSON) { - console.error("Invalid glb chunk type. Expected 0x004E4942, found 0x" + chunkType.toString(16)); - return null; - } - - const glTFData = new Uint8Array(glb, GLB_HEADER_LENGTH + 2 * UINT32_LENGTH, chunkLength); - const gltf = JSON.parse(decodeText(glTFData)); - - // read all buffers - const buffers = []; - let byteOffset = GLB_HEADER_LENGTH + 2 * UINT32_LENGTH + chunkLength; - - while (byteOffset < header.length) { - chunkLength = dataView.getUint32(byteOffset, true); - chunkType = dataView.getUint32(byteOffset + UINT32_LENGTH, true); - - if (chunkType !== GLB_CHUNK_TYPES.BIN) { - console.error("Invalid glb chunk type. Expected 0x004E4942, found 0x" + chunkType.toString(16)); - return null; - } - - const currentOffset = byteOffset + 2 * UINT32_LENGTH; - const buffer = glb.slice(currentOffset, currentOffset + chunkLength); - buffers.push(buffer); - - byteOffset += chunkLength + 2 * UINT32_LENGTH; - } - - // start parse glTF - return { - gltf, - buffers - }; -} diff --git a/packages/loader/src/gltf/parser/AnimationParser.ts b/packages/loader/src/gltf/parser/AnimationParser.ts new file mode 100644 index 0000000000..8055a02480 --- /dev/null +++ b/packages/loader/src/gltf/parser/AnimationParser.ts @@ -0,0 +1,96 @@ +import { AnimationClip, InterpolationType } from "@oasis-engine/core"; +import { GLTFResource } from "../GLTFResource"; +import { GLTFUtil } from "../GLTFUtil"; +import { AnimationChannelTargetPath, AnimationSamplerInterpolation } from "../Schema"; +import { EntityParser } from "./EntityParser"; +import { Parser } from "./Parser"; + +export class AnimationParser extends Parser { + parse(context: GLTFResource): void { + const { gltf, buffers } = context; + const { animations, accessors, nodes } = gltf; + if (!animations) return; + + const animationClips: AnimationClip[] = []; + + for (let i = 0; i < animations.length; i++) { + const gltfAnimation = animations[i]; + const { channels, samplers, name = `Animation${i}` } = gltfAnimation; + + const animationClip = new AnimationClip(name); + + let duration = -1; + let durationIndex = -1; + + // parse samplers + for (let i = 0; i < samplers.length; i++) { + const gltfSampler = samplers[i]; + const inputAccessor = accessors[gltfSampler.input]; + const outputAccessor = accessors[gltfSampler.output]; + const input = GLTFUtil.getAccessorData(gltf, inputAccessor, buffers); + const output = GLTFUtil.getAccessorData(gltf, outputAccessor, buffers); + let outputAccessorSize = GLTFUtil.getAccessorTypeSize(outputAccessor.type); + if (outputAccessorSize * input.length !== output.length) { + outputAccessorSize = output.length / input.length; + } + + let samplerInterpolation; + switch (gltfSampler.interpolation) { + case AnimationSamplerInterpolation.CUBICSPLINE: + samplerInterpolation = InterpolationType.CUBICSPLINE; + break; + case AnimationSamplerInterpolation.STEP: + samplerInterpolation = InterpolationType.STEP; + break; + case AnimationSamplerInterpolation.LINEAR: + samplerInterpolation = InterpolationType.LINEAR; + break; + } + const maxTime = input[input.length - 1]; + if (maxTime > duration) { + duration = maxTime; + durationIndex = i; + } + animationClip.addSampler( + input as Float32Array, + output as Float32Array, + outputAccessorSize, + samplerInterpolation + ); + } + + animationClip.durationIndex = durationIndex; + animationClip.duration = duration; + + for (let i = 0; i < channels.length; i++) { + const { target, sampler } = channels[i]; + let targetPath = ""; + + switch (target.path) { + case AnimationChannelTargetPath.TRANSLATION: + targetPath = "position"; + break; + case AnimationChannelTargetPath.ROTATION: + targetPath = "rotation"; + break; + case AnimationChannelTargetPath.SCALE: + targetPath = "scale"; + break; + case AnimationChannelTargetPath.WEIGHTS: + targetPath = "weights"; + break; + } + + animationClip.addChannel( + sampler, + nodes[target.node].name || `${EntityParser._defaultName}${target.node}`, + targetPath + ); + } + + animationClips[i] = animationClip; + } + + context.animations = animationClips; + } +} diff --git a/packages/loader/src/gltf/parser/BufferParser.ts b/packages/loader/src/gltf/parser/BufferParser.ts new file mode 100644 index 0000000000..fdb429343e --- /dev/null +++ b/packages/loader/src/gltf/parser/BufferParser.ts @@ -0,0 +1,47 @@ +import { AssetType } from "@oasis-engine/core"; +import { GLTFResource } from "../GLTFResource"; +import { IBuffer, IGLTF } from "../Schema"; +import { GLTFUtil } from "../GLTFUtil"; +import { Parser } from "./Parser"; + +export class BufferParser extends Parser { + parse(context: GLTFResource): Promise { + const { url, engine } = context; + + if (this._isGLB(url)) { + return engine.resourceManager + .load({ + url, + type: AssetType.Buffer + }) + .then(GLTFUtil.parseGLB) + .then(({ gltf, buffers }) => { + context.gltf = gltf; + context.buffers = buffers; + }); + } else { + return engine.resourceManager + .load({ + url, + type: AssetType.JSON + }) + .then((gltf: IGLTF) => { + context.gltf = gltf; + return Promise.all( + gltf.buffers.map((buffer: IBuffer) => { + return engine.resourceManager.load({ + type: AssetType.Buffer, + url: GLTFUtil.parseRelativeUrl(url, buffer.uri) + }); + }) + ).then((buffers: ArrayBuffer[]) => { + context.buffers = buffers; + }); + }); + } + } + + private _isGLB(url: string): boolean { + return url.substring(url.lastIndexOf(".") + 1) === "glb"; + } +} diff --git a/packages/loader/src/gltf/parser/EntityParser.ts b/packages/loader/src/gltf/parser/EntityParser.ts new file mode 100644 index 0000000000..03cab24668 --- /dev/null +++ b/packages/loader/src/gltf/parser/EntityParser.ts @@ -0,0 +1,98 @@ +import { Entity } from "@oasis-engine/core"; +import { GLTFResource } from "../GLTFResource"; +import { Parser } from "./Parser"; + +export class EntityParser extends Parser { + /** @internal */ + static _defaultName: String = "_GLTF_ENTITY_"; + + parse(context: GLTFResource): void { + const { + engine, + gltf: { nodes } + } = context; + if (!nodes) return; + + const entities: Entity[] = []; + + for (let i = 0; i < nodes.length; i++) { + const gltfNode = nodes[i]; + const { matrix, translation, rotation, scale } = gltfNode; + const entity = new Entity(engine, gltfNode.name || `${EntityParser._defaultName}${i}`); + + const { transform } = entity; + if (matrix) { + const localMatrix = transform.localMatrix; + localMatrix.setValueByArray(matrix); + transform.localMatrix = localMatrix; + } else { + if (translation) { + transform.setPosition(translation[0], translation[1], translation[2]); + } + if (rotation) { + transform.setRotationQuaternion(rotation[0], rotation[1], rotation[2], rotation[3]); + } + if (scale) { + transform.setScale(scale[0], scale[1], scale[2]); + } + } + + entities[i] = entity; + } + + context.entities = entities; + this._buildEntityTree(context); + this._createSceneRoots(context); + } + + private _buildEntityTree(context: GLTFResource): void { + const { + gltf: { nodes }, + entities + } = context; + + for (let i = 0; i < nodes.length; i++) { + const { children } = nodes[i]; + const entity = entities[i]; + + if (children) { + for (let j = 0; j < children.length; j++) { + const childEntity = entities[children[j]]; + + entity.addChild(childEntity); + } + } + } + } + + private _createSceneRoots(context: GLTFResource): void { + const { + engine, + gltf: { scene: sceneID = 0, scenes }, + entities + } = context; + + if (!scenes) return; + + const sceneRoots: Entity[] = []; + + for (let i = 0; i < scenes.length; i++) { + const { nodes } = scenes[i]; + + if (!nodes) continue; + + if (nodes.length === 1) { + sceneRoots[i] = entities[nodes[0]]; + } else { + const rootEntity = new Entity(engine, "GLTF_ROOT"); + for (let j = 0; j < nodes.length; j++) { + rootEntity.addChild(entities[nodes[j]]); + } + sceneRoots[i] = rootEntity; + } + } + + context.sceneRoots = sceneRoots; + context.defaultSceneRoot = sceneRoots[sceneID]; + } +} diff --git a/packages/loader/src/gltf/parser/MaterialParser.ts b/packages/loader/src/gltf/parser/MaterialParser.ts new file mode 100644 index 0000000000..a6a11c462f --- /dev/null +++ b/packages/loader/src/gltf/parser/MaterialParser.ts @@ -0,0 +1,138 @@ +import { Material, PBRMaterial, PBRSpecularMaterial, RenderFace, UnlitMaterial } from "@oasis-engine/core"; +import { Color } from "@oasis-engine/math"; +import { GLTFResource } from "../GLTFResource"; +import { MaterialAlphaMode } from "../Schema"; +import { Parser } from "./Parser"; + +export class MaterialParser extends Parser { + /** @internal */ + static _parseTextureTransform(material: Material, extensions: any = {}, context: GLTFResource): void { + const schema = extensions.KHR_texture_transform; + if (schema) { + Parser.parseEngineResource("KHR_texture_transform", schema, material, context); + } + } + + parse(context: GLTFResource): void { + const { gltf, engine, textures } = context; + if (!gltf.materials) return; + + const materials: Material[] = []; + + for (let i = 0; i < gltf.materials.length; i++) { + const { + extensions = {}, + pbrMetallicRoughness, + normalTexture, + occlusionTexture, + emissiveTexture, + emissiveFactor, + alphaMode, + alphaCutoff, + doubleSided, + name = "" + } = gltf.materials[i]; + + const { KHR_materials_unlit, KHR_materials_pbrSpecularGlossiness } = extensions; + + let material: UnlitMaterial | PBRMaterial | PBRSpecularMaterial = null; + + if (KHR_materials_unlit) { + material = Parser.createEngineResource("KHR_materials_unlit", KHR_materials_unlit, context); + } else if (KHR_materials_pbrSpecularGlossiness) { + material = ( + Parser.createEngineResource( + "KHR_materials_pbrSpecularGlossiness", + KHR_materials_pbrSpecularGlossiness, + context + ) + ); + } else { + material = new PBRMaterial(engine); + } + + material.name = name; + + if (pbrMetallicRoughness) { + const { + baseColorFactor, + baseColorTexture, + metallicFactor, + roughnessFactor, + metallicRoughnessTexture + } = pbrMetallicRoughness; + + if (baseColorFactor) { + material.baseColor = new Color(...baseColorFactor); + } + if (baseColorTexture) { + material.baseTexture = textures[baseColorTexture.index]; + MaterialParser._parseTextureTransform(material, baseColorTexture.extensions, context); + } + + if (!KHR_materials_unlit && !KHR_materials_pbrSpecularGlossiness) { + material = material as PBRMaterial; + material.metallicFactor = metallicFactor ?? 1; + material.roughnessFactor = roughnessFactor ?? 1; + if (metallicRoughnessTexture) { + material.metallicRoughnessTexture = textures[metallicRoughnessTexture.index]; + MaterialParser._parseTextureTransform(material, metallicRoughnessTexture.extensions, context); + } + } + } + + if (!KHR_materials_unlit) { + material = material as PBRMaterial | PBRSpecularMaterial; + + if (emissiveTexture) { + material.emissiveTexture = textures[emissiveTexture.index]; + MaterialParser._parseTextureTransform(material, emissiveTexture.extensions, context); + } + + if (emissiveFactor) { + material.emissiveColor = new Color(...emissiveFactor); + } + + if (normalTexture) { + const { index, scale } = normalTexture; + material.normalTexture = textures[index]; + MaterialParser._parseTextureTransform(material, normalTexture.extensions, context); + if (scale !== undefined) { + material.normalIntensity = scale; + } + } + + if (occlusionTexture) { + const { index, strength } = occlusionTexture; + material.occlusionTexture = textures[index]; + MaterialParser._parseTextureTransform(material, occlusionTexture.extensions, context); + if (strength !== undefined) { + material.occlusionStrength = strength; + } + } + } + + if (doubleSided) { + material.renderFace = RenderFace.Double; + } else { + material.renderFace = RenderFace.Front; + } + + switch (alphaMode) { + case MaterialAlphaMode.OPAQUE: + material.isTransparent = false; + break; + case MaterialAlphaMode.BLEND: + material.isTransparent = true; + break; + case MaterialAlphaMode.MASK: + material.alphaCutoff = alphaCutoff ?? 0.5; + break; + } + + materials[i] = material; + } + + context.materials = materials; + } +} diff --git a/packages/loader/src/gltf/parser/MeshParser.ts b/packages/loader/src/gltf/parser/MeshParser.ts new file mode 100644 index 0000000000..3d28f20579 --- /dev/null +++ b/packages/loader/src/gltf/parser/MeshParser.ts @@ -0,0 +1,181 @@ +import { + Buffer, + BufferBindFlag, + BufferMesh, + BufferUsage, + Engine, + EngineObject, + IndexBufferBinding, + IndexFormat, + Logger, + TypedArray, + VertexElement +} from "@oasis-engine/core"; +import { Vector3 } from "@oasis-engine/math"; +import { GLTFResource } from "../GLTFResource"; +import { GLTFUtil } from "../GLTFUtil"; +import { IGLTF, IMeshPrimitive } from "../Schema"; +import { Parser } from "./Parser"; + +export class MeshParser extends Parser { + private static _tempVector3 = new Vector3(); + parse(context: GLTFResource): Promise { + const { engine, gltf, buffers } = context; + if (!gltf.meshes) return; + + const meshPromises: Promise[] = []; + + for (let i = 0; i < gltf.meshes.length; i++) { + const gltfMesh = gltf.meshes[i]; + const primitivePromises: Promise[] = []; + + if (gltfMesh.weights) { + Logger.error("Sorry, morph animation is not supported now, wait please."); + } + + for (let j = 0; j < gltfMesh.primitives.length; j++) { + const gltfPrimitive = gltfMesh.primitives[j]; + const { targets, extensions = {} } = gltfPrimitive; + const { KHR_draco_mesh_compression } = extensions; + + primitivePromises.push( + new Promise((resolve) => { + const mesh = new BufferMesh(engine, gltfMesh.name || j + ""); + + if (targets) { + Logger.error("Sorry, morph animation is not supported now, wait please."); + } + + if (KHR_draco_mesh_compression) { + (>( + Parser.createEngineResource( + "KHR_draco_mesh_compression", + KHR_draco_mesh_compression, + context, + gltfPrimitive + ) + )) + .then((decodedGeometry: any) => { + return this._parseMeshFromGLTFPrimitive( + mesh, + gltfPrimitive, + gltf, + (attributeSemantic) => { + for (let j = 0; j < decodedGeometry.attributes.length; j++) { + if (decodedGeometry.attributes[j].name === attributeSemantic) { + return decodedGeometry.attributes[j].array; + } + } + return null; + }, + () => { + return decodedGeometry.index.array; + }, + engine + ); + }) + .then(resolve); + } else { + this._parseMeshFromGLTFPrimitive( + mesh, + gltfPrimitive, + gltf, + (attributeSemantic) => { + const accessorIdx = gltfPrimitive.attributes[attributeSemantic]; + const accessor = gltf.accessors[accessorIdx]; + return GLTFUtil.getAccessorData(gltf, accessor, buffers); + }, + () => { + const indexAccessor = gltf.accessors[gltfPrimitive.indices]; + return GLTFUtil.getAccessorData(gltf, indexAccessor, buffers); + }, + engine + ).then(resolve); + } + }) + ); + } + meshPromises.push(Promise.all(primitivePromises)); + } + + return Promise.all(meshPromises).then((meshes: BufferMesh[][]) => { + context.meshes = meshes; + }); + } + + private _parseMeshFromGLTFPrimitive( + mesh: BufferMesh, + gltfPrimitive: IMeshPrimitive, + gltf: IGLTF, + getVertexBufferData: (semantic: string) => TypedArray, + getIndexBufferData: () => TypedArray, + engine: Engine + ): Promise { + const { attributes, indices, mode } = gltfPrimitive; + const vertexElements: VertexElement[] = []; + let j = 0; + let vertexCount: number; + + for (const attributeSemantic in attributes) { + const accessorIdx = attributes[attributeSemantic]; + const accessor = gltf.accessors[accessorIdx]; + const stride = GLTFUtil.getVertexStride(gltf, accessor); + const vertexELement = GLTFUtil.createVertexElement(attributeSemantic, accessor, j); + + vertexElements.push(vertexELement); + const bufferData = getVertexBufferData(attributeSemantic); + const vertexBuffer = new Buffer(engine, BufferBindFlag.VertexBuffer, bufferData.byteLength, BufferUsage.Static); + vertexBuffer.setData(bufferData); + mesh.setVertexBufferBinding(vertexBuffer, stride, j++); + + // Bounds + if (vertexELement.semantic === "POSITION") { + const { bounds } = mesh; + if (accessor.min && accessor.max) { + bounds.min.setValueByArray(accessor.min); + bounds.max.setValueByArray(accessor.max); + } else { + const position = MeshParser._tempVector3; + const { min, max } = bounds; + + vertexCount = accessor.count; + min.setValue(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); + max.setValue(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); + + const stride = bufferData.length / vertexCount; + for (let j = 0; j < vertexCount; j++) { + const offset = j * stride; + position.setValueByArray(bufferData, offset); + Vector3.min(min, position, min); + Vector3.max(max, position, max); + } + } + } + } + mesh.setVertexElements(vertexElements); + + // Indices + if (indices !== undefined) { + const indexAccessor = gltf.accessors[indices]; + const indexData = getIndexBufferData(); + + const indexCount = indexAccessor.count; + const indexFormat = GLTFUtil.getIndexFormat(indexAccessor.componentType); + const indexByteSize = indexFormat == IndexFormat.UInt32 ? 4 : indexFormat == IndexFormat.UInt16 ? 2 : 1; + const indexBuffer = new Buffer( + engine, + BufferBindFlag.IndexBuffer, + indexCount * indexByteSize, + BufferUsage.Static + ); + + indexBuffer.setData(indexData); + mesh.setIndexBufferBinding(new IndexBufferBinding(indexBuffer, indexFormat)); + mesh.addSubMesh(0, indexCount, mode); + } else { + mesh.addSubMesh(0, vertexCount, mode); + } + + return Promise.resolve(mesh); + } +} diff --git a/packages/loader/src/gltf/parser/Parser.ts b/packages/loader/src/gltf/parser/Parser.ts new file mode 100644 index 0000000000..d85e8f2cc3 --- /dev/null +++ b/packages/loader/src/gltf/parser/Parser.ts @@ -0,0 +1,76 @@ +import { EngineObject } from "@oasis-engine/core"; +import { ExtensionParser } from "../extensions/ExtensionParser"; +import { ExtensionSchema } from "../extensions/Schema"; +import { GLTFResource } from "../GLTFResource"; + +export abstract class Parser { + private static _extensionParsers: Record = {}; + + static parseEngineResource( + extensionName: string, + extensionSchema: ExtensionSchema, + parseResource: EngineObject, + context: GLTFResource, + ...extra + ): void { + const parsers = Parser._extensionParsers[extensionName]; + + if (parsers?.length) { + for (let i = 0; i < parsers.length; i++) { + parsers[i].parseEngineResource(extensionSchema, parseResource, context, ...extra); + } + } + } + + static createEngineResource( + extensionName: string, + extensionSchema: ExtensionSchema, + context: GLTFResource, + ...extra + ): T | Promise { + const parsers = Parser._extensionParsers[extensionName]; + + if (parsers?.length) { + return parsers[0].createEngineResource(extensionSchema, context, ...extra) as T; + } + } + + static hasExtensionParser(extensionName: string): boolean { + const parsers = Parser._extensionParsers[extensionName]; + return !!parsers?.length; + } + + static initialize(extensionName: string) { + const parsers = Parser._extensionParsers[extensionName]; + + if (parsers?.length) { + for (let i = 0; i < parsers.length; i++) { + parsers[i].initialize(); + } + } + } + + /** + * @internal + */ + static _addExtensionParser(extensionName: string, extensionParser: ExtensionParser) { + if (!Parser._extensionParsers[extensionName]) { + Parser._extensionParsers[extensionName] = []; + } + Parser._extensionParsers[extensionName].push(extensionParser); + } + + abstract parse(context: GLTFResource): void | Promise; +} + +/** + * Declare ExtensionParser's decorator. + * @param extensionName - Extension name + */ +export function registerExtension(extensionName: string) { + return (parser: new () => ExtensionParser) => { + const extensionParser = new parser(); + + Parser._addExtensionParser(extensionName, extensionParser); + }; +} diff --git a/packages/loader/src/gltf/parser/SceneParser.ts b/packages/loader/src/gltf/parser/SceneParser.ts new file mode 100644 index 0000000000..7c1f189918 --- /dev/null +++ b/packages/loader/src/gltf/parser/SceneParser.ts @@ -0,0 +1,157 @@ +import { + Animation, + BlinnPhongMaterial, + Camera, + Engine, + Entity, + Logger, + MeshRenderer, + SkinnedMeshRenderer +} from "@oasis-engine/core"; +import { IKHRLightsPunctual, IKHRLightsPunctual_LightNode } from "../extensions/Schema"; +import { GLTFResource } from "../GLTFResource"; +import { CameraType, ICamera, INode } from "../Schema"; +import { Parser } from "./Parser"; + +export class SceneParser extends Parser { + private static _defaultMaterial: BlinnPhongMaterial; + + private static _getDefaultMaterial(engine: Engine): BlinnPhongMaterial { + if (!SceneParser._defaultMaterial) { + SceneParser._defaultMaterial = new BlinnPhongMaterial(engine); + } + + return SceneParser._defaultMaterial; + } + + parse(context: GLTFResource): void { + const { + gltf: { nodes, cameras: gltfCameras }, + entities + } = context; + + if (!nodes) return; + + for (let i = 0; i < nodes.length; i++) { + const gltfNode = nodes[i]; + const { camera: cameraID, mesh: meshID, extensions = {} } = gltfNode; + const KHR_lights_punctual = extensions.KHR_lights_punctual; + const entity = entities[i]; + + if (cameraID !== undefined) { + this._createCamera(context, gltfCameras[cameraID], entity); + } + + if (meshID !== undefined) { + this._createRenderer(context, gltfNode, entity); + } + + if (KHR_lights_punctual) { + const lightIndex = KHR_lights_punctual.light; + const lights = (context.gltf.extensions.KHR_lights_punctual as IKHRLightsPunctual).lights; + + Parser.parseEngineResource("KHR_lights_punctual", lights[lightIndex], entity, context); + } + } + + if (context.defaultSceneRoot) { + this._createAnimator(context); + } + } + + private _createCamera(context: GLTFResource, cameraSchema: ICamera, entity: Entity): void { + const { orthographic, perspective, type } = cameraSchema; + const camera = entity.addComponent(Camera); + + if (type === CameraType.ORTHOGRAPHIC) { + const { xmag, ymag, zfar, znear } = orthographic; + + camera.isOrthographic = true; + + if (znear !== undefined) { + camera.nearClipPlane = znear; + } + if (zfar !== undefined) { + camera.farClipPlane = zfar; + } + + camera.orthographicSize = Math.max(ymag ?? 0, xmag ?? 0) / 2; + } else if (type === CameraType.PERSPECTIVE) { + const { aspectRatio, yfov, zfar, znear } = perspective; + + if (aspectRatio !== undefined) { + camera.aspectRatio = aspectRatio; + } + if (yfov !== undefined) { + camera.fieldOfView = (yfov * 180) / Math.PI; + } + if (zfar !== undefined) { + camera.farClipPlane = zfar; + } + if (znear !== undefined) { + camera.nearClipPlane = znear; + } + } + + if (!context.cameras) context.cameras = []; + context.cameras.push(camera); + // @todo: use engine camera by default + camera.enabled = false; + } + + private _createRenderer(context: GLTFResource, gltfNode: INode, entity: Entity): void { + const { + engine, + gltf: { meshes: gltfMeshes }, + meshes, + materials, + skins + } = context; + const { mesh: meshID, skin: skinID, weights } = gltfNode; + + if (weights) { + Logger.error("Sorry, morph animation is not supported now, wait please."); + } + + const gltfMeshPrimitives = gltfMeshes[meshID].primitives; + + for (let i = 0; i < gltfMeshPrimitives.length; i++) { + const mesh = meshes[meshID][i]; + let renderer: MeshRenderer | SkinnedMeshRenderer; + + if (skinID !== undefined) { + const skin = skins[skinID]; + const skinRenderer = entity.addComponent(SkinnedMeshRenderer); + skinRenderer.mesh = mesh; + skinRenderer.skin = skin; + renderer = skinRenderer; + } else { + renderer = entity.addComponent(MeshRenderer); + renderer.mesh = mesh; + } + + const materialIndex = gltfMeshPrimitives[i].material; + const material = materials?.[materialIndex] || SceneParser._getDefaultMaterial(engine); + renderer.setMaterial(material); + + const { extensions = {} } = gltfMeshPrimitives[i]; + const { KHR_materials_variants } = extensions; + if (KHR_materials_variants) { + Parser.parseEngineResource("KHR_materials_variants", KHR_materials_variants, renderer, context); + } + } + } + + private _createAnimator(context: GLTFResource) { + const { defaultSceneRoot, animations } = context; + + if (!animations) return; + + const animator = defaultSceneRoot.addComponent(Animation); + + for (let i = 0; i < animations.length; i++) { + const animationClip = animations[i]; + animator.addAnimationClip(animationClip, animationClip.name); + } + } +} diff --git a/packages/loader/src/gltf/parser/SkinParser.ts b/packages/loader/src/gltf/parser/SkinParser.ts new file mode 100644 index 0000000000..a0f15a536d --- /dev/null +++ b/packages/loader/src/gltf/parser/SkinParser.ts @@ -0,0 +1,49 @@ +import { Skin } from "@oasis-engine/core"; +import { Matrix } from "@oasis-engine/math"; +import { GLTFResource } from "../GLTFResource"; +import { GLTFUtil } from "../GLTFUtil"; +import { Parser } from "./Parser"; + +export class SkinParser extends Parser { + parse(context: GLTFResource): void { + const { gltf, buffers, entities, defaultSceneRoot } = context; + const gltfSkins = gltf.skins; + + if (!gltfSkins) return; + + const skins: Skin[] = []; + + for (let i = 0; i < gltfSkins.length; i++) { + const { inverseBindMatrices, skeleton, joints, name = `SKIN_${i}` } = gltfSkins[i]; + const jointCount = joints.length; + + const skin = new Skin(name); + skin.inverseBindMatrices.length = jointCount; + + // parse IBM + const accessor = gltf.accessors[inverseBindMatrices]; + const buffer = GLTFUtil.getAccessorData(gltf, accessor, buffers); + for (let i = 0; i < jointCount; i++) { + const inverseBindMatrix = new Matrix(); + inverseBindMatrix.setValueByArray(buffer, i * 16); + skin.inverseBindMatrices[i] = inverseBindMatrix; + } + + // get joints + for (let i = 0; i < jointCount; i++) { + skin.joints[i] = entities[joints[i]].name; + } + + // get skeleton + if (skeleton !== undefined) { + skin.skeleton = entities[skeleton].name; + } else { + skin.skeleton = defaultSceneRoot.name; + } + + skins[i] = skin; + } + + context.skins = skins; + } +} diff --git a/packages/loader/src/gltf/parser/TextureParser.ts b/packages/loader/src/gltf/parser/TextureParser.ts new file mode 100644 index 0000000000..bf3dd85bae --- /dev/null +++ b/packages/loader/src/gltf/parser/TextureParser.ts @@ -0,0 +1,73 @@ +import { AssetType, Logger, Texture2D, TextureWrapMode } from "@oasis-engine/core"; +import { GLTFResource } from "../GLTFResource"; +import { ISampler } from "../Schema"; +import { GLTFUtil } from "../GLTFUtil"; +import { Parser } from "./Parser"; + +export class TextureParser extends Parser { + private static _wrapMap = { + 33071: TextureWrapMode.Clamp, + 33648: TextureWrapMode.Mirror, + 10497: TextureWrapMode.Repeat + }; + + parse(context: GLTFResource): void | Promise { + const { gltf, buffers, engine, url } = context; + + if (gltf.textures) { + return Promise.all( + gltf.textures.map(({ sampler, source = 0, name: textureName }, index) => { + const { uri, bufferView: bufferViewIndex, mimeType, name: imageName } = gltf.images[source]; + + if (uri) { + return engine.resourceManager + .load({ + url: GLTFUtil.parseRelativeUrl(url, uri), + type: AssetType.Texture2D + }) + .then((texture) => { + if (!texture.name) { + texture.name = textureName || imageName || `texture_${index}`; + } + if (sampler !== undefined) { + this._parseSampler(texture, gltf.samplers[sampler]); + } + return texture; + }); + } else { + const bufferView = gltf.bufferViews[bufferViewIndex]; + const bufferViewData = GLTFUtil.getBufferViewData(bufferView, buffers); + return GLTFUtil.loadImageBuffer(bufferViewData, mimeType).then((image) => { + const texture = new Texture2D(engine, image.width, image.height); + texture.setImageSource(image); + texture.generateMipmaps(); + texture.name = textureName || imageName || `texture_${index}`; + if (sampler !== undefined) { + this._parseSampler(texture, gltf.samplers[sampler]); + } + return texture; + }); + } + }) + ).then((textures: Texture2D[]) => { + context.textures = textures; + }); + } + } + + private _parseSampler(texture: Texture2D, sampler: ISampler): void { + const { magFilter, minFilter, wrapS, wrapT } = sampler; + + if (magFilter || minFilter) { + Logger.warn("texture use filterMode in engine"); + } + + if (wrapS) { + texture.wrapModeU = TextureParser._wrapMap[wrapS]; + } + + if (wrapT) { + texture.wrapModeV = TextureParser._wrapMap[wrapT]; + } + } +} diff --git a/packages/loader/src/gltf/parser/Validator.ts b/packages/loader/src/gltf/parser/Validator.ts new file mode 100644 index 0000000000..a7019451db --- /dev/null +++ b/packages/loader/src/gltf/parser/Validator.ts @@ -0,0 +1,42 @@ +import { Logger } from "@oasis-engine/core"; +import { GLTFResource } from "../GLTFResource"; +import { Parser } from "./Parser"; + +export class Validator extends Parser { + parse(context: GLTFResource): void { + const { + gltf: { + asset: { version }, + extensionsUsed, + extensionsRequired + } + } = context; + + const gltfVersion = Number(version); + if (!(gltfVersion >= 2 && gltfVersion < 3)) { + throw "Only support gltf 2.x."; + } + + if (extensionsUsed) { + Logger.info("extensionsUsed: ", extensionsUsed); + for (let i = 0; i < extensionsUsed.length; i++) { + if (!Parser.hasExtensionParser(extensionsUsed[i])) { + Logger.warn(`Extension ${extensionsUsed[i]} is not implemented, you can customize this extension in gltf.`); + } + } + } + + if (extensionsRequired) { + Logger.info(`extensionsRequired: ${extensionsRequired}`); + for (let i = 0; i < extensionsRequired.length; i++) { + const extensionRequired = extensionsRequired[i]; + + if (!Parser.hasExtensionParser(extensionRequired)) { + Logger.error(`GLTF parser has not supported required extension ${extensionRequired}.`); + } else { + Parser.initialize(extensionRequired); + } + } + } + } +} diff --git a/packages/loader/src/index.ts b/packages/loader/src/index.ts index 529488c3fa..9a84d8a49d 100644 --- a/packages/loader/src/index.ts +++ b/packages/loader/src/index.ts @@ -5,7 +5,9 @@ import "./KTXCubeLoader"; import "./KTXLoader"; import "./Texture2DLoader"; import "./TextureCubeLoader"; -export { RegistExtension, GLTFResource } from "./gltf/glTF"; +import "./gltf/extensions/index"; + +export { GLTFResource } from "./gltf/GLTFResource"; export { GLTFModel } from "./scene-loader/GLTFModel"; export { Model } from "./scene-loader/Model"; export * from "./scene-loader/index"; diff --git a/packages/loader/src/scene-loader/AbilityManager.ts b/packages/loader/src/scene-loader/AbilityManager.ts index fe949828c3..90522e3c07 100644 --- a/packages/loader/src/scene-loader/AbilityManager.ts +++ b/packages/loader/src/scene-loader/AbilityManager.ts @@ -32,6 +32,9 @@ export class AbilityManager { if (type === "GLTFModel") { // TODO (ability as any).init(abilityProps); + } else if (type === "Model") { + // TODO + (ability as any).setProps(abilityProps); } else { for (let k in abilityProps) { if (abilityProps[k] !== null) { @@ -56,7 +59,7 @@ export class AbilityManager { if (value && this.checkIsAsset(value)) { (this.get(id) as any).setProp(key, this.oasis.resourceManager.get(value.id).resource); } else { - (this.get(id) as any).setProp(key, value); + (this.get(id) as any).updateProp(key, value); } } else { if (value && this.checkIsAsset(value)) { @@ -69,6 +72,12 @@ export class AbilityManager { 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]; } @@ -90,7 +99,7 @@ export class AbilityManager { const constructor = Parser._components["o3"][type]; if (!constructor) { - throw new Error(`${type} is not defined`); + console.warn(`${type} is not defined`); } return constructor; } diff --git a/packages/loader/src/scene-loader/GLTFModel.ts b/packages/loader/src/scene-loader/GLTFModel.ts index 2b6a79521c..df680049cc 100644 --- a/packages/loader/src/scene-loader/GLTFModel.ts +++ b/packages/loader/src/scene-loader/GLTFModel.ts @@ -1,5 +1,5 @@ import { Component, Entity, WrapMode, Animation } from "@oasis-engine/core"; -import { GLTFResource } from "../gltf/glTF"; +import { GLTFResource } from "../gltf/GLTFResource"; /** * @deprecated diff --git a/packages/loader/src/scene-loader/Model.ts b/packages/loader/src/scene-loader/Model.ts index 4ffdad8491..c29c1a1194 100644 --- a/packages/loader/src/scene-loader/Model.ts +++ b/packages/loader/src/scene-loader/Model.ts @@ -2,52 +2,61 @@ import { BlinnPhongMaterial, Entity, MeshRenderer, PrimitiveMesh } from "@oasis- // Only for editor export class Model extends MeshRenderer { - private _geometryType: GeometryType; + private _props: Object = null; - set geometryType(value: GeometryType) { - switch (value) { + 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); + this.mesh = PrimitiveMesh.createSphere(this._engine, props.sphereRadius, props.sphereSegments); break; case "Cylinder": - this.mesh = PrimitiveMesh.createCylinder(this._engine); + 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); + this.mesh = PrimitiveMesh.createPlane( + this._engine, + props.planeWidth, + props.planeHeight, + props.planeHorizontalSegments, + props.planeVerticalSegments + ); break; case "Box": - this.mesh = PrimitiveMesh.createCuboid(this._engine); + this.mesh = PrimitiveMesh.createCuboid(this._engine, props.boxWidth, props.boxHeight, props.boxDepth); break; } - - this._geometryType = value; } - get geometryType() { - return this._geometryType; + updateProp(key: string, value: string | number) { + const props = this._props; + props[key] = value; + this.setProps(props); } - - constructor(entity: Entity) { - super(entity); - this.setMaterial(new BlinnPhongMaterial(this.engine)); - this.geometryType = GeometryType.Box; - } - - get material(): any { - return this.getMaterial(); - } - - set material(mtl: any) { - this.setMaterial(mtl); - } -} - -enum GeometryType { - Box = "Box", - Cylinder = "Cylinder", - Plane = "Plane", - Sphere = "Sphere" } diff --git a/packages/loader/src/scene-loader/Oasis.ts b/packages/loader/src/scene-loader/Oasis.ts index fea494a193..e5105b7c18 100644 --- a/packages/loader/src/scene-loader/Oasis.ts +++ b/packages/loader/src/scene-loader/Oasis.ts @@ -1,4 +1,4 @@ -import { Engine, EventDispatcher, ObjectValues } from "@oasis-engine/core"; +import { EventDispatcher, ObjectValues } from "@oasis-engine/core"; import { AbilityManager } from "./AbilityManager"; import { NodeManager } from "./NodeManager"; import { pluginHook, PluginManager } from "./plugins/PluginManager"; @@ -6,7 +6,6 @@ import { RESOURCE_CLASS, SchemaResourceManager } from "./ResourceManager"; import { Options, Schema } from "./types"; export class Oasis extends EventDispatcher { - public readonly engine: Engine = null; public readonly nodeManager: NodeManager; public readonly abilityManager: AbilityManager; public resourceManager: SchemaResourceManager; @@ -17,7 +16,6 @@ export class Oasis extends EventDispatcher { private constructor(private _options: Options, public readonly pluginManager: PluginManager) { super(_options.engine); - this.engine = _options.engine; this.schema = _options.config; this.timeout = _options.timeout; _options.scripts = _options.scripts ?? {}; diff --git a/packages/loader/src/scene-loader/resources/GLTFResource.ts b/packages/loader/src/scene-loader/resources/GLTFResource.ts index 19cac1353a..eb0e4c6f2d 100644 --- a/packages/loader/src/scene-loader/resources/GLTFResource.ts +++ b/packages/loader/src/scene-loader/resources/GLTFResource.ts @@ -2,15 +2,12 @@ import { AssetType, Entity, Logger, - Material, - Mesh, MeshRenderer, PBRMaterial, PBRSpecularMaterial, ResourceManager, UnlitMaterial } from "@oasis-engine/core"; -import { glTFDracoMeshCompression } from "../../gltf/glTFDracoMeshCompression"; import { Oasis } from "../Oasis"; import { AssetConfig, LoadAttachedResourceResult } from "../types"; import { BlinnPhongMaterialResource } from "./BlinnPhongMaterialResource"; @@ -21,9 +18,6 @@ import { UnlitMaterialResource } from "./UnlitMaterialResource"; export class GLTFResource extends SchemaResource { load(resourceManager: ResourceManager, assetConfig: AssetConfig, oasis: Oasis): Promise { - if (!!assetConfig.props?.compression) { - glTFDracoMeshCompression.init(); - } return resourceManager .load({ url: assetConfig.url, type: AssetType.Perfab }) .then((res) => { diff --git a/packages/loader/src/scene-loader/resources/SpriteResource.ts b/packages/loader/src/scene-loader/resources/SpriteResource.ts index 31c6fbc486..eb0a3596e9 100644 --- a/packages/loader/src/scene-loader/resources/SpriteResource.ts +++ b/packages/loader/src/scene-loader/resources/SpriteResource.ts @@ -58,7 +58,7 @@ export class SpriteResource extends SchemaResource { } for (let k in configProps) { - if (!isAsset(configProps[k])) { + if (!isAsset(configProps[k]) && typeof configProps[k] !== "undefined") { assetObj[k] = configProps[k]; } } diff --git a/packages/loader/test/gltf/Util.test.ts b/packages/loader/test/gltf/Util.test.ts index 058793bd39..ae8cd14964 100644 --- a/packages/loader/test/gltf/Util.test.ts +++ b/packages/loader/test/gltf/Util.test.ts @@ -1,20 +1,20 @@ -import { parseRelativeUrl } from "../../src/gltf/Util"; +import { GLTFUtil } from "../../src/gltf/GLTFUtil"; describe("utils test", () => { it("test base64", () => { const base64 = "data:image/png;base64,iVBORw0"; - expect(parseRelativeUrl("any", base64)).toEqual(base64); + expect(GLTFUtil.parseRelativeUrl("any", base64)).toEqual(base64); }); it("test http and https", () => { const http = "http://123.com"; const https = "https://123.com"; - expect(parseRelativeUrl("any", http)).toEqual(http); - expect(parseRelativeUrl("any", https)).toEqual(https); + expect(GLTFUtil.parseRelativeUrl("any", http)).toEqual(http); + expect(GLTFUtil.parseRelativeUrl("any", https)).toEqual(https); }); it("test relative", () => { const gltf = "/static/model/DamangedHelmet/DamagedHelmet.gltf"; const bin = "DamagedHelmet.bin"; - expect(parseRelativeUrl(gltf, bin)).toEqual("/static/model/DamangedHelmet/DamagedHelmet.bin"); + expect(GLTFUtil.parseRelativeUrl(gltf, bin)).toEqual("/static/model/DamangedHelmet/DamagedHelmet.bin"); }); }); diff --git a/packages/loader/tsconfig.json b/packages/loader/tsconfig.json index 526cf34ece..179345cd7f 100644 --- a/packages/loader/tsconfig.json +++ b/packages/loader/tsconfig.json @@ -8,6 +8,7 @@ "experimentalDecorators": true, "declarationDir": "types", "incremental": false, + "skipLibCheck": true, "emitDeclarationOnly": true, "sourceMap": true }, diff --git a/packages/math/package.json b/packages/math/package.json index 255767eade..731d33de17 100755 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -4,6 +4,7 @@ "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", + "debug": "src/index.ts", "types": "types/index.d.ts", "scripts": { "b:types": "tsc" diff --git a/packages/math/src/BoundingBox.ts b/packages/math/src/BoundingBox.ts index 6ef1d40867..f837f68a2c 100644 --- a/packages/math/src/BoundingBox.ts +++ b/packages/math/src/BoundingBox.ts @@ -60,7 +60,7 @@ export class BoundingBox implements IClone { } /** - * Transfrom a bounding box. + * Transform a bounding box. * @param source - The original bounding box * @param matrix - The transform to apply to the bounding box * @param out - The transformed bounding box @@ -188,7 +188,7 @@ export class BoundingBox implements IClone { } /** - * Transfrom a bounding box. + * Transform a bounding box. * @param matrix - The transform to apply to the bounding box * @returns The transformed bounding box */ diff --git a/packages/math/src/Matrix.ts b/packages/math/src/Matrix.ts index 3c0d101ad9..4a153b35ec 100644 --- a/packages/math/src/Matrix.ts +++ b/packages/math/src/Matrix.ts @@ -129,6 +129,40 @@ export class Matrix implements IClone { ); } + /** + * Performs a linear interpolation between two matrices. + * @param start - The first matrix + * @param end - The second matrix + * @param t - The blend amount where 0 returns start and 1 end + * @param out - The result of linear blending between two matrices + */ + static lerp(start: Matrix, end: Matrix, t: number, out: Matrix): void { + const se = start.elements; + const ee = end.elements; + const oe = out.elements; + const inv = 1.0 - t; + + oe[0] = se[0] * inv + ee[0] * t; + oe[1] = se[1] * inv + ee[1] * t; + oe[2] = se[2] * inv + ee[2] * t; + oe[3] = se[3] * inv + ee[3] * t; + + oe[4] = se[4] * inv + ee[4] * t; + oe[5] = se[5] * inv + ee[5] * t; + oe[6] = se[6] * inv + ee[6] * t; + oe[7] = se[7] * inv + ee[7] * t; + + oe[8] = se[8] * inv + ee[8] * t; + oe[9] = se[9] * inv + ee[9] * t; + oe[10] = se[10] * inv + ee[10] * t; + oe[11] = se[11] * inv + ee[11] * t; + + oe[12] = se[12] * inv + ee[12] * t; + oe[13] = se[13] * inv + ee[13] * t; + oe[14] = se[14] * inv + ee[14] * t; + oe[15] = se[15] * inv + ee[15] * t; + } + /** * Calculate a rotation matrix from a quaternion. * @param quaternion - The quaternion used to calculate the matrix diff --git a/packages/math/src/Matrix3x3.ts b/packages/math/src/Matrix3x3.ts index af66edc4a9..304520d716 100644 --- a/packages/math/src/Matrix3x3.ts +++ b/packages/math/src/Matrix3x3.ts @@ -123,6 +123,32 @@ export class Matrix3x3 implements IClone { ); } + /** + * Performs a linear interpolation between two matrices. + * @param start - The first matrix + * @param end - The second matrix + * @param t - The blend amount where 0 returns start and 1 end + * @param out - The result of linear blending between two matrices + */ + static lerp(start: Matrix3x3, end: Matrix3x3, t: number, out: Matrix3x3): void { + const se = start.elements; + const ee = end.elements; + const oe = out.elements; + const inv = 1.0 - t; + + oe[0] = se[0] * inv + ee[0] * t; + oe[1] = se[1] * inv + ee[1] * t; + oe[2] = se[2] * inv + ee[2] * t; + + oe[3] = se[3] * inv + ee[3] * t; + oe[4] = se[4] * inv + ee[4] * t; + oe[5] = se[5] * inv + ee[5] * t; + + oe[6] = se[6] * inv + ee[6] * t; + oe[7] = se[7] * inv + ee[7] * t; + oe[8] = se[8] * inv + ee[8] * t; + } + /** * Calculate a rotation matrix from a quaternion. * @param quaternion - The quaternion used to calculate the matrix diff --git a/packages/math/src/Vector2.ts b/packages/math/src/Vector2.ts index 1f788a2a59..d059160c1f 100644 --- a/packages/math/src/Vector2.ts +++ b/packages/math/src/Vector2.ts @@ -5,9 +5,9 @@ import { MathUtil } from "./MathUtil"; * Describes a 2D-vector. */ export class Vector2 implements IClone { - /** @internal zero.*/ + /** @internal */ static readonly _zero = new Vector2(0.0, 0.0); - /** @internal one.*/ + /** @internal */ static readonly _one = new Vector2(1.0, 1.0); /** diff --git a/packages/math/src/Vector3.ts b/packages/math/src/Vector3.ts index b444598a13..8afd777f68 100644 --- a/packages/math/src/Vector3.ts +++ b/packages/math/src/Vector3.ts @@ -8,12 +8,10 @@ import { Vector4 } from "./Vector4"; * Describes a 3D-vector. */ export class Vector3 implements IClone { - /** @internal zero.*/ + /** @internal */ static readonly _zero = new Vector3(0.0, 0.0, 0.0); - /** @internal one.*/ - static readonly _one = new Vector3(1.0, 1.0, 1.0); /** @internal */ - static readonly _tempVector3 = new Vector3(); + static readonly _one = new Vector3(1.0, 1.0, 1.0); /** * Determines the sum of two vectors. diff --git a/packages/math/src/Vector4.ts b/packages/math/src/Vector4.ts index 7fd6267017..60af696950 100644 --- a/packages/math/src/Vector4.ts +++ b/packages/math/src/Vector4.ts @@ -7,9 +7,9 @@ import { Quaternion } from "./Quaternion"; * Describes a 4D-vector. */ export class Vector4 implements IClone { - /** @internal zero.*/ + /** @internal */ static readonly _zero = new Vector4(0.0, 0.0, 0.0, 0.0); - /** @internal one.*/ + /** @internal */ static readonly _one = new Vector4(1.0, 1.0, 1.0, 1.0); /** diff --git a/packages/math/src/index.ts b/packages/math/src/index.ts index 1355e47613..85eb30f115 100755 --- a/packages/math/src/index.ts +++ b/packages/math/src/index.ts @@ -14,4 +14,4 @@ export { Vector3 } from "./Vector3"; export { Vector4 } from "./Vector4"; export { Plane } from "./Plane"; export { Color } from "./Color"; -export { Rect } from "./Rect"; \ No newline at end of file +export { Rect } from "./Rect"; diff --git a/packages/math/tsconfig.json b/packages/math/tsconfig.json index 1af1ad08d6..2c3585ef5f 100644 --- a/packages/math/tsconfig.json +++ b/packages/math/tsconfig.json @@ -10,6 +10,7 @@ "emitDeclarationOnly": true, "sourceMap": true, "incremental": false, + "skipLibCheck": true, "stripInternal": true }, "include": ["src/**/*"] diff --git a/packages/oasis-engine/src/index.ts b/packages/oasis-engine/src/index.ts index f7d6564073..5f9b8a0d5f 100644 --- a/packages/oasis-engine/src/index.ts +++ b/packages/oasis-engine/src/index.ts @@ -8,24 +8,22 @@ import { Camera, Component, DirectLight, - EnvironmentMapLight, ParticleRenderer, PointLight, - SkyBox, SphereCollider, - SpriteRenderer + SpriteRenderer, + SpriteMask, } from "@oasis-engine/core"; import { GLTFModel, Parser, Model } from "@oasis-engine/loader"; Parser.registerComponents("o3", { GLTFModel, SpriteRenderer, + SpriteMask, PointLight, AmbientLight, DirectLight, - EnvironmentMapLight, ParticleRenderer, - SkyBox, BoxCollider, Camera, Model, diff --git a/packages/oasis-engine/tsconfig.json b/packages/oasis-engine/tsconfig.json index fe0c58d146..1dc8cad306 100644 --- a/packages/oasis-engine/tsconfig.json +++ b/packages/oasis-engine/tsconfig.json @@ -9,6 +9,7 @@ "declarationDir": "types", "emitDeclarationOnly": true, "sourceMap": true, + "skipLibCheck": true, "incremental": false }, "include": [ diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index cfc9694114..567989b511 100755 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -4,6 +4,7 @@ "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", + "debug": "src/index.ts", "scripts": { "b:types": "tsc" }, diff --git a/packages/rhi-webgl/src/GLCapability.ts b/packages/rhi-webgl/src/GLCapability.ts index 3ed1dad6b4..1d67f922cb 100644 --- a/packages/rhi-webgl/src/GLCapability.ts +++ b/packages/rhi-webgl/src/GLCapability.ts @@ -17,7 +17,6 @@ export class GLCapability { /** * Whether can use more joints. - * @readonly */ get canIUseMoreJoints() { return ( diff --git a/packages/rhi-webgl/src/GLPrimitive.ts b/packages/rhi-webgl/src/GLPrimitive.ts index f03f2fb13b..82c00a35f7 100644 --- a/packages/rhi-webgl/src/GLPrimitive.ts +++ b/packages/rhi-webgl/src/GLPrimitive.ts @@ -130,10 +130,10 @@ export class GLPrimitive implements IPlatformPrimitive { } gl.enableVertexAttribArray(loc); - const { size, type } = element._glElementInfo; - gl.vertexAttribPointer(loc, size, type, element.normalized, stride, element.offset); + const { size, type, normalized } = element._glElementInfo; + gl.vertexAttribPointer(loc, size, type, normalized, stride, element.offset); if (this.canUseInstancedArrays) { - gl.vertexAttribDivisor(loc, element.instanceDivisor); + gl.vertexAttribDivisor(loc, element.instanceStepRate); } this.attribLocArray.push(loc); } else { diff --git a/packages/rhi-webgl/src/WebGLRenderer.ts b/packages/rhi-webgl/src/WebGLRenderer.ts index 320e82ae64..90e61e4105 100644 --- a/packages/rhi-webgl/src/WebGLRenderer.ts +++ b/packages/rhi-webgl/src/WebGLRenderer.ts @@ -1,7 +1,6 @@ import { Camera, Canvas, - ClearMode, ColorWriteMask, Engine, GLCapabilityType, @@ -20,8 +19,9 @@ import { Texture2D, TextureCubeMap } from "@oasis-engine/core"; +import { CameraClearFlags } from "@oasis-engine/core"; import { IPlatformPrimitive } from "@oasis-engine/design"; -import { Vector4 } from "@oasis-engine/math"; +import { Color } from "@oasis-engine/math"; import { GLCapability } from "./GLCapability"; import { GLExtensions } from "./GLExtensions"; import { GLPrimitive } from "./GLPrimitive"; @@ -78,7 +78,6 @@ export class WebGLRenderer implements IHardwareRenderer { /** * GL Context * @member {WebGLRenderingContext} - * @readonly */ get gl() { return this._gl; @@ -103,6 +102,7 @@ export class WebGLRenderer implements IHardwareRenderer { init(canvas: Canvas) { const option = this._options; option.alpha === undefined && (option.alpha = false); + option.stencil === undefined && (option.stencil = true); const webCanvas = (canvas as WebCanvas)._webCanvas; const webGLMode = option.webGLMode || WebGLMode.Auto; @@ -190,7 +190,11 @@ export class WebGLRenderer implements IHardwareRenderer { this._gl.colorMask(r, g, b, a); } - clearRenderTarget(engine: Engine, clearMode: ClearMode, clearParam: Vector4) { + clearRenderTarget( + engine: Engine, + clearFlags: CameraClearFlags.Depth | CameraClearFlags.DepthColor, + clearColor: Color + ) { const gl = this._gl; const { blendState: { targetBlendState }, @@ -198,45 +202,29 @@ export class WebGLRenderer implements IHardwareRenderer { stencilState } = engine._lastRenderState; - let clearFlag; - switch (clearMode) { - case ClearMode.SOLID_COLOR: - gl.clearColor(clearParam.x, clearParam.y, clearParam.z, clearParam.w); - clearFlag = gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT; - break; - case ClearMode.DEPTH_ONLY: - clearFlag = gl.DEPTH_BUFFER_BIT; - break; - case ClearMode.COLOR_ONLY: - gl.clearColor(clearParam.x, clearParam.y, clearParam.z, clearParam.w); - clearFlag = gl.COLOR_BUFFER_BIT; - break; - case ClearMode.STENCIL_ONLY: - gl.clear(gl.STENCIL_BUFFER_BIT); - clearFlag = gl.STENCIL_BUFFER_BIT; - break; - case ClearMode.ALL_CLEAR: - gl.clearColor(clearParam.x, clearParam.y, clearParam.z, clearParam.w); - clearFlag = gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT; - break; - } - - if (clearFlag & gl.COLOR_BUFFER_BIT && targetBlendState.colorWriteMask !== ColorWriteMask.All) { - gl.colorMask(true, true, true, true); - targetBlendState.colorWriteMask = ColorWriteMask.All; + let clearFlag = gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT; + if (clearFlags === CameraClearFlags.DepthColor) { + clearFlag = clearFlag | gl.COLOR_BUFFER_BIT; + if (clearColor) { + gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a); + } + if (targetBlendState.colorWriteMask !== ColorWriteMask.All) { + gl.colorMask(true, true, true, true); + targetBlendState.colorWriteMask = ColorWriteMask.All; + } } - if (clearFlag & gl.DEPTH_BUFFER_BIT && depthState.writeEnabled !== true) { + if (depthState.writeEnabled !== true) { gl.depthMask(true); depthState.writeEnabled = true; } - if (clearFlag & gl.STENCIL_BUFFER_BIT && stencilState.writeMask !== 0xff) { + if (stencilState.writeMask !== 0xff) { gl.stencilMask(0xff); stencilState.writeMask = 0xff; } - clearFlag && gl.clear(clearFlag); + gl.clear(clearFlag); } drawPrimitive(primitive: Mesh, subPrimitive: SubMesh, shaderProgram: any) { diff --git a/packages/rhi-webgl/tsconfig.json b/packages/rhi-webgl/tsconfig.json index f70fa535ae..91272a87bc 100644 --- a/packages/rhi-webgl/tsconfig.json +++ b/packages/rhi-webgl/tsconfig.json @@ -10,6 +10,7 @@ "emitDeclarationOnly": true, "sourceMap": true, "incremental": false, + "skipLibCheck": true, "stripInternal": true }, "include": [ diff --git a/packages/stats/tsconfig.json b/packages/stats/tsconfig.json index fe0c58d146..1dc8cad306 100644 --- a/packages/stats/tsconfig.json +++ b/packages/stats/tsconfig.json @@ -9,6 +9,7 @@ "declarationDir": "types", "emitDeclarationOnly": true, "sourceMap": true, + "skipLibCheck": true, "incremental": false }, "include": [ diff --git a/packages/tween/src/Tween.ts b/packages/tween/src/Tween.ts index 1acde2df33..5a6dd05996 100644 --- a/packages/tween/src/Tween.ts +++ b/packages/tween/src/Tween.ts @@ -16,7 +16,6 @@ class Tween { /** * Next id. * @member {number} - * @readonly */ getId() { return this._nextId++; diff --git a/packages/tween/src/plugins/FloatPlugin.ts b/packages/tween/src/plugins/FloatPlugin.ts index 51a7beaad5..8f0b37568d 100644 --- a/packages/tween/src/plugins/FloatPlugin.ts +++ b/packages/tween/src/plugins/FloatPlugin.ts @@ -1,4 +1,4 @@ -export const FloatPlugin = tweener => { +export const FloatPlugin = (tweener) => { const easing = tweener.options.easing; const result = easing( diff --git a/packages/tween/tsconfig.json b/packages/tween/tsconfig.json index fe0c58d146..1dc8cad306 100644 --- a/packages/tween/tsconfig.json +++ b/packages/tween/tsconfig.json @@ -9,6 +9,7 @@ "declarationDir": "types", "emitDeclarationOnly": true, "sourceMap": true, + "skipLibCheck": true, "incremental": false }, "include": [ diff --git a/rollup.config.js b/rollup.config.js index 2e77c22489..edd86d0c92 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -17,6 +17,7 @@ const { BUILD_TYPE, NODE_ENV } = process.env; const pkgsRoot = path.join(__dirname, "packages"); const pkgs = fs .readdirSync(pkgsRoot) + .filter((dir) => dir !== "design") .map((dir) => path.join(pkgsRoot, dir)) .filter((dir) => fs.statSync(dir).isDirectory()) .map((location) => { @@ -32,9 +33,10 @@ function toGlobalName(pkgName) { } const extensions = [".js", ".jsx", ".ts", ".tsx"]; +const mainFields = NODE_ENV === "development" ? ["debug", "module", "main"] : undefined; const commonPlugins = [ - resolve({ extensions, preferBuiltins: true }), + resolve({ extensions, preferBuiltins: true, mainFields }), glslify({ include: [/\.glsl$/] }), @@ -58,6 +60,7 @@ function config({ location, pkgJson }) { const name = pkgJson.name; commonPlugins.push( replace({ + preventAssignment: true, __buildVersion: pkgJson.version }) );