| @@ -121,6 +121,7 @@ To make changes and run the example, click on the CodePen button on the top righ | |||
| - [@threepipe/plugin-tweakpane-editor](#threepipeplugin-tweakpane-editor) - Tweakpane Editor Plugin | |||
| - [@threepipe/plugin-extra-importers](#threepipeplugin-extra-importers) - Plugin for loading more file types supported by loaders in three.js | |||
| - [@threepipe/plugin-blend-importer](#threepipeplugin-blend-importer) - Blender to add support for loading .blend file | |||
| - [@threepipe/plugin-geometry-generator](#threepipeplugin-extra-importers) - Generate parametric geometry types that can be re-generated from UI/API. | |||
| ## Getting Started | |||
| @@ -2062,11 +2063,9 @@ ThreePipe has a simple plugin system that allows you to easily add new features | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#tonemap-plugin/ | |||
| Source Code: [src/plugins/postprocessing/TonemapPlugin.ts](./src/plugins/postprocessing/TonemapPlugin.ts) | |||
| API Reference: [TonemapPlugin](https://threepipe.org/docs/classes/TonemapPlugin.html) | |||
| [Example](https://threepipe.org/examples/#tonemap-plugin/) — | |||
| [Source Code](./src/plugins/postprocessing/TonemapPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/TonemapPlugin.html) | |||
| TonemapPlugin adds a post-processing material extension to the ScreenPass in render manager | |||
| that applies tonemapping to the color. The tonemapping operator can be changed | |||
| @@ -2080,11 +2079,9 @@ TonemapPlugin is added by default in ThreeViewer unless `tonemap` is set to `fal | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#dropzone-plugin/ | |||
| Source Code: [src/plugins/interaction/DropzonePlugin.ts](./src/plugins/interaction/DropzonePlugin.ts) | |||
| API Reference: [DropzonePlugin](https://threepipe.org/docs/classes/DropzonePlugin.html) | |||
| [Example](https://threepipe.org/examples/#dropzone-plugin/) — | |||
| [Source Code](./src/plugins/interaction/DropzonePlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/DropzonePlugin.html) | |||
| DropzonePlugin adds support for drag and drop of local files to automatically import, process and load them into the viewer. | |||
| @@ -2126,11 +2123,9 @@ const viewer = new ThreeViewer({ | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#progressive-plugin/ | |||
| Source Code: [src/plugins/postprocessing/ProgressivePlugin.ts](./src/plugins/pipeline/ProgressivePlugin.ts) | |||
| API Reference: [ProgressivePlugin](https://threepipe.org/docs/classes/ProgressivePlugin.html) | |||
| [Example](https://threepipe.org/examples/#progressive-plugin/) — | |||
| [Source Code](./src/plugins/pipeline/ProgressivePlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/ProgressivePlugin.html) | |||
| Progressive Plugin adds a post-render pass to blend the last frame with the current frame. | |||
| @@ -2140,11 +2135,9 @@ This is used as a dependency in other plugins for progressive rendering effect w | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#depth-buffer-plugin/ | |||
| Source Code: [src/plugins/pipeline/DepthBufferPlugin.ts](./src/plugins/pipeline/DepthBufferPlugin.ts) | |||
| API Reference: [DepthBufferPlugin](https://threepipe.org/docs/classes/DepthBufferPlugin.html) | |||
| [Example](https://threepipe.org/examples/#depth-buffer-plugin/) — | |||
| [Source Code](./src/plugins/pipeline/DepthBufferPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/DepthBufferPlugin.html) | |||
| Depth Buffer Plugin adds a pre-render pass to the render manager and renders a depth buffer to a target. The render target can be accessed by other plugins throughout the rendering pipeline to create effects like depth of field, SSAO, SSR, etc. | |||
| @@ -2166,11 +2159,9 @@ The depth values are based on camera near far values, which are controlled autom | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#normal-buffer-plugin/ | |||
| Source Code: [src/plugins/pipeline/NormalBufferPlugin.ts](./src/plugins/pipeline/NormalBufferPlugin.ts) | |||
| API Reference: [NormalBufferPlugin](https://threepipe.org/docs/classes/NormalBufferPlugin.html) | |||
| [Example](https://threepipe.org/examples/#normal-buffer-plugin/) — | |||
| [Source Code](./src/plugins/pipeline/NormalBufferPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/NormalBufferPlugin.html) | |||
| Normal Buffer Plugin adds a pre-render pass to the render manager and renders a normal buffer to a target. The render target can be accessed by other plugins throughout the rendering pipeline to create effects like SSAO, SSR, etc. | |||
| @@ -2197,11 +2188,9 @@ todo | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#picking-plugin/ | |||
| Source Code: [src/plugins/pipeline/PickingPlugin.ts](./src/plugins/interaction/PickingPlugin.ts) | |||
| API Reference: [PickingPlugin](https://threepipe.org/docs/classes/PickingPlugin.html) | |||
| [Example](https://threepipe.org/examples/#picking-plugin/) — | |||
| [Source Code](./src/plugins/interaction/PickingPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/PickingPlugin.html) | |||
| Picking Plugin adds support for selecting and hovering over objects in the viewer with user interactions and selection widgets. | |||
| @@ -2268,11 +2257,9 @@ pickingPlugin.addEventListener('hoverObjectChanged', (e)=>{ | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#gltf-animation-plugin/ | |||
| Source Code: [src/plugins/animation/GLTFAnimationPlugin.ts](./src/plugins/animation/GLTFAnimationPlugin.ts) | |||
| API Reference: [GLTFAnimationPlugin](https://threepipe.org/docs/classes/GLTFAnimationPlugin.html) | |||
| [Example](https://threepipe.org/examples/#gltf-animation-plugin/) — | |||
| [Source Code](./src/plugins/animation/GLTFAnimationPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/GLTFAnimationPlugin.html) | |||
| Manages playback of GLTF animations. | |||
| @@ -2289,11 +2276,9 @@ To play individual animations, with custom choreography, use the {@link GLTFAnim | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#popmotion-plugin/ | |||
| Source Code: [src/plugins/animation/PopmotionPlugin.ts](./src/plugins/animation/PopmotionPlugin.ts) | |||
| API Reference: [PopmotionPlugin](https://threepipe.org/docs/classes/PopmotionPlugin.html) | |||
| [Example](https://threepipe.org/examples/#popmotion-plugin/) — | |||
| [Source Code](./src/plugins/animation/PopmotionPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/PopmotionPlugin.html) | |||
| Provides animation/tweening capabilities to the viewer using the [popmotion.io](https://popmotion.io/) library. | |||
| @@ -2356,11 +2341,9 @@ Note: The animation is started when the animate or animateAsync function is call | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#camera-view-plugin/ | |||
| Source Code: [src/plugins/animation/CameraViewPlugin.ts](./src/plugins/animation/CameraViewPlugin.ts) | |||
| API Reference: [CameraViewPlugin](https://threepipe.org/docs/classes/CameraViewPlugin.html) | |||
| [Example](https://threepipe.org/examples/#camera-view-plugin/) — | |||
| [Source Code](./src/plugins/animation/CameraViewPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/CameraViewPlugin.html) | |||
| CameraViewPlugin adds support to save and load camera views, which can then be animated to. | |||
| It uses PopmotionPlugin internally to animate any camera to a saved view or to loop through all the saved views. | |||
| @@ -2424,11 +2407,9 @@ cameraViewPlugin.viewLooping = false | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#render-target-preview/ | |||
| Source Code: [src/plugins/ui/RenderTargetPreviewPlugin.ts](./src/plugins/ui/RenderTargetPreviewPlugin.ts) | |||
| API Reference: [RenderTargetPreviewPlugin](https://threepipe.org/docs/classes/RenderTargetPreviewPlugin.html) | |||
| [Example](https://threepipe.org/examples/#render-target-preview/) — | |||
| [Source Code](./src/plugins/ui/RenderTargetPreviewPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/RenderTargetPreviewPlugin.html) | |||
| RenderTargetPreviewPlugin is a useful development and debugging plugin that renders any registered render-target to the screen in small collapsable panels. | |||
| @@ -2449,11 +2430,9 @@ previewPlugin.addTarget(()=>normalPlugin.target, 'normal', false, false) | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#geometry-uv-preview/ | |||
| Source Code: [src/plugins/ui/GeometryUVPreviewPlugin.ts](./src/plugins/ui/GeometryUVPreviewPlugin.ts) | |||
| API Reference: [GeometryUVPreviewPlugin](https://threepipe.org/docs/classes/GeometryUVPreviewPlugin.html) | |||
| [Example](https://threepipe.org/examples/#geometry-uv-preview/) — | |||
| [Source Code](./src/plugins/ui/GeometryUVPreviewPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/GeometryUVPreviewPlugin.html) | |||
| GeometryUVPreviewPlugin is a useful development and debugging plugin | |||
| that adds a panel to the viewer to show the UVs of a geometry. | |||
| @@ -2474,11 +2453,9 @@ previewPlugin.addGeometry(geometry, 'sphere') | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#frame-fade-plugin/ | |||
| Source Code: [src/plugins/pipeline/FrameFadePlugin.ts](./src/plugins/pipeline/FrameFadePlugin.ts) | |||
| API Reference: [FrameFadePlugin](https://threepipe.org/docs/classes/FrameFadePlugin.html) | |||
| [Example](https://threepipe.org/examples/#frame-fade-plugin/) — | |||
| [Source Code](./src/plugins/pipeline/FrameFadePlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/FrameFadePlugin.html) | |||
| FrameFadePlugin adds a post-render pass to the render manager and blends the last frame with the current frame over time. This is useful for creating smooth transitions between frames for example when changing the camera position, material, object properties, etc to avoid a sudden jump. | |||
| @@ -2504,11 +2481,9 @@ The plugin automatically tracks `setDirty()` function calls in objects, material | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#vignette-plugin/ | |||
| Source Code: [src/plugins/postprocessing/VignettePlugin.ts](./src/plugins/postprocessing/VignettePlugin.ts) | |||
| API Reference: [VignettePlugin](https://threepipe.org/docs/classes/VignettePlugin.html) | |||
| [Example](https://threepipe.org/examples/#vignette-plugin/) — | |||
| [Source Code](./src/plugins/postprocessing/VignettePlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/VignettePlugin.html) | |||
| VignettePlugin adds a post-processing material extension to the ScreenPass in render manager | |||
| that applies a vignette effect to the final render. The parameters `power` and `color` can be changed to customize the effect. | |||
| @@ -2532,11 +2507,9 @@ vignettePlugin.color = new Color(0.5, 0, 0) | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#chromatic-aberration-plugin/ | |||
| Source Code: [src/plugins/postprocessing/ChromaticAberrationPlugin.ts](./src/plugins/postprocessing/ChromaticAberrationPlugin.ts) | |||
| API Reference: [ChromaticAberrationPlugin](https://threepipe.org/docs/classes/ChromaticAberrationPlugin.html) | |||
| [Example](https://threepipe.org/examples/#chromatic-aberration-plugin/) — | |||
| [Source Code](./src/plugins/postprocessing/ChromaticAberrationPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/ChromaticAberrationPlugin.html) | |||
| ChromaticAberrationPlugin adds a post-processing material extension to the ScreenPass in render manager | |||
| that applies a chromatic-aberration effect to the final render. The parameter `intensity` can be changed to customize the effect. | |||
| @@ -2556,11 +2529,9 @@ chromaticAberrationPlugin.intensity = 0.5 | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#filmic-grain-plugin/ | |||
| Source Code: [src/plugins/postprocessing/FilmicGrainPlugin.ts](./src/plugins/postprocessing/FilmicGrainPlugin.ts) | |||
| API Reference: [FilmicGrainPlugin](https://threepipe.org/docs/classes/FilmicGrainPlugin.html) | |||
| [Example](https://threepipe.org/examples/#filmic-grain-plugin/) — | |||
| [Source Code](./src/plugins/postprocessing/FilmicGrainPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/FilmicGrainPlugin.html) | |||
| FilmicGrainPlugin adds a post-processing material extension to the ScreenPass in render manager | |||
| that applies a filmic-grain effect to the final render. The parameters `power` and `color` can be changed to customize the effect. | |||
| @@ -2581,11 +2552,9 @@ filmicGrainPlugin.multiply = false | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#noise-bump-material-plugin/ | |||
| Source Code: [src/plugins/material/NoiseBumpMaterialPlugin.ts](./src/plugins/material/NoiseBumpMaterialPlugin.ts) | |||
| API Reference: [NoiseBumpMaterialPlugin](https://threepipe.org/docs/classes/NoiseBumpMaterialPlugin.html) | |||
| [Example](https://threepipe.org/examples/#noise-bump-material-plugin/) — | |||
| [Source Code](./src/plugins/material/NoiseBumpMaterialPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/NoiseBumpMaterialPlugin.html) | |||
| NoiseBumpMaterialPlugin adds a material extension to PhysicalMaterial to add support for sparkle bump / noise bump by creating procedural bump map from noise to simulate sparkle flakes. | |||
| It uses voronoise function from blender along with several additions to generate the noise for the generation. | |||
| @@ -2617,11 +2586,9 @@ material.setDirty() | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#custom-bump-map-plugin/ | |||
| Source Code: [src/plugins/material/CustomBumpMapPlugin.ts](./src/plugins/material/CustomBumpMapPlugin.ts) | |||
| API Reference: [CustomBumpMapPlugin](https://threepipe.org/docs/classes/CustomBumpMapPlugin.html) | |||
| [Example](https://threepipe.org/examples/#custom-bump-map-plugin/) — | |||
| [Source Code](./src/plugins/material/CustomBumpMapPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/CustomBumpMapPlugin.html) | |||
| CustomBumpMapPlugin adds a material extension to PhysicalMaterial to support custom bump maps. | |||
| A Custom bump map is similar to the built-in bump map, but allows using an extra bump map and scale to give a combined effect. | |||
| @@ -2654,11 +2621,9 @@ material.setDirty() | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#clearcoat-tint-plugin/ | |||
| Source Code: [src/plugins/material/ClearcoatTintPlugin.ts](./src/plugins/material/ClearcoatTintPlugin.ts) | |||
| API Reference: [ClearcoatTintPlugin](https://threepipe.org/docs/classes/ClearcoatTintPlugin.html) | |||
| [Example](https://threepipe.org/examples/#clearcoat-tint-plugin/) — | |||
| [Source Code](./src/plugins/material/ClearcoatTintPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/ClearcoatTintPlugin.html) | |||
| ClearcoatTintPlugin adds a material extension to PhysicalMaterial which adds tint and thickness to the built-in clearcoat properties. | |||
| It also adds a UI to the material to edit the settings. | |||
| @@ -2691,11 +2656,9 @@ material.setDirty() | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#fragment-clipping-extension-plugin/ | |||
| Source Code: [src/plugins/materials/FragmentClippingExtensionPlugin.ts](./src/plugins/material/FragmentClippingExtensionPlugin.ts) | |||
| API Reference: [FragmentClippingExtensionPlugin](https://threepipe.org/docs/classes/FragmentClippingExtensionPlugin.html) | |||
| [Example](https://threepipe.org/examples/#fragment-clipping-extension-plugin/) — | |||
| [Source Code](./src/plugins/material/FragmentClippingExtensionPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/FragmentClippingExtensionPlugin.html) | |||
| FragmentClippingExtensionPlugin 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. | |||
| @@ -2729,11 +2692,9 @@ material.setDirty() | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#hdri-ground-plugin/ | |||
| Source Code: [src/plugins/extras/HDRiGroundPlugin.ts](./src/plugins/extras/HDRiGroundPlugin.ts) | |||
| API Reference: [HDRiGroundPlugin](https://threepipe.org/docs/classes/HDRiGroundPlugin.html) | |||
| [Example](https://threepipe.org/examples/#hdri-ground-plugin/) — | |||
| [Source Code](./src/plugins/extras/HDRiGroundPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/HDRiGroundPlugin.html) | |||
| HDRiGroundPlugin patches the background shader in the renderer to add support for ground projected environment map/skybox. Works simply by setting the background same as the environemnt and enabling the plugin. | |||
| @@ -2766,11 +2727,9 @@ Check the [example](https://threepipe.org/examples/#hdri-ground-plugin/) for a d | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#virtual-cameras-plugin/ | |||
| Source Code: [src/plugins/rendering/VirtualCamerasPlugin.ts](./src/plugins/rendering/VirtualCamerasPlugin.ts) | |||
| API Reference: [VirtualCamerasPlugin](https://threepipe.org/docs/classes/VirtualCamerasPlugin.html) | |||
| [Example](https://threepipe.org/examples/#virtual-cameras-plugin/) — | |||
| [Source Code](./src/plugins/rendering/VirtualCamerasPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/VirtualCamerasPlugin.html) | |||
| VirtualCamerasPlugin adds support for rendering to multiple virtual cameras in the viewer. These cameras are rendered in preRender callback just before the main camera is rendered. The virtual cameras can be added to the plugin and removed from it. | |||
| @@ -2801,11 +2760,9 @@ Check the [virtual camera](https://threepipe.org/examples/#hdri-ground-plugin/) | |||
| ## Rhino3dmLoadPlugin | |||
| Example: https://threepipe.org/examples/#rhino3dm-load/ | |||
| Source Code: [src/plugins/import/Rhino3dmLoadPlugin.ts](./src/plugins/import/Rhino3dmLoadPlugin.ts) | |||
| API Reference: [Rhino3dmLoadPlugin](https://threepipe.org/docs/classes/Rhino3dmLoadPlugin.html) | |||
| [Example](https://threepipe.org/examples/#rhino3dm-load/) — | |||
| [Source Code](./src/plugins/import/Rhino3dmLoadPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/Rhino3dmLoadPlugin.html) | |||
| Adds support for loading .3dm files generated by [Rhino 3D](https://www.rhino3d.com/). This plugin includes some changes with how 3dm files are loaded in three.js. The changes are around loading layer and primitive properties when set as reference in the 3dm files. | |||
| @@ -2825,11 +2782,9 @@ const mesh = await viewer.load('file.3dm') | |||
| ## PLYLoadPlugin | |||
| Example: https://threepipe.org/examples/#ply-load/ | |||
| Source Code: [src/plugins/import/PLYLoadPlugin.ts](./src/plugins/import/PLYLoadPlugin.ts) | |||
| API Reference: [PLYLoadPlugin](https://threepipe.org/docs/classes/PLYLoadPlugin.html) | |||
| [Example](https://threepipe.org/examples/#ply-load/) — | |||
| [Source Code](./src/plugins/import/PLYLoadPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/PLYLoadPlugin.html) | |||
| Adds support for loading .ply ([Polygon file format](https://en.wikipedia.org/wiki/PLY_(file_format))) files. | |||
| @@ -2842,11 +2797,9 @@ const mesh = await viewer.load('file.ply') | |||
| ## USDZLoadPlugin | |||
| Example: https://threepipe.org/examples/#usdz-load/ | |||
| Source Code: [src/plugins/import/USDZLoadPlugin.ts](./src/plugins/import/USDZLoadPlugin.ts) | |||
| API Reference: [USDZLoadPlugin](https://threepipe.org/docs/classes/USDZLoadPlugin.html) | |||
| [Example](https://threepipe.org/examples/#usdz-load/) — | |||
| [Source Code](./src/plugins/import/USDZLoadPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/USDZLoadPlugin.html) | |||
| Adds support for loading .usdz and .usda ([Universal Scene Description](https://graphics.pixar.com/usd/docs/index.html)) files. | |||
| @@ -2860,11 +2813,9 @@ const mesh2 = await viewer.load('file.usda') | |||
| ## STLLoadPlugin | |||
| Example: https://threepipe.org/examples/#stl-load/ | |||
| Source Code: [src/plugins/import/STLLoadPlugin.ts](./src/plugins/import/STLLoadPlugin.ts) | |||
| API Reference: [STLLoadPlugin](https://threepipe.org/docs/classes/STLLoadPlugin.html) | |||
| [Example](https://threepipe.org/examples/#stl-load/) — | |||
| [Source Code](./src/plugins/import/STLLoadPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/STLLoadPlugin.html) | |||
| Adds support for loading .stl ([Stereolithography](https://en.wikipedia.org/wiki/STL_(file_format))) files. | |||
| @@ -2877,11 +2828,9 @@ const mesh = await viewer.load('file.stl') | |||
| ## KTX2LoadPlugin | |||
| Example: https://threepipe.org/examples/#ktx2-load/ | |||
| Source Code: [src/plugins/import/KTX2LoadPlugin.ts](./src/plugins/import/KTX2LoadPlugin.ts) | |||
| API Reference: [KTX2LoadPlugin](https://threepipe.org/docs/classes/KTX2LoadPlugin.html) | |||
| [Example](https://threepipe.org/examples/#ktx2-load/) — | |||
| [Source Code](./src/plugins/import/KTX2LoadPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/KTX2LoadPlugin.html) | |||
| Adds support for loading .ktx2 ([Khronos Texture](https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/) files. | |||
| @@ -2896,11 +2845,9 @@ const texture = await viewer.load('file.ktx2') | |||
| ## KTXLoadPlugin | |||
| Example: https://threepipe.org/examples/#ktx-load/ | |||
| Source Code: [src/plugins/import/KTXLoadPlugin.ts](./src/plugins/import/KTXLoadPlugin.ts) | |||
| API Reference: [KTXLoadPlugin](https://threepipe.org/docs/classes/KTXLoadPlugin.html) | |||
| [Example](https://threepipe.org/examples/#ktx-load/) — | |||
| [Source Code](./src/plugins/import/KTXLoadPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/KTXLoadPlugin.html) | |||
| Adds support for loading .ktx ([Khronos Texture](https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/) files. | |||
| @@ -2923,11 +2870,9 @@ These add support for integrating with other libraries, adding new features, and | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#tweakpane-ui-plugin/ | |||
| Source Code: [plugins/tweakpane/src/TweakpaneUiPlugin.ts](plugins/tweakpane/src/TweakpaneUiPlugin.ts) | |||
| API Reference: [TweakpaneUiPlugin](https://threepipe.org/plugins/tweakpane/docs/classes/TweakpaneUiPlugin.html) | |||
| [Example](https://threepipe.org/examples/#tweakpane-ui-plugin/) — | |||
| [Source Code](./plugins/tweakpane/src/TweakpaneUiPlugin.ts) — | |||
| [API Reference](https://threepipe.org/plugins/tweakpane/docs/classes/TweakpaneUiPlugin.html) | |||
| NPM: `npm install @threepipe/plugin-tweakpane` | |||
| @@ -2959,11 +2904,9 @@ plugin.setupPlugins(TonemapPlugin, DropzonePlugin) | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#blueprintjs-ui-plugin/ | |||
| Source Code: [plugins/blueprintjs/src/BlueprintJsUiPlugin.ts](plugins/blueprintjs/src/BlueprintJsUiPlugin.ts) | |||
| API Reference: [BlueprintJsUiPlugin](https://threepipe.org/plugins/blueprintjs/docs/classes/BlueprintJsUiPlugin.html) | |||
| [Example](https://threepipe.org/examples/#blueprintjs-ui-plugin/) — | |||
| [Source Code](./plugins/blueprintjs/src/BlueprintJsUiPlugin.ts) — | |||
| [API Reference](https://threepipe.org/plugins/blueprintjs/docs/classes/BlueprintJsUiPlugin.html) | |||
| NPM: `npm install @threepipe/plugin-blueprintjs` | |||
| @@ -2996,11 +2939,9 @@ Tweakpane Editor Plugin for ThreePipe | |||
| [//]: # (todo: image) | |||
| Example: https://threepipe.org/examples/#tweakpane-editor/ | |||
| Source Code: [plugins/tweakpane-editor/src/TweakpaneEditorPlugin.ts](plugins/tweakpane-editor/src/TweakpaneEditorPlugin.ts) | |||
| API Reference: [TweakpaneEditorPlugin](https://threepipe.org/plugins/tweakpane-editor/docs/classes/TweakpaneEditorPlugin.html) | |||
| [Example](https://threepipe.org/examples/#tweakpane-editor/) — | |||
| [Source Code](./plugins/tweakpane-editor/src/TweakpaneEditorPlugin.ts) — | |||
| [API Reference](https://threepipe.org/plugins/tweakpane-editor/docs/classes/TweakpaneEditorPlugi — | |||
| NPM: `npm install @threepipe/plugin-tweakpane-editor` | |||
| @@ -3040,11 +2981,9 @@ editor.loadPlugins({ | |||
| Exports several plugins to add support for various file types. | |||
| Example: https://threepipe.org/examples/#extra-importer-plugins/ | |||
| Source Code: [plugins/extra-importers/src/index.ts](plugins/extra-importers/src/index.ts) | |||
| API Reference: [@threepipe/plugin-extra-importers](https://threepipe.org/plugins/extra-importers/docs) | |||
| [Example](https://threepipe.org/examples/#extra-importer-plugins/) — | |||
| [Source Code](./plugins/extra-importers/src/index.ts) — | |||
| [API Reference](https://threepipe.org/plugins/extra-importers/docs) | |||
| NPM: `npm install @threepipe/plugin-extra-importers` | |||
| @@ -3094,11 +3033,9 @@ Note: This is still a WIP. | |||
| Currently working: `Mesh`, `BufferGeometry` and basic `PointLight`. | |||
| To be added: `PhysicalMaterial`, `UnlitMaterial` (similar to blender-gltf-io plugin) | |||
| Example: https://threepipe.org/examples/#blend-load/ | |||
| Source Code: [plugins/blend-importer/src/index.ts](plugins/blend-importer/src/index.ts) | |||
| API Reference: [@threepipe/plugin-blend-importer](https://threepipe.org/plugins/blend-importer/docs) | |||
| [Example](https://threepipe.org/examples/#blend-load/) — | |||
| [Source Code](./plugins/blend-importer/src/index.ts) — | |||
| [API Reference](https://threepipe.org/plugins/blend-importer/docs) | |||
| NPM: `npm install @threepipe/plugin-blend-importer` | |||
| @@ -3119,3 +3056,44 @@ const model1 = await viewer.load<IObject3D>('data:application/x-blender;base64,. | |||
| [//]: # ( TODO: The plugin should parse and references to other assets and find them relative to the .blend file or the current location.) | |||
| ## @threepipe/plugin-geometry-generator | |||
| Exports [GeometryGeneratorPlugin](https://threepipe.org/plugins/geometry-generator/docs/classes/BlendLoadPlugin.html) with several Geometry generators to create parametric and updatable geometries like plane, circle, sphere, box, torus, cylinder, cone etc. | |||
| [Example](https://threepipe.org/examples/#geometry-generator-plugin/) — | |||
| [Source Code](./plugins/geometry-generator/src/index.ts) — | |||
| [API Reference](https://threepipe.org/plugins/geometry-generator/docs) | |||
| NPM: `npm install @threepipe/plugin-geometry-generator` | |||
| The generated geometries/meshes include the parameters in the userData and can be re-generated by changing the parameters from the UI or the plugin API. | |||
| Includes the following generator which inherit from [AGeometryGenerator](https://threepipe.org/plugins/geometry-generator/docs/classes/AGeometryGenerator.html): | |||
| - **plane**: [PlaneGeometryGenerator](https://threepipe.org/plugins/geometry-generator/docs/classes/PlaneGeometryGenerator), | |||
| - **sphere**: [SphereGeometryGenerator](https://threepipe.org/plugins/geometry-generator/docs/classes/SphereGeometryGenerator), | |||
| - **box**: [BoxGeometryGenerator](https://threepipe.org/plugins/geometry-generator/docs/classes/BoxGeometryGenerator), | |||
| - **circle**: [CircleGeometryGenerator](https://threepipe.org/plugins/geometry-generator/docs/classes/CircleGeometryGenerator), | |||
| - **torus**: [TorusGeometryGenerator](https://threepipe.org/plugins/geometry-generator/docs/classes/TorusGeometryGenerator), | |||
| - **cylinder**: [CylinderGeometryGenerator](https://threepipe.org/plugins/geometry-generator/docs/classes/CylinderGeometryGenerator), | |||
| Sample Usage: | |||
| ```typescript | |||
| import {ThreeViewer} from 'threepipe' | |||
| import {GeometryGeneratorPlugin} from '@threepipe/plugin-geometry-generator' | |||
| const viewer = new ThreeViewer({...}) | |||
| const generator = viewer.addPluginSync(GeometryGeneratorPlugin) | |||
| const sphere = generator.generateObject('sphere', {radius: 3}) | |||
| viewer.scene.addObject(sphere) | |||
| // to update the geometry | |||
| generator.updateGeometry(sphere.geometry, {radius: 4, widthSegments: 100}) | |||
| // to add a custom generator | |||
| generator.generators.custom = new CustomGenerator('custom') // Extend from AGeometryGenerator or implement GeometryGenerator | |||
| generator.uiConfig.uiRefresh?.() | |||
| ``` | |||
| @@ -0,0 +1,38 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Extra importer plugins</title> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "three": "./../../dist/index.mjs", | |||
| "threepipe": "./../../dist/index.mjs", | |||
| "@threepipe/plugin-geometry-generator": "./../../plugins/geometry-generator/dist/index.mjs", | |||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs" | |||
| } | |||
| } | |||
| </script> | |||
| <style id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||
| </head> | |||
| <body> | |||
| <div id="canvas-container"> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,29 @@ | |||
| import {_testFinish, CameraViewPlugin, PickingPlugin, ThreeViewer} from 'threepipe' | |||
| import {GeometryGeneratorPlugin} from '@threepipe/plugin-geometry-generator' | |||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| plugins: [PickingPlugin, CameraViewPlugin], | |||
| }) | |||
| const generator = viewer.addPluginSync(GeometryGeneratorPlugin) | |||
| viewer.scene.setBackgroundColor('#444466') | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||
| console.log(generator.generators) | |||
| const sphere = generator.generateObject('sphere', {radius: 0.5, widthSegments: 32, heightSegments: 32}) | |||
| viewer.scene.addObject(sphere) | |||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||
| ui.setupPluginUi(GeometryGeneratorPlugin) | |||
| ui.setupPluginUi(PickingPlugin) | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -20,7 +20,8 @@ | |||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs", | |||
| "@threepipe/plugin-tweakpane-editor": "./../../plugins/tweakpane-editor/dist/index.mjs", | |||
| "@threepipe/plugin-extra-importers": "./../../plugins/extra-importers/dist/index.mjs", | |||
| "@threepipe/plugin-blend-importer": "./../../plugins/blend-importer/dist/index.mjs" | |||
| "@threepipe/plugin-blend-importer": "./../../plugins/blend-importer/dist/index.mjs", | |||
| "@threepipe/plugin-geometry-generator": "./../../plugins/geometry-generator/dist/index.mjs" | |||
| } | |||
| } | |||
| @@ -36,6 +36,7 @@ import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||
| import {HierarchyUiPlugin, TweakpaneEditorPlugin} from '@threepipe/plugin-tweakpane-editor' | |||
| import {BlendLoadPlugin} from '@threepipe/plugin-blend-importer' | |||
| import {extraImportPlugins} from '@threepipe/plugin-extra-importers' | |||
| import {GeometryGeneratorPlugin} from '@threepipe/plugin-geometry-generator' | |||
| async function init() { | |||
| @@ -83,6 +84,7 @@ async function init() { | |||
| USDZLoadPlugin, | |||
| BlendLoadPlugin, | |||
| HierarchyUiPlugin, | |||
| GeometryGeneratorPlugin, | |||
| ...extraImportPlugins, | |||
| ]) | |||
| @@ -92,7 +94,7 @@ async function init() { | |||
| editor.loadPlugins({ | |||
| ['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin], | |||
| ['Interaction']: [HierarchyUiPlugin, PickingPlugin], | |||
| ['Interaction']: [HierarchyUiPlugin, PickingPlugin, GeometryGeneratorPlugin], | |||
| ['GBuffer']: [DepthBufferPlugin, NormalBufferPlugin], | |||
| ['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin, VignettePlugin], | |||
| ['Animation']: [GLTFAnimationPlugin, CameraViewPlugin], | |||
| @@ -0,0 +1,27 @@ | |||
| { | |||
| "name": "@threepipe/plugins-extra-importers", | |||
| "version": "0.1.1", | |||
| "lockfileVersion": 2, | |||
| "requires": true, | |||
| "packages": { | |||
| "": { | |||
| "name": "@threepipe/plugins-extra-importers", | |||
| "version": "0.1.1", | |||
| "license": "Apache-2.0", | |||
| "dependencies": { | |||
| "threepipe": "file:./../../src/" | |||
| }, | |||
| "devDependencies": {} | |||
| }, | |||
| "../../src": {}, | |||
| "node_modules/threepipe": { | |||
| "resolved": "../../src", | |||
| "link": true | |||
| } | |||
| }, | |||
| "dependencies": { | |||
| "threepipe": { | |||
| "version": "file:../../src" | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,58 @@ | |||
| { | |||
| "name": "@threepipe/plugins-geometry-generator", | |||
| "description": "Geometry generator plugin to create updatable parametric objects/geometries.", | |||
| "version": "0.1.0", | |||
| "devDependencies": { | |||
| }, | |||
| "dependencies": { | |||
| "threepipe": "file:./../../src/" | |||
| }, | |||
| "clean-package": { | |||
| "remove": [ | |||
| "clean-package", | |||
| "scripts", | |||
| "devDependencies", | |||
| "//", | |||
| "markdown-to-html" | |||
| ], | |||
| "replace": { | |||
| "dependencies": { | |||
| "threepipe": "^0.0.19" | |||
| } | |||
| } | |||
| }, | |||
| "type": "module", | |||
| "main": "dist/index.js", | |||
| "module": "dist/index.mjs", | |||
| "types": "dist/index.d.ts", | |||
| "files": [ | |||
| "dist", | |||
| "src" | |||
| ], | |||
| "scripts": { | |||
| "new:pack": "npm run prepare && clean-package && npm pack && clean-package restore", | |||
| "new:publish": "npm run prepare && clean-package && npm publish --access public && clean-package restore", | |||
| "prepare": "npm run build", | |||
| "build": "rimraf dist && NODE_ENV=production rollup -c", | |||
| "dev": "rollup -c -w", | |||
| "docs": "rimraf docs && npx typedoc" | |||
| }, | |||
| "author": "repalash <palash@shaders.app>", | |||
| "license": "Apache-2.0", | |||
| "keywords": [ | |||
| "three", | |||
| "three.js", | |||
| "threepipe", | |||
| "tweakpane", | |||
| "editor", | |||
| "plugin" | |||
| ], | |||
| "bugs": { | |||
| "url": "https://github.com/repalash/threepipe/issues" | |||
| }, | |||
| "homepage": "https://github.com/repalash/threepipe#readme", | |||
| "repository": { | |||
| "type": "git", | |||
| "url": "git://github.com/repalash/threepipe.git" | |||
| } | |||
| } | |||
| @@ -0,0 +1,100 @@ | |||
| // rollup.config.js | |||
| import commonjs from '@rollup/plugin-commonjs'; | |||
| import json from '@rollup/plugin-json'; | |||
| import resolve from '@rollup/plugin-node-resolve'; | |||
| import typescript from '@rollup/plugin-typescript'; | |||
| import license from 'rollup-plugin-license' | |||
| import packageJson from './package.json' assert {type: 'json'}; | |||
| import path from 'node:path' | |||
| import {fileURLToPath} from 'node:url'; | |||
| import postcss from 'rollup-plugin-postcss' | |||
| import replace from '@rollup/plugin-replace' | |||
| import terser from "@rollup/plugin-terser"; | |||
| const __filename = fileURLToPath(import.meta.url); | |||
| const __dirname = path.dirname(__filename); | |||
| const {name, version, author} = packageJson | |||
| // const {main, module, browser} = packageJson["clean-package"].replace | |||
| const isProduction = process.env.NODE_ENV === 'production' | |||
| const settings = { | |||
| globals: { | |||
| "threepipe": "threepipe", | |||
| "three": "threepipe" | |||
| }, | |||
| sourcemap: true | |||
| } | |||
| export default { | |||
| input: './src/index.ts', | |||
| output: [ | |||
| // { | |||
| // file: main, | |||
| // name: main, | |||
| // ...settings, | |||
| // format: 'cjs', | |||
| // plugins: [ | |||
| // isProduction && terser() | |||
| // ] | |||
| // }, | |||
| { | |||
| file: './dist/index.mjs', | |||
| ...settings, | |||
| name: name, | |||
| format: 'es', | |||
| plugins: [ | |||
| isProduction && terser() | |||
| ] | |||
| }, | |||
| { | |||
| file: './dist/index.js', | |||
| ...settings, | |||
| name: name, | |||
| format: 'umd', | |||
| plugins: [ | |||
| isProduction && terser() | |||
| ] | |||
| } | |||
| ], | |||
| external: Object.keys(settings.globals), | |||
| plugins: [ | |||
| replace({ | |||
| 'from \'three\'': 'from \'threepipe\'', | |||
| delimiters: ['', ''], | |||
| }), | |||
| replace({ | |||
| 'process.env.NODE_ENV': JSON.stringify('production'), | |||
| preventAssignment: true, | |||
| }), | |||
| postcss({ | |||
| modules: false, | |||
| autoModules: true, // todo; issues with typescript import css, because inject is false | |||
| inject: false, | |||
| minimize: isProduction, | |||
| // Or with custom options for `postcss-modules` | |||
| }), | |||
| json(), | |||
| resolve({}), | |||
| typescript({ | |||
| }), | |||
| commonjs({ | |||
| include: 'node_modules/**', | |||
| extensions: ['.js'], | |||
| ignoreGlobal: false, | |||
| sourceMap: false | |||
| }), | |||
| license({ | |||
| banner: ` | |||
| @license | |||
| ${name} v${version} | |||
| Copyright 2022<%= moment().format('YYYY') > 2022 ? '-' + moment().format('YYYY') : null %> ${author} | |||
| ${packageJson.license} License | |||
| `, | |||
| thirdParty: { | |||
| output: path.join(__dirname, 'dist', 'dependencies.txt'), | |||
| includePrivate: true, // Default is false. | |||
| }, | |||
| }) | |||
| ] | |||
| } | |||
| @@ -0,0 +1,125 @@ | |||
| import { | |||
| BufferAttribute, | |||
| BufferGeometry, | |||
| BufferGeometry2, | |||
| Class, | |||
| Float32BufferAttribute, | |||
| generateUiConfig, | |||
| getOrCall, | |||
| IGeometry, | |||
| UiObjectConfig, | |||
| } from 'threepipe' | |||
| declare module 'threepipe'{ | |||
| interface IGeometryUserData{ | |||
| generationParams?: { | |||
| type: string | |||
| // [key: string]: any | |||
| } | |||
| } | |||
| } | |||
| export interface GeometryGenerator<T=any>{ | |||
| generate(g?: IGeometry, parameters?: T): IGeometry | |||
| createUiConfig?(geometry: IGeometry): UiObjectConfig[] | |||
| } | |||
| function updateAttribute<T extends BufferAttribute=Float32BufferAttribute>(geometry: BufferGeometry, attribute: string, itemSize: number, array: any[], cls?: Class<T>) { | |||
| const attr = geometry.getAttribute(attribute) as T | |||
| const count = array.length / itemSize | |||
| if (attr && attr.count === count) { | |||
| attr.set(array) | |||
| attr.needsUpdate = true | |||
| } else { | |||
| geometry.setAttribute(attribute, new (cls ?? Float32BufferAttribute)(array, itemSize)) | |||
| } | |||
| return attr | |||
| } | |||
| function updateIndices(geometry: BufferGeometry, indices: any[]) { | |||
| const index = geometry.index | |||
| if (index && index.count === indices.length) { | |||
| index.set(indices) | |||
| index.needsUpdate = true // todo: wireframe attribute is not updating | |||
| } else geometry.setIndex(indices) | |||
| } | |||
| export function updateUi(geometry: BufferGeometry, childrenUi: () => UiObjectConfig[]) { | |||
| if (!(geometry as any).uiConfig) return | |||
| let oldUi = (geometry as any).uiConfig?.children?.find((c: UiObjectConfig) => c.tags?.includes('generatedGeometry')) | |||
| if (!oldUi) { | |||
| oldUi = { | |||
| type: 'folder', | |||
| label: 'Generation Params', | |||
| tags: ['generatedGeometry'], | |||
| children: [], | |||
| } | |||
| ;(geometry as any).uiConfig.children?.push(oldUi) | |||
| } | |||
| if (geometry.userData.__generationParamsUiType !== geometry.userData.generationParams.type) { | |||
| oldUi.children = childrenUi() | |||
| geometry.userData.__generationParamsUiType = geometry.userData.generationParams.type | |||
| oldUi.uiRefresh?.('postFrame', true) | |||
| } | |||
| } | |||
| export abstract class AGeometryGenerator<Tp=any> implements GeometryGenerator<Tp> { | |||
| constructor(public type: string) { | |||
| } | |||
| abstract defaultParams: Tp | |||
| createUiConfig(geometry: IGeometry): UiObjectConfig[] { | |||
| if (!geometry.userData.generationParams) return [] | |||
| const ui = generateUiConfig(geometry.userData.generationParams) | |||
| // @ts-expect-error we assume only functions will be generated since its an object | |||
| .map(v=>v()) | |||
| .filter(v=>getOrCall(v.property)?.[1] !== 'type') | |||
| ui.forEach(u=> { | |||
| u.onChange = () => this.generate(geometry) | |||
| }) | |||
| return ui | |||
| } | |||
| protected abstract _generateData(params: Tp): {indices: number[]; vertices: number[]; normals: number[]; uvs: number[], groups?: {start: number, count: number, materialIndex: number}[]} | |||
| generate(g?: IGeometry, parameters: Partial<Tp> = {}): IGeometry|BufferGeometry2 { | |||
| const geometry: IGeometry = g ?? new BufferGeometry2() | |||
| if (!geometry.userData.generationParams) geometry.userData.generationParams = {type: this.type} | |||
| geometry.userData.generationParams.type = this.type | |||
| if ((parameters as any).type) { | |||
| console.error('Cannot change type of generated geometry here, use the plugin instead') | |||
| return geometry | |||
| } | |||
| const params = { | |||
| ...this.defaultParams, | |||
| ...geometry.userData.generationParams, | |||
| ...parameters, | |||
| } as Tp | |||
| const {indices, vertices, normals, uvs, groups} = this._generateData(params) | |||
| // console.log(indices, vertices, normals, uvs, groups) | |||
| updateIndices(geometry, indices) | |||
| updateAttribute(geometry, 'position', 3, vertices) | |||
| updateAttribute(geometry, 'normal', 3, normals) | |||
| updateAttribute(geometry, 'uv', 2, uvs) | |||
| if (groups) { | |||
| geometry.clearGroups() | |||
| for (const group of groups) { | |||
| geometry.addGroup(group.start, group.count, group.materialIndex) | |||
| } | |||
| } | |||
| geometry.computeBoundingBox() | |||
| geometry.computeBoundingSphere() | |||
| Object.assign(geometry.userData.generationParams, params) | |||
| const childrenUi = ()=>this.createUiConfig(geometry) | |||
| updateUi(geometry, childrenUi) | |||
| geometry.setDirty() | |||
| return geometry | |||
| } | |||
| } | |||
| @@ -0,0 +1,90 @@ | |||
| import {AViewerPluginSync, BufferGeometry2, IGeometry, Mesh, PhysicalMaterial, ThreeViewer} from 'threepipe' | |||
| import {TorusGeometryGenerator} from './primitives/TorusGeometryGenerator' | |||
| import {CircleGeometryGenerator} from './primitives/CircleGeometryGenerator' | |||
| import {BoxGeometryGenerator} from './primitives/BoxGeometryGenerator' | |||
| import {SphereGeometryGenerator} from './primitives/SphereGeometryGenerator' | |||
| import {PlaneGeometryGenerator} from './primitives/PlaneGeometryGenerator' | |||
| import {CylinderGeometryGenerator} from './primitives/CylinderGeometryGenerator' | |||
| import {GeometryGenerator, updateUi} from './AGeometryGenerator' | |||
| /** | |||
| * GeometryGeneratorPlugin | |||
| * | |||
| * Geometry generator plugin to create updatable parametric objects/geometries. | |||
| * Includes support for several primitive types from three.js | |||
| */ | |||
| export class GeometryGeneratorPlugin extends AViewerPluginSync<''> { | |||
| public static readonly PluginType = 'GeometryGeneratorPlugin' | |||
| enabled = true | |||
| toJSON: any = undefined | |||
| generators: Record<string, GeometryGenerator> = { | |||
| plane: new PlaneGeometryGenerator('plane'), | |||
| sphere: new SphereGeometryGenerator('sphere'), | |||
| box: new BoxGeometryGenerator('box'), | |||
| circle: new CircleGeometryGenerator('circle'), | |||
| torus: new TorusGeometryGenerator('torus'), | |||
| cylinder: new CylinderGeometryGenerator('cylinder'), | |||
| } | |||
| generateObject(type: string, params?: any) { | |||
| const generator = this.generators[type] | |||
| if (!generator) throw new Error('Unknown generator type: ' + type) | |||
| const obj = new Mesh(new BufferGeometry2(), new PhysicalMaterial()) | |||
| generator.generate(obj.geometry, params) | |||
| obj.name = type | |||
| obj.geometry.name = 'Generated ' + type | |||
| return obj | |||
| } | |||
| generateGeometry(type: string, params: any, geometry?: IGeometry) { | |||
| const generator = this.generators[type] | |||
| if (!generator) throw new Error('Unknown generator type: ' + type) | |||
| const g = generator.generate(geometry, params) | |||
| g.name = 'Generated ' + type | |||
| return g | |||
| } | |||
| updateGeometry(geometry: IGeometry, params: any) { | |||
| if (!geometry.userData.generationParams?.type) throw new Error('Geometry is not generated') | |||
| const generator = this.generators[geometry.userData.generationParams.type] | |||
| if (!generator) throw new Error('Unknown generator type: ' + geometry.userData.generationParams.type) | |||
| generator.generate(geometry, params) | |||
| } | |||
| onAdded(v: ThreeViewer) { | |||
| super.onAdded(v) | |||
| v.scene.addEventListener('sceneUpdate', this._sceneUpdate) | |||
| } | |||
| protected _sceneUpdate = (e: any)=>{ | |||
| if (e.hierarchyChanged) { | |||
| const obj = e.object || this._viewer?.scene.modelRoot | |||
| console.log(obj) | |||
| if (obj) { | |||
| obj.traverse((o: any)=>{ | |||
| const type = o.geometry?.userData?.generationParams?.type | |||
| if (!type) return | |||
| updateUi(o.geometry, ()=>{ | |||
| const gen = this.generators[type] | |||
| return gen?.createUiConfig ? gen.createUiConfig(o.geometry) ?? [] : [] | |||
| }) | |||
| }) | |||
| } | |||
| } | |||
| } | |||
| uiConfig = { | |||
| type: 'folder', | |||
| label: 'Generate Geometry', | |||
| children: | |||
| [()=>Object.keys(this.generators).map((v) => ({ | |||
| type: 'button', | |||
| label: 'Generate ' + v, | |||
| value: async() => { | |||
| const obj = this.generateObject(v) | |||
| obj.name = v | |||
| this._viewer?.scene.addObject(obj) | |||
| }, | |||
| }))], | |||
| } | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| declare module '*.txt' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.glsl' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.vert' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.frag' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.module.scss' { | |||
| const content: any | |||
| export default content | |||
| export const stylesheet: string | |||
| } | |||
| declare module '*.module.css' { | |||
| const content: any | |||
| export default content | |||
| export const stylesheet: string | |||
| } | |||
| declare module '*.css' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| // export {} | |||
| // hack for typedoc | |||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||
| // declare type OffscreenCanvas = HTMLCanvasElement | |||
| @@ -0,0 +1,9 @@ | |||
| export {GeometryGeneratorPlugin} from './GeometryGeneratorPlugin' | |||
| export {AGeometryGenerator, updateUi, type GeometryGenerator} from './AGeometryGenerator' | |||
| export {BoxGeometryGenerator, type BoxGeometryGeneratorParams} from './primitives/BoxGeometryGenerator' | |||
| export {CircleGeometryGenerator, type CircleGeometryGeneratorParams} from './primitives/CircleGeometryGenerator' | |||
| export {CylinderGeometryGenerator, type CylinderGeometryGeneratorParams} from './primitives/CylinderGeometryGenerator' | |||
| export {PlaneGeometryGenerator, type PlaneGeometryGeneratorParams} from './primitives/PlaneGeometryGenerator' | |||
| export {SphereGeometryGenerator, type SphereGeometryGeneratorParams} from './primitives/SphereGeometryGenerator' | |||
| export {TorusGeometryGenerator, type TorusGeometryGeneratorParams} from './primitives/TorusGeometryGenerator' | |||
| @@ -0,0 +1,127 @@ | |||
| import {AGeometryGenerator} from '../AGeometryGenerator' | |||
| import {Vector3} from 'threepipe' | |||
| export interface BoxGeometryGeneratorParams { | |||
| width: number, | |||
| height: number, | |||
| depth: number, | |||
| widthSegments: number, | |||
| heightSegments: number, | |||
| depthSegments: number | |||
| } | |||
| export class BoxGeometryGenerator extends AGeometryGenerator<BoxGeometryGeneratorParams> { | |||
| defaultParams = { | |||
| width: 1, | |||
| height: 1, | |||
| depth: 1, | |||
| widthSegments: 1, | |||
| heightSegments: 1, | |||
| depthSegments: 1, | |||
| } | |||
| // helper variables | |||
| protected _buildPlane(state: any, u: 'x'|'y'|'z', v: 'x'|'y'|'z', w: 'x'|'y'|'z', udir: number, vdir: number, width: number, height: number, depth: number, gridX: number, gridY: number, materialIndex: number) { | |||
| const {indices, vertices, normals, uvs, numberOfVertices, groupStart, groups} = state | |||
| const segmentWidth = width / gridX | |||
| const segmentHeight = height / gridY | |||
| const widthHalf = width / 2 | |||
| const heightHalf = height / 2 | |||
| const depthHalf = depth / 2 | |||
| const gridX1 = gridX + 1 | |||
| const gridY1 = gridY + 1 | |||
| let vertexCounter = 0 | |||
| let groupCount = 0 | |||
| const vector = new Vector3() | |||
| // generate vertices, normals and uvs | |||
| for (let iy = 0; iy < gridY1; iy++) { | |||
| const y = iy * segmentHeight - heightHalf | |||
| for (let ix = 0; ix < gridX1; ix++) { | |||
| const x = ix * segmentWidth - widthHalf | |||
| // set values to correct vector component | |||
| vector[ u ] = x * udir | |||
| vector[ v ] = y * vdir | |||
| vector[ w ] = depthHalf | |||
| // now apply vector to vertex buffer | |||
| vertices.push(vector.x, vector.y, vector.z) | |||
| // set values to correct vector component | |||
| vector[ u ] = 0 | |||
| vector[ v ] = 0 | |||
| vector[ w ] = depth > 0 ? 1 : -1 | |||
| // now apply vector to normal buffer | |||
| normals.push(vector.x, vector.y, vector.z) | |||
| // uvs | |||
| uvs.push(ix / gridX) | |||
| uvs.push(1 - iy / gridY) | |||
| // counters | |||
| vertexCounter += 1 | |||
| } | |||
| } | |||
| // indices | |||
| // 1. you need three indices to draw a single face | |||
| // 2. a single segment consists of two faces | |||
| // 3. so we need to generate six (2*3) indices per segment | |||
| for (let iy = 0; iy < gridY; iy++) { | |||
| for (let ix = 0; ix < gridX; ix++) { | |||
| const a = numberOfVertices + ix + gridX1 * iy | |||
| const b = numberOfVertices + ix + gridX1 * (iy + 1) | |||
| const c = numberOfVertices + (ix + 1) + gridX1 * (iy + 1) | |||
| const d = numberOfVertices + (ix + 1) + gridX1 * iy | |||
| // faces | |||
| indices.push(a, b, d) | |||
| indices.push(b, c, d) | |||
| // increase counter | |||
| groupCount += 6 | |||
| } | |||
| } | |||
| // add a group to the geometry. this will ensure multi material support | |||
| groups.push({start: groupStart, count: groupCount, materialIndex}) | |||
| // calculate new start value for groups | |||
| state.groupStart += groupCount | |||
| // update total number of vertices | |||
| state.numberOfVertices += vertexCounter | |||
| } | |||
| protected _generateData(params: BoxGeometryGeneratorParams) { | |||
| const {width, height, depth} = params | |||
| let {widthSegments, heightSegments, depthSegments} = params | |||
| // segments | |||
| widthSegments = Math.floor(widthSegments) | |||
| heightSegments = Math.floor(heightSegments) | |||
| depthSegments = Math.floor(depthSegments) | |||
| // buffers | |||
| const state = { | |||
| indices: [], | |||
| vertices: [], | |||
| normals: [], | |||
| uvs: [], | |||
| numberOfVertices: 0, | |||
| groupStart: 0, | |||
| groups: [], | |||
| } | |||
| // build each side of the box geometry | |||
| this._buildPlane(state, 'z', 'y', 'x', -1, -1, depth, height, width, depthSegments, heightSegments, 0) // px | |||
| this._buildPlane(state, 'z', 'y', 'x', 1, -1, depth, height, -width, depthSegments, heightSegments, 1) // nx | |||
| this._buildPlane(state, 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2) // py | |||
| this._buildPlane(state, 'x', 'z', 'y', 1, -1, width, depth, -height, widthSegments, depthSegments, 3) // ny | |||
| this._buildPlane(state, 'x', 'y', 'z', 1, -1, width, height, depth, widthSegments, heightSegments, 4) // pz | |||
| this._buildPlane(state, 'x', 'y', 'z', -1, -1, width, height, -depth, widthSegments, heightSegments, 5) // nz | |||
| return state | |||
| } | |||
| } | |||
| @@ -0,0 +1,79 @@ | |||
| import {AGeometryGenerator} from '../AGeometryGenerator' | |||
| import {Vector2, Vector3} from 'threepipe' | |||
| export interface CircleGeometryGeneratorParams { | |||
| radius: number, | |||
| segments: number, | |||
| thetaStart: number, | |||
| thetaLength: number | |||
| } | |||
| export class CircleGeometryGenerator extends AGeometryGenerator<CircleGeometryGeneratorParams> { | |||
| defaultParams = { | |||
| radius: 1, | |||
| segments: 32, | |||
| thetaStart: 0, | |||
| thetaLength: Math.PI * 2, | |||
| } | |||
| protected _generateData(params: CircleGeometryGeneratorParams) { | |||
| const {radius, thetaStart, thetaLength} = params | |||
| const segments = Math.max(3, params.segments) | |||
| // buffers | |||
| const indices = [] | |||
| const vertices = [] | |||
| const normals = [] | |||
| const uvs = [] | |||
| // helper variables | |||
| const vertex = new Vector3() | |||
| const uv = new Vector2() | |||
| // center point | |||
| vertices.push(0, 0, 0) | |||
| normals.push(0, 0, 1) | |||
| uvs.push(0.5, 0.5) | |||
| for (let s = 0, i = 3; s <= segments; s++, i += 3) { | |||
| const segment = thetaStart + s / segments * thetaLength | |||
| // vertex | |||
| vertex.x = radius * Math.cos(segment) | |||
| vertex.y = radius * Math.sin(segment) | |||
| vertices.push(vertex.x, vertex.y, vertex.z) | |||
| // normal | |||
| normals.push(0, 0, 1) | |||
| // uvs | |||
| uv.x = (vertices[ i ] / radius + 1) / 2 | |||
| uv.y = (vertices[ i + 1 ] / radius + 1) / 2 | |||
| uvs.push(uv.x, uv.y) | |||
| } | |||
| // indices | |||
| for (let i = 1; i <= segments; i++) { | |||
| indices.push(i, i + 1, 0) | |||
| } | |||
| return {indices, vertices, normals, uvs} | |||
| } | |||
| } | |||
| @@ -0,0 +1,193 @@ | |||
| import {AGeometryGenerator} from '../AGeometryGenerator' | |||
| import {Vector2, Vector3} from 'threepipe' | |||
| export interface CylinderGeometryGeneratorParams { | |||
| radiusTop: number, | |||
| radiusBottom: number, | |||
| height: number, | |||
| radialSegments: number, | |||
| heightSegments: number, | |||
| openEnded: boolean, | |||
| thetaStart: number, | |||
| thetaLength: number | |||
| } | |||
| export class CylinderGeometryGenerator extends AGeometryGenerator<CylinderGeometryGeneratorParams> { | |||
| defaultParams = { | |||
| radiusTop: 1, | |||
| radiusBottom: 1, | |||
| height: 1, | |||
| radialSegments: 32, | |||
| heightSegments: 1, | |||
| openEnded: false, | |||
| thetaStart: 0, | |||
| thetaLength: Math.PI * 2, | |||
| } | |||
| protected _generateTorso(state: any) { | |||
| const {radiusTop, radiusBottom, height, | |||
| radialSegments, heightSegments, | |||
| thetaStart, thetaLength, indexArray, indices, groups, | |||
| vertices, normals, uvs, groupStart, halfHeight} = state | |||
| const normal = new Vector3() | |||
| const vertex = new Vector3() | |||
| let groupCount = 0 | |||
| // this will be used to calculate the normal | |||
| const slope = (radiusBottom - radiusTop) / height | |||
| // generate vertices, normals and uvs | |||
| for (let y = 0; y <= heightSegments; y++) { | |||
| const indexRow = [] | |||
| const v = y / heightSegments | |||
| // calculate the radius of the current row | |||
| const radius = v * (radiusBottom - radiusTop) + radiusTop | |||
| for (let x = 0; x <= radialSegments; x++) { | |||
| const u = x / radialSegments | |||
| const theta = u * thetaLength + thetaStart | |||
| const sinTheta = Math.sin(theta) | |||
| const cosTheta = Math.cos(theta) | |||
| // vertex | |||
| vertex.x = radius * sinTheta | |||
| vertex.y = -v * height + halfHeight | |||
| vertex.z = radius * cosTheta | |||
| vertices.push(vertex.x, vertex.y, vertex.z) | |||
| // normal | |||
| normal.set(sinTheta, slope, cosTheta).normalize() | |||
| normals.push(normal.x, normal.y, normal.z) | |||
| // uv | |||
| uvs.push(u, 1 - v) | |||
| // save index of vertex in respective row | |||
| indexRow.push(state.index++) | |||
| } | |||
| // now save vertices of the row in our index array | |||
| indexArray.push(indexRow) | |||
| } | |||
| // generate indices | |||
| for (let x = 0; x < radialSegments; x++) { | |||
| for (let y = 0; y < heightSegments; y++) { | |||
| // we use the index array to access the correct indices | |||
| const a = indexArray[ y ][ x ] | |||
| const b = indexArray[ y + 1 ][ x ] | |||
| const c = indexArray[ y + 1 ][ x + 1 ] | |||
| const d = indexArray[ y ][ x + 1 ] | |||
| // faces | |||
| indices.push(a, b, d) | |||
| indices.push(b, c, d) | |||
| // update group counter | |||
| groupCount += 6 | |||
| } | |||
| } | |||
| // add a group to the geometry. this will ensure multi material support | |||
| groups.push({start: groupStart, count: groupCount, materialIndex: 0}) | |||
| // calculate new start value for groups | |||
| state.groupStart += groupCount | |||
| } | |||
| protected _generateCap(state: any, top: boolean) { | |||
| const {radiusTop, radiusBottom, | |||
| radialSegments, | |||
| thetaStart, thetaLength, indices, groups, | |||
| vertices, normals, uvs, groupStart, halfHeight} = state | |||
| // save the index of the first center vertex | |||
| const centerIndexStart = state.index | |||
| const uv = new Vector2() | |||
| const vertex = new Vector3() | |||
| let groupCount = 0 | |||
| const radius = top === true ? radiusTop : radiusBottom | |||
| const sign = top === true ? 1 : -1 | |||
| // first we generate the center vertex data of the cap. | |||
| // because the geometry needs one set of uvs per face, | |||
| // we must generate a center vertex per face/segment | |||
| for (let x = 1; x <= radialSegments; x++) { | |||
| // vertex | |||
| vertices.push(0, halfHeight * sign, 0) | |||
| // normal | |||
| normals.push(0, sign, 0) | |||
| // uv | |||
| uvs.push(0.5, 0.5) | |||
| // increase index | |||
| state.index++ | |||
| } | |||
| // save the index of the last center vertex | |||
| const centerIndexEnd = state.index | |||
| // now we generate the surrounding vertices, normals and uvs | |||
| for (let x = 0; x <= radialSegments; x++) { | |||
| const u = x / radialSegments | |||
| const theta = u * thetaLength + thetaStart | |||
| const cosTheta = Math.cos(theta) | |||
| const sinTheta = Math.sin(theta) | |||
| // vertex | |||
| vertex.x = radius * sinTheta | |||
| vertex.y = halfHeight * sign | |||
| vertex.z = radius * cosTheta | |||
| vertices.push(vertex.x, vertex.y, vertex.z) | |||
| // normal | |||
| normals.push(0, sign, 0) | |||
| // uv | |||
| uv.x = cosTheta * 0.5 + 0.5 | |||
| uv.y = sinTheta * 0.5 * sign + 0.5 | |||
| uvs.push(uv.x, uv.y) | |||
| // increase index | |||
| state.index++ | |||
| } | |||
| // generate indices | |||
| for (let x = 0; x < radialSegments; x++) { | |||
| const c = centerIndexStart + x | |||
| const i = centerIndexEnd + x | |||
| if (top === true) { | |||
| // face top | |||
| indices.push(i, i + 1, c) | |||
| } else { | |||
| // face bottom | |||
| indices.push(i + 1, i, c) | |||
| } | |||
| groupCount += 3 | |||
| } | |||
| // add a group to the geometry. this will ensure multi material support | |||
| groups.push({start: groupStart, count: groupCount, materialIndex: top === true ? 1 : 2}) | |||
| // calculate new start value for groups | |||
| state.groupStart += groupCount | |||
| } | |||
| protected _generateData(params: CylinderGeometryGeneratorParams) { | |||
| let {radialSegments, heightSegments} = params | |||
| radialSegments = Math.floor(radialSegments) | |||
| heightSegments = Math.floor(heightSegments) | |||
| const state = { | |||
| indices: [], | |||
| vertices: [], | |||
| normals: [], | |||
| uvs: [], | |||
| numberOfVertices: 0, | |||
| groupStart: 0, | |||
| groups: [], | |||
| index: 0, | |||
| indexArray: [], | |||
| halfHeight: params.height / 2, | |||
| ...params, | |||
| radialSegments, | |||
| heightSegments, | |||
| } | |||
| // generate geometry | |||
| this._generateTorso(state) | |||
| if (params.openEnded === false) { | |||
| if (params.radiusTop > 0) this._generateCap(state, true) | |||
| if (params.radiusBottom > 0) this._generateCap(state, false) | |||
| } | |||
| return state | |||
| } | |||
| } | |||
| @@ -0,0 +1,61 @@ | |||
| import {AGeometryGenerator} from '../AGeometryGenerator' | |||
| export interface PlaneGeometryGeneratorParams { | |||
| width: number, | |||
| height: number, | |||
| widthSegments: number, | |||
| heightSegments: number | |||
| } | |||
| export class PlaneGeometryGenerator extends AGeometryGenerator<PlaneGeometryGeneratorParams> { | |||
| defaultParams = { | |||
| width: 1, | |||
| height: 1, | |||
| widthSegments: 2, | |||
| heightSegments: 2, | |||
| } | |||
| protected _generateData(params: PlaneGeometryGeneratorParams) { | |||
| const widthHalf = params.width / 2 | |||
| const heightHalf = params.height / 2 | |||
| const gridX = Math.floor(params.widthSegments) | |||
| const gridY = Math.floor(params.heightSegments) | |||
| const gridX1 = gridX + 1 | |||
| const gridY1 = gridY + 1 | |||
| const segmentWidth = params.width / gridX | |||
| const segmentHeight = params.height / gridY | |||
| const indices = [] | |||
| const vertices = [] | |||
| const normals = [] | |||
| const uvs = [] | |||
| for (let iy = 0; iy < gridY1; iy++) { | |||
| const y = iy * segmentHeight - heightHalf | |||
| for (let ix = 0; ix < gridX1; ix++) { | |||
| const x = ix * segmentWidth - widthHalf | |||
| vertices.push(x, -y, 0) | |||
| normals.push(0, 0, 1) | |||
| uvs.push(ix / gridX) | |||
| uvs.push(1 - iy / gridY) | |||
| } | |||
| } | |||
| for (let iy = 0; iy < gridY; iy++) { | |||
| for (let ix = 0; ix < gridX; ix++) { | |||
| const a = ix + gridX1 * iy | |||
| const b = ix + gridX1 * (iy + 1) | |||
| const c = ix + 1 + gridX1 * (iy + 1) | |||
| const d = ix + 1 + gridX1 * iy | |||
| indices.push(a, b, d) | |||
| indices.push(b, c, d) | |||
| } | |||
| } | |||
| return {indices, vertices, normals, uvs} | |||
| } | |||
| } | |||
| @@ -0,0 +1,90 @@ | |||
| import {Vector3} from 'threepipe' | |||
| import {AGeometryGenerator} from '../AGeometryGenerator' | |||
| export interface SphereGeometryGeneratorParams { | |||
| radius: number, | |||
| widthSegments: number, | |||
| heightSegments: number, | |||
| phiStart: number, | |||
| phiLength: number, | |||
| thetaStart: number, | |||
| thetaLength: number, | |||
| } | |||
| export class SphereGeometryGenerator extends AGeometryGenerator<SphereGeometryGeneratorParams> { | |||
| defaultParams = { | |||
| radius: 1, | |||
| widthSegments: 32, | |||
| heightSegments: 16, | |||
| phiStart: 0, | |||
| phiLength: Math.PI * 2, | |||
| thetaStart: 0, | |||
| thetaLength: Math.PI, | |||
| } | |||
| protected _generateData(params: SphereGeometryGeneratorParams) { | |||
| const {radius, phiStart, phiLength, thetaStart, thetaLength} = params | |||
| let {widthSegments, heightSegments} = params | |||
| widthSegments = Math.max(3, Math.floor(widthSegments)) | |||
| heightSegments = Math.max(2, Math.floor(heightSegments)) | |||
| const thetaEnd = Math.min(thetaStart + thetaLength, Math.PI) | |||
| let index = 0 | |||
| const grid = [] | |||
| const vertex = new Vector3() | |||
| const normal = new Vector3() | |||
| // buffers | |||
| const indices = [] | |||
| const vertices = [] | |||
| const normals = [] | |||
| const uvs = [] | |||
| // generate vertices, normals and uvs | |||
| for (let iy = 0; iy <= heightSegments; iy++) { | |||
| const verticesRow = [] | |||
| const v = iy / heightSegments | |||
| // special case for the poles | |||
| let uOffset = 0 | |||
| if (iy === 0 && thetaStart === 0) { | |||
| uOffset = 0.5 / widthSegments | |||
| } else if (iy === heightSegments && thetaEnd === Math.PI) { | |||
| uOffset = -0.5 / widthSegments | |||
| } | |||
| for (let ix = 0; ix <= widthSegments; ix++) { | |||
| const u = ix / widthSegments | |||
| // vertex | |||
| vertex.x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength) | |||
| vertex.y = radius * Math.cos(thetaStart + v * thetaLength) | |||
| vertex.z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength) | |||
| vertices.push(vertex.x, vertex.y, vertex.z) | |||
| // normal | |||
| normal.copy(vertex).normalize() | |||
| normals.push(normal.x, normal.y, normal.z) | |||
| // uv | |||
| uvs.push(u + uOffset, 1 - v) | |||
| verticesRow.push(index++) | |||
| } | |||
| grid.push(verticesRow) | |||
| } | |||
| // indices | |||
| for (let iy = 0; iy < heightSegments; iy++) { | |||
| for (let ix = 0; ix < widthSegments; ix++) { | |||
| const a = grid[iy][ix + 1] | |||
| const b = grid[iy][ix] | |||
| const c = grid[iy + 1][ix] | |||
| const d = grid[iy + 1][ix + 1] | |||
| if (iy !== 0 || thetaStart > 0) indices.push(a, b, d) | |||
| if (iy !== heightSegments - 1 || thetaEnd < Math.PI) indices.push(b, c, d) | |||
| } | |||
| } | |||
| return {indices, vertices, normals, uvs} | |||
| } | |||
| } | |||
| @@ -0,0 +1,102 @@ | |||
| import {AGeometryGenerator} from '../AGeometryGenerator' | |||
| import {Vector3} from 'threepipe' | |||
| export interface TorusGeometryGeneratorParams { | |||
| radius: number, | |||
| tube: number, | |||
| radialSegments: number, | |||
| tubularSegments: number, | |||
| arc: number | |||
| } | |||
| export class TorusGeometryGenerator extends AGeometryGenerator<TorusGeometryGeneratorParams> { | |||
| defaultParams = { | |||
| radius: 1, | |||
| tube: 0.4, | |||
| radialSegments: 12, | |||
| tubularSegments: 48, | |||
| arc: Math.PI * 2, | |||
| } | |||
| protected _generateData(params: TorusGeometryGeneratorParams) { | |||
| const {radius, tube, arc} = params | |||
| let {radialSegments, tubularSegments} = params | |||
| radialSegments = Math.floor(radialSegments) | |||
| tubularSegments = Math.floor(tubularSegments) | |||
| // buffers | |||
| const indices = [] | |||
| const vertices = [] | |||
| const normals = [] | |||
| const uvs = [] | |||
| // helper variables | |||
| const center = new Vector3() | |||
| const vertex = new Vector3() | |||
| const normal = new Vector3() | |||
| // generate vertices, normals and uvs | |||
| for (let j = 0; j <= radialSegments; j++) { | |||
| for (let i = 0; i <= tubularSegments; i++) { | |||
| const u = i / tubularSegments * arc | |||
| const v = j / radialSegments * Math.PI * 2 | |||
| // vertex | |||
| vertex.x = (radius + tube * Math.cos(v)) * Math.cos(u) | |||
| vertex.y = (radius + tube * Math.cos(v)) * Math.sin(u) | |||
| vertex.z = tube * Math.sin(v) | |||
| vertices.push(vertex.x, vertex.y, vertex.z) | |||
| // normal | |||
| center.x = radius * Math.cos(u) | |||
| center.y = radius * Math.sin(u) | |||
| normal.subVectors(vertex, center).normalize() | |||
| normals.push(normal.x, normal.y, normal.z) | |||
| // uv | |||
| uvs.push(i / tubularSegments) | |||
| uvs.push(j / radialSegments) | |||
| } | |||
| } | |||
| // generate indices | |||
| for (let j = 1; j <= radialSegments; j++) { | |||
| for (let i = 1; i <= tubularSegments; i++) { | |||
| // indices | |||
| const a = (tubularSegments + 1) * j + i - 1 | |||
| const b = (tubularSegments + 1) * (j - 1) + i - 1 | |||
| const c = (tubularSegments + 1) * (j - 1) + i | |||
| const d = (tubularSegments + 1) * j + i | |||
| // faces | |||
| indices.push(a, b, d) | |||
| indices.push(b, c, d) | |||
| } | |||
| } | |||
| return {indices, vertices, normals, uvs} | |||
| } | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| { | |||
| "compilerOptions": { | |||
| "baseUrl": "./src", | |||
| "rootDir": "./src", | |||
| "allowJs": false, | |||
| "checkJs": false, | |||
| "skipLibCheck": true, | |||
| "allowSyntheticDefaultImports": true, | |||
| "experimentalDecorators": true, | |||
| "isolatedModules": true, | |||
| "module": "es2020", | |||
| "noImplicitAny": true, | |||
| "declaration": true, | |||
| "declarationMap": true, | |||
| "declarationDir": "dist", | |||
| "outDir": "dist", | |||
| "noImplicitThis": true, | |||
| "noUnusedLocals": true, | |||
| "noUnusedParameters": true, | |||
| "removeComments": false, | |||
| "preserveConstEnums": true, | |||
| "moduleResolution": "node", | |||
| "emitDecoratorMetadata": false, | |||
| "sourceMap": true, | |||
| "target": "ES2020", | |||
| "strictNullChecks": true, | |||
| "lib": [ | |||
| "es2020", | |||
| "esnext", | |||
| "dom" | |||
| ], | |||
| "paths": { | |||
| "three": ["threepipe"] | |||
| } | |||
| }, | |||
| "include": [ | |||
| "src/**/*" | |||
| ], | |||
| "exclude": [ | |||
| "node_modules", | |||
| "**/*.spec.ts", | |||
| "dist" | |||
| ] | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| { | |||
| "extends": [ | |||
| "../../typedoc.json" | |||
| ], | |||
| "entryPoints": [ | |||
| "src/index.ts" | |||
| ], | |||
| "name": "Threepipe Geometry Generator Plugin", | |||
| "readme": "none" | |||
| } | |||
| @@ -1,5 +1,5 @@ | |||
| import {execEachPlugin} from "./utils.mjs"; | |||
| // Each plugin should have "prepare" that will also build the plugin | |||
| execEachPlugin('npm install') // install dependencies | |||
| execEachPlugin('npm ci') // install dependencies | |||
| execEachPlugin('npm run docs') | |||