@pmndrs/uikit
Version:
Build performant 3D user interfaces with Three.js and yoga.
165 lines (164 loc) • 6.73 kB
JavaScript
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;
}
}