UNPKG

@pmndrs/uikit

Version:

Build performant 3D user interfaces with Three.js and yoga.

165 lines (164 loc) 6.73 kB
import { signal } from '@preact/signals-core'; import { Matrix4 } from 'three'; import { defaultClippingData } from '../clipping.js'; import { abortableEffect } from '../utils.js'; import { setupImmediateProperties } from '../properties/immediate.js'; export function setupInstancedPanel(propertiesSignal, orderInfo, panelGroupDependencies, panelGroupManager, matrix, size, offset, borderInset, clippingRect, isVisible, materialConfig, abortSignal) { abortableEffect(() => { if (orderInfo.value == null) { return; } const innerAbortController = new AbortController(); const group = panelGroupManager.getGroup(orderInfo.value.majorIndex, panelGroupDependencies.value); new InstancedPanel(propertiesSignal, group, orderInfo.value.minorIndex, matrix, size, offset, borderInset, clippingRect, isVisible, materialConfig, innerAbortController.signal); return () => innerAbortController.abort(); }, abortSignal); } const matrixHelper1 = new Matrix4(); const matrixHelper2 = new Matrix4(); export class InstancedPanel { group; minorIndex; matrix; size; offset; borderInset; clippingRect; materialConfig; indexInBucket; bucket; insertedIntoGroup = false; active = signal(false); abortController; constructor(propertiesSignal, group, minorIndex, matrix, size, offset, borderInset, clippingRect, isVisible, materialConfig, abortSignal) { this.group = group; this.minorIndex = minorIndex; this.matrix = matrix; this.size = size; this.offset = offset; this.borderInset = borderInset; this.clippingRect = clippingRect; this.materialConfig = materialConfig; const setters = materialConfig.setters; setupImmediateProperties(propertiesSignal, this.active, materialConfig.hasProperty, (key, value) => { const index = this.getIndexInBuffer(); if (index == null) { return; } const { instanceData, instanceDataOnUpdate: instanceDataAddUpdateRange, root } = this.group; setters[key](instanceData.array, instanceData.itemSize * index, value, size, instanceDataAddUpdateRange); root.requestRender(); }, abortSignal); const isPanelVisible = materialConfig.computedIsVisibile(propertiesSignal, borderInset, size, isVisible); abortableEffect(() => { if (isPanelVisible.value) { this.requestShow(); return; } this.hide(); }, abortSignal); abortSignal.addEventListener('abort', () => this.hide()); } setIndexInBucket(index) { this.indexInBucket = index; } getIndexInBuffer() { if (this.bucket == null || this.indexInBucket == null) { return undefined; } return this.bucket.offset + this.indexInBucket; } activate(bucket, index) { this.bucket = bucket; this.indexInBucket = index; this.active.value = true; this.abortController = new AbortController(); abortableEffect(() => { if (this.matrix.value == null || this.size.value == null) { return; } const index = this.getIndexInBuffer(); if (index == null) { return; } const arrayIndex = index * 16; const [width, height] = this.size.value; const pixelSize = this.group.root.pixelSize.value; matrixHelper1.makeScale(width * pixelSize, height * pixelSize, 1); if (this.offset != null) { const [x, y] = this.offset.value; matrixHelper1.premultiply(matrixHelper2.makeTranslation(x * pixelSize, y * pixelSize, 0)); } matrixHelper1.premultiply(this.matrix.value); const { instanceMatrix, root } = this.group; matrixHelper1.toArray(instanceMatrix.array, arrayIndex); instanceMatrix.addUpdateRange(arrayIndex, 16); instanceMatrix.needsUpdate = true; root.requestRender(); }, this.abortController.signal); abortableEffect(() => { const index = this.getIndexInBuffer(); if (index == null || this.size.value == null) { return; } const [width, height] = this.size.value; const { instanceData, root } = this.group; const { array } = instanceData; const bufferIndex = index * 16 + 13; array[bufferIndex] = width; array[bufferIndex + 1] = height; instanceData.addUpdateRange(bufferIndex, 2); instanceData.needsUpdate = true; root.requestRender(); }, this.abortController.signal); abortableEffect(() => { const index = this.getIndexInBuffer(); if (index == null || this.borderInset.value == null) { return; } const { instanceData, root } = this.group; const offset = index * 16 + 0; instanceData.array.set(this.borderInset.value, offset); instanceData.addUpdateRange(offset, 4); instanceData.needsUpdate = true; root.requestRender(); }, this.abortController.signal), abortableEffect(() => { const index = this.getIndexInBuffer(); if (index == null) { return; } const { instanceClipping, root } = this.group; const offset = index * 16; const clipping = this.clippingRect?.value; if (clipping != null) { clipping.toArray(instanceClipping.array, offset); } else { instanceClipping.array.set(defaultClippingData, offset); } instanceClipping.addUpdateRange(offset, 16); instanceClipping.needsUpdate = true; root.requestRender(); }, this.abortController.signal); } requestShow() { if (this.insertedIntoGroup) { return; } this.insertedIntoGroup = true; this.group.insert(this.minorIndex, this); } hide() { if (!this.insertedIntoGroup) { return; } this.active.value = false; this.group.delete(this.minorIndex, this.indexInBucket, this); this.insertedIntoGroup = false; this.bucket = undefined; this.indexInBucket = undefined; this.abortController?.abort(); this.abortController = undefined; } }