prev:
text: 'Serialization'
link: './serialization'
All plugins follow the same basic structure, independent of the logic, with the API to add and remove plugins being always consistent (and one-liner). This makes it easy to debug, bundle, tree-shake, serialisation/deserialisation and extend functionality to the 3d viewer. It is also recommended to keep individual plugins small and handle one specific functionality.
SSAOPlugin depends on GBufferPlugin to get the depth and normal data. So, when SSAOPlugin is added to the viewer, it automatically adds GBufferPlugin before that (if not added already).Threepipe ships with a library of internal and external plugins to achieve photorealistic rendering, generating user interfaces, handling events, loading and exporting assets, building 3d models etc.
The plugins can be added synchronously or asynchronously using viewer.addPluginSync and viewer.addPlugin methods respectively.
It is recommended to create custom plugins for reusable features, as they provide built-in features for ui configuration, serialization, integration with editors etc. and are easy to manage and tree-shake in the code.
Check out the list of plugins in the Core Plugin and @threepipe Packages pages.
To create new plugins, simply implement the IViewerPlugin interface or extend the AViewerPluginSync or AViewerPluginAsync classes.
The only difference is that in async the onAdded and onRemove functions are async.
Here is a sample plugin
@uiFolder("Sample Plugin") // This creates a folder in the Ui. (Supported by TweakpaneUiPlugin)
export class SamplePlugin extends AViewerPluginSync<"sample-1" | "sample-2"> {
// These are the list of events that this plugin can dispatch.
static readonly PluginType = "SamplePlugin"; // This is required for serialization and handling plugins. Also used in viewer.getPluginByType()
@uiToggle() // This creates a checkbox in the Ui. (Supported by TweakpaneUiPlugin)
@serialize() // Adds this property to the list of serializable. This is also used when serializing to glb in AssetExporter.
enabled = true;
// A plugin can have custom properties.
@uiSlider("Some Number", [0, 100], 1) // Adds a slider to the Ui, with custom bounds and step size (Supported by TweakpaneUiPlugin)
@serialize("someNumber")
@onChange(SamplePlugin.prototype._updateParams) // this function will be called whenevr this value changes.
val1 = 0;
// A plugin can have custom properties.
@uiInput("Some Text") // Adds a slider to the Ui, with custom bounds and step size (Supported by TweakpaneUiPlugin)
@onChange(SamplePlugin.prototype._updateParams) // this function will be called whenevr this value changes.
@serialize()
val2 = "Hello";
@uiButton("Print Counters") // Adds a button to the Ui. (Supported by TweakpaneUiPlugin)
public printValues = () => {
console.log(this.val1, this.val2);
this.dispatchEvent({ type: "sample-1", detail: { sample: this.val1 } }); // This will dispatch an event.
}
constructor() {
super();
this._updateParams = this._updateParams.bind(this);
}
private _updateParams() {
console.log("Parameters updated.");
this.dispatchEvent({ type: "sample-2" }); // This will dispatch an event.
}
onAdded(v: ThreeViewer): void {
super.onAdded(v);
// Do some initialization here.
this.val1 = 0;
this.val2 = "Hello";
v.addEventListener("preRender", this._preRender);
v.addEventListener("postRender", this._postRender);
v.addEventListener("preFrame", this._preFrame);
v.addEventListener("postFrame", this._postFrame);
this._viewer!.scene.addEventListener("addSceneObject", this._objectAdded); // this._viewer can also be used while this plugin is attached.
}
onRemove(v: ThreeViewer): void {
// remove dispose objects
v.removeEventListener("preRender", this._preRender);
v.removeEventListener("postRender", this._postRender);
v.removeEventListener("preFrame", this._preFrame);
v.removeEventListener("postFrame", this._postFrame);
this._viewer!.scene.removeEventListener("addSceneObject", this._objectAdded); // this._viewer can also be used while this plugin is attached.
super.onRemove(v);
}
private _objectAdded = (ev: IEvent<any>) => {
console.log("A new object, texture or material is added to the scene.", ev.object);
};
private _preFrame = (ev: IEvent<any>) => {
// This function will be called before each frame. This is called even if the viewer is not dirty, so it's a good place to do viewer.setDirty()
};
private _preRender = (ev: IEvent<any>) => {
// This is called before each frame is rendered, only when the viewer is dirty.
};
// postFrame and postRender work the same way as preFrame and preRender.
}
Notes:
super.onAddedthis.dispatchEvent, and subscribed to with plugin.addEventListener. The event type must be described in the class signature for typescript autocomplete to work.onAdded and onRemove functions for the viewer and other plugins.viewer.setDirty() can be called, or set this.dirty = true in preFrame and reset in postFrame to stop the rendering. (Note that rendering may continue if some other plugin sets the viewer dirty like ProgressivePlugin or any of the animation plugins). Check isConverged in ProgressivePlugin to check if it’s the final frame.serializeWithViewer = false to disable serialisation with the viewer in config and glb or toJSON: any = undefined to disable serialisation entirelyplugin.toJSON() and plugin.fromJSON() or ThreeSerialization can be used to serialize and deserialize plugins. viewer.exportPluginConfig and viewer.importPluginConfig also exist for this.Check various plugins in the source code for more examples.