| upgradeTexture, | upgradeTexture, | ||||
| WebGLRenderTarget, | WebGLRenderTarget, | ||||
| SVGTextureLoader, | SVGTextureLoader, | ||||
| uploadFile, | |||||
| } from 'threepipe' | } from 'threepipe' | ||||
| import type {UiObjectConfig} from 'uiconfig.js' | import type {UiObjectConfig} from 'uiconfig.js' | ||||
| import {TweakpaneUiPlugin} from './TweakpaneUiPlugin' | import {TweakpaneUiPlugin} from './TweakpaneUiPlugin' | ||||
| tempMap: {} as any, | tempMap: {} as any, | ||||
| } | } | ||||
| const allowedImageExtensions = ['.jpg', '.png', '.svg', '.hdr', '.ktx2', | |||||
| '.exr', /* '.mp4', '.ogg', '.mov',*/ '.jpeg', | |||||
| '.bmp', '.gif', '.webp', '.cube', '.ktx2', '.avif', '.ico', '.tiff'] // todo update blueprint editor with this list | |||||
| function proxyGetValue(cc: any, viewer: ThreeViewer, config: UiObjectConfig) { | function proxyGetValue(cc: any, viewer: ThreeViewer, config: UiObjectConfig) { | ||||
| if (cc?.get) cc = cc.get() | if (cc?.get) cc = cc.get() | ||||
| let ret = staticData.placeholderVal | let ret = staticData.placeholderVal | ||||
| config.uiRefresh?.(false, 'postFrame') | config.uiRefresh?.(false, 'postFrame') | ||||
| } | } | ||||
| function setterFile(viewer: ThreeViewer, file: File, path: string | undefined, config: UiObjectConfig, renderer: TweakpaneUiPlugin) { | |||||
| path = path || file.webkitRelativePath || file.name | |||||
| viewer.assetManager.importer.importSingle<ITexture>({file, path: path}).then(texture => { | |||||
| if (!texture) { | |||||
| console.warn('Failed to load texture', file) | |||||
| return | |||||
| } | |||||
| const ext = path?.split('?')?.[0]?.split('.').pop() ?? '' | |||||
| if ((texture as any).userData) { // todo why is this required? | |||||
| if (!(texture as any).userData.mimeType) | |||||
| (texture as any).userData.mimeType = 'image/' + (['jpg', 'jpeg'].includes(ext) ? 'jpeg' : 'png') | |||||
| } | |||||
| setterTex(texture, config, renderer) | |||||
| }) | |||||
| } | |||||
| function proxySetValue(v: any, cc: any, config: UiObjectConfig, viewer: ThreeViewer, renderer: TweakpaneUiPlugin) { | function proxySetValue(v: any, cc: any, config: UiObjectConfig, viewer: ThreeViewer, renderer: TweakpaneUiPlugin) { | ||||
| if (typeof v === 'string') { | if (typeof v === 'string') { | ||||
| if (typeof cc === 'string') setterTex(v, config, renderer) | if (typeof cc === 'string') setterTex(v, config, renderer) | ||||
| )) return | )) return | ||||
| if (v instanceof File) { // v.src must be from createObjectURL. | if (v instanceof File) { // v.src must be from createObjectURL. | ||||
| viewer.assetManager.importer.importSingle<ITexture>({file: v, path: (v as any).src}).then(texture => { | |||||
| if (!texture) return | |||||
| if (texture.isDataTexture) texture.needsUpdate = true | |||||
| const ext = (v as any).src?.split('?')?.[0]?.split('.').pop() | |||||
| if ((texture as any).userData) { | |||||
| if (!(texture as any).userData.mimeType) | |||||
| (texture as any).userData.mimeType = 'image/' + (['jpg', 'jpeg'].includes(ext) ? 'jpeg' : 'png') | |||||
| } | |||||
| setterTex(texture, config, renderer) | |||||
| }) | |||||
| setterFile(viewer, v, (v as any).src, config, renderer) | |||||
| } else if (v.isTexture) { | } else if (v.isTexture) { | ||||
| setterTex(v, config, renderer) | setterTex(v, config, renderer) | ||||
| } else { // HTMLImageElement, ImageBitmap, HTMLVideoElement | } else { // HTMLImageElement, ImageBitmap, HTMLVideoElement | ||||
| tex.assetType = 'texture' | tex.assetType = 'texture' | ||||
| tex.needsUpdate = true | tex.needsUpdate = true | ||||
| // set userData.mimeType for GLTFExporter | // set userData.mimeType for GLTFExporter | ||||
| const ext = v.src?.split('?')?.[0]?.split('.').pop() | |||||
| const ext = v.src?.split('?')?.[0]?.split('.').pop() ?? '' | |||||
| if (!tex.userData.mimeType) | if (!tex.userData.mimeType) | ||||
| tex.userData.mimeType = 'image/' + (['jpg', 'jpeg'].includes(ext) ? 'jpeg' : 'png') | tex.userData.mimeType = 'image/' + (['jpg', 'jpeg'].includes(ext) ? 'jpeg' : 'png') | ||||
| setterTex(tex, config, renderer) | setterTex(tex, config, renderer) | ||||
| } else { | } else { | ||||
| url = url.trim() | url = url.trim() | ||||
| } | } | ||||
| const cc = config.__proxy.value_ | |||||
| const isStr = typeof cc === 'string' | |||||
| const last = config.__proxy.value_ | |||||
| const isStr = typeof last === 'string' | |||||
| if (isStr) { | if (isStr) { | ||||
| setterTex(url, config, renderer) | setterTex(url, config, renderer) | ||||
| } else { // texture | } else { // texture | ||||
| } | } | ||||
| } | } | ||||
| async function imageFromFile(renderer: TweakpaneUiPlugin, config: UiObjectConfig, viewer: ThreeViewer, inp: HTMLInputElement, params: any) { | |||||
| const last = config.__proxy.value_ | |||||
| const isStr = typeof last === 'string' | |||||
| if (isStr) { | |||||
| inp.click() | |||||
| return | |||||
| } | |||||
| const files = await uploadFile(false, false, params.extensions?.map((ext: string) => `image/${ext.replace(/^\./, '')}`).join(', ') ?? 'image/*') | |||||
| if (!files.length) return | |||||
| const file = files[0] | |||||
| setterFile(viewer, file, undefined, config, renderer) | |||||
| } | |||||
| export const tpImageInputGenerator: (viewer: ThreeViewer) => (parent: any, config: UiObjectConfig, renderer: TweakpaneUiPlugin, params?: any) => any = (viewer: ThreeViewer) => (parent: any /* FolderApi */, config: UiObjectConfig, renderer: TweakpaneUiPlugin, params?: any) => { | export const tpImageInputGenerator: (viewer: ThreeViewer) => (parent: any, config: UiObjectConfig, renderer: TweakpaneUiPlugin, params?: any) => any = (viewer: ThreeViewer) => (parent: any /* FolderApi */, config: UiObjectConfig, renderer: TweakpaneUiPlugin, params?: any) => { | ||||
| // if (config.value !== undefined) throw 'Not supported yet' | // if (config.value !== undefined) throw 'Not supported yet' | ||||
| config.__proxy.value_ = renderer.methods.getRawValue(config) | config.__proxy.value_ = renderer.methods.getRawValue(config) | ||||
| params = params ?? {} | params = params ?? {} | ||||
| params.extensions = ['.jpg', '.png', '.svg', '.hdr', '.ktx2', | |||||
| '.exr', /* '.mp4', '.ogg', '.mov',*/ '.jpeg', | |||||
| '.bmp', '.gif', '.webp', '.cube', '.ktx2', '.avif', '.ico', '.tiff'] // todo update blueprint editor with this list | |||||
| params.extensions = allowedImageExtensions | |||||
| if (typeof params.imageFit === 'undefined') params.imageFit = 'contain' | if (typeof params.imageFit === 'undefined') params.imageFit = 'contain' | ||||
| if (typeof params.clickCallback === 'undefined') params.clickCallback = (ev: MouseEvent, inp: HTMLInputElement) => { | if (typeof params.clickCallback === 'undefined') params.clickCallback = (ev: MouseEvent, inp: HTMLInputElement) => { | ||||
| const target = ev?.target as HTMLElement | const target = ev?.target as HTMLElement | ||||
| ['remove image']: () => removeImage(config, renderer), | ['remove image']: () => removeImage(config, renderer), | ||||
| }) | }) | ||||
| if (!readOnly) Object.assign(items, { | if (!readOnly) Object.assign(items, { | ||||
| ['set/replace image']: () => inp.click(), | |||||
| ['set/replace image']: async() => imageFromFile(renderer, config, viewer, inp, params), | |||||
| ['from url']: async() => imageFromUrl(renderer, config, viewer), | ['from url']: async() => imageFromUrl(renderer, config, viewer), | ||||
| }) | }) | ||||
| const menu = CustomContextMenu.Create({ | const menu = CustomContextMenu.Create({ |