UNPKG

threepipe

Version:

A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.

333 lines 13.7 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var BaseGroundPlugin_1; import { AViewerPluginSync } from '../../viewer'; import { iGeometryCommons, Mesh2, PhysicalMaterial } from '../../core'; import { Euler, PlaneGeometry, Vector3 } from 'three'; import { onChange, onChange2, serialize } from 'ts-browser-helpers'; import { bindToValue } from '../../three'; import { uiConfig, uiFolderContainer, uiNumber, uiToggle } from 'uiconfig.js'; let BaseGroundPlugin = BaseGroundPlugin_1 = class BaseGroundPlugin extends AViewerPluginSync { get enabled() { return this.visible; } set enabled(value) { this.visible = value; } constructor() { super(); this._transformNeedRefresh = true; this.visible = true; this.size = 8; this.yOffset = 0; this.renderToDepth = true; /** * If false, the ground will not be tonemapped in post processing. * note: this will only work when {@link GBufferPlugin} is being used. Also needs {@link renderToDepth} to be true. */ this.tonemapGround = true; /** * If true, the camera will be limited to not go below the ground. * note: this will only work when {@link OrbitControls3} or three.js OrbitControls are being used. */ this.limitCameraAboveGround = false; this.autoAdjustTransform = true; /** * Extra flag for plugins to disable transform refresh like when animating or dragging */ this.enableRefreshTransform = true; this._cameraLimitsSet = false; this._cameraLastMaxPolarAngle = Math.PI; // not serialized. this can be controlled by other plugins like ModelStagePlugin and serialized there this.useModelBounds = false; this._refreshMaterial = this._refreshMaterial.bind(this); this._refreshTransform = this._refreshTransform.bind(this); this._refreshCameraLimits = this._refreshCameraLimits.bind(this); this.refresh = this.refresh.bind(this); this._refresh2 = this._refresh2.bind(this); this._onSceneUpdate = this._onSceneUpdate.bind(this); this._preRender = this._preRender.bind(this); this._postFrame = this._postFrame.bind(this); this._geometry = iGeometryCommons.upgradeGeometry.call(new PlaneGeometry(1, 1, 1, 1)); this._geometry.attributes.uv2 = this._geometry.attributes.uv.clone(); this._geometry.attributes.uv2.needsUpdate = true; this._mesh = this._createMesh(); this._defaultMaterial = this._mesh.material; this.refresh(); } onAdded(viewer) { super.onAdded(viewer); // if (viewer.getPlugin('TweakpaneUi')) console.error('TweakpaneUiPlugin must be added after Ground Plugin') viewer.scene.addObject(this._mesh, { addToRoot: true }); viewer.scene.addEventListener('sceneUpdate', this._onSceneUpdate); // todo: refresh when update... viewer.scene.addEventListener('addSceneObject', this._onSceneUpdate); viewer.addEventListener('preRender', this._preRender); viewer.addEventListener('postFrame', this._postFrame); this.refresh(); } onRemove(viewer) { this._mesh?.dispose(true); this._removeMaterial(); viewer.scene.removeEventListener('sceneUpdate', this._onSceneUpdate); viewer.scene.removeEventListener('addSceneObject', this._onSceneUpdate); viewer.removeEventListener('postFrame', this._postFrame); viewer.removeEventListener('preRender', this._preRender); return super.onRemove(viewer); } _postFrame() { if (this._transformNeedRefresh) this._refreshTransform(); if (!this._viewer) return; } _preRender() { if (!this._viewer) return; } dispose() { this._removeMaterial(); this._geometry.dispose(); this._material?.dispose(); // todo this._mesh?.dispose?.(); super.dispose(); } _removeMaterial() { if (!this._material || this._material === this._defaultMaterial) return; // this._manager?.materials?.unregisterMaterial(this._material) this._material.userData.renderToDepth = this._material.userData.__renderToDepth; this._material.userData.__renderToDepth = undefined; // todo reset gBufferData.tonemapEnabled also this._material = this._defaultMaterial; } _onSceneUpdate(event) { if (event?.geometryChanged === false) return; if (event?.updateGround !== false) this.refreshTransform(); } refreshTransform() { if (!this.enableRefreshTransform) return; this._transformNeedRefresh = true; } refresh() { if (!this._viewer) return; this._refreshMaterial(); this.refreshTransform(); this._refreshCameraLimits(); } // because of inheritance breaks onChange _refresh2() { this.refresh(); } _refreshCameraLimits() { const orbit = this._viewer?.scene.mainCamera.controls; if (!orbit) return; if (orbit.maxPolarAngle === undefined) { console.warn('refreshCameraLimits only available with orbit controls.'); return; } if (this.limitCameraAboveGround) { if (!this._cameraLimitsSet) this._cameraLastMaxPolarAngle = orbit.maxPolarAngle; orbit.maxPolarAngle = Math.PI / 2; this._cameraLimitsSet = true; } else if (this._cameraLimitsSet) { orbit.maxPolarAngle = this._cameraLastMaxPolarAngle; this._cameraLimitsSet = false; } } _refreshTransform() { if (!this._mesh) return false; if (!this._viewer) return false; let updated = false; if (this.visible !== this._mesh.visible) { this._mesh.visible = this.visible; updated = true; } if (this.isDisabled()) { if (updated) this._viewer?.scene.setDirty(); return false; } if (this.autoAdjustTransform) { this._mesh.userData.bboxVisible = false; const bbox = this.useModelBounds ? this._viewer.scene.getModelBounds(true, true, true) : this._viewer.scene.getBounds(true, true, true); this._mesh.userData.bboxVisible = true; const v = bbox.getCenter(new Vector3()).sub(new Vector3(0, bbox.getSize(new Vector3()).y / 2 + this.yOffset, 0)); updated = updated || v.clone().sub(this._mesh.position).length() > 0.0001; if (updated) { this._mesh.position.copy(v); } } updated = updated || Math.abs(this._mesh.scale.x - this.size) > 0.0001; // todo: check rotation, someone could externally change it if (updated) { this._mesh.scale.setScalar(this.size); // this._mesh.lookAt(new Vector3().fromArray(this._options.up)) this._mesh.setRotationFromEuler(new Euler(-Math.PI / 2., 0, this._mesh.rotation.z)); this._mesh.matrixWorldNeedsUpdate = true; this._mesh.setDirty({ refreshScene: false, source: BaseGroundPlugin_1.PluginType }); // this._viewer.scene.setDirty() } this._transformNeedRefresh = false; return true; } _createMesh(mesh) { if (!mesh) mesh = new Mesh2(this._geometry, this._createMaterial()); else mesh.geometry = this._geometry; if (mesh) { mesh.userData.physicsMass = 0; mesh.userData.userSelectable = false; mesh.userData.isGroundMesh = true; mesh.castShadow = true; mesh.receiveShadow = true; mesh.name = 'Ground Plane'; } return mesh; } setGeometry(g) { if (!g) g = this._geometry; else if (this._geometry) this._geometry.dispose(); if (!g) return; iGeometryCommons.upgradeGeometry.call(g); if (!this._geometry.attributes.uv2) { this._geometry.attributes.uv2 = this._geometry.attributes.uv.clone(); this._geometry.attributes.uv2.needsUpdate = true; } if (this._mesh) this._mesh.geometry = this._geometry; } _createMaterial(material) { if (!material) material = new PhysicalMaterial({ name: 'BaseGroundMaterial', color: 0xffffff, roughness: 0.8, metalness: 0.5, }); material.userData.runtimeMaterial = true; return material; } _refreshMaterial() { if (!this._viewer) return; if (this.isDisabled()) return; if (!this._material) { // new material // this._removeMaterial() this._material = this._defaultMaterial; // const id = this._material?.uuid // if (!id) console.warn('No material found for ground') this._viewer.scene.setDirty(); // if (this._mesh && this._material) { // this._mesh.material = this._material // must be set even if same, for update event handlers. // } } if (this._material.userData.__renderToDepth === undefined) { this._material.userData.__renderToDepth = this._material.userData.renderToDepth; } if (this._material.userData.renderToDepth !== this.renderToDepth) { this._material.userData.renderToDepth = this.renderToDepth; // required to work with SSR, SSAO etc when the ground is transparent / transmissive } if (!this._material.userData.gBufferData) this._material.userData.gBufferData = {}; if (this._material.userData.gBufferData.__tonemapEnabled === undefined) { this._material.userData.gBufferData.__tonemapEnabled = this._material.userData.gBufferData.tonemapEnabled; } if (this._material.userData.gBufferData.tonemapEnabled !== this.tonemapGround) { this._material.userData.gBufferData.tonemapEnabled = this.tonemapGround; } // this._material.userData.ssaoDisabled = true //todo should be in BakedGroundPlugin // this._material.userData.sscsDisabled = true //todo should be in BakedGroundPlugin // if (this._material.userData.__postTonemap === undefined) { // this._material.userData.__postTonemap = this._material.userData.postTonemap // } // if (this._material.userData.postTonemap !== this.tonemapGround) { // this._material.userData.postTonemap = this.tonemapGround // } this._viewer.setDirty(this); // todo: something else also? return; } get material() { return this._material; } get mesh() { return this._mesh; } fromJSON(data, meta) { if (data.options) { console.error('todo: support old webgi v0 file'); } if (!super.fromJSON(data, meta)) return null; // if (this._material && this._material.transmission >= 0.01) this._material.transparent = true this.refresh(); // Note: baked shadow reset is done in ShadowMapBaker.fromJSON return this; } }; BaseGroundPlugin.PluginType = 'BaseGroundPlugin'; BaseGroundPlugin.OldPluginType = 'Ground'; __decorate([ uiToggle('Visible'), onChange(BaseGroundPlugin.prototype.refreshTransform), serialize() ], BaseGroundPlugin.prototype, "visible", void 0); __decorate([ uiNumber('Size'), onChange2(BaseGroundPlugin.prototype._onSceneUpdate), serialize() ], BaseGroundPlugin.prototype, "size", void 0); __decorate([ uiNumber('Height (yOffset)'), onChange2(BaseGroundPlugin.prototype._onSceneUpdate), serialize() ], BaseGroundPlugin.prototype, "yOffset", void 0); __decorate([ uiToggle('Render to Depth'), onChange(BaseGroundPlugin.prototype._refresh2), serialize() ], BaseGroundPlugin.prototype, "renderToDepth", void 0); __decorate([ uiToggle('Tonemap Ground'), onChange(BaseGroundPlugin.prototype._refresh2), serialize() ], BaseGroundPlugin.prototype, "tonemapGround", void 0); __decorate([ uiToggle('Limit Camera Above Ground'), onChange(BaseGroundPlugin.prototype._refreshCameraLimits), serialize() ], BaseGroundPlugin.prototype, "limitCameraAboveGround", void 0); __decorate([ uiToggle('Auto Adjust Transform'), onChange(BaseGroundPlugin.prototype.refreshTransform), serialize() ], BaseGroundPlugin.prototype, "autoAdjustTransform", void 0); __decorate([ serialize('material'), uiConfig(), bindToValue({ obj: 'mesh', key: 'material', allowUndefined: true }) ], BaseGroundPlugin.prototype, "_material", void 0); BaseGroundPlugin = BaseGroundPlugin_1 = __decorate([ uiFolderContainer('Ground') ], BaseGroundPlugin); export { BaseGroundPlugin }; //# sourceMappingURL=BaseGroundPlugin.js.map