/** * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose */ import { Sphere3D } from '../../mol-math/geometry'; import { Vec3, Mat4 } from '../../mol-math/linear-algebra'; import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper'; export function calculateTextureInfo (n: number, itemSize: number) { n = Math.max(n, 2); // observed issues with 1 pixel textures const sqN = Math.sqrt(n); let width = Math.ceil(sqN); width = width + (itemSize - (width % itemSize)) % itemSize; const height = width > 0 ? Math.ceil(n / width) : 0; return { width, height, length: width * height * itemSize }; } export interface TextureImage { readonly array: T readonly width: number readonly height: number readonly flipY?: boolean } export interface TextureVolume { readonly array: T readonly width: number readonly height: number readonly depth: number } export function createTextureImage(n: number, itemSize: number, arrayCtor: new (length: number) => T, array?: T): TextureImage { const { length, width, height } = calculateTextureInfo(n, itemSize); array = array && array.length >= length ? array : new arrayCtor(length); return { array, width, height }; } export function printTextureImage(textureImage: TextureImage, scale = 1) { const { array, width, height } = textureImage; const itemSize = array.length / (width * height); const data = new Uint8ClampedArray(width * height * 4); if (itemSize === 1) { for (let y = 0; y < height; ++y) { for (let x = 0; x < width; ++x) { data[(y * width + x) * 4 + 3] = array[y * width + x]; } } } else if (itemSize === 4) { data.set(array); } else { console.warn(`itemSize '${itemSize}' not supported`); } return printImageData(new ImageData(data, width, height), scale); } export function printImageData(imageData: ImageData, scale = 1, pixelated = false) { const canvas = document.createElement('canvas'); canvas.width = imageData.width; canvas.height = imageData.height; const ctx = canvas.getContext('2d'); if (!ctx) throw new Error('Could not create canvas 2d context'); ctx.putImageData(imageData, 0, 0); canvas.toBlob(imgBlob => { const objectURL = window.URL.createObjectURL(imgBlob); const img = document.createElement('img'); img.src = objectURL; img.style.width = imageData.width * scale + 'px'; img.style.height = imageData.height * scale + 'px'; if (pixelated) { // not supported in Firefox and IE img.style.imageRendering = 'pixelated'; } img.style.position = 'absolute'; img.style.top = '0px'; img.style.left = '0px'; img.style.border = 'solid grey'; img.style.pointerEvents = 'none'; document.body.appendChild(img); }, 'image/png'); } // const v = Vec3(); const boundaryHelperCoarse = new BoundaryHelper('14'); const boundaryHelperFine = new BoundaryHelper('98'); function getHelper(count: number) { return count > 100_000 ? boundaryHelperCoarse : boundaryHelperFine; } export function calculateInvariantBoundingSphere(position: Float32Array, positionCount: number, stepFactor: number): Sphere3D { const step = stepFactor * 3; const boundaryHelper = getHelper(positionCount); boundaryHelper.reset(); for (let i = 0, _i = positionCount * 3; i < _i; i += step) { Vec3.fromArray(v, position, i); boundaryHelper.includePosition(v); } boundaryHelper.finishedIncludeStep(); for (let i = 0, _i = positionCount * 3; i < _i; i += step) { Vec3.fromArray(v, position, i); boundaryHelper.radiusPosition(v); } const sphere = boundaryHelper.getSphere(); if (positionCount <= 98) { const extrema: Vec3[] = []; for (let i = 0, _i = positionCount * 3; i < _i; i += step) { extrema.push(Vec3.fromArray(Vec3(), position, i)); } Sphere3D.setExtrema(sphere, extrema); } return sphere; } const _mat4 = Mat4(); export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere3D, transform: Float32Array, transformCount: number): Sphere3D { if (transformCount === 1) { Mat4.fromArray(_mat4, transform, 0); const s = Sphere3D.clone(invariantBoundingSphere); return Mat4.isIdentity(_mat4) ? s : Sphere3D.transform(s, s, _mat4); } const boundaryHelper = getHelper(transformCount); boundaryHelper.reset(); const { center, radius, extrema } = invariantBoundingSphere; // only use extrema if there are not too many transforms if (extrema && transformCount < 50) { for (let i = 0, _i = transformCount; i < _i; ++i) { for (const e of extrema) { Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16); boundaryHelper.includePosition(v); } } boundaryHelper.finishedIncludeStep(); for (let i = 0, _i = transformCount; i < _i; ++i) { for (const e of extrema) { Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16); boundaryHelper.radiusPosition(v); } } } else { for (let i = 0, _i = transformCount; i < _i; ++i) { Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16); boundaryHelper.includePositionRadius(v, radius); } boundaryHelper.finishedIncludeStep(); for (let i = 0, _i = transformCount; i < _i; ++i) { Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16); boundaryHelper.radiusPositionRadius(v, radius); } } return boundaryHelper.getSphere(); } export function calculateBoundingSphere(position: Float32Array, positionCount: number, transform: Float32Array, transformCount: number, padding = 0, stepFactor = 1): { boundingSphere: Sphere3D, invariantBoundingSphere: Sphere3D } { const invariantBoundingSphere = calculateInvariantBoundingSphere(position, positionCount, stepFactor); const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform, transformCount); Sphere3D.expand(boundingSphere, boundingSphere, padding); Sphere3D.expand(invariantBoundingSphere, invariantBoundingSphere, padding); return { boundingSphere, invariantBoundingSphere }; }