Explorar el Código

Add TransformControlsPlugin, EditorViewWidgetPlugin and their examples.

master
Palash Bansal hace 2 años
padre
commit
ef3d5364c4
No account linked to committer's email address

+ 56
- 2
README.md Ver fichero

@@ -95,6 +95,7 @@ To make changes and run the example, click on the CodePen button on the top righ
- [NormalBufferPlugin](#normalbufferplugin) - Pre-rendering of normal buffer
- [GBufferPlugin](#depthnormalbufferplugin) - Pre-rendering of depth and normal buffers in a single pass buffer
- [PickingPlugin](#pickingplugin) - Adds support for selecting objects in the viewer with user interactions and selection widgets
- [TransformControlsPlugin](#transformcontrolsplugin) - Adds support for moving, rotating and scaling objects in the viewer with interactive widgets
- [GLTFAnimationPlugin](#gltfanimationplugin) - Add support for playing and seeking gltf animations
- [PopmotionPlugin](#popmotionplugin) - Integrates with popmotion.io library for animation/tweening
- [CameraViewPlugin](#cameraviewplugin) - Add support for saving, loading, animating, looping between camera views
@@ -110,6 +111,7 @@ To make changes and run the example, click on the CodePen button on the top righ
- [FragmentClippingExtensionPlugin](#fragmentclippingextensionplugin) - Fragment/SDF Clipping material extension for PhysicalMaterial
- [HDRiGroundPlugin](#hdrigroundplugin) - Add support for ground projected hdri/skybox to the webgl background shader.
- [VirtualCamerasPlugin](#virtualcamerasplugin) - Add support for rendering virtual cameras before the main one every frame.
- [EditorViewWidgetPlugin](#editorviewwidgetplugin) - Adds an interactive ViewHelper/AxisHelper that syncs with the main camera.
- [Rhino3dmLoadPlugin](#rhino3dmloadplugin) - Add support for loading .3dm files
- [PLYLoadPlugin](#plyloadplugin) - Add support for loading .ply files
- [STLLoadPlugin](#stlloadplugin) - Add support for loading .stl files
@@ -2255,6 +2257,34 @@ pickingPlugin.addEventListener('hoverObjectChanged', (e)=>{

```

## TransformControlsPlugin

[//]: # (todo: image)

[Example](https://threepipe.org/examples/#transform-controls-plugin/) —
[Source Code](./src/plugins/interaction/TransformControlsPlugin.ts) —
[API Reference](https://threepipe.org/docs/classes/TransformControlsPlugin.html)

Transform Controls Plugin adds support for moving, rotating and scaling objects in the viewer with interactive widgets.

Under the hood, TransformControlsPlugin uses [TransformControls2](https://threepipe.org/docs/classes/TransformControls2) to provide the interactive controls, it is a extended version of three.js [TransformControls](https://threejs.org/docs/#examples/en/controls/TransformControls).

When the plugin is added to the viewer, it interfaces with the [PickingPlugin](#pickingplugin) and shows the control gizmos when an object is selected and hides them when the object is unselected.

If the PickingPlugin is not added to the viewer before the TransformControlsPlugin, it is added automatically with the plugin.

```typescript
import {ThreeViewer, TransformControlsPlugin} from 'threepipe'

const viewer = new ThreeViewer({...})

const transfromControlsPlugin = viewer.addPluginSync(new TransformControlsPlugin())

// Get the underlying transform controls
console.log(transfromControlsPlugin.transformControls)
```


## GLTFAnimationPlugin

[//]: # (todo: image)
@@ -2742,7 +2772,7 @@ import {ThreeViewer, VirtualCamerasPlugin} from 'threepipe'

const viewer = new ThreeViewer({...})

const hdriGround = viewer.addPluginSync(new VirtualCamerasPlugin())
const virtualCameras = viewer.addPluginSync(new VirtualCamerasPlugin())

const camera = new PerspectiveCamera2('orbit', viewer.canvas, false, 45, 1)
camera.name = name
@@ -2758,7 +2788,31 @@ const vCam = virtualCameras.addCamera(camera)
console.log(vCam.target) // target is a WebGLRenderTarget/IRenderTarget
```

Check the [virtual camera](https://threepipe.org/examples/#hdri-ground-plugin/) example for using the texture in the scene.
Check the [virtual camera](https://threepipe.org/examples/#virtual-camera/) example for using the texture in the scene.

## EditorViewWidgetPlugin

[//]: # (todo: image)

[Example](https://threepipe.org/examples/#editor-view-widget-plugin/) —
[Source Code](./src/plugins/interaction/EditorViewWidgetPlugin.ts) —
[API Reference](https://threepipe.org/docs/classes/EditorViewWidgetPlugin.html)

EditorViewWidgetPlugin adds a ViewHelper in the parent of the viewer canvas to show the current camera view and allow the user to change the camera view to one of the primary world axes.

Simply add the plugin to the viewer to see the widget.

```typescript
import {ThreeViewer, EditorViewWidgetPlugin} from 'threepipe'

const viewer = new ThreeViewer({...})

const plugin = viewer.addPluginSync(new EditorViewWidgetPlugin())

// to hide the widget
plugin.enabled = false
```


## Rhino3dmLoadPlugin


+ 36
- 0
examples/editor-view-widget-plugin/index.html Ver fichero

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Editor View Widget Plugin</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": {
"threepipe": "./../../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>

+ 35
- 0
examples/editor-view-widget-plugin/script.ts Ver fichero

@@ -0,0 +1,35 @@
import {_testFinish, EditorViewWidgetPlugin, IObject3D, ThreeViewer, timeout} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'

async function init() {

const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
msaa: true,
})

viewer.scene.setBackgroundColor(0x151822)

const plugin = viewer.addPluginSync(new EditorViewWidgetPlugin('bottom-left', 256))

await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr')
await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf')

const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true))
ui.setupPluginUi(EditorViewWidgetPlugin)

// look at the model from left
plugin.setOrientation('-z')

await timeout(1000) // wait for 1 sec

// look at the model from back
plugin.setOrientation('-x')

await timeout(1000) // wait for 1 sec

// look at the model from front
plugin.setOrientation('+z')
}

init().then(_testFinish)

+ 3
- 1
examples/index.html Ver fichero

@@ -239,8 +239,9 @@
<li><a href="./picking-plugin/">Picking (Selection) Plugin </a></li>
<li><a href="./camera-view-plugin/">Camera View (Animation) Plugin </a></li>
<li><a href="./dropzone-plugin/">Dropzone (Drag & Drop) Plugin </a></li>
<li><a href="./transform-controls-plugin/">Transform Controls Plugin </a></li>
<li><a href="./editor-view-widget-plugin/">Editor View Widget Plugin </a></li>
<li><a href="./fullscreen-plugin/">FullScreen Plugin </a></li>
<li><a href="./geometry-generator-plugin/">Geometry Generator Plugin </a></li>
</ul>
<h2 class="category">Import</h2>
<ul>
@@ -298,6 +299,7 @@
<ul>
<li><a href="./hdri-ground-plugin/">HDRi Ground Plugin <br/>(Projected Skybox)</a></li>
<li><a href="./render-target-preview/">Render Target Preview Plugin </a></li>
<li><a href="./geometry-generator-plugin/">Geometry Generator Plugin </a></li>
<li><a href="./geometry-uv-preview/">Geometry UV Preview Plugin </a></li>
<li><a href="./parallel-asset-import/">Parallel Asset Import </a></li>
<li><a href="./obj-to-glb/">Convert OBJ to GLB </a></li>

+ 36
- 0
examples/transform-controls-plugin/index.html Ver fichero

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Transform Controls Plugin</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": {
"threepipe": "./../../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>

+ 32
- 0
examples/transform-controls-plugin/script.ts Ver fichero

@@ -0,0 +1,32 @@
import {_testFinish, IObject3D, PickingPlugin, ThreeViewer, TransformControlsPlugin} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'

async function init() {

const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
})

viewer.scene.setBackgroundColor(0x151822)

const picking = viewer.addPluginSync(PickingPlugin)

const transformControlsPlugin = viewer.addPluginSync(TransformControlsPlugin)

await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr')
const model = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf')

const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true))
ui.setupPluginUi(TransformControlsPlugin)
ui.setupPluginUi(PickingPlugin)

// Get the underlying transform controls (instance of TransformControls2)
const transformControls = transformControlsPlugin.transformControls
console.log(transformControls)

// Transform controls plugin automatically tracks the selected object in the PickingPlugin and shows the transform controls
picking.setSelectedObject(model)

}

init().then(_testFinish)

+ 1
- 1
examples/tweakpane-editor/index.html Ver fichero

@@ -34,7 +34,7 @@
<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>
<body class="code-preview-container">
<div id="canvas-container">
<canvas id="mcanvas"></canvas>
</div>

+ 9
- 3
examples/tweakpane-editor/script.ts Ver fichero

@@ -6,6 +6,7 @@ import {
CustomBumpMapPlugin,
DepthBufferPlugin,
DropzonePlugin,
EditorViewWidgetPlugin,
FilmicGrainPlugin,
FragmentClippingExtensionPlugin,
FrameFadePlugin,
@@ -27,6 +28,7 @@ import {
STLLoadPlugin,
ThreeViewer,
TonemapPlugin,
TransformControlsPlugin,
USDZLoadPlugin,
ViewerUiConfigPlugin,
VignettePlugin,
@@ -60,6 +62,8 @@ async function init() {
new ProgressivePlugin(),
GLTFAnimationPlugin,
PickingPlugin,
TransformControlsPlugin,
EditorViewWidgetPlugin,
CameraViewPlugin,
ViewerUiConfigPlugin,
ClearcoatTintPlugin,
@@ -94,15 +98,17 @@ async function init() {

editor.loadPlugins({
['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin],
['Interaction']: [HierarchyUiPlugin, PickingPlugin, GeometryGeneratorPlugin],
['Interaction']: [HierarchyUiPlugin, TransformControlsPlugin, PickingPlugin, GeometryGeneratorPlugin, EditorViewWidgetPlugin],
['GBuffer']: [DepthBufferPlugin, NormalBufferPlugin],
['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin, VignettePlugin],
['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin, VignettePlugin, ChromaticAberrationPlugin, FilmicGrainPlugin],
['Animation']: [GLTFAnimationPlugin, CameraViewPlugin],
['Extras']: [HDRiGroundPlugin, Rhino3dmLoadPlugin, ClearcoatTintPlugin, FragmentClippingExtensionPlugin, NoiseBumpMaterialPlugin, CustomBumpMapPlugin, VirtualCamerasPlugin],
['Debug']: [RenderTargetPreviewPlugin],
})

viewer.scene.addObject(new HemisphereLight(0xffffff, 0x444444, 5))
const hemiLight = viewer.scene.addObject(new HemisphereLight(0xffffff, 0x444444, 5))
hemiLight.name = 'Hemisphere Light'

await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr')

// const result = await viewer.load<IObject3D>('https://cdn.jsdelivr.net/gh/KhronosGroup/glTF-Blender-Exporter@master/polly/project_polly.gltf', {

+ 16
- 20
package-lock.json Ver fichero

@@ -1,18 +1,20 @@
{
"name": "threepipe",
"version": "0.0.18",
"version": "0.0.19",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "threepipe",
"version": "0.0.18",
"version": "0.0.19",
"license": "Apache-2.0",
"dependencies": {
"@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1018/package.tgz",
"@types/webxr": "^0.5.1",
"@types/wicg-file-system-access": "^2020.9.5",
"ts-browser-helpers": "^0.8.0"
"stats.js": "^0.17.0",
"ts-browser-helpers": "^0.9.0",
"uiconfig.js": "^0.0.9"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^25.0.0",
@@ -38,13 +40,11 @@
"rollup-plugin-glsl": "^1.3.0",
"rollup-plugin-license": "^3.0.1",
"rollup-plugin-postcss": "^4.0.2",
"stats.js": "^0.17.0",
"three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2018/package.tgz",
"tslib": "^2.5.0",
"typedoc": "^0.24.7",
"typescript": "^5.0.4",
"typescript-plugin-css-modules": "^5.0.1",
"uiconfig.js": "^0.0.9"
"typescript-plugin-css-modules": "^5.0.1"
},
"optionalDependencies": {
"win-node-env": "^0.6.1"
@@ -8980,8 +8980,7 @@
"node_modules/stats.js": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
"dev": true
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw=="
},
"node_modules/statuses": {
"version": "1.5.0",
@@ -9599,9 +9598,9 @@
}
},
"node_modules/ts-browser-helpers": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.8.0.tgz",
"integrity": "sha512-r7k6udt+tS/FZLwTUbzSgHdHQ56R/MD1oS2rFcEk8O4jKUTzuDVqlUoLX1EjJQYEBGMBI8TEyySTkuomxewZEw==",
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.9.0.tgz",
"integrity": "sha512-8ViPxn9X/K+is+4i8x9H1MB0RmPYFqkrrX2v9EtVZxGbYXLaKmkiL+kfzISUGNOQdzu4E93KjluT9S7cxegs0g==",
"dependencies": {
"@types/wicg-file-system-access": "^2020.9.5"
}
@@ -9828,8 +9827,7 @@
"node_modules/uiconfig.js": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.9.tgz",
"integrity": "sha512-jB2LIUTBA7gHegrFr62J7xOEkyXmb+h0/5FczD9qzcBjCbb2R8UsdaxOyVoa+9ecfc2NAaeboTzJK8n8ji1cbw==",
"dev": true
"integrity": "sha512-jB2LIUTBA7gHegrFr62J7xOEkyXmb+h0/5FczD9qzcBjCbb2R8UsdaxOyVoa+9ecfc2NAaeboTzJK8n8ji1cbw=="
},
"node_modules/unbox-primitive": {
"version": "1.0.2",
@@ -16864,8 +16862,7 @@
"stats.js": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
"dev": true
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw=="
},
"statuses": {
"version": "1.5.0",
@@ -17329,9 +17326,9 @@
"dev": true
},
"ts-browser-helpers": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.8.0.tgz",
"integrity": "sha512-r7k6udt+tS/FZLwTUbzSgHdHQ56R/MD1oS2rFcEk8O4jKUTzuDVqlUoLX1EjJQYEBGMBI8TEyySTkuomxewZEw==",
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.9.0.tgz",
"integrity": "sha512-8ViPxn9X/K+is+4i8x9H1MB0RmPYFqkrrX2v9EtVZxGbYXLaKmkiL+kfzISUGNOQdzu4E93KjluT9S7cxegs0g==",
"requires": {
"@types/wicg-file-system-access": "^2020.9.5"
}
@@ -17503,8 +17500,7 @@
"uiconfig.js": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.9.tgz",
"integrity": "sha512-jB2LIUTBA7gHegrFr62J7xOEkyXmb+h0/5FczD9qzcBjCbb2R8UsdaxOyVoa+9ecfc2NAaeboTzJK8n8ji1cbw==",
"dev": true
"integrity": "sha512-jB2LIUTBA7gHegrFr62J7xOEkyXmb+h0/5FczD9qzcBjCbb2R8UsdaxOyVoa+9ecfc2NAaeboTzJK8n8ji1cbw=="
},
"unbox-primitive": {
"version": "1.0.2",

+ 2
- 0
src/plugins/index.ts Ver fichero

@@ -21,6 +21,8 @@ export {SceneUiConfigPlugin} from './ui/SceneUiConfigPlugin'
export {DropzonePlugin, type DropzonePluginOptions} from './interaction/DropzonePlugin'
export {FullScreenPlugin} from './interaction/FullScreenPlugin'
export {PickingPlugin} from './interaction/PickingPlugin'
export {TransformControlsPlugin} from './interaction/TransformControlsPlugin'
export {EditorViewWidgetPlugin} from './interaction/EditorViewWidgetPlugin'

// import
export {Rhino3dmLoadPlugin} from './import/Rhino3dmLoadPlugin'

+ 78
- 0
src/plugins/interaction/EditorViewWidgetPlugin.ts Ver fichero

@@ -0,0 +1,78 @@
import {AViewerPluginSync, type IViewerEvent, ThreeViewer} from '../../viewer'
import {DomPlacement, GizmoOrientation, ViewHelper2} from '../../three'
import {uiFolderContainer, uiToggle} from 'uiconfig.js'
import {onChange} from 'ts-browser-helpers'

@uiFolderContainer('Editor View Widget')
export class EditorViewWidgetPlugin extends AViewerPluginSync<''> {
public static readonly PluginType = 'EditorViewWidgetPlugin'

@uiToggle()
@onChange(EditorViewWidgetPlugin.prototype._enableChange)
enabled = true

protected _enableChange() {
if (!this._viewer || !this.widget) return
this.widget.domContainer.style.display = this.enabled ? 'block' : 'none'
}

constructor(public readonly placement: DomPlacement = 'top-left', public readonly size = 128) {
super()
}

widget?: ViewHelper2

onAdded(v: ThreeViewer) {
super.onAdded(v)
this.widget = new ViewHelper2(v.scene.mainCamera as any, v.canvas, this.placement, this.size)
this.widget.target = v.scene.mainCamera.target
this.widget.addEventListener('animating-changed', (e)=>{
const val = e.detail.value
v.scene.mainCamera.setInteractions(!val, EditorViewWidgetPlugin.PluginType)
})
this.widget.addEventListener('update', ()=>this._needsRender = true) // when mouse hover and leave.
v.scene.addEventListener('mainCameraChange', this._mainCameraChange)
v.scene.addEventListener('mainCameraUpdate', this._mainCameraUpdate)
}

onRemove(viewer: ThreeViewer) {
this.widget?.dispose()
this.widget = undefined
viewer.scene.removeEventListener('mainCameraChange', this._mainCameraChange)
viewer.scene.removeEventListener('mainCameraUpdate', this._mainCameraUpdate)
super.onRemove(viewer)
}

protected _mainCameraChange() {
if (!this._viewer || !this.widget) return
this.widget.camera = this._viewer.scene.mainCamera as any
}
protected _mainCameraUpdate() {
if (!this._viewer || !this.widget) return
this.widget.target = this._viewer.scene.mainCamera.target
}

// this is required separately so that when we hover on the gizmo we dont need to set dirty for the whole viewer
protected _needsRender = false
protected _viewerListeners = {
postRender: (_: IViewerEvent)=>{
if (!this._viewer || !this.widget || !this.enabled) return
this._needsRender = true
},
postFrame: (_: IViewerEvent)=>{
if (!this._viewer || !this.widget || !this.enabled || !this._needsRender) return
this.widget.update()
this.widget.render()
if (this.widget.animating) this._viewer.scene.mainCamera.setDirty()
this._needsRender = false
},
}

setOrientation(orientation: GizmoOrientation) {
if (!this.widget) return
this.widget.setOrientation(orientation)
}

}



+ 1
- 0
src/plugins/interaction/PickingPlugin.ts Ver fichero

@@ -51,6 +51,7 @@ export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'ho
this._widget?.attach(this._picker.selectedObject)
else
this._widget?.detach()
this.uiConfig?.uiRefresh?.(true)
}

public setDirty() {

+ 97
- 0
src/plugins/interaction/TransformControlsPlugin.ts Ver fichero

@@ -0,0 +1,97 @@
import {uiConfig, uiPanelContainer, uiToggle} from 'uiconfig.js'
import {AViewerPluginSync, ThreeViewer} from '../../viewer'
import {OrbitControls3, TransformControls2} from '../../three'
import {PickingPlugin} from './PickingPlugin'
import {onChange} from 'ts-browser-helpers'

@uiPanelContainer('Transform Controls')
export class TransformControlsPlugin extends AViewerPluginSync<''> {
public static readonly PluginType = 'TransformControlsPlugin'

@uiToggle()
@onChange(TransformControlsPlugin.prototype._enableChange)
enabled = true

private _pickingWidgetDisabled = false
private _enableChange() {
if (!this._viewer) return
const picking = this._viewer.getPlugin(PickingPlugin)!
if (this.enabled && picking.widgetEnabled) {
picking.widgetEnabled = false
this._pickingWidgetDisabled = true
} else if (!this.enabled && this._pickingWidgetDisabled) {
picking.widgetEnabled = true
this._pickingWidgetDisabled = false
}
if (this.transformControls) {
if (this.enabled && picking.getSelectedObject()) this.transformControls.attach(picking.getSelectedObject()!)
else this.transformControls.detach()
}
}

toJSON: any = undefined

dependencies = [PickingPlugin]

@uiConfig()
transformControls: TransformControls2 | undefined

protected _isInteracting = false
protected _viewerListeners = {
postFrame: ()=>{
if (!this.transformControls || !this._viewer) return
// this._viewer.scene.mainCamera.setInteractions(!this._isInteracting, TransformControlsPlugin.PluginType)
},
}

onAdded(viewer: ThreeViewer) {
super.onAdded(viewer)
this._enableChange()
this.transformControls = new TransformControls2(viewer.scene.mainCamera, viewer.canvas)
this._mainCameraChange = this._mainCameraChange.bind(this)
viewer.scene.addEventListener('mainCameraChange', this._mainCameraChange)
this.transformControls.addEventListener('dragging-changed', (event) => {
if (!this?._viewer) return
const controls = this._viewer.scene.mainCamera.controls
if (typeof (controls as any)?.stopDamping === 'function' && controls?.enabled) (controls as OrbitControls3).stopDamping()
this._viewer.scene.mainCamera.setInteractions(!event.value, TransformControlsPlugin.PluginType)
// this._viewer.scene.mainCamera.autoNearFar = !event.value // todo: maintain state
})
this.transformControls.addEventListener('axis-changed', (event) => {
if (!this?._viewer) return
this._isInteracting = !!event.value
const controls = this._viewer.scene.mainCamera.controls
if (typeof (controls as any)?.stopDamping === 'function' && controls?.enabled) (controls as OrbitControls3).stopDamping()
this._viewer.setDirty() // rerender for color change
})
viewer.scene.addObject(this.transformControls, {addToRoot: true})
const picking = viewer.getPlugin(PickingPlugin)!
picking.addEventListener('selectedObjectChanged', (event) => {
if (!this.transformControls) return
if (!this.enabled) {
if (this.transformControls.object) this.transformControls.detach()
return
}
event.object ? this.transformControls.attach(event.object) : this.transformControls.detach()
})

}

onRemove(viewer: ThreeViewer) {
viewer.scene.removeEventListener('mainCameraChange', this._mainCameraChange)
if (this.transformControls) {
this.transformControls.detach()
viewer.scene.remove(this.transformControls)
this.transformControls.dispose()
}
this.transformControls = undefined
super.onRemove(viewer)
}

private _mainCameraChange = () => {
if (!this.transformControls || !this._viewer) return
this.transformControls.camera = this._viewer.scene.mainCamera
}


}

+ 79
- 0
src/three/utils/TransformControls.d.ts Ver fichero

@@ -0,0 +1,79 @@
/* eslint-disable */
import {Camera, Mesh, MOUSE, Object3D, Quaternion, Raycaster, Vector3} from 'three';

export class TransformControls extends Object3D {
constructor(object: Camera, domElement?: HTMLElement);

domElement: HTMLElement;

// API

camera: Camera;
object: Object3D | undefined;
enabled: boolean;
axis: 'X' | 'Y' | 'Z' | 'E' | 'XY' | 'YZ' | 'XZ' | 'XYZ' | 'XYZE' | null;
mode: 'translate' | 'rotate' | 'scale';
translationSnap: number | null;
rotationSnap: number | null;
space: 'world' | 'local';
size: number;
dragging: boolean;
showX: boolean;
showY: boolean;
showZ: boolean;
readonly isTransformControls: true;
mouseButtons: { LEFT: MOUSE; MIDDLE: MOUSE; RIGHT: MOUSE };

attach(object: Object3D): this;
detach(): this;
getMode(): 'translate' | 'rotate' | 'scale';
getRaycaster(): Raycaster;
setMode(mode: 'translate' | 'rotate' | 'scale'): void;
setTranslationSnap(translationSnap: number | null): void;
setRotationSnap(rotationSnap: number | null): void;
setScaleSnap(scaleSnap: number | null): void;
setSize(size: number): void;
setSpace(space: 'world' | 'local'): void;
reset(): void;
dispose(): void;
}

export class TransformControlsGizmo extends Object3D {
type: 'TransformControlsGizmo';
isTransformControlsGizmo: true;

gizmo: {
translate: Object3D;
rotate: Object3D;
scale: Object3D;
};
helper: {
translate: Object3D;
rotate: Object3D;
scale: Object3D;
};
picker: {
translate: Object3D;
rotate: Object3D;
scale: Object3D;
};

constructor();
}

export class TransformControlsPlane extends Mesh {
type: 'TransformControlsPlane';
isTransformControlsPlane: true;

constructor();

mode: 'translate' | 'scale' | 'rotate';

axis: 'X' | 'Y' | 'Z' | 'XY' | 'YZ' | 'XZ' | 'XYZ' | 'E';

space: 'local' | 'world';

eye: Vector3;
worldPosition: Vector3;
worldQuaternion: Quaternion;
}

+ 1609
- 0
src/three/utils/TransformControls.js
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 185
- 0
src/three/utils/TransformControls2.ts Ver fichero

@@ -0,0 +1,185 @@
import {TransformControls} from './TransformControls.js'
import {MathUtils} from 'three'
import {ICamera, IObject3D, iObjectCommons, ISceneEvent, IWidget} from '../../core'
import {uiDropdown, uiNumber, uiPanelContainer, uiToggle} from 'uiconfig.js'

@uiPanelContainer('Transform Controls')
export class TransformControls2 extends TransformControls implements IWidget, IObject3D {
isWidget = true as const
assetType = 'widget' as const
setDirty = iObjectCommons.setDirty
refreshUi = iObjectCommons.refreshUi.bind(this)

object: IObject3D | undefined
private _keyDownListener(event: KeyboardEvent) {
if (!this.enabled) return
if (!this.object) return

switch (event.code) {

case 'KeyQ':
this.space = this.space === 'local' ? 'world' : 'local'
break

case 'ShiftLeft':
this.translationSnap = 0.5
this.rotationSnap = MathUtils.degToRad(15)
this.scaleSnap = 0.25
break

case 'KeyW':
this.mode = 'translate'
break

case 'KeyE':
this.mode = 'rotate'
break

case 'KeyR':
this.mode = 'scale'
break

case 'Equal':
case 'NumpadAdd':
case 'Plus':
this.size = this.size + 0.1
break

case 'Minus':
case 'NumpadSubtract':
case 'Underscore':
this.size = Math.max(this.size - 0.1, 0.1)
break

case 'KeyX':
this.showX = !this.showX
break

case 'KeyY':
this.showY = !this.showY
break

case 'KeyZ':
this.showZ = !this.showZ
break

case 'Space':
this.enabled = !this.enabled
break

default:
return
}

this.setDirty({refreshScene: true, frameFade: true})

}

private _keyUpListener(event: KeyboardEvent) {
if (!this.enabled) return

// reset events
switch (event.code) {
case 'ShiftLeft':
this.translationSnap = null
this.rotationSnap = null
this.scaleSnap = null
break

default:
break
}

if (!this.object) return

// non-reset events
switch (event.code) {
default:
break
}

}

constructor(camera: ICamera, canvas: HTMLCanvasElement) {
super(camera, canvas)

this.visible = false
this.userData.bboxVisible = false

this.size = 2

this.addEventListener('objectChange', () => {
this?.object?.setDirty({fadeFrame: false})
// todo: do this.setDirty?
})

this._keyUpListener = this._keyUpListener.bind(this)
this._keyDownListener = this._keyDownListener.bind(this)
window.addEventListener('keydown', this._keyDownListener)
window.addEventListener('keyup', this._keyUpListener)
}

dispose() {
window.removeEventListener('keydown', this._keyDownListener)
window.removeEventListener('keyup', this._keyUpListener)
super.dispose()
}


// region properties

enabled: boolean

// axis: 'X' | 'Y' | 'Z' | 'E' | 'XY' | 'YZ' | 'XZ' | 'XYZ' | 'XYZE' | null

@uiDropdown('Mode', ['translate', 'rotate', 'scale'].map(label=>({label})))
mode: 'translate' | 'rotate' | 'scale'

translationSnap: number | null
rotationSnap: number | null
scaleSnap: number | null

@uiDropdown('Space', ['world', 'local'].map(label=>({label})))
space: 'world' | 'local'
@uiNumber('Size')
size: number
@uiToggle('Show X')
showX: boolean
@uiToggle('Show Y')
showY: boolean
@uiToggle('Show Z')
showZ: boolean

// dragging: boolean

// endregion



/**
* Get the threejs object
* @deprecated
*/
get modelObject(): this {
return this as any
}

// todo: https://helpx.adobe.com/after-effects/using/3d-transform-gizmo.html

// region inherited type fixes

traverse: (callback: (object: IObject3D) => void) => void
traverseVisible: (callback: (object: IObject3D) => void) => void
traverseAncestors: (callback: (object: IObject3D) => void) => void
getObjectById: <T extends IObject3D = IObject3D>(id: number) => T | undefined
getObjectByName: <T extends IObject3D = IObject3D>(name: string) => T | undefined
getObjectByProperty: <T extends IObject3D = IObject3D>(name: string, value: string) => T | undefined
copy: (source: this, recursive?: boolean, ...args: any[]) => this
clone: (recursive?: boolean) => this
remove: (...object: IObject3D[]) => this
dispatchEvent: (event: ISceneEvent) => void
parent: IObject3D | null
children: IObject3D[]

// endregion
}

+ 658
- 0
src/three/utils/ViewHelper2.ts Ver fichero

@@ -0,0 +1,658 @@
import {
BackSide,
Camera,
CanvasTexture,
Clock,
Color,
Euler,
LinearFilter,
Material,
Mesh,
MeshBasicMaterial,
Object3D,
OrthographicCamera,
PerspectiveCamera,
Quaternion,
Raycaster,
RepeatWrapping,
SphereGeometry,
Sprite,
SpriteMaterial,
SRGBColorSpace,
Vector2,
Vector3,
Vector4,
WebGLRenderer,
} from 'three'
import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js'
import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js'
import {LineMaterial} from 'three/examples/jsm/lines/LineMaterial.js'
import {onChangeDispatchEvent} from 'ts-browser-helpers'


const [POS_X, POS_Y, POS_Z, NEG_X, NEG_Y, NEG_Z] = Array(6)
.fill(0)
.map((_, i) => i)

const axesColors = [
new Color(0xff3653),
new Color(0x8adb00),
new Color(0x2c8fff),
]

const clock = new Clock()
const targetPosition = new Vector3()
const targetQuaternion = new Quaternion()
// const euler = new Euler()
const q1 = new Quaternion()
const q2 = new Quaternion()
const point = new Vector3()
// const dim = 128
const turnRate = 2 * Math.PI // turn rate in angles per second
const raycaster = new Raycaster()
const mouse = new Vector2()
// const mouseStart = new Vector2()
// const mouseAngle = new Vector2()
const dummy = new Object3D()
let radius = 0

export type GizmoOrientation = '+x' | '-x' | '+y' | '-y' | '+z' | '-z'

export type DomPlacement =
| 'top-left'
| 'top-right'
| 'top-center'
| 'center-right'
| 'center-left'
| 'center-center'
| 'bottom-left'
| 'bottom-right'
| 'bottom-center'

/**
* Extended ViewHelper implemented from the following source:
* https://github.com/Fennec-hub/viewHelper
* MIT License
* Copyright (c) 2022 Fennec-hub
*/
export class ViewHelper2 extends Object3D {
camera: OrthographicCamera | PerspectiveCamera
orthoCamera = new OrthographicCamera(-1.8, 1.8, 1.8, -1.8, 0, 4)
isViewHelper = true
@onChangeDispatchEvent()
animating = false
target = new Vector3()
backgroundSphere: Mesh
axesLines: LineSegments2
spritePoints: Sprite[]
domElement: HTMLElement
domContainer: HTMLElement
domRect: DOMRect
// dragging = false
renderer: WebGLRenderer
// controls?: OrbitControls | TrackballControls
// controlsChangeEvent: {listener: () => void}
viewport: Vector4 = new Vector4()
offsetHeight = 0

constructor(
camera: PerspectiveCamera | OrthographicCamera,
canvas: HTMLCanvasElement,
placement: DomPlacement = 'bottom-right',
size = 128,
pixelRatio = 2,
) {
super()

this.renderer = new WebGLRenderer({
canvas: document.createElement('canvas'),
alpha: true,
antialias: true,
preserveDrawingBuffer: false,
})
this.renderer.setPixelRatio(pixelRatio)
this.camera = camera
this.domElement = canvas

this.orthoCamera.position.set(0, 0, 2)

this.backgroundSphere = getBackgroundSphere()
this.axesLines = getAxesLines()
this.spritePoints = getAxesSpritePoints()

this.add(this.backgroundSphere, this.axesLines, ...this.spritePoints)

this.domContainer = getDomContainer(placement, size)
this.domContainer.appendChild(this.renderer.domElement)
this.renderer.domElement.style.width = '100%'
this.renderer.domElement.style.height = '100%'

// This may cause confusion if the parent isn't the body and doesn't have a `position:relative`
this.domElement.parentElement!.appendChild(this.domContainer)

this.domRect = this.domContainer.getBoundingClientRect()
this.startListening()

// this.controlsChangeEvent = {listener: () => this.updateOrientation()}

this.update()
this.updateOrientation()
}

startListening() {
// this.domContainer.onpointerdown = (e) => this.onPointerDown(e)
this.domContainer.onpointermove = (e) => this.onPointerMove(e)
this.domContainer.onpointerleave = (e) => this.onPointerLeave(e)
this.domContainer.onclick = (e) => this.handleClick(e)
}

// onPointerDown(e: PointerEvent) {
// const drag = (e1: PointerEvent) => {
// if (!this.dragging && isClick(e1, mouseStart)) return
// if (!this.dragging) {
// resetSprites(this.spritePoints)
// this.dragging = true
// }
//
// mouseAngle
// .set(e1.clientX, e1.clientY)
// .sub(mouseStart)
// .multiplyScalar(1 / this.domRect.width * Math.PI)
//
// this.rotation.x = MathUtils.clamp(
// rotationStart.x + mouseAngle.y,
// Math.PI / -2 + 0.001,
// Math.PI / 2 - 0.001
// )
// this.rotation.y = rotationStart.y + mouseAngle.x
// this.updateMatrixWorld()
//
// q1.copy(this.quaternion).invert()
//
// this.camera.position
// .set(0, 0, 1)
// .applyQuaternion(q1)
// .multiplyScalar(radius)
// .add(this.target)
//
// this.camera.rotation.setFromQuaternion(q1)
//
// this.updateOrientation(false)
// }
// const endDrag = () => {
// document.removeEventListener('pointermove', drag, false)
// document.removeEventListener('pointerup', endDrag, false)
//
// if (!this.dragging) {
// // this.handleClick(e)
// return
// }
//
// this.dragging = false
// }
//
// if (this.animating === true) return
// e.preventDefault()
//
// mouseStart.set(e.clientX, e.clientY)
//
// const rotationStart = euler.copy(this.rotation)
//
// setRadius(this.camera, this.target)
//
// document.addEventListener('pointermove', drag, false)
// document.addEventListener('pointerup', endDrag, false)
// }

onPointerMove(e: PointerEvent) {
// if (this.dragging) return;
(this.backgroundSphere.material as Material).opacity = 0.4
this.handleHover(e)
this.dispatchEvent({type: 'update', event: e})
}

onPointerLeave(e: PointerEvent) {
// if (this.dragging) return;
(this.backgroundSphere.material as Material).opacity = 0.2
resetSprites(this.spritePoints)
this.domContainer.style.cursor = ''
this.dispatchEvent({type: 'update', event: e})
}

handleClick(e: PointerEvent|MouseEvent) {
const object = getIntersectionObject(
e,
this.domRect,
this.orthoCamera,
this.spritePoints
)

if (!object) return

this.setOrientation(object.userData.type)
}

handleHover(e: PointerEvent) {
const object = getIntersectionObject(
e,
this.domRect,
this.orthoCamera,
this.spritePoints
)

resetSprites(this.spritePoints)

if (!object) {
this.domContainer.style.cursor = ''
} else {
object.material.map!.offset.x = 0.5
object.scale.multiplyScalar(1.2)
this.domContainer.style.cursor = 'pointer'
}
}

// setControls(controls?: OrbitControls | TrackballControls) {
// if (this.controls) {
// (this.controls as any).removeEventListener(
// 'change',
// this.controlsChangeEvent.listener
// )
// this.target = new Vector3()
// }
//
// if (!controls) return
//
// this.controls = controls;
// (controls as any).addEventListener('change', this.controlsChangeEvent.listener)
// this.target = controls.target
// }

render() {
const delta = clock.getDelta()
if (this.animating) this.animate(Math.min(delta, 1 / 30.0))

// const x = this.domRect.left
// const y = this.offsetHeight - this.domRect.bottom

const autoClear = this.renderer.autoClear
this.renderer.autoClear = false
// this.renderer.setViewport(x, y, dim, dim)
this.renderer.render(this, this.orthoCamera)
// this.renderer.setViewport(this.viewport)
this.renderer.autoClear = autoClear
}

updateOrientation(fromCamera = true) {
if (fromCamera) {
this.quaternion.copy(this.camera.quaternion).invert()
this.updateMatrixWorld()
}

updateSpritesOpacity(this.spritePoints, this.camera)
}

update() {
this.domRect = this.domContainer.getBoundingClientRect()
this.offsetHeight = this.domElement.offsetHeight
setRadius(this.camera, this.target)
this.renderer.getViewport(this.viewport)

this.updateOrientation()
}

animate(delta: number) {
const step = delta * turnRate

// animate position by doing a slerp and then scaling the position on the unit sphere

q1.rotateTowards(q2, step)
this.camera.position
.set(0, 0, 1)
.applyQuaternion(q1)
.multiplyScalar(radius)
.add(this.target)

// animate orientation

this.camera.quaternion.rotateTowards(targetQuaternion, step)

this.updateOrientation()

if (q1.angleTo(q2) === 0) {
this.animating = false
}
}

setOrientation(orientation: GizmoOrientation) {
prepareAnimationData(this.camera, this.target, orientation)
this.animating = true
this.dispatchEvent({type: 'update'})
}

dispose() {
this.axesLines.geometry.dispose();
(this.axesLines.material as Material).dispose()

this.backgroundSphere.geometry.dispose();
(this.backgroundSphere.material as Material).dispose()

this.spritePoints.forEach((sprite) => {
sprite.material.map!.dispose()
sprite.material.dispose()
})

this.domContainer.remove()

// ;(this.controls as any)?.removeEventListener(
// 'change',
// this.controlsChangeEvent.listener
// )
}
}

function getDomContainer(placement: DomPlacement, size: number) {
const div = document.createElement('div')
const style = div.style

style.height = `${size}px`
style.width = `${size}px`
style.borderRadius = '100%'
style.position = 'absolute'

const [y, x] = placement.split('-')

style.transform = ''
style.left = x === 'left' ? '0' : x === 'center' ? '50%' : ''
style.right = x === 'right' ? '0' : ''
style.transform += x === 'center' ? 'translateX(-50%)' : ''
style.top = y === 'top' ? '0' : y === 'bottom' ? '' : '50%'
style.bottom = y === 'bottom' ? '0' : ''
style.transform += y === 'center' ? 'translateY(-50%)' : ''

return div
}

function getAxesLines() {
const distance = 0.9
const position = Array(3)
.fill(0)
.map((_, i) => [
!i ? distance : 0,
i === 1 ? distance : 0,
i === 2 ? distance : 0,
0,
0,
0,
])
.flat()
const color = Array(6)
.fill(0)
.map((_, i) =>
i < 2
? axesColors[0].toArray()
: i < 4
? axesColors[1].toArray()
: axesColors[2].toArray()
)
.flat()

// const geometry = new BufferGeometry()
// geometry.setAttribute(
// 'position',
// new BufferAttribute(new Float32Array(position), 3)
// )
// geometry.setAttribute(
// 'color',
// new BufferAttribute(new Float32Array(color), 3)
// )
const geometry = new LineSegmentsGeometry()
geometry.setPositions(position)
geometry.setColors(color)

return new LineSegments2(
geometry,
new LineMaterial({
linewidth: 0.02,
vertexColors: true,
})
)
}

function getBackgroundSphere() {
const geometry = new SphereGeometry(1.6)
const sphere = new Mesh(
geometry,
new MeshBasicMaterial({
color: 0xffffff,
side: BackSide,
transparent: true,
opacity: 0.2,
depthTest: false,
})
)

return sphere
}

function getAxesSpritePoints() {
const axes = ['x', 'y', 'z'] as const
return Array(6)
.fill(0)
.map((_, i) => {
const isPositive = i < 3
const sign = isPositive ? '+' : '-'
const axis = axes[i % 3]
const color = axesColors[i % 3]

const sprite = new Sprite(
getSpriteMaterial(color, isPositive ? axis : null)
)
sprite.userData.type = `${sign}${axis}`
sprite.scale.setScalar(isPositive ? 0.6 : 0.4)
sprite.position[axis] = isPositive ? 1.2 : -1.2
sprite.renderOrder = 1

return sprite
})
}

function getSpriteMaterial(color: Color, text: 'x' | 'y' | 'z' | null = null) {
const canvas = document.createElement('canvas')
const padding = 0
const scale = 1
const padding2 = 0 // has a bug

canvas.width = 128 * scale + 4 * padding + padding2 * 2
canvas.height = 64 * scale + 2 * padding + padding2 * 2

const context = canvas.getContext('2d', {alpha: true})!

context.beginPath()
context.arc(32 * scale + padding, 32 * scale + padding, 32 * scale - padding, 0, 2 * Math.PI)
context.closePath()
context.fillStyle = color.getStyle()
context.fill()

// for black border due to interpolation, transparent slightly bigger circle
context.beginPath()
context.arc(32 * scale + padding, 32 * scale + padding, 35 * scale - padding, 0, 2 * Math.PI)
context.closePath()
context.fillStyle = '#' + color.getHexString() + '01'
context.fill()

context.beginPath()
context.arc(96 * scale + padding * 3 + padding2, 32 * scale + padding + padding2, 32 * scale - padding - padding2, 0, 2 * Math.PI)
context.closePath()
context.fillStyle = '#FFF'
context.fill()

// for black border due to interpolation, transparent slightly bigger circle
context.beginPath()
context.arc(96 * scale + padding * 3 + padding2, 32 * scale + padding + padding2, 35 + scale - padding - padding2, 0, 2 * Math.PI)
context.closePath()
context.fillStyle = '#FFFFFF01'
context.fill()

if (text !== null) {
context.font = 'bold calc(44px * ' + scale + ') Arial'
context.textAlign = 'center'
context.fillStyle = '#111'
context.fillText(text.toUpperCase(), 32 * scale + padding, 48 * scale + padding)
context.fillText(text.toUpperCase(), 96 * scale + padding * 3 + padding2, 48 * scale + padding + padding2)
}

// canvas.style.background = '#ff0000'
const texture = new CanvasTexture(canvas)
texture.wrapS = texture.wrapT = RepeatWrapping
texture.repeat.x = 0.5
texture.colorSpace = SRGBColorSpace
texture.minFilter = LinearFilter
texture.magFilter = LinearFilter
texture.generateMipmaps = false
texture.needsUpdate = true


return new SpriteMaterial({
map: texture,
toneMapped: false,
transparent: true,
})
}

function prepareAnimationData(
camera: OrthographicCamera | PerspectiveCamera,
focusPoint: Vector3,
axis: GizmoOrientation
) {
switch (axis) {
case '+x':
targetPosition.set(1, 0, 0)
targetQuaternion.setFromEuler(new Euler(0, Math.PI * 0.5, 0))
break

case '+y':
targetPosition.set(0, 1, 0)
targetQuaternion.setFromEuler(new Euler(-Math.PI * 0.5, 0, 0))
break

case '+z':
targetPosition.set(0, 0, 1)
targetQuaternion.setFromEuler(new Euler())
break

case '-x':
targetPosition.set(-1, 0, 0)
targetQuaternion.setFromEuler(new Euler(0, -Math.PI * 0.5, 0))
break

case '-y':
targetPosition.set(0, -1, 0)
targetQuaternion.setFromEuler(new Euler(Math.PI * 0.5, 0, 0))
break

case '-z':
targetPosition.set(0, 0, -1)
targetQuaternion.setFromEuler(new Euler(0, Math.PI, 0))
break

default:
console.error('ViewHelper: Invalid axis.')
}

setRadius(camera, focusPoint)
prepareQuaternions(camera, focusPoint)
}

function setRadius(camera: Camera, focusPoint: Vector3) {
radius = camera.position.distanceTo(focusPoint)
}

function prepareQuaternions(camera: Camera, focusPoint: Vector3) {
targetPosition.multiplyScalar(radius).add(focusPoint)

dummy.position.copy(focusPoint)

dummy.lookAt(camera.position)
q1.copy(dummy.quaternion)

dummy.lookAt(targetPosition)
q2.copy(dummy.quaternion)
}

function updatePointer(
e: PointerEvent|MouseEvent,
domRect: DOMRect,
orthoCamera: OrthographicCamera
) {
mouse.x = (e.clientX - domRect.left) / domRect.width * 2 - 1
mouse.y = -((e.clientY - domRect.top) / domRect.height) * 2 + 1

raycaster.setFromCamera(mouse, orthoCamera)
}

// function isClick(
// e: PointerEvent,
// startCoords: Vector2,
// threshold = 2
// ) {
// return (
// Math.abs(e.clientX - startCoords.x) < threshold &&
// Math.abs(e.clientY - startCoords.y) < threshold
// )
// }

function getIntersectionObject(
event: PointerEvent|MouseEvent,
domRect: DOMRect,
orthoCamera: OrthographicCamera,
intersectionObjects: Sprite[]
) {
updatePointer(event, domRect, orthoCamera)

const intersects = raycaster.intersectObjects(intersectionObjects)

if (!intersects.length) return null

const intersection = intersects[0]
return intersection.object as Sprite
}

function resetSprites(sprites: Sprite[]) {
let i = sprites.length

while (i--) {
const scale = i < 3 ? 0.6 : 0.4
sprites[i].scale.set(scale, scale, scale)
sprites[i].material.map!.offset.x = 1
}
// sprites.forEach((sprite) => (sprite.material.map!.offset.x = 1));
}

function updateSpritesOpacity(sprites: Sprite[], camera: Camera) {
point.set(0, 0, 1)
point.applyQuaternion(camera.quaternion)

if (point.x >= 0) {
sprites[POS_X].material.opacity = 1
sprites[NEG_X].material.opacity = 0.5
} else {
sprites[POS_X].material.opacity = 0.5
sprites[NEG_X].material.opacity = 1
}

if (point.y >= 0) {
sprites[POS_Y].material.opacity = 1
sprites[NEG_Y].material.opacity = 0.5
} else {
sprites[POS_Y].material.opacity = 0.5
sprites[NEG_Y].material.opacity = 1
}

if (point.z >= 0) {
sprites[POS_Z].material.opacity = 1
sprites[NEG_Z].material.opacity = 0.5
} else {
sprites[POS_Z].material.opacity = 0.5
sprites[NEG_Z].material.opacity = 1
}
}

+ 3
- 0
src/three/utils/index.ts Ver fichero

@@ -9,5 +9,8 @@ export {threeConstMappings} from './const-mappings'
export {ObjectPicker} from './ObjectPicker'
export {SelectionWidget, BoxSelectionWidget} from './SelectionWidget'
export {autoGPUInstanceMeshes} from './gpu-instancing'
export {ViewHelper2, type GizmoOrientation, type DomPlacement} from './ViewHelper2'
export {TransformControls2} from './TransformControls2'
export {TransformControls, TransformControlsGizmo, TransformControlsPlane} from './TransformControls'

// export {} from './constants'

Cargando…
Cancelar
Guardar