threepipe
Version:
A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.
153 lines (134 loc) • 5.42 kB
text/typescript
import {AViewerPluginEventMap, AViewerPluginSync, ThreeViewer} from '../../viewer'
import {createDiv, createStyles, getOrCall, onChange, ValOrFunc} from 'ts-browser-helpers'
import styles from './GeometryUVPreviewPlugin.css?inline'
import {CustomContextMenu} from '../../utils'
import {uiFolderContainer, uiToggle} from 'uiconfig.js'
import {IGeometry} from '../../core'
import {UVsDebug} from 'three/examples/jsm/utils/UVsDebug.js'
export interface TargetBlock {
target: ValOrFunc<IGeometry|undefined>
name: string
visible: boolean
div: HTMLDivElement
uvCanvas?: HTMLCanvasElement
}
export class GeometryUVPreviewPlugin<TE extends AViewerPluginEventMap = AViewerPluginEventMap> extends AViewerPluginSync<TE> {
static readonly PluginType = 'GeometryUVPreviewPlugin'
enabled = true
toJSON: any = null
mainDiv: HTMLDivElement = createDiv({id: 'GeometryUVPreviewPluginContainer', addToBody: false})
stylesheet?: HTMLStyleElement
constructor(enabled = true) {
super()
this.enabled = enabled
}
targetBlocks: TargetBlock[] = []
onAdded(viewer: ThreeViewer): void {
super.onAdded(viewer)
viewer.addEventListener('postRender', this._postRender)
this.stylesheet = createStyles(styles, viewer.container)
this.refreshUi()
}
onRemove(viewer: ThreeViewer): void {
viewer.removeEventListener('postRender', this._postRender)
this.stylesheet?.remove()
this.stylesheet = undefined
this.refreshUi()
super.onRemove(viewer)
}
private _postRender = () => {
if (!this._viewer) return
for (const target of this.targetBlocks) {
if (!target.visible) continue
const geo = getOrCall(target.target)
if (!geo?.attributes?.uv) {
// todo draw white or pink
continue
}
if (!target.uvCanvas) {
target.uvCanvas = UVsDebug(geo, 1024)
target.uvCanvas.style.width = '100%'
target.uvCanvas.style.height = '100%'
}
if (target.uvCanvas && target.uvCanvas.parentElement !== target.div) target.div.appendChild(target.uvCanvas)
}
}
addGeometry(target: ValOrFunc<IGeometry|undefined>, name: string, visible = true): this {
if (!target) return this
const div = document.createElement('div')
const targetDef: TargetBlock = {target, name, div, visible}
div.classList.add('GeometryUVPreviewPluginTarget')
if (!targetDef.visible) div.classList.add('GeometryUVPreviewPluginCollapsed')
const header = document.createElement('div')
header.classList.add('GeometryUVPreviewPluginTargetHeader')
header.innerText = name
header.onclick = () => {
targetDef.visible = !targetDef.visible
if (!targetDef.visible) div.classList.add('GeometryUVPreviewPluginCollapsed')
else div.classList.remove('GeometryUVPreviewPluginCollapsed')
this._viewer?.setDirty()
}
header.oncontextmenu = (e) => {
e.preventDefault()
e.stopPropagation()
CustomContextMenu.Create({
'Download': () => this.downloadGeometryUV(targetDef),
'Remove': () => this.removeGeometry(target),
}, e.clientX, e.clientY)
}
div.appendChild(header)
this.mainDiv.appendChild(div)
this.targetBlocks.push(targetDef)
this.refreshUi()
return this
}
removeGeometry(target: ValOrFunc<IGeometry|undefined>): this {
const index = this.targetBlocks.findIndex(t => t.target === target)
if (index >= 0) {
const t = this.targetBlocks[index]
this.targetBlocks.splice(index, 1)
t.div.remove()
}
this.refreshUi()
return this
}
downloadGeometryUV(targetDef: TargetBlock): this {
if (!this._viewer) return this
if (!targetDef.uvCanvas) return this
const canvas = targetDef.uvCanvas
const url = canvas.toDataURL('image/png')
const link = document.createElement('a')
document.body.appendChild(link)
link.style.display = 'none'
link.href = url
link.download = 'renderTarget.' + 'png'
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
return this
}
refreshUi(): void {
if (!this.mainDiv) return
if (!this._viewer) {
if (this.mainDiv.parentElement) this.mainDiv.remove()
this.mainDiv.style.display = 'none'
this.mainDiv.style.zIndex = '1000'
return
}
if (!this.mainDiv.parentElement) this._viewer.container?.appendChild(this.mainDiv)
this.mainDiv.style.display = !this.isDisabled() ? 'flex' : 'none'
this.mainDiv.style.zIndex = parseInt(this._viewer.canvas.style.zIndex || '0') + 1 + ''
this._viewer?.setDirty()
}
setDirty() { // for enable/disable functions
this.refreshUi()
}
dispose() {
for (const target of this.targetBlocks) {
this.removeGeometry(target.target)
}
super.dispose()
}
}