From 1637affc76892baec2e0c0c04227a5a461b097bc Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 8 Sep 2019 13:49:47 +0200 Subject: [PATCH 01/14] renderer: Abstract state apply logic. --- lib/game/imovable.ts | 4 +- lib/math/matrix3d.ts | 1 + lib/render/irender-api.ts | 31 +++++++++++++ lib/render/three-render-api.ts | 67 +++++++++++++++++++++++++++ lib/vpt/ball/ball-mover.spec.ts | 6 ++- lib/vpt/ball/ball.ts | 5 +- lib/vpt/flipper/flipper.ts | 5 +- lib/vpt/gate/gate.ts | 7 +-- lib/vpt/mesh.ts | 1 + lib/vpt/plunger/plunger-mover.spec.ts | 6 ++- lib/vpt/plunger/plunger.ts | 13 +++--- lib/vpt/spinner/spinner.ts | 7 +-- 12 files changed, 130 insertions(+), 23 deletions(-) create mode 100644 lib/render/irender-api.ts create mode 100644 lib/render/three-render-api.ts diff --git a/lib/game/imovable.ts b/lib/game/imovable.ts index abc41773..93229e62 100644 --- a/lib/game/imovable.ts +++ b/lib/game/imovable.ts @@ -17,9 +17,9 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Object3D } from 'three'; import { Table } from '..'; import { MoverObject } from '../physics/mover-object'; +import { IRenderApi } from '../render/irender-api'; import { ItemState } from '../vpt/item-state'; import { IPlayable } from './iplayable'; import { Player } from './player'; @@ -30,5 +30,5 @@ export interface IMovable extends IPlayable { getState(): STATE; - applyState(obj: Object3D, table: Table, player: Player, oldState: STATE): void; + applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player, oldState: STATE): void; } diff --git a/lib/math/matrix3d.ts b/lib/math/matrix3d.ts index 5a388a78..525182cc 100644 --- a/lib/math/matrix3d.ts +++ b/lib/math/matrix3d.ts @@ -149,6 +149,7 @@ export class Matrix3D { return this; } + /** @deprecated use {@link IRenderApi} */ public applyToObject3D(obj: Object3D) { if (!obj.matrix) { obj.matrix = new Matrix4(); diff --git a/lib/render/irender-api.ts b/lib/render/irender-api.ts new file mode 100644 index 00000000..eb060458 --- /dev/null +++ b/lib/render/irender-api.ts @@ -0,0 +1,31 @@ +/* + * VPDB - Virtual Pinball Database + * Copyright (C) 2019 freezy + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { Matrix3D } from '../math/matrix3d'; +import { Mesh } from '../vpt/mesh'; + +export interface IRenderApi { + + findInGroup(group: GROUP, name: string): OBJECT | undefined; + + applyMatrixToObject(matrix: Matrix3D, obj: OBJECT | undefined): void; + + applyMeshToObject(mesh: Mesh, obj: OBJECT | undefined): void; + +} diff --git a/lib/render/three-render-api.ts b/lib/render/three-render-api.ts new file mode 100644 index 00000000..ee8156a4 --- /dev/null +++ b/lib/render/three-render-api.ts @@ -0,0 +1,67 @@ +/* + * VPDB - Virtual Pinball Database + * Copyright (C) 2019 freezy + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { Group, Matrix4, Object3D } from 'three'; +import { Matrix3D } from '../math/matrix3d'; +import { Pool } from '../util/object-pool'; +import { Mesh } from '../vpt/mesh'; +import { IRenderApi } from './irender-api'; + +export class ThreeRenderApi implements IRenderApi { + + public findInGroup(group: Group, name: string): Object3D | undefined { + return group.children.find(c => c.name === name); + } + + public applyMatrixToObject(matrix: Matrix3D, obj: Object3D): void { + if (!obj) { + return; + } + if (!obj.matrix) { + obj.matrix = new Matrix4(); + } else { + obj.matrix.identity(); + } + const m4 = Pool.GENERIC.Matrix4.get(); + m4.set( + matrix._11, matrix._21, matrix._31, matrix._41, + matrix._12, matrix._22, matrix._32, matrix._42, + matrix._13, matrix._23, matrix._33, matrix._43, + matrix._14, matrix._24, matrix._34, matrix._44, + ); + obj.applyMatrix(m4); + Pool.GENERIC.Matrix4.release(m4); + } + + public applyMeshToObject(mesh: Mesh, obj: Object3D): void { + if (!obj) { + return; + } + const destGeo = (obj as any).geometry; + const srcGeo = mesh.getBufferGeometry(); + if (srcGeo.attributes.position.array.length !== destGeo.attributes.position.array.length) { + throw new Error(`Trying to apply geometry of ${srcGeo.attributes.position.array.length} positions to ${destGeo.attributes.position.array.length} positions.`); + } + for (let i = 0; i < destGeo.attributes.position.array.length; i++) { + destGeo.attributes.position.array[i] = srcGeo.attributes.position.array[i]; + } + destGeo.attributes.position.needsUpdate = true; + } + +} diff --git a/lib/vpt/ball/ball-mover.spec.ts b/lib/vpt/ball/ball-mover.spec.ts index 293d8406..8878261e 100644 --- a/lib/vpt/ball/ball-mover.spec.ts +++ b/lib/vpt/ball/ball-mover.spec.ts @@ -25,10 +25,12 @@ import { createBall } from '../../../test/physics.helper'; import { ThreeHelper } from '../../../test/three.helper'; import { Player } from '../../game/player'; import { NodeBinaryReader } from '../../io/binary-reader.node'; +import { ThreeRenderApi } from '../../render/three-render-api'; import { Table } from '../table/table'; chai.use(sinonChai); const three = new ThreeHelper(); +const renderApi = new ThreeRenderApi(); describe('The VPinball ball physics', () => { @@ -120,12 +122,12 @@ describe('The VPinball ball physics', () => { // position ball player.updatePhysics(0); - ball.applyState(ballObj, table, player); + ball.applyState(ballObj, renderApi, table, player); ballObj.getWorldPosition(startPos); // let ball roll some player.updatePhysics(100); - ball.applyState(ballObj, table, player); + ball.applyState(ballObj, renderApi, table, player); ballObj.getWorldPosition(endPos); expect(startPos.y).to.be.below(endPos.y); diff --git a/lib/vpt/ball/ball.ts b/lib/vpt/ball/ball.ts index 07bac9e8..d0d6902e 100644 --- a/lib/vpt/ball/ball.ts +++ b/lib/vpt/ball/ball.ts @@ -25,6 +25,7 @@ import { Player } from '../../game/player'; import { Matrix3D } from '../../math/matrix3d'; import { Vertex3D } from '../../math/vertex3d'; import { HitObject } from '../../physics/hit-object'; +import { IRenderApi } from '../../render/irender-api'; import { Meshes } from '../item-data'; import { Table } from '../table/table'; import { TableData } from '../table/table-data'; @@ -65,7 +66,7 @@ export class Ball implements IPlayable, IMovable, IRenderable { return `Ball${this.id}`; } - public applyState(obj: Object3D, table: Table, player: Player): void { + public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { const zHeight = !this.hit.isFrozen ? this.state.pos.z : this.state.pos.z - this.data.radius; const orientation = Matrix3D.claim().setEach( this.state.orientation.matrix[0][0], this.state.orientation.matrix[1][0], this.state.orientation.matrix[2][0], 0.0, @@ -80,7 +81,7 @@ export class Ball implements IPlayable, IMovable, IRenderable { .multiply(trans) .toRightHanded(); - matrix.applyToObject3D(obj); + renderApi.applyMatrixToObject(matrix, obj); Matrix3D.release(orientation, trans, matrix); } diff --git a/lib/vpt/flipper/flipper.ts b/lib/vpt/flipper/flipper.ts index 7f7a993a..0cba61a2 100644 --- a/lib/vpt/flipper/flipper.ts +++ b/lib/vpt/flipper/flipper.ts @@ -38,6 +38,7 @@ import { FlipperHit } from './flipper-hit'; import { FlipperMesh } from './flipper-mesh'; import { FlipperMover } from './flipper-mover'; import { FlipperState } from './flipper-state'; +import { IRenderApi } from '../../render/irender-api'; /** * VPinball's flippers @@ -125,7 +126,7 @@ export class Flipper implements IRenderable, IPlayable, IMovable, return meshes; } - public applyState(obj: Object3D, table: Table): void { + public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { const height = table.getSurfaceHeight(this.data.szSurface, this.data.center.x, this.data.center.y) * table.getScaleZ(); const matToOrigin = Matrix3D.claim().setTranslation(-this.data.center.x, -this.data.center.y, -height); @@ -133,7 +134,7 @@ export class Flipper implements IRenderable, IPlayable, IMovable, const matRotate = Matrix3D.claim().rotateZMatrix(this.state.angle - degToRad(this.data.startAngle)); const matrix = matToOrigin.multiply(matRotate).multiply(matFromOrigin); - matrix.applyToObject3D(obj); + renderApi.applyMatrixToObject(matrix, obj); Matrix3D.release(matToOrigin, matFromOrigin, matRotate); // matrix and matToOrigin are the same instance } diff --git a/lib/vpt/gate/gate.ts b/lib/vpt/gate/gate.ts index e095e08e..aa03c2c3 100644 --- a/lib/vpt/gate/gate.ts +++ b/lib/vpt/gate/gate.ts @@ -41,6 +41,7 @@ import { GateHitGenerator } from './gate-hit-generator'; import { GateMeshGenerator } from './gate-mesh-generator'; import { GateMover } from './gate-mover'; import { GateState } from './gate-state'; +import { IRenderApi } from '../../render/irender-api'; /** * VPinball's gates. @@ -141,7 +142,7 @@ export class Gate implements IRenderable, IPlayable, IMovable, IHitta } /* istanbul ignore next */ - public applyState(obj: Object3D, table: Table, player: Player, oldState: GateState): void { + public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { const posZ = this.data.height; const matTransToOrigin = Matrix3D.claim().setTranslation(-this.data.vCenter.x, -this.data.vCenter.y, posZ); const matRotateToOrigin = Matrix3D.claim().rotateZMatrix(degToRad(-this.data.rotation)); @@ -155,8 +156,8 @@ export class Gate implements IRenderable, IPlayable, IMovable, IHitta .multiply(matRotateFromOrigin) .multiply(matTransFromOrigin); - const wireObj = obj.children.find(c => c.name === `gate.wire-${this.data.getName()}`)!; - matrix.applyToObject3D(wireObj); + const wireObj = renderApi.findInGroup(obj, `gate.wire-${this.data.getName()}`); + renderApi.applyMatrixToObject(matrix, wireObj); Matrix3D.release(matTransToOrigin, matRotateToOrigin, matTransFromOrigin, matRotateFromOrigin, matRotateX); } diff --git a/lib/vpt/mesh.ts b/lib/vpt/mesh.ts index 48644aca..509500a7 100644 --- a/lib/vpt/mesh.ts +++ b/lib/vpt/mesh.ts @@ -118,6 +118,7 @@ export class Mesh { return this; } + /** @deprecated use {@link IRenderApi} */ public applyToObject(obj: Object3D) { const destGeo = (obj as any).geometry; const srcGeo = this.getBufferGeometry(); diff --git a/lib/vpt/plunger/plunger-mover.spec.ts b/lib/vpt/plunger/plunger-mover.spec.ts index e30bddad..4231343b 100644 --- a/lib/vpt/plunger/plunger-mover.spec.ts +++ b/lib/vpt/plunger/plunger-mover.spec.ts @@ -25,12 +25,14 @@ import { simulateCycles } from '../../../test/physics.helper'; import { ThreeHelper } from '../../../test/three.helper'; import { Player } from '../../game/player'; import { NodeBinaryReader } from '../../io/binary-reader.node'; +import { ThreeRenderApi } from '../../render/three-render-api'; import { Table } from '../table/table'; import { PlungerMover } from './plunger-mover'; import { PlungerState } from './plunger-state'; chai.use(sinonChai); const three = new ThreeHelper(); +const renderApi = new ThreeRenderApi(); describe('The VPinball plunger physics', () => { @@ -120,7 +122,7 @@ describe('The VPinball plunger physics', () => { const springObj = plungerObj.children.find(c => c.name === 'spring') as Mesh; // apply player state to plunger - plunger.applyState(plungerObj, table); + plunger.applyState(plungerObj, renderApi, table, player); rodObj.geometry.computeBoundingBox(); springObj.geometry.computeBoundingBox(); @@ -133,7 +135,7 @@ describe('The VPinball plunger physics', () => { simulateCycles(player, 50); // apply again - plunger.applyState(plungerObj, table); + plunger.applyState(plungerObj, renderApi, table, player); rodObj.geometry.computeBoundingBox(); springObj.geometry.computeBoundingBox(); diff --git a/lib/vpt/plunger/plunger.ts b/lib/vpt/plunger/plunger.ts index 6a2acdf1..77de877d 100644 --- a/lib/vpt/plunger/plunger.ts +++ b/lib/vpt/plunger/plunger.ts @@ -17,7 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Object3D } from 'three'; import { Storage, Table } from '../..'; import { EventProxy } from '../../game/event-proxy'; import { IHittable } from '../../game/ihittable'; @@ -29,6 +28,7 @@ import { IBallCreationPosition, Player } from '../../game/player'; import { PlayerPhysics } from '../../game/player-physics'; import { Vertex3D } from '../../math/vertex3d'; import { HitObject } from '../../physics/hit-object'; +import { IRenderApi } from '../../render/irender-api'; import { Ball } from '../ball/ball'; import { Meshes } from '../item-data'; import { VpTableExporterOptions } from '../table/table-exporter'; @@ -148,16 +148,15 @@ export class Plunger implements IRenderable, IPlayable, IMovable, } } - public applyState(obj: Object3D, table: Table): void { - + public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { const mesh = this.meshGenerator.generateMeshes(this.state.frame, table); - const rodObj = obj.children.find(o => o.name === 'rod') as any; + const rodObj = renderApi.findInGroup(obj, 'rod'); if (rodObj) { - mesh.rod!.applyToObject(rodObj); + renderApi.applyMeshToObject(mesh.rod!, rodObj); } - const springObj = obj.children.find(o => o.name === 'spring') as any; + const springObj = renderApi.findInGroup(obj, 'spring'); if (springObj) { - mesh.spring!.applyToObject(springObj); + renderApi.applyMeshToObject(mesh.spring!, springObj); } } diff --git a/lib/vpt/spinner/spinner.ts b/lib/vpt/spinner/spinner.ts index dacfa4dd..f37c6f1c 100644 --- a/lib/vpt/spinner/spinner.ts +++ b/lib/vpt/spinner/spinner.ts @@ -38,6 +38,7 @@ import { SpinnerHit } from './spinner-hit'; import { SpinnerHitGenerator } from './spinner-hit-generator'; import { SpinnerMeshGenerator } from './spinner-mesh-generator'; import { SpinnerState } from './spinner-state'; +import { IRenderApi } from '../../render/irender-api'; /** * VPinball's spinners. @@ -125,7 +126,7 @@ export class Spinner implements IRenderable, IPlayable, IMovable, } /* istanbul ignore next */ - public applyState(obj: Object3D, table: Table, player: Player): void { + public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { const posZ = this.meshGenerator.getZ(table); const matTransToOrigin = Matrix3D.claim().setTranslation(-this.data.vCenter.x, -this.data.vCenter.y, posZ); @@ -140,8 +141,8 @@ export class Spinner implements IRenderable, IPlayable, IMovable, .multiply(matRotateFromOrigin) .multiply(matTransFromOrigin); - const plateObj = obj.children.find(c => c.name === `spinner.plate-${this.getName()}`)!; - matrix.applyToObject3D(plateObj); + const plateObj = renderApi.findInGroup(obj, `spinner.plate-${this.getName()}`); + renderApi.applyMatrixToObject(matrix, plateObj!); Matrix3D.release(matTransToOrigin, matRotateToOrigin, matTransFromOrigin, matRotateFromOrigin, matRotateX); } From 04f792bc6f4a2e01d0195cd2a77a671783762bd1 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 8 Sep 2019 14:09:12 +0200 Subject: [PATCH 02/14] renderer: Abstract animation state updates. --- lib/game/ianimatable.ts | 3 ++- lib/vpt/bumper/bumper-hit.spec.ts | 6 ++++-- lib/vpt/bumper/bumper-mesh-updater.ts | 20 ++++++++++---------- lib/vpt/bumper/bumper.ts | 5 +++-- lib/vpt/hit-target/hit-target.ts | 6 +++--- lib/vpt/trigger/trigger.ts | 6 +++--- 6 files changed, 25 insertions(+), 21 deletions(-) diff --git a/lib/game/ianimatable.ts b/lib/game/ianimatable.ts index 49caa483..96e88046 100644 --- a/lib/game/ianimatable.ts +++ b/lib/game/ianimatable.ts @@ -22,6 +22,7 @@ import { Table } from '..'; import { IPlayable } from './iplayable'; import { Player } from './player'; import { PlayerPhysics } from './player-physics'; +import { IRenderApi } from '../render/irender-api'; /** * Animatables are like movables but their position is only updated @@ -37,7 +38,7 @@ export interface IAnimatable extends IPlayable { getState(): STATE; - applyState(obj: Object3D, table: Table, player: Player, oldState: STATE): void; + applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player, oldState: STATE): void; } export interface IAnimation { diff --git a/lib/vpt/bumper/bumper-hit.spec.ts b/lib/vpt/bumper/bumper-hit.spec.ts index 7b9270e0..0be9f263 100644 --- a/lib/vpt/bumper/bumper-hit.spec.ts +++ b/lib/vpt/bumper/bumper-hit.spec.ts @@ -25,11 +25,13 @@ import { createBall } from '../../../test/physics.helper'; import { ThreeHelper } from '../../../test/three.helper'; import { Player } from '../../game/player'; import { NodeBinaryReader } from '../../io/binary-reader.node'; +import { ThreeRenderApi } from '../../render/three-render-api'; import { Table } from '../table/table'; import { BumperState } from './bumper-state'; chai.use(sinonChai); const three = new ThreeHelper(); +const renderApi = new ThreeRenderApi(); describe('The VPinball bumper collision', () => { @@ -98,14 +100,14 @@ describe('The VPinball bumper collision', () => { player.updatePhysics(710); let states = player.popStates(); let state = states.getState('Bumper2'); - bumper.applyState(bumperObj, table, player, state.oldState as BumperState); + bumper.applyState(bumperObj, renderApi, table, player, state.oldState as BumperState); ringObj.getWorldPosition(ringPos); expect(ringPos.z).to.equal(16); player.updatePhysics(770); states = player.popStates(); state = states.getState('Bumper2'); - bumper.applyState(bumperObj, table, player, state.oldState as BumperState); + bumper.applyState(bumperObj, renderApi, table, player, state.oldState as BumperState); ringObj.getWorldPosition(ringPos); expect(ringPos.z).to.equal(61); }); diff --git a/lib/vpt/bumper/bumper-mesh-updater.ts b/lib/vpt/bumper/bumper-mesh-updater.ts index 418611a6..cd3c2946 100644 --- a/lib/vpt/bumper/bumper-mesh-updater.ts +++ b/lib/vpt/bumper/bumper-mesh-updater.ts @@ -17,10 +17,10 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Object3D } from 'three'; import { Player } from '../../game/player'; import { degToRad } from '../../math/float'; import { Matrix3D } from '../../math/matrix3d'; +import { IRenderApi } from '../../render/irender-api'; import { Table } from '../table/table'; import { BumperData } from './bumper-data'; import { BumperMeshGenerator } from './bumper-mesh-generator'; @@ -38,28 +38,28 @@ export class BumperMeshUpdater { this.meshGenerator = meshGenerator; } - public applyState(obj: Object3D, table: Table, player: Player, oldState: BumperState): void { + public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player, oldState: BumperState) { if (this.data.isRingVisible && this.state.ringOffset !== oldState.ringOffset) { - this.applyRingState(obj); + this.applyRingState(obj, renderApi); } if (this.data.isSkirtVisible && (this.state.skirtRotX !== oldState.skirtRotX || this.state.skirtRotY !== oldState.skirtRotY)) { - this.applySkirtState(obj, table); + this.applySkirtState(obj, renderApi, table); } } - private applyRingState(obj: Object3D) { - const ringObj = obj.children.find(o => o.name === `bumper-ring-${this.data.getName()}`) as Object3D; + private applyRingState(obj: OBJECT, renderApi: IRenderApi) { + const ringObj = renderApi.findInGroup(obj, `bumper-ring-${this.data.getName()}`); if (ringObj) { const matrix = Matrix3D.claim().setTranslation(0, 0, -this.state.ringOffset); - matrix.applyToObject3D(ringObj); + renderApi.applyMatrixToObject(matrix, ringObj); Matrix3D.release(matrix); } } /* istanbul ignore next: this looks weird. test when sure it's the correct "animation" */ - private applySkirtState(obj: Object3D, table: Table) { - const skirtObj = obj.children.find(o => o.name === `bumper-socket-${this.data.getName()}`) as any; + private applySkirtState(obj: OBJECT, renderApi: IRenderApi, table: Table) { + const skirtObj = renderApi.findInGroup(obj, `bumper-socket-${this.data.getName()}`); if (skirtObj) { const height = table.getSurfaceHeight(this.data.szSurface, this.data.vCenter.x, this.data.vCenter.y) * table.getScaleZ(); const matToOrigin = Matrix3D.claim().setTranslation(-this.data.vCenter.x, -this.data.vCenter.y, -height); @@ -69,7 +69,7 @@ export class BumperMeshUpdater { const matrix = matToOrigin.multiply(matRotY).multiply(matRotX).multiply(matFromOrigin); - matrix.applyToObject3D(skirtObj); + renderApi.applyMatrixToObject(matrix, skirtObj); Matrix3D.release(matToOrigin, matFromOrigin, matRotX, matRotY); } } diff --git a/lib/vpt/bumper/bumper.ts b/lib/vpt/bumper/bumper.ts index 342e21a8..d4ebbb05 100644 --- a/lib/vpt/bumper/bumper.ts +++ b/lib/vpt/bumper/bumper.ts @@ -34,6 +34,7 @@ import { BumperHit } from './bumper-hit'; import { BumperMeshGenerator } from './bumper-mesh-generator'; import { BumperMeshUpdater } from './bumper-mesh-updater'; import { BumperState } from './bumper-state'; +import { IRenderApi } from '../../render/irender-api'; /** * VPinball's bumper item. @@ -85,8 +86,8 @@ export class Bumper implements IRenderable, IHittable, IAnimatable this.hit = new BumperHit(this.data, this.state, this.animation, this.events, height); } - public applyState(obj: Object3D, table: Table, player: Player, oldState: BumperState): void { - this.meshUpdater.applyState(obj, table, player, oldState); + public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player, oldState: BumperState): void { + this.meshUpdater.applyState(obj, renderApi, table, player, oldState); } public getHitShapes(): HitObject[] { diff --git a/lib/vpt/hit-target/hit-target.ts b/lib/vpt/hit-target/hit-target.ts index 3fd11ad2..c1bcc427 100644 --- a/lib/vpt/hit-target/hit-target.ts +++ b/lib/vpt/hit-target/hit-target.ts @@ -17,7 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Object3D } from 'three'; import { EventProxy } from '../../game/event-proxy'; import { IAnimatable, IAnimation } from '../../game/ianimatable'; import { IHittable } from '../../game/ihittable'; @@ -29,6 +28,7 @@ import { Storage } from '../../io/ole-doc'; import { degToRad, f4 } from '../../math/float'; import { Matrix3D } from '../../math/matrix3d'; import { HitObject } from '../../physics/hit-object'; +import { IRenderApi } from '../../render/irender-api'; import { Ball } from '../ball/ball'; import { Meshes } from '../item-data'; import { Table } from '../table/table'; @@ -138,7 +138,7 @@ export class HitTarget implements IRenderable, IHittable, IAnimatable(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { const matTransToOrigin = Matrix3D.claim().setTranslation(-this.data.vPosition.x, -this.data.vPosition.y, -this.data.vPosition.z); const matRotateToOrigin = Matrix3D.claim().rotateZMatrix(degToRad(-this.data.rotZ)); const matTransFromOrigin = Matrix3D.claim().setTranslation(this.data.vPosition.x, this.data.vPosition.y, this.data.vPosition.z); @@ -152,7 +152,7 @@ export class HitTarget implements IRenderable, IHittable, IAnimatable(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { const matrix = Matrix3D.claim().setTranslation(0, 0, -this.state.heightOffset); - matrix.applyToObject3D(obj); + renderApi.applyMatrixToObject(matrix, obj); Matrix3D.release(matrix); } From a99f5138b6b2a6c195bfbf897493ad3056a71796 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 8 Sep 2019 14:11:16 +0200 Subject: [PATCH 03/14] chore: Clean up unused code. --- lib/math/matrix3d.ts | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/lib/math/matrix3d.ts b/lib/math/matrix3d.ts index 525182cc..ab67b58f 100644 --- a/lib/math/matrix3d.ts +++ b/lib/math/matrix3d.ts @@ -17,7 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Matrix4, Object3D } from 'three'; import { Pool } from '../util/object-pool'; import { f4, fr } from './float'; @@ -149,29 +148,6 @@ export class Matrix3D { return this; } - /** @deprecated use {@link IRenderApi} */ - public applyToObject3D(obj: Object3D) { - if (!obj.matrix) { - obj.matrix = new Matrix4(); - } else { - obj.matrix.identity(); - } - const m4 = Pool.GENERIC.Matrix4.get(); - this.applyToThreeMatrix4(m4); - obj.applyMatrix(m4); - Pool.GENERIC.Matrix4.release(m4); - } - - public applyToThreeMatrix4(matrix: Matrix4): Matrix4 { - matrix.set( - this._11, this._21, this._31, this._41, - this._12, this._22, this._32, this._42, - this._13, this._23, this._33, this._43, - this._14, this._24, this._34, this._44, - ); - return matrix; - } - private static multiplyMatrices(a: Matrix3D, b: Matrix3D, recycle = false): Matrix3D { /* istanbul ignore else: we always recycle now */ const result = recycle ? Matrix3D.claim() : new Matrix3D(); From 83d45625fa607f68e5d5317298b67cc9f85e2a1c Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 8 Sep 2019 15:54:56 +0200 Subject: [PATCH 04/14] renderer: Copy three mesh generation code to three renderer API. --- lib/render/irender-api.ts | 15 ++- lib/render/threejs/three-converter.ts | 132 +++++++++++++++++++ lib/render/{ => threejs}/three-render-api.ts | 37 +++++- lib/vpt/ball/ball-mover.spec.ts | 10 +- lib/vpt/ball/ball.ts | 24 ++-- lib/vpt/bumper/bumper-hit.spec.ts | 2 +- lib/vpt/plunger/plunger-mover.spec.ts | 2 +- lib/vpt/table/table.ts | 15 +++ lib/vpt/texture.ts | 11 +- 9 files changed, 221 insertions(+), 27 deletions(-) create mode 100644 lib/render/threejs/three-converter.ts rename lib/render/{ => threejs}/three-render-api.ts (66%) diff --git a/lib/render/irender-api.ts b/lib/render/irender-api.ts index eb060458..12fa00d3 100644 --- a/lib/render/irender-api.ts +++ b/lib/render/irender-api.ts @@ -17,15 +17,28 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { IRenderable } from '../game/irenderable'; import { Matrix3D } from '../math/matrix3d'; import { Mesh } from '../vpt/mesh'; +import { Table } from '../vpt/table/table'; export interface IRenderApi { - findInGroup(group: GROUP, name: string): OBJECT | undefined; + addToGroup(group: GROUP, obj: OBJECT | GROUP): void; + + findInGroup(group: GROUP, name: string): OBJECT | GROUP | undefined; + + removeFromGroup(group: GROUP, obj: OBJECT | GROUP | undefined): void; applyMatrixToObject(matrix: Matrix3D, obj: OBJECT | undefined): void; applyMeshToObject(mesh: Mesh, obj: OBJECT | undefined): void; + createObjectFromRenderable(renderable: IRenderable, table: Table): Promise; +} + +export interface MeshConvertOptions { + applyMaterials?: boolean; + applyTextures?: boolean; + optimizeTextures?: boolean; } diff --git a/lib/render/threejs/three-converter.ts b/lib/render/threejs/three-converter.ts new file mode 100644 index 00000000..8bc42e98 --- /dev/null +++ b/lib/render/threejs/three-converter.ts @@ -0,0 +1,132 @@ +/* + * VPDB - Virtual Pinball Database + * Copyright (C) 2019 freezy + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { + Color, + DoubleSide, + Group, + Mesh as ThreeMesh, + MeshStandardMaterial, + RGBAFormat, + RGBFormat, + Texture as ThreeTexture, +} from 'three'; +import { IRenderable, RenderInfo } from '../../game/irenderable'; +import { IImage } from '../../gltf/image'; +import { logger } from '../../util/logger'; +import { Table } from '../../vpt/table/table'; +import { Texture } from '../../vpt/texture'; +import { MeshConvertOptions } from '../irender-api'; + +export class ThreeConverter { + + private readonly opts: MeshConvertOptions; + + constructor(opts: MeshConvertOptions) { + this.opts = opts; + } + + public async createObject(renderable: IRenderable, table: Table): Promise { + const objects = renderable.getMeshes(table, this.opts); + const itemGroup = new Group(); + itemGroup.matrixAutoUpdate = false; + itemGroup.name = renderable.getName(); + for (const obj of Object.values(objects)) { + const mesh = await this.createMesh(renderable, obj, table); + itemGroup.add(mesh); + } + return itemGroup; + } + + private async createMesh(renderable: IRenderable, obj: RenderInfo, table: Table): Promise { + /* istanbul ignore if */ + if (!obj.geometry && !obj.mesh) { + throw new Error('Mesh export must either provide mesh or geometry.'); + } + const geometry = obj.geometry || obj.mesh!.getBufferGeometry(); + const material = await this.getMaterial(obj, table); + const postProcessedMaterial = renderable.postProcessMaterial ? renderable.postProcessMaterial(table, geometry, material) : material; + const mesh = new ThreeMesh(geometry, postProcessedMaterial); + mesh.name = (obj.geometry || obj.mesh!).name; + mesh.matrixAutoUpdate = false; + + return mesh; + } + + private async getMaterial(obj: RenderInfo, table: Table): Promise { + const material = new MeshStandardMaterial(); + const name = (obj.geometry || obj.mesh!).name; + material.name = `material:${name}`; + const materialInfo = obj.material; + if (materialInfo && this.opts.applyMaterials) { + material.metalness = materialInfo.bIsMetal ? 1.0 : 0.0; + material.roughness = Math.max(0, 1 - (materialInfo.fRoughness / 1.5)); + material.color = new Color(materialInfo.cBase); + material.opacity = materialInfo.bOpacityActive ? Math.min(1, Math.max(0, materialInfo.fOpacity)) : 1; + material.transparent = materialInfo.bOpacityActive && materialInfo.fOpacity < 0.98; + material.side = DoubleSide; + + if (materialInfo.emissiveIntensity > 0) { + material.emissive = new Color(materialInfo.emissiveColor); + material.emissiveIntensity = materialInfo.emissiveIntensity; + } + } + + if (this.opts.applyTextures) { + if (obj.map) { + material.map = new ThreeTexture(); + material.map.name = 'texture:' + obj.map.getName(); + if (await this.loadMap(name, obj.map, material.map, table)) { + if ((material.map.image as IImage).containsTransparency()) { + material.transparent = true; + } + material.needsUpdate = true; + } else { + logger().warn('[VpTableExporter.getMaterial] Error getting map.'); + material.map = null; + } + } + if (obj.normalMap) { + material.normalMap = new ThreeTexture(); + material.normalMap.name = 'normal-map:' + obj.normalMap.getName(); + if (await this.loadMap(name, obj.normalMap, material.normalMap, table)) { + material.normalMap.anisotropy = 16; + material.needsUpdate = true; + } else { + material.normalMap = null; + } + } + } + return material; + } + + private async loadMap(name: string, texture: Texture, threeMaterial: ThreeTexture, table: Table): Promise { + try { + const image = await texture.getImage(table); + threeMaterial.image = image; + threeMaterial.format = image.hasTransparency() ? RGBAFormat : RGBFormat; + threeMaterial.needsUpdate = true; + return true; + } catch (err) { + threeMaterial.image = ThreeTexture.DEFAULT_IMAGE; + logger().warn('[VpTableExporter.loadMap] Error loading map %s (%s/%s): %s', name, texture.storageName, texture.getName(), err.message); + return false; + } + } +} diff --git a/lib/render/three-render-api.ts b/lib/render/threejs/three-render-api.ts similarity index 66% rename from lib/render/three-render-api.ts rename to lib/render/threejs/three-render-api.ts index ee8156a4..249333cf 100644 --- a/lib/render/three-render-api.ts +++ b/lib/render/threejs/three-render-api.ts @@ -18,17 +18,43 @@ */ import { Group, Matrix4, Object3D } from 'three'; -import { Matrix3D } from '../math/matrix3d'; -import { Pool } from '../util/object-pool'; -import { Mesh } from '../vpt/mesh'; -import { IRenderApi } from './irender-api'; +import { IRenderable } from '../../game/irenderable'; +import { Matrix3D } from '../../math/matrix3d'; +import { Pool } from '../../util/object-pool'; +import { Mesh } from '../../vpt/mesh'; +import { Table } from '../../vpt/table/table'; +import { IRenderApi, MeshConvertOptions } from '../irender-api'; +import { ThreeConverter } from './three-converter'; export class ThreeRenderApi implements IRenderApi { + private readonly converter: ThreeConverter; + private readonly meshConvertOpts: MeshConvertOptions; + + constructor(opts?: MeshConvertOptions) { + this.meshConvertOpts = opts || { + applyMaterials: false, + applyTextures: false, + optimizeTextures: false, + }; + this.converter = new ThreeConverter(this.meshConvertOpts); + } + + public addToGroup(group: Group, obj: Object3D | Group): void { + group.add(obj); + } + public findInGroup(group: Group, name: string): Object3D | undefined { return group.children.find(c => c.name === name); } + public removeFromGroup(group: Group, obj: Object3D | Group): void { + if (!obj) { + return; + } + group.remove(obj); + } + public applyMatrixToObject(matrix: Matrix3D, obj: Object3D): void { if (!obj) { return; @@ -64,4 +90,7 @@ export class ThreeRenderApi implements IRenderApi { destGeo.attributes.position.needsUpdate = true; } + public async createObjectFromRenderable(renderable: IRenderable, table: Table): Promise { + return this.converter.createObject(renderable, table); + } } diff --git a/lib/vpt/ball/ball-mover.spec.ts b/lib/vpt/ball/ball-mover.spec.ts index 8878261e..77e0c40d 100644 --- a/lib/vpt/ball/ball-mover.spec.ts +++ b/lib/vpt/ball/ball-mover.spec.ts @@ -20,12 +20,12 @@ import * as chai from 'chai'; import { expect } from 'chai'; import sinonChai = require('sinon-chai'); -import { Mesh, Vector3 } from 'three'; +import { Group, Mesh, Vector3 } from 'three'; import { createBall } from '../../../test/physics.helper'; import { ThreeHelper } from '../../../test/three.helper'; import { Player } from '../../game/player'; import { NodeBinaryReader } from '../../io/binary-reader.node'; -import { ThreeRenderApi } from '../../render/three-render-api'; +import { ThreeRenderApi } from '../../render/threejs/three-render-api'; import { Table } from '../table/table'; chai.use(sinonChai); @@ -113,7 +113,7 @@ describe('The VPinball ball physics', () => { // add ball const ball = createBall(player, 500, 500, 0, 0, 10); - await ball.addToScene(gltf.scene, table); + await ball.addToScene(gltf.scene as any, renderApi, table); // fixme type const ballObj = three.find(gltf, 'balls', ball.getName()); // init vectors @@ -140,14 +140,14 @@ describe('The VPinball ball physics', () => { // add ball const ball = createBall(player, 500, 500, 0, 0, 10); - await ball.addToScene(gltf.scene, table); + await ball.addToScene(gltf.scene as any, renderApi, table); // fixme type // assert it's in the scene three.expectObject(gltf, 'balls', ball.getName()); // destroy ball player.destroyBall(ball); - ball.removeFromScene(gltf.scene); + ball.removeFromScene(gltf.scene as any, renderApi); // fixme type // assert it's gone three.expectNoObject(gltf, 'balls', ball.getName()); diff --git a/lib/vpt/ball/ball.ts b/lib/vpt/ball/ball.ts index d0d6902e..40535c19 100644 --- a/lib/vpt/ball/ball.ts +++ b/lib/vpt/ball/ball.ts @@ -17,7 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Group, Object3D, Scene } from 'three'; import { IMovable } from '../../game/imovable'; import { IPlayable } from '../../game/iplayable'; import { IRenderable } from '../../game/irenderable'; @@ -85,20 +84,19 @@ export class Ball implements IPlayable, IMovable, IRenderable { Matrix3D.release(orientation, trans, matrix); } - public async addToScene(scene: Scene, table: Table): Promise { - const mesh = await table.exportElement(this); - const playfield = scene.children.find(c => c.name === 'playfield')!; - const ballGroup = playfield.children.find(c => c.name === 'balls')!; - mesh.matrixAutoUpdate = false; - ballGroup.add(mesh); - return mesh; + public async addToScene(scene: GROUP, renderApi: IRenderApi, table: Table): Promise { + const ballMesh = await renderApi.createObjectFromRenderable(this, table); + const playfield = renderApi.findInGroup(scene, 'playfield') as GROUP; + const ballGroup = renderApi.findInGroup(playfield, 'balls') as GROUP; + renderApi.addToGroup(ballGroup, ballMesh); + return ballMesh; } - public removeFromScene(scene: Scene): void { - const playfield = scene.children.find(c => c.name === 'playfield')!; - const ballGroup = playfield.children.find(c => c.name === 'balls')!; - const ball = ballGroup.children.find(c => c.name === this.getName())!; - ballGroup.remove(ball); + public removeFromScene(scene: GROUP, renderApi: IRenderApi): void { + const playfield = renderApi.findInGroup(scene, 'playfield') as GROUP; + const ballGroup = renderApi.findInGroup(playfield, 'balls') as GROUP; + const ball = renderApi.findInGroup(ballGroup, this.getName()); + renderApi.removeFromGroup(ballGroup, ball); } public getState(): BallState { diff --git a/lib/vpt/bumper/bumper-hit.spec.ts b/lib/vpt/bumper/bumper-hit.spec.ts index 0be9f263..ed6c95bc 100644 --- a/lib/vpt/bumper/bumper-hit.spec.ts +++ b/lib/vpt/bumper/bumper-hit.spec.ts @@ -25,7 +25,7 @@ import { createBall } from '../../../test/physics.helper'; import { ThreeHelper } from '../../../test/three.helper'; import { Player } from '../../game/player'; import { NodeBinaryReader } from '../../io/binary-reader.node'; -import { ThreeRenderApi } from '../../render/three-render-api'; +import { ThreeRenderApi } from '../../render/threejs/three-render-api'; import { Table } from '../table/table'; import { BumperState } from './bumper-state'; diff --git a/lib/vpt/plunger/plunger-mover.spec.ts b/lib/vpt/plunger/plunger-mover.spec.ts index 4231343b..fe126687 100644 --- a/lib/vpt/plunger/plunger-mover.spec.ts +++ b/lib/vpt/plunger/plunger-mover.spec.ts @@ -25,7 +25,7 @@ import { simulateCycles } from '../../../test/physics.helper'; import { ThreeHelper } from '../../../test/three.helper'; import { Player } from '../../game/player'; import { NodeBinaryReader } from '../../io/binary-reader.node'; -import { ThreeRenderApi } from '../../render/three-render-api'; +import { ThreeRenderApi } from '../../render/threejs/three-render-api'; import { Table } from '../table/table'; import { PlungerMover } from './plunger-mover'; import { PlungerState } from './plunger-state'; diff --git a/lib/vpt/table/table.ts b/lib/vpt/table/table.ts index 594d0847..479a783d 100644 --- a/lib/vpt/table/table.ts +++ b/lib/vpt/table/table.ts @@ -57,6 +57,7 @@ import { TableExporter, VpTableExporterOptions } from './table-exporter'; import { TableHitGenerator } from './table-hit-generator'; import { LoadedTable, TableLoader } from './table-loader'; import { TableMeshGenerator } from './table-mesh-generator'; +import { IImage } from '../../gltf/image'; /** * A Visual Pinball table. @@ -71,6 +72,8 @@ export class Table implements IRenderable { public readonly items: IItem[]; public readonly tableScript?: string; + private readonly imageCache: Map = new Map(); + public readonly textures: { [key: string]: Texture } = {}; public readonly bumpers: { [key: string]: Bumper } = {}; @@ -356,6 +359,18 @@ export class Table implements IRenderable { hittable.getEventProxy().fireVoidEvent(Event.GameEventsInit); } } + + public getImageFromCache(name: string) { + return this.imageCache.get(name); + } + + public addImageToCache(name: string, image: IImage) { + this.imageCache.set(name, image); + } + + public clearImageCache() { + this.imageCache.clear(); + } } function isLoaded(items: any[] | undefined) { diff --git a/lib/vpt/texture.ts b/lib/vpt/texture.ts index 1d603dbc..f5d72ecc 100644 --- a/lib/vpt/texture.ts +++ b/lib/vpt/texture.ts @@ -89,16 +89,23 @@ export class Texture extends BiffParser { */ public async getImage(table: Table): Promise { + let image = table.getImageFromCache(this.getName()); + if (image) { + return image; + } + if (this.isRaw()) { - return await loadImage(this.getName(), getRawImage(this.pdsBuffer!.getData(), this.width, this.height)); + image = await loadImage(this.getName(), getRawImage(this.pdsBuffer!.getData(), this.width, this.height)); } else { const data = await table.streamStorage('GameStg', storage => streamImage(storage, this.storageName, this.binary, this.localPath)); if (!data || !data.length) { throw new Error(`Cannot load image data for texture ${this.getName()}`); } - return await loadImage(this.getName(), data); + image = await loadImage(this.getName(), data); } + table.addImageToCache(this.getName(), image); + return image; } public isRaw(): boolean { From b0ed4dbef1e5899880dd781a99b5b4f7ed128a5f Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 8 Sep 2019 23:37:27 +0200 Subject: [PATCH 05/14] renderer: Split export and scene generation and abstract most of it. --- bin/vpt2glb.ts | 9 +- bin/vptscript.ts | 2 +- lib/game/ianimatable.ts | 7 +- lib/game/imovable.ts | 4 +- lib/game/iplayable.ts | 2 +- lib/game/irenderable.ts | 11 +- lib/game/player-physics.ts | 2 +- lib/gltf/export-gltf.node.ts | 5 +- lib/gltf/gltf-exporter.ts | 8 +- lib/gltf/image.node.ts | 2 +- lib/index.ts | 6 +- lib/physics/hit-object.ts | 2 +- lib/render/irender-api.ts | 23 +- .../threejs/three-light-mesh-generator.ts | 88 ++++++ lib/render/threejs/three-render-api.ts | 35 +- lib/vpt/ball/ball-mover.spec.ts | 9 +- lib/vpt/ball/ball.ts | 20 +- lib/vpt/bumper/bumper-hit.spec.ts | 5 +- lib/vpt/bumper/bumper-mesh-updater.ts | 6 +- lib/vpt/bumper/bumper-mesh.spec.ts | 6 +- lib/vpt/bumper/bumper.ts | 11 +- lib/vpt/flipper/flipper-mesh.spec.ts | 6 +- lib/vpt/flipper/flipper.ts | 8 +- lib/vpt/gate/gate-mesh.spec.ts | 6 +- lib/vpt/gate/gate.ts | 8 +- lib/vpt/hit-target/hit-target-mesh.spec.ts | 6 +- lib/vpt/hit-target/hit-target.ts | 5 +- lib/vpt/item-data.ts | 7 +- lib/vpt/kicker/kicker-mesh.spec.ts | 6 +- lib/vpt/kicker/kicker.ts | 3 +- lib/vpt/light/light-data.ts | 3 +- lib/vpt/light/light-mesh.spec.ts | 6 +- lib/vpt/light/light.ts | 5 +- lib/vpt/plunger/plunger-mesh.spec.ts | 6 +- lib/vpt/plunger/plunger-mover.spec.ts | 5 +- lib/vpt/plunger/plunger.ts | 11 +- lib/vpt/primitive/primitive-mesh.spec.ts | 9 +- lib/vpt/primitive/primitive.ts | 3 +- lib/vpt/ramp/ramp-mesh.spec.ts | 7 +- lib/vpt/ramp/ramp.ts | 3 +- lib/vpt/rubber/rubber-mesh.spec.ts | 6 +- lib/vpt/rubber/rubber.ts | 3 +- lib/vpt/spinner/spinner-mesh.spec.ts | 6 +- lib/vpt/spinner/spinner.ts | 8 +- lib/vpt/surface/surface-mesh.spec.ts | 6 +- lib/vpt/surface/surface.ts | 6 +- lib/vpt/table/table-exporter.ts | 298 ++---------------- lib/vpt/table/table-mesh-generator.ts | 213 +++++++++---- lib/vpt/table/table.spec.ts | 30 +- lib/vpt/table/table.ts | 94 ++++-- lib/vpt/texture.ts | 2 +- lib/vpt/trigger/trigger-mesh.spec.ts | 6 +- lib/vpt/trigger/trigger.ts | 5 +- test/physics.helper.ts | 4 +- test/setup.ts | 2 +- test/vpt/invisible-items.spec.ts | 2 +- test/vpt/texture.spec.ts | 2 +- 57 files changed, 548 insertions(+), 521 deletions(-) create mode 100644 lib/render/threejs/three-light-mesh-generator.ts diff --git a/bin/vpt2glb.ts b/bin/vpt2glb.ts index 3825be6f..e3049d9d 100644 --- a/bin/vpt2glb.ts +++ b/bin/vpt2glb.ts @@ -20,8 +20,10 @@ import { existsSync, writeFileSync } from 'fs'; import { basename, dirname, resolve } from 'path'; -import { Logger, Table } from '../lib'; import { NodeBinaryReader } from '../lib/io/binary-reader.node'; +import { TableExporter } from '../lib/vpt/table/table-exporter'; +import { Logger } from '../lib/util/logger'; +import { Table } from '../lib/vpt/table/table'; (async () => { @@ -90,11 +92,12 @@ import { NodeBinaryReader } from '../lib/io/binary-reader.node'; console.log('Parsing file from %s...', vpxPath); - const vpt = await Table.load(new NodeBinaryReader(vpxPath)); + const table = await Table.load(new NodeBinaryReader(vpxPath)); + const exporter = new TableExporter(table); const loaded = Date.now(); console.log('Exporting file to %s...', glbPath); - const glb = await vpt.exportGlb({ + const glb = await exporter.exportGlb({ applyTextures, applyMaterials, diff --git a/bin/vptscript.ts b/bin/vptscript.ts index 2753e501..bafcf520 100644 --- a/bin/vptscript.ts +++ b/bin/vptscript.ts @@ -20,8 +20,8 @@ import { existsSync } from 'fs'; import { resolve } from 'path'; -import { Table } from '../lib'; import { NodeBinaryReader } from '../lib/io/binary-reader.node'; +import { Table } from '../lib/vpt/table/table'; (async () => { diff --git a/lib/game/ianimatable.ts b/lib/game/ianimatable.ts index 96e88046..968e83c9 100644 --- a/lib/game/ianimatable.ts +++ b/lib/game/ianimatable.ts @@ -17,12 +17,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Object3D } from 'three'; -import { Table } from '..'; +import { IRenderApi } from '../render/irender-api'; +import { Table } from '../vpt/table/table'; import { IPlayable } from './iplayable'; import { Player } from './player'; import { PlayerPhysics } from './player-physics'; -import { IRenderApi } from '../render/irender-api'; /** * Animatables are like movables but their position is only updated @@ -38,7 +37,7 @@ export interface IAnimatable extends IPlayable { getState(): STATE; - applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player, oldState: STATE): void; + applyState(obj: NODE, renderApi: IRenderApi, table: Table, player: Player, oldState: STATE): void; } export interface IAnimation { diff --git a/lib/game/imovable.ts b/lib/game/imovable.ts index 93229e62..f2553962 100644 --- a/lib/game/imovable.ts +++ b/lib/game/imovable.ts @@ -17,10 +17,10 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Table } from '..'; import { MoverObject } from '../physics/mover-object'; import { IRenderApi } from '../render/irender-api'; import { ItemState } from '../vpt/item-state'; +import { Table } from '../vpt/table/table'; import { IPlayable } from './iplayable'; import { Player } from './player'; @@ -30,5 +30,5 @@ export interface IMovable extends IPlayable { getState(): STATE; - applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player, oldState: STATE): void; + applyState(obj: NODE, renderApi: IRenderApi, table: Table, player: Player, oldState: STATE): void; } diff --git a/lib/game/iplayable.ts b/lib/game/iplayable.ts index 3dd148d0..cbbe03ea 100644 --- a/lib/game/iplayable.ts +++ b/lib/game/iplayable.ts @@ -17,7 +17,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Table } from '..'; +import { Table } from '../vpt/table/table'; import { IItem } from './iitem'; import { Player } from './player'; diff --git a/lib/game/irenderable.ts b/lib/game/irenderable.ts index 0173a808..7fc1596e 100644 --- a/lib/game/irenderable.ts +++ b/lib/game/irenderable.ts @@ -18,23 +18,26 @@ */ import { BufferGeometry, Material as ThreeMaterial, MeshStandardMaterial } from 'three'; -import { Table } from '..'; -import { Meshes } from '../vpt/item-data'; import { Material } from '../vpt/material'; import { Mesh } from '../vpt/mesh'; -import { VpTableExporterOptions } from '../vpt/table/table-exporter'; +import { Table } from '../vpt/table/table'; +import { TableExportOptions } from '../vpt/table/table-exporter'; import { Texture } from '../vpt/texture'; import { IItem } from './iitem'; export interface IRenderable extends IItem { - getMeshes(table: Table, opts: VpTableExporterOptions): Meshes; + getMeshes(table: Table, opts: TableExportOptions): Meshes; isVisible(table: Table): boolean; postProcessMaterial?(table: Table, geometry: BufferGeometry, material: MeshStandardMaterial): MeshStandardMaterial | MeshStandardMaterial[]; } +export interface Meshes { + [key: string]: RenderInfo; +} + export interface RenderInfo { mesh?: Mesh; geometry?: BufferGeometry; diff --git a/lib/game/player-physics.ts b/lib/game/player-physics.ts index a4e0f6eb..73c10ee4 100644 --- a/lib/game/player-physics.ts +++ b/lib/game/player-physics.ts @@ -17,7 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Table } from '..'; import { degToRad } from '../math/float'; import { Vertex3D } from '../math/vertex3d'; import { CollisionEvent } from '../physics/collision-event'; @@ -40,6 +39,7 @@ import { Ball } from '../vpt/ball/ball'; import { BallData } from '../vpt/ball/ball-data'; import { BallState } from '../vpt/ball/ball-state'; import { FlipperMover } from '../vpt/flipper/flipper-mover'; +import { Table } from '../vpt/table/table'; import { IBallCreationPosition } from './player'; const SLOW_MO = 1; // the lower, the slower diff --git a/lib/gltf/export-gltf.node.ts b/lib/gltf/export-gltf.node.ts index a1f69ae9..6a4e928e 100644 --- a/lib/gltf/export-gltf.node.ts +++ b/lib/gltf/export-gltf.node.ts @@ -1,8 +1,9 @@ import { Scene } from 'three'; -import { ParseOptions, VpTableExporterOptions } from '../vpt/table/table-exporter'; +import { TableGenerateGltfOptions } from '../vpt/table/table'; +import { TableExportOptions } from '../vpt/table/table-exporter'; import { GLTFExporter } from './gltf-exporter'; -export function exportGltf(scene: Scene, opts: VpTableExporterOptions, gltfOpts?: ParseOptions) { +export function exportGltf(scene: Scene, opts: TableExportOptions, gltfOpts?: TableGenerateGltfOptions) { const gltfExporter = new GLTFExporter(Object.assign({}, { embedImages: true, optimizeImages: opts.optimizeTextures }, gltfOpts)); return gltfExporter.parse(scene); } diff --git a/lib/gltf/gltf-exporter.ts b/lib/gltf/gltf-exporter.ts index 0d8fcb7a..3bf671cf 100644 --- a/lib/gltf/gltf-exporter.ts +++ b/lib/gltf/gltf-exporter.ts @@ -54,7 +54,7 @@ import { Vector3, } from 'three'; import { logger } from '../util/logger'; -import { ParseOptions } from '../vpt/table/table-exporter'; +import { TableGenerateGltfOptions } from '../vpt/table/table'; import { GltfAnimationSampler, GltfBufferView, @@ -141,7 +141,7 @@ const PATH_PROPERTIES: { [key: string]: string } = { export class GLTFExporter { private started = false; - private options: ParseOptions; + private options: TableGenerateGltfOptions; private byteOffset: number = 0; private buffers: Buffer[] = []; private pending: Array<() => Promise> = []; @@ -162,8 +162,8 @@ export class GLTFExporter { }, }; - constructor(options?: ParseOptions) { - const DEFAULT_OPTIONS: ParseOptions = { + constructor(options?: TableGenerateGltfOptions) { + const DEFAULT_OPTIONS: TableGenerateGltfOptions = { binary: false, optimizeImages: false, trs: false, diff --git a/lib/gltf/image.node.ts b/lib/gltf/image.node.ts index 8622ba50..bb5f3ff4 100644 --- a/lib/gltf/image.node.ts +++ b/lib/gltf/image.node.ts @@ -23,7 +23,7 @@ import { State } from 'gm'; import * as sharp from 'sharp'; import { Stream } from 'stream'; -import { Storage } from '..'; +import { Storage } from '../io/ole-doc'; import { logger } from '../util/logger'; import { Binary } from '../vpt/binary'; import { IImage } from './image'; diff --git a/lib/index.ts b/lib/index.ts index 6bac2f92..c1d23369 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -17,6 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -export { Table } from './vpt/table/table'; -export { OleCompoundDoc, Storage } from './io/ole-doc'; -export { Logger } from './util/logger'; +// export { Table } from './vpt/table/table'; +// export { OleCompoundDoc, Storage } from './io/ole-doc'; +// export { Logger } from './util/logger'; diff --git a/lib/physics/hit-object.ts b/lib/physics/hit-object.ts index 4f1150ed..31efeb2b 100644 --- a/lib/physics/hit-object.ts +++ b/lib/physics/hit-object.ts @@ -17,7 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Table } from '..'; import { Event } from '../game/event'; import { EventProxy } from '../game/event-proxy'; import { PlayerPhysics } from '../game/player-physics'; @@ -26,6 +25,7 @@ import { FRect3D } from '../math/frect3d'; import { Vertex3D } from '../math/vertex3d'; import { Ball } from '../vpt/ball/ball'; import { IPhysicalData } from '../vpt/item-data'; +import { Table } from '../vpt/table/table'; import { CollisionEvent } from './collision-event'; import { CollisionType } from './collision-type'; diff --git a/lib/render/irender-api.ts b/lib/render/irender-api.ts index 12fa00d3..664c53f5 100644 --- a/lib/render/irender-api.ts +++ b/lib/render/irender-api.ts @@ -19,22 +19,31 @@ import { IRenderable } from '../game/irenderable'; import { Matrix3D } from '../math/matrix3d'; +import { LightData } from '../vpt/light/light-data'; import { Mesh } from '../vpt/mesh'; import { Table } from '../vpt/table/table'; -export interface IRenderApi { +export interface IRenderApi { - addToGroup(group: GROUP, obj: OBJECT | GROUP): void; + transformScene(scene: NODE, table: Table): void; - findInGroup(group: GROUP, name: string): OBJECT | GROUP | undefined; + createGroup(name: string): NODE; - removeFromGroup(group: GROUP, obj: OBJECT | GROUP | undefined): void; + addToGroup(group: NODE, obj: NODE | POINT_LIGHT): void; - applyMatrixToObject(matrix: Matrix3D, obj: OBJECT | undefined): void; + findInGroup(group: NODE, name: string): NODE | undefined; - applyMeshToObject(mesh: Mesh, obj: OBJECT | undefined): void; + removeFromGroup(group: NODE, obj: NODE | undefined): void; - createObjectFromRenderable(renderable: IRenderable, table: Table): Promise; + applyMatrixToObject(matrix: Matrix3D, obj: NODE | undefined): void; + + applyMeshToObject(mesh: Mesh, obj: NODE | undefined): void; + + createObjectFromRenderable(renderable: IRenderable, table: Table): Promise; + + createLightGeometry(lightData: LightData, table: Table): GEOMETRY; + + createPointLight(lightData: LightData): POINT_LIGHT; } export interface MeshConvertOptions { diff --git a/lib/render/threejs/three-light-mesh-generator.ts b/lib/render/threejs/three-light-mesh-generator.ts new file mode 100644 index 00000000..af9ddefb --- /dev/null +++ b/lib/render/threejs/three-light-mesh-generator.ts @@ -0,0 +1,88 @@ +/* + * VPDB - Virtual Pinball Database + * Copyright (C) 2019 freezy + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { ExtrudeBufferGeometry, Path, Shape, Vector2 } from 'three'; +import { SplineVertex } from '../../math/spline-vertex'; +import { LightData } from '../../vpt/light/light-data'; +import { Table } from '../../vpt/table/table'; + +export class ThreeLightMeshGenerator { + + public createLight(lightData: LightData, table: Table, depth = 5, bevel = 0.5): ExtrudeBufferGeometry { + const shape = this.getShape(lightData, table); + const dim = table.getDimensions(); + const invTableWidth = 1.0 / dim.width; + const invTableHeight = 1.0 / dim.height; + + const geometry = new ExtrudeBufferGeometry(shape, { + depth, + bevelEnabled: bevel > 0, + bevelSegments: 1, + steps: 1, + bevelSize: bevel, + bevelThickness: bevel, + UVGenerator: { + generateSideWallUV(g: ExtrudeBufferGeometry, vertices: number[], indexA: number, indexB: number, indexC: number, indexD: number): Vector2[] { + return [ + new Vector2( 0, 0), + new Vector2( 0, 0), + new Vector2( 0, 0), + new Vector2( 0, 0), + ]; + }, + generateTopUV(g: ExtrudeBufferGeometry, vertices: number[], indexA: number, indexB: number, indexC: number): Vector2[] { + const ax = vertices[indexA * 3]; + const ay = vertices[indexA * 3 + 1]; + const bx = vertices[indexB * 3]; + const by = vertices[indexB * 3 + 1]; + const cx = vertices[indexC * 3]; + const cy = vertices[indexC * 3 + 1]; + return [ + new Vector2(ax * invTableWidth, 1 - ay * invTableHeight), + new Vector2(bx * invTableWidth, 1 - by * invTableHeight), + new Vector2(cx * invTableWidth, 1 - cy * invTableHeight), + ]; + }, + }, + }); + if (lightData.szSurface) { + geometry.translate(0, 0, -table.getSurfaceHeight(lightData.szSurface, 0, 0)); + } + geometry.name = `surface.light-${lightData.getName()}`; + return geometry; + } + + public getShape(lightData: LightData, table: Table): Shape { + const vVertex = SplineVertex.getCentralCurve(lightData.dragPoints, table.getDetailLevel(), -1); + return this.getPathFromPoints(vVertex.map(v => new Vector2(v.x, v.y)), new Shape()); + } + + private getPathFromPoints(points: Vector2[], path: T): T { + /* istanbul ignore if */ + if (points.length === 0) { + throw new Error('Cannot get path from no points.'); + } + path.moveTo(points[0].x, points[0].y); + for (const v of points.slice(1)) { + path.lineTo(v.x, v.y); + } + //path.moveTo(points[0].x, points[0].y); + return path; + } +} diff --git a/lib/render/threejs/three-render-api.ts b/lib/render/threejs/three-render-api.ts index 249333cf..1027ac78 100644 --- a/lib/render/threejs/three-render-api.ts +++ b/lib/render/threejs/three-render-api.ts @@ -17,19 +17,24 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Group, Matrix4, Object3D } from 'three'; +import { BufferGeometry, Group, Matrix4, Object3D, PointLight } from 'three'; import { IRenderable } from '../../game/irenderable'; import { Matrix3D } from '../../math/matrix3d'; import { Pool } from '../../util/object-pool'; +import { LightData } from '../../vpt/light/light-data'; import { Mesh } from '../../vpt/mesh'; import { Table } from '../../vpt/table/table'; import { IRenderApi, MeshConvertOptions } from '../irender-api'; import { ThreeConverter } from './three-converter'; +import { ThreeLightMeshGenerator } from './three-light-mesh-generator'; -export class ThreeRenderApi implements IRenderApi { +export class ThreeRenderApi implements IRenderApi { + + private static readonly SCALE = 0.05; private readonly converter: ThreeConverter; private readonly meshConvertOpts: MeshConvertOptions; + private readonly lightGenerator: ThreeLightMeshGenerator; constructor(opts?: MeshConvertOptions) { this.meshConvertOpts = opts || { @@ -38,6 +43,28 @@ export class ThreeRenderApi implements IRenderApi { optimizeTextures: false, }; this.converter = new ThreeConverter(this.meshConvertOpts); + this.lightGenerator = new ThreeLightMeshGenerator(); + } + + public transformScene(scene: Group, table: Table): void { + const dim = table.getDimensions(); + scene.rotateX(Math.PI / 2); + scene.translateY(-dim.height * ThreeRenderApi.SCALE / 2); + scene.translateX(-dim.width * ThreeRenderApi.SCALE / 2); + scene.scale.set(ThreeRenderApi.SCALE, ThreeRenderApi.SCALE, ThreeRenderApi.SCALE); + } + + public createGroup(name: string): Group { + const group = new Group(); + group.name = name; + return group; + } + + public createPointLight(lightData: LightData): PointLight { + const light = new PointLight(lightData.color, lightData.intensity, lightData.falloff * ThreeRenderApi.SCALE, 2); + light.name = 'light:' + lightData.getName(); + light.position.set(lightData.vCenter.x, lightData.vCenter.y, -17); + return light; } public addToGroup(group: Group, obj: Object3D | Group): void { @@ -93,4 +120,8 @@ export class ThreeRenderApi implements IRenderApi { public async createObjectFromRenderable(renderable: IRenderable, table: Table): Promise { return this.converter.createObject(renderable, table); } + + public createLightGeometry(lightData: LightData, table: Table): BufferGeometry { + return this.lightGenerator.createLight(lightData, table); + } } diff --git a/lib/vpt/ball/ball-mover.spec.ts b/lib/vpt/ball/ball-mover.spec.ts index 77e0c40d..9fc4a78b 100644 --- a/lib/vpt/ball/ball-mover.spec.ts +++ b/lib/vpt/ball/ball-mover.spec.ts @@ -20,13 +20,14 @@ import * as chai from 'chai'; import { expect } from 'chai'; import sinonChai = require('sinon-chai'); -import { Group, Mesh, Vector3 } from 'three'; +import { Mesh, Vector3 } from 'three'; import { createBall } from '../../../test/physics.helper'; import { ThreeHelper } from '../../../test/three.helper'; import { Player } from '../../game/player'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { ThreeRenderApi } from '../../render/threejs/three-render-api'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; chai.use(sinonChai); const three = new ThreeHelper(); @@ -35,10 +36,12 @@ const renderApi = new ThreeRenderApi(); describe('The VPinball ball physics', () => { let table: Table; + let exporter: TableExporter; let player: Player; before(async () => { table = await Table.load(new NodeBinaryReader(three.fixturePath('table-empty.vpx'))); + exporter = new TableExporter(table); }); beforeEach(() => { @@ -109,7 +112,7 @@ describe('The VPinball ball physics', () => { it('should apply the mesh transformation', async () => { // create scene - const gltf = await three.loadGlb(await table.exportGlb()); + const gltf = await three.loadGlb(await exporter.exportGlb()); // add ball const ball = createBall(player, 500, 500, 0, 0, 10); @@ -136,7 +139,7 @@ describe('The VPinball ball physics', () => { it('should remove a ball from the scene', async () => { // create scene - const gltf = await three.loadGlb(await table.exportGlb()); + const gltf = await three.loadGlb(await exporter.exportGlb()); // add ball const ball = createBall(player, 500, 500, 0, 0, 10); diff --git a/lib/vpt/ball/ball.ts b/lib/vpt/ball/ball.ts index 40535c19..55039bed 100644 --- a/lib/vpt/ball/ball.ts +++ b/lib/vpt/ball/ball.ts @@ -19,16 +19,14 @@ import { IMovable } from '../../game/imovable'; import { IPlayable } from '../../game/iplayable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { Player } from '../../game/player'; import { Matrix3D } from '../../math/matrix3d'; import { Vertex3D } from '../../math/vertex3d'; import { HitObject } from '../../physics/hit-object'; import { IRenderApi } from '../../render/irender-api'; -import { Meshes } from '../item-data'; import { Table } from '../table/table'; import { TableData } from '../table/table-data'; -import { VpTableExporterOptions } from '../table/table-exporter'; import { BallData } from './ball-data'; import { BallHit } from './ball-hit'; import { BallMeshGenerator } from './ball-mesh-generator'; @@ -65,7 +63,7 @@ export class Ball implements IPlayable, IMovable, IRenderable { return `Ball${this.id}`; } - public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { + public applyState(obj: NODE, renderApi: IRenderApi, table: Table, player: Player): void { const zHeight = !this.hit.isFrozen ? this.state.pos.z : this.state.pos.z - this.data.radius; const orientation = Matrix3D.claim().setEach( this.state.orientation.matrix[0][0], this.state.orientation.matrix[1][0], this.state.orientation.matrix[2][0], 0.0, @@ -84,17 +82,17 @@ export class Ball implements IPlayable, IMovable, IRenderable { Matrix3D.release(orientation, trans, matrix); } - public async addToScene(scene: GROUP, renderApi: IRenderApi, table: Table): Promise { + public async addToScene(scene: NODE, renderApi: IRenderApi, table: Table): Promise { const ballMesh = await renderApi.createObjectFromRenderable(this, table); - const playfield = renderApi.findInGroup(scene, 'playfield') as GROUP; - const ballGroup = renderApi.findInGroup(playfield, 'balls') as GROUP; + const playfield = renderApi.findInGroup(scene, 'playfield')!; + const ballGroup = renderApi.findInGroup(playfield, 'balls')!; renderApi.addToGroup(ballGroup, ballMesh); return ballMesh; } - public removeFromScene(scene: GROUP, renderApi: IRenderApi): void { - const playfield = renderApi.findInGroup(scene, 'playfield') as GROUP; - const ballGroup = renderApi.findInGroup(playfield, 'balls') as GROUP; + public removeFromScene(scene: NODE, renderApi: IRenderApi): void { + const playfield = renderApi.findInGroup(scene, 'playfield')!; + const ballGroup = renderApi.findInGroup(playfield, 'balls')!; const ball = renderApi.findInGroup(ballGroup, this.getName()); renderApi.removeFromGroup(ballGroup, ball); } @@ -117,7 +115,7 @@ export class Ball implements IPlayable, IMovable, IRenderable { return [ this.hit ]; } - public getMeshes(table: Table, opts: VpTableExporterOptions): Meshes { + public getMeshes(table: Table): Meshes { return { ball: { mesh: this.meshGenerator.getMesh().transform(Matrix3D.RIGHT_HANDED) } }; } diff --git a/lib/vpt/bumper/bumper-hit.spec.ts b/lib/vpt/bumper/bumper-hit.spec.ts index ed6c95bc..abf5a955 100644 --- a/lib/vpt/bumper/bumper-hit.spec.ts +++ b/lib/vpt/bumper/bumper-hit.spec.ts @@ -27,6 +27,7 @@ import { Player } from '../../game/player'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { ThreeRenderApi } from '../../render/threejs/three-render-api'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; import { BumperState } from './bumper-state'; chai.use(sinonChai); @@ -36,10 +37,12 @@ const renderApi = new ThreeRenderApi(); describe('The VPinball bumper collision', () => { let table: Table; + let exporter: TableExporter; let player: Player; beforeEach(async () => { table = await Table.load(new NodeBinaryReader(three.fixturePath('table-bumper.vpx'))); + exporter = new TableExporter(table); player = new Player(table).init(); }); @@ -88,7 +91,7 @@ describe('The VPinball bumper collision', () => { it('should apply the ring transformation to the object', async () => { // create scene - const gltf = await three.loadGlb(await table.exportGlb()); + const gltf = await three.loadGlb(await exporter.exportGlb()); // add ball createBall(player, 450, 750, 50, 0, 1); diff --git a/lib/vpt/bumper/bumper-mesh-updater.ts b/lib/vpt/bumper/bumper-mesh-updater.ts index cd3c2946..90702c7b 100644 --- a/lib/vpt/bumper/bumper-mesh-updater.ts +++ b/lib/vpt/bumper/bumper-mesh-updater.ts @@ -38,7 +38,7 @@ export class BumperMeshUpdater { this.meshGenerator = meshGenerator; } - public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player, oldState: BumperState) { + public applyState(obj: NODE, renderApi: IRenderApi, table: Table, player: Player, oldState: BumperState) { if (this.data.isRingVisible && this.state.ringOffset !== oldState.ringOffset) { this.applyRingState(obj, renderApi); @@ -48,7 +48,7 @@ export class BumperMeshUpdater { } } - private applyRingState(obj: OBJECT, renderApi: IRenderApi) { + private applyRingState(obj: NODE, renderApi: IRenderApi) { const ringObj = renderApi.findInGroup(obj, `bumper-ring-${this.data.getName()}`); if (ringObj) { const matrix = Matrix3D.claim().setTranslation(0, 0, -this.state.ringOffset); @@ -58,7 +58,7 @@ export class BumperMeshUpdater { } /* istanbul ignore next: this looks weird. test when sure it's the correct "animation" */ - private applySkirtState(obj: OBJECT, renderApi: IRenderApi, table: Table) { + private applySkirtState(obj: NODE, renderApi: IRenderApi, table: Table) { const skirtObj = renderApi.findInGroup(obj, `bumper-socket-${this.data.getName()}`); if (skirtObj) { const height = table.getSurfaceHeight(this.data.szSurface, this.data.vCenter.x, this.data.vCenter.y) * table.getScaleZ(); diff --git a/lib/vpt/bumper/bumper-mesh.spec.ts b/lib/vpt/bumper/bumper-mesh.spec.ts index 76437047..ee4bee3a 100644 --- a/lib/vpt/bumper/bumper-mesh.spec.ts +++ b/lib/vpt/bumper/bumper-mesh.spec.ts @@ -22,6 +22,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -30,8 +31,9 @@ describe('The VPinball bumper generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-bumper.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb()); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-bumper.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb()); }); it('should generate a scaled and rotated bumper mesh', async () => { diff --git a/lib/vpt/bumper/bumper.ts b/lib/vpt/bumper/bumper.ts index d4ebbb05..cafe7348 100644 --- a/lib/vpt/bumper/bumper.ts +++ b/lib/vpt/bumper/bumper.ts @@ -17,16 +17,16 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Object3D } from 'three'; -import { Storage, Table } from '../..'; import { EventProxy } from '../../game/event-proxy'; import { IAnimatable } from '../../game/ianimatable'; import { IHittable } from '../../game/ihittable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { Player } from '../../game/player'; +import { Storage } from '../../io/ole-doc'; import { Matrix3D } from '../../math/matrix3d'; import { HitObject } from '../../physics/hit-object'; -import { Meshes } from '../item-data'; +import { IRenderApi } from '../../render/irender-api'; +import { Table } from '../table/table'; import { Texture } from '../texture'; import { BumperAnimation } from './bumper-animation'; import { BumperData } from './bumper-data'; @@ -34,7 +34,6 @@ import { BumperHit } from './bumper-hit'; import { BumperMeshGenerator } from './bumper-mesh-generator'; import { BumperMeshUpdater } from './bumper-mesh-updater'; import { BumperState } from './bumper-state'; -import { IRenderApi } from '../../render/irender-api'; /** * VPinball's bumper item. @@ -86,7 +85,7 @@ export class Bumper implements IRenderable, IHittable, IAnimatable this.hit = new BumperHit(this.data, this.state, this.animation, this.events, height); } - public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player, oldState: BumperState): void { + public applyState(obj: NODE, renderApi: IRenderApi, table: Table, player: Player, oldState: BumperState): void { this.meshUpdater.applyState(obj, renderApi, table, player, oldState); } diff --git a/lib/vpt/flipper/flipper-mesh.spec.ts b/lib/vpt/flipper/flipper-mesh.spec.ts index 2628464d..050da9e1 100644 --- a/lib/vpt/flipper/flipper-mesh.spec.ts +++ b/lib/vpt/flipper/flipper-mesh.spec.ts @@ -22,6 +22,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -30,8 +31,9 @@ describe('The VPinball flipper generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-flipper.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb()); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-flipper.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb()); }); it('should generate a default flipper mesh', async () => { diff --git a/lib/vpt/flipper/flipper.ts b/lib/vpt/flipper/flipper.ts index 0cba61a2..3300eff8 100644 --- a/lib/vpt/flipper/flipper.ts +++ b/lib/vpt/flipper/flipper.ts @@ -17,12 +17,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Object3D } from 'three'; import { EventProxy } from '../../game/event-proxy'; import { IHittable } from '../../game/ihittable'; import { IMovable } from '../../game/imovable'; import { IPlayable } from '../../game/iplayable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { IScriptable } from '../../game/iscriptable'; import { Player } from '../../game/player'; import { Storage } from '../../io/ole-doc'; @@ -30,7 +29,7 @@ import { degToRad } from '../../math/float'; import { Matrix3D } from '../../math/matrix3d'; import { Vertex2D } from '../../math/vertex2d'; import { HitObject } from '../../physics/hit-object'; -import { Meshes } from '../item-data'; +import { IRenderApi } from '../../render/irender-api'; import { Table } from '../table/table'; import { FlipperApi } from './flipper-api'; import { FlipperData } from './flipper-data'; @@ -38,7 +37,6 @@ import { FlipperHit } from './flipper-hit'; import { FlipperMesh } from './flipper-mesh'; import { FlipperMover } from './flipper-mover'; import { FlipperState } from './flipper-state'; -import { IRenderApi } from '../../render/irender-api'; /** * VPinball's flippers @@ -126,7 +124,7 @@ export class Flipper implements IRenderable, IPlayable, IMovable, return meshes; } - public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { + public applyState(obj: NODE, renderApi: IRenderApi, table: Table, player: Player): void { const height = table.getSurfaceHeight(this.data.szSurface, this.data.center.x, this.data.center.y) * table.getScaleZ(); const matToOrigin = Matrix3D.claim().setTranslation(-this.data.center.x, -this.data.center.y, -height); diff --git a/lib/vpt/gate/gate-mesh.spec.ts b/lib/vpt/gate/gate-mesh.spec.ts index e4ab90cf..ffea43c9 100644 --- a/lib/vpt/gate/gate-mesh.spec.ts +++ b/lib/vpt/gate/gate-mesh.spec.ts @@ -22,6 +22,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -30,8 +31,9 @@ describe('The VPinball gate generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-gate.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb()); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-gate.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb()); }); it('should generate a long gate mesh', async () => { diff --git a/lib/vpt/gate/gate.ts b/lib/vpt/gate/gate.ts index aa03c2c3..7dea0bf6 100644 --- a/lib/vpt/gate/gate.ts +++ b/lib/vpt/gate/gate.ts @@ -17,12 +17,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Object3D } from 'three'; import { EventProxy } from '../../game/event-proxy'; import { IHittable } from '../../game/ihittable'; import { IMovable } from '../../game/imovable'; import { IPlayable } from '../../game/iplayable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { IScriptable } from '../../game/iscriptable'; import { Player } from '../../game/player'; import { Storage } from '../../io/ole-doc'; @@ -32,7 +31,7 @@ import { Vertex2D } from '../../math/vertex2d'; import { HitCircle } from '../../physics/hit-circle'; import { HitObject } from '../../physics/hit-object'; import { LineSeg } from '../../physics/line-seg'; -import { Meshes } from '../item-data'; +import { IRenderApi } from '../../render/irender-api'; import { Table } from '../table/table'; import { GateApi } from './gate-api'; import { GateData } from './gate-data'; @@ -41,7 +40,6 @@ import { GateHitGenerator } from './gate-hit-generator'; import { GateMeshGenerator } from './gate-mesh-generator'; import { GateMover } from './gate-mover'; import { GateState } from './gate-state'; -import { IRenderApi } from '../../render/irender-api'; /** * VPinball's gates. @@ -142,7 +140,7 @@ export class Gate implements IRenderable, IPlayable, IMovable, IHitta } /* istanbul ignore next */ - public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { + public applyState(obj: NODE, renderApi: IRenderApi, table: Table, player: Player): void { const posZ = this.data.height; const matTransToOrigin = Matrix3D.claim().setTranslation(-this.data.vCenter.x, -this.data.vCenter.y, posZ); const matRotateToOrigin = Matrix3D.claim().rotateZMatrix(degToRad(-this.data.rotation)); diff --git a/lib/vpt/hit-target/hit-target-mesh.spec.ts b/lib/vpt/hit-target/hit-target-mesh.spec.ts index f01f51b5..9844fff7 100644 --- a/lib/vpt/hit-target/hit-target-mesh.spec.ts +++ b/lib/vpt/hit-target/hit-target-mesh.spec.ts @@ -22,6 +22,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -30,8 +31,9 @@ describe('The VPinball hit target generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-hit-target.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb()); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-hit-target.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb()); }); it('should generate a beveled drop target mesh', async () => { diff --git a/lib/vpt/hit-target/hit-target.ts b/lib/vpt/hit-target/hit-target.ts index c1bcc427..7f5f5067 100644 --- a/lib/vpt/hit-target/hit-target.ts +++ b/lib/vpt/hit-target/hit-target.ts @@ -20,7 +20,7 @@ import { EventProxy } from '../../game/event-proxy'; import { IAnimatable, IAnimation } from '../../game/ianimatable'; import { IHittable } from '../../game/ihittable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { IScriptable } from '../../game/iscriptable'; import { Player } from '../../game/player'; import { PlayerPhysics } from '../../game/player-physics'; @@ -30,7 +30,6 @@ import { Matrix3D } from '../../math/matrix3d'; import { HitObject } from '../../physics/hit-object'; import { IRenderApi } from '../../render/irender-api'; import { Ball } from '../ball/ball'; -import { Meshes } from '../item-data'; import { Table } from '../table/table'; import { HitTargetAnimation } from './hit-target-animation'; import { HitTargetApi } from './hit-target-api'; @@ -138,7 +137,7 @@ export class HitTarget implements IRenderable, IHittable, IAnimatable(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { + public applyState(obj: NODE, renderApi: IRenderApi, table: Table, player: Player): void { const matTransToOrigin = Matrix3D.claim().setTranslation(-this.data.vPosition.x, -this.data.vPosition.y, -this.data.vPosition.z); const matRotateToOrigin = Matrix3D.claim().rotateZMatrix(degToRad(-this.data.rotZ)); const matTransFromOrigin = Matrix3D.claim().setTranslation(this.data.vPosition.x, this.data.vPosition.y, this.data.vPosition.z); diff --git a/lib/vpt/item-data.ts b/lib/vpt/item-data.ts index 3d578c06..f862205b 100644 --- a/lib/vpt/item-data.ts +++ b/lib/vpt/item-data.ts @@ -17,9 +17,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Storage } from '..'; -import { RenderInfo } from '../game/irenderable'; import { BiffParser } from '../io/biff-parser'; +import { Storage } from '../io/ole-doc'; import { Table } from './table/table'; /** @@ -133,7 +132,3 @@ export class TimerDataRoot { public interval: number = 0; public enabled: boolean = false; } - -export interface Meshes { - [key: string]: RenderInfo; -} diff --git a/lib/vpt/kicker/kicker-mesh.spec.ts b/lib/vpt/kicker/kicker-mesh.spec.ts index 5ff15fb1..d55a494a 100644 --- a/lib/vpt/kicker/kicker-mesh.spec.ts +++ b/lib/vpt/kicker/kicker-mesh.spec.ts @@ -22,6 +22,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -30,8 +31,9 @@ describe('The VPinball kicker generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-kicker.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb()); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-kicker.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb()); }); it('should generate a cup kicker mesh', async () => { diff --git a/lib/vpt/kicker/kicker.ts b/lib/vpt/kicker/kicker.ts index 63e5fb37..7484b000 100644 --- a/lib/vpt/kicker/kicker.ts +++ b/lib/vpt/kicker/kicker.ts @@ -20,7 +20,7 @@ import { EventEmitter } from 'events'; import { EventProxy } from '../../game/event-proxy'; import { IHittable } from '../../game/ihittable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { IScriptable } from '../../game/iscriptable'; import { IBallCreationPosition, Player } from '../../game/player'; import { PlayerPhysics } from '../../game/player-physics'; @@ -29,7 +29,6 @@ import { Matrix3D } from '../../math/matrix3d'; import { Vertex3D } from '../../math/vertex3d'; import { HitObject } from '../../physics/hit-object'; import { Ball } from '../ball/ball'; -import { Meshes } from '../item-data'; import { FLT_MAX } from '../mesh'; import { Table } from '../table/table'; import { Texture } from '../texture'; diff --git a/lib/vpt/light/light-data.ts b/lib/vpt/light/light-data.ts index 0b9c6111..f7cb4edc 100644 --- a/lib/vpt/light/light-data.ts +++ b/lib/vpt/light/light-data.ts @@ -17,11 +17,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Storage, Table } from '../..'; import { BiffParser } from '../../io/biff-parser'; +import { Storage } from '../../io/ole-doc'; import { DragPoint } from '../../math/dragpoint'; import { Vertex2D } from '../../math/vertex2d'; import { ItemData } from '../item-data'; +import { Table } from '../table/table'; import { Light } from './light'; export class LightData extends ItemData { diff --git a/lib/vpt/light/light-mesh.spec.ts b/lib/vpt/light/light-mesh.spec.ts index 8962fbf0..406b22aa 100644 --- a/lib/vpt/light/light-mesh.spec.ts +++ b/lib/vpt/light/light-mesh.spec.ts @@ -23,6 +23,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); const scale = 0.05; @@ -32,8 +33,9 @@ describe('The VPinball lights generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-light.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb({ exportPlayfieldLights: true, applyTextures: false })); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-light.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb({ exportPlayfieldLights: true, applyTextures: false })); }); it('should generate a static light bulb mesh', async () => { diff --git a/lib/vpt/light/light.ts b/lib/vpt/light/light.ts index baf4e00e..ceae4513 100644 --- a/lib/vpt/light/light.ts +++ b/lib/vpt/light/light.ts @@ -18,10 +18,9 @@ */ import { BufferGeometry, MeshStandardMaterial } from 'three'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { Storage } from '../../io/ole-doc'; import { Matrix3D } from '../../math/matrix3d'; -import { Meshes } from '../item-data'; import { Material } from '../material'; import { Table } from '../table/table'; import { LightData } from './light-data'; @@ -45,7 +44,7 @@ export class Light implements IRenderable { get vCenter() { return this.data.vCenter; } get offImage() { return this.data.szOffImage; } - private readonly data: LightData; + public readonly data: LightData; private readonly meshGenerator: LightMeshGenerator; public static async fromStorage(storage: Storage, itemName: string): Promise { diff --git a/lib/vpt/plunger/plunger-mesh.spec.ts b/lib/vpt/plunger/plunger-mesh.spec.ts index 18a08d20..7c11da85 100644 --- a/lib/vpt/plunger/plunger-mesh.spec.ts +++ b/lib/vpt/plunger/plunger-mesh.spec.ts @@ -22,6 +22,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -30,8 +31,9 @@ describe('The VPinball plunger generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-plunger.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb({ exportPlayfieldLights: true, applyTextures: false })); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-plunger.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb({ exportPlayfieldLights: true, applyTextures: false })); }); it('should generate a flat plunger', async () => { diff --git a/lib/vpt/plunger/plunger-mover.spec.ts b/lib/vpt/plunger/plunger-mover.spec.ts index fe126687..3d7c4c37 100644 --- a/lib/vpt/plunger/plunger-mover.spec.ts +++ b/lib/vpt/plunger/plunger-mover.spec.ts @@ -27,6 +27,7 @@ import { Player } from '../../game/player'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { ThreeRenderApi } from '../../render/threejs/three-render-api'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; import { PlungerMover } from './plunger-mover'; import { PlungerState } from './plunger-state'; @@ -37,10 +38,12 @@ const renderApi = new ThreeRenderApi(); describe('The VPinball plunger physics', () => { let table: Table; + let exporter: TableExporter; let player: Player; before(async () => { table = await Table.load(new NodeBinaryReader(three.fixturePath('table-plunger.vpx'))); + exporter = new TableExporter(table); }); beforeEach(() => { @@ -113,7 +116,7 @@ describe('The VPinball plunger physics', () => { it('should apply the mesh transformation when animated', async () => { // create scene - const gltf = await three.loadGlb(await table.exportGlb()); + const gltf = await three.loadGlb(await exporter.exportGlb()); const plunger = table.plungers.CustomPlunger; // retrieve plunger diff --git a/lib/vpt/plunger/plunger.ts b/lib/vpt/plunger/plunger.ts index 77de877d..69e8e905 100644 --- a/lib/vpt/plunger/plunger.ts +++ b/lib/vpt/plunger/plunger.ts @@ -17,21 +17,20 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Storage, Table } from '../..'; import { EventProxy } from '../../game/event-proxy'; import { IHittable } from '../../game/ihittable'; import { IMovable } from '../../game/imovable'; import { IPlayable } from '../../game/iplayable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { IScriptable } from '../../game/iscriptable'; import { IBallCreationPosition, Player } from '../../game/player'; import { PlayerPhysics } from '../../game/player-physics'; +import { Storage } from '../../io/ole-doc'; import { Vertex3D } from '../../math/vertex3d'; import { HitObject } from '../../physics/hit-object'; import { IRenderApi } from '../../render/irender-api'; import { Ball } from '../ball/ball'; -import { Meshes } from '../item-data'; -import { VpTableExporterOptions } from '../table/table-exporter'; +import { Table } from '../table/table'; import { PlungerApi } from './plunger-api'; import { PlungerData } from './plunger-data'; import { PlungerHit } from './plunger-hit'; @@ -77,7 +76,7 @@ export class Plunger implements IRenderable, IPlayable, IMovable, return this.state!; } - public getMeshes(table: Table, opts: VpTableExporterOptions): Meshes { + public getMeshes(table: Table): Meshes { const plunger = this.meshGenerator.generateMeshes(0, table); const meshes: Meshes = {}; const material = table.getMaterial(this.data.szMaterial); @@ -148,7 +147,7 @@ export class Plunger implements IRenderable, IPlayable, IMovable, } } - public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { + public applyState(obj: NODE, renderApi: IRenderApi, table: Table, player: Player): void { const mesh = this.meshGenerator.generateMeshes(this.state.frame, table); const rodObj = renderApi.findInGroup(obj, 'rod'); if (rodObj) { diff --git a/lib/vpt/primitive/primitive-mesh.spec.ts b/lib/vpt/primitive/primitive-mesh.spec.ts index 43c57c47..8447dc1a 100644 --- a/lib/vpt/primitive/primitive-mesh.spec.ts +++ b/lib/vpt/primitive/primitive-mesh.spec.ts @@ -22,6 +22,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -30,8 +31,9 @@ describe('The VPinball primitive generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-primitive.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb({ applyTextures: false })); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-primitive.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb({ applyTextures: false })); }); it('should generate a simple primitive mesh', async () => { @@ -93,7 +95,8 @@ describe('The VPinball primitive generator', () => { it('should generate a compressed mesh', async () => { const vptSink = await Table.load(new NodeBinaryReader(three.fixturePath('table-sink.vpx'))); - const gltfSink = await three.loadGlb(await vptSink.exportGlb()); + const exporterSink = new TableExporter(vptSink); + const gltfSink = await three.loadGlb(await exporterSink.exportGlb()); const sinkMesh = three.find(gltfSink, 'primitives', 'Primitive1', 'primitive-Primitive1'); const sinkMeshVertices = three.vertices(sinkMesh); diff --git a/lib/vpt/primitive/primitive.ts b/lib/vpt/primitive/primitive.ts index e80a0ca2..8c5f221e 100644 --- a/lib/vpt/primitive/primitive.ts +++ b/lib/vpt/primitive/primitive.ts @@ -19,14 +19,13 @@ import { EventProxy } from '../../game/event-proxy'; import { IHittable } from '../../game/ihittable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { IScriptable } from '../../game/iscriptable'; import { Player } from '../../game/player'; import { Storage } from '../../io/ole-doc'; import { Matrix3D } from '../../math/matrix3d'; import { HitObject } from '../../physics/hit-object'; import { Ball } from '../ball/ball'; -import { Meshes } from '../item-data'; import { Mesh } from '../mesh'; import { Table } from '../table/table'; import { PrimitiveApi } from './primitive-api'; diff --git a/lib/vpt/ramp/ramp-mesh.spec.ts b/lib/vpt/ramp/ramp-mesh.spec.ts index 378185d6..1094c189 100644 --- a/lib/vpt/ramp/ramp-mesh.spec.ts +++ b/lib/vpt/ramp/ramp-mesh.spec.ts @@ -22,6 +22,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -33,8 +34,10 @@ describe('The VPinball ramp generator', () => { before(async () => { const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-ramp.vpx'))); const vptOnSurface = await Table.load(new NodeBinaryReader(three.fixturePath('table-ramp-surface.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb()); - gltfOnSurface = await three.loadGlb(await vptOnSurface.exportGlb()); + const exporter = new TableExporter(vpt); + const exporterSurface = new TableExporter(vptOnSurface); + gltf = await three.loadGlb(await exporter.exportGlb()); + gltfOnSurface = await three.loadGlb(await exporterSurface.exportGlb()); }); it('should generate a flat ramp mesh', async () => { diff --git a/lib/vpt/ramp/ramp.ts b/lib/vpt/ramp/ramp.ts index 8bd7f82f..69b20864 100644 --- a/lib/vpt/ramp/ramp.ts +++ b/lib/vpt/ramp/ramp.ts @@ -19,14 +19,13 @@ import { EventProxy } from '../../game/event-proxy'; import { IHittable } from '../../game/ihittable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { Player } from '../../game/player'; import { Storage } from '../../io/ole-doc'; import { f4 } from '../../math/float'; import { Matrix3D } from '../../math/matrix3d'; import { Vertex2D } from '../../math/vertex2d'; import { HitObject } from '../../physics/hit-object'; -import { Meshes } from '../item-data'; import { Mesh } from '../mesh'; import { Table } from '../table/table'; import { RampData } from './ramp-data'; diff --git a/lib/vpt/rubber/rubber-mesh.spec.ts b/lib/vpt/rubber/rubber-mesh.spec.ts index 3b9b7f91..7723cafb 100644 --- a/lib/vpt/rubber/rubber-mesh.spec.ts +++ b/lib/vpt/rubber/rubber-mesh.spec.ts @@ -22,6 +22,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -30,8 +31,9 @@ describe('The VPinball rubber generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-rubber.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb()); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-rubber.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb()); }); it('should generate a rubber mesh', async () => { diff --git a/lib/vpt/rubber/rubber.ts b/lib/vpt/rubber/rubber.ts index f6284556..08a1ac82 100644 --- a/lib/vpt/rubber/rubber.ts +++ b/lib/vpt/rubber/rubber.ts @@ -19,12 +19,11 @@ import { EventProxy } from '../../game/event-proxy'; import { IHittable } from '../../game/ihittable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { Player } from '../../game/player'; import { Storage } from '../../io/ole-doc'; import { Matrix3D } from '../../math/matrix3d'; import { HitObject } from '../../physics/hit-object'; -import { Meshes } from '../item-data'; import { Table } from '../table/table'; import { RubberData } from './rubber-data'; import { RubberHitGenerator } from './rubber-hit-generator'; diff --git a/lib/vpt/spinner/spinner-mesh.spec.ts b/lib/vpt/spinner/spinner-mesh.spec.ts index 14fb4520..fb75622c 100644 --- a/lib/vpt/spinner/spinner-mesh.spec.ts +++ b/lib/vpt/spinner/spinner-mesh.spec.ts @@ -22,6 +22,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -30,8 +31,9 @@ describe('The VPinball spinner generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-spinner.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb()); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-spinner.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb()); }); it('should generate a standard spinner mesh', async () => { diff --git a/lib/vpt/spinner/spinner.ts b/lib/vpt/spinner/spinner.ts index f37c6f1c..8a7938e4 100644 --- a/lib/vpt/spinner/spinner.ts +++ b/lib/vpt/spinner/spinner.ts @@ -17,12 +17,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Object3D } from 'three'; import { EventProxy } from '../../game/event-proxy'; import { IHittable } from '../../game/ihittable'; import { IMovable } from '../../game/imovable'; import { IPlayable } from '../../game/iplayable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { Player } from '../../game/player'; import { Storage } from '../../io/ole-doc'; import { degToRad } from '../../math/float'; @@ -30,15 +29,14 @@ import { Matrix3D } from '../../math/matrix3d'; import { HitCircle } from '../../physics/hit-circle'; import { HitObject } from '../../physics/hit-object'; import { MoverObject } from '../../physics/mover-object'; +import { IRenderApi } from '../../render/irender-api'; import { FlipperState } from '../flipper/flipper-state'; -import { Meshes } from '../item-data'; import { Table } from '../table/table'; import { SpinnerData } from './spinner-data'; import { SpinnerHit } from './spinner-hit'; import { SpinnerHitGenerator } from './spinner-hit-generator'; import { SpinnerMeshGenerator } from './spinner-mesh-generator'; import { SpinnerState } from './spinner-state'; -import { IRenderApi } from '../../render/irender-api'; /** * VPinball's spinners. @@ -126,7 +124,7 @@ export class Spinner implements IRenderable, IPlayable, IMovable, } /* istanbul ignore next */ - public applyState(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { + public applyState(obj: NODE, renderApi: IRenderApi, table: Table, player: Player): void { const posZ = this.meshGenerator.getZ(table); const matTransToOrigin = Matrix3D.claim().setTranslation(-this.data.vCenter.x, -this.data.vCenter.y, posZ); diff --git a/lib/vpt/surface/surface-mesh.spec.ts b/lib/vpt/surface/surface-mesh.spec.ts index 065ccf39..d2c911e2 100644 --- a/lib/vpt/surface/surface-mesh.spec.ts +++ b/lib/vpt/surface/surface-mesh.spec.ts @@ -21,6 +21,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -29,8 +30,9 @@ describe('The VPinball surface generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-surface.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb()); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-surface.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb()); }); it('should generate a surface mesh', async () => { diff --git a/lib/vpt/surface/surface.ts b/lib/vpt/surface/surface.ts index ce24db50..e4cc8d4a 100644 --- a/lib/vpt/surface/surface.ts +++ b/lib/vpt/surface/surface.ts @@ -19,15 +19,13 @@ import { EventProxy } from '../../game/event-proxy'; import { IHittable } from '../../game/ihittable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { IScriptable } from '../../game/iscriptable'; import { Player } from '../../game/player'; import { Storage } from '../../io/ole-doc'; import { Matrix3D } from '../../math/matrix3d'; import { HitObject } from '../../physics/hit-object'; -import { Meshes } from '../item-data'; import { Table } from '../table/table'; -import { VpTableExporterOptions } from '../table/table-exporter'; import { SurfaceApi } from './surface-api'; import { SurfaceData } from './surface-data'; import { SurfaceHitGenerator } from './surface-hit-generator'; @@ -104,7 +102,7 @@ export class Surface implements IRenderable, IHittable, IScriptable } } - public getMeshes(table: Table, opts: VpTableExporterOptions): Meshes { + public getMeshes(table: Table): Meshes { const meshes: Meshes = {}; const surface = this.meshGenerator.generateMeshes(this.data, table); if (surface.top) { diff --git a/lib/vpt/table/table-exporter.ts b/lib/vpt/table/table-exporter.ts index 406a8904..3eb4a557 100644 --- a/lib/vpt/table/table-exporter.ts +++ b/lib/vpt/table/table-exporter.ts @@ -17,274 +17,51 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { - Color, - DoubleSide, - Group, - Mesh as ThreeMesh, - MeshStandardMaterial, - PointLight, - RGBAFormat, - RGBFormat, - Scene, - Texture, -} from 'three'; -import { IRenderable, RenderInfo } from '../../game/irenderable'; -import { IImage } from '../../gltf/image'; +import { Scene } from 'three'; import { exportGltf } from '../../refs.node'; -import { logger } from '../../util/logger'; -import { Bumper } from '../bumper/bumper'; -import { Flipper } from '../flipper/flipper'; -import { Primitive } from '../primitive/primitive'; -import { Ramp } from '../ramp/ramp'; -import { Rubber } from '../rubber/rubber'; -import { Surface } from '../surface/surface'; -import { Texture as VpTexture } from '../texture'; -import { Table } from './table'; +import { MeshConvertOptions } from '../../render/irender-api'; +import { ThreeRenderApi } from '../../render/threejs/three-render-api'; +import { Table, TableGenerateOptions } from './table'; +import { TableMeshGenerator } from './table-mesh-generator'; export class TableExporter { - private static readonly scale = 0.05; private readonly table: Table; - private readonly scene: Scene; - private readonly opts: VpTableExporterOptions; - private readonly playfield: Group; - private readonly images: Map = new Map(); + private readonly meshGenerator: TableMeshGenerator; - constructor(table: Table, opts: VpTableExporterOptions = {}) { - this.opts = Object.assign({}, defaultOptions, opts); - - const dim = table.getDimensions(); + constructor(table: Table) { this.table = table; - this.scene = new Scene(); - this.scene.name = 'vpdb-table'; - this.playfield = new Group(); - this.playfield.name = 'playfield'; - this.playfield.rotateX(Math.PI / 2); - this.playfield.translateY(-dim.height * TableExporter.scale / 2); - this.playfield.translateX(-dim.width * TableExporter.scale / 2); - this.playfield.scale.set(TableExporter.scale, TableExporter.scale, TableExporter.scale); + this.meshGenerator = new TableMeshGenerator(table); } - public async exportGltf(): Promise { - this.opts.gltfOptions!.binary = false; - return JSON.stringify(await this.export()); - } + // public async exportGltf(): Promise { + // this.opts.gltfOptions!.binary = false; + // return JSON.stringify(await this.export()); + // } - public async exportGlb(): Promise { - this.opts.gltfOptions!.binary = true; - return await this.export(); + public async exportGlb(opts: TableExportOptions = {}): Promise { + opts = Object.assign({}, defaultOptions, opts); + opts.gltfOptions!.binary = true; + return await this.export(opts); } - public async createScene(): Promise { - - const renderGroups: IRenderGroup[] = [ - { name: 'playfield', meshes: [ this.table ], enabled: !!this.opts.exportPlayfield }, - { name: 'primitives', meshes: Object.values(this.table.primitives), enabled: !!this.opts.exportPrimitives }, - { name: 'rubbers', meshes: Object.values(this.table.rubbers), enabled: !!this.opts.exportRubbers }, - { name: 'surfaces', meshes: Object.values(this.table.surfaces), enabled: !!this.opts.exportSurfaces}, - { name: 'flippers', meshes: Object.values(this.table.flippers), enabled: !!this.opts.exportFlippers}, - { name: 'bumpers', meshes: Object.values(this.table.bumpers), enabled: !!this.opts.exportBumpers }, - { name: 'ramps', meshes: Object.values(this.table.ramps), enabled: !!this.opts.exportRamps }, - { name: 'lightBulbs', meshes: Object.values(this.table.lights).filter(l => l.isBulbLight()), enabled: !!this.opts.exportLightBulbs }, - { name: 'playfieldLights', meshes: Object.values(this.table.lights).filter(l => l.isSurfaceLight(this.table)), enabled: !!this.opts.exportPlayfieldLights }, - { name: 'hitTargets', meshes: Object.values(this.table.hitTargets), enabled: !!this.opts.exportHitTargets }, - { name: 'gates', meshes: Object.values(this.table.gates), enabled: !!this.opts.exportGates }, - { name: 'kickers', meshes: Object.values(this.table.kickers), enabled: !!this.opts.exportKickers }, - { name: 'triggers', meshes: Object.values(this.table.triggers), enabled: !!this.opts.exportTriggers }, - { name: 'spinners', meshes: Object.values(this.table.spinners), enabled: !!this.opts.exportSpinners }, - { name: 'plungers', meshes: Object.values(this.table.plungers), enabled: !!this.opts.exportPlungers }, - ]; - - // meshes - for (const group of renderGroups) { - if (!group.enabled) { - continue; - } - const itemTypeGroup = new Group(); - itemTypeGroup.name = group.name; - for (const renderable of group.meshes.filter(i => i.isVisible(this.table))) { - const itemGroup = await this.createItemMesh(renderable); - itemTypeGroup.add(itemGroup); - } - if (itemTypeGroup.children.length > 0) { - this.playfield.add(itemTypeGroup); - } - } - - const lightGroup = new Group(); - lightGroup.name = 'lights'; - - // light bulb lights - if (this.opts.exportLightBulbLights) { - for (const lightInfo of Object.values(this.table.lights).filter(l => l.isBulbLight())) { - const light = new PointLight(lightInfo.color, lightInfo.intensity, lightInfo.falloff * TableExporter.scale, 2); - const itemGroup = new Group(); - itemGroup.name = lightInfo.getName(); - light.name = 'light:' + lightInfo.getName(); - light.position.set(lightInfo.vCenter.x, lightInfo.vCenter.y, -17); - itemGroup.add(light); - lightGroup.add(itemGroup); - } - } - - // ball group - const ballGroup = new Group(); - ballGroup.name = 'balls'; - this.playfield.add(ballGroup); - - // playfield lights - // if (this.opts.exportPlayfieldLights) { - // for (const lightInfo of this.table.lights.filter(l => l.isSurfaceLight(this.table)).slice(0, 10)) { - // const light = new PointLight(lightInfo.color, lightInfo.intensity, lightInfo.falloff * TableExporter.scale, 2); - // light.name = 'light:' + lightInfo.getName(); - // light.position.set(lightInfo.vCenter.x, lightInfo.vCenter.y, 10); - // lightGroup.add(light); - // } - // } + private async export(opts: TableExportOptions): Promise { + // we always use Three.js for GLTF generation + const renderApi = new ThreeRenderApi(); + const playfieldGroup = await this.meshGenerator.generateTableNode(renderApi, opts); - if (lightGroup.children.length > 0) { - this.playfield.add(lightGroup); - } - - // finally, add to scene - this.scene.add(this.playfield); - - return this.scene; - } - - public async createItemMesh(renderable: IRenderable): Promise { - const objects = renderable.getMeshes(this.table, this.opts); - let obj: RenderInfo; - const itemGroup = new Group(); - itemGroup.name = renderable.getName(); - for (obj of Object.values(objects)) { - const mesh = await this.createElementMesh(renderable, obj); - itemGroup.add(mesh); - } - return itemGroup; - } - - private async createElementMesh(renderable: IRenderable, obj: RenderInfo): Promise { - /* istanbul ignore if */ - if (!obj.geometry && !obj.mesh) { - throw new Error('Mesh export must either provide mesh or geometry.'); - } - const geometry = obj.geometry || obj.mesh!.getBufferGeometry(); - const material = await this.getMaterial(obj); - const postProcessedMaterial = renderable.postProcessMaterial ? renderable.postProcessMaterial(this.table, geometry, material) : material; - const mesh = new ThreeMesh(geometry, postProcessedMaterial); - mesh.name = (obj.geometry || obj.mesh!).name; - - return mesh; - } - - private async export(): Promise { - - await this.createScene(); + const scene = new Scene(); + scene.name = 'table'; + scene.add(playfieldGroup); // now, export to GLTF - return exportGltf(this.scene, this.opts, this.opts.gltfOptions); - } - - private async getMaterial(obj: RenderInfo): Promise { - const material = new MeshStandardMaterial(); - const name = (obj.geometry || obj.mesh!).name; - material.name = `material:${name}`; - const materialInfo = obj.material; - if (materialInfo && this.opts.applyMaterials) { - material.metalness = materialInfo.bIsMetal ? 1.0 : 0.0; - material.roughness = Math.max(0, 1 - (materialInfo.fRoughness / 1.5)); - material.color = new Color(materialInfo.cBase); - material.opacity = materialInfo.bOpacityActive ? Math.min(1, Math.max(0, materialInfo.fOpacity)) : 1; - material.transparent = materialInfo.bOpacityActive && materialInfo.fOpacity < 0.98; - material.side = DoubleSide; - - if (materialInfo.emissiveIntensity > 0) { - material.emissive = new Color(materialInfo.emissiveColor); - material.emissiveIntensity = materialInfo.emissiveIntensity; - } - } - - if (this.opts.applyTextures) { - if (obj.map) { - material.map = new Texture(); - material.map.name = 'texture:' + obj.map.getName(); - if (await this.loadMap(name, obj.map, material.map)) { - if ((material.map.image as IImage).containsTransparency()) { - material.transparent = true; - } - material.needsUpdate = true; - } else { - logger().warn('[VpTableExporter.getMaterial] Error getting map.'); - material.map = null; - } - } - if (obj.normalMap) { - material.normalMap = new Texture(); - material.normalMap.name = 'normal-map:' + obj.normalMap.getName(); - if (await this.loadMap(name, obj.normalMap, material.normalMap)) { - material.normalMap.anisotropy = 16; - material.needsUpdate = true; - } else { - material.normalMap = null; - } - } - } - return material; - } - - private async loadMap(name: string, texture: VpTexture, threeMaterial: Texture): Promise { - try { - let image: IImage; - if (this.images.has(texture.getName())) { - image = this.images.get(texture.getName())!; - } else { - image = await texture.getImage(this.table); - this.images.set(texture.getName(), image); - } - threeMaterial.image = image; - threeMaterial.format = image.hasTransparency() ? RGBAFormat : RGBFormat; - threeMaterial.needsUpdate = true; - return true; - } catch (err) { - threeMaterial.image = Texture.DEFAULT_IMAGE; - logger().warn('[VpTableExporter.loadMap] Error loading map %s (%s/%s): %s', name, texture.storageName, texture.getName(), err.message); - return false; - } + return exportGltf(scene, opts, opts.gltfOptions); } } -interface IRenderGroup { - name: string; - meshes: IRenderable[]; - enabled: boolean; -} +export interface TableExportOptions extends TableGenerateOptions, MeshConvertOptions { } -export interface VpTableExporterOptions { - applyMaterials?: boolean; - applyTextures?: boolean; - optimizeTextures?: boolean; - exportPlayfield?: boolean; - exportPrimitives?: boolean; - exportRubbers?: boolean; - exportSurfaces?: boolean; - exportFlippers?: boolean; - exportBumpers?: boolean; - exportRamps?: boolean; - exportLightBulbs?: boolean; - exportPlayfieldLights?: boolean; - exportLightBulbLights?: boolean; - exportHitTargets?: boolean; - exportGates?: boolean; - exportKickers?: boolean; - exportTriggers?: boolean; - exportSpinners?: boolean; - exportPlungers?: boolean; - gltfOptions?: ParseOptions; -} - -const defaultOptions: VpTableExporterOptions = { +const defaultOptions: TableExportOptions = { applyMaterials: true, applyTextures: true, optimizeTextures: false, @@ -306,26 +83,3 @@ const defaultOptions: VpTableExporterOptions = { exportPlungers: true, gltfOptions: {}, }; - -export interface ParseOptions { - binary?: boolean; - optimizeImages?: boolean; - trs?: boolean; - onlyVisible?: boolean; - truncateDrawRange?: boolean; - embedImages?: boolean; - animations?: any[]; - forceIndices?: boolean; - forcePowerOfTwoTextures?: boolean; - compressVertices?: boolean; - versionString?: string; - dracoOptions?: { - compressionLevel?: number; - quantizePosition?: number; - quantizeNormal?: number; - quantizeTexcoord?: number; - quantizeColor?: number; - quantizeSkin?: number; - unifiedQuantization?: boolean; - }; -} diff --git a/lib/vpt/table/table-mesh-generator.ts b/lib/vpt/table/table-mesh-generator.ts index e7e09d75..526d8cec 100644 --- a/lib/vpt/table/table-mesh-generator.ts +++ b/lib/vpt/table/table-mesh-generator.ts @@ -19,34 +19,92 @@ /* tslint:disable:no-bitwise */ import { BufferGeometry, ExtrudeBufferGeometry, Shape, Vector2 } from 'three'; -import { Vertex3DNoTex2 } from '../../math/vertex'; -import { Mesh } from '../mesh'; -import { Table } from './table'; -import { TableData } from './table-data'; -import { VpTableExporterOptions } from './table-exporter'; +import { IRenderable } from '../../game/irenderable'; +import { IRenderApi } from '../../render/irender-api'; +import { Bumper } from '../bumper/bumper'; +import { Flipper } from '../flipper/flipper'; +import { Primitive } from '../primitive/primitive'; +import { Ramp } from '../ramp/ramp'; +import { Rubber } from '../rubber/rubber'; +import { Surface } from '../surface/surface'; +import { Table, TableGenerateOptions } from './table'; export class TableMeshGenerator { - private readonly data: TableData; + private readonly table: Table; - constructor(data: TableData) { - this.data = data; + constructor(table: Table) { + this.table = table; } - public getMesh(table: Table, opts: VpTableExporterOptions): BufferGeometry { + public async generateTableNode(renderApi: IRenderApi, opts: TableGenerateOptions = {}): Promise { + + opts = Object.assign({}, defaultOptions, opts); + const playfield = renderApi.createGroup('playfield'); + renderApi.transformScene(playfield, this.table); + const renderGroups: IRenderGroup[] = [ + { name: 'playfield', meshes: [ this.table ], enabled: !!opts.exportPlayfield }, + { name: 'primitives', meshes: Object.values(this.table.primitives), enabled: !!opts.exportPrimitives }, + { name: 'rubbers', meshes: Object.values(this.table.rubbers), enabled: !!opts.exportRubbers }, + { name: 'surfaces', meshes: Object.values(this.table.surfaces), enabled: !!opts.exportSurfaces}, + { name: 'flippers', meshes: Object.values(this.table.flippers), enabled: !!opts.exportFlippers}, + { name: 'bumpers', meshes: Object.values(this.table.bumpers), enabled: !!opts.exportBumpers }, + { name: 'ramps', meshes: Object.values(this.table.ramps), enabled: !!opts.exportRamps }, + { name: 'lightBulbs', meshes: Object.values(this.table.lights).filter(l => l.isBulbLight()), enabled: !!opts.exportLightBulbs }, + { name: 'playfieldLights', meshes: Object.values(this.table.lights).filter(l => l.isSurfaceLight(this.table)), enabled: !!opts.exportPlayfieldLights }, + { name: 'hitTargets', meshes: Object.values(this.table.hitTargets), enabled: !!opts.exportHitTargets }, + { name: 'gates', meshes: Object.values(this.table.gates), enabled: !!opts.exportGates }, + { name: 'kickers', meshes: Object.values(this.table.kickers), enabled: !!opts.exportKickers }, + { name: 'triggers', meshes: Object.values(this.table.triggers), enabled: !!opts.exportTriggers }, + { name: 'spinners', meshes: Object.values(this.table.spinners), enabled: !!opts.exportSpinners }, + { name: 'plungers', meshes: Object.values(this.table.plungers), enabled: !!opts.exportPlungers }, + ]; + + // meshes + for (const group of renderGroups) { + if (!group.enabled) { + continue; + } + const itemTypeGroup = renderApi.createGroup(group.name); + for (const renderable of group.meshes.filter(i => i.isVisible(this.table))) { + const itemGroup = await renderApi.createObjectFromRenderable(renderable, this.table); + renderApi.addToGroup(itemTypeGroup, itemGroup); + } + renderApi.addToGroup(playfield, itemTypeGroup); + } + + // light bulb lights + if (opts.exportLightBulbLights) { + const lightGroup = renderApi.createGroup('lights'); + for (const lightInfo of Object.values(this.table.lights).filter(l => l.isBulbLight())) { + const light = renderApi.createPointLight(lightInfo.data); + const itemGroup = renderApi.createGroup(lightInfo.getName()); + renderApi.addToGroup(itemGroup, light); + renderApi.addToGroup(lightGroup, itemGroup); + } + renderApi.addToGroup(playfield, lightGroup); + } + + // ball group + renderApi.addToGroup(playfield, renderApi.createGroup('balls')); + + return playfield; + } + + public getPlayfieldMesh(table: Table, opts: TableGenerateOptions): BufferGeometry { /* istanbul ignore if */ - if (!this.data) { + if (!this.table.data) { throw new Error('Table data is not loaded. Load table with tableDataOnly = false.'); } let geometry: BufferGeometry; const dim = table.getDimensions(); const pfShape = new Shape(); - pfShape.moveTo(this.data.left, this.data.top); - pfShape.lineTo(this.data.right, this.data.top); - pfShape.lineTo(this.data.right, this.data.bottom); - pfShape.lineTo(this.data.left, this.data.bottom); - pfShape.lineTo(this.data.left, this.data.top); + pfShape.moveTo(this.table.data.left, this.table.data.top); + pfShape.lineTo(this.table.data.right, this.table.data.top); + pfShape.lineTo(this.table.data.right, this.table.data.bottom); + pfShape.lineTo(this.table.data.left, this.table.data.bottom); + pfShape.lineTo(this.table.data.left, this.table.data.top); // drill holes if playfield lights are rendered separately. if (opts.exportPlayfieldLights) { @@ -90,55 +148,80 @@ export class TableMeshGenerator { return geometry; } - /* istanbul ignore next */ - private get2DMesh(): Mesh { - const rgv: Vertex3DNoTex2[] = []; - for (let i = 0; i < 7; i++) { - rgv.push(new Vertex3DNoTex2()); - } - rgv[0].x = this.data.left; rgv[0].y = this.data.top; rgv[0].z = this.data.tableheight; - rgv[1].x = this.data.right; rgv[1].y = this.data.top; rgv[1].z = this.data.tableheight; - rgv[2].x = this.data.right; rgv[2].y = this.data.bottom; rgv[2].z = this.data.tableheight; - rgv[3].x = this.data.left; rgv[3].y = this.data.bottom; rgv[3].z = this.data.tableheight; - - // These next 4 vertices are used just to set the extents - rgv[4].x = this.data.left; rgv[4].y = this.data.top; rgv[4].z = this.data.tableheight + Table.playfieldThickness; - rgv[5].x = this.data.left; rgv[5].y = this.data.bottom; rgv[5].z = this.data.tableheight + Table.playfieldThickness; - rgv[6].x = this.data.right; rgv[6].y = this.data.bottom; rgv[6].z = this.data.tableheight + Table.playfieldThickness; - //rgv[7].x=g_pplayer->m_ptable->m_right; rgv[7].y=g_pplayer->m_ptable->m_top; rgv[7].z=50.0f; - - for (let i = 0; i < 4; ++i) { - rgv[i].nx = 0; - rgv[i].ny = 0; - rgv[i].nz = 1.0; - - rgv[i].tv = (i & 2) ? 1.0 : 0.0; - rgv[i].tu = (i === 1 || i === 2) ? 1.0 : 0.0; - } - - const playfieldPolyIndices = [ 0, 1, 3, 0, 3, 2, 2, 3, 5, 6 ]; - Mesh.setNormal(rgv, playfieldPolyIndices.splice(6), 4); + // private getPlayfield2DMesh(): Mesh { + // const rgv: Vertex3DNoTex2[] = []; + // for (let i = 0; i < 7; i++) { + // rgv.push(new Vertex3DNoTex2()); + // } + // rgv[0].x = this.data.left; rgv[0].y = this.data.top; rgv[0].z = this.data.tableheight; + // rgv[1].x = this.data.right; rgv[1].y = this.data.top; rgv[1].z = this.data.tableheight; + // rgv[2].x = this.data.right; rgv[2].y = this.data.bottom; rgv[2].z = this.data.tableheight; + // rgv[3].x = this.data.left; rgv[3].y = this.data.bottom; rgv[3].z = this.data.tableheight; + // + // // These next 4 vertices are used just to set the extents + // rgv[4].x = this.data.left; rgv[4].y = this.data.top; rgv[4].z = this.data.tableheight + Table.playfieldThickness; + // rgv[5].x = this.data.left; rgv[5].y = this.data.bottom; rgv[5].z = this.data.tableheight + Table.playfieldThickness; + // rgv[6].x = this.data.right; rgv[6].y = this.data.bottom; rgv[6].z = this.data.tableheight + Table.playfieldThickness; + // //rgv[7].x=g_pplayer->m_ptable->m_right; rgv[7].y=g_pplayer->m_ptable->m_top; rgv[7].z=50.0f; + // + // for (let i = 0; i < 4; ++i) { + // rgv[i].nx = 0; + // rgv[i].ny = 0; + // rgv[i].nz = 1.0; + // + // rgv[i].tv = (i & 2) ? 1.0 : 0.0; + // rgv[i].tu = (i === 1 || i === 2) ? 1.0 : 0.0; + // } + // + // const playfieldPolyIndices = [ 0, 1, 3, 0, 3, 2, 2, 3, 5, 6 ]; + // Mesh.setNormal(rgv, playfieldPolyIndices.splice(6), 4); + // + // const buffer: Vertex3DNoTex2[] = []; + // for (let i = 0; i < 7; i++) { + // buffer.push(new Vertex3DNoTex2()); + // } + // let offs = 0; + // for (let y = 0; y <= 1; ++y) { + // for (let x = 0; x <= 1; ++x) { + // buffer[offs].x = (x & 1) ? rgv[1].x : rgv[0].x; + // buffer[offs].y = (y & 1) ? rgv[2].y : rgv[0].y; + // buffer[offs].z = rgv[0].z; + // + // buffer[offs].tu = (x & 1) ? rgv[1].tu : rgv[0].tu; + // buffer[offs].tv = (y & 1) ? rgv[2].tv : rgv[0].tv; + // + // buffer[offs].nx = rgv[0].nx; + // buffer[offs].ny = rgv[0].ny; + // buffer[offs].nz = rgv[0].nz; + // ++offs; + // } + // } + // return new Mesh(buffer, playfieldPolyIndices); + // } +} - const buffer: Vertex3DNoTex2[] = []; - for (let i = 0; i < 7; i++) { - buffer.push(new Vertex3DNoTex2()); - } - let offs = 0; - for (let y = 0; y <= 1; ++y) { - for (let x = 0; x <= 1; ++x) { - buffer[offs].x = (x & 1) ? rgv[1].x : rgv[0].x; - buffer[offs].y = (y & 1) ? rgv[2].y : rgv[0].y; - buffer[offs].z = rgv[0].z; - - buffer[offs].tu = (x & 1) ? rgv[1].tu : rgv[0].tu; - buffer[offs].tv = (y & 1) ? rgv[2].tv : rgv[0].tv; - - buffer[offs].nx = rgv[0].nx; - buffer[offs].ny = rgv[0].ny; - buffer[offs].nz = rgv[0].nz; - ++offs; - } - } - return new Mesh(buffer, playfieldPolyIndices); - } +interface IRenderGroup { + name: string; + meshes: IRenderable[]; + enabled: boolean; } + +const defaultOptions: TableGenerateOptions = { + exportPlayfield: true, + exportPrimitives: true, + exportRubbers: true, + exportSurfaces: true, + exportFlippers: true, + exportBumpers: true, + exportRamps: true, + exportPlayfieldLights: false, + exportLightBulbs: true, + exportLightBulbLights: true, + exportHitTargets: true, + exportGates: true, + exportKickers: true, + exportTriggers: true, + exportSpinners: true, + exportPlungers: true, + gltfOptions: {}, +}; diff --git a/lib/vpt/table/table.spec.ts b/lib/vpt/table/table.spec.ts index 5b804814..223c1fc9 100644 --- a/lib/vpt/table/table.spec.ts +++ b/lib/vpt/table/table.spec.ts @@ -23,6 +23,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from './table'; +import { TableExporter } from './table-exporter'; const three = new ThreeHelper(); @@ -33,11 +34,12 @@ const tableDepth = 20; describe('The VPinball table generator', () => { let gltf: GLTF; - let vpt: Table; + let table: Table; before(async () => { - vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-empty.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb({ applyTextures: false, exportPlayfieldLights: false })); + table = await Table.load(new NodeBinaryReader(three.fixturePath('table-empty.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb({ applyTextures: false, exportPlayfieldLights: false })); }); it('should generate the correct playfield mesh', async () => { @@ -48,21 +50,21 @@ describe('The VPinball table generator', () => { }); it('should read the table script correctly', () => { - const script = vpt.getTableScript(); + const script = table.getTableScript(); expect(script).to.equal(`Option Explicit\r\n`); }); it('should read the table info correctly', async () => { - expect(vpt.info!.TableRules).to.equal('Rules'); - expect(vpt.info!.AuthorName).to.equal('Table Author'); - expect(vpt.info!.TableName).to.equal('Table Name'); - expect(vpt.info!.TableBlurb).to.equal('Short Blurb'); - expect(vpt.info!.ReleaseDate).to.equal('2019-04-14'); - expect(vpt.info!.AuthorEmail).to.equal('test@vpdb.io'); - expect(vpt.info!.customdata1).to.equal('customvalue1'); - expect(vpt.info!.AuthorWebSite).to.equal('https://vpdb.io'); - expect(vpt.info!.TableVersion).to.equal('Version'); - expect(vpt.info!.TableDescription).to.equal('Description'); + expect(table.info!.TableRules).to.equal('Rules'); + expect(table.info!.AuthorName).to.equal('Table Author'); + expect(table.info!.TableName).to.equal('Table Name'); + expect(table.info!.TableBlurb).to.equal('Short Blurb'); + expect(table.info!.ReleaseDate).to.equal('2019-04-14'); + expect(table.info!.AuthorEmail).to.equal('test@vpdb.io'); + expect(table.info!.customdata1).to.equal('customvalue1'); + expect(table.info!.AuthorWebSite).to.equal('https://vpdb.io'); + expect(table.info!.TableVersion).to.equal('Version'); + expect(table.info!.TableDescription).to.equal('Description'); }); it('should not crash when reading a corrupt file', async () => { diff --git a/lib/vpt/table/table.ts b/lib/vpt/table/table.ts index 479a783d..a1da75ee 100644 --- a/lib/vpt/table/table.ts +++ b/lib/vpt/table/table.ts @@ -17,28 +17,28 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Group, Scene } from 'three'; import { Event } from '../../game/event'; import { IAnimatable } from '../../game/ianimatable'; import { IHittable } from '../../game/ihittable'; import { IItem } from '../../game/iitem'; import { IMovable } from '../../game/imovable'; import { IPlayable } from '../../game/iplayable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { IScriptable } from '../../game/iscriptable'; +import { IImage } from '../../gltf/image'; import { IBinaryReader, Storage } from '../../io/ole-doc'; import { degToRad, f4 } from '../../math/float'; import { FRect3D } from '../../math/frect3d'; import { Vertex3D } from '../../math/vertex3d'; import { HitObject } from '../../physics/hit-object'; import { HitPlane } from '../../physics/hit-plane'; +import { IRenderApi } from '../../render/irender-api'; import { Transpiler } from '../../scripting/transpiler'; import { logger } from '../../util/logger'; import { Bumper } from '../bumper/bumper'; import { Flipper } from '../flipper/flipper'; import { Gate } from '../gate/gate'; import { HitTarget } from '../hit-target/hit-target'; -import { Meshes } from '../item-data'; import { Kicker } from '../kicker/kicker'; import { Light } from '../light/light'; import { Material } from '../material'; @@ -53,11 +53,10 @@ import { Texture } from '../texture'; import { TimerItem } from '../timer-item'; import { Trigger } from '../trigger/trigger'; import { TableData } from './table-data'; -import { TableExporter, VpTableExporterOptions } from './table-exporter'; +import { TableExportOptions } from './table-exporter'; import { TableHitGenerator } from './table-hit-generator'; import { LoadedTable, TableLoader } from './table-loader'; import { TableMeshGenerator } from './table-mesh-generator'; -import { IImage } from '../../gltf/image'; /** * A Visual Pinball table. @@ -110,7 +109,7 @@ export class Table implements IRenderable { this.items = loadedTable.items; if (loadedTable.data) { this.data = loadedTable.data; - this.meshGenerator = new TableMeshGenerator(loadedTable.data); + this.meshGenerator = new TableMeshGenerator(this); this.hitGenerator = new TableHitGenerator(loadedTable.data); } if (loadedTable.info) { @@ -287,25 +286,15 @@ export class Table implements IRenderable { return this.data.tableheight; } - public async exportScene(opts?: VpTableExporterOptions): Promise { - const exporter = new TableExporter(this, opts || {}); - return await exporter.createScene(); - } - - public async exportGltf(opts?: VpTableExporterOptions): Promise { - const exporter = new TableExporter(this, opts || {}); - return await exporter.exportGltf(); - } - - public async exportGlb(opts?: VpTableExporterOptions): Promise { - const exporter = new TableExporter(this, opts || {}); - return await exporter.exportGlb(); - } + // public async exportGltf(opts?: TableExportOptions): Promise { + // const exporter = new TableExporter(this, opts || {}); + // return await exporter.exportGltf(); + // } - public async exportElement(renderable: IRenderable, opts?: VpTableExporterOptions): Promise { - const exporter = new TableExporter(this, opts || {}); - return await exporter.createItemMesh(renderable); - } + // public async exportGlb(opts?: TableExportOptions): Promise { + // const exporter = new TableExporter(new ThreeRenderApi(), this, opts || {}); + // return await exporter.exportGlb(); + // } public async streamStorage(name: string, streamer: (stg: Storage) => Promise): Promise { return this.loader.streamStorage(name, streamer); @@ -323,12 +312,12 @@ export class Table implements IRenderable { return true; } - public getMeshes(table: Table, opts: VpTableExporterOptions): Meshes { + public getMeshes(table: Table, opts: TableExportOptions): Meshes { /* istanbul ignore if */ if (!this.data) { throw new Error('Table data is not loaded. Load table with tableDataOnly = false.'); } - const geometry = this.meshGenerator!.getMesh(this, opts); + const geometry = this.meshGenerator!.getPlayfieldMesh(this, opts); return { playfield: { geometry, @@ -338,6 +327,16 @@ export class Table implements IRenderable { }; } + /** + * Generates the top-most node for the render engine that contains the entire table. + * + * @param renderApi Render API + * @param opts Which elements to generate + */ + public async generateTableNode(renderApi: IRenderApi, opts: TableExportOptions = {}): Promise { + return await this.meshGenerator!.generateTableNode(renderApi, opts); + } + public prepareToPlay() { for (const primitive of Object.values(this.primitives)) { primitive.clearMesh(); @@ -405,3 +404,46 @@ export interface TableLoadOptions { */ loadTableScript?: boolean; } + +export interface TableGenerateOptions { + exportPlayfield?: boolean; + exportPrimitives?: boolean; + exportRubbers?: boolean; + exportSurfaces?: boolean; + exportFlippers?: boolean; + exportBumpers?: boolean; + exportRamps?: boolean; + exportLightBulbs?: boolean; + exportPlayfieldLights?: boolean; + exportLightBulbLights?: boolean; + exportHitTargets?: boolean; + exportGates?: boolean; + exportKickers?: boolean; + exportTriggers?: boolean; + exportSpinners?: boolean; + exportPlungers?: boolean; + gltfOptions?: TableGenerateGltfOptions; +} + +export interface TableGenerateGltfOptions { + binary?: boolean; + optimizeImages?: boolean; + trs?: boolean; + onlyVisible?: boolean; + truncateDrawRange?: boolean; + embedImages?: boolean; + animations?: any[]; + forceIndices?: boolean; + forcePowerOfTwoTextures?: boolean; + compressVertices?: boolean; + versionString?: string; + dracoOptions?: { + compressionLevel?: number; + quantizePosition?: number; + quantizeNormal?: number; + quantizeTexcoord?: number; + quantizeColor?: number; + quantizeSkin?: number; + unifiedQuantization?: boolean; + }; +} diff --git a/lib/vpt/texture.ts b/lib/vpt/texture.ts index f5d72ecc..da53c3f5 100644 --- a/lib/vpt/texture.ts +++ b/lib/vpt/texture.ts @@ -18,10 +18,10 @@ */ import { basename, resolve as resolvePath } from 'path'; -import { Storage } from '..'; import { IImage } from '../gltf/image'; import { LzwReader } from '../gltf/lzw-reader'; import { BiffParser } from '../io/biff-parser'; +import { Storage } from '../io/ole-doc'; import { getRawImage, loadImage, streamImage } from '../refs.node'; import { logger } from '../util/logger'; import { Binary } from './binary'; diff --git a/lib/vpt/trigger/trigger-mesh.spec.ts b/lib/vpt/trigger/trigger-mesh.spec.ts index 0b6b6338..99e6f04f 100644 --- a/lib/vpt/trigger/trigger-mesh.spec.ts +++ b/lib/vpt/trigger/trigger-mesh.spec.ts @@ -22,6 +22,7 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { ThreeHelper } from '../../../test/three.helper'; import { NodeBinaryReader } from '../../io/binary-reader.node'; import { Table } from '../table/table'; +import { TableExporter } from '../table/table-exporter'; const three = new ThreeHelper(); @@ -30,8 +31,9 @@ describe('The VPinball trigger generator', () => { let gltf: GLTF; before(async () => { - const vpt = await Table.load(new NodeBinaryReader(three.fixturePath('table-trigger.vpx'))); - gltf = await three.loadGlb(await vpt.exportGlb()); + const table = await Table.load(new NodeBinaryReader(three.fixturePath('table-trigger.vpx'))); + const exporter = new TableExporter(table); + gltf = await three.loadGlb(await exporter.exportGlb()); }); it('should generate a button trigger mesh', async () => { diff --git a/lib/vpt/trigger/trigger.ts b/lib/vpt/trigger/trigger.ts index 25691479..fe26f378 100644 --- a/lib/vpt/trigger/trigger.ts +++ b/lib/vpt/trigger/trigger.ts @@ -20,14 +20,13 @@ import { EventProxy } from '../../game/event-proxy'; import { IAnimatable, IAnimation } from '../../game/ianimatable'; import { IHittable } from '../../game/ihittable'; -import { IRenderable } from '../../game/irenderable'; +import { IRenderable, Meshes } from '../../game/irenderable'; import { IScriptable } from '../../game/iscriptable'; import { Player } from '../../game/player'; import { Storage } from '../../io/ole-doc'; import { Matrix3D } from '../../math/matrix3d'; import { HitObject } from '../../physics/hit-object'; import { IRenderApi } from '../../render/irender-api'; -import { Meshes } from '../item-data'; import { Table } from '../table/table'; import { TriggerAnimation } from './trigger-animation'; import { TriggerApi } from './trigger-api'; @@ -127,7 +126,7 @@ export class Trigger implements IRenderable, IHittable, IAnimatable(obj: OBJECT, renderApi: IRenderApi, table: Table, player: Player): void { + public applyState(obj: NODE, renderApi: IRenderApi, table: Table, player: Player): void { const matrix = Matrix3D.claim().setTranslation(0, 0, -this.state.heightOffset); renderApi.applyMatrixToObject(matrix, obj); Matrix3D.release(matrix); diff --git a/test/physics.helper.ts b/test/physics.helper.ts index 2dbfecee..3b2b4620 100644 --- a/test/physics.helper.ts +++ b/test/physics.helper.ts @@ -19,11 +19,9 @@ import { PlayerPhysics } from '../lib/game/player-physics'; import { Ball } from '../lib/vpt/ball/ball'; -import { Table } from '../lib'; import { Vertex3D } from '../lib/math/vertex3d'; -import { Spinner } from '../lib/vpt/spinner/spinner'; -import { radToDeg } from '../lib/math/float'; import { Player } from '../lib/game/player'; +import { Table } from '../lib/vpt/table/table'; /** * Simulates a given number of milliseconds. diff --git a/test/setup.ts b/test/setup.ts index b31b8136..7e3a7382 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -17,7 +17,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Logger } from '../lib'; +import { Logger } from '../lib/util/logger'; before(() => { // disable logging diff --git a/test/vpt/invisible-items.spec.ts b/test/vpt/invisible-items.spec.ts index 522dd754..46b301f0 100644 --- a/test/vpt/invisible-items.spec.ts +++ b/test/vpt/invisible-items.spec.ts @@ -18,9 +18,9 @@ */ import { ThreeHelper } from '../three.helper'; -import { Table } from '../../lib'; import { expect } from 'chai'; import { NodeBinaryReader } from '../../lib/io/binary-reader.node'; +import { Table } from '../../lib/vpt/table/table'; const three = new ThreeHelper(); diff --git a/test/vpt/texture.spec.ts b/test/vpt/texture.spec.ts index 738e2e12..a1b726c3 100644 --- a/test/vpt/texture.spec.ts +++ b/test/vpt/texture.spec.ts @@ -18,13 +18,13 @@ */ import { ThreeHelper } from '../three.helper'; -import { Table } from '../../lib'; import { readFileSync, writeFileSync } from 'fs'; import { createDiff } from 'looks-same'; import * as sharp from 'sharp'; import { expect } from 'chai'; import looksSame = require('looks-same'); import { NodeBinaryReader } from '../../lib/io/binary-reader.node'; +import { Table } from '../../lib/vpt/table/table'; const three = new ThreeHelper(); const imgDiffTolerance = 7; From ddda9787673a8af05ada03160e2ce581294c6448 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 9 Sep 2019 00:36:17 +0200 Subject: [PATCH 06/14] renderer: Move light- and playfield generation to render API. --- lib/game/irenderable.ts | 14 +- lib/render/irender-api.ts | 6 +- lib/render/threejs/three-converter.ts | 22 +-- .../threejs/three-playfield-mesh-generator.ts | 134 ++++++++++++++++++ lib/render/threejs/three-render-api.ts | 13 +- lib/vpt/ball/ball.ts | 2 +- lib/vpt/bumper/bumper.ts | 4 +- lib/vpt/flipper/flipper.ts | 4 +- lib/vpt/gate/gate.ts | 4 +- lib/vpt/hit-target/hit-target.ts | 2 +- lib/vpt/kicker/kicker.ts | 2 +- lib/vpt/light/light-mesh-generator.ts | 95 +++---------- lib/vpt/light/light.ts | 13 +- lib/vpt/plunger/plunger.ts | 4 +- lib/vpt/primitive/primitive.ts | 2 +- lib/vpt/ramp/ramp.ts | 4 +- lib/vpt/rubber/rubber.ts | 3 +- lib/vpt/spinner/spinner.ts | 4 +- lib/vpt/surface/surface.ts | 4 +- lib/vpt/table/table-mesh-generator.ts | 121 ++-------------- lib/vpt/table/table.ts | 4 +- lib/vpt/trigger/trigger.ts | 2 +- 22 files changed, 227 insertions(+), 236 deletions(-) create mode 100644 lib/render/threejs/three-playfield-mesh-generator.ts diff --git a/lib/game/irenderable.ts b/lib/game/irenderable.ts index 7fc1596e..74c18760 100644 --- a/lib/game/irenderable.ts +++ b/lib/game/irenderable.ts @@ -18,29 +18,29 @@ */ import { BufferGeometry, Material as ThreeMaterial, MeshStandardMaterial } from 'three'; +import { IRenderApi } from '../render/irender-api'; import { Material } from '../vpt/material'; import { Mesh } from '../vpt/mesh'; -import { Table } from '../vpt/table/table'; -import { TableExportOptions } from '../vpt/table/table-exporter'; +import { Table, TableGenerateOptions } from '../vpt/table/table'; import { Texture } from '../vpt/texture'; import { IItem } from './iitem'; export interface IRenderable extends IItem { - getMeshes(table: Table, opts: TableExportOptions): Meshes; + getMeshes(table: Table, renderApi: IRenderApi, opts: TableGenerateOptions): Meshes; isVisible(table: Table): boolean; postProcessMaterial?(table: Table, geometry: BufferGeometry, material: MeshStandardMaterial): MeshStandardMaterial | MeshStandardMaterial[]; } -export interface Meshes { - [key: string]: RenderInfo; +export interface Meshes { + [key: string]: RenderInfo; } -export interface RenderInfo { +export interface RenderInfo { mesh?: Mesh; - geometry?: BufferGeometry; + geometry?: GEOMETRY; map?: Texture; normalMap?: Texture; material?: Material; diff --git a/lib/render/irender-api.ts b/lib/render/irender-api.ts index 664c53f5..0316b591 100644 --- a/lib/render/irender-api.ts +++ b/lib/render/irender-api.ts @@ -21,7 +21,7 @@ import { IRenderable } from '../game/irenderable'; import { Matrix3D } from '../math/matrix3d'; import { LightData } from '../vpt/light/light-data'; import { Mesh } from '../vpt/mesh'; -import { Table } from '../vpt/table/table'; +import { Table, TableGenerateOptions } from '../vpt/table/table'; export interface IRenderApi { @@ -43,6 +43,8 @@ export interface IRenderApi { createLightGeometry(lightData: LightData, table: Table): GEOMETRY; + createPlayfieldGeometry(table: Table, opts: TableGenerateOptions): GEOMETRY; + createPointLight(lightData: LightData): POINT_LIGHT; } @@ -51,3 +53,5 @@ export interface MeshConvertOptions { applyTextures?: boolean; optimizeTextures?: boolean; } + +export interface TableExportOptions extends TableGenerateOptions, MeshConvertOptions { } diff --git a/lib/render/threejs/three-converter.ts b/lib/render/threejs/three-converter.ts index 8bc42e98..13ecf4e9 100644 --- a/lib/render/threejs/three-converter.ts +++ b/lib/render/threejs/three-converter.ts @@ -18,11 +18,12 @@ */ import { + BufferGeometry, Color, DoubleSide, Group, Mesh as ThreeMesh, - MeshStandardMaterial, + MeshStandardMaterial, Object3D, PointLight, RGBAFormat, RGBFormat, Texture as ThreeTexture, @@ -30,31 +31,32 @@ import { import { IRenderable, RenderInfo } from '../../game/irenderable'; import { IImage } from '../../gltf/image'; import { logger } from '../../util/logger'; -import { Table } from '../../vpt/table/table'; +import { Table, TableGenerateOptions } from '../../vpt/table/table'; import { Texture } from '../../vpt/texture'; -import { MeshConvertOptions } from '../irender-api'; +import { IRenderApi, MeshConvertOptions } from '../irender-api'; export class ThreeConverter { - private readonly opts: MeshConvertOptions; + private readonly opts: TableGenerateOptions & MeshConvertOptions; - constructor(opts: MeshConvertOptions) { + constructor(opts: TableGenerateOptions & MeshConvertOptions) { this.opts = opts; } - public async createObject(renderable: IRenderable, table: Table): Promise { - const objects = renderable.getMeshes(table, this.opts); + public async createObject(renderable: IRenderable, table: Table, renderApi: IRenderApi): Promise { + const objects = renderable.getMeshes(table, renderApi, this.opts); const itemGroup = new Group(); itemGroup.matrixAutoUpdate = false; itemGroup.name = renderable.getName(); - for (const obj of Object.values(objects)) { + let obj: RenderInfo; + for (obj of Object.values>(objects)) { const mesh = await this.createMesh(renderable, obj, table); itemGroup.add(mesh); } return itemGroup; } - private async createMesh(renderable: IRenderable, obj: RenderInfo, table: Table): Promise { + private async createMesh(renderable: IRenderable, obj: RenderInfo, table: Table): Promise { /* istanbul ignore if */ if (!obj.geometry && !obj.mesh) { throw new Error('Mesh export must either provide mesh or geometry.'); @@ -69,7 +71,7 @@ export class ThreeConverter { return mesh; } - private async getMaterial(obj: RenderInfo, table: Table): Promise { + private async getMaterial(obj: RenderInfo, table: Table): Promise { const material = new MeshStandardMaterial(); const name = (obj.geometry || obj.mesh!).name; material.name = `material:${name}`; diff --git a/lib/render/threejs/three-playfield-mesh-generator.ts b/lib/render/threejs/three-playfield-mesh-generator.ts new file mode 100644 index 00000000..fbf9cc4a --- /dev/null +++ b/lib/render/threejs/three-playfield-mesh-generator.ts @@ -0,0 +1,134 @@ +/* + * VPDB - Virtual Pinball Database + * Copyright (C) 2019 freezy + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { BufferGeometry, ExtrudeBufferGeometry, Shape, Vector2 } from 'three'; +import { Table, TableGenerateOptions } from '../../vpt/table/table'; + +export class ThreePlayfieldMeshGenerator { + + public createPlayfieldGeometry(table: Table, opts: TableGenerateOptions): BufferGeometry { + + /* istanbul ignore if */ + if (!table.data) { + throw new Error('Table data is not loaded. Load table with tableDataOnly = false.'); + } + + let geometry: BufferGeometry; + const dim = table.getDimensions(); + + const pfShape = new Shape(); + pfShape.moveTo(table.data.left, table.data.top); + pfShape.lineTo(table.data.right, table.data.top); + pfShape.lineTo(table.data.right, table.data.bottom); + pfShape.lineTo(table.data.left, table.data.bottom); + pfShape.lineTo(table.data.left, table.data.top); + + // drill holes if playfield lights are rendered separately. + // if (opts.exportPlayfieldLights) { + // pfShape.holes = Object.values(table.lights) + // .filter(l => l.isPlayfieldLight(table)) + // .map(l => l.getPath(table)); + // } + + const invTableWidth = 1.0 / dim.width; + const invTableHeight = 1.0 / dim.height; + + geometry = new ExtrudeBufferGeometry(pfShape, { + depth: Table.playfieldThickness, + bevelEnabled: false, + steps: 1, + UVGenerator: { + generateSideWallUV(g: ExtrudeBufferGeometry, vertices: number[], indexA: number, indexB: number, indexC: number, indexD: number): Vector2[] { + return [ + new Vector2(0, 0), + new Vector2(0, 0), + new Vector2(0, 0), + new Vector2(0, 0), + ]; + }, + generateTopUV(g: ExtrudeBufferGeometry, vertices: number[], indexA: number, indexB: number, indexC: number): Vector2[] { + const ax = vertices[indexA * 3]; + const ay = vertices[indexA * 3 + 1]; + const bx = vertices[indexB * 3]; + const by = vertices[indexB * 3 + 1]; + const cx = vertices[indexC * 3]; + const cy = vertices[indexC * 3 + 1]; + return [ + new Vector2(ax * invTableWidth, 1 - ay * invTableHeight), + new Vector2(bx * invTableWidth, 1 - by * invTableHeight), + new Vector2(cx * invTableWidth, 1 - cy * invTableHeight), + ]; + }, + }, + }); + + return geometry; + } + + // private getPlayfield2DMesh(): Mesh { + // const rgv: Vertex3DNoTex2[] = []; + // for (let i = 0; i < 7; i++) { + // rgv.push(new Vertex3DNoTex2()); + // } + // rgv[0].x = this.data.left; rgv[0].y = this.data.top; rgv[0].z = this.data.tableheight; + // rgv[1].x = this.data.right; rgv[1].y = this.data.top; rgv[1].z = this.data.tableheight; + // rgv[2].x = this.data.right; rgv[2].y = this.data.bottom; rgv[2].z = this.data.tableheight; + // rgv[3].x = this.data.left; rgv[3].y = this.data.bottom; rgv[3].z = this.data.tableheight; + // + // // These next 4 vertices are used just to set the extents + // rgv[4].x = this.data.left; rgv[4].y = this.data.top; rgv[4].z = this.data.tableheight + Table.playfieldThickness; + // rgv[5].x = this.data.left; rgv[5].y = this.data.bottom; rgv[5].z = this.data.tableheight + Table.playfieldThickness; + // rgv[6].x = this.data.right; rgv[6].y = this.data.bottom; rgv[6].z = this.data.tableheight + Table.playfieldThickness; + // //rgv[7].x=g_pplayer->m_ptable->m_right; rgv[7].y=g_pplayer->m_ptable->m_top; rgv[7].z=50.0f; + // + // for (let i = 0; i < 4; ++i) { + // rgv[i].nx = 0; + // rgv[i].ny = 0; + // rgv[i].nz = 1.0; + // + // rgv[i].tv = (i & 2) ? 1.0 : 0.0; + // rgv[i].tu = (i === 1 || i === 2) ? 1.0 : 0.0; + // } + // + // const playfieldPolyIndices = [ 0, 1, 3, 0, 3, 2, 2, 3, 5, 6 ]; + // Mesh.setNormal(rgv, playfieldPolyIndices.splice(6), 4); + // + // const buffer: Vertex3DNoTex2[] = []; + // for (let i = 0; i < 7; i++) { + // buffer.push(new Vertex3DNoTex2()); + // } + // let offs = 0; + // for (let y = 0; y <= 1; ++y) { + // for (let x = 0; x <= 1; ++x) { + // buffer[offs].x = (x & 1) ? rgv[1].x : rgv[0].x; + // buffer[offs].y = (y & 1) ? rgv[2].y : rgv[0].y; + // buffer[offs].z = rgv[0].z; + // + // buffer[offs].tu = (x & 1) ? rgv[1].tu : rgv[0].tu; + // buffer[offs].tv = (y & 1) ? rgv[2].tv : rgv[0].tv; + // + // buffer[offs].nx = rgv[0].nx; + // buffer[offs].ny = rgv[0].ny; + // buffer[offs].nz = rgv[0].nz; + // ++offs; + // } + // } + // return new Mesh(buffer, playfieldPolyIndices); + // } +} diff --git a/lib/render/threejs/three-render-api.ts b/lib/render/threejs/three-render-api.ts index 1027ac78..c9a8a564 100644 --- a/lib/render/threejs/three-render-api.ts +++ b/lib/render/threejs/three-render-api.ts @@ -17,16 +17,17 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { BufferGeometry, Group, Matrix4, Object3D, PointLight } from 'three'; +import { BufferGeometry, ExtrudeBufferGeometry, Group, Matrix4, Object3D, PointLight, Shape, Vector2 } from 'three'; import { IRenderable } from '../../game/irenderable'; import { Matrix3D } from '../../math/matrix3d'; import { Pool } from '../../util/object-pool'; import { LightData } from '../../vpt/light/light-data'; import { Mesh } from '../../vpt/mesh'; -import { Table } from '../../vpt/table/table'; +import { Table, TableGenerateOptions } from '../../vpt/table/table'; import { IRenderApi, MeshConvertOptions } from '../irender-api'; import { ThreeConverter } from './three-converter'; import { ThreeLightMeshGenerator } from './three-light-mesh-generator'; +import { ThreePlayfieldMeshGenerator } from './three-playfield-mesh-generator'; export class ThreeRenderApi implements IRenderApi { @@ -34,6 +35,7 @@ export class ThreeRenderApi implements IRenderApi { - return this.converter.createObject(renderable, table); + return this.converter.createObject(renderable, table, this); } public createLightGeometry(lightData: LightData, table: Table): BufferGeometry { return this.lightGenerator.createLight(lightData, table); } + + public createPlayfieldGeometry(table: Table, opts: TableGenerateOptions): BufferGeometry { + return this.playfieldGenerator.createPlayfieldGeometry(table, opts); + } } diff --git a/lib/vpt/ball/ball.ts b/lib/vpt/ball/ball.ts index 55039bed..175e5538 100644 --- a/lib/vpt/ball/ball.ts +++ b/lib/vpt/ball/ball.ts @@ -115,7 +115,7 @@ export class Ball implements IPlayable, IMovable, IRenderable { return [ this.hit ]; } - public getMeshes(table: Table): Meshes { + public getMeshes(table: Table): Meshes { return { ball: { mesh: this.meshGenerator.getMesh().transform(Matrix3D.RIGHT_HANDED) } }; } diff --git a/lib/vpt/bumper/bumper.ts b/lib/vpt/bumper/bumper.ts index cafe7348..9d6d3e8a 100644 --- a/lib/vpt/bumper/bumper.ts +++ b/lib/vpt/bumper/bumper.ts @@ -101,8 +101,8 @@ export class Bumper implements IRenderable, IHittable, IAnimatable return this.events!; } - public getMeshes(table: Table): Meshes { - const meshes: Meshes = {}; + public getMeshes(table: Table): Meshes { + const meshes: Meshes = {}; const bumper = this.meshGenerator.getMeshes(table); if (bumper.base) { meshes.base = { diff --git a/lib/vpt/flipper/flipper.ts b/lib/vpt/flipper/flipper.ts index 3300eff8..87b25f65 100644 --- a/lib/vpt/flipper/flipper.ts +++ b/lib/vpt/flipper/flipper.ts @@ -101,8 +101,8 @@ export class Flipper implements IRenderable, IPlayable, IMovable, return this.events!; } - public getMeshes(table: Table): Meshes { - const meshes: Meshes = {}; + public getMeshes(table: Table): Meshes { + const meshes: Meshes = {}; const matrix = this.getMatrix().toRightHanded(); const flipper = this.mesh.generateMeshes(this.data, table); diff --git a/lib/vpt/gate/gate.ts b/lib/vpt/gate/gate.ts index 7dea0bf6..59b404e7 100644 --- a/lib/vpt/gate/gate.ts +++ b/lib/vpt/gate/gate.ts @@ -87,8 +87,8 @@ export class Gate implements IRenderable, IPlayable, IMovable, IHitta return this.data.isCollidable; } - public getMeshes(table: Table): Meshes { - const meshes: Meshes = {}; + public getMeshes(table: Table): Meshes { + const meshes: Meshes = {}; const gate = this.meshGenerator.getMeshes(table); // wire mesh diff --git a/lib/vpt/hit-target/hit-target.ts b/lib/vpt/hit-target/hit-target.ts index 7f5f5067..db4975cc 100644 --- a/lib/vpt/hit-target/hit-target.ts +++ b/lib/vpt/hit-target/hit-target.ts @@ -90,7 +90,7 @@ export class HitTarget implements IRenderable, IHittable, IAnimatable(table: Table): Meshes { return { hitTarget: { mesh: this.meshGenerator.getMesh(table).transform(Matrix3D.RIGHT_HANDED), diff --git a/lib/vpt/kicker/kicker.ts b/lib/vpt/kicker/kicker.ts index 7484b000..5dcd5bbb 100644 --- a/lib/vpt/kicker/kicker.ts +++ b/lib/vpt/kicker/kicker.ts @@ -85,7 +85,7 @@ export class Kicker extends EventEmitter implements IRenderable, IHittable, IBal return this.events!; } - public getMeshes(table: Table): Meshes { + public getMeshes(table: Table): Meshes { return { kicker: { mesh: this.meshGenerator.getMesh(table).transform(Matrix3D.RIGHT_HANDED), diff --git a/lib/vpt/light/light-mesh-generator.ts b/lib/vpt/light/light-mesh-generator.ts index b5e4c98f..8c6e3fd1 100644 --- a/lib/vpt/light/light-mesh-generator.ts +++ b/lib/vpt/light/light-mesh-generator.ts @@ -17,10 +17,9 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { ExtrudeBufferGeometry, Path, Shape, Vector2 } from 'three'; import { bulbLightMesh } from '../../../res/meshes/bulb-light-mesh'; import { bulbSocketMesh } from '../../../res/meshes/bulb-socket-mesh'; -import { SplineVertex } from '../../math/spline-vertex'; +import { IRenderApi } from '../../render/irender-api'; import { Mesh } from '../mesh'; import { Table } from '../table/table'; import { LightData } from './light-data'; @@ -33,24 +32,19 @@ export class LightMeshGenerator { this.data = data; } - public getMeshes(table: Table): LightMeshes { + public getMeshes(table: Table, renderApi: IRenderApi): LightMeshes { if (this.data.isBulbLight()) { return this.getBulbMeshes(table); } return { - surfaceLight: this.getSurfaceGeometry(table, Table.playfieldThickness / 2), + surfaceLight: renderApi.createLightGeometry(this.data, table), }; } - public getShape(table: Table): Shape { - const vvertex = SplineVertex.getCentralCurve(this.data.dragPoints, table.getDetailLevel(), -1); - return this.getPathFromPoints(vvertex.map(v => new Vector2(v.x, v.y)), new Shape()); - } - - public getPath(table: Table): Path { - const vvertex = SplineVertex.getCentralCurve(this.data.dragPoints, table.getDetailLevel(), -1); - return this.getPathFromPoints(vvertex.map(v => new Vector2(v.x, v.y)), new Path()); - } + // public getPath(table: Table): Path { + // const vvertex = SplineVertex.getCentralCurve(this.data.dragPoints, table.getDetailLevel(), -1); + // return this.getPathFromPoints(vvertex.map(v => new Vector2(v.x, v.y)), new Path()); + // } // public getExtendedPath(table: Table, distance: number): Path { // const path = this.getPath(table); @@ -70,65 +64,20 @@ export class LightMeshGenerator { // return this.getPathFromPoints(points); // } - public getSurfaceGeometry(table: Table, depth = 5, bevel = 0.5): ExtrudeBufferGeometry { - - const shape = this.getShape(table); - const dim = table.getDimensions(); - const invTableWidth = 1.0 / dim.width; - const invTableHeight = 1.0 / dim.height; - - const geometry = new ExtrudeBufferGeometry(shape, { - depth, - bevelEnabled: bevel > 0, - bevelSegments: 1, - steps: 1, - bevelSize: bevel, - bevelThickness: bevel, - UVGenerator: { - generateSideWallUV(g: ExtrudeBufferGeometry, vertices: number[], indexA: number, indexB: number, indexC: number, indexD: number): Vector2[] { - return [ - new Vector2( 0, 0), - new Vector2( 0, 0), - new Vector2( 0, 0), - new Vector2( 0, 0), - ]; - }, - generateTopUV(g: ExtrudeBufferGeometry, vertices: number[], indexA: number, indexB: number, indexC: number): Vector2[] { - const ax = vertices[indexA * 3]; - const ay = vertices[indexA * 3 + 1]; - const bx = vertices[indexB * 3]; - const by = vertices[indexB * 3 + 1]; - const cx = vertices[indexC * 3]; - const cy = vertices[indexC * 3 + 1]; - return [ - new Vector2(ax * invTableWidth, 1 - ay * invTableHeight), - new Vector2(bx * invTableWidth, 1 - by * invTableHeight), - new Vector2(cx * invTableWidth, 1 - cy * invTableHeight), - ]; - }, - }, - }); - if (this.data.szSurface) { - geometry.translate(0, 0, -table.getSurfaceHeight(this.data.szSurface, 0, 0)); - } - geometry.name = `surface.light-${this.data.getName()}`; - return geometry; - } - - private getPathFromPoints(points: Vector2[], path: T): T { - /* istanbul ignore if */ - if (points.length === 0) { - throw new Error('Cannot get path from no points.'); - } - path.moveTo(points[0].x, points[0].y); - for (const v of points.slice(1)) { - path.lineTo(v.x, v.y); - } - //path.moveTo(points[0].x, points[0].y); - return path; - } + // private getPathFromPoints(points: Vector2[], path: T): T { + // /* istanbul ignore if */ + // if (points.length === 0) { + // throw new Error('Cannot get path from no points.'); + // } + // path.moveTo(points[0].x, points[0].y); + // for (const v of points.slice(1)) { + // path.lineTo(v.x, v.y); + // } + // //path.moveTo(points[0].x, points[0].y); + // return path; + // } - private getBulbMeshes(table: Table): LightMeshes { + private getBulbMeshes(table: Table): LightMeshes { const lightMesh = bulbLightMesh.clone(`bulb.light-${this.data.getName()}`); const height = table.getSurfaceHeight(this.data.szSurface, this.data.vCenter.x, this.data.vCenter.y) * table.getScaleZ(); for (const vertex of lightMesh.vertices) { @@ -151,8 +100,8 @@ export class LightMeshGenerator { } } -export interface LightMeshes { +export interface LightMeshes { light?: Mesh; socket?: Mesh; - surfaceLight?: ExtrudeBufferGeometry; + surfaceLight?: GEOMETRY; } diff --git a/lib/vpt/light/light.ts b/lib/vpt/light/light.ts index ceae4513..45ee4f97 100644 --- a/lib/vpt/light/light.ts +++ b/lib/vpt/light/light.ts @@ -21,6 +21,7 @@ import { BufferGeometry, MeshStandardMaterial } from 'three'; import { IRenderable, Meshes } from '../../game/irenderable'; import { Storage } from '../../io/ole-doc'; import { Matrix3D } from '../../math/matrix3d'; +import { IRenderApi } from '../../render/irender-api'; import { Material } from '../material'; import { Table } from '../table/table'; import { LightData } from './light-data'; @@ -65,8 +66,8 @@ export class Light implements IRenderable { return true; // we filter by bulb/playfield light } - public getMeshes(table: Table): Meshes { - const light = this.meshGenerator.getMeshes(table); + public getMeshes(table: Table, renderApi: IRenderApi): Meshes { + const light = this.meshGenerator.getMeshes(table, renderApi); if (light.surfaceLight) { return { surfaceLight: { @@ -75,7 +76,7 @@ export class Light implements IRenderable { }, }; } - const meshes: Meshes = {}; + const meshes: Meshes = {}; if (light.light) { const lightMaterial = new Material(); lightMaterial.cBase = 0; @@ -134,9 +135,9 @@ export class Light implements IRenderable { return this.data.isPlayfieldLight(table); } - public getPath(table: Table) { - return this.meshGenerator.getPath(table); - } + // public getPath(table: Table) { + // return this.meshGenerator.getPath(table); + // } public postProcessMaterial(table: Table, geometry: BufferGeometry, material: MeshStandardMaterial): MeshStandardMaterial | MeshStandardMaterial[] { if (!this.data.isSurfaceLight(table)) { diff --git a/lib/vpt/plunger/plunger.ts b/lib/vpt/plunger/plunger.ts index 69e8e905..0c884a87 100644 --- a/lib/vpt/plunger/plunger.ts +++ b/lib/vpt/plunger/plunger.ts @@ -76,9 +76,9 @@ export class Plunger implements IRenderable, IPlayable, IMovable, return this.state!; } - public getMeshes(table: Table): Meshes { + public getMeshes(table: Table): Meshes { const plunger = this.meshGenerator.generateMeshes(0, table); - const meshes: Meshes = {}; + const meshes: Meshes = {}; const material = table.getMaterial(this.data.szMaterial); const map = table.getTexture(this.data.szImage); diff --git a/lib/vpt/primitive/primitive.ts b/lib/vpt/primitive/primitive.ts index 8c5f221e..16c8df7c 100644 --- a/lib/vpt/primitive/primitive.ts +++ b/lib/vpt/primitive/primitive.ts @@ -71,7 +71,7 @@ export class Primitive implements IRenderable, IHittable, IScriptable(table: Table): Meshes { return { primitive: { mesh: this.getMesh(table).clone().transform(Matrix3D.RIGHT_HANDED), diff --git a/lib/vpt/ramp/ramp.ts b/lib/vpt/ramp/ramp.ts index 69b20864..cc5c76e4 100644 --- a/lib/vpt/ramp/ramp.ts +++ b/lib/vpt/ramp/ramp.ts @@ -92,8 +92,8 @@ export class Ramp implements IRenderable, IHittable { return this.events!; } - public getMeshes(table: Table): Meshes { - const meshes: Meshes = {}; + public getMeshes(table: Table): Meshes { + const meshes: Meshes = {}; const ramp = this.meshGenerator.getMeshes(table); if (ramp.wire1) { diff --git a/lib/vpt/rubber/rubber.ts b/lib/vpt/rubber/rubber.ts index 08a1ac82..545d5dad 100644 --- a/lib/vpt/rubber/rubber.ts +++ b/lib/vpt/rubber/rubber.ts @@ -65,8 +65,7 @@ export class Rubber implements IRenderable, IHittable { return this.data.isCollidable; } - public getMeshes(table: Table): Meshes { - + public getMeshes(table: Table): Meshes { const mesh = this.meshGenerator.getMeshes(table); return { rubber: { diff --git a/lib/vpt/spinner/spinner.ts b/lib/vpt/spinner/spinner.ts index 8a7938e4..c16ae045 100644 --- a/lib/vpt/spinner/spinner.ts +++ b/lib/vpt/spinner/spinner.ts @@ -81,9 +81,9 @@ export class Spinner implements IRenderable, IPlayable, IMovable, return true; } - public getMeshes(table: Table): Meshes { + public getMeshes(table: Table): Meshes { const spinner = this.meshGenerator.generateMeshes(table); - const meshes: Meshes = {}; + const meshes: Meshes = {}; meshes.plate = { mesh: spinner.plate.transform(Matrix3D.RIGHT_HANDED), diff --git a/lib/vpt/surface/surface.ts b/lib/vpt/surface/surface.ts index e4cc8d4a..fb8dd880 100644 --- a/lib/vpt/surface/surface.ts +++ b/lib/vpt/surface/surface.ts @@ -102,8 +102,8 @@ export class Surface implements IRenderable, IHittable, IScriptable } } - public getMeshes(table: Table): Meshes { - const meshes: Meshes = {}; + public getMeshes(table: Table): Meshes { + const meshes: Meshes = {}; const surface = this.meshGenerator.generateMeshes(this.data, table); if (surface.top) { meshes.top = { diff --git a/lib/vpt/table/table-mesh-generator.ts b/lib/vpt/table/table-mesh-generator.ts index 526d8cec..24a2a635 100644 --- a/lib/vpt/table/table-mesh-generator.ts +++ b/lib/vpt/table/table-mesh-generator.ts @@ -18,7 +18,6 @@ */ /* tslint:disable:no-bitwise */ -import { BufferGeometry, ExtrudeBufferGeometry, Shape, Vector2 } from 'three'; import { IRenderable } from '../../game/irenderable'; import { IRenderApi } from '../../render/irender-api'; import { Bumper } from '../bumper/bumper'; @@ -40,8 +39,8 @@ export class TableMeshGenerator { public async generateTableNode(renderApi: IRenderApi, opts: TableGenerateOptions = {}): Promise { opts = Object.assign({}, defaultOptions, opts); - const playfield = renderApi.createGroup('playfield'); - renderApi.transformScene(playfield, this.table); + const tableNode = renderApi.createGroup('playfield'); + renderApi.transformScene(tableNode, this.table); const renderGroups: IRenderGroup[] = [ { name: 'playfield', meshes: [ this.table ], enabled: !!opts.exportPlayfield }, { name: 'primitives', meshes: Object.values(this.table.primitives), enabled: !!opts.exportPrimitives }, @@ -70,7 +69,7 @@ export class TableMeshGenerator { const itemGroup = await renderApi.createObjectFromRenderable(renderable, this.table); renderApi.addToGroup(itemTypeGroup, itemGroup); } - renderApi.addToGroup(playfield, itemTypeGroup); + renderApi.addToGroup(tableNode, itemTypeGroup); } // light bulb lights @@ -82,122 +81,18 @@ export class TableMeshGenerator { renderApi.addToGroup(itemGroup, light); renderApi.addToGroup(lightGroup, itemGroup); } - renderApi.addToGroup(playfield, lightGroup); + renderApi.addToGroup(tableNode, lightGroup); } // ball group - renderApi.addToGroup(playfield, renderApi.createGroup('balls')); + renderApi.addToGroup(tableNode, renderApi.createGroup('balls')); - return playfield; + return tableNode; } - public getPlayfieldMesh(table: Table, opts: TableGenerateOptions): BufferGeometry { - /* istanbul ignore if */ - if (!this.table.data) { - throw new Error('Table data is not loaded. Load table with tableDataOnly = false.'); - } - let geometry: BufferGeometry; - const dim = table.getDimensions(); - - const pfShape = new Shape(); - pfShape.moveTo(this.table.data.left, this.table.data.top); - pfShape.lineTo(this.table.data.right, this.table.data.top); - pfShape.lineTo(this.table.data.right, this.table.data.bottom); - pfShape.lineTo(this.table.data.left, this.table.data.bottom); - pfShape.lineTo(this.table.data.left, this.table.data.top); - - // drill holes if playfield lights are rendered separately. - if (opts.exportPlayfieldLights) { - pfShape.holes = Object.values(table.lights) - .filter(l => l.isPlayfieldLight(table)) - .map(l => l.getPath(table)); - } - - const invTableWidth = 1.0 / dim.width; - const invTableHeight = 1.0 / dim.height; - - geometry = new ExtrudeBufferGeometry(pfShape, { - depth: Table.playfieldThickness, - bevelEnabled: false, - steps: 1, - UVGenerator: { - generateSideWallUV(g: ExtrudeBufferGeometry, vertices: number[], indexA: number, indexB: number, indexC: number, indexD: number): Vector2[] { - return [ - new Vector2(0, 0), - new Vector2(0, 0), - new Vector2(0, 0), - new Vector2(0, 0), - ]; - }, - generateTopUV(g: ExtrudeBufferGeometry, vertices: number[], indexA: number, indexB: number, indexC: number): Vector2[] { - const ax = vertices[indexA * 3]; - const ay = vertices[indexA * 3 + 1]; - const bx = vertices[indexB * 3]; - const by = vertices[indexB * 3 + 1]; - const cx = vertices[indexC * 3]; - const cy = vertices[indexC * 3 + 1]; - return [ - new Vector2(ax * invTableWidth, 1 - ay * invTableHeight), - new Vector2(bx * invTableWidth, 1 - by * invTableHeight), - new Vector2(cx * invTableWidth, 1 - cy * invTableHeight), - ]; - }, - }, - }); - - return geometry; + public getPlayfieldMesh(renderApi: IRenderApi, opts: TableGenerateOptions): GEOMETRY { + return renderApi.createPlayfieldGeometry(this.table, opts); } - - // private getPlayfield2DMesh(): Mesh { - // const rgv: Vertex3DNoTex2[] = []; - // for (let i = 0; i < 7; i++) { - // rgv.push(new Vertex3DNoTex2()); - // } - // rgv[0].x = this.data.left; rgv[0].y = this.data.top; rgv[0].z = this.data.tableheight; - // rgv[1].x = this.data.right; rgv[1].y = this.data.top; rgv[1].z = this.data.tableheight; - // rgv[2].x = this.data.right; rgv[2].y = this.data.bottom; rgv[2].z = this.data.tableheight; - // rgv[3].x = this.data.left; rgv[3].y = this.data.bottom; rgv[3].z = this.data.tableheight; - // - // // These next 4 vertices are used just to set the extents - // rgv[4].x = this.data.left; rgv[4].y = this.data.top; rgv[4].z = this.data.tableheight + Table.playfieldThickness; - // rgv[5].x = this.data.left; rgv[5].y = this.data.bottom; rgv[5].z = this.data.tableheight + Table.playfieldThickness; - // rgv[6].x = this.data.right; rgv[6].y = this.data.bottom; rgv[6].z = this.data.tableheight + Table.playfieldThickness; - // //rgv[7].x=g_pplayer->m_ptable->m_right; rgv[7].y=g_pplayer->m_ptable->m_top; rgv[7].z=50.0f; - // - // for (let i = 0; i < 4; ++i) { - // rgv[i].nx = 0; - // rgv[i].ny = 0; - // rgv[i].nz = 1.0; - // - // rgv[i].tv = (i & 2) ? 1.0 : 0.0; - // rgv[i].tu = (i === 1 || i === 2) ? 1.0 : 0.0; - // } - // - // const playfieldPolyIndices = [ 0, 1, 3, 0, 3, 2, 2, 3, 5, 6 ]; - // Mesh.setNormal(rgv, playfieldPolyIndices.splice(6), 4); - // - // const buffer: Vertex3DNoTex2[] = []; - // for (let i = 0; i < 7; i++) { - // buffer.push(new Vertex3DNoTex2()); - // } - // let offs = 0; - // for (let y = 0; y <= 1; ++y) { - // for (let x = 0; x <= 1; ++x) { - // buffer[offs].x = (x & 1) ? rgv[1].x : rgv[0].x; - // buffer[offs].y = (y & 1) ? rgv[2].y : rgv[0].y; - // buffer[offs].z = rgv[0].z; - // - // buffer[offs].tu = (x & 1) ? rgv[1].tu : rgv[0].tu; - // buffer[offs].tv = (y & 1) ? rgv[2].tv : rgv[0].tv; - // - // buffer[offs].nx = rgv[0].nx; - // buffer[offs].ny = rgv[0].ny; - // buffer[offs].nz = rgv[0].nz; - // ++offs; - // } - // } - // return new Mesh(buffer, playfieldPolyIndices); - // } } interface IRenderGroup { diff --git a/lib/vpt/table/table.ts b/lib/vpt/table/table.ts index a1da75ee..71d77976 100644 --- a/lib/vpt/table/table.ts +++ b/lib/vpt/table/table.ts @@ -312,12 +312,12 @@ export class Table implements IRenderable { return true; } - public getMeshes(table: Table, opts: TableExportOptions): Meshes { + public getMeshes(table: Table, renderApi: IRenderApi, opts: TableExportOptions): Meshes { /* istanbul ignore if */ if (!this.data) { throw new Error('Table data is not loaded. Load table with tableDataOnly = false.'); } - const geometry = this.meshGenerator!.getPlayfieldMesh(this, opts); + const geometry = this.meshGenerator!.getPlayfieldMesh(renderApi, opts); return { playfield: { geometry, diff --git a/lib/vpt/trigger/trigger.ts b/lib/vpt/trigger/trigger.ts index fe26f378..0cbce681 100644 --- a/lib/vpt/trigger/trigger.ts +++ b/lib/vpt/trigger/trigger.ts @@ -93,7 +93,7 @@ export class Trigger implements IRenderable, IHittable, IAnimatable(table: Table): Meshes { return { trigger: { mesh: this.meshGenerator.getMesh(table).transform(Matrix3D.RIGHT_HANDED), From f31114ed26b9dd5de29a8e0692f118a4b4ce7a4e Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 9 Sep 2019 00:44:47 +0200 Subject: [PATCH 07/14] renderer: Remove material post process in favor of directly providing the right material. --- lib/game/irenderable.ts | 4 - lib/physics/hit-object.ts | 6 +- lib/render/threejs/three-converter.ts | 27 +++-- lib/vpt/light/light-data.ts | 2 - lib/vpt/light/light.ts | 70 ++++++------ lib/vpt/material.ts | 152 +++++++++++++++----------- lib/vpt/mesh.ts | 13 --- lib/vpt/ramp/ramp-mesh-generator.ts | 4 +- 8 files changed, 149 insertions(+), 129 deletions(-) diff --git a/lib/game/irenderable.ts b/lib/game/irenderable.ts index 74c18760..eb7c73f3 100644 --- a/lib/game/irenderable.ts +++ b/lib/game/irenderable.ts @@ -17,7 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { BufferGeometry, Material as ThreeMaterial, MeshStandardMaterial } from 'three'; import { IRenderApi } from '../render/irender-api'; import { Material } from '../vpt/material'; import { Mesh } from '../vpt/mesh'; @@ -30,8 +29,6 @@ export interface IRenderable extends IItem { getMeshes(table: Table, renderApi: IRenderApi, opts: TableGenerateOptions): Meshes; isVisible(table: Table): boolean; - - postProcessMaterial?(table: Table, geometry: BufferGeometry, material: MeshStandardMaterial): MeshStandardMaterial | MeshStandardMaterial[]; } export interface Meshes { @@ -44,5 +41,4 @@ export interface RenderInfo { map?: Texture; normalMap?: Texture; material?: Material; - threeMaterial?: ThreeMaterial; } diff --git a/lib/physics/hit-object.ts b/lib/physics/hit-object.ts index 31efeb2b..761e82ce 100644 --- a/lib/physics/hit-object.ts +++ b/lib/physics/hit-object.ts @@ -173,9 +173,9 @@ export abstract class HitObject { public applyPhysics(data: IPhysicalData, table: Table) { const mat = table.getMaterial(data.szPhysicsMaterial); if (mat && !data.overwritePhysics) { - this.setElasticity(mat.fElasticity, mat.fElasticityFalloff); - this.setFriction(mat.fFriction); - this.setScatter(degToRad(mat.fScatterAngle)); + this.setElasticity(mat.elasticity, mat.elasticityFalloff); + this.setFriction(mat.friction); + this.setScatter(degToRad(mat.scatterAngle)); } else { this.setElasticity(data.elasticity, data.elasticityFalloff); diff --git a/lib/render/threejs/three-converter.ts b/lib/render/threejs/three-converter.ts index 13ecf4e9..e1c966d3 100644 --- a/lib/render/threejs/three-converter.ts +++ b/lib/render/threejs/three-converter.ts @@ -63,8 +63,7 @@ export class ThreeConverter { } const geometry = obj.geometry || obj.mesh!.getBufferGeometry(); const material = await this.getMaterial(obj, table); - const postProcessedMaterial = renderable.postProcessMaterial ? renderable.postProcessMaterial(table, geometry, material) : material; - const mesh = new ThreeMesh(geometry, postProcessedMaterial); + const mesh = new ThreeMesh(geometry, material); mesh.name = (obj.geometry || obj.mesh!).name; mesh.matrixAutoUpdate = false; @@ -77,11 +76,11 @@ export class ThreeConverter { material.name = `material:${name}`; const materialInfo = obj.material; if (materialInfo && this.opts.applyMaterials) { - material.metalness = materialInfo.bIsMetal ? 1.0 : 0.0; - material.roughness = Math.max(0, 1 - (materialInfo.fRoughness / 1.5)); - material.color = new Color(materialInfo.cBase); - material.opacity = materialInfo.bOpacityActive ? Math.min(1, Math.max(0, materialInfo.fOpacity)) : 1; - material.transparent = materialInfo.bOpacityActive && materialInfo.fOpacity < 0.98; + material.metalness = materialInfo.isMetal ? 1.0 : 0.0; + material.roughness = Math.max(0, 1 - (materialInfo.roughness / 1.5)); + material.color = new Color(materialInfo.baseColor); + material.opacity = materialInfo.isOpacityActive ? Math.min(1, Math.max(0, materialInfo.opacity)) : 1; + material.transparent = materialInfo.isOpacityActive && materialInfo.opacity < 0.98; material.side = DoubleSide; if (materialInfo.emissiveIntensity > 0) { @@ -114,6 +113,20 @@ export class ThreeConverter { material.normalMap = null; } } + // todo TEST! + if (obj.material && obj.material.emissiveMap) { + material.emissiveMap = new ThreeTexture(); + material.emissiveMap.name = 'emissive-map:' + obj.material.emissiveMap.getName(); + if (await this.loadMap(name, obj.material.emissiveMap, material.emissiveMap, table)) { + if ((material.emissiveMap.image as IImage).containsTransparency()) { + material.transparent = true; + } + material.needsUpdate = true; + } else { + logger().warn('[VpTableExporter.getMaterial] Error getting map.'); + material.map = null; + } + } } return material; } diff --git a/lib/vpt/light/light-data.ts b/lib/vpt/light/light-data.ts index f7cb4edc..923dc285 100644 --- a/lib/vpt/light/light-data.ts +++ b/lib/vpt/light/light-data.ts @@ -34,8 +34,6 @@ export class LightData extends ItemData { public color: number = 0xffff00; public color2: number = 0xffffff; public szOffImage?: string; - public fTimerEnabled: boolean = false; - public TimerInterval?: number; public roundLight: boolean = false; public rgblinkpattern?: string; public blinkinterval: number = 125; diff --git a/lib/vpt/light/light.ts b/lib/vpt/light/light.ts index 45ee4f97..6333eaf2 100644 --- a/lib/vpt/light/light.ts +++ b/lib/vpt/light/light.ts @@ -17,7 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { BufferGeometry, MeshStandardMaterial } from 'three'; import { IRenderable, Meshes } from '../../game/irenderable'; import { Storage } from '../../io/ole-doc'; import { Matrix3D } from '../../math/matrix3d'; @@ -73,24 +72,25 @@ export class Light implements IRenderable { surfaceLight: { geometry: light.surfaceLight, map: table.getTexture(this.data.szOffImage), + material: this.getSurfaceMaterial(table), }, }; } const meshes: Meshes = {}; if (light.light) { const lightMaterial = new Material(); - lightMaterial.cBase = 0; - lightMaterial.fWrapLighting = 0.5; - lightMaterial.bOpacityActive = true; - lightMaterial.fOpacity = 0.2; - lightMaterial.cGlossy = 0xFFFFFF; - lightMaterial.bIsMetal = false; - lightMaterial.fEdge = 1.0; - lightMaterial.fEdgeAlpha = 1.0; - lightMaterial.fRoughness = 0.9; - lightMaterial.fGlossyImageLerp = 1.0; - lightMaterial.fThickness = 0.05; - lightMaterial.cClearcoat = 0xFFFFFF; + lightMaterial.baseColor = 0; + lightMaterial.wrapLighting = 0.5; + lightMaterial.isOpacityActive = true; + lightMaterial.opacity = 0.2; + lightMaterial.glossiness = 0xFFFFFF; + lightMaterial.isMetal = false; + lightMaterial.edge = 1.0; + lightMaterial.edgeAlpha = 1.0; + lightMaterial.roughness = 0.9; + lightMaterial.glossyImageLerp = 1.0; + lightMaterial.thickness = 0.05; + lightMaterial.clearCoat = 0xFFFFFF; lightMaterial.emissiveColor = this.data.color; lightMaterial.emissiveIntensity = 1; @@ -102,18 +102,18 @@ export class Light implements IRenderable { if (light.socket) { const socketMaterial = new Material(); - socketMaterial.cBase = 0x181818; - socketMaterial.fWrapLighting = 0.5; - socketMaterial.bOpacityActive = false; - socketMaterial.fOpacity = 1.0; - socketMaterial.cGlossy = 0xB4B4B4; - socketMaterial.bIsMetal = false; - socketMaterial.fEdge = 1.0; - socketMaterial.fEdgeAlpha = 1.0; - socketMaterial.fRoughness = 0.9; - socketMaterial.fGlossyImageLerp = 1.0; - socketMaterial.fThickness = 0.05; - socketMaterial.cClearcoat = 0; + socketMaterial.baseColor = 0x181818; + socketMaterial.wrapLighting = 0.5; + socketMaterial.isOpacityActive = false; + socketMaterial.opacity = 1.0; + socketMaterial.glossiness = 0xB4B4B4; + socketMaterial.isMetal = false; + socketMaterial.edge = 1.0; + socketMaterial.edgeAlpha = 1.0; + socketMaterial.roughness = 0.9; + socketMaterial.glossyImageLerp = 1.0; + socketMaterial.thickness = 0.05; + socketMaterial.clearCoat = 0; meshes.socket = { mesh: light.socket.transform(Matrix3D.RIGHT_HANDED), @@ -123,6 +123,15 @@ export class Light implements IRenderable { return meshes; } + public getSurfaceMaterial(table: Table): Material { + const material = new Material(); + material.emissiveMap = table.getTexture(this.data.szOffImage); + material.emissiveIntensity = 0; + material.emissiveColor = 0x808080; + material.opacity = 1; + return material; + } + public isBulbLight() { return this.data.isBulbLight(); } @@ -138,15 +147,4 @@ export class Light implements IRenderable { // public getPath(table: Table) { // return this.meshGenerator.getPath(table); // } - - public postProcessMaterial(table: Table, geometry: BufferGeometry, material: MeshStandardMaterial): MeshStandardMaterial | MeshStandardMaterial[] { - if (!this.data.isSurfaceLight(table)) { - return material; - } - material.emissiveMap = material.map; - material.emissiveIntensity = 0; - material.emissive.setRGB(50, 50, 50); - material.opacity = 1; - return material; - } } diff --git a/lib/vpt/material.ts b/lib/vpt/material.ts index 329d2894..ce3e0293 100644 --- a/lib/vpt/material.ts +++ b/lib/vpt/material.ts @@ -18,6 +18,7 @@ */ import { BiffParser } from '../io/biff-parser'; +import { Texture } from './texture'; /** * VPinball's material definition. @@ -27,7 +28,10 @@ import { BiffParser } from '../io/biff-parser'; export class Material { public szName!: string; - public fWrapLighting?: number; + /** + * Wrap/rim lighting factor (0(off)..1(full)) + */ + public wrapLighting?: number; /** * Roughness seems to be mapped to the "specular" exponent. @@ -45,44 +49,68 @@ export class Material { * > fRoughness = exp2f(10.0f * mat->m_fRoughness + 1.0f); // map from 0..1 to 2..2048 * */ - public fRoughness: number = 0.0; - public fGlossyImageLerp: number = 1.0; - public fThickness: number = 0.05; - public fEdge: number = 1.0; - public fEdgeAlpha: number = 1.0; - public fOpacity: number = 1.0; - public cBase: number = 0xB469FF; - public cGlossy: number = 0.0; - public cClearcoat: number = 0.0; - public bIsMetal: boolean = false; - public bOpacityActive: boolean = false; + public roughness: number = 0.0; + /** + * Use image also for the glossy layer (0(no tinting at all)..1(use image)), + * stupid quantization because of legacy loading/saving + */ + public glossyImageLerp: number = 1.0; + /** + * Thickness for transparent materials (0(paper thin)..1(maximum)), + * stupid quantization because of legacy loading/saving + */ + public thickness: number = 0.05; + /** + * Edge weight/brightness for glossy and clearcoat (0(dark edges)..1(full fresnel)) + */ + public edge: number = 1.0; + public edgeAlpha: number = 1.0; + public opacity: number = 1.0; + /** + * Can be overridden by texture on object itself + */ + public baseColor: number = 0xB469FF; + /** + * Specular of glossy layer + */ + public glossiness: number = 0.0; + /** + * Specular of clearcoat layer + */ + public clearCoat: number = 0.0; + /** + * Is a metal material or not + */ + public isMetal: boolean = false; + public isOpacityActive: boolean = false; // these are a additional props public emissiveColor?: number; public emissiveIntensity: number = 0; + public emissiveMap?: Texture; //physics - public fElasticity: number = 0.0; - public fElasticityFalloff: number = 0.0; - public fFriction: number = 0.0; - public fScatterAngle: number = 0.0; + public elasticity: number = 0.0; + public elasticityFalloff: number = 0.0; + public friction: number = 0.0; + public scatterAngle: number = 0.0; public static fromSaved(saveMaterial: SaveMaterial): Material { const material = new Material(); material.szName = saveMaterial.szName; - material.cBase = BiffParser.bgrToRgb(saveMaterial.cBase); - material.cGlossy = BiffParser.bgrToRgb(saveMaterial.cGlossy); - material.cClearcoat = BiffParser.bgrToRgb(saveMaterial.cClearcoat); - material.fWrapLighting = saveMaterial.fWrapLighting; - material.fRoughness = saveMaterial.fRoughness; - material.fGlossyImageLerp = 0; //1.0f - dequantizeUnsigned<8>(mats[i].fGlossyImageLerp); //!! '1.0f -' to be compatible with previous table versions - material.fThickness = 0; //(mats[i].fThickness == 0) ? 0.05f : dequantizeUnsigned<8>(mats[i].fThickness); //!! 0 -> 0.05f to be compatible with previous table versions - material.fEdge = saveMaterial.fEdge; - material.fOpacity = saveMaterial.fOpacity; - material.bIsMetal = saveMaterial.bIsMetal; + material.baseColor = BiffParser.bgrToRgb(saveMaterial.baseColor); + material.glossiness = BiffParser.bgrToRgb(saveMaterial.glossiness); + material.clearCoat = BiffParser.bgrToRgb(saveMaterial.clearCoat); + material.wrapLighting = saveMaterial.wrapLighting; + material.roughness = saveMaterial.roughness; + material.glossyImageLerp = 0; //1.0f - dequantizeUnsigned<8>(mats[i].fGlossyImageLerp); //!! '1.0f -' to be compatible with previous table versions + material.thickness = 0; //(mats[i].fThickness == 0) ? 0.05f : dequantizeUnsigned<8>(mats[i].fThickness); //!! 0 -> 0.05f to be compatible with previous table versions + material.edge = saveMaterial.edge; + material.opacity = saveMaterial.opacity; + material.isMetal = saveMaterial.isMetal; // tslint:disable-next-line:no-bitwise - material.bOpacityActive = !!(saveMaterial.bOpacityActiveEdgeAlpha & 1); - material.fEdgeAlpha = 0; //dequantizeUnsigned<7>(mats[i].bOpacityActiveEdgeAlpha >> 1); + material.isOpacityActive = !!(saveMaterial.opacityActiveEdgeAlpha & 1); + material.edgeAlpha = 0; //dequantizeUnsigned<7>(mats[i].bOpacityActiveEdgeAlpha >> 1); return material; } @@ -98,10 +126,10 @@ export class Material { } public physUpdate(savePhysMat: SavePhysicsMaterial) { - this.fElasticity = savePhysMat.fElasticity; - this.fElasticityFalloff = savePhysMat.fElasticityFallOff; - this.fFriction = savePhysMat.fFriction; - this.fScatterAngle = savePhysMat.fScatterAngle; + this.elasticity = savePhysMat.elasticity; + this.elasticityFalloff = savePhysMat.elasticityFallOff; + this.friction = savePhysMat.friction; + this.scatterAngle = savePhysMat.scatterAngle; } } @@ -110,32 +138,32 @@ export class SaveMaterial { public static size = 76; public szName: string; - public cBase: number; // can be overriden by texture on object itself - public cGlossy: number; // specular of glossy layer - public cClearcoat: number; // specular of clearcoat layer - public fWrapLighting: number; // wrap/rim lighting factor (0(off)..1(full)) - public bIsMetal: boolean; // is a metal material or not - public fRoughness: number; // roughness of glossy layer (0(diffuse)..1(specular)) - public fGlossyImageLerp: number; // use image also for the glossy layer (0(no tinting at all)..1(use image)), stupid quantization because of legacy loading/saving - public fEdge: number; // edge weight/brightness for glossy and clearcoat (0(dark edges)..1(full fresnel)) - public fThickness: number; // thickness for transparent materials (0(paper thin)..1(maximum)), stupid quantization because of legacy loading/saving - public fOpacity: number; // opacity (0..1) - public bOpacityActiveEdgeAlpha: number; + public baseColor: number; // can be overriden by texture on object itself + public glossiness: number; // specular of glossy layer + public clearCoat: number; // specular of clearcoat layer + public wrapLighting: number; // wrap/rim lighting factor (0(off)..1(full)) + public isMetal: boolean; // is a metal material or not + public roughness: number; // roughness of glossy layer (0(diffuse)..1(specular)) + public glossyImageLerp: number; // use image also for the glossy layer (0(no tinting at all)..1(use image)), stupid quantization because of legacy loading/saving + public edge: number; // edge weight/brightness for glossy and clearcoat (0(dark edges)..1(full fresnel)) + public thickness: number; // thickness for transparent materials (0(paper thin)..1(maximum)), stupid quantization because of legacy loading/saving + public opacity: number; // opacity (0..1) + public opacityActiveEdgeAlpha: number; constructor(buffer: Buffer, i = 0) { const offset = i * SaveMaterial.size; this.szName = BiffParser.parseNullTerminatedString(buffer.slice(offset, offset + 32)); - this.cBase = buffer.readInt32LE(offset + 32); - this.cGlossy = buffer.readInt32LE(offset + 36); - this.cClearcoat = buffer.readInt32LE(offset + 40); - this.fWrapLighting = buffer.readFloatLE(offset + 44); - this.bIsMetal = buffer.readInt8(offset + 48) > 0; - this.fRoughness = buffer.readFloatLE(offset + 52); - this.fGlossyImageLerp = buffer.readInt32LE(offset + 56); - this.fEdge = buffer.readFloatLE(offset + 60); - this.fThickness = buffer.readInt32LE(offset + 64); - this.fOpacity = buffer.readFloatLE(offset + 68); - this.bOpacityActiveEdgeAlpha = buffer.readInt32LE(offset + 72); + this.baseColor = buffer.readInt32LE(offset + 32); + this.glossiness = buffer.readInt32LE(offset + 36); + this.clearCoat = buffer.readInt32LE(offset + 40); + this.wrapLighting = buffer.readFloatLE(offset + 44); + this.isMetal = buffer.readInt8(offset + 48) > 0; + this.roughness = buffer.readFloatLE(offset + 52); + this.glossyImageLerp = buffer.readInt32LE(offset + 56); + this.edge = buffer.readFloatLE(offset + 60); + this.thickness = buffer.readInt32LE(offset + 64); + this.opacity = buffer.readFloatLE(offset + 68); + this.opacityActiveEdgeAlpha = buffer.readInt32LE(offset + 72); } } @@ -144,17 +172,17 @@ export class SavePhysicsMaterial { public static size = 48; public szName: string; - public fElasticity: number; - public fElasticityFallOff: number; - public fFriction: number; - public fScatterAngle: number; + public elasticity: number; + public elasticityFallOff: number; + public friction: number; + public scatterAngle: number; constructor(buffer: Buffer, i = 0) { const offset = i * SavePhysicsMaterial.size; this.szName = BiffParser.parseNullTerminatedString(buffer.slice(offset, offset + 32)); - this.fElasticity = buffer.readFloatLE(offset + 32); - this.fElasticityFallOff = buffer.readFloatLE(offset + 36); - this.fFriction = buffer.readFloatLE(offset + 40); - this.fScatterAngle = buffer.readFloatLE(offset + 44); + this.elasticity = buffer.readFloatLE(offset + 32); + this.elasticityFallOff = buffer.readFloatLE(offset + 36); + this.friction = buffer.readFloatLE(offset + 40); + this.scatterAngle = buffer.readFloatLE(offset + 44); } } diff --git a/lib/vpt/mesh.ts b/lib/vpt/mesh.ts index 509500a7..930fd3ec 100644 --- a/lib/vpt/mesh.ts +++ b/lib/vpt/mesh.ts @@ -118,19 +118,6 @@ export class Mesh { return this; } - /** @deprecated use {@link IRenderApi} */ - public applyToObject(obj: Object3D) { - const destGeo = (obj as any).geometry; - const srcGeo = this.getBufferGeometry(); - if (srcGeo.attributes.position.array.length !== destGeo.attributes.position.array.length) { - throw new Error(`Trying to apply geometry of ${srcGeo.attributes.position.array.length} positions to ${destGeo.attributes.position.array.length} positions.`); - } - for (let i = 0; i < destGeo.attributes.position.array.length; i++) { - destGeo.attributes.position.array[i] = srcGeo.attributes.position.array[i]; - } - destGeo.attributes.position.needsUpdate = true; - } - public clone(name?: string): Mesh { const mesh = new Mesh(); mesh.name = name || this.name; diff --git a/lib/vpt/ramp/ramp-mesh-generator.ts b/lib/vpt/ramp/ramp-mesh-generator.ts index c327adc6..5d270912 100644 --- a/lib/vpt/ramp/ramp-mesh-generator.ts +++ b/lib/vpt/ramp/ramp-mesh-generator.ts @@ -298,7 +298,7 @@ export class RampMeshGenerator { // as solid ramps are rendered into the static buffer, always use maximum precision const mat = table.getMaterial(this.data.szMaterial); - if (!mat || !mat.bOpacityActive) { + if (!mat || !mat.isOpacityActive) { accuracy = f4(12.0); // see above } @@ -562,7 +562,7 @@ export class RampMeshGenerator { accuracy = acc; // used for hit shape calculation, always! } else { const mat = table.getMaterial(this.data.szMaterial); - if (!mat || !mat.bOpacityActive) { + if (!mat || !mat.isOpacityActive) { accuracy = 10.0; } else { accuracy = table.getDetailLevel(); From 7c17328597fb16fa1c62bee0023fe823087157c3 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 9 Sep 2019 01:03:48 +0200 Subject: [PATCH 08/14] renderer: Move remaining three dependencies to its IRenderApi. --- lib/render/README.md | 18 +++++++++++ lib/render/threejs/three-converter.ts | 14 +++++++- .../csg.ts => render/threejs/three-csg.ts} | 32 +++++++++---------- .../threejs/three-mesh-generator.ts} | 8 ++--- lib/render/threejs/three-render-api.ts | 17 +++++++--- lib/util/object-pool.ts | 5 --- lib/vpt/mesh.ts | 7 ---- 7 files changed, 63 insertions(+), 38 deletions(-) create mode 100644 lib/render/README.md rename lib/{math/csg.ts => render/threejs/three-csg.ts} (95%) rename lib/{gltf/mesh-converter.ts => render/threejs/three-mesh-generator.ts} (98%) diff --git a/lib/render/README.md b/lib/render/README.md new file mode 100644 index 00000000..574694a5 --- /dev/null +++ b/lib/render/README.md @@ -0,0 +1,18 @@ +# Rendering + +VPX-JS abstracts the rendering layer and delegates it to a third party WebGL +framework. That means it doesn't come with a ready-to-use web application but +only provides tools and APIs that make it easy to integrate. + +The `IRenderApi` interface is the link between the framework and the VPX-JS +engine. It makes the following assumptions: + +- A mesh consists of a bunch of vertices together with a set of indices. +- Meshes can be grouped together, and their transformation matrix is relative + to its parent. +- The framework's materials are using physically-based rendering using the + Metallic-Roughness workflow. + +VPX-JS ships with [three.js](https://threejs.org) and [Babylon.js](https://www.babylonjs.com/) +adapters. What you basically do is create your scene, instantiate the respective +adapter implementing `ĂŚRenderAPi` and use it to update the scene every frame. diff --git a/lib/render/threejs/three-converter.ts b/lib/render/threejs/three-converter.ts index e1c966d3..8ef67277 100644 --- a/lib/render/threejs/three-converter.ts +++ b/lib/render/threejs/three-converter.ts @@ -34,6 +34,7 @@ import { logger } from '../../util/logger'; import { Table, TableGenerateOptions } from '../../vpt/table/table'; import { Texture } from '../../vpt/texture'; import { IRenderApi, MeshConvertOptions } from '../irender-api'; +import { ThreeMeshGenerator } from './three-mesh-generator'; export class ThreeConverter { @@ -61,7 +62,18 @@ export class ThreeConverter { if (!obj.geometry && !obj.mesh) { throw new Error('Mesh export must either provide mesh or geometry.'); } - const geometry = obj.geometry || obj.mesh!.getBufferGeometry(); + let geometry: BufferGeometry; + if (obj.geometry) { + geometry = obj.geometry; + + } else if (obj.mesh) { + const generator = new ThreeMeshGenerator(obj.mesh); + geometry = generator.convertToBufferGeometry(); + + } else { + throw new Error('Either `geometry` or `mesh` must be defined!'); + } + const material = await this.getMaterial(obj, table); const mesh = new ThreeMesh(geometry, material); mesh.name = (obj.geometry || obj.mesh!).name; diff --git a/lib/math/csg.ts b/lib/render/threejs/three-csg.ts similarity index 95% rename from lib/math/csg.ts rename to lib/render/threejs/three-csg.ts index 110121cd..dd0c4a60 100644 --- a/lib/math/csg.ts +++ b/lib/render/threejs/three-csg.ts @@ -30,7 +30,7 @@ import { BufferGeometry, Face3, Geometry, Matrix3, Matrix4, Mesh, Vector2, Vecto * * @see https://github.com/manthrax/THREE-CSGMesh */ -export default class CSG { +export default class ThreeCsg { private polygons: Polygon[] = []; // private static currentOp: string; @@ -41,7 +41,7 @@ export default class CSG { private static _tmpm3 = new Matrix3(); public clone() { - const csg = new CSG(); + const csg = new ThreeCsg(); csg.polygons = this.polygons.map(p => p.clone()); return csg; } @@ -64,7 +64,7 @@ export default class CSG { * ``` * @param csg */ - public union(csg: CSG): CSG { + public union(csg: ThreeCsg): ThreeCsg { const a = new Node(this.clone().polygons); const b = new Node(csg.clone().polygons); a.clipTo(b); @@ -73,7 +73,7 @@ export default class CSG { b.clipTo(a); b.invert(); a.build(b.allPolygons()); - return CSG.fromPolygons(a.allPolygons()); + return ThreeCsg.fromPolygons(a.allPolygons()); } /** @@ -94,7 +94,7 @@ export default class CSG { * ``` * @param csg */ - public subtract(csg: CSG): CSG { + public subtract(csg: ThreeCsg): ThreeCsg { const a = new Node(this.clone().polygons); const b = new Node(csg.clone().polygons); a.invert(); @@ -105,7 +105,7 @@ export default class CSG { b.invert(); a.build(b.allPolygons()); a.invert(); - return CSG.fromPolygons(a.allPolygons()); + return ThreeCsg.fromPolygons(a.allPolygons()); } /** @@ -126,7 +126,7 @@ export default class CSG { * ``` * @param csg */ - public intersect(csg: CSG): CSG { + public intersect(csg: ThreeCsg): ThreeCsg { const a = new Node(this.clone().polygons); const b = new Node(csg.clone().polygons); a.invert(); @@ -136,14 +136,14 @@ export default class CSG { b.clipTo(a); a.build(b.allPolygons()); a.invert(); - return CSG.fromPolygons(a.allPolygons()); + return ThreeCsg.fromPolygons(a.allPolygons()); } /** * Return a new CSG solid with solid and empty space switched. This solid is * not modified. */ - public inverse(): CSG { + public inverse(): ThreeCsg { const csg = this.clone(); csg.polygons.map(p => p.flip()); return csg; @@ -157,8 +157,8 @@ export default class CSG { * Construct a CSG solid from a list of `Polygon` instances. * @param polygons */ - static fromPolygons(polygons: Polygon[]): CSG { - const csg = new CSG(); + static fromPolygons(polygons: Polygon[]): ThreeCsg { + const csg = new ThreeCsg(); csg.polygons = polygons; return csg; } @@ -179,25 +179,25 @@ export default class CSG { vertices.push(new Vertex(vs[f.c], f.vertexNormals[2] as Vector, geometry.faceVertexUvs[0][i][2])); polys.push(new Polygon(vertices)); } - return CSG.fromPolygons(polys) + return ThreeCsg.fromPolygons(polys) } static fromMesh(mesh: Mesh) { - const csg = CSG.fromGeometry(mesh.geometry); - CSG._tmpm3.getNormalMatrix(mesh.matrix); + const csg = ThreeCsg.fromGeometry(mesh.geometry); + ThreeCsg._tmpm3.getNormalMatrix(mesh.matrix); for (let i = 0; i < csg.polygons.length; i++) { const p = csg.polygons[i]; for (let j = 0; j < p.vertices.length; j++) { const v = p.vertices[j]; v.pos.applyMatrix4(mesh.matrix); - v.normal.applyMatrix3(CSG._tmpm3); + v.normal.applyMatrix3(ThreeCsg._tmpm3); } } return csg; } - static toMesh(csg: CSG, toMatrix: Matrix4) { + static toMesh(csg: ThreeCsg, toMatrix: Matrix4) { const geom = new Geometry(); const ps = csg.polygons; const vs = geom.vertices; diff --git a/lib/gltf/mesh-converter.ts b/lib/render/threejs/three-mesh-generator.ts similarity index 98% rename from lib/gltf/mesh-converter.ts rename to lib/render/threejs/three-mesh-generator.ts index 1666ad9b..60352660 100644 --- a/lib/gltf/mesh-converter.ts +++ b/lib/render/threejs/three-mesh-generator.ts @@ -28,9 +28,9 @@ import { Vector2, Vector3, } from 'three'; -import { Vertex3DNoTex2 } from '../math/vertex'; -import { logger } from '../util/logger'; -import { Mesh } from '../vpt/mesh'; +import { Vertex3DNoTex2 } from '../../math/vertex'; +import { logger } from '../../util/logger'; +import { Mesh } from '../../vpt/mesh'; /** * A class that converts the meshes we read from VPinball to Three.js meshes. @@ -38,7 +38,7 @@ import { Mesh } from '../vpt/mesh'; * It takes a similar approach as Three's OBJLoader, e.g. first read data into * a "state" and then convert it into BufferGeometries. */ -export class MeshConverter { +export class ThreeMeshGenerator { private mesh: Mesh; diff --git a/lib/render/threejs/three-render-api.ts b/lib/render/threejs/three-render-api.ts index c9a8a564..40bad756 100644 --- a/lib/render/threejs/three-render-api.ts +++ b/lib/render/threejs/three-render-api.ts @@ -17,7 +17,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { BufferGeometry, ExtrudeBufferGeometry, Group, Matrix4, Object3D, PointLight, Shape, Vector2 } from 'three'; +import { BufferGeometry, Group, Matrix4, Object3D, PointLight } from 'three'; import { IRenderable } from '../../game/irenderable'; import { Matrix3D } from '../../math/matrix3d'; import { Pool } from '../../util/object-pool'; @@ -27,11 +27,16 @@ import { Table, TableGenerateOptions } from '../../vpt/table/table'; import { IRenderApi, MeshConvertOptions } from '../irender-api'; import { ThreeConverter } from './three-converter'; import { ThreeLightMeshGenerator } from './three-light-mesh-generator'; +import { ThreeMeshGenerator } from './three-mesh-generator'; import { ThreePlayfieldMeshGenerator } from './three-playfield-mesh-generator'; export class ThreeRenderApi implements IRenderApi { - private static readonly SCALE = 0.05; + public static readonly SCALE = 0.05; + + public static POOL = { + Matrix4: new Pool(Matrix4), + }; private readonly converter: ThreeConverter; private readonly meshConvertOpts: MeshConvertOptions; @@ -94,7 +99,7 @@ export class ThreeRenderApi implements IRenderApi { - public static GENERIC = { - Matrix4: new Pool(Matrix4), - }; - private static DEBUG = 0; // globally enable debug prints private static TRACE = false; private static MAX_POOL_SIZE = 100; diff --git a/lib/vpt/mesh.ts b/lib/vpt/mesh.ts index 930fd3ec..8e459719 100644 --- a/lib/vpt/mesh.ts +++ b/lib/vpt/mesh.ts @@ -17,8 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { BufferGeometry, Object3D } from 'three'; -import { MeshConverter } from '../gltf/mesh-converter'; import { f4, fr } from '../math/float'; import { Matrix3D } from '../math/matrix3d'; import { Vertex3DNoTex2 } from '../math/vertex'; @@ -78,11 +76,6 @@ export class Mesh { return objFile.join('\n'); } - public getBufferGeometry(): BufferGeometry { - const converter = new MeshConverter(this); - return converter.convertToBufferGeometry(); - } - public transform(matrix: Matrix3D, normalMatrix?: Matrix3D, getZ?: (x: number) => number): this { for (const vertex of this.vertices) { const vert = Vertex3D.claim(vertex.x, vertex.y, vertex.z).multiplyMatrix(matrix); From 2b28d9301dabe1ab15b626d40f7fbd88d5e3264e Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 9 Sep 2019 01:12:58 +0200 Subject: [PATCH 09/14] doc: Update README. --- README.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 5bed03b3..7d6af4a5 100644 --- a/README.md +++ b/README.md @@ -9,38 +9,40 @@ ## Features This isn't a ready-to-use game. It's a library of loosely-coupled components that -implement some of Visual Pinball's features. +together implement [Visual Pinball](https://sourceforge.net/projects/vpinball/)'s +player for the web. -Visual Pinball's player can be split into three parts: +The player can be split into three parts: 1. The rendering engine 2. The physics engine 3. The scripting engine -This library allows exporting a VPX file into a [three.js](https://threejs.org/) -scene, which covers the first point. A physics loop is implemented by the `Player` -class. Collision detection and rigid body dynamics is now ported, covering the -second part. Work on the third has begun with the wiring set up and simple +This library provides an abstraction layer for rendering with [three.js](https://threejs.org/), +which covers the first point. A physics loop is implemented by the `Player` +class. Collision detection and rigid body dynamics are fully ported, covering the +second part. Work on scripting has begun with the wiring set up and simple statements working. More info about how we go about this can be found [here](https://github.com/freezy/vpweb/issues/1). ### Rendering -VPX-JS allows reading a VPX file file and exporting a three.js scene directly -in the browser. However, it also supports export to [GLTF](https://www.khronos.org/gltf/) -files, which is nice, because it allows off-loading the export to a server. +VPX-JS reads Visual Pinball's VPX format and extracts all meshes in VP's internal +format. Using an abstraction layer, any WebGL framework can convert this format +and construct a scene. An adapter for three.js is shipped with this library. -So why use this when Visual Pinball already has an [OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file) -export feature? Well, VPX-JS does some more things: +Additionally, VPX-JS supports direct export to [GLTF](https://www.khronos.org/gltf/) +files, which is nice, because it allows off-loading the export to a server. It's +also nice because GLTF allows doing stuff that Visual Pinball's [OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file) +export doesn't, for example: -- GLTF is somewhat more powerful than OBJ. It allows us to include materials, - textures and lights in one single file. -- VPX-JS does some optimizations when reading data from the `.vpx` file: +- Include materials, textures and lights in one single file +- Apply optimizations: - PNG textures with no transparency are converted to JPEG - PNG textures with transparency are [PNG-crushed](https://en.wikipedia.org/wiki/Pngcrush) - Meshes are compressed using [Draco](https://google.github.io/draco/) -- It's platform-independent, so you can run it on Linux and MacOS as well. ![image](https://user-images.githubusercontent.com/70426/56841267-0419fc00-688d-11e9-9996-6d84070da392.png) +*A table in the browser using three.js* ### Physics From f6939637766760d78d5527cd66006bbf9b01dae8 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 9 Sep 2019 23:43:30 +0200 Subject: [PATCH 10/14] renderer: Properly pass options. --- lib/render/irender-api.ts | 2 +- lib/render/threejs/three-converter.ts | 20 ++++++++++---------- lib/render/threejs/three-render-api.ts | 4 ++-- lib/vpt/ball/ball.ts | 2 +- lib/vpt/table/table-mesh-generator.ts | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/render/irender-api.ts b/lib/render/irender-api.ts index 0316b591..c5ba120c 100644 --- a/lib/render/irender-api.ts +++ b/lib/render/irender-api.ts @@ -39,7 +39,7 @@ export interface IRenderApi { applyMeshToObject(mesh: Mesh, obj: NODE | undefined): void; - createObjectFromRenderable(renderable: IRenderable, table: Table): Promise; + createObjectFromRenderable(renderable: IRenderable, table: Table, opts: TableGenerateOptions): Promise; createLightGeometry(lightData: LightData, table: Table): GEOMETRY; diff --git a/lib/render/threejs/three-converter.ts b/lib/render/threejs/three-converter.ts index 8ef67277..f4ffcd9c 100644 --- a/lib/render/threejs/three-converter.ts +++ b/lib/render/threejs/three-converter.ts @@ -38,14 +38,14 @@ import { ThreeMeshGenerator } from './three-mesh-generator'; export class ThreeConverter { - private readonly opts: TableGenerateOptions & MeshConvertOptions; + private readonly meshConvertOpts: MeshConvertOptions; - constructor(opts: TableGenerateOptions & MeshConvertOptions) { - this.opts = opts; + constructor(opts: MeshConvertOptions) { + this.meshConvertOpts = opts; } - public async createObject(renderable: IRenderable, table: Table, renderApi: IRenderApi): Promise { - const objects = renderable.getMeshes(table, renderApi, this.opts); + public async createObject(renderable: IRenderable, table: Table, renderApi: IRenderApi, opts: TableGenerateOptions): Promise { + const objects = renderable.getMeshes(table, renderApi, opts); const itemGroup = new Group(); itemGroup.matrixAutoUpdate = false; itemGroup.name = renderable.getName(); @@ -87,7 +87,7 @@ export class ThreeConverter { const name = (obj.geometry || obj.mesh!).name; material.name = `material:${name}`; const materialInfo = obj.material; - if (materialInfo && this.opts.applyMaterials) { + if (materialInfo && this.meshConvertOpts.applyMaterials) { material.metalness = materialInfo.isMetal ? 1.0 : 0.0; material.roughness = Math.max(0, 1 - (materialInfo.roughness / 1.5)); material.color = new Color(materialInfo.baseColor); @@ -101,7 +101,7 @@ export class ThreeConverter { } } - if (this.opts.applyTextures) { + if (this.meshConvertOpts.applyTextures) { if (obj.map) { material.map = new ThreeTexture(); material.map.name = 'texture:' + obj.map.getName(); @@ -111,7 +111,7 @@ export class ThreeConverter { } material.needsUpdate = true; } else { - logger().warn('[VpTableExporter.getMaterial] Error getting map.'); + logger().warn('[ThreeConverter.getMaterial] Error getting map.'); material.map = null; } } @@ -135,7 +135,7 @@ export class ThreeConverter { } material.needsUpdate = true; } else { - logger().warn('[VpTableExporter.getMaterial] Error getting map.'); + logger().warn('[ThreeConverter.getMaterial] Error getting map.'); material.map = null; } } @@ -152,7 +152,7 @@ export class ThreeConverter { return true; } catch (err) { threeMaterial.image = ThreeTexture.DEFAULT_IMAGE; - logger().warn('[VpTableExporter.loadMap] Error loading map %s (%s/%s): %s', name, texture.storageName, texture.getName(), err.message); + logger().warn('[ThreeConverter.loadMap] Error loading map %s (%s/%s): %s', name, texture.storageName, texture.getName(), err.message); return false; } } diff --git a/lib/render/threejs/three-render-api.ts b/lib/render/threejs/three-render-api.ts index 40bad756..4bed337d 100644 --- a/lib/render/threejs/three-render-api.ts +++ b/lib/render/threejs/three-render-api.ts @@ -127,8 +127,8 @@ export class ThreeRenderApi implements IRenderApi { - return this.converter.createObject(renderable, table, this); + public async createObjectFromRenderable(renderable: IRenderable, table: Table, opts: TableGenerateOptions): Promise { + return this.converter.createObject(renderable, table, this, opts); } public createLightGeometry(lightData: LightData, table: Table): BufferGeometry { diff --git a/lib/vpt/ball/ball.ts b/lib/vpt/ball/ball.ts index 175e5538..16f48cfa 100644 --- a/lib/vpt/ball/ball.ts +++ b/lib/vpt/ball/ball.ts @@ -83,7 +83,7 @@ export class Ball implements IPlayable, IMovable, IRenderable { } public async addToScene(scene: NODE, renderApi: IRenderApi, table: Table): Promise { - const ballMesh = await renderApi.createObjectFromRenderable(this, table); + const ballMesh = await renderApi.createObjectFromRenderable(this, table, {}); const playfield = renderApi.findInGroup(scene, 'playfield')!; const ballGroup = renderApi.findInGroup(playfield, 'balls')!; renderApi.addToGroup(ballGroup, ballMesh); diff --git a/lib/vpt/table/table-mesh-generator.ts b/lib/vpt/table/table-mesh-generator.ts index 24a2a635..8f53bed4 100644 --- a/lib/vpt/table/table-mesh-generator.ts +++ b/lib/vpt/table/table-mesh-generator.ts @@ -66,7 +66,7 @@ export class TableMeshGenerator { } const itemTypeGroup = renderApi.createGroup(group.name); for (const renderable of group.meshes.filter(i => i.isVisible(this.table))) { - const itemGroup = await renderApi.createObjectFromRenderable(renderable, this.table); + const itemGroup = await renderApi.createObjectFromRenderable(renderable, this.table, opts); renderApi.addToGroup(itemTypeGroup, itemGroup); } renderApi.addToGroup(tableNode, itemTypeGroup); From dfc7beaa5145d7dc36bc63b7d0f4b7054e48aeb1 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 10 Sep 2019 21:12:56 +0200 Subject: [PATCH 11/14] renderer: Make API names more generic. --- lib/render/irender-api.ts | 81 +++++++++++++++++++++++--- lib/render/threejs/three-render-api.ts | 10 ++-- lib/vpt/ball/ball.ts | 6 +- lib/vpt/bumper/bumper-mesh-updater.ts | 4 +- lib/vpt/flipper/flipper.ts | 2 +- lib/vpt/gate/gate.ts | 2 +- lib/vpt/hit-target/hit-target.ts | 2 +- lib/vpt/plunger/plunger.ts | 4 +- lib/vpt/spinner/spinner.ts | 2 +- lib/vpt/table/table-mesh-generator.ts | 20 +++---- lib/vpt/trigger/trigger.ts | 2 +- 11 files changed, 100 insertions(+), 35 deletions(-) diff --git a/lib/render/irender-api.ts b/lib/render/irender-api.ts index c5ba120c..256c40fe 100644 --- a/lib/render/irender-api.ts +++ b/lib/render/irender-api.ts @@ -25,26 +25,93 @@ import { Table, TableGenerateOptions } from '../vpt/table/table'; export interface IRenderApi { + /** + * Applies global transformations to the scene. + * + * Use this to size and rotate the playfield to a suitable position. + * + * @param scene Root table node + * @param table Table + */ transformScene(scene: NODE, table: Table): void; - createGroup(name: string): NODE; + /** + * Creates a new parent node. + * + * @param name Name of the node + */ + createParentNode(name: string): NODE; - addToGroup(group: NODE, obj: NODE | POINT_LIGHT): void; + /** + * Adds a child to a parent node. + * + * @param parent Parent node + * @param child Child node + */ + addChildToParent(parent: NODE, child: NODE | POINT_LIGHT): void; - findInGroup(group: NODE, name: string): NODE | undefined; + /** + * Retrieves a child node from a parent node. + * + * @param parent Parent node + * @param name Name of the child node + */ + findInGroup(parent: NODE, name: string): NODE | undefined; - removeFromGroup(group: NODE, obj: NODE | undefined): void; + /** + * Removes a child from a parent node + * + * @param parent The parent node + * @param child The child node to remove + */ + removeFromParent(parent: NODE, child: NODE | undefined): void; - applyMatrixToObject(matrix: Matrix3D, obj: NODE | undefined): void; + /** + * Applies a matrix transformation to a node. + * + * @param matrix The transformation matrix + * @param node The node to transform. Does nothing if not set. + */ + applyMatrixToNode(matrix: Matrix3D, node: NODE | undefined): void; - applyMeshToObject(mesh: Mesh, obj: NODE | undefined): void; + /** + * Updates a node with a new mesh. + * + * @param mesh New mesh. Must contain the same number of vertices as the node. + * @param node The node to which the new mesh is applied to. + */ + applyMeshToNode(mesh: Mesh, node: NODE | undefined): void; + /** + * Creates a new node based on a renderable. + * + * @param renderable The renderable from the VPX file + * @param table The table object + * @param opts Options, see {@link TableGenerateOptions}. + */ createObjectFromRenderable(renderable: IRenderable, table: Table, opts: TableGenerateOptions): Promise; + /** + * Creates a playfield light geometry. + * + * @param lightData Light parameters from the VPX file + * @param table The table object + */ createLightGeometry(lightData: LightData, table: Table): GEOMETRY; + /** + * Creates the playfield geometry. + * + * @param table The table object + * @param opts Options, see {@link TableGenerateOptions}. + */ createPlayfieldGeometry(table: Table, opts: TableGenerateOptions): GEOMETRY; + /** + * Creates a new point light. + * + * @param lightData Light parameters from the VPX file. + */ createPointLight(lightData: LightData): POINT_LIGHT; } @@ -53,5 +120,3 @@ export interface MeshConvertOptions { applyTextures?: boolean; optimizeTextures?: boolean; } - -export interface TableExportOptions extends TableGenerateOptions, MeshConvertOptions { } diff --git a/lib/render/threejs/three-render-api.ts b/lib/render/threejs/three-render-api.ts index 4bed337d..64f7e326 100644 --- a/lib/render/threejs/three-render-api.ts +++ b/lib/render/threejs/three-render-api.ts @@ -62,7 +62,7 @@ export class ThreeRenderApi implements IRenderApi c.name === name); } - public removeFromGroup(group: Group, obj: Object3D | Group): void { + public removeFromParent(group: Group, obj: Object3D | Group): void { if (!obj) { return; } group.remove(obj); } - public applyMatrixToObject(matrix: Matrix3D, obj: Object3D): void { + public applyMatrixToNode(matrix: Matrix3D, obj: Object3D): void { if (!obj) { return; } @@ -110,7 +110,7 @@ export class ThreeRenderApi implements IRenderApi, IRenderable { .multiply(trans) .toRightHanded(); - renderApi.applyMatrixToObject(matrix, obj); + renderApi.applyMatrixToNode(matrix, obj); Matrix3D.release(orientation, trans, matrix); } @@ -86,7 +86,7 @@ export class Ball implements IPlayable, IMovable, IRenderable { const ballMesh = await renderApi.createObjectFromRenderable(this, table, {}); const playfield = renderApi.findInGroup(scene, 'playfield')!; const ballGroup = renderApi.findInGroup(playfield, 'balls')!; - renderApi.addToGroup(ballGroup, ballMesh); + renderApi.addChildToParent(ballGroup, ballMesh); return ballMesh; } @@ -94,7 +94,7 @@ export class Ball implements IPlayable, IMovable, IRenderable { const playfield = renderApi.findInGroup(scene, 'playfield')!; const ballGroup = renderApi.findInGroup(playfield, 'balls')!; const ball = renderApi.findInGroup(ballGroup, this.getName()); - renderApi.removeFromGroup(ballGroup, ball); + renderApi.removeFromParent(ballGroup, ball); } public getState(): BallState { diff --git a/lib/vpt/bumper/bumper-mesh-updater.ts b/lib/vpt/bumper/bumper-mesh-updater.ts index 90702c7b..39753d47 100644 --- a/lib/vpt/bumper/bumper-mesh-updater.ts +++ b/lib/vpt/bumper/bumper-mesh-updater.ts @@ -52,7 +52,7 @@ export class BumperMeshUpdater { const ringObj = renderApi.findInGroup(obj, `bumper-ring-${this.data.getName()}`); if (ringObj) { const matrix = Matrix3D.claim().setTranslation(0, 0, -this.state.ringOffset); - renderApi.applyMatrixToObject(matrix, ringObj); + renderApi.applyMatrixToNode(matrix, ringObj); Matrix3D.release(matrix); } } @@ -69,7 +69,7 @@ export class BumperMeshUpdater { const matrix = matToOrigin.multiply(matRotY).multiply(matRotX).multiply(matFromOrigin); - renderApi.applyMatrixToObject(matrix, skirtObj); + renderApi.applyMatrixToNode(matrix, skirtObj); Matrix3D.release(matToOrigin, matFromOrigin, matRotX, matRotY); } } diff --git a/lib/vpt/flipper/flipper.ts b/lib/vpt/flipper/flipper.ts index 87b25f65..f967ab6a 100644 --- a/lib/vpt/flipper/flipper.ts +++ b/lib/vpt/flipper/flipper.ts @@ -132,7 +132,7 @@ export class Flipper implements IRenderable, IPlayable, IMovable, const matRotate = Matrix3D.claim().rotateZMatrix(this.state.angle - degToRad(this.data.startAngle)); const matrix = matToOrigin.multiply(matRotate).multiply(matFromOrigin); - renderApi.applyMatrixToObject(matrix, obj); + renderApi.applyMatrixToNode(matrix, obj); Matrix3D.release(matToOrigin, matFromOrigin, matRotate); // matrix and matToOrigin are the same instance } diff --git a/lib/vpt/gate/gate.ts b/lib/vpt/gate/gate.ts index 59b404e7..6bb997c6 100644 --- a/lib/vpt/gate/gate.ts +++ b/lib/vpt/gate/gate.ts @@ -155,7 +155,7 @@ export class Gate implements IRenderable, IPlayable, IMovable, IHitta .multiply(matTransFromOrigin); const wireObj = renderApi.findInGroup(obj, `gate.wire-${this.data.getName()}`); - renderApi.applyMatrixToObject(matrix, wireObj); + renderApi.applyMatrixToNode(matrix, wireObj); Matrix3D.release(matTransToOrigin, matRotateToOrigin, matTransFromOrigin, matRotateFromOrigin, matRotateX); } diff --git a/lib/vpt/hit-target/hit-target.ts b/lib/vpt/hit-target/hit-target.ts index db4975cc..c3b45832 100644 --- a/lib/vpt/hit-target/hit-target.ts +++ b/lib/vpt/hit-target/hit-target.ts @@ -151,7 +151,7 @@ export class HitTarget implements IRenderable, IHittable, IAnimatable, const mesh = this.meshGenerator.generateMeshes(this.state.frame, table); const rodObj = renderApi.findInGroup(obj, 'rod'); if (rodObj) { - renderApi.applyMeshToObject(mesh.rod!, rodObj); + renderApi.applyMeshToNode(mesh.rod!, rodObj); } const springObj = renderApi.findInGroup(obj, 'spring'); if (springObj) { - renderApi.applyMeshToObject(mesh.spring!, springObj); + renderApi.applyMeshToNode(mesh.spring!, springObj); } } diff --git a/lib/vpt/spinner/spinner.ts b/lib/vpt/spinner/spinner.ts index c16ae045..4fa828cb 100644 --- a/lib/vpt/spinner/spinner.ts +++ b/lib/vpt/spinner/spinner.ts @@ -140,7 +140,7 @@ export class Spinner implements IRenderable, IPlayable, IMovable, .multiply(matTransFromOrigin); const plateObj = renderApi.findInGroup(obj, `spinner.plate-${this.getName()}`); - renderApi.applyMatrixToObject(matrix, plateObj!); + renderApi.applyMatrixToNode(matrix, plateObj!); Matrix3D.release(matTransToOrigin, matRotateToOrigin, matTransFromOrigin, matRotateFromOrigin, matRotateX); } diff --git a/lib/vpt/table/table-mesh-generator.ts b/lib/vpt/table/table-mesh-generator.ts index 8f53bed4..b9ad0120 100644 --- a/lib/vpt/table/table-mesh-generator.ts +++ b/lib/vpt/table/table-mesh-generator.ts @@ -39,7 +39,7 @@ export class TableMeshGenerator { public async generateTableNode(renderApi: IRenderApi, opts: TableGenerateOptions = {}): Promise { opts = Object.assign({}, defaultOptions, opts); - const tableNode = renderApi.createGroup('playfield'); + const tableNode = renderApi.createParentNode('playfield'); renderApi.transformScene(tableNode, this.table); const renderGroups: IRenderGroup[] = [ { name: 'playfield', meshes: [ this.table ], enabled: !!opts.exportPlayfield }, @@ -64,28 +64,28 @@ export class TableMeshGenerator { if (!group.enabled) { continue; } - const itemTypeGroup = renderApi.createGroup(group.name); + const itemTypeGroup = renderApi.createParentNode(group.name); for (const renderable of group.meshes.filter(i => i.isVisible(this.table))) { const itemGroup = await renderApi.createObjectFromRenderable(renderable, this.table, opts); - renderApi.addToGroup(itemTypeGroup, itemGroup); + renderApi.addChildToParent(itemTypeGroup, itemGroup); } - renderApi.addToGroup(tableNode, itemTypeGroup); + renderApi.addChildToParent(tableNode, itemTypeGroup); } // light bulb lights if (opts.exportLightBulbLights) { - const lightGroup = renderApi.createGroup('lights'); + const lightGroup = renderApi.createParentNode('lights'); for (const lightInfo of Object.values(this.table.lights).filter(l => l.isBulbLight())) { const light = renderApi.createPointLight(lightInfo.data); - const itemGroup = renderApi.createGroup(lightInfo.getName()); - renderApi.addToGroup(itemGroup, light); - renderApi.addToGroup(lightGroup, itemGroup); + const itemGroup = renderApi.createParentNode(lightInfo.getName()); + renderApi.addChildToParent(itemGroup, light); + renderApi.addChildToParent(lightGroup, itemGroup); } - renderApi.addToGroup(tableNode, lightGroup); + renderApi.addChildToParent(tableNode, lightGroup); } // ball group - renderApi.addToGroup(tableNode, renderApi.createGroup('balls')); + renderApi.addChildToParent(tableNode, renderApi.createParentNode('balls')); return tableNode; } diff --git a/lib/vpt/trigger/trigger.ts b/lib/vpt/trigger/trigger.ts index 0cbce681..e8fd780f 100644 --- a/lib/vpt/trigger/trigger.ts +++ b/lib/vpt/trigger/trigger.ts @@ -128,7 +128,7 @@ export class Trigger implements IRenderable, IHittable, IAnimatable(obj: NODE, renderApi: IRenderApi, table: Table, player: Player): void { const matrix = Matrix3D.claim().setTranslation(0, 0, -this.state.heightOffset); - renderApi.applyMatrixToObject(matrix, obj); + renderApi.applyMatrixToNode(matrix, obj); Matrix3D.release(matrix); } From 0273529e3a2c1c0158dcb9f84b87f68378dc6936 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 10 Sep 2019 21:16:09 +0200 Subject: [PATCH 12/14] style: Fix linting issues. --- lib/render/threejs/three-converter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/render/threejs/three-converter.ts b/lib/render/threejs/three-converter.ts index f4ffcd9c..ee8cf01a 100644 --- a/lib/render/threejs/three-converter.ts +++ b/lib/render/threejs/three-converter.ts @@ -23,7 +23,9 @@ import { DoubleSide, Group, Mesh as ThreeMesh, - MeshStandardMaterial, Object3D, PointLight, + MeshStandardMaterial, + Object3D, + PointLight, RGBAFormat, RGBFormat, Texture as ThreeTexture, From 1741e9e5e0628f69a098b1b7498319ad70461faa Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 10 Sep 2019 21:42:45 +0200 Subject: [PATCH 13/14] coverage: Ignore debug functions of object pool. --- lib/util/object-pool.ts | 7 +++++++ lib/vpt/item-state.ts | 6 ------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/util/object-pool.ts b/lib/util/object-pool.ts index c59bb4f3..089549c1 100644 --- a/lib/util/object-pool.ts +++ b/lib/util/object-pool.ts @@ -40,6 +40,7 @@ export class Pool { constructor(poolable: IPoolable) { this.pool = []; this.poolable = poolable; + /* istanbul ignore next: only needed for debugging */ if (Pool.DEBUG > 0) { this.setupDebug(Pool.DEBUG); } @@ -48,6 +49,7 @@ export class Pool { public get(): T { let caller = ''; let obj: any; + /* istanbul ignore next: only needed for debugging */ if (this.debugging && Pool.TRACE) { caller = (new Error()).stack!.split('\n')[3].trim(); if (!this.claimed[caller]) { @@ -68,6 +70,7 @@ export class Pool { obj = new this.poolable() as any; } + /* istanbul ignore next: only set when debugging */ if (caller) { // update meta props obj.__caller = caller; } else if (obj._caller) { @@ -79,6 +82,7 @@ export class Pool { public release(o: T): void { const obj = o as any; + /* istanbul ignore next: only needed for debugging */ if (obj.__caller) { const caller = obj.__caller; delete obj.__caller; @@ -99,6 +103,7 @@ export class Pool { logger().warn('Trying to recycle non-claimed %s, aborting.', this.poolable.name); return; } + /* istanbul ignore next: not supposed to happen! */ if (this.pool.length >= Pool.MAX_POOL_SIZE) { if (!this.warned) { logger().warn('Pool size %s of %s is exhausted, future objects will be garbage-collected.', Pool.MAX_POOL_SIZE, this.poolable.name); @@ -114,6 +119,7 @@ export class Pool { this.pool.push(o); } + /* istanbul ignore next: only needed for debugging */ public enableDebug(interval = 10000): this { if (Pool.DEBUG <= 0 && interval > 0 && !this.debugging) { logger().debug('[Pool] %s: Debug enabled.', this.poolable.name); @@ -122,6 +128,7 @@ export class Pool { return this; } + /* istanbul ignore next: only needed for debugging */ private setupDebug(interval: number) { this.debugging = setInterval(() => { logger().debug('[Pool] %s: %s recycled, %s created, %s released, %s skipped (%s%)', diff --git a/lib/vpt/item-state.ts b/lib/vpt/item-state.ts index 417d539e..b4a54aaa 100644 --- a/lib/vpt/item-state.ts +++ b/lib/vpt/item-state.ts @@ -21,12 +21,6 @@ export abstract class ItemState { protected name: string = ''; - protected constructor(name?: string) { - if (name) { - this.name = name; - } - } - /** * Clones the state. * From 2241a4bb698a76d94d48820c6fa260caeccc4bbc Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 10 Sep 2019 21:50:55 +0200 Subject: [PATCH 14/14] coverage: Ignore texture mapping code in three.js. --- lib/render/threejs/three-converter.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/render/threejs/three-converter.ts b/lib/render/threejs/three-converter.ts index ee8cf01a..b2ba88f9 100644 --- a/lib/render/threejs/three-converter.ts +++ b/lib/render/threejs/three-converter.ts @@ -68,10 +68,11 @@ export class ThreeConverter { if (obj.geometry) { geometry = obj.geometry; - } else if (obj.mesh) { + } else if (obj.mesh) { const generator = new ThreeMeshGenerator(obj.mesh); geometry = generator.convertToBufferGeometry(); + /* istanbul ignore next: Should not happen. */ } else { throw new Error('Either `geometry` or `mesh` must be defined!'); } @@ -84,6 +85,7 @@ export class ThreeConverter { return mesh; } + /* istanbul ignore next: These are subject to change and are currently untested. */ private async getMaterial(obj: RenderInfo, table: Table): Promise { const material = new MeshStandardMaterial(); const name = (obj.geometry || obj.mesh!).name; @@ -145,6 +147,7 @@ export class ThreeConverter { return material; } + /* istanbul ignore next: Texture extraction is tested, but applying them to three.js is out of scope. */ private async loadMap(name: string, texture: Texture, threeMaterial: ThreeTexture, table: Table): Promise { try { const image = await texture.getImage(table);