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.
271 lines • 14.2 kB
JavaScript
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 FragmentClippingExtensionPlugin_1;
import { Matrix3, Plane as PlaneThree, Vector4 } from 'three';
import { AViewerPluginSync } from '../../viewer';
import { uiFolderContainer, uiToggle } from 'uiconfig.js';
import { serialize } from 'ts-browser-helpers';
import { updateMaterialDefines } from '../../materials';
import { shaderReplaceString, ThreeSerialization } from '../../utils';
import FragmentClippingExtensionPluginPars from './shaders/FragmentClippingExtensionPlugin.pars.glsl';
import FragmentClippingExtensionPluginPatch from './shaders/FragmentClippingExtensionPlugin.patch.glsl';
/**
* FragmentClipping Materials Extension
* Adds a material extension to PhysicalMaterial to add support for fragment clipping.
* Fragment clipping allows to clip fragments of the material in screen space or world space based on a circle, rectangle, plane, sphere, etc.
* It uses fixed SDFs with params defined by the user for clipping.
* It also adds a UI to the material to edit the settings.
* It uses `WEBGI_materials_fragment_clipping_extension` glTF extension to save the settings in glTF files.
* @category Plugins
*/
let FragmentClippingExtensionPlugin = FragmentClippingExtensionPlugin_1 = class FragmentClippingExtensionPlugin extends AViewerPluginSync {
static AddFragmentClipping(material, params) {
const ud = material?.userData;
if (!ud)
return false;
if (!ud._fragmentClippingExt) {
ud._fragmentClippingExt = {};
}
const tf = ud._fragmentClippingExt;
tf.clipEnabled = true;
if (tf.clipPosition === undefined)
tf.clipPosition = [0, 0, 0, 0];
if (tf.clipParams === undefined)
tf.clipParams = [0, 0, 0, 0];
if (tf.clipMode === undefined !== undefined)
tf.clipMode = FragmentClippingMode.Circle;
if (tf.clipInvert === undefined !== undefined)
tf.clipInvert = false;
params && Object.assign(tf, params);
if (material.setDirty)
material.setDirty();
return true;
}
constructor() {
super();
this.enabled = true;
this._defines = {
['FRAG_CLIPPING_DEBUG']: 0,
};
this._uniforms = {
fragClippingPosition: { value: new Vector4() }, // point on plane, center of sphere, center of cylinder, etc
fragClippingParams: { value: new Vector4() }, // normal of plane, radius of sphere, radius of cylinder, etc
fragClippingCamAspect: { value: 1 },
};
this._plane = new PlaneThree();
this._viewNormalMatrix = new Matrix3();
this._v4 = new Vector4();
this.materialExtension = {
parsFragmentSnippet: (_, material) => {
if (!this.enabled || !material?.userData._fragmentClippingExt?.clipEnabled)
return '';
return Object.entries(FragmentClippingMode)
.map(v => ['FragmentClippingMode.' + v[0], '' + v[1]]) // replace enum with integer values in the shader
.reduce((a, v) => a.replace(v[0], v[1]), FragmentClippingExtensionPluginPars);
},
shaderExtender: (shader, material) => {
if (!this.enabled || !material?.userData._fragmentClippingExt?.clipEnabled)
return;
shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#glMarker mainStart', Object.entries(FragmentClippingMode)
.map(v => ['FragmentClippingMode.' + v[0], '' + v[1]]) // replace enum with integer values in the shader
.reduce((a, v) => a.replace(v[0], v[1]), '\n' + FragmentClippingExtensionPluginPatch), { append: true });
},
onObjectRender: (object, material) => {
let tfUd = material.userData._fragmentClippingExt;
if (material.userData.isGBufferMaterial && object && object.material && !Array.isArray(object.material)) { // todo isGBufferMaterial
tfUd = object.material?.userData._fragmentClippingExt;
}
if (!tfUd?.clipEnabled)
return;
if (Array.isArray(tfUd.clipPosition))
this._uniforms.fragClippingPosition.value.fromArray(tfUd.clipPosition);
else
this._uniforms.fragClippingPosition.value.copy(tfUd.clipPosition);
if (tfUd.clipMode === FragmentClippingMode.Plane && tfUd.clipParams) {
const clipParams = Array.isArray(tfUd.clipParams) ? this._v4.fromArray(tfUd.clipParams) : this._v4.copy(tfUd.clipParams);
const viewMatrix = this._viewer.scene.mainCamera.matrixWorldInverse;
this._plane.normal.set(clipParams.x, clipParams.y, clipParams.z);
this._plane.constant = clipParams.w;
this._viewNormalMatrix.getNormalMatrix(viewMatrix);
this._plane.applyMatrix4(viewMatrix, this._viewNormalMatrix);
this._uniforms.fragClippingParams.value.set(this._plane.normal.x, this._plane.normal.y, this._plane.normal.z, this._plane.constant);
}
else {
if (Array.isArray(tfUd.clipPosition))
this._uniforms.fragClippingParams.value.fromArray(tfUd.clipParams);
else
this._uniforms.fragClippingParams.value.copy(tfUd.clipParams);
}
if (this._viewer?.scene.mainCamera.isPerspectiveCamera)
this._uniforms.fragClippingCamAspect.value = this._viewer?.scene.mainCamera.aspect;
else
this._uniforms.fragClippingCamAspect.value = 1.0;
updateMaterialDefines({
...this._defines,
// ['FRAGMENT_CLIPPING_EXTENSION_ENABLED']: this.enabled,
['FRAG_CLIPPING_MODE']: +(tfUd.clipMode ?? FragmentClippingMode.Circle),
['FRAG_CLIPPING_INVERSE']: +(tfUd.clipInvert ?? false),
}, material);
},
extraUniforms: {
// ...this._uniforms, // done in constructor
},
computeCacheKey: (material1) => {
return (this.enabled ? '1' : '0') + (material1.userData._fragmentClippingExt?.clipEnabled ? '1' : '0');
},
isCompatible: (material1) => {
return material1.isPhysicalMaterial || material1.userData.isGBufferMaterial; // todo isGBufferMaterial
},
getUiConfig: material => {
const viewer = this._viewer;
if (material.userData._fragmentClippingExt === undefined)
material.userData._fragmentClippingExt = {};
const state = material.userData._fragmentClippingExt;
const config = {
type: 'folder',
label: 'Fragment Clipping',
onChange: (ev) => {
if (!ev.config)
return;
this.setDirty();
},
children: [
{
type: 'checkbox',
label: 'Enabled',
get value() {
return state.clipEnabled || false;
},
set value(v) {
if (v === state.clipEnabled)
return;
if (v) {
if (!FragmentClippingExtensionPlugin_1.AddFragmentClipping(material))
viewer.dialog.alert('Cannot add FragmentClippingExtension.');
}
else {
state.clipEnabled = false;
if (material.setDirty)
material.setDirty();
}
config.uiRefresh?.(true, 'postFrame');
},
},
{
type: 'dropdown',
label: 'Mode',
children: Object.entries(FragmentClippingMode)
// .filter(key => !isNaN(Number(FragmentClippingMode[key])))
.map(v => ({ label: v[0], value: v[1] })),
hidden: () => !state.clipEnabled,
property: [state, 'clipMode'],
},
{
type: 'vec4',
label: 'Position',
bounds: [-1, 1],
hidden: () => !state.clipEnabled,
property: [state, 'clipPosition'],
},
{
type: 'vec4',
label: 'Params',
bounds: [0, 1],
hidden: () => !state.clipEnabled,
property: [state, 'clipParams'],
},
{
type: 'toggle',
label: 'Invert',
hidden: () => !state.clipEnabled,
property: [state, 'clipInvert'],
},
],
};
return config;
},
};
this.setDirty = () => {
this.materialExtension.setDirty?.();
this._viewer?.setDirty();
};
Object.assign(this.materialExtension.extraUniforms, this._uniforms);
}
onAdded(v) {
super.onAdded(v);
// v.addEventListener('preRender', this._preRender)
v.assetManager.materials.registerMaterialExtension(this.materialExtension);
v.assetManager.registerGltfExtension(fragmentClippingGLTFExtension);
// v.getPlugin(GBufferPlugin)?.material?.registerMaterialExtensions([this.materialExtension])
}
onRemove(v) {
v.assetManager.materials?.unregisterMaterialExtension(this.materialExtension);
v.assetManager.unregisterGltfExtension(fragmentClippingGLTFExtension.name);
// v.getPlugin(GBufferPlugin)?.material?.unregisterMaterialExtensions([this.materialExtension])
return super.onRemove(v);
}
};
FragmentClippingExtensionPlugin.PluginType = 'FragmentClippingExtensionPlugin1';
/**
* @deprecated use - use {@link fragmentClippingGLTFExtension}
*/
FragmentClippingExtensionPlugin.FRAGMENT_CLIPPING_EXTENSION_GLTF_EXTENSION = 'WEBGI_materials_fragment_clipping_extension';
__decorate([
uiToggle('Enabled', (that) => ({ onChange: that.setDirty })),
serialize()
], FragmentClippingExtensionPlugin.prototype, "enabled", void 0);
FragmentClippingExtensionPlugin = FragmentClippingExtensionPlugin_1 = __decorate([
uiFolderContainer('Fragment Clipping (MatExt)')
], FragmentClippingExtensionPlugin);
export { FragmentClippingExtensionPlugin };
export var FragmentClippingMode;
(function (FragmentClippingMode) {
FragmentClippingMode[FragmentClippingMode["Circle"] = 0] = "Circle";
FragmentClippingMode[FragmentClippingMode["Ellipse"] = 1] = "Ellipse";
FragmentClippingMode[FragmentClippingMode["Rectangle"] = 2] = "Rectangle";
FragmentClippingMode[FragmentClippingMode["Plane"] = 3] = "Plane";
FragmentClippingMode[FragmentClippingMode["Sphere"] = 4] = "Sphere";
})(FragmentClippingMode || (FragmentClippingMode = {}));
/**
* FragmentClipping Materials Extension
*
* Specification: https://threepipe.org/docs/gltf-extensions/WEBGI_materials_fragment_clipping_extension.html
*/
class GLTFMaterialsFragmentClippingExtensionImport {
constructor(parser) {
this.parser = parser;
this.name = fragmentClippingGLTFExtension.name;
}
async extendMaterialParams(materialIndex, materialParams) {
const parser = this.parser;
const materialDef = parser.json.materials[materialIndex];
if (!materialDef.extensions || !materialDef.extensions[this.name])
return;
const extension = materialDef.extensions[this.name];
if (!materialParams.userData)
materialParams.userData = {};
FragmentClippingExtensionPlugin.AddFragmentClipping(materialParams);
ThreeSerialization.Deserialize(extension, materialParams.userData._fragmentClippingExt);
}
}
const glTFMaterialsFragmentClippingExtensionExport = (w) => ({
writeMaterial: (material, materialDef) => {
if (!material.isMeshStandardMaterial || !material.userData._fragmentClippingExt?.clipEnabled)
return;
materialDef.extensions = materialDef.extensions || {};
const extensionDef = ThreeSerialization.Serialize(material.userData._fragmentClippingExt);
materialDef.extensions[fragmentClippingGLTFExtension.name] = extensionDef;
w.extensionsUsed[fragmentClippingGLTFExtension.name] = true;
},
});
export const fragmentClippingGLTFExtension = {
name: 'WEBGI_materials_fragment_clipping',
import: (p) => new GLTFMaterialsFragmentClippingExtensionImport(p),
export: glTFMaterialsFragmentClippingExtensionExport,
textures: undefined,
};
//# sourceMappingURL=FragmentClippingExtensionPlugin.js.map